From 8cb65fefa65b4c03b10f5b0ebf1bfdd55b7ae457 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Sat, 20 Jan 2018 17:21:13 +0100 Subject: [PATCH] better focus handover after closing a popup --- examples/messagebox/main.cpp | 16 +++- src/controls/QskControl.cpp | 21 +++++ src/controls/QskControl.h | 2 + src/controls/QskFocusIndicator.cpp | 4 +- src/controls/QskPopup.cpp | 135 ++++++++++++++++++----------- src/controls/QskTabView.cpp | 3 - 6 files changed, 126 insertions(+), 55 deletions(-) diff --git a/examples/messagebox/main.cpp b/examples/messagebox/main.cpp index d4df9e66..aca184c2 100644 --- a/examples/messagebox/main.cpp +++ b/examples/messagebox/main.cpp @@ -36,6 +36,8 @@ public: ButtonBox( QQuickItem* parent = nullptr ): QskLinearBox( Qt::Horizontal, 2, parent ) { + setObjectName( "ButtonBox" ); + setMargins( 10 ); setSpacing( 5 ); @@ -130,9 +132,19 @@ int main( int argc, char* argv[] ) qskDialog->setPolicy( QskDialog::EmbeddedBox ); ButtonBox* box = new ButtonBox(); - box->itemAtIndex( 0 )->setFocus( true ); - box->setObjectName( "ButtonBox" ); + /* + To avoid losing the focus, when a message box is executed + we have to define the "main window" ( here a ButtonBox ) to + be a focusScope. + */ + box->setFlag( QQuickItem::ItemIsFocusScope, true ); + box->setTabFence( true ); + box->setFocusPolicy( Qt::TabFocus ); + + // setting the initial focus + box->itemAtIndex( 0 )->setFocus( true ); + box->setFocus( true ); QskWindow window; window.addItem( box ); diff --git a/src/controls/QskControl.cpp b/src/controls/QskControl.cpp index a8a2e29f..437bac8c 100644 --- a/src/controls/QskControl.cpp +++ b/src/controls/QskControl.cpp @@ -93,6 +93,27 @@ bool qskIsTransparentForPositioner( const QQuickItem* item ) return QQuickItemPrivate::get( item )->isTransparentForPositioner(); } +QQuickItem* qskNearestFocusScope( const QQuickItem* item ) +{ + if ( item ) + { + for ( QQuickItem* scope = item->parentItem(); + scope != nullptr; scope = scope->parentItem() ) + { + if ( scope->isFocusScope() ) + return scope; + } + + /* + As the default setting of the root item is to be a focus scope + we usually never get here - beside the flag has been explicitely + disabled in application code. + */ + } + + return nullptr; +} + const QSGNode* qskItemNode( const QQuickItem* item ) { if ( item == nullptr ) diff --git a/src/controls/QskControl.h b/src/controls/QskControl.h index f78bcbf1..681c9177 100644 --- a/src/controls/QskControl.h +++ b/src/controls/QskControl.h @@ -246,6 +246,8 @@ QSK_EXPORT bool qskIsTransparentForPositioner( const QQuickItem* ); QSK_EXPORT bool qskIsTabFence( const QQuickItem* ); QSK_EXPORT bool qskIsShortcutScope( const QQuickItem* ); +QSK_EXPORT QQuickItem* qskNearestFocusScope( const QQuickItem* ); + QSK_EXPORT const QSGNode* qskItemNode( const QQuickItem* ); QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* ); diff --git a/src/controls/QskFocusIndicator.cpp b/src/controls/QskFocusIndicator.cpp index 536b6055..72a6138d 100644 --- a/src/controls/QskFocusIndicator.cpp +++ b/src/controls/QskFocusIndicator.cpp @@ -19,6 +19,7 @@ static void qskSetupGeometryConnections( QObject::connect( sender, SIGNAL( yChanged() ), receiver, method ); QObject::connect( sender, SIGNAL( widthChanged() ), receiver, method ); QObject::connect( sender, SIGNAL( heightChanged() ), receiver, method ); + QObject::connect( sender, SIGNAL( visibleChanged() ), receiver, method ); bool hasIndicatorSignal = ( qobject_cast< const QskControl* >( sender ) != nullptr ); if ( !hasIndicatorSignal ) @@ -143,7 +144,8 @@ QRectF QskFocusIndicator::focusRect() const if ( window() && parentItem() ) { const QQuickItem* focusItem = window()->activeFocusItem(); - if ( focusItem && ( focusItem != window()->contentItem() ) ) + if ( focusItem && ( focusItem != this ) + && ( focusItem != window()->contentItem() ) ) { const auto rect = qskFocusIndicatorRect( focusItem ); return parentItem()->mapRectFromItem( focusItem, rect ); diff --git a/src/controls/QskPopup.cpp b/src/controls/QskPopup.cpp index 70854192..f203517a 100644 --- a/src/controls/QskPopup.cpp +++ b/src/controls/QskPopup.cpp @@ -6,7 +6,8 @@ #include "QskPopup.h" #include "QskAspect.h" #include -#include +#include +#include #include QSK_QT_PRIVATE_BEGIN @@ -16,25 +17,7 @@ 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; - } - - /* - As the default setting of the root item is to be a focus scope - we usually never get here - beside the flag has been explicitely - disabled in application code. - */ - - return nullptr; -} - -static void qskSetFocus( QQuickItem* item, bool on ) +static void qskSetFocusInScope( QQuickItem* item, bool on ) { if ( item->window() == nullptr ) return; @@ -45,8 +28,7 @@ static void qskSetFocus( QQuickItem* item, bool on ) QQuickWindowPrivate::setFocusInScope/clearFocusInScope directly, */ - const auto scope = qskNearestFocusScope( item ); - if ( scope ) + if ( const auto scope = qskNearestFocusScope( item ) ) { auto dw = QQuickWindowPrivate::get( item->window() ); @@ -57,6 +39,71 @@ static void qskSetFocus( QQuickItem* item, bool on ) } } +static QQuickItem* qskNextFocusItem( const QskPopup* popup ) +{ + if ( popup == nullptr || popup->parentItem() == nullptr ) + return nullptr; + + const auto children = popup->parentItem()->childItems(); + if ( children.count() <= 1 ) + return nullptr; + + QskPopup* modalPopup = nullptr; + + for ( auto child : children ) + { + if ( ( child != popup ) && child->isVisible() ) + { + if ( auto otherPopup = qobject_cast< QskPopup* >( child ) ) + { + if ( !otherPopup->isModal() || ( modalPopup != nullptr ) ) + { + /* + We can't decide, wether to give the focus to + one of the popups or the top level item + */ + return nullptr; + } + + modalPopup = otherPopup; + } + } + } + + if ( modalPopup ) + { + // Exactly one popup, that is modal. + return modalPopup; + } + + const auto tabFocusBehavior = QGuiApplication::styleHints()->tabFocusBehavior(); + + int i = 0; + while( children[i] != popup ) + i++; + + for ( int j = i - 1; j != i; j-- ) + { + auto item = children[j]; + if ( item->isEnabled() && item->isVisible() ) + { + if ( item->activeFocusOnTab() ) + { + if ( ( tabFocusBehavior == Qt::TabFocusAllControls ) || + QQuickItemPrivate::canAcceptTabFocus( item ) ) + { + return item; + } + } + } + + if ( j == 0 ) + j = children.count(); + } + + return nullptr; +} + namespace { class InputGrabber final : public QQuickItem @@ -194,16 +241,17 @@ public: inputGrabber( nullptr ), isModal( false ), isOpen( false ), - autoGrabFocus( true ) + autoGrabFocus( true ), + handoverFocus( true ) { } InputGrabber* inputGrabber; - QPointer< QQuickItem > initialFocusItem; bool isModal : 1; bool isOpen : 1; bool autoGrabFocus : 1; + bool handoverFocus : 1; }; QskPopup::QskPopup( QQuickItem* parent ): @@ -217,7 +265,7 @@ QskPopup::QskPopup( QQuickItem* parent ): setFlag( ItemIsFocusScope, true ); setTabFence( true ); - setFocusPolicy( Qt::ClickFocus ); + setFocusPolicy( Qt::StrongFocus ); } QskPopup::~QskPopup() @@ -286,35 +334,31 @@ bool QskPopup::hasOverlay() const 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; if ( on ) { - if ( auto scope = qskNearestFocusScope( this ) ) - { - m_data->initialFocusItem = scope->scopedFocusItem(); - qskSetFocus( this, true ); - } + qskSetFocusInScope( this, true ); } else { - QQuickItem* focusItem = m_data->initialFocusItem; - m_data->initialFocusItem = nullptr; + QQuickItem* focusItem = nullptr; - if ( focusItem == nullptr ) - focusItem = nextItemInFocusChain( false ); + if ( m_data->handoverFocus ) + { + /* + Qt/Quick does not handover the focus to another item, + when the active focus gets lost. For the situation of + a popup being closed we try to do it. + */ + focusItem = qskNextFocusItem( this ); + } if ( focusItem ) - qskSetFocus( focusItem, true ); + qskSetFocusInScope( focusItem, true ); else - qskSetFocus( this, false ); + qskSetFocusInScope( this, false ); } } @@ -384,13 +428,6 @@ void QskPopup::itemChange( QQuickItem::ItemChange change, break; } - case QQuickItem::ItemActiveFocusHasChanged: - { - if ( !hasFocus() ) - m_data->initialFocusItem = nullptr; - - break; - } default: ; diff --git a/src/controls/QskTabView.cpp b/src/controls/QskTabView.cpp index 50e9dedb..0f453187 100644 --- a/src/controls/QskTabView.cpp +++ b/src/controls/QskTabView.cpp @@ -108,9 +108,6 @@ int QskTabView::insertTab( int index, QskTabButton* button, QQuickItem* item ) index = m_data->tabBar->insertTab( index, button ); m_data->stackBox->insertItem( index, item, Qt::Alignment() ); - if ( m_data->tabBar->count() == 1 ) - button->setFocus( true ); - return index; }