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 ):
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 );

View File

@ -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 )

View File

@ -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* );

View File

@ -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 );

View File

@ -6,7 +6,8 @@
#include "QskPopup.h"
#include "QskAspect.h"
#include <QQuickWindow>
#include <QPointer>
#include <QGuiApplication>
#include <QStyleHints>
#include <QtMath>
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:
;

View File

@ -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;
}