From 73d911cee22c48144002cf7429c597833f6f04c3 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 2 Jul 2018 08:08:38 +0200 Subject: [PATCH] QskInputGrabber added --- src/controls/QskInputGrabber.cpp | 218 +++++++++++++++++++++++ src/controls/QskInputGrabber.h | 36 ++++ src/controls/QskPopup.cpp | 266 +++++++++++------------------ src/controls/QskPopup.h | 24 ++- src/dialogs/QskDialogSubWindow.cpp | 39 +++-- src/dialogs/QskDialogSubWindow.h | 4 - src/src.pro | 2 + 7 files changed, 403 insertions(+), 186 deletions(-) create mode 100644 src/controls/QskInputGrabber.cpp create mode 100644 src/controls/QskInputGrabber.h diff --git a/src/controls/QskInputGrabber.cpp b/src/controls/QskInputGrabber.cpp new file mode 100644 index 00000000..9faf90e1 --- /dev/null +++ b/src/controls/QskInputGrabber.cpp @@ -0,0 +1,218 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskInputGrabber.h" +#include "QskWindow.h" +#include "QskQuick.h" +#include + +QSK_QT_PRIVATE_BEGIN +#include +#include +QSK_QT_PRIVATE_END + +class QskInputGrabber::PrivateData : public QQuickItemChangeListener +{ +public: + PrivateData( QskInputGrabber* grabber ): + m_grabber( grabber ) + { + } + + inline QRectF grabberRect() const + { + if ( itemBelow && itemAbove ) + { + const auto pos = itemBelow->mapToItem( itemAbove, QPointF() ); + return QRectF( pos.x(), pos.y(), itemBelow->width(), itemBelow->height() ); + } + + return QRectF(); + } + + void enableListener( QQuickItem* item, + QQuickItemPrivate::ChangeTypes types, bool on ) + { + if ( item ) + { + auto d = QQuickItemPrivate::get( item ); + + if ( on ) + d->addItemChangeListener( this, types ); + else + d->removeItemChangeListener( this, types ); + } + } + + void setup( QQuickItem* item ) + { + QQuickItem* newItemBelow = item ? item->parentItem() : nullptr; + QQuickItem* newItemAbove = item; + + if ( newItemBelow != itemBelow ) + { + const auto changeTypes = QQuickItemPrivate::Geometry; + + enableListener( itemBelow, changeTypes, false ); + enableListener( newItemBelow, changeTypes, true ); + + itemBelow = newItemBelow; + } + + if ( newItemAbove != itemAbove ) + { + const auto changeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Parent; + + enableListener( itemAbove, changeTypes, false ); + enableListener( newItemAbove, changeTypes, true ); + + itemAbove = newItemAbove; + } + } + +private: + +#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) + virtual void itemGeometryChanged( QQuickItem* item, + QQuickGeometryChange change, const QRectF& ) override final + { + bool doUpdate = false; + + if ( item == itemAbove ) + doUpdate = change.positionChange(); + else + doUpdate = change.sizeChange(); + + if ( doUpdate ) + m_grabber->updateGeometry(); + + } +#else + virtual void itemGeometryChanged( + QQuickItem* item, const QRectF& newRect, const QRectF& oldRect ) override + { + bool doUpdate = false; + + if ( item == itemAbove ) + doUpdate = newRect.size() != oldRect.size(); + else + doUpdate = newRect.topLeft() != oldRect.topLeft(); + + if ( doUpdate ) + m_grabber->updateGeometry(); + } +#endif + + virtual void itemParentChanged( QQuickItem* item, QQuickItem* parentItem ) override + { + if ( item == m_grabber && parentItem ) + { + setup( parentItem ); + m_grabber->updateGeometry(); + } + } + + QskInputGrabber* m_grabber; + +public: + QPointer< QQuickItem > itemAbove; + QPointer< QQuickItem > itemBelow; +}; + +QskInputGrabber::QskInputGrabber( QQuickItem* parent ) : + Inherited( parent ), + m_data( new PrivateData( this ) ) +{ + setAcceptedMouseButtons( Qt::AllButtons ); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + setAcceptTouchEvents( true ); +#endif + setAcceptHoverEvents( true ); + + setTransparentForPositioner( true ); + setFlag( QQuickItem::ItemHasContents, false ); + + m_data->setup( parent ); + updateGeometry(); +} + +QskInputGrabber::~QskInputGrabber() +{ + m_data->setup( nullptr ); +} + +bool QskInputGrabber::event( QEvent* event ) +{ + bool doBlock = false; + + switch ( static_cast< int >( event->type() ) ) + { + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + { + const auto ev = static_cast< QMouseEvent* > ( event ); + doBlock = isBlocking( ev->localPos() ); + break; + } + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchCancel: + { +#if 1 + // TODO + doBlock = true; +#endif + break; + } + case QEvent::Wheel: + { + const auto ev = static_cast< QWheelEvent* > ( event ); + doBlock = isBlocking( ev->posF() ); + break; + } + case QEvent::HoverEnter: + case QEvent::HoverLeave: + { + const auto ev = static_cast< QHoverEvent* > ( event ); + doBlock = isBlocking( ev->posF() ); + break; + } + } + + if ( doBlock ) + { + event->accept(); + + if ( auto w = qobject_cast< QskWindow* >( window() ) ) + w->setEventAcceptance( QskWindow::EventPropagationStopped ); + + return true; + } + + return Inherited::event( event ); +} + +bool QskInputGrabber::isBlocking( const QPointF& pos ) const +{ + if ( const auto item = parentItem() ) + return !item->contains( position() + pos ); + + return true; +} + +QRectF QskInputGrabber::grabberRect() const +{ + return m_data->grabberRect(); +} + +void QskInputGrabber::updateGeometry() +{ + const QRectF rect = grabberRect(); + if ( rect != geometry() ) + setGeometry( rect ); +} + +#include "moc_QskInputGrabber.cpp" diff --git a/src/controls/QskInputGrabber.h b/src/controls/QskInputGrabber.h new file mode 100644 index 00000000..68d8e976 --- /dev/null +++ b/src/controls/QskInputGrabber.h @@ -0,0 +1,36 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_INPUT_GRABBER_H +#define QSK_INPUT_GRABBER_H 1 + +#include "QskGlobal.h" +#include "QskControl.h" + +class QskInputGrabber: public QskControl +{ + Q_OBJECT + + using Inherited = QskControl; + +public: + QskInputGrabber( QQuickItem* parent = nullptr ); + virtual ~QskInputGrabber(); + + virtual QRectF grabberRect() const; + virtual bool isBlocking( const QPointF& ) const; + +public Q_SLOTS: + void updateGeometry(); + +protected: + virtual bool event( QEvent* ) override; + +private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/controls/QskPopup.cpp b/src/controls/QskPopup.cpp index 7c2398f4..60bb3cbf 100644 --- a/src/controls/QskPopup.cpp +++ b/src/controls/QskPopup.cpp @@ -4,9 +4,10 @@ *****************************************************************************/ #include "QskPopup.h" -#include "QskQuick.h" +#include "QskInputGrabber.h" #include "QskAspect.h" #include "QskWindow.h" +#include "QskQuick.h" #include QSK_QT_PRIVATE_BEGIN @@ -40,147 +41,45 @@ static void qskSetFocus( QQuickItem* item, bool on ) namespace { - class InputGrabber final : public QQuickItem + class InputGrabber final : public QskInputGrabber { - using Inherited = QQuickItem; + using Inherited = QskInputGrabber; public: - InputGrabber( QskPopup* popup ): - Inherited( popup ) + InputGrabber( QskPopup* parent ): + Inherited( parent ) { - setObjectName( QStringLiteral( "QskPopupInputGrabber" ) ); - - /* - We want to receive those events to stop them - from being propagated any further - */ - setAcceptedMouseButtons( Qt::AllButtons ); -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - setAcceptTouchEvents( true ); -#endif - setAcceptHoverEvents( true ); - - QQuickItemPrivate::get( this )->setTransparentForPositioner( true ); - - setFlag( QQuickItem::ItemHasContents, false ); - - /* - The grabber has to be adjusted to the geometry of - the parent of the popup, but being a child of the popup its - coordinate system is relative to it and needs to be adjusted - when the position of the popup changes too. - Using a QQuickItemChangeListener instead of connections - doesn't make the code much better as we need to deal with - how to remove the listener when the grand parent has changed then. - */ - - auto method = &InputGrabber::updateGeometry; - - connect( popup, &QQuickItem::xChanged, this, method ); - connect( popup, &QQuickItem::yChanged, this, method ); - - if ( QQuickItem* item = parentItem()->parentItem() ) - { - connect( item, &QQuickItem::xChanged, this, method ); - connect( item, &QQuickItem::yChanged, this, method ); - connect( item, &QQuickItem::widthChanged, this, method ); - connect( item, &QQuickItem::heightChanged, this, method ); - } - - updateGeometry(); } - protected: - virtual bool event( QEvent* event ) override final + virtual void geometryChanged( + const QRectF& newGeometry, const QRectF& oldGeometry ) override final { - bool doSwallow = false; + Inherited::geometryChanged( newGeometry, oldGeometry ); - switch ( static_cast( event->type() ) ) - { - case QEvent::MouseButtonPress: - case QEvent::MouseMove: - case QEvent::MouseButtonRelease: - { - doSwallow = handleMouseEvent( static_cast< QMouseEvent* > ( event ) ); - break; - } - case QEvent::TouchBegin: - case QEvent::TouchUpdate: - case QEvent::TouchCancel: - { - doSwallow = handleTouchEvent( static_cast< QTouchEvent* > ( event ) ); - break; - } - case QEvent::Wheel: - { - doSwallow = handleWheelEvent( static_cast< QWheelEvent* > ( event ) ); - break; - } - case QEvent::HoverEnter: - case QEvent::HoverLeave: - { - doSwallow = handleHoverEvent( static_cast< QHoverEvent* > ( event ) ); - break; - } - } - - if ( doSwallow ) - { - event->accept(); - - if ( auto w = qobject_cast< QskWindow* >( window() ) ) - w->setEventAcceptance( QskWindow::EventPropagationStopped ); - - return true; - } - - return Inherited::event( event ); - } - - private: - inline bool handleMouseEvent( const QMouseEvent* event ) - { - return !isInsidePopup( event->localPos() ); - } - - inline bool handleWheelEvent( const QWheelEvent* event ) - { - return !isInsidePopup( event->posF() ); - } - - inline bool handleTouchEvent( const QTouchEvent* ) - { -#if 1 - return true; // TODO -#endif - } - - inline bool handleHoverEvent( const QHoverEvent* event ) - { - return !isInsidePopup( event->posF() ); - } - - inline bool isInsidePopup( const QPointF& pos ) const - { - if ( const auto item = parentItem() ) - return item->contains( position() + pos ); - - return false; - } - - void updateGeometry() - { if ( auto popup = static_cast< QskPopup* >( parentItem() ) ) { - const QRectF rect = popup->grabberRect(); - if ( rect != qskItemGeometry( this ) ) - { - qskSetItemGeometry( this, rect ); + if ( popup->hasOverlay() ) + popup->update(); + } + } - if ( popup->hasOverlay() ) - popup->update(); + virtual bool event( QEvent* event ) override final + { + bool ok = Inherited::event( event ); + + if ( event->type() == QEvent::MouseButtonPress ) + { + if ( auto popup = static_cast< QskPopup* >( parentItem() ) ) + { + if ( event->isAccepted() && + ( popup->popupFlags() & QskPopup::CloseOnPressOutside ) ) + { + popup->close(); + } } } + + return ok; } }; } @@ -190,6 +89,7 @@ class QskPopup::PrivateData public: PrivateData(): inputGrabber( nullptr ), + flags( 0 ), isModal( false ), isOpen( false ), autoGrabFocus( true ), @@ -199,6 +99,7 @@ public: InputGrabber* inputGrabber; + int flags : 4; bool isModal : 1; bool isOpen : 1; @@ -226,28 +127,48 @@ QskPopup::~QskPopup() { } -QRectF QskPopup::overlayRect() const +void QskPopup::open() { - if ( hasOverlay() ) - return grabberRect(); - - return QRectF(); + setFading( true ); } -QRectF QskPopup::grabberRect() const +void QskPopup::close() { - if ( const auto item = parentItem() ) + const bool wasOpen = m_data->isOpen; + m_data->isOpen = false; + + setFading( false ); + + if ( wasOpen ) { - if ( isVisible() && m_data->isModal ) - return QRectF( -position(), QSizeF( item->width(), item->height() ) ); + Q_EMIT closed(); + + if ( testPopupFlag( DeleteOnClose ) ) + deleteLater(); } +} + +void QskPopup::setFading( bool on ) +{ + setVisible( on ); +} + +bool QskPopup::isOpen() const +{ + return m_data->isOpen; +} + +QRectF QskPopup::overlayRect() const +{ + if ( hasOverlay() && m_data->inputGrabber ) + return m_data->inputGrabber->grabberRect(); return QRectF(); } void QskPopup::updateInputGrabber() { - if ( window() && parentItem() && isVisible() && isModal() ) + if ( isVisible() && ( isModal() || testPopupFlag( CloseOnPressOutside ) ) ) { if ( m_data->inputGrabber == nullptr ) { @@ -289,6 +210,37 @@ bool QskPopup::isModal() const return m_data->isModal; } +void QskPopup::setPopupFlags( PopupFlags flags ) +{ + m_data->flags = flags; +} + +QskPopup::PopupFlags QskPopup::popupFlags() const +{ + return static_cast< PopupFlags >( m_data->flags ); +} + +void QskPopup::setPopupFlag( PopupFlag flag, bool on ) +{ + auto flags = m_data->flags; + + if ( on ) + flags |= flag; + else + flags &= ~flag; + + if ( flags != m_data->flags ) + { + m_data->flags = flags; + updateInputGrabber(); + } +} + +bool QskPopup::testPopupFlag( PopupFlag flag ) const +{ + return m_data->flags & flag; +} + void QskPopup::setOverlay( bool on ) { if ( hasOverlay() != on ) @@ -432,12 +384,12 @@ QQuickItem* QskPopup::focusSuccessor() const void QskPopup::aboutToShow() { - if ( !m_data->isOpen ) - { - if ( m_data->autoGrabFocus ) - grabFocus( true ); + m_data->isOpen = true; - m_data->isOpen = true; + if ( m_data->autoGrabFocus ) + { + // What to do, when we are hidden below another popup ?? + grabFocus( true ); } Inherited::aboutToShow(); @@ -448,33 +400,19 @@ void QskPopup::itemChange( QQuickItem::ItemChange change, { Inherited::itemChange( change, value ); - switch ( change ) + if ( change == QQuickItem::ItemVisibleHasChanged ) { - case QQuickItem::ItemVisibleHasChanged: + if ( !value.boolValue ) { updateInputGrabber(); - if ( !value.boolValue ) + grabFocus( false ); + if ( m_data->isOpen ) { - m_data->isOpen = false; - grabFocus( false ); + if ( testPopupFlag( CloseOnHide ) ) + close(); } - - break; } - case QQuickItem::ItemParentHasChanged: - case QQuickItem::ItemSceneChange: - { - delete m_data->inputGrabber; - m_data->inputGrabber = nullptr; - - updateInputGrabber(); - - break; - } - - default: - ; } } diff --git a/src/controls/QskPopup.h b/src/controls/QskPopup.h index 629278b8..c4f5e938 100644 --- a/src/controls/QskPopup.h +++ b/src/controls/QskPopup.h @@ -21,9 +21,25 @@ class QSK_EXPORT QskPopup : public QskControl public: QSK_SUBCONTROLS( Overlay ) + enum PopupFlag + { + CloseOnHide = 1 << 0, + DeleteOnClose = 1 << 1, + CloseOnPressOutside = 1 << 2 + }; + + Q_ENUM( PopupFlag ) + Q_DECLARE_FLAGS( PopupFlags, PopupFlag ) + QskPopup( QQuickItem* parent = nullptr ); virtual ~QskPopup(); + void setPopupFlags( PopupFlags ); + PopupFlags popupFlags() const; + + void setPopupFlag( PopupFlag, bool on = true ); + bool testPopupFlag( PopupFlag ) const; + void setModal( bool on = true ); bool isModal() const; @@ -31,15 +47,21 @@ public: bool hasOverlay() const; virtual QRectF overlayRect() const; - virtual QRectF grabberRect() const; + bool isOpen() const; + +public Q_SLOTS: + void open(); + void close(); Q_SIGNALS: + void closed(); void modalChanged( bool ); void overlayChanged( bool ); protected: virtual void aboutToShow() override; + virtual void setFading( bool on ); virtual bool event( QEvent* ) override; virtual void focusInEvent( QFocusEvent * ) override; diff --git a/src/dialogs/QskDialogSubWindow.cpp b/src/dialogs/QskDialogSubWindow.cpp index 175f40eb..807f7b89 100644 --- a/src/dialogs/QskDialogSubWindow.cpp +++ b/src/dialogs/QskDialogSubWindow.cpp @@ -7,27 +7,31 @@ #include #include +static inline void qskSetRejectOnClose( QskDialogSubWindow* subWindow, bool on ) +{ + if ( on ) + { + QObject::connect( subWindow, &QskPopup::closed, + subWindow, &QskDialogSubWindow::reject ); + } + else + { + QObject::disconnect( subWindow, &QskPopup::closed, + subWindow, &QskDialogSubWindow::reject ); + } +} + QskDialogSubWindow::QskDialogSubWindow( QQuickItem* parent ): Inherited( parent ), - m_result( QskDialog::Rejected ), - m_deleteOnDone( false ) + m_result( QskDialog::Rejected ) { + qskSetRejectOnClose( this, true ); } QskDialogSubWindow::~QskDialogSubWindow() { } -void QskDialogSubWindow::setDeleteOnDone( bool on ) -{ - m_deleteOnDone = on; -} - -bool QskDialogSubWindow::deleteOnDone() const -{ - return m_deleteOnDone; -} - void QskDialogSubWindow::setResult( QskDialog::DialogCode result ) { m_result = result; @@ -68,17 +72,18 @@ void QskDialogSubWindow::done( QskDialog::DialogCode result ) { m_result = result; + if ( !isOpen() ) + return; + + qskSetRejectOnClose( this, false ); + close(); + Q_EMIT finished( result ); if ( result == QskDialog::Accepted ) Q_EMIT accepted(); else Q_EMIT rejected(); - - hide(); - - if ( m_deleteOnDone ) - deleteLater(); } void QskDialogSubWindow::accept() diff --git a/src/dialogs/QskDialogSubWindow.h b/src/dialogs/QskDialogSubWindow.h index 6133c4e2..29b9d1c3 100644 --- a/src/dialogs/QskDialogSubWindow.h +++ b/src/dialogs/QskDialogSubWindow.h @@ -20,9 +20,6 @@ public: QskDialogSubWindow( QQuickItem* parent = nullptr ); virtual ~QskDialogSubWindow(); - void setDeleteOnDone( bool on ); - bool deleteOnDone() const; - Q_INVOKABLE QskDialog::DialogCode result() const; Q_INVOKABLE QskDialog::DialogCode exec(); @@ -44,7 +41,6 @@ protected: private: QskDialog::DialogCode m_result; - bool m_deleteOnDone : 1; }; #endif diff --git a/src/src.pro b/src/src.pro index 41997cbb..16ffdc8d 100644 --- a/src/src.pro +++ b/src/src.pro @@ -135,6 +135,7 @@ HEADERS += \ controls/QskGraphicLabel.h \ controls/QskGraphicLabelSkinlet.h \ controls/QskHintAnimator.h \ + controls/QskInputGrabber.h \ controls/QskListView.h \ controls/QskListViewSkinlet.h \ controls/QskObjectTree.h \ @@ -200,6 +201,7 @@ SOURCES += \ controls/QskGraphicLabel.cpp \ controls/QskGraphicLabelSkinlet.cpp \ controls/QskHintAnimator.cpp \ + controls/QskInputGrabber.cpp \ controls/QskListView.cpp \ controls/QskListViewSkinlet.cpp \ controls/QskObjectTree.cpp \