1 /** @file widgets/dialogwidget.h  Popup dialog.
2  *
3  * @authors Copyright (c) 2013-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  *
5  * @par License
6  * LGPL: http://www.gnu.org/licenses/lgpl.html
7  *
8  * <small>This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or (at your
11  * option) any later version. This program is distributed in the hope that it
12  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
14  * General Public License for more details. You should have received a copy of
15  * the GNU Lesser General Public License along with this program; if not, see:
16  * http://www.gnu.org/licenses</small>
17  */
18 
19 #ifndef LIBAPPFW_DIALOGWIDGET_H
20 #define LIBAPPFW_DIALOGWIDGET_H
21 
22 #include "popupwidget.h"
23 #include "scrollareawidget.h"
24 #include "menuwidget.h"
25 #include "popupbuttonwidget.h"
26 
27 namespace de {
28 
29 class GuiRootWidget;
30 class DialogContentStylist;
31 
32 /**
33  * Popup dialog.
34  *
35  * The content area of a dialog is scrollable. A menu with buttons is placed in
36  * the bottom of the dialog, for the actions available to the user.
37  *
38  * The contents of the dialog should be placed under the scroll area returned
39  * by DialogWidget::area() and positioned in relation to its content rule.
40  * When the dialog is set up, one must define the size of the content scroll
41  * area (width and height rules).
42  *
43  * Note that when a widget is added to the content area, the dialog
44  * automatically applies certain common style parameters (margins, backgrounds,
45  * etc.).
46  *
47  * @par Widget Structure
48  *
49  * Dialogs are composed of several child widgets:
50  * <pre>
51  * DialogWidget    (PopupWidget)
52  *  +- container   (GuiWidget; the popup content widget)
53  *      +- heading (LabelWidget; optional)
54  *      +- area    (ScrollAreaWidget; contains actual dialog widgets)
55  *      +- buttons (MenuWidget)
56  *      +- extra   (MenuWidget; might be empty)
57  * </pre>
58  *
59  * Scrolling is set up so that the dialog height doesn't surpass the view
60  * rectangle's height. Contents of the "area" widget scroll while the other
61  * elements remain static in relation to the container.
62  *
63  * The dialog can optionally have two scrollable content areas side-by-side.
64  *
65  * @ingroup guiWidgets
66  */
67 class LIBAPPFW_PUBLIC DialogWidget : public PopupWidget
68 {
69     Q_OBJECT
70 
71 public:
72     /**
73      * Modality of the dialog. By default, dialogs are modal, meaning that
74      * while they are open, no events can get past the dialog.
75      */
76     enum Modality {
77         Modal,
78         NonModal
79     };
80 
81     enum Flag {
82         DefaultFlags = 0,
83         WithHeading  = 0x1      ///< Dialog has a heading above the content area.
84     };
85     Q_DECLARE_FLAGS(Flags, Flag)
86 
87     enum RoleFlag {
88         None    = 0,
89         Default = 0x1, ///< Pressing Space or Enter will activate this.
90         Accept  = 0x2,
91         Reject  = 0x4,
92         Yes     = 0x8,
93         No      = 0x10,
94         Action  = 0x20,
95         Popup   = 0x40, ///< Uses a PopupButtonWidget.
96 
97         ActionPopup = Action | Popup,
98 
99         IdMask  = 0xff0000,
100         Id1     = 0x010000,
101         Id2     = 0x020000,
102         Id3     = 0x030000,
103         Id4     = 0x040000
104     };
Q_DECLARE_FLAGS(RoleFlags,RoleFlag)105     Q_DECLARE_FLAGS(RoleFlags, RoleFlag)
106 
107     /**
108      * All buttons in a dialog must be ButtonItem instances or instances of
109      * derived classes.
110      *
111      * The DialogButtonItem typedef is provided for convenience.
112      */
113     class LIBAPPFW_PUBLIC ButtonItem : public ui::ActionItem
114     {
115     public:
116         /**
117          * Button with the role's default label and action.
118          * @param flags  Role flags for the button.
119          * @param label  Label for the button. If empty, the default label will be used.
120          */
121         ButtonItem(RoleFlags flags, String const &label = "");
122 
123         ButtonItem(RoleFlags flags, Image const &image);
124 
125         /**
126          * Button with custom action.
127          * @param flags   Role flags for the button.
128          * @param label   Label for the button. If empty, the default label will be used.
129          * @param action  Action for the button.
130          */
131         ButtonItem(RoleFlags flags, String const &label, RefArg<de::Action> action);
132 
133         ButtonItem(RoleFlags flags, Image const &image, RefArg<de::Action> action);
134 
135         ButtonItem(RoleFlags flags, Image const &image, String const &label,
136                    RefArg<de::Action> action = RefArg<de::Action>());
137 
138         RoleFlags role() const { return _role; }
139 
140         static Semantics itemSemantics(RoleFlags flags);
141 
142     private:
143         RoleFlags _role;
144     };
145 
146     /// Asked for a label that does not exist in the dialog. @ingroup errors
147     DENG2_ERROR(UndefinedLabel);
148 
149 public:
150     DialogWidget(String const &name = String(), Flags const &flags = DefaultFlags);
151 
152     Modality modality() const;
153 
154     /**
155      * If the dialog was created using the WithHeading flag, this will return the
156      * label used for the dialog heading.
157      */
158     LabelWidget &heading();
159 
160     ScrollAreaWidget &area();
161 
162     /**
163      * Returns the left content area. The first time this is called, the dialog
164      * layout is configured for two column areas.
165      */
166     ScrollAreaWidget &leftArea();
167 
168     /**
169      * Returns the right content area. The first time this is called, the dialog
170      * layout is configured for two column areas.
171      */
172     ScrollAreaWidget &rightArea();
173 
174     /**
175      * Sets the rule for the minimum width of the dialog. The default is that the
176      * dialog is at least as wide as the content area, or all the button widths
177      * summed together.
178      *
179      * @param minWidth  Custom minimum width for the dialog.
180      */
181     void setMinimumContentWidth(Rule const &minWidth);
182 
183     void setMaximumContentHeight(Rule const &maxHeight);
184 
185     MenuWidget &buttonsMenu();
186 
187     /**
188      * Additional buttons of the dialog, laid out opposite to the normal dialog
189      * buttons. These are used for functionality related to the dialog's content,
190      * but they don't accept or reject the dialog. For instance, shortcut for
191      * settings, showing what's new in an update, etc.
192      *
193      * @return Widget for dialog's extra buttons.
194      */
195     MenuWidget &extraButtonsMenu();
196 
197     ui::Data &buttons();
198 
199     ButtonWidget &buttonWidget(String const &label) const;
200     PopupButtonWidget &popupButtonWidget(String const &label) const;
201 
202     ButtonWidget *buttonWidget(int roleId) const;
203     PopupButtonWidget *popupButtonWidget(int roleId) const;
204 
205     QList<ButtonWidget *> buttonWidgets() const;
206 
207     /**
208      * Sets the action that will be triggered if the dialog is accepted. The action
209      * will be triggered after the dialog has started closing (called from
210      * DialogWidget::finish()).
211      *
212      * @param action  Action to trigger after the dialog has been accepted.
213      */
214     void setAcceptanceAction(RefArg<de::Action> action);
215 
216     de::Action *acceptanceAction() const;
217 
218     /**
219      * Shows the dialog and blocks execution until the dialog is closed --
220      * another event loop is started for event processing. Call either accept()
221      * or reject() to dismiss the dialog.
222      *
223      * The dialog must not be part of the widget tree when this is called.
224      *
225      * @param root  Root where to execute the dialog.
226      *
227      * @return Result code.
228      */
229     int exec(GuiRootWidget &root);
230 
231     /**
232      * Opens the dialog as non-modal. The dialog must already be added to the
233      * widget tree. Use accept() or reject() to close the dialog.
234      */
235     void open() override;
236 
237     void open(Modality modality);
238 
239     ui::ActionItem *defaultActionItem();
240 
241     // Events.
242     void offerFocus() override;
243     void update() override;
244     bool handleEvent(Event const &event) override;
245 
246 public slots:
247     void accept(int result = 1);
248     void reject(int result = 0);
249 
250 signals:
251     void accepted(int result);
252     void rejected(int result);
253 
254 protected:
255     void preparePanelForOpening() override;
256 
257     /**
258      * Derived classes can override this to do additional tasks before
259      * execution of the dialog begins. DialogWidget::prepare() must be called
260      * from the overridding methods. The focused widget is reset in the method.
261      */
262     virtual void prepare();
263 
264     /**
265      * Handles any tasks needed when the dialog is closing.
266      * DialogWidget::finish() must be called from overridding methods.
267      *
268      * @param result  Result code.
269      */
270     virtual void finish(int result);
271 
272 private:
273     DENG2_PRIVATE(d)
274 };
275 
276 typedef DialogWidget::ButtonItem DialogButtonItem;
277 
278 Q_DECLARE_OPERATORS_FOR_FLAGS(DialogWidget::Flags)
279 Q_DECLARE_OPERATORS_FOR_FLAGS(DialogWidget::RoleFlags)
280 
281 } // namespace de
282 
283 #endif // LIBAPPFW_DIALOGWIDGET_H
284