From 3b8dc2410a14b721cb2b8592f43a9b44315791ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= Date: Sat, 23 Jul 2016 22:05:16 +0300 Subject: [PATCH] Widgets|libappfw: Basic focus switching with Tab/Shift-Tab; button activation IssueID #2131 --- doomsday/sdk/libappfw/src/guiwidget.cpp | 46 +++++++++++++++++-- .../sdk/libappfw/src/widgets/buttonwidget.cpp | 17 +++++++ .../sdk/libappfw/src/widgets/dialogwidget.cpp | 37 +++++++++------ .../sdk/libappfw/src/widgets/popupwidget.cpp | 11 +++-- 4 files changed, 89 insertions(+), 22 deletions(-) diff --git a/doomsday/sdk/libappfw/src/guiwidget.cpp b/doomsday/sdk/libappfw/src/guiwidget.cpp index a8240bdbc8..cf5e4f7d40 100644 --- a/doomsday/sdk/libappfw/src/guiwidget.cpp +++ b/doomsday/sdk/libappfw/src/guiwidget.cpp @@ -351,9 +351,33 @@ DENG2_PIMPL(GuiWidget) } } - static float toDevicePixels(float logicalPixels) + GuiWidget *findNextWidgetToFocus(WalkDirection dir) { - return logicalPixels * DENG2_BASE_GUI_APP->dpiFactor(); + Rectanglei const viewRect = self.root().viewRule().recti(); + auto *widget = self.walkInOrder(dir, [this, &viewRect] (Widget &widget) + { + if (widget.behavior().testFlag(Focusable) && widget.isEnabled() && + widget.isVisible() && widget.is()) + { + // The widget's center must be in view. + if (viewRect.contains(widget.as().rule().recti().middle())) + { + // This is good. + return LoopAbort; + } + } + return LoopContinue; + }); + if (widget) + { + return widget->asPtr(); + } + return nullptr; + } + + static float toDevicePixels(double logicalPixels) + { + return float(logicalPixels * DENG2_BASE_GUI_APP->dpiFactor()); } }; @@ -729,6 +753,20 @@ bool GuiWidget::handleEvent(Event const &event) } } + if (hasFocus() && event.isKey()) + { + KeyEvent const &key = event.as(); + if (key.isKeyDown() && key.ddKey() == DDKEY_TAB) + { + if (auto *focus = d->findNextWidgetToFocus( + key.modifiers().testFlag(KeyEvent::Shift)? Backward : Forward)) + { + root().setFocus(focus); + return true; + } + } + } + if (Widget::handleEvent(event)) { return true; @@ -737,8 +775,8 @@ bool GuiWidget::handleEvent(Event const &event) if (d->attribs.testFlag(EatAllMouseEvents)) { if ((event.type() == Event::MouseButton || - event.type() == Event::MousePosition || - event.type() == Event::MouseWheel) && hitTest(event)) + event.type() == Event::MousePosition || + event.type() == Event::MouseWheel) && hitTest(event)) { return true; } diff --git a/doomsday/sdk/libappfw/src/widgets/buttonwidget.cpp b/doomsday/sdk/libappfw/src/widgets/buttonwidget.cpp index af949ff5f9..c5f63adc77 100644 --- a/doomsday/sdk/libappfw/src/widgets/buttonwidget.cpp +++ b/doomsday/sdk/libappfw/src/widgets/buttonwidget.cpp @@ -311,6 +311,23 @@ bool ButtonWidget::handleEvent(Event const &event) { if (isDisabled()) return false; + if (event.isKey() && hasFocus()) + { + KeyEvent const &key = event.as(); + if (key.ddKey() == DDKEY_RETURN || + key.ddKey() == DDKEY_ENTER || + key.ddKey() == ' ') + { + if (key.isKeyDown()) + { + d->setState(Down); + trigger(); + d->setState(Up); + } + return true; + } + } + if (event.isMouse()) { MouseEvent const &mouse = event.as(); diff --git a/doomsday/sdk/libappfw/src/widgets/dialogwidget.cpp b/doomsday/sdk/libappfw/src/widgets/dialogwidget.cpp index eb76856183..d72a172f3e 100644 --- a/doomsday/sdk/libappfw/src/widgets/dialogwidget.cpp +++ b/doomsday/sdk/libappfw/src/widgets/dialogwidget.cpp @@ -391,6 +391,15 @@ DENG_GUI_PIMPL(DialogWidget) return 0; } + ButtonWidget *findDefaultButton() const + { + if (ui::ActionItem const *defaultAction = findDefaultAction()) + { + return &buttonWidget(*defaultAction); + } + return nullptr; + } + ButtonWidget &buttonWidget(ui::Item const &item) const { GuiWidget *w = extraButtons->organizer().itemWidget(item); @@ -587,23 +596,21 @@ void DialogWidget::update() bool DialogWidget::handleEvent(Event const &event) { + if (!isOpen()) return false; + if (event.isKeyDown()) { KeyEvent const &key = event.as(); - if (key.ddKey() == DDKEY_ENTER || - key.ddKey() == DDKEY_RETURN || - key.ddKey() == ' ') + if (key.ddKey() == DDKEY_ENTER || + key.ddKey() == DDKEY_RETURN || + key.ddKey() == ' ') { - if (ui::ActionItem const *defaultAction = d->findDefaultAction()) + if (ButtonWidget *but = d->findDefaultButton()) { - ButtonWidget const &but = d->buttonWidget(*defaultAction); - if (but.action()) - { - const_cast(but.action())->trigger(); - } + but->trigger(); + return true; } - return true; } if (key.ddKey() == DDKEY_ESCAPE) @@ -618,9 +625,9 @@ bool DialogWidget::handleEvent(Event const &event) { // The event should already have been handled by the children. if ((event.isKeyDown() && !event.as().isModifier()) || - (event.type() == Event::MouseButton && - event.as().state() == MouseEvent::Pressed && - !hitTest(event))) + (event.type() == Event::MouseButton && + event.as().state() == MouseEvent::Pressed && + !hitTest(event))) { d->startBorderFlash(); } @@ -629,7 +636,7 @@ bool DialogWidget::handleEvent(Event const &event) else { if ((event.type() == Event::MouseButton || event.type() == Event::MousePosition || - event.type() == Event::MouseWheel) && + event.type() == Event::MouseWheel) && hitTest(event)) { // Non-modal dialogs eat mouse clicks/position inside the dialog. @@ -675,7 +682,7 @@ void DialogWidget::prepare() // Mouse needs to be untrapped for the user to be access the dialog. d->untrapper.reset(new Untrapper(root().window())); - root().setFocus(0); + root().setFocus(d->findDefaultButton()); if (openingDirection() == ui::NoDirection) { diff --git a/doomsday/sdk/libappfw/src/widgets/popupwidget.cpp b/doomsday/sdk/libappfw/src/widgets/popupwidget.cpp index 725dcfd494..91dd140b08 100644 --- a/doomsday/sdk/libappfw/src/widgets/popupwidget.cpp +++ b/doomsday/sdk/libappfw/src/widgets/popupwidget.cpp @@ -375,10 +375,15 @@ bool PopupWidget::handleEvent(Event const &event) } if (event.type() == Event::KeyPress || - event.type() == Event::KeyRepeat || - event.type() == Event::KeyRelease) + event.type() == Event::KeyRepeat || + event.type() == Event::KeyRelease) { - if (event.isKeyDown() && event.as().ddKey() == DDKEY_ESCAPE) + KeyEvent const &key = event.as(); + if (event.isKeyDown() && + (key.ddKey() == DDKEY_ESCAPE || + key.ddKey() == DDKEY_ENTER || + key.ddKey() == DDKEY_RETURN || + key.ddKey() == ' ')) { close(); return true;