diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index 875bf16c63..6b67315e39 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -185,6 +185,7 @@ DENG_CONVENIENCE_HEADERS += \ include/ui/Margins \ include/ui/Stylist \ include/ui/SubmenuItem \ + include/ui/SubwidgetItem \ include/ui/VariableToggleItem \ include/Vertex \ include/WallEdge \ @@ -415,6 +416,7 @@ DENG_HEADERS += \ include/ui/framework/signalaction.h \ include/ui/framework/stylist.h \ include/ui/framework/submenuitem.h \ + include/ui/framework/subwidgetitem.h \ include/ui/framework/textdrawable.h \ include/ui/framework/variabletoggleitem.h \ include/ui/framework/widgetactions.h \ diff --git a/doomsday/client/include/ui/SubwidgetItem b/doomsday/client/include/ui/SubwidgetItem new file mode 100644 index 0000000000..18d2de66af --- /dev/null +++ b/doomsday/client/include/ui/SubwidgetItem @@ -0,0 +1 @@ +#include "framework/subwidgetitem.h" diff --git a/doomsday/client/include/ui/framework/subwidgetitem.h b/doomsday/client/include/ui/framework/subwidgetitem.h new file mode 100644 index 0000000000..7fd7c4d215 --- /dev/null +++ b/doomsday/client/include/ui/framework/subwidgetitem.h @@ -0,0 +1,65 @@ +/** @file subwidgetitem.h + * + * @authors Copyright (c) 2013 Jaakko Keränen + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#ifndef DENG_CLIENT_UI_SUBWIDGETITEM_H +#define DENG_CLIENT_UI_SUBWIDGETITEM_H + +#include "../uidefs.h" +#include "item.h" + +#include + +class PopupWidget; + +namespace ui { + +/** + * UI context item that opens a widget as a popup. + */ +class SubwidgetItem : public Item +{ +public: + typedef PopupWidget *(*WidgetConstructor)(); + +public: + SubwidgetItem(de::String const &label, ui::Direction openingDirection, + WidgetConstructor constructor) + : Item(ShownAsButton, label) + , _constructor(constructor) + , _dir(openingDirection) {} + + SubwidgetItem(de::Image const &image, de::String const &label, ui::Direction openingDirection, + WidgetConstructor constructor) + : Item(ShownAsButton, label) + , _constructor(constructor) + , _dir(openingDirection) + , _image(image) {} + + PopupWidget *makeWidget() const { return _constructor(); } + ui::Direction openingDirection() const { return _dir; } + de::Image image() const { return _image; } + +private: + WidgetConstructor _constructor; + ui::Direction _dir; + de::Image _image; +}; + +} // namespace ui + +#endif // DENG_CLIENT_UI_SUBWIDGETITEM_H diff --git a/doomsday/client/include/ui/widgets/consolewidget.h b/doomsday/client/include/ui/widgets/consolewidget.h index badaa4036b..f21f7e159f 100644 --- a/doomsday/client/include/ui/widgets/consolewidget.h +++ b/doomsday/client/include/ui/widgets/consolewidget.h @@ -65,6 +65,7 @@ class ConsoleWidget : public GuiWidget signals: void commandModeChanged(); + void commandLineGotFocus(); public slots: void openLog(); @@ -72,6 +73,7 @@ public slots: void clearLog(); void showFullLog(); void setFullyOpaque(); + void commandLineFocusGained(); void commandLineFocusLost(); void focusOnCommandLine(); void openMenu(); diff --git a/doomsday/client/include/ui/widgets/panelwidget.h b/doomsday/client/include/ui/widgets/panelwidget.h index 398a9d78ce..5f4877e57f 100644 --- a/doomsday/client/include/ui/widgets/panelwidget.h +++ b/doomsday/client/include/ui/widgets/panelwidget.h @@ -98,7 +98,7 @@ public slots: * Opens the panel, positioning it appropriately so that is anchored to the * position specified with setAnchor(). */ - void open(); + virtual void open(); /** * Closes the panel. The widget is dismissed once the closing animation diff --git a/doomsday/client/include/ui/widgets/taskbarwidget.h b/doomsday/client/include/ui/widgets/taskbarwidget.h index b03616ab1e..d587efcd14 100644 --- a/doomsday/client/include/ui/widgets/taskbarwidget.h +++ b/doomsday/client/include/ui/widgets/taskbarwidget.h @@ -62,11 +62,6 @@ public slots: void unloadGame(); void showAbout(); void showUpdaterSettings(); - void showRendererSettings(); - void showVideoSettings(); - void showAudioSettings(); - void showInputSettings(); - void showNetworkSettings(); protected slots: void updateCommandLineLayout(); diff --git a/doomsday/client/src/ui/widgets/consolewidget.cpp b/doomsday/client/src/ui/widgets/consolewidget.cpp index c34d168513..1bd2b893d2 100644 --- a/doomsday/client/src/ui/widgets/consolewidget.cpp +++ b/doomsday/client/src/ui/widgets/consolewidget.cpp @@ -287,17 +287,23 @@ ConsoleWidget::ConsoleWidget() : GuiWidget("console"), d(new Instance(this)) // Signals. connect(d->log, SIGNAL(contentHeightIncreased(int)), this, SLOT(logContentHeightIncreased(int))); - connect(d->cmdLine, SIGNAL(gotFocus()), this, SLOT(setFullyOpaque())); - connect(d->cmdLine, SIGNAL(gotFocus()), this, SLOT(openLog())); + connect(d->cmdLine, SIGNAL(gotFocus()), this, SLOT(commandLineFocusGained())); connect(d->cmdLine, SIGNAL(lostFocus()), this, SLOT(commandLineFocusLost())); connect(d->cmdLine, SIGNAL(commandEntered(de::String)), this, SLOT(commandWasEntered(de::String))); - connect(d->scriptCmd, SIGNAL(gotFocus()), this, SLOT(setFullyOpaque())); - connect(d->scriptCmd, SIGNAL(gotFocus()), this, SLOT(openLog())); + connect(d->scriptCmd, SIGNAL(gotFocus()), this, SLOT(commandLineFocusGained())); connect(d->scriptCmd, SIGNAL(lostFocus()), this, SLOT(commandLineFocusLost())); connect(d->scriptCmd, SIGNAL(commandEntered(de::String)), this, SLOT(commandWasEntered(de::String))); } +void ConsoleWidget::commandLineFocusGained() +{ + setFullyOpaque(); + openLog(); + + emit commandLineGotFocus(); +} + ButtonWidget &ConsoleWidget::button() { return *d->button; diff --git a/doomsday/client/src/ui/widgets/dialogwidget.cpp b/doomsday/client/src/ui/widgets/dialogwidget.cpp index f833ac63b9..b49115d913 100644 --- a/doomsday/client/src/ui/widgets/dialogwidget.cpp +++ b/doomsday/client/src/ui/widgets/dialogwidget.cpp @@ -472,7 +472,7 @@ void DialogWidget::open() d->modality = NonModal; DENG2_ASSERT(hasRoot()); - prepare(); + prepare(); // calls base class's open() } ui::ActionItem *DialogWidget::defaultActionItem() diff --git a/doomsday/client/src/ui/widgets/menuwidget.cpp b/doomsday/client/src/ui/widgets/menuwidget.cpp index 45712cab2a..d9ed517bc9 100644 --- a/doomsday/client/src/ui/widgets/menuwidget.cpp +++ b/doomsday/client/src/ui/widgets/menuwidget.cpp @@ -22,38 +22,34 @@ #include "ChildWidgetOrganizer" #include "ui/ListData" #include "ui/ActionItem" +#include "ui/SubwidgetItem" #include "GridLayout" using namespace de; using namespace ui; -DENG2_PIMPL(MenuWidget), -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 ChildWidgetOrganizer::IWidgetFactory +DENG2_PIMPL(MenuWidget) +, 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) +, DENG2_OBSERVES(Widget, Deletion) +, public ChildWidgetOrganizer::IWidgetFactory { /** - * Action owned by the button that represents a SubmenuItem. + * Base class for sub-widget actions. Handles ownership/openness tracking. */ - class SubmenuAction : public de::Action, DENG2_OBSERVES(Widget, Deletion) + class SubAction : public de::Action, DENG2_OBSERVES(Widget, Deletion) { public: - SubmenuAction(Instance *inst, ui::SubmenuItem const &parentItem) - : d(inst), _submenu(parentItem) - { - _widget = new PopupMenuWidget; - _widget->audienceForDeletion += this; - - // Use the items from the submenu. - _widget->menu().setItems(parentItem.items()); - - // Popups need a parent; hidden items are ignored by the menu. - d->self.add(_widget); - } - - ~SubmenuAction() + SubAction(Instance *inst, ui::Item const &parentItem) + : d(inst) + , _parentItem(parentItem) + , _dir(ui::Right) + , _widget(0) + {} + + ~SubAction() { if(_widget) { @@ -61,25 +57,39 @@ public ChildWidgetOrganizer::IWidgetFactory } } + void setWidget(PopupWidget *w, ui::Direction openingDirection) + { + _widget = w; + + // Popups need a parent. + d->self.add(_widget); + + _widget->audienceForDeletion += this; + _dir = openingDirection; + } + void widgetBeingDeleted(Widget &) { _widget = 0; } + bool isTriggered() const + { + return _widget != 0; + } + void trigger() { Action::trigger(); DENG2_ASSERT(_widget != 0); - GuiWidget *parent = d->organizer.itemWidget(_submenu); + GuiWidget *parent = d->organizer.itemWidget(_parentItem); DENG2_ASSERT(parent != 0); - _widget->setAnchorAndOpeningDirection(parent->hitRule(), _submenu.openingDirection()); - - d->openPopups.insert(_widget); - _widget->audienceForClose += d; + _widget->setAnchorAndOpeningDirection(parent->hitRule(), _dir); + d->keepTrackOfSubWidget(_widget); _widget->open(); } @@ -89,10 +99,54 @@ public ChildWidgetOrganizer::IWidgetFactory return 0; } - private: + protected: Instance *d; - ui::SubmenuItem const &_submenu; - PopupMenuWidget *_widget; + ui::Item const &_parentItem; + ui::Direction _dir; + PopupWidget *_widget; + }; + + /** + * Action owned by the button that represents a SubmenuItem. + */ + class SubmenuAction : public SubAction + { + public: + SubmenuAction(Instance *inst, ui::SubmenuItem const &parentItem) + : SubAction(inst, parentItem) + { + PopupMenuWidget *sub = new PopupMenuWidget; + setWidget(sub, parentItem.openingDirection()); + + // Use the items from the submenu. + sub->menu().setItems(parentItem.items()); + } + }; + + /** + * Action owned by the button that represents a SubwidgetItem. + */ + class SubwidgetAction : public SubAction + { + public: + SubwidgetAction(Instance *inst, ui::SubwidgetItem const &parentItem) + : SubAction(inst, parentItem) + , _item(parentItem) + {} + + void trigger() + { + if(isTriggered()) return; // Already open, cannot retrigger. + + // The widget is created only at this point. + setWidget(_item.makeWidget(), _item.openingDirection()); + _widget->setDeleteAfterDismissed(true); + + SubAction::trigger(); + } + + private: + ui::SubwidgetItem const &_item; }; bool needLayout; @@ -100,7 +154,7 @@ public ChildWidgetOrganizer::IWidgetFactory ListData defaultItems; Data const *items; ChildWidgetOrganizer organizer; - QSet openPopups; + QSet openSubs; SizePolicy colPolicy; SizePolicy rowPolicy; @@ -173,6 +227,10 @@ public ChildWidgetOrganizer::IWidgetFactory { b->setAction(new SubmenuAction(this, item.as())); } + else if(item.is()) + { + b->setAction(new SubwidgetAction(this, item.as())); + } return b; } else if(item.semantics().testFlag(Item::Separator)) @@ -228,12 +286,30 @@ public ChildWidgetOrganizer::IWidgetFactory // Other kinds of items are represented as labels or // label-derived widgets. widget.as().setText(item.label()); + + if(SubwidgetItem const *sub = item.maybeAs()) + { + widget.as().setImage(sub->image()); + } } } void panelBeingClosed(PanelWidget &popup) + { + openSubs.remove(&popup.as()); + } + + void widgetBeingDeleted(Widget &widget) + { + openSubs.remove(&widget.as()); + } + + void keepTrackOfSubWidget(PopupWidget *w) { - openPopups.remove(&popup.as()); + openSubs.insert(w); + + w->audienceForClose += this; + w->audienceForDeletion += this; } bool isVisibleItem(Widget const *child) const @@ -361,8 +437,6 @@ ChildWidgetOrganizer const &MenuWidget::organizer() const void MenuWidget::update() { - //if(isHidden()) return; - if(d->needLayout) { updateLayout(); @@ -373,22 +447,12 @@ void MenuWidget::update() bool MenuWidget::handleEvent(Event const &event) { -#if 0 - if(event.type() == Event::MousePosition) - { - if(rule().recti().contains(event.as().pos())) - { - // Eat position events inside the menu area. - return true; - } - } -#endif return ScrollAreaWidget::handleEvent(event); } void MenuWidget::dismissPopups() { - foreach(PopupWidget *pop, d->openPopups) + foreach(PopupWidget *pop, d->openSubs) { pop->close(); } diff --git a/doomsday/client/src/ui/widgets/taskbarwidget.cpp b/doomsday/client/src/ui/widgets/taskbarwidget.cpp index 1f67893485..d0ba352bab 100644 --- a/doomsday/client/src/ui/widgets/taskbarwidget.cpp +++ b/doomsday/client/src/ui/widgets/taskbarwidget.cpp @@ -30,6 +30,7 @@ #include "ui/dialogs/renderersettingsdialog.h" #include "updater/updatersettingsdialog.h" #include "ui/clientwindow.h" +#include "ui/SubwidgetItem" #include "GuiRootWidget" #include "SequentialLayout" #include "CommandAction" @@ -58,14 +59,14 @@ static uint POS_GAME_SEPARATOR = 1; static uint POS_RENDERER_SETTINGS = 0; static uint POS_CONFIG_SEPARATOR = 1; -static uint POS_VIDEO_SETTINGS = 2; +//static uint POS_VIDEO_SETTINGS = 2; static uint POS_AUDIO_SETTINGS = 3; static uint POS_INPUT_SETTINGS = 4; -static uint POS_NETWORK_SETTINGS = 5; -static uint POS_UPDATER_SETTINGS = 6; +//static uint POS_NETWORK_SETTINGS = 5; +//static uint POS_UPDATER_SETTINGS = 6; -DENG_GUI_PIMPL(TaskBarWidget), -DENG2_OBSERVES(App, GameChange) +DENG_GUI_PIMPL(TaskBarWidget) +, DENG2_OBSERVES(App, GameChange) { typedef DefaultVertexBuf VertexBuf; @@ -84,6 +85,7 @@ DENG2_OBSERVES(App, GameChange) LabelWidget *status; PopupMenuWidget *mainMenu; PopupMenuWidget *configMenu; + ScalarRule *vertShift; bool mouseWasTrappedWhenOpening; int minSpace; @@ -263,22 +265,14 @@ DENG2_OBSERVES(App, GameChange) status->setText(tr("No game loaded")); } } +}; - void setupItemSubDialog(PopupMenuWidget *menu, ui::Data::Pos item, DialogWidget *dlg) - { - dlg->setDeleteAfterDismissed(true); - if(menu->isOpen()) - { - dlg->setAnchorAndOpeningDirection(menu->menu().organizer(). - itemWidget(item)->hitRule(), - ui::Left); +template +PopupWidget *makePopup() { return new ClassName; } - // Mutual, automatic closing. - connect(dlg, SIGNAL(accepted(int)), menu, SLOT(close())); - connect(menu, SIGNAL(closed()), dlg, SLOT(close())); - } - } -}; +PopupWidget *makeUpdaterSettings() { + return new UpdaterSettingsDialog(UpdaterSettingsDialog::WithApplyAndCheckButton); +} TaskBarWidget::TaskBarWidget() : GuiWidget("taskbar"), d(new Instance(this)) { @@ -369,25 +363,13 @@ TaskBarWidget::TaskBarWidget() : GuiWidget("taskbar"), d(new Instance(this)) * depending on whether a game is loaded. */ d->configMenu->items() - << new ui::ActionItem(ui::Item::ShownAsButton, - style().images().image("renderer"), tr("Renderer"), - new SignalAction(this, SLOT(showRendererSettings()))) + << new ui::SubwidgetItem(style().images().image("renderer"), tr("Renderer"), ui::Left, makePopup) << new ui::Item(ui::Item::Separator) - << new ui::ActionItem(ui::Item::ShownAsButton, - style().images().image("display"), tr("Video"), - new SignalAction(this, SLOT(showVideoSettings()))) - << new ui::ActionItem(ui::Item::ShownAsButton, - style().images().image("audio"), tr("Audio"), - new SignalAction(this, SLOT(showAudioSettings()))) - << new ui::ActionItem(ui::Item::ShownAsButton, - style().images().image("input"), tr("Input"), - new SignalAction(this, SLOT(showInputSettings()))) - << new ui::ActionItem(ui::Item::ShownAsButton, - style().images().image("network"), tr("Network"), - new SignalAction(this, SLOT(showNetworkSettings()))) - << new ui::ActionItem(ui::Item::ShownAsButton, - style().images().image("updater"), tr("Updater"), - new SignalAction(this, SLOT(showUpdaterSettings()))); + << new ui::SubwidgetItem(style().images().image("display"), tr("Video"), ui::Left, makePopup) + << new ui::SubwidgetItem(style().images().image("audio"), tr("Audio"), ui::Left, makePopup) + << new ui::SubwidgetItem(style().images().image("input"), tr("Input"), ui::Left, makePopup) + << new ui::SubwidgetItem(style().images().image("network"), tr("Network"), ui::Left, makePopup) + << new ui::SubwidgetItem(style().images().image("updater"), tr("Updater"), ui::Left, makeUpdaterSettings); d->mainMenu->items() << unloadMenu // hidden with null-game @@ -415,6 +397,8 @@ TaskBarWidget::TaskBarWidget() : GuiWidget("taskbar"), d(new Instance(this)) updateCommandLineLayout(); connect(d->console, SIGNAL(commandModeChanged()), this, SLOT(updateCommandLineLayout())); + connect(d->console, SIGNAL(commandLineGotFocus()), this, SLOT(closeMainMenu())); + connect(d->console, SIGNAL(commandLineGotFocus()), this, SLOT(closeConfigMenu())); } ConsoleWidget &TaskBarWidget::console() @@ -632,6 +616,7 @@ void TaskBarWidget::close() void TaskBarWidget::openConfigMenu() { + d->mainMenu->close(0); d->configMenu->open(); } @@ -642,6 +627,7 @@ void TaskBarWidget::closeConfigMenu() void TaskBarWidget::openMainMenu() { + d->configMenu->close(0); d->mainMenu->open(); } @@ -666,48 +652,9 @@ void TaskBarWidget::showAbout() void TaskBarWidget::showUpdaterSettings() { + /// @todo This has actually little to do with the taskbar. -jk UpdaterSettingsDialog *dlg = new UpdaterSettingsDialog(UpdaterSettingsDialog::WithApplyAndCheckButton); - d->setupItemSubDialog(d->configMenu, POS_UPDATER_SETTINGS, dlg); - root().addOnTop(dlg); - dlg->open(); -} - -void TaskBarWidget::showRendererSettings() -{ - RendererSettingsDialog *dlg = new RendererSettingsDialog; - d->setupItemSubDialog(d->configMenu, POS_RENDERER_SETTINGS, dlg); - root().addOnTop(dlg); - dlg->open(); -} - -void TaskBarWidget::showVideoSettings() -{ - VideoSettingsDialog *dlg = new VideoSettingsDialog; - d->setupItemSubDialog(d->configMenu, POS_VIDEO_SETTINGS, dlg); - root().addOnTop(dlg); - dlg->open(); -} - -void TaskBarWidget::showAudioSettings() -{ - AudioSettingsDialog *dlg = new AudioSettingsDialog; - d->setupItemSubDialog(d->configMenu, POS_AUDIO_SETTINGS, dlg); - root().addOnTop(dlg); - dlg->open(); -} - -void TaskBarWidget::showInputSettings() -{ - InputSettingsDialog *dlg = new InputSettingsDialog; - d->setupItemSubDialog(d->configMenu, POS_INPUT_SETTINGS, dlg); - root().addOnTop(dlg); - dlg->open(); -} - -void TaskBarWidget::showNetworkSettings() -{ - NetworkSettingsDialog *dlg = new NetworkSettingsDialog; - d->setupItemSubDialog(d->configMenu, POS_NETWORK_SETTINGS, dlg); + dlg->setDeleteAfterDismissed(true); root().addOnTop(dlg); dlg->open(); }