From 33efb2d17acaf0b12f8184c84e6920c684f107c8 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Thu, 2 Nov 2017 16:25:15 +0100 Subject: [PATCH] better focus handling for popups --- src/controls/QskPopup.cpp | 92 +++++++++++++++++++++++++++++- src/controls/QskPopup.h | 3 + src/controls/QskSubWindow.cpp | 8 --- src/controls/QskSubWindow.h | 1 - src/controls/QskWindow.h | 3 +- src/dialogs/QskDialogSubWindow.cpp | 5 -- 6 files changed, 95 insertions(+), 17 deletions(-) diff --git a/src/controls/QskPopup.cpp b/src/controls/QskPopup.cpp index 57296ba1..9eb58d6d 100644 --- a/src/controls/QskPopup.cpp +++ b/src/controls/QskPopup.cpp @@ -6,6 +6,7 @@ #include "QskPopup.h" #include "QskAspect.h" #include +#include #include QSK_QT_PRIVATE_BEGIN @@ -14,6 +15,18 @@ QSK_QT_PRIVATE_END QSK_SUBCONTROL( QskPopup, Overlay ) +static inline QQuickItem* qskNearestFocusScope( const QQuickItem* item ) +{ + for ( QQuickItem* scope = item->parentItem(); + scope != nullptr; scope = scope->parentItem() ) + { + if ( scope->isFocusScope() ) + return scope; + } + + return nullptr; +} + namespace { class InputGrabber final : public QQuickItem @@ -149,12 +162,18 @@ class QskPopup::PrivateData public: PrivateData(): inputGrabber( nullptr ), - isModal( false ) + isModal( false ), + isOpen( false ), + autoGrabFocus( true ) { } InputGrabber* inputGrabber; + QPointer< QQuickItem > initialFocusItem; + bool isModal : 1; + bool isOpen : 1; + bool autoGrabFocus : 1; }; QskPopup::QskPopup( QQuickItem* parent ): @@ -168,6 +187,7 @@ QskPopup::QskPopup( QQuickItem* parent ): setFlag( ItemIsFocusScope ); setTabFence( true ); + setFocusPolicy( Qt::ClickFocus ); } QskPopup::~QskPopup() @@ -234,6 +254,50 @@ bool QskPopup::hasOverlay() const return flagHint< bool >( QskPopup::Overlay | QskAspect::Style, true ); } +void QskPopup::grabFocus( bool on ) +{ + /* + Note, that we are grabbing the local focus, what only + has an effect on the active focus, when all surrounding + focus scopes already have the focus. + */ + + if ( on == hasFocus() ) + return; + + /* + For unknown reasons Qt::PopupFocusReason is blocked inside of + QQuickItem::setFocus. Nontheless there is specific code dealing + with it f.e in qquicktextinput.cpp. If this becomes a problem + we will have to bypass QQuickItem::setFocus by calling + QQuickWindowPrivate::setFocusInScope/clearFocusInScope directly, + but for the moment we use Qt::OtherFocusReason instead. TODO ... + */ + const auto reason = Qt::OtherFocusReason; + + if ( on ) + { + if ( auto scope = qskNearestFocusScope( this ) ) + { + m_data->initialFocusItem = scope->scopedFocusItem(); + setFocus( true, reason ); + } + } + else + { + QQuickItem* focusItem = m_data->initialFocusItem; + m_data->initialFocusItem = nullptr; + + if ( focusItem == nullptr ) + focusItem = nextItemInFocusChain( false ); + + if ( focusItem ) + focusItem->setFocus( true, reason ); + else + setFocus( false, reason ); + } +} + bool QskPopup::event( QEvent* event ) { bool ok = Inherited::event( event ); @@ -260,6 +324,19 @@ bool QskPopup::event( QEvent* event ) return ok; } +void QskPopup::updateLayout() +{ + if ( !m_data->isOpen ) + { + if ( m_data->autoGrabFocus ) + grabFocus( true ); + + m_data->isOpen = true; + } + + Inherited::updateLayout(); +} + void QskPopup::itemChange( QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& value ) { @@ -270,6 +347,12 @@ void QskPopup::itemChange( QQuickItem::ItemChange change, case QQuickItem::ItemVisibleHasChanged: { updateInputGrabber(); + if ( !value.boolValue ) + { + m_data->isOpen = false; + grabFocus( false ); + } + break; } case QQuickItem::ItemParentHasChanged: @@ -281,6 +364,13 @@ void QskPopup::itemChange( QQuickItem::ItemChange change, break; } + case QQuickItem::ItemActiveFocusHasChanged: + { + if ( !hasFocus() ) + m_data->initialFocusItem = nullptr; + + break; + } default: ; diff --git a/src/controls/QskPopup.h b/src/controls/QskPopup.h index 4f31a927..63bb47e7 100644 --- a/src/controls/QskPopup.h +++ b/src/controls/QskPopup.h @@ -37,11 +37,14 @@ Q_SIGNALS: void overlayChanged(); protected: + virtual void updateLayout() override; virtual bool event( QEvent* ) override; virtual void itemChange( QQuickItem::ItemChange, const QQuickItem::ItemChangeData& ) override; + void grabFocus( bool ); + private: void updateInputGrabber(); diff --git a/src/controls/QskSubWindow.cpp b/src/controls/QskSubWindow.cpp index 4befa219..bb500f05 100644 --- a/src/controls/QskSubWindow.cpp +++ b/src/controls/QskSubWindow.cpp @@ -136,14 +136,6 @@ QRectF QskSubWindow::layoutRect() const return innerBox( Panel, rect ); } -void QskSubWindow::updateLayout() -{ - if ( !isInitiallyPainted() ) - setFocus( true ); - - Inherited::updateLayout(); -} - QSizeF QskSubWindow::contentsSizeHint() const { qreal w = -1; diff --git a/src/controls/QskSubWindow.h b/src/controls/QskSubWindow.h index 9d6e5264..0e24710c 100644 --- a/src/controls/QskSubWindow.h +++ b/src/controls/QskSubWindow.h @@ -62,7 +62,6 @@ Q_SIGNALS: protected: virtual bool event( QEvent* ) override; - virtual void updateLayout() override; virtual void itemChange( QQuickItem::ItemChange, const QQuickItem::ItemChangeData& ) override; diff --git a/src/controls/QskWindow.h b/src/controls/QskWindow.h index 89429dfd..62845ad0 100644 --- a/src/controls/QskWindow.h +++ b/src/controls/QskWindow.h @@ -26,8 +26,7 @@ class QSK_EXPORT QskWindow : public QQuickWindow WRITE setLocale RESET resetLocale NOTIFY localeChanged FINAL ) Q_PROPERTY( FramebufferMode framebufferMode READ framebufferMode - WRITE setFramebufferMode - NOTIFY framebufferModeChanged FINAL ) + WRITE setFramebufferMode NOTIFY framebufferModeChanged FINAL ) using Inherited = QQuickWindow; diff --git a/src/dialogs/QskDialogSubWindow.cpp b/src/dialogs/QskDialogSubWindow.cpp index 49f371fc..db6d1d59 100644 --- a/src/dialogs/QskDialogSubWindow.cpp +++ b/src/dialogs/QskDialogSubWindow.cpp @@ -55,8 +55,6 @@ QskDialog::DialogCode QskDialogSubWindow::exec() mouseGrabber->ungrabMouse(); } - QPointer< QQuickItem > focusItem = window()->activeFocusItem(); - show(); QEventLoop eventLoop; @@ -64,9 +62,6 @@ QskDialog::DialogCode QskDialogSubWindow::exec() connect( this, &QskDialogSubWindow::finished, &eventLoop, &QEventLoop::quit ); ( void ) eventLoop.exec( QEventLoop::DialogExec ); - if ( focusItem ) - focusItem->setFocus( true ); - return m_result; }