From 4c412afec62957ccd26c91817373b7ead5c404e4 Mon Sep 17 00:00:00 2001 From: skyjake Date: Fri, 13 Sep 2013 16:52:57 +0300 Subject: [PATCH] Refactor|UI|Client: Dialog action button placement to opposite edge Dialog action buttons are now placed in a separate menu on the opposite side of the dialog. However, there is only a single buttons data model for a dialog, shared by both widgets. The Gear button in UpdateAvailableDialog is now displayed using this mechanism. --- doomsday/client/client.pro | 6 +- doomsday/client/include/ChildWidgetOrganizer | 1 + .../client/include/ContextWidgetOrganizer | 1 - ...dgetorganizer.h => childwidgetorganizer.h} | 45 ++++- doomsday/client/include/ui/framework/data.h | 2 +- .../include/ui/framework/guiwidgetprivate.h | 48 +++++- .../client/include/ui/widgets/dialogwidget.h | 29 +++- .../client/include/ui/widgets/menuwidget.h | 7 +- .../client/src/ui/dialogs/aboutdialog.cpp | 6 +- .../src/ui/dialogs/audiosettingsdialog.cpp | 2 +- .../src/ui/dialogs/coloradjustmentdialog.cpp | 2 +- .../client/src/ui/dialogs/inputdialog.cpp | 2 +- .../src/ui/dialogs/inputsettingsdialog.cpp | 2 +- .../src/ui/dialogs/networksettingsdialog.cpp | 2 +- .../src/ui/dialogs/renderersettingsdialog.cpp | 5 +- .../src/ui/dialogs/videosettingsdialog.cpp | 2 +- ...organizer.cpp => childwidgetorganizer.cpp} | 58 +++++-- .../client/src/ui/framework/guirootwidget.cpp | 3 + .../client/src/ui/widgets/choicewidget.cpp | 4 +- .../client/src/ui/widgets/dialogwidget.cpp | 160 +++++++++++++----- .../client/src/ui/widgets/documentwidget.cpp | 1 - .../src/ui/widgets/gameselectionwidget.cpp | 2 +- doomsday/client/src/ui/widgets/logwidget.cpp | 1 - doomsday/client/src/ui/widgets/menuwidget.cpp | 13 +- .../client/src/ui/widgets/popupmenuwidget.cpp | 6 +- .../src/ui/widgets/profilepickerwidget.cpp | 6 +- .../client/src/updater/downloaddialog.cpp | 10 +- .../src/updater/updateavailabledialog.cpp | 31 ++-- .../src/updater/updatersettingsdialog.cpp | 4 +- doomsday/libdeng2/include/de/data/observers.h | 8 +- 30 files changed, 332 insertions(+), 137 deletions(-) create mode 100644 doomsday/client/include/ChildWidgetOrganizer delete mode 100644 doomsday/client/include/ContextWidgetOrganizer rename doomsday/client/include/ui/framework/{contextwidgetorganizer.h => childwidgetorganizer.h} (77%) rename doomsday/client/src/ui/framework/{contextwidgetorganizer.cpp => childwidgetorganizer.cpp} (81%) diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index 7f3d1ab7b7..ee1e46b251 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -118,7 +118,7 @@ DENG_CONVENIENCE_HEADERS += \ include/BspLeaf \ include/BspNode \ include/CommandAction \ - include/ContextWidgetOrganizer \ + include/ChildWidgetOrganizer \ include/Decoration \ include/DialogContentStylist \ include/EntityDatabase \ @@ -376,7 +376,7 @@ DENG_HEADERS += \ include/ui/framework/actionitem.h \ include/ui/framework/atlasproceduralimage.h \ include/ui/framework/commandaction.h \ - include/ui/framework/contextwidgetorganizer.h \ + include/ui/framework/childwidgetorganizer.h \ include/ui/framework/data.h \ include/ui/framework/dialogcontentstylist.h \ include/ui/framework/fontlinewrapping.h \ @@ -722,7 +722,7 @@ SOURCES += \ src/ui/fi_main.cpp \ src/ui/finaleinterpreter.cpp \ src/ui/framework/commandaction.cpp \ - src/ui/framework/contextwidgetorganizer.cpp \ + src/ui/framework/childwidgetorganizer.cpp \ src/ui/framework/data.cpp \ src/ui/framework/dialogcontentstylist.cpp \ src/ui/framework/fontlinewrapping.cpp \ diff --git a/doomsday/client/include/ChildWidgetOrganizer b/doomsday/client/include/ChildWidgetOrganizer new file mode 100644 index 0000000000..1292fc0784 --- /dev/null +++ b/doomsday/client/include/ChildWidgetOrganizer @@ -0,0 +1 @@ +#include "ui/framework/childwidgetorganizer.h" diff --git a/doomsday/client/include/ContextWidgetOrganizer b/doomsday/client/include/ContextWidgetOrganizer deleted file mode 100644 index 9a3c5d2aa8..0000000000 --- a/doomsday/client/include/ContextWidgetOrganizer +++ /dev/null @@ -1 +0,0 @@ -#include "ui/framework/contextwidgetorganizer.h" diff --git a/doomsday/client/include/ui/framework/contextwidgetorganizer.h b/doomsday/client/include/ui/framework/childwidgetorganizer.h similarity index 77% rename from doomsday/client/include/ui/framework/contextwidgetorganizer.h rename to doomsday/client/include/ui/framework/childwidgetorganizer.h index abc4869dda..0e34de6e9a 100644 --- a/doomsday/client/include/ui/framework/contextwidgetorganizer.h +++ b/doomsday/client/include/ui/framework/childwidgetorganizer.h @@ -1,4 +1,4 @@ -/** @file contextwidgetorganizer.h Organizes widgets according to a UI context. +/** @file childwidgetorganizer.h Organizes widgets according to a UI context. * * @authors Copyright (c) 2013 Jaakko Keränen * @@ -16,8 +16,8 @@ * http://www.gnu.org/licenses */ -#ifndef DENG_CLIENT_CONTEXTWIDGETORGANIZER_H -#define DENG_CLIENT_CONTEXTWIDGETORGANIZER_H +#ifndef DENG_CLIENT_CHILDWIDGETORGANIZER_H +#define DENG_CLIENT_CHILDWIDGETORGANIZER_H #include "data.h" #include "guiwidget.h" @@ -29,7 +29,7 @@ * change, and reordering them when the items' order changes. * * The concrete task of creating widgets is done by an object that implements - * the ContextWidgetOrganizer::IWidgetFactory interface. Also, third parties + * the ChildWidgetOrganizer::IWidgetFactory interface. Also, third parties * may observe widget creation and updates and alter the widget as they choose. * * @todo Virtualization: it is not required that all the items of the context @@ -37,7 +37,7 @@ * large numbers of items, virtualization should be applied to keep only a * subset/range of items present as widgets. */ -class ContextWidgetOrganizer +class ChildWidgetOrganizer { public: /** @@ -71,6 +71,28 @@ class ContextWidgetOrganizer virtual void updateItemWidget(GuiWidget &widget, ui::Item const &item) = 0; }; + /** + * Filters out data items. + */ + class IFilter + { + public: + virtual ~IFilter() {} + + /** + * Determines whether an item should be ignored by the organizer. + * + * @param organizer Which organizer is asking the question. + * @param data Data context. + * @param pos Position of the item in the context. + * + * @return @c true to accept item, @c false to ignore it. + */ + virtual bool isItemAccepted(ChildWidgetOrganizer const &organizer, + ui::Data const &data, + ui::Data::Pos pos) const = 0; + }; + /** * Notified when the organizer creates a widget for a context item. Allows * third parties to customize the widget as needed. @@ -88,7 +110,7 @@ class ContextWidgetOrganizer ui::Item const &item)) public: - ContextWidgetOrganizer(GuiWidget &container); + ChildWidgetOrganizer(GuiWidget &container); /** * Sets the object responsible for creating widgets for this organizer. The @@ -101,6 +123,13 @@ class ContextWidgetOrganizer IWidgetFactory &widgetFactory() const; + /** + * Sets the object that decides whether items are accepted or ignored. + * + * @param filter Filtering object. + */ + void setFilter(IFilter const &filter); + /** * Sets the data context of the organizer. If there was a previous context, * all widgets created for it are deleted from the container. The widgets @@ -126,11 +155,11 @@ class ContextWidgetOrganizer * Simple widget factory that creates label widgets with their default * settings, using the label from the ui::Item. */ -class DefaultWidgetFactory : public ContextWidgetOrganizer::IWidgetFactory +class DefaultWidgetFactory : public ChildWidgetOrganizer::IWidgetFactory { public: GuiWidget *makeItemWidget(ui::Item const &item, GuiWidget const *parent); void updateItemWidget(GuiWidget &widget, ui::Item const &item); }; -#endif // DENG_CLIENT_CONTEXTWIDGETORGANIZER_H +#endif // DENG_CLIENT_CHILDWIDGETORGANIZER_H diff --git a/doomsday/client/include/ui/framework/data.h b/doomsday/client/include/ui/framework/data.h index e067168d93..fbd3799026 100644 --- a/doomsday/client/include/ui/framework/data.h +++ b/doomsday/client/include/ui/framework/data.h @@ -37,7 +37,7 @@ class Item; * * Data has ownership of all the items in it. * - * @see ContextWidgetOrganizer + * @see ChildWidgetOrganizer */ class Data { diff --git a/doomsday/client/include/ui/framework/guiwidgetprivate.h b/doomsday/client/include/ui/framework/guiwidgetprivate.h index e28e024701..477e572c32 100644 --- a/doomsday/client/include/ui/framework/guiwidgetprivate.h +++ b/doomsday/client/include/ui/framework/guiwidgetprivate.h @@ -13,20 +13,36 @@ class Style; * used (i.e., glInit() and glDeinit() are being called). * * Use DENG_GUI_PIMPL() instead of the DENG2_PIMPL() macro. + * + * Note that GuiWidgetPrivate automatically observes the root widget's atlas + * content repositioning, so derived private implementations can just override + * the observer method if necessary. */ template -class GuiWidgetPrivate : public de::Private +class GuiWidgetPrivate : public de::Private, + DENG2_OBSERVES(de::Atlas, Reposition) { public: typedef GuiWidgetPrivate Base; // shadows de::Private<>::Base public: - GuiWidgetPrivate(PublicType &i) : de::Private(i) {} + GuiWidgetPrivate(PublicType &i) + : de::Private(i), + _observingAtlas(0) + {} - GuiWidgetPrivate(PublicType *i) : de::Private(i) {} + GuiWidgetPrivate(PublicType *i) + : de::Private(i), + _observingAtlas(0) + {} virtual ~GuiWidgetPrivate() { + if(_observingAtlas) + { + _observingAtlas->audienceForReposition -= this; + } + /** * Ensure that the derived's class's glDeinit() method has been * called before the private class instance is destroyed. At least @@ -38,6 +54,16 @@ class GuiWidgetPrivate : public de::Private DENG2_ASSERT(!Base::self.isInitialized()); } + void observeRootAtlas() const + { + if(!_observingAtlas) + { + // Automatically start observing the root atlas. + _observingAtlas = &root().atlas(); + _observingAtlas->audienceForReposition += this; + } + } + bool hasRoot() const { return Base::self.hasRoot(); @@ -50,12 +76,14 @@ class GuiWidgetPrivate : public de::Private } de::AtlasTexture &atlas() const - { + { + observeRootAtlas(); return root().atlas(); } de::GLUniform &uAtlas() const { + observeRootAtlas(); return root().uAtlas(); } @@ -68,6 +96,18 @@ class GuiWidgetPrivate : public de::Private { return Base::self.style(); } + + void atlasContentRepositioned(de::Atlas &atlas) + { + if(_observingAtlas == &atlas) + { + // Make sure the new texture coordinates get used by the widget. + Base::self.requestGeometry(); + } + } + +private: + mutable de::Atlas *_observingAtlas; }; #define DENG_GUI_PIMPL(ClassName) \ diff --git a/doomsday/client/include/ui/widgets/dialogwidget.h b/doomsday/client/include/ui/widgets/dialogwidget.h index 0469dc4a44..0650d23bdc 100644 --- a/doomsday/client/include/ui/widgets/dialogwidget.h +++ b/doomsday/client/include/ui/widgets/dialogwidget.h @@ -49,6 +49,7 @@ class GuiRootWidget; * +- heading (LabelWidget; optional) * +- area (ScrollAreaWidget; contains actual dialog widgets) * +- buttons (MenuWidget) + * +- extra (MenuWidget; might be empty) * * * Scrolling is set up so that the dialog height doesn't surpass the view @@ -82,7 +83,13 @@ class DialogWidget : public PopupWidget Reject = 0x4, Yes = 0x8, No = 0x10, - Action = 0x20 + Action = 0x20, + + IdMask = 0xff0000, + Id1 = 0x010000, + Id2 = 0x020000, + Id3 = 0x030000, + Id4 = 0x040000 }; Q_DECLARE_FLAGS(RoleFlags, RoleFlag) @@ -109,6 +116,8 @@ class DialogWidget : public PopupWidget */ ButtonItem(RoleFlags flags, de::String const &label, de::Action *action); + ButtonItem(RoleFlags flags, de::Image const &image, de::Action *action); + RoleFlags role() const { return _role; } private: @@ -128,7 +137,23 @@ class DialogWidget : public PopupWidget ScrollAreaWidget &area(); - MenuWidget &buttons(); + //MenuWidget &buttons(); + + /** + * Additional buttons of the dialog, laid out opposite to the normal dialog + * buttons. These are used for functionality related to the dialog's content, + * but they don't accept or reject the dialog. For instance, shortcut for + * settings, showing what's new in an update, etc. + * + * @return Widget for dialog's extra buttons. + */ + //MenuWidget &extraButtons(); + + ui::Data &buttons(); + + ButtonWidget &buttonWidget(de::String const &label) const; + + ButtonWidget *buttonWidget(int roleId) const; /** * Shows the dialog and blocks execution until the dialog is closed -- diff --git a/doomsday/client/include/ui/widgets/menuwidget.h b/doomsday/client/include/ui/widgets/menuwidget.h index 10c35fac06..bbc39d7067 100644 --- a/doomsday/client/include/ui/widgets/menuwidget.h +++ b/doomsday/client/include/ui/widgets/menuwidget.h @@ -20,7 +20,7 @@ #define DENG_CLIENT_MENUWIDGET_H #include "ui/Data" -#include "ContextWidgetOrganizer" +#include "ChildWidgetOrganizer" #include "ui/ActionItem" #include "ui/SubmenuItem" #include "ui/VariableToggleItem" @@ -39,7 +39,7 @@ * MenuWidget::ISortOrder. Sorting affects layout only, not the actual order of * the children. * - * MenuWidget uses a ContextWidgetOrganizer to create widgets based on the + * MenuWidget uses a ChildWidgetOrganizer to create widgets based on the * provided menu items. The organizer can be queried to find widgets matching * specific items. */ @@ -87,7 +87,8 @@ class MenuWidget : public ScrollAreaWidget */ void setItems(ui::Data const &items); - ContextWidgetOrganizer const &organizer() const; + ChildWidgetOrganizer &organizer(); + ChildWidgetOrganizer const &organizer() const; /** * Returns the number of visible items in the menu. Hidden items are not diff --git a/doomsday/client/src/ui/dialogs/aboutdialog.cpp b/doomsday/client/src/ui/dialogs/aboutdialog.cpp index 33281df2b7..49be0fca93 100644 --- a/doomsday/client/src/ui/dialogs/aboutdialog.cpp +++ b/doomsday/client/src/ui/dialogs/aboutdialog.cpp @@ -113,14 +113,14 @@ AboutDialog::AboutDialog() : DialogWidget("about"), d(new Instance(this)) // Total size of the dialog's content. area().setContentSize(layout.width(), layout.height() + homepage->rule().height()); - buttons().items() + buttons() << new DialogButtonItem(DialogWidget::Accept | DialogWidget::Default, tr("Close")) << new DialogButtonItem(DialogWidget::Action, tr("GL"), new SignalAction(this, SLOT(showGLInfo()))) << new DialogButtonItem(DialogWidget::Action, tr("Audio"), new SignalAction(this, SLOT(showAudioInfo()))); // The popups are anchored to their button. - d->glPopup->setAnchorAndOpeningDirection(buttons().organizer().itemWidget(tr("GL"))->rule(), ui::Up); - d->audioPopup->setAnchorAndOpeningDirection(buttons().organizer().itemWidget(tr("Audio"))->rule(), ui::Up); + d->glPopup->setAnchorAndOpeningDirection(buttonWidget(tr("GL")).rule(), ui::Up); + d->audioPopup->setAnchorAndOpeningDirection(buttonWidget(tr("Audio")).rule(), ui::Up); } void AboutDialog::showGLInfo() diff --git a/doomsday/client/src/ui/dialogs/audiosettingsdialog.cpp b/doomsday/client/src/ui/dialogs/audiosettingsdialog.cpp index 4cb5606a63..3edad45b58 100644 --- a/doomsday/client/src/ui/dialogs/audiosettingsdialog.cpp +++ b/doomsday/client/src/ui/dialogs/audiosettingsdialog.cpp @@ -110,7 +110,7 @@ AudioSettingsDialog::AudioSettingsDialog(String const &name) area().setContentSize(layout.width(), layout.height()); - buttons().items() + buttons() << new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close")) << new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"), new SignalAction(this, SLOT(resetToDefaults()))); diff --git a/doomsday/client/src/ui/dialogs/coloradjustmentdialog.cpp b/doomsday/client/src/ui/dialogs/coloradjustmentdialog.cpp index e4a6d86a8b..2340dc5322 100644 --- a/doomsday/client/src/ui/dialogs/coloradjustmentdialog.cpp +++ b/doomsday/client/src/ui/dialogs/coloradjustmentdialog.cpp @@ -69,7 +69,7 @@ ColorAdjustmentDialog::ColorAdjustmentDialog(String const &name) : DialogWidget(name, WithHeading), d(new Instance(this)) { heading().setText(tr("Color Adjustments")); - buttons().items() + buttons() << new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close")) << new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"), new SignalAction(this, SLOT(resetToDefaults()))); diff --git a/doomsday/client/src/ui/dialogs/inputdialog.cpp b/doomsday/client/src/ui/dialogs/inputdialog.cpp index bc4eb98d34..97044051e1 100644 --- a/doomsday/client/src/ui/dialogs/inputdialog.cpp +++ b/doomsday/client/src/ui/dialogs/inputdialog.cpp @@ -33,7 +33,7 @@ InputDialog::InputDialog(String const &name) d->editor->setSignalOnEnter(true); connect(d->editor, SIGNAL(enterPressed(QString)), this, SLOT(accept())); - buttons().items() + buttons() << new DialogButtonItem(Default | Accept) << new DialogButtonItem(Reject); diff --git a/doomsday/client/src/ui/dialogs/inputsettingsdialog.cpp b/doomsday/client/src/ui/dialogs/inputsettingsdialog.cpp index 484a74b61f..077c15db75 100644 --- a/doomsday/client/src/ui/dialogs/inputsettingsdialog.cpp +++ b/doomsday/client/src/ui/dialogs/inputsettingsdialog.cpp @@ -137,7 +137,7 @@ InputSettingsDialog::InputSettingsDialog(String const &name) area().setContentSize(layout.width(), layout.height()); - buttons().items() + buttons() << new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close")) << new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"), new SignalAction(this, SLOT(resetToDefaults()))); diff --git a/doomsday/client/src/ui/dialogs/networksettingsdialog.cpp b/doomsday/client/src/ui/dialogs/networksettingsdialog.cpp index f2b3b3dc92..d2372c15e6 100644 --- a/doomsday/client/src/ui/dialogs/networksettingsdialog.cpp +++ b/doomsday/client/src/ui/dialogs/networksettingsdialog.cpp @@ -60,7 +60,7 @@ NetworkSettingsDialog::NetworkSettingsDialog(String const &name) area().setContentSize(layout.width(), layout.height()); - buttons().items() + buttons() << new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close")) << new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"), new SignalAction(this, SLOT(resetToDefaults()))); diff --git a/doomsday/client/src/ui/dialogs/renderersettingsdialog.cpp b/doomsday/client/src/ui/dialogs/renderersettingsdialog.cpp index 36dc1c9c4f..fb91d8eb07 100644 --- a/doomsday/client/src/ui/dialogs/renderersettingsdialog.cpp +++ b/doomsday/client/src/ui/dialogs/renderersettingsdialog.cpp @@ -187,7 +187,7 @@ RendererSettingsDialog::RendererSettingsDialog(String const &name) area().setContentSize(layout.width(), layout.height()); - buttons().items() + buttons() << new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept, tr("Close")) << new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"), new SignalAction(this, SLOT(resetToDefaults()))) @@ -195,8 +195,7 @@ RendererSettingsDialog::RendererSettingsDialog(String const &name) new SignalAction(this, SLOT(showDeveloperPopup()))); // Identifiers popup opens from the button. - d->devPopup->setAnchorAndOpeningDirection( - buttons().organizer().itemWidget(tr("Developer"))->rule(), ui::Up); + d->devPopup->setAnchorAndOpeningDirection(buttonWidget(tr("Developer")).rule(), ui::Up); connect(this, SIGNAL(closed()), d->devPopup, SLOT(close())); connect(d->appear, SIGNAL(profileEditorRequested()), this, SLOT(editProfile())); diff --git a/doomsday/client/src/ui/dialogs/videosettingsdialog.cpp b/doomsday/client/src/ui/dialogs/videosettingsdialog.cpp index af102b0fb8..a36df36a63 100644 --- a/doomsday/client/src/ui/dialogs/videosettingsdialog.cpp +++ b/doomsday/client/src/ui/dialogs/videosettingsdialog.cpp @@ -192,7 +192,7 @@ VideoSettingsDialog::VideoSettingsDialog(String const &name) << new ChoiceItem(tr("16-bit"), 16); #endif - buttons().items() + buttons() << new DialogButtonItem(DialogWidget::Accept | DialogWidget::Default, tr("Close")) << new DialogButtonItem(DialogWidget::Action, tr("Reset to Defaults"), new SignalAction(this, SLOT(resetToDefaults()))); diff --git a/doomsday/client/src/ui/framework/contextwidgetorganizer.cpp b/doomsday/client/src/ui/framework/childwidgetorganizer.cpp similarity index 81% rename from doomsday/client/src/ui/framework/contextwidgetorganizer.cpp rename to doomsday/client/src/ui/framework/childwidgetorganizer.cpp index efb0e2f346..27094b2c23 100644 --- a/doomsday/client/src/ui/framework/contextwidgetorganizer.cpp +++ b/doomsday/client/src/ui/framework/childwidgetorganizer.cpp @@ -1,4 +1,4 @@ -/** @file contextwidgetorganizer.cpp Organizes widgets according to a UI context. +/** @file childwidgetorganizer.cpp Organizes widgets according to a UI context. * * @authors Copyright (c) 2013 Jaakko Keränen * @@ -16,7 +16,7 @@ * http://www.gnu.org/licenses */ -#include "ContextWidgetOrganizer" +#include "ChildWidgetOrganizer" #include "ui/widgets/labelwidget.h" #include "ui/Item" @@ -26,16 +26,17 @@ using namespace de; static DefaultWidgetFactory defaultWidgetFactory; -DENG2_PIMPL(ContextWidgetOrganizer), -DENG2_OBSERVES(Widget, Deletion ), +DENG2_PIMPL(ChildWidgetOrganizer), +DENG2_OBSERVES(Widget, Deletion ), DENG2_OBSERVES(ui::Data, Addition ), DENG2_OBSERVES(ui::Data, Removal ), DENG2_OBSERVES(ui::Data, OrderChange), -DENG2_OBSERVES(ui::Item, Change ) +DENG2_OBSERVES(ui::Item, Change ) { GuiWidget *container; ui::Data const *context; IWidgetFactory *factory; + IFilter const *filter; typedef QMap Mapping; typedef QMutableMapIterator MutableMappingIterator; @@ -45,7 +46,8 @@ DENG2_OBSERVES(ui::Item, Change ) : Base(i), container(c), context(0), - factory(&defaultWidgetFactory) + factory(&defaultWidgetFactory), + filter(0) {} ~Instance() @@ -84,6 +86,15 @@ DENG2_OBSERVES(ui::Item, Change ) { DENG2_ASSERT(factory != 0); + if(filter) + { + if(!filter->isItemAccepted(self, *context, pos)) + { + // Skip this one. + return; + } + } + ui::Item const &item = context->at(pos); GuiWidget *w = factory->makeItemWidget(item, container); if(!w) return; // Unpresentable. @@ -184,14 +195,20 @@ DENG2_OBSERVES(ui::Item, Change ) } for(ui::Data::Pos i = 0; i < context->size(); ++i) { - DENG2_ASSERT(mapping.contains(&context->at(i))); - container->add(mapping[&context->at(i)]); + if(mapping.contains(&context->at(i))) + { + container->add(mapping[&context->at(i)]); + } } } void itemChanged(ui::Item const &item) { - DENG2_ASSERT(mapping.contains(&item)); + if(!mapping.contains(&item)) + { + // Not represented as a child widget. + return; + } GuiWidget &w = *mapping[&item]; factory->updateItemWidget(w, item); @@ -223,48 +240,53 @@ DENG2_OBSERVES(ui::Item, Change ) } }; -ContextWidgetOrganizer::ContextWidgetOrganizer(GuiWidget &container) +ChildWidgetOrganizer::ChildWidgetOrganizer(GuiWidget &container) : d(new Instance(this, &container)) {} -void ContextWidgetOrganizer::setContext(ui::Data const &context) +void ChildWidgetOrganizer::setContext(ui::Data const &context) { d->set(&context); } -void ContextWidgetOrganizer::unsetContext() +void ChildWidgetOrganizer::unsetContext() { d->set(0); } -ui::Data const &ContextWidgetOrganizer::context() const +ui::Data const &ChildWidgetOrganizer::context() const { DENG2_ASSERT(d->context != 0); return *d->context; } -GuiWidget *ContextWidgetOrganizer::itemWidget(ui::Data::Pos pos) const +GuiWidget *ChildWidgetOrganizer::itemWidget(ui::Data::Pos pos) const { return itemWidget(context().at(pos)); } -void ContextWidgetOrganizer::setWidgetFactory(IWidgetFactory &factory) +void ChildWidgetOrganizer::setWidgetFactory(IWidgetFactory &factory) { d->factory = &factory; } -ContextWidgetOrganizer::IWidgetFactory &ContextWidgetOrganizer::widgetFactory() const +ChildWidgetOrganizer::IWidgetFactory &ChildWidgetOrganizer::widgetFactory() const { DENG2_ASSERT(d->factory != 0); return *d->factory; } -GuiWidget *ContextWidgetOrganizer::itemWidget(ui::Item const &item) const +void ChildWidgetOrganizer::setFilter(ChildWidgetOrganizer::IFilter const &filter) +{ + d->filter = &filter; +} + +GuiWidget *ChildWidgetOrganizer::itemWidget(ui::Item const &item) const { return d->find(item); } -GuiWidget *ContextWidgetOrganizer::itemWidget(String const &label) const +GuiWidget *ChildWidgetOrganizer::itemWidget(String const &label) const { return d->findByLabel(label); } diff --git a/doomsday/client/src/ui/framework/guirootwidget.cpp b/doomsday/client/src/ui/framework/guirootwidget.cpp index 53701c2899..fe6a295799 100644 --- a/doomsday/client/src/ui/framework/guirootwidget.cpp +++ b/doomsday/client/src/ui/framework/guirootwidget.cpp @@ -60,6 +60,9 @@ DENG2_OBSERVES(Widget, ChildAddition) // class destructor will destroy all widgets, but this class governs // shared GL resources, so we'll ask the widgets to do this now. self.notifyTree(&Widget::deinitialize); + + // Destroy GUI widgets while the shared resources are still available. + self.clearTree(); } void initAtlas() diff --git a/doomsday/client/src/ui/widgets/choicewidget.cpp b/doomsday/client/src/ui/widgets/choicewidget.cpp index 2d5dabe4d4..a2d208e872 100644 --- a/doomsday/client/src/ui/widgets/choicewidget.cpp +++ b/doomsday/client/src/ui/widgets/choicewidget.cpp @@ -27,8 +27,8 @@ DENG_GUI_PIMPL(ChoiceWidget), DENG2_OBSERVES(Data, Addition), DENG2_OBSERVES(Data, Removal), DENG2_OBSERVES(Data, OrderChange), -DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation), -DENG2_OBSERVES(ContextWidgetOrganizer, WidgetUpdate) +DENG2_OBSERVES(ChildWidgetOrganizer, WidgetCreation), +DENG2_OBSERVES(ChildWidgetOrganizer, WidgetUpdate) { /** * Items in the choice's popup uses this as action to change the selected diff --git a/doomsday/client/src/ui/widgets/dialogwidget.cpp b/doomsday/client/src/ui/widgets/dialogwidget.cpp index df2821aa8e..32d3064129 100644 --- a/doomsday/client/src/ui/widgets/dialogwidget.cpp +++ b/doomsday/client/src/ui/widgets/dialogwidget.cpp @@ -46,30 +46,25 @@ static bool dialogButtonOrder(ui::Item const &a, ui::Item const &b) DialogButtonItem const &left = a.as(); DialogButtonItem const &right = b.as(); -#ifdef MACOSX - // Default buttons go to the end on OS X. if(!left.role().testFlag(DialogWidget::Default) && right.role().testFlag(DialogWidget::Default)) { +#ifdef MACOSX + // Default buttons go to the right on OS X. return true; +#else + // Default buttons to the left. + return false; +#endif } if(left.role().testFlag(DialogWidget::Default) && !right.role().testFlag(DialogWidget::Default)) { +#ifdef MACOSX + // Default buttons go to the right on OS X. return false; - } - - bool const actionsFirst = true; // OS X actions before other buttons. #else - bool const actionsFirst = false; // Actions after the regular buttons. + // Default buttons to the left. + return true; #endif - - // Action buttons appear before/after other buttons. - if(left.role().testFlag(DialogWidget::Action) && !right.role().testFlag(DialogWidget::Action)) - { - return actionsFirst; - } - if(!left.role().testFlag(DialogWidget::Action) && right.role().testFlag(DialogWidget::Action)) - { - return !actionsFirst; } // Order unchanged. @@ -77,16 +72,19 @@ static bool dialogButtonOrder(ui::Item const &a, ui::Item const &b) } DENG_GUI_PIMPL(DialogWidget), -DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation), -DENG2_OBSERVES(ContextWidgetOrganizer, WidgetUpdate), +DENG2_OBSERVES(ChildWidgetOrganizer, WidgetCreation), +DENG2_OBSERVES(ChildWidgetOrganizer, WidgetUpdate), DENG2_OBSERVES(ui::Data, Addition), -DENG2_OBSERVES(ui::Data, Removal) +DENG2_OBSERVES(ui::Data, Removal), +public ChildWidgetOrganizer::IFilter { Modality modality; Flags flags; ScrollAreaWidget *area; LabelWidget *heading; MenuWidget *buttons; + MenuWidget *extraButtons; + ui::ListData buttonItems; QEventLoop subloop; Animation glow; bool needButtonUpdate; @@ -113,14 +111,25 @@ DENG2_OBSERVES(ui::Data, Removal) area = new ScrollAreaWidget("area"); buttons = new MenuWidget("buttons"); + buttons->setItems(buttonItems); buttons->items().audienceForAddition += this; buttons->items().audienceForRemoval += this; buttons->organizer().audienceForWidgetCreation += this; buttons->organizer().audienceForWidgetUpdate += this; + buttons->organizer().setFilter(*this); + + extraButtons = new MenuWidget("extra"); + extraButtons->setItems(buttonItems); + extraButtons->items().audienceForAddition += this; + extraButtons->items().audienceForRemoval += this; + extraButtons->organizer().audienceForWidgetCreation += this; + extraButtons->organizer().audienceForWidgetUpdate += this; + extraButtons->organizer().setFilter(*this); // The menu maintains its own width and height based on children. // Set up one row with variable number of columns. buttons->setGridSize(0, ui::Expand, 1, ui::Expand); + extraButtons->setGridSize(0, ui::Expand, 1, ui::Expand); area->rule() .setInput(Rule::Left, self.rule().left()) @@ -150,20 +159,21 @@ DENG2_OBSERVES(ui::Data, Removal) area->rule().setInput(Rule::Top, heading->rule().bottom()); } - //if(!flags.testFlag(Buttonless)) - { - area->rule().setInput(Rule::Height, container->rule().height() - - buttons->rule().height() + area->margins().bottom()); + area->rule().setInput(Rule::Height, container->rule().height() - + buttons->rule().height() + area->margins().bottom()); - // Buttons below the area. - buttons->rule() - .setInput(Rule::Top, area->rule().bottom() - area->margins().bottom()) // overlap margins - .setInput(Rule::Right, self.rule().right()); + // Buttons below the area. + buttons->rule() + .setInput(Rule::Top, area->rule().bottom() - area->margins().bottom()) // overlap margins + .setInput(Rule::Right, self.rule().right()); + extraButtons->rule() + .setInput(Rule::Top, buttons->rule().top()) + .setInput(Rule::Left, self.rule().left()); - // A blank container widget acts as the popup content parent. - container->rule().setInput(Rule::Width, OperatorRule::maximum(area->rule().width(), - buttons->rule().width())); - } + // A blank container widget acts as the popup content parent. + container->rule().setInput(Rule::Width, OperatorRule::maximum( + area->rule().width(), + buttons->rule().width() + extraButtons->rule().width())); if(flags.testFlag(WithHeading)) { @@ -173,17 +183,10 @@ DENG2_OBSERVES(ui::Data, Removal) - buttons->rule().height() + area->margins().bottom()); } - /*else - { - area->rule().setInput(Rule::Height, container->rule().height() + area->margins().height()); - container->rule().setInput(Rule::Width, area->rule().width()); - }*/ container->add(area); - //if(!flags.testFlag(Buttonless)) - { - container->add(buttons); - } + container->add(buttons); + container->add(extraButtons); self.setContent(container); } @@ -208,15 +211,26 @@ DENG2_OBSERVES(ui::Data, Removal) area->margins().bottom() + buttons->rule().height())); } + } - /*else + bool isItemAccepted(ChildWidgetOrganizer const &organizer, ui::Data const &data, ui::Data::Pos pos) const + { + // Only dialog buttons allowed in the dialog button menus. + if(!data.at(pos).is()) return false; + + if(&organizer == &buttons->organizer()) { - // A blank container widget acts as the popup content parent. - self.content().rule().setInput(Rule::Height, - OperatorRule::minimum(root().viewHeight(), - area->contentRule().height() + - area->margins().height())); - }*/ + // Non-Action buttons only. + return !data.at(pos).as().role().testFlag(Action); + } + else if(&organizer == &extraButtons->organizer()) + { + // Only Action buttons allowed. + return data.at(pos).as().role().testFlag(Action); + } + + DENG2_ASSERT(false); // unexpected + return false; } void contextItemAdded(ui::Data::Pos, ui::Item const &) @@ -232,6 +246,7 @@ DENG2_OBSERVES(ui::Data, Removal) void updateButtonLayout() { buttons->items().sort(dialogButtonOrder); + //extraButtons->item().sort(dialogButtonOrder); needButtonUpdate = false; } @@ -269,6 +284,9 @@ DENG2_OBSERVES(ui::Data, Removal) { ButtonWidget &but = widget.as(); + // Button images must be a certain size. + but.setOverrideImageSize(style().fonts().font("default").height().valuei()); + // Set default label? if(item.label().isEmpty()) { @@ -305,6 +323,8 @@ DENG2_OBSERVES(ui::Data, Removal) ui::ActionItem const *findDefaultAction() const { + // Note: extra buttons not searched because they shouldn't contain default actions. + for(ui::Data::Pos i = 0; i < buttons->items().size(); ++i) { ButtonItem const *act = buttons->items().at(i).maybeAs(); @@ -319,6 +339,9 @@ DENG2_OBSERVES(ui::Data, Removal) ButtonWidget const &buttonWidget(ui::Item const &item) const { + GuiWidget *w = extraButtons->organizer().itemWidget(item); + if(w) return w->as(); + // Try the normal buttons. return buttons->organizer().itemWidget(item)->as(); } @@ -382,11 +405,51 @@ ScrollAreaWidget &DialogWidget::area() return *d->area; } +/* MenuWidget &DialogWidget::buttons() { return *d->buttons; } +MenuWidget &DialogWidget::extraButtons() +{ + return *d->extraButtons; +} +*/ + +ui::Data &DialogWidget::buttons() +{ + return d->buttonItems; +} + +ButtonWidget &DialogWidget::buttonWidget(de::String const &label) const +{ + GuiWidget *w = d->buttons->organizer().itemWidget(label); + if(w) return w->as(); + + w = d->extraButtons->organizer().itemWidget(label); + DENG2_ASSERT(w != 0); + + return w->as(); +} + +ButtonWidget *DialogWidget::buttonWidget(int roleId) const +{ + for(uint i = 0; i < d->buttonItems.size(); ++i) + { + DialogButtonItem const &item = d->buttonItems.at(i).as(); + + if((item.role() & IdMask) == roleId) + { + GuiWidget *w = d->buttons->organizer().itemWidget(i); + if(w) return &w->as(); + + return &d->extraButtons->organizer().itemWidget(i)->as(); + } + } + return 0; +} + int DialogWidget::exec(GuiRootWidget &root) { d->modality = Modal; @@ -537,6 +600,7 @@ void DialogWidget::preparePanelForOpening() // Redo the layout (items visible now). d->buttons->updateLayout(); + d->extraButtons->updateLayout(); d->updateBackground(); } @@ -554,3 +618,7 @@ DialogWidget::ButtonItem::ButtonItem(RoleFlags flags, String const &label) DialogWidget::ButtonItem::ButtonItem(RoleFlags flags, String const &label, de::Action *action) : ui::ActionItem(label, action), _role(flags) {} + +DialogWidget::ButtonItem::ButtonItem(RoleFlags flags, Image const &image, de::Action *action) + : ui::ActionItem(image, "", action), _role(flags) +{} diff --git a/doomsday/client/src/ui/widgets/documentwidget.cpp b/doomsday/client/src/ui/widgets/documentwidget.cpp index 20cb79f976..5b48b70fd6 100644 --- a/doomsday/client/src/ui/widgets/documentwidget.cpp +++ b/doomsday/client/src/ui/widgets/documentwidget.cpp @@ -28,7 +28,6 @@ static int const ID_BACKGROUND = 1; // does not scroll static int const ID_TEXT = 2; // scrolls DENG_GUI_PIMPL(DocumentWidget), -DENG2_OBSERVES(Atlas, Reposition), public Font::RichFormat::IStyle { typedef DefaultVertexBuf VertexBuf; diff --git a/doomsday/client/src/ui/widgets/gameselectionwidget.cpp b/doomsday/client/src/ui/widgets/gameselectionwidget.cpp index 7f7655a5ed..29be741116 100644 --- a/doomsday/client/src/ui/widgets/gameselectionwidget.cpp +++ b/doomsday/client/src/ui/widgets/gameselectionwidget.cpp @@ -30,7 +30,7 @@ using namespace de; DENG_GUI_PIMPL(GameSelectionWidget), DENG2_OBSERVES(Games, Addition), DENG2_OBSERVES(App, StartupComplete), -DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation) +DENG2_OBSERVES(ChildWidgetOrganizer, WidgetCreation) { /// ActionItem with a Game member. struct GameItem : public ui::ActionItem { diff --git a/doomsday/client/src/ui/widgets/logwidget.cpp b/doomsday/client/src/ui/widgets/logwidget.cpp index 212895bc5b..f4b9c1767a 100644 --- a/doomsday/client/src/ui/widgets/logwidget.cpp +++ b/doomsday/client/src/ui/widgets/logwidget.cpp @@ -45,7 +45,6 @@ using namespace de; using namespace ui; DENG_GUI_PIMPL(LogWidget), -DENG2_OBSERVES(Atlas, Reposition), DENG2_OBSERVES(Atlas, OutOfSpace), public Font::RichFormat::IStyle { diff --git a/doomsday/client/src/ui/widgets/menuwidget.cpp b/doomsday/client/src/ui/widgets/menuwidget.cpp index f060008702..45712cab2a 100644 --- a/doomsday/client/src/ui/widgets/menuwidget.cpp +++ b/doomsday/client/src/ui/widgets/menuwidget.cpp @@ -19,7 +19,7 @@ #include "ui/widgets/menuwidget.h" #include "ui/widgets/popupmenuwidget.h" #include "ui/widgets/variabletogglewidget.h" -#include "ContextWidgetOrganizer" +#include "ChildWidgetOrganizer" #include "ui/ListData" #include "ui/ActionItem" #include "GridLayout" @@ -32,7 +32,7 @@ DENG2_OBSERVES(Data, Addition), // for layout update DENG2_OBSERVES(Data, Removal), // for layout update DENG2_OBSERVES(Data, OrderChange), // for layout update DENG2_OBSERVES(PopupWidget, Close), -public ContextWidgetOrganizer::IWidgetFactory +public ChildWidgetOrganizer::IWidgetFactory { /** * Action owned by the button that represents a SubmenuItem. @@ -99,7 +99,7 @@ public ContextWidgetOrganizer::IWidgetFactory GridLayout layout; ListData defaultItems; Data const *items; - ContextWidgetOrganizer organizer; + ChildWidgetOrganizer organizer; QSet openPopups; SizePolicy colPolicy; @@ -349,7 +349,12 @@ GridLayout const &MenuWidget::layout() const return d->layout; } -ContextWidgetOrganizer const &MenuWidget::organizer() const +ChildWidgetOrganizer &MenuWidget::organizer() +{ + return d->organizer; +} + +ChildWidgetOrganizer const &MenuWidget::organizer() const { return d->organizer; } diff --git a/doomsday/client/src/ui/widgets/popupmenuwidget.cpp b/doomsday/client/src/ui/widgets/popupmenuwidget.cpp index 7291239e8c..d7e1eb9956 100644 --- a/doomsday/client/src/ui/widgets/popupmenuwidget.cpp +++ b/doomsday/client/src/ui/widgets/popupmenuwidget.cpp @@ -19,7 +19,7 @@ #include "ui/widgets/popupmenuwidget.h" #include "ui/widgets/menuwidget.h" #include "GuiRootWidget" -#include "ContextWidgetOrganizer" +#include "ChildWidgetOrganizer" #include "ui/Item" #include "clientapp.h" @@ -28,8 +28,8 @@ using namespace de; DENG2_PIMPL(PopupMenuWidget), DENG2_OBSERVES(ButtonWidget, StateChange), DENG2_OBSERVES(ButtonWidget, Triggered), -DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation), -DENG2_OBSERVES(ContextWidgetOrganizer, WidgetUpdate) +DENG2_OBSERVES(ChildWidgetOrganizer, WidgetCreation), +DENG2_OBSERVES(ChildWidgetOrganizer, WidgetUpdate) { ButtonWidget *hover; int oldScrollY; diff --git a/doomsday/client/src/ui/widgets/profilepickerwidget.cpp b/doomsday/client/src/ui/widgets/profilepickerwidget.cpp index b6ca9af5b7..7ad62c71bf 100644 --- a/doomsday/client/src/ui/widgets/profilepickerwidget.cpp +++ b/doomsday/client/src/ui/widgets/profilepickerwidget.cpp @@ -113,7 +113,7 @@ void ProfilePickerWidget::openMenu() << new ActionItem(tr("Delete..."), new SignalAction(this, SLOT(remove()))); add(popup); - ContextWidgetOrganizer const &org = popup->menu().organizer(); + ChildWidgetOrganizer const &org = popup->menu().organizer(); // Enable or disable buttons depending on the selected profile. String selProf = selectedItem().data().toString(); @@ -219,7 +219,7 @@ void ProfilePickerWidget::reset() .arg(d->description) .arg(_E(b) + d->currentProfile() + _E(.))); - dlg->buttons().items() + dlg->buttons() << new DialogButtonItem(DialogWidget::Default | DialogWidget::Reject) << new DialogButtonItem(DialogWidget::Accept, tr("Reset Profile")); @@ -238,7 +238,7 @@ void ProfilePickerWidget::remove() tr("Are you sure you want to delete the %1 profile %2? This cannot be undone.") .arg(d->description) .arg(_E(b) + d->currentProfile() + _E(.))); - dlg->buttons().items() + dlg->buttons() << new DialogButtonItem(DialogWidget::Default | DialogWidget::Reject) << new DialogButtonItem(DialogWidget::Accept, tr("Delete Profile")); diff --git a/doomsday/client/src/updater/downloaddialog.cpp b/doomsday/client/src/updater/downloaddialog.cpp index cca9266f81..51655ec3f5 100644 --- a/doomsday/client/src/updater/downloaddialog.cpp +++ b/doomsday/client/src/updater/downloaddialog.cpp @@ -85,9 +85,9 @@ DENG2_PIMPL(DownloadDialog) area.setContentSize(progress->rule().width(), progress->rule().height()); - self.buttons().items() << new DialogButtonItem(DialogWidget::Reject, - tr("Cancel Download"), - new SignalAction(thisPublic, SLOT(cancel()))); + self.buttons() << new DialogButtonItem(DialogWidget::Reject, + tr("Cancel Download"), + new SignalAction(thisPublic, SLOT(cancel()))); updateLocation(uri); updateProgress(); @@ -263,7 +263,7 @@ void DownloadDialog::finished(QNetworkReply *reply) emit downloadFailed(d->uri.toString()); } - buttons().items().clear() + buttons().clear() << new DialogButtonItem(DialogWidget::Reject, tr("Abort"), new SignalAction(this, SLOT(cancel()))) << new DialogButtonItem(DialogWidget::Accept | DialogWidget::Default, tr("Install")); @@ -286,7 +286,7 @@ void DownloadDialog::cancel() if(d->reply) { d->reply->abort(); - buttons().items().clear() + buttons().clear() << new DialogButtonItem(DialogWidget::Reject, tr("Close")); } else diff --git a/doomsday/client/src/updater/updateavailabledialog.cpp b/doomsday/client/src/updater/updateavailabledialog.cpp index f1729816c4..cb074b8181 100644 --- a/doomsday/client/src/updater/updateavailabledialog.cpp +++ b/doomsday/client/src/updater/updateavailabledialog.cpp @@ -37,12 +37,12 @@ using namespace de; static TimeDelta const SHOW_ANIM_SPAN = 0.3; -DENG2_PIMPL(UpdateAvailableDialog), +DENG_GUI_PIMPL(UpdateAvailableDialog), DENG2_OBSERVES(ToggleWidget, Toggle) { ProgressWidget *checking; ToggleWidget *autoCheck; - ButtonWidget *settings; + //ButtonWidget *settings; VersionInfo latestVersion; String changeLog; @@ -76,7 +76,7 @@ DENG2_OBSERVES(ToggleWidget, Toggle) if(show) { // Set up a cancel button. - self.buttons().items().clear() + self.buttons().clear() << new DialogButtonItem(DialogWidget::Reject); } } @@ -90,18 +90,18 @@ DENG2_OBSERVES(ToggleWidget, Toggle) checking->rule().setRect(self.rule()); self.add(checking); - settings = new ButtonWidget; - self.add(settings); + //settings = new ButtonWidget; + //self.add(settings); /// @todo The dialog buttons should support a opposite-aligned button. - settings->setSizePolicy(ui::Filled, ui::Filled); + /*settings->setSizePolicy(ui::Filled, ui::Filled); settings->setImage(self.style().images().image("gear")); settings->rule() .setInput(Rule::Left, self.area().contentRule().left()) .setInput(Rule::Bottom, self.buttons().contentRule().bottom()) .setInput(Rule::Height, self.buttons().contentRule().height()) .setInput(Rule::Width, settings->rule().height()); - settings->setAction(new SignalAction(thisPublic, SLOT(editSettings()))); + settings->setAction()));*/ autoCheck = new ToggleWidget; self.area().add(autoCheck); @@ -155,30 +155,35 @@ DENG2_OBSERVES(ToggleWidget, Toggle) autoCheck->setInactive(UpdaterSettings().onlyCheckManually()); - self.buttons().items().clear(); + self.buttons().clear(); if(askDowngrade) { - self.buttons().items() + self.buttons() << new DialogButtonItem(DialogWidget::Accept, tr("Downgrade to Older")) << new DialogButtonItem(DialogWidget::Reject | DialogWidget::Default, tr("Close")); } else if(askUpgrade) { - self.buttons().items() + self.buttons() << new DialogButtonItem(DialogWidget::Accept | DialogWidget::Default, tr("Upgrade")) << new DialogButtonItem(DialogWidget::Reject, tr("Not Now")); } else { - self.buttons().items() + self.buttons() << new DialogButtonItem(DialogWidget::Accept, tr("Reinstall")) << new DialogButtonItem(DialogWidget::Reject | DialogWidget::Default, tr("Close")); } + self.buttons() + << new DialogButtonItem(DialogWidget::Action | DialogWidget::Id1, + style().images().image("gear"), + new SignalAction(thisPublic, SLOT(editSettings()))); + if(askUpgrade) { - self.buttons().items() + self.buttons() << new DialogButtonItem(DialogWidget::Action, tr("What's New?"), new SignalAction(thisPublic, SLOT(showWhatsNew()))); } @@ -217,7 +222,7 @@ void UpdateAvailableDialog::showWhatsNew() void UpdateAvailableDialog::editSettings() { UpdaterSettingsDialog *st = new UpdaterSettingsDialog; - st->setAnchorAndOpeningDirection(d->settings->rule(), ui::Up); + st->setAnchorAndOpeningDirection(buttonWidget(DialogWidget::Id1)->rule(), ui::Up); st->setDeleteAfterDismissed(true); if(st->exec(root())) { diff --git a/doomsday/client/src/updater/updatersettingsdialog.cpp b/doomsday/client/src/updater/updatersettingsdialog.cpp index ff15845c9e..8201863ff5 100644 --- a/doomsday/client/src/updater/updatersettingsdialog.cpp +++ b/doomsday/client/src/updater/updatersettingsdialog.cpp @@ -116,13 +116,13 @@ DENG2_OBSERVES(ToggleWidget, Toggle) area.setContentSize(layout.width(), layout.height()); - self.buttons().items() + self.buttons() << new DialogButtonItem(DialogWidget::Default | DialogWidget::Accept) << new DialogButtonItem(DialogWidget::Reject); if(mode == WithApplyAndCheckButton) { - self.buttons().items() + self.buttons() << new DialogButtonItem(DialogWidget::Action, tr("Apply & Check Now"), new SignalAction(thisPublic, SLOT(applyAndCheckNow()))); } diff --git a/doomsday/libdeng2/include/de/data/observers.h b/doomsday/libdeng2/include/de/data/observers.h index 33e4fc06ae..50ab551b52 100644 --- a/doomsday/libdeng2/include/de/data/observers.h +++ b/doomsday/libdeng2/include/de/data/observers.h @@ -212,13 +212,13 @@ class Observers : public Lockable return *this; } - Observers const &operator += (Type *observer) const { - const_cast *>(this)->add(observer); + Observers const &operator += (Type const *observer) const { + const_cast *>(this)->add(const_cast(observer)); return *this; } - Observers const &operator += (Type &observer) const { - const_cast *>(this)->add(&observer); + Observers const &operator += (Type const &observer) const { + const_cast *>(this)->add(const_cast(&observer)); return *this; }