1 /** @file widgets/dialogwidget.cpp 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 #include "de/DialogWidget"
20 #include "de/ToggleWidget"
21 #include "de/ChoiceWidget"
22 #include "de/GuiRootWidget"
23 #include "de/SignalAction"
24 #include "de/DialogContentStylist"
25 #include "de/ui/FilteredData"
26
27 #include <de/KeyEvent>
28 #include <de/MouseEvent>
29 #include <de/Untrapper>
30 #include <QEventLoop>
31
32 namespace de {
33
34 static TimeSpan const FLASH_ANIM_SPAN = 0.75;
35
36 /**
37 * Compares dialog button items to determine the order in which they
38 * should appear in the UI.
39 *
40 * @param a Dialog button item.
41 * @param b Dialog button item.
42 *
43 * @return @c true, if a < b.
44 */
dialogButtonOrder(ui::Item const & a,ui::Item const & b)45 static bool dialogButtonOrder(ui::Item const &a, ui::Item const &b)
46 {
47 DialogButtonItem const &left = a.as<DialogButtonItem>();
48 DialogButtonItem const &right = b.as<DialogButtonItem>();
49
50 if (!left.role().testFlag(DialogWidget::Default) && right.role().testFlag(DialogWidget::Default))
51 {
52 #ifdef MACOSX
53 // Default buttons go to the right on macOS.
54 return true;
55 #else
56 // Default buttons to the left.
57 return false;
58 #endif
59 }
60 if (left.role().testFlag(DialogWidget::Default) && !right.role().testFlag(DialogWidget::Default))
61 {
62 #ifdef MACOSX
63 // Default buttons go to the right on macOS.
64 return false;
65 #else
66 // Default buttons to the left.
67 return true;
68 #endif
69 }
70 if (a.label().isEmpty() && !b.label().isEmpty())
71 {
72 // Label-less buttons go first.
73 return true;
74 }
75
76 // Order unchanged.
77 return false;
78 }
79
DENG_GUI_PIMPL(DialogWidget)80 DENG_GUI_PIMPL(DialogWidget)
81 , DENG2_OBSERVES(ChildWidgetOrganizer, WidgetCreation)
82 , DENG2_OBSERVES(ChildWidgetOrganizer, WidgetUpdate)
83 , DENG2_OBSERVES(ui::Data, Addition)
84 , DENG2_OBSERVES(ui::Data, Removal)
85 {
86 Modality modality;
87 Flags flags;
88 ScrollAreaWidget *area = nullptr;
89 ScrollAreaWidget *rightArea = nullptr;
90 LabelWidget *heading;
91 MenuWidget *buttons;
92 MenuWidget *extraButtons;
93 ui::ListData buttonItems;
94 ui::FilteredData mainButtonItems { buttonItems };
95 ui::FilteredData extraButtonItems { buttonItems };
96 QEventLoop subloop;
97 de::Action *acceptAction;
98 Animation glow;
99 bool needButtonUpdate;
100 float normalGlow;
101 bool animatingGlow;
102 QScopedPointer<Untrapper> untrapper;
103 DialogContentStylist stylist;
104 IndirectRule *minWidth;
105 Rule const *maxContentHeight = nullptr;
106
107 Impl(Public *i, Flags const &dialogFlags)
108 : Base(i)
109 , modality(Modal)
110 , flags(dialogFlags)
111 , heading(0)
112 , acceptAction(0)
113 , needButtonUpdate(false)
114 , animatingGlow(false)
115 {
116 minWidth = new IndirectRule;
117
118 // Initialize the border glow.
119 normalGlow = style().colors().colorf("glow").w;
120 glow.setValue(normalGlow);
121 glow.setStyle(Animation::Linear);
122
123 // Set up widget structure.
124 GuiWidget *container = new GuiWidget("container");
125
126 area = new ScrollAreaWidget("area");
127
128 buttons = new MenuWidget("buttons");
129 buttons->margins().setTop("");
130 buttons->setItems(mainButtonItems);
131 buttons->organizer().audienceForWidgetCreation() += this;
132 buttons->organizer().audienceForWidgetUpdate() += this;
133
134 extraButtons = new MenuWidget("extra");
135 extraButtons->margins().setTop("");
136 extraButtons->setItems(extraButtonItems);
137 extraButtons->organizer().audienceForWidgetCreation() += this;
138 extraButtons->organizer().audienceForWidgetUpdate() += this;
139
140 buttonItems.audienceForAddition() += this;
141 buttonItems.audienceForRemoval() += this;
142
143 // Segregate Action buttons into the extra buttons set.
144 mainButtonItems.setFilter([] (ui::Item const &it)
145 {
146 if (!is<DialogButtonItem>(it)) return false;
147 // Non-Action buttons only.
148 return !it.as<DialogButtonItem>().role().testFlag(Action);
149 });
150 extraButtonItems.setFilter([] (ui::Item const &it)
151 {
152 if (!is<DialogButtonItem>(it)) return false;
153 // Only Action buttons allowed.
154 return it.as<DialogButtonItem>().role().testFlag(Action);
155 });
156
157 // The menu maintains its own width and height based on children.
158 // Set up one row with variable number of columns.
159 buttons->setGridSize(0, ui::Expand, 1, ui::Expand);
160 extraButtons->setGridSize(0, ui::Expand, 1, ui::Expand);
161
162 area->rule()
163 .setInput(Rule::Left, self().rule().left())
164 .setInput(Rule::Top, self().rule().top())
165 .setInput(Rule::Width, area->contentRule().width() + area->margins().width());
166
167 // Will a title be included?
168 if (flags & WithHeading)
169 {
170 heading = new LabelWidget("heading");
171 heading->setFont("heading");
172 heading->margins()
173 .setBottom("")
174 .setTop (rule("gap") + rule("dialog.gap"))
175 .setLeft(rule("gap") + rule("dialog.gap"));
176 heading->setSizePolicy(ui::Expand, ui::Expand);
177 heading->setTextColor("accent");
178 heading->setImageColor(style().colors().colorf("accent"));
179 heading->setOverrideImageSize(heading->font().ascent());
180 heading->setTextGap("dialog.gap");
181 heading->setAlignment(ui::AlignLeft);
182 heading->setTextAlignment(ui::AlignRight);
183 heading->setTextLineAlignment(ui::AlignLeft);
184 heading->setFillMode(LabelWidget::FillWithText);
185 container->add(heading);
186
187 heading->rule()
188 .setInput(Rule::Top, self().rule().top())
189 .setInput(Rule::Left, self().rule().left());
190
191 area->rule().setInput(Rule::Top, heading->rule().bottom());
192 }
193
194 area->rule().setInput(Rule::Height, container->rule().height() - buttons->rule().height());
195
196 // Buttons below the area.
197 buttons->rule()
198 .setInput(Rule::Bottom, container->rule().bottom())
199 .setInput(Rule::Right, self().rule().right());
200 extraButtons->rule()
201 .setInput(Rule::Top, buttons->rule().top())
202 .setInput(Rule::Left, self().rule().left());
203
204 // A blank container widget acts as the popup content parent.
205 container->rule().setInput(Rule::Width, OperatorRule::maximum(
206 area->rule().width(),
207 buttons->rule().width() + extraButtons->rule().width(),
208 *minWidth));
209
210 if (flags.testFlag(WithHeading))
211 {
212 area->rule().setInput(Rule::Height,
213 container->rule().height()
214 - heading->rule().height()
215 - buttons->rule().height());
216 }
217
218 container->add(area);
219 container->add(extraButtons);
220 container->add(buttons);
221 self().setContent(container);
222 }
223
224 ~Impl()
225 {
226 releaseRef(minWidth);
227 releaseRef(maxContentHeight);
228 releaseRef(acceptAction);
229 }
230
231 void setupForTwoColumns()
232 {
233 // Create an additional content area.
234 if (!rightArea)
235 {
236 rightArea = new ScrollAreaWidget("rightArea");
237 self().content().add(rightArea);
238
239 rightArea->rule()
240 .setInput(Rule::Top, area->rule().top())
241 .setInput(Rule::Left, area->rule().right())
242 .setInput(Rule::Height, area->rule().height())
243 .setInput(Rule::Width, rightArea->contentRule().width() + rightArea->margins().width());
244
245 if (heading)
246 {
247 heading->rule().setInput(Rule::Right, rightArea->rule().right());
248 }
249
250 // Content size is now wider.
251 self().content().rule().setInput(Rule::Width, OperatorRule::maximum(
252 area->rule().width() + rightArea->rule().width(),
253 buttons->rule().width() + extraButtons->rule().width(),
254 *minWidth));
255
256 if (self().isOpen()) updateContentHeight();
257 }
258 }
259
260 void updateContentHeight()
261 {
262 // Determine suitable maximum height.
263 Rule const *maxHeight = holdRef(root().viewHeight());
264 if (self().openingDirection() == ui::Down)
265 {
266 changeRef(maxHeight, *maxHeight - self().anchor().top() - rule("gap"));
267 }
268 if (maxContentHeight)
269 {
270 changeRef(maxHeight, OperatorRule::minimum(*maxHeight, *maxContentHeight));
271 }
272
273 // Scrollable area content height.
274 AutoRef<Rule> areaContentHeight = area->contentRule().height() + area->margins().height();
275 if (rightArea)
276 {
277 areaContentHeight.reset(OperatorRule::maximum(areaContentHeight,
278 rightArea->contentRule().height() + rightArea->margins().height()));
279 }
280
281 // The container's height is limited by the height of the view. Normally
282 // the dialog tries to show the full height of the content area.
283 if (!flags.testFlag(WithHeading))
284 {
285 self().content().rule().setInput(Rule::Height,
286 OperatorRule::minimum(*maxHeight, areaContentHeight + buttons->rule().height()));
287 }
288 else
289 {
290 self().content().rule().setInput(Rule::Height,
291 OperatorRule::minimum(*maxHeight, (heading? heading->rule().height() : Const(0)) +
292 areaContentHeight + buttons->rule().height()));
293 }
294
295 releaseRef(maxHeight);
296 }
297
298 void dataItemAdded(ui::Data::Pos, ui::Item const &)
299 {
300 needButtonUpdate = true;
301 }
302
303 void dataItemRemoved(ui::Data::Pos, ui::Item &)
304 {
305 needButtonUpdate = true;
306 }
307
308 void updateButtonLayout()
309 {
310 buttonItems.stableSort(dialogButtonOrder);
311 needButtonUpdate = false;
312 }
313
314 void widgetCreatedForItem(GuiWidget &widget, ui::Item const &item)
315 {
316 // Make sure all label-based widgets in the button area
317 // manage their own size.
318 if (LabelWidget *lab = maybeAs<LabelWidget>(widget))
319 {
320 lab->setSizePolicy(ui::Expand, ui::Expand);
321 }
322
323 // Apply dialog button specific roles.
324 if (ButtonItem const *i = maybeAs<ButtonItem>(item))
325 {
326 ButtonWidget &but = widget.as<ButtonWidget>();
327 but.setColorTheme(self().colorTheme());
328 if (!i->action())
329 {
330 if (i->role() & (Accept | Yes))
331 {
332 but.setAction(new SignalAction(thisPublic, SLOT(accept())));
333 }
334 else if (i->role() & (Reject | No))
335 {
336 but.setAction(new SignalAction(thisPublic, SLOT(reject())));
337 }
338 }
339 }
340 }
341
342 void widgetUpdatedForItem(GuiWidget &widget, ui::Item const &item)
343 {
344 if (ButtonItem const *i = maybeAs<ButtonItem>(item))
345 {
346 ButtonWidget &but = widget.as<ButtonWidget>();
347
348 // Button images must be a certain size.
349 but.setOverrideImageSize(style().fonts().font("default").height());
350
351 // Set default label?
352 if (item.label().isEmpty())
353 {
354 if (i->role().testFlag(Accept))
355 {
356 but.setText(tr("OK"));
357 }
358 else if (i->role().testFlag(Reject))
359 {
360 but.setText(tr("Cancel"));
361 }
362 else if (i->role().testFlag(Yes))
363 {
364 but.setText(tr("Yes"));
365 }
366 else if (i->role().testFlag(No))
367 {
368 but.setText(tr("No"));
369 }
370 }
371
372 // Highlight the default button(s).
373 if (i->role().testFlag(Default))
374 {
375 but.setTextColor(self().colorTheme() == Normal? "dialog.default" : "inverted.text");
376 if (self().colorTheme() == Normal)
377 {
378 but.setHoverTextColor("dialog.default", ButtonWidget::ReplaceColor);
379 }
380 but.setText(_E(b) + but.text());
381 }
382 else
383 {
384 but.setTextColor(self().colorTheme() == Normal? "text" : "inverted.text");
385 }
386 }
387 }
388
389 ui::ActionItem const *findDefaultAction() const
390 {
391 // Note: extra buttons not searched because they shouldn't contain default actions.
392
393 for (ui::Data::Pos i = 0; i < mainButtonItems.size(); ++i)
394 {
395 ButtonItem const *act = maybeAs<ButtonItem>(mainButtonItems.at(i));
396 if (act->role().testFlag(Default) &&
397 buttons->organizer().itemWidget(i)->isEnabled())
398 {
399 return act;
400 }
401 }
402 return 0;
403 }
404
405 ButtonWidget *findDefaultButton() const
406 {
407 if (ui::ActionItem const *defaultAction = findDefaultAction())
408 {
409 return &buttonWidget(*defaultAction);
410 }
411 return nullptr;
412 }
413
414 ButtonWidget &buttonWidget(ui::Item const &item) const
415 {
416 GuiWidget *w = extraButtons->organizer().itemWidget(item);
417 if (w) return w->as<ButtonWidget>();
418 // Try the normal buttons.
419 return buttons->organizer().itemWidget(item)->as<ButtonWidget>();
420 }
421
422 void startBorderFlash()
423 {
424 animatingGlow = true;
425 glow.setValueFrom(1, normalGlow, FLASH_ANIM_SPAN);
426 Background bg = self().background();
427 bg.color.w = glow;
428 self().set(bg);
429 }
430
431 void updateBorderFlash()
432 {
433 Background bg = self().background();
434 bg.color.w = glow;
435 self().set(bg);
436
437 if (glow.done()) animatingGlow = false;
438 }
439
440 void updateBackground()
441 {
442 Background bg = self().background();
443 if (self().isUsingInfoStyle())
444 {
445 bg = self().infoStyleBackground();
446 }
447 else if (style().isBlurringAllowed())
448 {
449 /// @todo Should use the Style for this.
450 bg.type = Background::SharedBlurWithBorderGlow;
451 bg.blur = style().sharedBlurWidget();
452 bg.solidFill = Vector4f(0, 0, 0, .65f);
453 }
454 else
455 {
456 bg.type = Background::BorderGlow;
457 bg.solidFill = style().colors().colorf("dialog.background");
458 }
459 self().set(bg);
460 }
461 };
462
DialogWidget(String const & name,Flags const & flags)463 DialogWidget::DialogWidget(String const &name, Flags const &flags)
464 : PopupWidget(name), d(new Impl(this, flags))
465 {
466 d->stylist.setContainer(area());
467 setOpeningDirection(ui::NoDirection);
468 d->updateBackground();
469 area().enableIndicatorDraw(true);
470 }
471
modality() const472 DialogWidget::Modality DialogWidget::modality() const
473 {
474 return d->modality;
475 }
476
heading()477 LabelWidget &DialogWidget::heading()
478 {
479 DENG2_ASSERT(d->heading != 0);
480 return *d->heading;
481 }
482
area()483 ScrollAreaWidget &DialogWidget::area()
484 {
485 return *d->area;
486 }
487
leftArea()488 ScrollAreaWidget &DialogWidget::leftArea()
489 {
490 d->setupForTwoColumns();
491 return *d->area;
492 }
493
rightArea()494 ScrollAreaWidget &DialogWidget::rightArea()
495 {
496 d->setupForTwoColumns();
497 return *d->rightArea;
498 }
499
setMinimumContentWidth(Rule const & minWidth)500 void DialogWidget::setMinimumContentWidth(Rule const &minWidth)
501 {
502 d->minWidth->setSource(minWidth);
503 }
504
setMaximumContentHeight(const de::Rule & maxHeight)505 void DialogWidget::setMaximumContentHeight(const de::Rule &maxHeight)
506 {
507 changeRef(d->maxContentHeight, maxHeight);
508 }
509
buttonsMenu()510 MenuWidget &DialogWidget::buttonsMenu()
511 {
512 return *d->buttons;
513 }
514
extraButtonsMenu()515 MenuWidget &DialogWidget::extraButtonsMenu()
516 {
517 return *d->extraButtons;
518 }
519
buttons()520 ui::Data &DialogWidget::buttons()
521 {
522 return d->buttonItems;
523 }
524
buttonWidget(String const & label) const525 ButtonWidget &DialogWidget::buttonWidget(String const &label) const
526 {
527 GuiWidget *w = d->buttons->organizer().itemWidget(label);
528 if (w) return w->as<ButtonWidget>();
529
530 w = d->extraButtons->organizer().itemWidget(label);
531 if (w) return w->as<ButtonWidget>();
532
533 throw UndefinedLabel("DialogWidget::buttonWidget", "Undefined label \"" + label + "\"");
534 }
535
popupButtonWidget(String const & label) const536 PopupButtonWidget &DialogWidget::popupButtonWidget(String const &label) const
537 {
538 return buttonWidget(label).as<PopupButtonWidget>();
539 }
540
buttonWidget(int roleId) const541 ButtonWidget *DialogWidget::buttonWidget(int roleId) const
542 {
543 for (uint i = 0; i < d->buttonItems.size(); ++i)
544 {
545 DialogButtonItem const &item = d->buttonItems.at(i).as<DialogButtonItem>();
546
547 if ((item.role() & IdMask) == uint(roleId))
548 {
549 return &d->buttonWidget(item);
550 }
551 }
552 return nullptr;
553 }
554
popupButtonWidget(int roleId) const555 PopupButtonWidget *DialogWidget::popupButtonWidget(int roleId) const
556 {
557 if (auto *btn = buttonWidget(roleId))
558 {
559 return &btn->as<PopupButtonWidget>();
560 }
561 return nullptr;
562 }
563
buttonWidgets() const564 QList<ButtonWidget *> DialogWidget::buttonWidgets() const
565 {
566 QList<ButtonWidget *> buttons;
567 foreach (GuiWidget *w, d->buttons->childWidgets())
568 {
569 if (auto *but = maybeAs<ButtonWidget>(w))
570 {
571 buttons << but;
572 }
573 }
574 return buttons;
575 }
576
setAcceptanceAction(RefArg<de::Action> action)577 void DialogWidget::setAcceptanceAction(RefArg<de::Action> action)
578 {
579 changeRef(d->acceptAction, action);
580 }
581
acceptanceAction() const582 Action *DialogWidget::acceptanceAction() const
583 {
584 return d->acceptAction;
585 }
586
exec(GuiRootWidget & root)587 int DialogWidget::exec(GuiRootWidget &root)
588 {
589 d->modality = Modal;
590
591 // The widget is added to the root temporarily (as top child).
592 DENG2_ASSERT(!hasRoot());
593 root.add(this);
594
595 prepare();
596
597 int result = 0;
598 try
599 {
600 #if defined (DENG_MOBILE)
601 // The subloop will likely access the root independently.
602 root.unlock();
603 #endif
604
605 result = d->subloop.exec();
606
607 #if defined (DENG_MOBILE)
608 root.lock();
609 #endif
610 }
611 catch (...)
612 {
613 #if defined (DENG_MOBILE)
614 // The lock needs to be reacquired in any case.
615 root.lock();
616 #endif
617 throw;
618 }
619
620 finish(result);
621 return result;
622 }
623
open()624 void DialogWidget::open()
625 {
626 open(NonModal);
627 }
628
open(Modality modality)629 void DialogWidget::open(Modality modality)
630 {
631 d->modality = modality;
632
633 DENG2_ASSERT(hasRoot());
634 prepare(); // calls base class's open()
635 }
636
defaultActionItem()637 ui::ActionItem *DialogWidget::defaultActionItem()
638 {
639 return const_cast<ui::ActionItem *>(d->findDefaultAction());
640 }
641
offerFocus()642 void DialogWidget::offerFocus()
643 {
644 root().setFocus(d->findDefaultButton());
645 }
646
update()647 void DialogWidget::update()
648 {
649 PopupWidget::update();
650
651 if (d->needButtonUpdate)
652 {
653 d->updateButtonLayout();
654 }
655
656 if (d->animatingGlow)
657 {
658 d->updateBorderFlash();
659 }
660 }
661
handleEvent(Event const & event)662 bool DialogWidget::handleEvent(Event const &event)
663 {
664 if (!isOpen()) return false;
665
666 if (event.isKeyDown())
667 {
668 KeyEvent const &key = event.as<KeyEvent>();
669
670 if (key.ddKey() == DDKEY_ENTER ||
671 key.ddKey() == DDKEY_RETURN ||
672 key.ddKey() == ' ')
673 {
674 if (ButtonWidget *but = d->findDefaultButton())
675 {
676 but->trigger();
677 return true;
678 }
679 }
680
681 if (key.ddKey() == DDKEY_ESCAPE)
682 {
683 // Esc always cancels a dialog.
684 reject();
685 return true;
686 }
687 }
688
689 if (d->modality == Modal)
690 {
691 // The event should already have been handled by the children.
692 if ((event.isKeyDown() && !event.as<KeyEvent>().isModifier()) ||
693 (event.type() == Event::MouseButton &&
694 event.as<MouseEvent>().state() == MouseEvent::Pressed &&
695 !hitTest(event)))
696 {
697 d->startBorderFlash();
698 }
699 return true;
700 }
701 else
702 {
703 if ((event.type() == Event::MouseButton || event.type() == Event::MousePosition ||
704 event.type() == Event::MouseWheel) &&
705 hitTest(event))
706 {
707 // Non-modal dialogs eat mouse clicks/position inside the dialog.
708 return true;
709 }
710 }
711
712 return PopupWidget::handleEvent(event);
713 }
714
accept(int result)715 void DialogWidget::accept(int result)
716 {
717 if (d->subloop.isRunning())
718 {
719 DENG2_ASSERT(d->modality == Modal);
720 d->subloop.exit(result);
721 emit accepted(result);
722 }
723 else
724 {
725 emit accepted(result);
726 finish(result);
727 }
728 }
729
reject(int result)730 void DialogWidget::reject(int result)
731 {
732 if (d->subloop.isRunning())
733 {
734 DENG2_ASSERT(d->modality == Modal);
735 d->subloop.exit(result);
736 emit rejected(result);
737 }
738 else
739 {
740 emit rejected(result);
741 finish(result);
742 }
743 }
744
prepare()745 void DialogWidget::prepare()
746 {
747 // Mouse needs to be untrapped for the user to be access the dialog.
748 d->untrapper.reset(new Untrapper(root().window()));
749
750 if (openingDirection() == ui::NoDirection)
751 {
752 // Center the dialog.
753 setAnchor(root().viewWidth() / 2, root().viewHeight() / 2);
754 }
755
756 d->updateContentHeight();
757
758 PopupWidget::open();
759 }
760
preparePanelForOpening()761 void DialogWidget::preparePanelForOpening()
762 {
763 PopupWidget::preparePanelForOpening();
764
765 // Redo the layout (items visible now).
766 d->buttons->updateLayout();
767 d->extraButtons->updateLayout();
768
769 d->updateBackground();
770 }
771
finish(int result)772 void DialogWidget::finish(int result)
773 {
774 root().setFocus(nullptr);
775 close();
776
777 d->untrapper.reset();
778
779 if (result > 0)
780 {
781 // Success!
782 if (d->acceptAction)
783 {
784 AutoRef<de::Action> held = *d->acceptAction;
785 held->trigger();
786 }
787 }
788 }
789
ButtonItem(RoleFlags flags,String const & label)790 DialogWidget::ButtonItem::ButtonItem(RoleFlags flags, String const &label)
791 : ui::ActionItem(itemSemantics(flags), label, 0)
792 , _role(flags)
793 {}
794
ButtonItem(RoleFlags flags,Image const & image)795 DialogWidget::ButtonItem::ButtonItem(RoleFlags flags, Image const &image)
796 : ui::ActionItem(itemSemantics(flags), image)
797 , _role(flags)
798 {}
799
ButtonItem(RoleFlags flags,String const & label,RefArg<de::Action> action)800 DialogWidget::ButtonItem::ButtonItem(RoleFlags flags, String const &label, RefArg<de::Action> action)
801 : ui::ActionItem(itemSemantics(flags), label, action)
802 , _role(flags)
803 {}
804
ButtonItem(RoleFlags flags,Image const & image,RefArg<de::Action> action)805 DialogWidget::ButtonItem::ButtonItem(RoleFlags flags, Image const &image, RefArg<de::Action> action)
806 : ui::ActionItem(itemSemantics(flags), image, "", action)
807 , _role(flags)
808 {}
809
ButtonItem(RoleFlags flags,Image const & image,String const & label,RefArg<de::Action> action)810 DialogWidget::ButtonItem::ButtonItem(RoleFlags flags, Image const &image, String const &label, RefArg<de::Action> action)
811 : ui::ActionItem(itemSemantics(flags), image, label, action)
812 , _role(flags)
813 {}
814
itemSemantics(RoleFlags flags)815 ui::Item::Semantics DialogWidget::ButtonItem::itemSemantics(RoleFlags flags)
816 {
817 Semantics smt = ActivationClosesPopup | ShownAsButton;
818 if (flags & Popup) smt |= ShownAsPopupButton;
819 return smt;
820 }
821
822 } // namespace de
823