From 5d91175d05ede934a592fbc92892fac36b56e589 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Wed, 24 Jan 2018 10:14:50 +0100 Subject: [PATCH] focus navigation improved --- src/controls/QskControl.cpp | 30 +++++++++--- src/controls/QskControl.h | 1 + src/controls/QskDirtyItemFilter.cpp | 17 +++++++ src/controls/QskFocusIndicator.cpp | 11 +++-- src/controls/QskPopup.cpp | 5 +- src/controls/QskWindow.cpp | 72 ++++++++++++++++++++++++++++- src/controls/QskWindow.h | 3 ++ 7 files changed, 124 insertions(+), 15 deletions(-) diff --git a/src/controls/QskControl.cpp b/src/controls/QskControl.cpp index 9cbc4171..c0e35151 100644 --- a/src/controls/QskControl.cpp +++ b/src/controls/QskControl.cpp @@ -42,6 +42,11 @@ static inline void qskSendEventTo( QObject* object, QEvent::Type type ) QCoreApplication::sendEvent( object, &event ); } +bool qskIsItemComplete( const QQuickItem* item ) +{ + return QQuickItemPrivate::get( item )->componentComplete; +} + bool qskIsAncestorOf( const QQuickItem* item, const QQuickItem* child ) { #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) @@ -413,6 +418,12 @@ public: bool isWheelEnabled : 1; }; +static void qskUpdateControlFlags( QskControl::Flags flags, QskControl* control ) +{ + auto d = static_cast< QskControlPrivate* >( QQuickItemPrivate::get( control ) ); + d->updateControlFlags( flags ); +} + QskControl::QskControl( QQuickItem* parent ): Inherited( *( new QskControlPrivate() ), parent ) { @@ -443,16 +454,18 @@ QskControl::QskControl( QQuickItem* parent ): qskRegistry->insert( this ); } -static void qskUpdateControlFlags( QskControl::Flags flags, QskControl* control ) -{ - auto d = static_cast< QskControlPrivate* >( QQuickItemPrivate::get( control ) ); - d->updateControlFlags( flags ); -} - QskControl::~QskControl() { if ( qskRegistry ) qskRegistry->remove( this ); + + /* + We set componentComplete to false, so that operations + that are triggered by detaching the item from its parent + can be aware of the about-to-delete state. + */ + Q_D( QskControl ); + d->componentComplete = false; } const char* QskControl::className() const @@ -1089,8 +1102,11 @@ bool QskControl::event( QEvent* event ) if ( auto focusItem = nextItemInFocusChain( true ) ) { - if ( qskIsAncestorOf( this, focusItem ) ) + if ( qskIsItemComplete( focusItem ) + && qskIsAncestorOf( this, focusItem ) ) + { focusItem->setFocus( true ); + } } } break; diff --git a/src/controls/QskControl.h b/src/controls/QskControl.h index 016e12c1..c289d0cd 100644 --- a/src/controls/QskControl.h +++ b/src/controls/QskControl.h @@ -241,6 +241,7 @@ inline QSizeF QskControl::sizeHint() const return effectiveConstraint( Qt::PreferredSize ); } +QSK_EXPORT bool qskIsItemComplete( const QQuickItem* item ); QSK_EXPORT bool qskIsAncestorOf( const QQuickItem* item, const QQuickItem *child ); QSK_EXPORT bool qskIsTransparentForPositioner( const QQuickItem* ); QSK_EXPORT bool qskIsTabFence( const QQuickItem* ); diff --git a/src/controls/QskDirtyItemFilter.cpp b/src/controls/QskDirtyItemFilter.cpp index 9f670764..8fadd973 100644 --- a/src/controls/QskDirtyItemFilter.cpp +++ b/src/controls/QskDirtyItemFilter.cpp @@ -21,6 +21,23 @@ static inline bool qskIsUpdateBlocked( const QQuickItem* item ) if ( const auto control = qobject_cast< const QskControl* >( item ) ) return control->testControlFlag( QskControl::DeferredUpdate ); } + +#if 0 + /* + Blocking items, that are outside the window would be easy, + but we have not yet found a performant way to send update notifications + when an item enters/leaves the window. TODO ... + */ + else if ( const auto control = qobject_cast< const QskControl* >( item ) ) + { + const QRectF itemRect( item->mapToScene( QPointF() ), item->size() ); + const QRectF sceneRect( 0, 0, item->window()->width(), item->window()->height() ); + + return !itemRect.intersects( sceneRect ); + } +} +#endif + return false; } diff --git a/src/controls/QskFocusIndicator.cpp b/src/controls/QskFocusIndicator.cpp index 72a6138d..323f3017 100644 --- a/src/controls/QskFocusIndicator.cpp +++ b/src/controls/QskFocusIndicator.cpp @@ -143,12 +143,13 @@ QRectF QskFocusIndicator::focusRect() const { if ( window() && parentItem() ) { - const QQuickItem* focusItem = window()->activeFocusItem(); - if ( focusItem && ( focusItem != this ) - && ( focusItem != window()->contentItem() ) ) + const QQuickItem* item = window()->activeFocusItem(); + + if ( item && ( item != this ) && item->isVisible() + && ( item != window()->contentItem() ) ) { - const auto rect = qskFocusIndicatorRect( focusItem ); - return parentItem()->mapRectFromItem( focusItem, rect ); + const auto rect = qskFocusIndicatorRect( item ); + return parentItem()->mapRectFromItem( item, rect ); } } diff --git a/src/controls/QskPopup.cpp b/src/controls/QskPopup.cpp index ce15e07e..2508b5f9 100644 --- a/src/controls/QskPopup.cpp +++ b/src/controls/QskPopup.cpp @@ -331,8 +331,11 @@ QQuickItem* QskPopup::focusSuccessor() const { auto child = *it; - if ( child != this && child->isFocusScope() ) + if ( ( child != this ) && child->isFocusScope() + && child->activeFocusOnTab() && child->isVisible() ) + { return child; + } } } diff --git a/src/controls/QskWindow.cpp b/src/controls/QskWindow.cpp index 05566bbe..09a834ba 100644 --- a/src/controls/QskWindow.cpp +++ b/src/controls/QskWindow.cpp @@ -24,12 +24,29 @@ QSK_QT_PRIVATE_END static void qskResolveLocale( QskWindow* ); static bool qskEnforcedSkin = false; -static void qskSendEventTo( QObject* object, QEvent::Type type ) +static inline void qskSendEventTo( QObject* object, QEvent::Type type ) { QEvent event( type ); QCoreApplication::sendEvent( object, &event ); } +static QQuickItem* qskDefaultFocusItem( QQuickWindow* window ) +{ + const auto children = qskPaintOrderChildItems( window->contentItem() ); + for ( auto it = children.crbegin(); it != children.crend(); ++it) + { + auto child = *it; + + if ( child->isFocusScope() && child->isVisible() + && child->isEnabled() && child->activeFocusOnTab() ) + { + return child; + } + } + + return window->contentItem()->nextItemInFocusChain( true ); +} + namespace { class ChildListener final : public QQuickItemChangeListener @@ -187,7 +204,6 @@ bool QskWindow::autoLayoutChildren() const return d->autoLayoutChildren; } - void QskWindow::addItem( QQuickItem* item ) { if ( item == nullptr ) @@ -246,9 +262,49 @@ bool QskWindow::event( QEvent* event ) return Inherited::event( event ); } +void QskWindow::keyPressEvent( QKeyEvent* event ) +{ + if ( !( event->modifiers() & ( Qt::ControlModifier | Qt::AltModifier ) ) ) + { + if ( ( event->key() == Qt::Key_Backtab ) || ( event->key() == Qt::Key_Tab ) ) + { + auto focusItem = activeFocusItem(); + if ( focusItem == nullptr || focusItem == contentItem() ) + { + /* + The Qt/Quick implementation for navigating along the + focus tab chain gives unsufficient results, when the + starting point is the root item. In this specific + situation we also have to include all items being + tab fences into consideration. + + In certain situations Qt/Quick gets even stuck in a non + terminating loop: see Qt-Bug 65943 + + So we better block the focus navigation and find the + next focus item on our own. + */ + ensureFocus( Qt::TabFocusReason ); + event->accept(); + + return; + } + } + } + + Inherited::keyPressEvent( event ); +} + +void QskWindow::keyReleaseEvent( QKeyEvent* event ) +{ + Inherited::keyReleaseEvent( event ); +} + void QskWindow::exposeEvent( QExposeEvent* event ) { + ensureFocus( Qt::OtherFocusReason ); layoutItems(); + Inherited::exposeEvent( event ); } @@ -391,6 +447,18 @@ void QskWindow::layoutItems() } } +void QskWindow::ensureFocus( Qt::FocusReason reason ) +{ + auto focusItem = contentItem()->scopedFocusItem(); + + if ( focusItem == nullptr ) + { + focusItem = qskDefaultFocusItem( this ); + if ( focusItem ) + focusItem->setFocus( true, reason ); + } +} + void QskWindow::setCustomRenderMode( const char* mode ) { class RenderJob final : public QRunnable diff --git a/src/controls/QskWindow.h b/src/controls/QskWindow.h index 62845ad0..2c9d7f15 100644 --- a/src/controls/QskWindow.h +++ b/src/controls/QskWindow.h @@ -82,8 +82,11 @@ protected: virtual bool event( QEvent* ) override; virtual void resizeEvent( QResizeEvent* ) override; virtual void exposeEvent( QExposeEvent* ) override; + virtual void keyPressEvent(QKeyEvent *) override; + virtual void keyReleaseEvent(QKeyEvent *) override; virtual void layoutItems(); + virtual void ensureFocus( Qt::FocusReason ); private: void resizeFramebuffer();