diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index 1cab001f8d..b9309076b5 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -356,6 +356,7 @@ DENG_HEADERS += \ include/ui/widgets/contextwidgetorganizer.h \ include/ui/widgets/documentwidget.h \ include/ui/widgets/gameselectionwidget.h \ + include/ui/widgets/dialogwidget.h \ include/ui/widgets/gltextcomposer.h \ include/ui/widgets/guirootwidget.h \ include/ui/widgets/guiwidget.h \ @@ -667,6 +668,7 @@ SOURCES += \ src/ui/widgets/contextwidgetorganizer.cpp \ src/ui/widgets/documentwidget.cpp \ src/ui/widgets/gameselectionwidget.cpp \ + src/ui/widgets/dialogwidget.cpp \ src/ui/widgets/gltextcomposer.cpp \ src/ui/widgets/guirootwidget.cpp \ src/ui/widgets/guiwidget.cpp \ diff --git a/doomsday/client/include/ui/widgets/dialogwidget.h b/doomsday/client/include/ui/widgets/dialogwidget.h new file mode 100644 index 0000000000..1bf845ab3c --- /dev/null +++ b/doomsday/client/include/ui/widgets/dialogwidget.h @@ -0,0 +1,108 @@ +/** @file dialogwidget.h Popup dialog. + * + * @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_DIALOGWIDGET_H +#define DENG_CLIENT_DIALOGWIDGET_H + +#include "popupwidget.h" +#include "scrollareawidget.h" +#include "menuwidget.h" + +class GuiRootWidget; + +/** + * Popup dialog. + * + * The content area of a dialog is scrollable. A menu with buttons is placed in + * the bottom of the dialog, for the actions available to the user. + * + * The contents of the dialog should be placed under the scroll area returned + * by DialogWidget::content() and positioned in relation to its content rule. + * When the dialog is set up, one must define the size of the content scroll + * area (width and height rules). + */ +class DialogWidget : public PopupWidget +{ + Q_OBJECT + +public: + /** + * Modality of the dialog. By default, dialogs are modal, meaning that + * while they are open, no events can get past the dialog. + */ + enum Modality { + Modal, + Nonmodal + }; + +public: + DialogWidget(de::String const &name = ""); + + void setModality(Modality modality); + + Modality modality() const; + + ScrollAreaWidget &content(); + + MenuWidget &buttons(); + + /** + * Shows the dialog and blocks execution until the dialog is closed -- + * another event loop is started for event processing. Call either accept() + * or reject() to dismiss the dialog. + * + * @param root Root where to execute the dialog. + * + * @return Result code. + */ + int exec(GuiRootWidget &root); + + // Events. + bool handleEvent(de::Event const &event); + +public slots: + void accept(int result = 1); + void reject(int result = 0); + +signals: + void accepted(int result); + void rejected(int result); + +protected: + void preparePopupForOpening(); + + /** + * Derived classes can override this to do additional tasks before + * execution of the dialog begins. DialogWidget::prepare() must be called + * from the overridding methods. + */ + virtual void prepare(); + + /** + * Handles any tasks needed when the dialog is closing. + * DialogWidget::finish() must be called from overridding methods. + * + * @param result Result code. + */ + virtual void finish(int result); + +private: + DENG2_PRIVATE(d) +}; + +#endif // DENG_CLIENT_DIALOGWIDGET_H diff --git a/doomsday/client/src/ui/widgets/dialogwidget.cpp b/doomsday/client/src/ui/widgets/dialogwidget.cpp new file mode 100644 index 0000000000..a857b739e1 --- /dev/null +++ b/doomsday/client/src/ui/widgets/dialogwidget.cpp @@ -0,0 +1,181 @@ +/** @file dialogwidget.cpp Popup dialog. + * + * @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 + */ + +#include "ui/widgets/dialogwidget.h" +#include "ui/widgets/guirootwidget.h" +#include "dd_main.h" + +#include + +using namespace de; + +DENG2_PIMPL(DialogWidget), +DENG2_OBSERVES(ContextWidgetOrganizer, WidgetCreation) +{ + Modality modality; + ScrollAreaWidget *area; + MenuWidget *buttons; + QEventLoop subloop; + + Instance(Public *i) : Base(i), modality(Modal) + { + area = new ScrollAreaWidget("area"); + buttons = new MenuWidget("buttons"); + + buttons->organizer().audienceForWidgetCreation += 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); + + area->rule() + .setInput(Rule::Left, self.rule().left()) + .setInput(Rule::Top, self.rule().top()) + .setInput(Rule::Width, area->contentRule().width() + area->margin() * 2) + .setInput(Rule::Height, area->contentRule().height() + area->margin() * 2); + + // Buttons below the area. + buttons->rule() + .setInput(Rule::Top, area->rule().bottom() - area->margin()) // overlap margins + .setInput(Rule::Right, self.rule().right()); + + // A blank container widget acts as the popup content parent. + GuiWidget *container = new GuiWidget("container"); + container->rule() + .setInput(Rule::Width, OperatorRule::maximum(area->rule().width(), buttons->rule().width())) + .setInput(Rule::Height, area->rule().height() + buttons->rule().height() - + area->margin()); + container->add(area); + container->add(buttons); + self.setContent(container); + } + + void widgetCreatedForItem(GuiWidget &widget, ui::Item const &) + { + // Make sure all label-based widgets in the button area + // manage their own size. + if(widget.is()) + { + widget.as().setSizePolicy(ui::Expand, ui::Expand); + } + } +}; + +DialogWidget::DialogWidget(String const &name) + : PopupWidget(name), d(new Instance(this)) +{ + setOpeningDirection(ui::NoDirection); + + if(!App_GameLoaded()) // blurring is not yet compatible with game rendering + { + Background bg = background(); + bg.type = Background::BlurredWithBorderGlow; + bg.solidFill = Vector4f(0, 0, 0, .65f); + set(bg); + } +} + +void DialogWidget::setModality(DialogWidget::Modality modality) +{ + d->modality = modality; +} + +DialogWidget::Modality DialogWidget::modality() const +{ + return d->modality; +} + +ScrollAreaWidget &DialogWidget::content() +{ + return *d->area; +} + +MenuWidget &DialogWidget::buttons() +{ + return *d->buttons; +} + +int DialogWidget::exec(GuiRootWidget &root) +{ + // The widget is added to the root temporarily (as top child). + DENG2_ASSERT(!hasRoot()); + root.add(this); + + prepare(); + + int result = d->subloop.exec(); + + finish(result); + + return result; +} + +bool DialogWidget::handleEvent(Event const &event) +{ + if(d->modality == Modal) + { + // The event should already have been handled by the children. + return true; + } + + return PopupWidget::handleEvent(event); +} + +void DialogWidget::accept(int result) +{ + if(d->subloop.isRunning()) + { + d->subloop.exit(result); + emit accepted(result); + } +} + +void DialogWidget::reject(int result) +{ + if(d->subloop.isRunning()) + { + d->subloop.exit(result); + emit rejected(result); + } +} + +void DialogWidget::preparePopupForOpening() +{ + PopupWidget::preparePopupForOpening(); + + // Redo the layout (items visible now). + d->buttons->updateLayout(); +} + +void DialogWidget::prepare() +{ + // Center the dialog. + setAnchor(root().viewWidth() / 2, root().viewHeight() / 2); + d->buttons->updateLayout(); + + // Make sure the newly added widget knows the view size. + viewResized(); + notifyTree(&Widget::viewResized); + + open(); +} + +void DialogWidget::finish(int) +{ + close(); +}