better focus handover after closing a popup

This commit is contained in:
Uwe Rathmann 2018-01-20 17:21:13 +01:00
parent 2cdabf34d6
commit 8cb65fefa6
6 changed files with 126 additions and 55 deletions

View File

@ -36,6 +36,8 @@ public:
ButtonBox( QQuickItem* parent = nullptr ): ButtonBox( QQuickItem* parent = nullptr ):
QskLinearBox( Qt::Horizontal, 2, parent ) QskLinearBox( Qt::Horizontal, 2, parent )
{ {
setObjectName( "ButtonBox" );
setMargins( 10 ); setMargins( 10 );
setSpacing( 5 ); setSpacing( 5 );
@ -130,9 +132,19 @@ int main( int argc, char* argv[] )
qskDialog->setPolicy( QskDialog::EmbeddedBox ); qskDialog->setPolicy( QskDialog::EmbeddedBox );
ButtonBox* box = new ButtonBox(); 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; QskWindow window;
window.addItem( box ); window.addItem( box );

View File

@ -93,6 +93,27 @@ bool qskIsTransparentForPositioner( const QQuickItem* item )
return QQuickItemPrivate::get( item )->isTransparentForPositioner(); 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 ) const QSGNode* qskItemNode( const QQuickItem* item )
{ {
if ( item == nullptr ) if ( item == nullptr )

View File

@ -246,6 +246,8 @@ QSK_EXPORT bool qskIsTransparentForPositioner( const QQuickItem* );
QSK_EXPORT bool qskIsTabFence( const QQuickItem* ); QSK_EXPORT bool qskIsTabFence( const QQuickItem* );
QSK_EXPORT bool qskIsShortcutScope( 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* qskItemNode( const QQuickItem* );
QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* ); QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* );

View File

@ -19,6 +19,7 @@ static void qskSetupGeometryConnections(
QObject::connect( sender, SIGNAL( yChanged() ), receiver, method ); QObject::connect( sender, SIGNAL( yChanged() ), receiver, method );
QObject::connect( sender, SIGNAL( widthChanged() ), receiver, method ); QObject::connect( sender, SIGNAL( widthChanged() ), receiver, method );
QObject::connect( sender, SIGNAL( heightChanged() ), receiver, method ); QObject::connect( sender, SIGNAL( heightChanged() ), receiver, method );
QObject::connect( sender, SIGNAL( visibleChanged() ), receiver, method );
bool hasIndicatorSignal = ( qobject_cast< const QskControl* >( sender ) != nullptr ); bool hasIndicatorSignal = ( qobject_cast< const QskControl* >( sender ) != nullptr );
if ( !hasIndicatorSignal ) if ( !hasIndicatorSignal )
@ -143,7 +144,8 @@ QRectF QskFocusIndicator::focusRect() const
if ( window() && parentItem() ) if ( window() && parentItem() )
{ {
const QQuickItem* focusItem = window()->activeFocusItem(); const QQuickItem* focusItem = window()->activeFocusItem();
if ( focusItem && ( focusItem != window()->contentItem() ) ) if ( focusItem && ( focusItem != this )
&& ( focusItem != window()->contentItem() ) )
{ {
const auto rect = qskFocusIndicatorRect( focusItem ); const auto rect = qskFocusIndicatorRect( focusItem );
return parentItem()->mapRectFromItem( focusItem, rect ); return parentItem()->mapRectFromItem( focusItem, rect );

View File

@ -6,7 +6,8 @@
#include "QskPopup.h" #include "QskPopup.h"
#include "QskAspect.h" #include "QskAspect.h"
#include <QQuickWindow> #include <QQuickWindow>
#include <QPointer> #include <QGuiApplication>
#include <QStyleHints>
#include <QtMath> #include <QtMath>
QSK_QT_PRIVATE_BEGIN QSK_QT_PRIVATE_BEGIN
@ -16,25 +17,7 @@ QSK_QT_PRIVATE_END
QSK_SUBCONTROL( QskPopup, Overlay ) QSK_SUBCONTROL( QskPopup, Overlay )
static inline QQuickItem* qskNearestFocusScope( const QQuickItem* item ) static void qskSetFocusInScope( QQuickItem* item, bool on )
{
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 )
{ {
if ( item->window() == nullptr ) if ( item->window() == nullptr )
return; return;
@ -45,8 +28,7 @@ static void qskSetFocus( QQuickItem* item, bool on )
QQuickWindowPrivate::setFocusInScope/clearFocusInScope directly, QQuickWindowPrivate::setFocusInScope/clearFocusInScope directly,
*/ */
const auto scope = qskNearestFocusScope( item ); if ( const auto scope = qskNearestFocusScope( item ) )
if ( scope )
{ {
auto dw = QQuickWindowPrivate::get( item->window() ); 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 namespace
{ {
class InputGrabber final : public QQuickItem class InputGrabber final : public QQuickItem
@ -194,16 +241,17 @@ public:
inputGrabber( nullptr ), inputGrabber( nullptr ),
isModal( false ), isModal( false ),
isOpen( false ), isOpen( false ),
autoGrabFocus( true ) autoGrabFocus( true ),
handoverFocus( true )
{ {
} }
InputGrabber* inputGrabber; InputGrabber* inputGrabber;
QPointer< QQuickItem > initialFocusItem;
bool isModal : 1; bool isModal : 1;
bool isOpen : 1; bool isOpen : 1;
bool autoGrabFocus : 1; bool autoGrabFocus : 1;
bool handoverFocus : 1;
}; };
QskPopup::QskPopup( QQuickItem* parent ): QskPopup::QskPopup( QQuickItem* parent ):
@ -217,7 +265,7 @@ QskPopup::QskPopup( QQuickItem* parent ):
setFlag( ItemIsFocusScope, true ); setFlag( ItemIsFocusScope, true );
setTabFence( true ); setTabFence( true );
setFocusPolicy( Qt::ClickFocus ); setFocusPolicy( Qt::StrongFocus );
} }
QskPopup::~QskPopup() QskPopup::~QskPopup()
@ -286,35 +334,31 @@ bool QskPopup::hasOverlay() const
void QskPopup::grabFocus( bool on ) 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() ) if ( on == hasFocus() )
return; return;
if ( on ) if ( on )
{ {
if ( auto scope = qskNearestFocusScope( this ) ) qskSetFocusInScope( this, true );
{
m_data->initialFocusItem = scope->scopedFocusItem();
qskSetFocus( this, true );
}
} }
else else
{ {
QQuickItem* focusItem = m_data->initialFocusItem; QQuickItem* focusItem = nullptr;
m_data->initialFocusItem = nullptr;
if ( focusItem == nullptr ) if ( m_data->handoverFocus )
focusItem = nextItemInFocusChain( false ); {
/*
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 ) if ( focusItem )
qskSetFocus( focusItem, true ); qskSetFocusInScope( focusItem, true );
else else
qskSetFocus( this, false ); qskSetFocusInScope( this, false );
} }
} }
@ -384,13 +428,6 @@ void QskPopup::itemChange( QQuickItem::ItemChange change,
break; break;
} }
case QQuickItem::ItemActiveFocusHasChanged:
{
if ( !hasFocus() )
m_data->initialFocusItem = nullptr;
break;
}
default: default:
; ;

View File

@ -108,9 +108,6 @@ int QskTabView::insertTab( int index, QskTabButton* button, QQuickItem* item )
index = m_data->tabBar->insertTab( index, button ); index = m_data->tabBar->insertTab( index, button );
m_data->stackBox->insertItem( index, item, Qt::Alignment() ); m_data->stackBox->insertItem( index, item, Qt::Alignment() );
if ( m_data->tabBar->count() == 1 )
button->setFocus( true );
return index; return index;
} }