diff --git a/src/controls/QskControl.cpp b/src/controls/QskControl.cpp index a115de74..5137e7ba 100644 --- a/src/controls/QskControl.cpp +++ b/src/controls/QskControl.cpp @@ -4,9 +4,10 @@ *****************************************************************************/ #include "QskControl.h" +#include "QskControlPrivate.h" + #include "QskAspect.h" #include "QskFunctions.h" -#include "QskDirtyItemFilter.h" #include "QskEvent.h" #include "QskQuick.h" #include "QskSetup.h" @@ -15,543 +16,29 @@ #include "QskSkinHintTable.h" #include "QskLayoutConstraint.h" -#include #include #include -QSK_QT_PRIVATE_BEGIN -#include -#if defined( QT_DEBUG ) -#include -#endif -QSK_QT_PRIVATE_END - -#include -#include - QSK_SYSTEM_STATE( QskControl, Disabled, QskAspect::FirstSystemState ) QSK_SYSTEM_STATE( QskControl, Hovered, QskAspect::LastSystemState >> 1 ) QSK_SYSTEM_STATE( QskControl, Focused, QskAspect::LastSystemState ) -void qskResolveLocale( QskControl* ); // not static as being used from outside ! -static void qskUpdateControlFlags( QskControl::Flags, QskControl* ); - static inline void qskSendEventTo( QObject* object, QEvent::Type type ) { QEvent event( type ); QCoreApplication::sendEvent( object, &event ); } -static inline quint16 qskControlFlags() -{ - // we are only interested in the first 8 bits - return static_cast< quint16 >( qskSetup->controlFlags() ); -} - -static inline void qskFilterWindow( QQuickWindow* window ) -{ - if ( window == nullptr ) - return; - - static QskDirtyItemFilter itemFilter; - itemFilter.addWindow( window ); -} - -namespace -{ - /* - A helper class to store the released window to be able to - put it later into the WindowChange event. - */ - class QskWindowStore - { - public: - QskWindowStore() - : m_refCount( 0 ) - , m_window( nullptr ) - { - } - - void setWindow( QQuickWindow* window ) - { - if ( m_window != window ) - { - m_window = window; - m_refCount = 0; - } - - if ( m_window ) - m_refCount++; - } - - QQuickWindow* window() - { - QQuickWindow* w = m_window; - - if ( m_window ) - { - if ( --m_refCount == 0 ) - m_window = nullptr; - } - - return w; - } - - private: - int m_refCount; - QQuickWindow* m_window; - }; - - class QskControlRegistry - { - public: - QskControlRegistry() - { - /* - Its faster and saves some memory to have this registry instead - of setting up direct connections between qskSetup and each control - */ - QObject::connect( qskSetup, &QskSetup::controlFlagsChanged, - qskSetup, [ this ] { updateControlFlags(); } ); - - QObject::connect( qskSetup, &QskSetup::skinChanged, - qskSetup, [ this ] { updateSkin(); } ); - } - - inline void insert( QskControl* control ) - { - m_controls.insert( control ); - } - - inline void remove( QskControl* control ) - { - m_controls.erase( control ); - } - - void updateControlFlags() - { - const auto flags = static_cast< QskControl::Flags >( qskControlFlags() ); - - for ( auto control : m_controls ) - qskUpdateControlFlags( flags, control ); - } - - void updateSkin() - { - QEvent event( QEvent::StyleChange ); - - for ( auto control : m_controls ) - { - event.setAccepted( true ); - QCoreApplication::sendEvent( control, &event ); - } - } - - private: - std::unordered_set< QskControl* > m_controls; - }; -} - -Q_GLOBAL_STATIC( QskWindowStore, qskReleasedWindowCounter ) -Q_GLOBAL_STATIC( QskControlRegistry, qskRegistry ) - -/* - QQuickItemPrivate somehow implies, that it is about private data, - but it is only the part of the public API, that does not fall - under the Qt binary compatibility rules. - - Actually QQuickItemPrivate puts everything into its public section - and classes - even from totally different modules like QC2 - - are manipulating its members in a totally unguarded way - - While hacking internals of QQuickItemPrivate can't be considered - being acceptable for any standards of software engineering - we need to do the same to convince QQuickItem/QuickWindow to do what we - need. - - This is not the way how we want to create APIs in QSkinny, but that - does not mean, that the D-Pointer concept is bad in general: - it allows to allocate private data of base and derived class - together. - - At the moment QSkinny uses D-Pointer only, when deriving from - Qt classes, but does not expose these private data to the public. - Once QSkinny is in a stable state this might be changed - but - without compromising the privacy of its members. - */ -class QskControlPrivate final : public QQuickItemPrivate -{ - Q_DECLARE_PUBLIC( QskControl ) - - public: - - QskControlPrivate(); - ~QskControlPrivate() override; - - void mirrorChange() override; - - qreal getImplicitWidth() const override; - qreal getImplicitHeight() const override; - - void updateImplicitSize( bool doNotify ); - -#if 0 - // can we do something useful with overloading ??? - - QSGTransformNode* createTransformNode() override; - void transformChanged() override; -#endif - - void implicitWidthChanged() override; - void implicitHeightChanged() override; - - void setExplicitSizeHint( Qt::SizeHint, const QSizeF& ); - void resetExplicitSizeHint( Qt::SizeHint ); - QSizeF explicitSizeHint( Qt::SizeHint ) const; - - bool maybeGesture( QQuickItem*, QEvent* ); - void updateControlFlags( QskControl::Flags ); - - /* - Qt 5.11: - sizeof( QQuickItemPrivate::ExtraData ) -> 184 - sizeof( QQuickItemPrivate ) -> 320 - - ( these numbers include pointers to optional extra data, but not - the size for the extra data. So the effective memory footprint, - is often even worse ). - - sizeof( QskControlPrivate ) -> sizeof( QQuickItemPrivate ) + 32 - sizeof( QskSkinnable::PrivateData ) -> 40 - - It might be possible to save some bytes, but in the end QskControl - is heavy simply because of deriving from QQuickItem. So without - patching Qt the only way to limit the memory footprint of an application - substantially is to limit the number of QQuickItems. - - That's why QSkinny builds more complex controls from scene graph nodes - instead of doing QQuickItem composition. As this can only be done - in C++ it is kind of obvious, why it is often a bad idea to build - custom controls in QML. - */ - - private: - void implicitSizeChanged(); - void setImplicitSize( qreal width, qreal height, bool doNotify ); - - QSizeF* explicitSizeHints; - - public: - QLocale locale; - - quint16 controlFlags; - quint16 controlFlagsMask; - - QskSizePolicy sizePolicy; - - bool explicitLocale : 1; - - bool autoFillBackground : 1; - bool autoLayoutChildren : 1; - - bool polishOnResize : 1; - - bool blockedPolish : 1; - bool blockedImplicitSize : 1; - mutable bool blockLayoutRequestEvents : 1; - bool clearPreviousNodes : 1; - - bool isInitiallyPainted : 1; - - uint focusPolicy : 4; - bool isWheelEnabled : 1; -}; - -static inline void qskUpdateControlFlags( QskControl::Flags flags, QskControl* control ) -{ - auto d = static_cast< QskControlPrivate* >( QQuickItemPrivate::get( control ) ); - d->updateControlFlags( flags ); -} - -QskControlPrivate::QskControlPrivate() - : explicitSizeHints( nullptr ) - , controlFlags( qskControlFlags() ) - , controlFlagsMask( 0 ) - , sizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Preferred ) - , explicitLocale( false ) - , autoFillBackground( false ) - , autoLayoutChildren( false ) - , polishOnResize( false ) - , blockedPolish( false ) - , blockedImplicitSize( true ) - , blockLayoutRequestEvents( true ) - , clearPreviousNodes( false ) - , isInitiallyPainted( false ) - , focusPolicy( Qt::NoFocus ) - , isWheelEnabled( false ) -{ - if ( controlFlags & QskControl::DeferredLayout ) - { - /* - In general the geometry of an item should be the job of - the parent - unfortunatly not done by Qt Quick - probably in the spirit of "making things easier". - - To avoid potentially expensive calculations happening - too often and early QskControl blocks updates of - the implicitSize and any auto resizing of the control - according to it. - - There should be no strong reason for using concepts - like Positioners, that rely on implicit resizing, - but to make it working: the DeferredLayout flag needs to be disabled. - */ - - widthValid = heightValid = true; - } -} - -QskControlPrivate::~QskControlPrivate() -{ - delete [] explicitSizeHints; -} - -void QskControlPrivate::mirrorChange() -{ - Q_Q( QskControl ); - qskSendEventTo( q, QEvent::LayoutDirectionChange ); -} - -inline void QskControlPrivate::implicitSizeChanged() -{ - blockedImplicitSize = false; - - Q_Q( QskControl ); - if ( !q->explicitSizeHint( Qt::PreferredSize ).isValid() ) - { - // when we have no PreferredSize we fall back - // to the implicit size - - q->layoutConstraintChanged(); - } -} - -qreal QskControlPrivate::getImplicitWidth() const -{ - if ( blockedImplicitSize ) - { - auto that = const_cast< QskControlPrivate* >( this ); - that->updateImplicitSize( false ); - } - - return implicitWidth; -} - -qreal QskControlPrivate::getImplicitHeight() const -{ - if ( blockedImplicitSize ) - { - auto that = const_cast< QskControlPrivate* >( this ); - that->updateImplicitSize( false ); - } - - return implicitHeight; -} - -void QskControlPrivate::updateImplicitSize( bool doNotify ) -{ - Q_Q( const QskControl ); - - blockedImplicitSize = false; - - const auto m = q->margins(); - const auto dw = m.left() + m.right(); - const auto dh = m.top() + m.bottom(); - - const auto hint = q->contentsSizeHint(); - - const qreal w = ( hint.width() >= 0 ) ? dw + hint.width() : -1.0; - const qreal h = ( hint.height() >= 0 ) ? dh + hint.height() : -1.0; - - setImplicitSize( w, h, doNotify ); -} - -void QskControlPrivate::setImplicitSize( qreal w, qreal h, bool doNotify ) -{ - const bool doWidth = ( w != implicitWidth ); - const bool doHeight = ( h != implicitHeight ); - - if ( !( doWidth || doHeight ) ) - return; // nothing to do - - implicitWidth = w; - implicitHeight = h; - - if ( !( widthValid && heightValid ) ) - { - // auto adjusting the size - - const qreal oldWidth = width; - const qreal oldHeight = height; - - if ( doWidth && !widthValid ) - width = qMax( w, qreal( 0.0 ) ); - - if ( doHeight && !heightValid ) - height = qMax( h, qreal( 0.0 ) ); - - if ( ( width != oldWidth ) || ( height != oldHeight ) ) - { - dirty( QQuickItemPrivate::Size ); - - const QRectF oldRect( x, y, oldWidth, oldHeight ); - const QRectF newRect( x, y, width, height ); - - Q_Q( QskControl ); - q->geometryChanged( newRect, oldRect ); - } - } - - if ( doNotify ) - { - if ( doWidth ) - QQuickItemPrivate::implicitWidthChanged(); - - if ( doHeight ) - QQuickItemPrivate::implicitHeightChanged(); - } -} - -void QskControlPrivate::implicitWidthChanged() -{ - QQuickItemPrivate::implicitWidthChanged(); - implicitSizeChanged(); -} - -void QskControlPrivate::implicitHeightChanged() -{ - QQuickItemPrivate::implicitWidthChanged(); - implicitSizeChanged(); -} - -void QskControlPrivate::setExplicitSizeHint( - Qt::SizeHint whichHint, const QSizeF& size ) -{ - if ( explicitSizeHints == nullptr ) - { - using namespace QskLayoutConstraint; - - explicitSizeHints = new QSizeF[3]; - explicitSizeHints[0] = defaultSizeHints[0]; - explicitSizeHints[1] = defaultSizeHints[1]; - explicitSizeHints[2] = defaultSizeHints[2]; - } - - explicitSizeHints[ whichHint ] = size; -} - -void QskControlPrivate::resetExplicitSizeHint( Qt::SizeHint whichHint ) -{ - if ( explicitSizeHints ) - { - using namespace QskLayoutConstraint; - explicitSizeHints[ whichHint ] = defaultSizeHints[ whichHint ]; - } -} - -inline QSizeF QskControlPrivate::explicitSizeHint( Qt::SizeHint whichHint ) const -{ - if ( explicitSizeHints ) - return explicitSizeHints[ whichHint ]; - - return QskLayoutConstraint::defaultSizeHints[ whichHint ]; -} - -bool QskControlPrivate::maybeGesture( QQuickItem* child, QEvent* event ) -{ - Q_Q( QskControl ); - - switch ( event->type() ) - { - case QEvent::MouseButtonPress: - { - const auto mouseEvent = static_cast< const QMouseEvent* >( event ); - const QPointF pos = q->mapFromScene( mouseEvent->windowPos() ); - - if ( !q->gestureRect().contains( pos ) ) - return false; - - break; - } - - case QEvent::MouseMove: - case QEvent::MouseButtonRelease: - case QEvent::MouseButtonDblClick: - case QEvent::UngrabMouse: - case QEvent::TouchBegin: - case QEvent::TouchCancel: - case QEvent::TouchUpdate: - break; - - default: - return false; - } - - return q->gestureFilter( child, event ); -} - -void QskControlPrivate::updateControlFlags( QskControl::Flags flags ) -{ - Q_Q( QskControl ); - - const auto oldFlags = controlFlags; - const auto newFlags = static_cast< quint16 >( flags ); - - if ( oldFlags != newFlags ) - { - const auto numBits = qCountTrailingZeroBits( - static_cast< quint32 >( QskControl::LastFlag ) ); - - for ( quint32 i = 0; i <= numBits; ++i ) - { - const quint32 flag = ( 1 << i ); - q->updateControlFlag( flag, flags & flag ); - } - - Q_EMIT q->controlFlagsChanged(); - } -} - -// -------- - QskControl::QskControl( QQuickItem* parent ) - : Inherited( *( new QskControlPrivate() ), parent ) + : QskQuickItem( *( new QskControlPrivate() ), parent ) { - setFlag( QQuickItem::ItemHasContents, true ); - QQuickItem::setActiveFocusOnTab( false ); + Inherited::setActiveFocusOnTab( false ); if ( parent ) { // inheriting attributes from parent - qskResolveLocale( this ); + QskControlPrivate::resolveLocale( this ); } - - // since Qt 5.10 we have QQuickItem::ItemEnabledHasChanged -#if QT_VERSION < QT_VERSION_CHECK( 5, 10, 0 ) - /* - Setting up this connections slows down the time needed - for construction by almost 100%. Would be nice to - avoid this penalty also for earlier Qt versions. - */ - connect( this, &QQuickItem::enabledChanged, - [ this ] { qskSendEventTo( this, QEvent::EnabledChange ); } ); -#endif - - Q_D( QskControl ); - if ( d->controlFlags & QskControl::DeferredUpdate ) - qskFilterWindow( window() ); - - qskRegistry->insert( this ); } QskControl::~QskControl() @@ -563,107 +50,6 @@ QskControl::~QskControl() Q_ASSERT( this != w->mouseGrabberItem() ); } #endif - - if ( qskRegistry ) - qskRegistry->remove( this ); - -#if QT_VERSION < QT_VERSION_CHECK( 5, 10, 0 ) - disconnect( this, &QQuickItem::enabledChanged, nullptr, nullptr ); -#endif - - /* - 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 -{ - return metaObject()->className(); -} - -void QskControl::setVisible( bool on ) -{ - // QQuickItem::setVisible is no slot - Inherited::setVisible( on ); -} - -void QskControl::show() -{ - Inherited::setVisible( true ); -} - -void QskControl::hide() -{ - Inherited::setVisible( false ); -} - -bool QskControl::isVisibleTo( const QQuickItem* ancestor ) const -{ - return qskIsVisibleTo( this, ancestor ); -} - -void QskControl::setGeometry( qreal x, qreal y, qreal width, qreal height ) -{ - // QQuickItem does not even offer changing the geometry - // in one call - what leads to 2 calls of the updateGeometry - // hook. Grmpf ... - - Q_D( QQuickItem ); - - d->heightValid = true; - d->widthValid = true; - - const QRectF oldRect( d->x, d->y, d->width, d->height ); - - int dirtyType = 0; - - if ( d->x != x || d->y != y ) - { - d->x = x; - d->y = y; - - dirtyType |= QQuickItemPrivate::Position; - } - - if ( d->width != width || d->height != height ) - { - d->height = height; - d->width = width; - - dirtyType |= QQuickItemPrivate::Size; - } - - if ( dirtyType ) - { - if ( dirtyType & QQuickItemPrivate::Position ) - d->dirty( QQuickItemPrivate::Position ); - - if ( dirtyType & QQuickItemPrivate::Size ) - d->dirty( QQuickItemPrivate::Size ); - - /* - Unfortunately geometryChanged is protected and we can't implement - this code as qskSetItemGeometry - further hacking required: TODO ... - */ - - geometryChanged( QRectF( d->x, d->y, d->width, d->height ), oldRect ); - } -} - -QRectF QskControl::rect() const -{ - Q_D( const QskControl ); - return QRectF( 0, 0, d->width, d->height ); -} - -QRectF QskControl::geometry() const -{ - Q_D( const QskControl ); - return QRectF( d->x, d->y, d->width, d->height ); } void QskControl::setAutoFillBackground( bool on ) @@ -672,9 +58,7 @@ void QskControl::setAutoFillBackground( bool on ) if ( on != d->autoFillBackground ) { d->autoFillBackground = on; - update(); - Q_EMIT controlFlagsChanged(); } } @@ -691,8 +75,6 @@ void QskControl::setAutoLayoutChildren( bool on ) d->autoLayoutChildren = on; if ( on ) polish(); - - Q_EMIT controlFlagsChanged(); } } @@ -701,38 +83,6 @@ bool QskControl::autoLayoutChildren() const return d_func()->autoLayoutChildren; } -void QskControl::setTransparentForPositioner( bool on ) -{ - Q_D( QskControl ); - if ( on != d->isTransparentForPositioner() ) - { - d->setTransparentForPositioner( on ); - Q_EMIT controlFlagsChanged(); - } -} - -bool QskControl::isTransparentForPositioner() const -{ - return d_func()->isTransparentForPositioner(); -} - -void QskControl::setPolishOnResize( bool on ) -{ - Q_D( QskControl ); - if ( on != d->polishOnResize ) - { - d->polishOnResize = on; - polish(); - - Q_EMIT controlFlagsChanged(); - } -} - -bool QskControl::polishOnResize() const -{ - return d_func()->polishOnResize; -} - void QskControl::setWheelEnabled( bool on ) { Q_D( QskControl ); @@ -753,8 +103,8 @@ void QskControl::setFocusPolicy( Qt::FocusPolicy policy ) Q_D( QskControl ); if ( policy != d->focusPolicy ) { - d->focusPolicy = policy & ~Qt::TabFocus; - QQuickItem::setActiveFocusOnTab( policy & Qt::TabFocus ); + d->focusPolicy = ( policy & ~Qt::TabFocus ); + Inherited::setActiveFocusOnTab( policy & Qt::TabFocus ); Q_EMIT focusPolicyChanged(); } @@ -763,153 +113,12 @@ void QskControl::setFocusPolicy( Qt::FocusPolicy policy ) Qt::FocusPolicy QskControl::focusPolicy() const { uint policy = d_func()->focusPolicy; - if ( activeFocusOnTab() ) + if ( Inherited::activeFocusOnTab() ) policy |= Qt::TabFocus; return static_cast< Qt::FocusPolicy >( policy ); } -void QskControl::setTabFence( bool on ) -{ - Q_D( QskControl ); - if ( on != d->isTabFence ) - { - d->isTabFence = on; - Q_EMIT controlFlagsChanged(); - } -} - -bool QskControl::isTabFence() const -{ - return d_func()->isTabFence; -} - -QskControl::Flags QskControl::controlFlags() const -{ - return QskControl::Flags( d_func()->controlFlags ); -} - -void QskControl::setControlFlags( Flags flags ) -{ - Q_D( QskControl ); - - // set all bits in the mask - d->controlFlagsMask = std::numeric_limits< quint16 >::max(); - d->updateControlFlags( flags ); -} - -void QskControl::resetControlFlags() -{ - Q_D( QskControl ); - - // clear all bits in the mask - d->controlFlagsMask = 0; - d->updateControlFlags( static_cast< Flags >( qskControlFlags() ) ); -} - -void QskControl::setControlFlag( Flag flag, bool on ) -{ - Q_D( QskControl ); - - d->controlFlagsMask |= flag; - - if ( ( d->controlFlags & flag ) != on ) - { - updateControlFlag( flag, on ); - Q_EMIT controlFlagsChanged(); - } -} - -void QskControl::resetControlFlag( Flag flag ) -{ - Q_D( QskControl ); - - d->controlFlagsMask &= ~flag; - - const bool on = qskSetup->testControlFlag( static_cast< QskSetup::Flag >( flag ) ); - - if ( ( d->controlFlags & flag ) != on ) - { - updateControlFlag( flag, on ); - Q_EMIT controlFlagsChanged(); - } -} - -bool QskControl::testControlFlag( Flag flag ) const -{ - return d_func()->controlFlags & flag; -} - -void QskControl::updateControlFlag( uint flag, bool on ) -{ - Q_D( QskControl ); - - if ( ( flag > std::numeric_limits< quint16 >::max() ) || - ( bool( d->controlFlags & flag ) == on ) ) - { - return; - } - - if ( on ) - d->controlFlags |= flag; - else - d->controlFlags &= ~flag; - - switch ( flag ) - { - case QskControl::DeferredUpdate: - { - if ( on ) - { - qskFilterWindow( window() ); - } - else - { - if ( !isVisible() ) - update(); - } - - break; - } - case QskControl::DeferredPolish: - { - if ( !on && d->blockedPolish ) - polish(); - - break; - } - case QskControl::DeferredLayout: - { - if ( !on ) - { - // Update the implicitSize and rebind the size to it. - // Having set the size explicitly gets lost. - - d->widthValid = d->heightValid = false; - d->updateImplicitSize( false ); - } - - break; - } - case QskControl::CleanupOnVisibility: - { - if ( on && !isVisible() ) - cleanupNodes(); - - break; - } - case QskControl::DebugForceBackground: - { - // no need to mark it dirty - if ( flags() & QQuickItem::ItemHasContents ) - update(); - break; - } - default: - break; - } -} - void QskControl::setBackgroundColor( const QColor& color ) { setAutoFillBackground( true ); @@ -962,7 +171,7 @@ void QskControl::setMargins( const QMarginsF& margins ) { using namespace QskAspect; - const Subcontrol subControl = effectiveSubcontrol( QskAspect::Control ); + const auto subControl = effectiveSubcontrol( QskAspect::Control ); const QMarginsF m( qMax( qreal( margins.left() ), qreal( 0.0 ) ), @@ -976,7 +185,7 @@ void QskControl::setMargins( const QMarginsF& margins ) resetImplicitSize(); Q_D( const QskControl ); - if ( d->polishOnResize || d->autoLayoutChildren ) + if ( polishOnResize() || d->autoLayoutChildren ) polish(); qskSendEventTo( this, QEvent::ContentsRectChange ); @@ -1000,7 +209,7 @@ void QskControl::resetMargins() resetImplicitSize(); Q_D( const QskControl ); - if ( d->polishOnResize || d->autoLayoutChildren ) + if ( polishOnResize() || d->autoLayoutChildren ) polish(); qskSendEventTo( this, QEvent::ContentsRectChange ); @@ -1020,7 +229,7 @@ QRectF QskControl::contentsRect() const } QRectF QskControl::subControlRect( QskAspect::Subcontrol subControl ) const -{ +{ return effectiveSkinlet()->subControlRect( this, contentsRect(), subControl ); } @@ -1033,51 +242,6 @@ QRectF QskControl::subControlRect( return effectiveSkinlet()->subControlRect( this, rect, subControl ); } -bool QskControl::layoutMirroring() const -{ - return d_func()->effectiveLayoutMirror; -} - -void QskControl::setLayoutMirroring( bool on, bool recursive ) -{ - // Again we have to deal with an existing API made for QML, - // that is weired for C++: LayoutMirroring/QQuickLayoutMirroringAttached - // Internally it is managed by 5(!) different flags - condolences - // to the poor guy who has been sentenced to maintain this. - - // Anyway, the code below might achieve the desired behavior without - // breaking the QML path. - - Q_D( QskControl ); - - if ( recursive != d->inheritMirrorFromItem ) - { - d->inheritMirrorFromItem = recursive; - d->resolveLayoutMirror(); - } - - d->isMirrorImplicit = false; - - if ( on != d->effectiveLayoutMirror ) - { - d->setLayoutMirror( on ); - if ( recursive ) - d->resolveLayoutMirror(); - } -} - -void QskControl::resetLayoutMirroring() -{ - Q_D( QskControl ); - - if ( d && !d->isMirrorImplicit ) - { - d->isMirrorImplicit = true; - // d->inheritMirrorFromItem = false; - d->resolveLayoutMirror(); - } -} - QLocale QskControl::locale() const { return d_func()->locale; @@ -1104,35 +268,7 @@ void QskControl::resetLocale() if ( d->explicitLocale ) { d->explicitLocale = false; - qskResolveLocale( this ); - } -} - -// not static as being called from QskSetup.cpp -bool qskInheritLocale( QskControl* control, const QLocale& locale ) -{ - auto d = static_cast< QskControlPrivate* >( QQuickItemPrivate::get( control ) ); - - if ( d->explicitLocale || d->locale == locale ) - return true; - - d->locale = locale; - qskSendEventTo( control, QEvent::LocaleChange ); - - return false; -} - -void qskResolveLocale( QskControl* control ) -{ - const QLocale locale = qskSetup->inheritedLocale( control ); - - auto d = static_cast< QskControlPrivate* >( QQuickItemPrivate::get( control ) ); - if ( d->locale != locale ) - { - d->locale = locale; - - qskSendEventTo( control, QEvent::LocaleChange ); - qskSetup->inheritLocale( control, locale ); + QskControlPrivate::resolveLocale( this ); } } @@ -1166,13 +302,13 @@ void QskControl::setSizePolicy( QskSizePolicy policy ) if ( policy != d->sizePolicy ) { d->sizePolicy = policy; - layoutConstraintChanged(); + d->layoutConstraintChanged(); if ( policy.policy( Qt::Horizontal ) == QskSizePolicy::Constrained && policy.policy( Qt::Vertical ) == QskSizePolicy::Constrained ) { qWarning( "QskControl::setSizePolicy: conflicting constraints"); - } + } } } @@ -1191,7 +327,7 @@ void QskControl::setSizePolicy( if ( d->sizePolicy.policy( orientation ) != policy ) { d->sizePolicy.setPolicy( orientation, policy ); - layoutConstraintChanged(); + d->layoutConstraintChanged(); } } @@ -1279,7 +415,7 @@ void QskControl::setFixedSize( const QSizeF& size ) d->sizePolicy = policy; d->setExplicitSizeHint( Qt::PreferredSize, newSize ); - layoutConstraintChanged(); + d->layoutConstraintChanged(); } } @@ -1305,7 +441,7 @@ void QskControl::setFixedWidth( qreal width ) d->sizePolicy.setHorizontalPolicy( QskSizePolicy::Fixed ); d->setExplicitSizeHint( Qt::PreferredSize, size ); - layoutConstraintChanged(); + d->layoutConstraintChanged(); } } @@ -1326,7 +462,7 @@ void QskControl::setFixedHeight( qreal height ) d->sizePolicy.setVerticalPolicy( QskSizePolicy::Fixed ); d->setExplicitSizeHint( Qt::PreferredSize, size ); - layoutConstraintChanged(); + d->layoutConstraintChanged(); } } @@ -1340,7 +476,7 @@ void QskControl::resetExplicitSizeHint( Qt::SizeHint whichHint ) d->resetExplicitSizeHint( whichHint ); if ( oldHint != d->explicitSizeHint( whichHint ) ) - layoutConstraintChanged(); + d->layoutConstraintChanged(); } } @@ -1356,7 +492,7 @@ void QskControl::setExplicitSizeHint( Qt::SizeHint whichHint, const QSizeF& size if ( newSize != d->explicitSizeHint( whichHint ) ) { d->setExplicitSizeHint( whichHint, newSize ); - layoutConstraintChanged(); + d->layoutConstraintChanged(); } } } @@ -1426,7 +562,7 @@ QSizeF QskControl::effectiveSizeHint( /* The explicit size has always precedence over te implicit size. - + Implicit sizes are currently only implemented for preferred and explicitSizeHint returns valid explicit hints for minimum/maximum even if not being set by application code. @@ -1472,7 +608,7 @@ QSizeF QskControl::effectiveSizeHint( if ( hint.width() >= 0 ) hint.setWidth( qMax( hint.width(), minimumHint.width() ) ); - + if ( hint.height() >= 0 ) hint.setHeight( qMax( hint.height(), minimumHint.height() ) ); } @@ -1493,7 +629,7 @@ QSizeF QskControl::effectiveSizeHint( { const auto minH = minimumHint.height(); const auto maxH = qMax( minH, maximumHint.height() ); - + hint.setHeight( qBound( minH, hint.height(), maxH ) ); } } @@ -1511,21 +647,6 @@ QSizeF QskControl::effectiveSizeHint( return hint; } -void QskControl::resetImplicitSize() -{ - Q_D( QskControl ); - - if ( d->controlFlags & QskControl::DeferredLayout ) - { - d->blockedImplicitSize = true; - layoutConstraintChanged(); - } - else - { - d->updateImplicitSize( true ); - } -} - qreal QskControl::heightForWidth( qreal width ) const { Q_D( const QskControl ); @@ -1558,9 +679,7 @@ qreal QskControl::widthForHeight( qreal height ) const bool QskControl::event( QEvent* event ) { - const int eventType = event->type(); - - switch ( eventType ) + switch ( static_cast< int >( event->type() ) ) { case QEvent::EnabledChange: { @@ -1577,22 +696,8 @@ bool QskControl::event( QEvent* event ) if ( d_func()->autoLayoutChildren ) resetImplicitSize(); - if ( d_func()->polishOnResize ) - polish(); - break; } - case QEvent::ContentsRectChange: - { - if ( d_func()->polishOnResize ) - polish(); - - break; - } - } - - switch ( eventType ) - { case QEvent::StyleChange: { // The skin has changed @@ -1607,55 +712,18 @@ bool QskControl::event( QEvent* event ) setSkinlet( nullptr ); } - /* - We might have a totally different skinlet, - that can't deal with nodes created from other skinlets - */ - d_func()->clearPreviousNodes = true; - - resetImplicitSize(); - polish(); - - if ( flags() & QQuickItem::ItemHasContents ) - update(); - - changeEvent( event ); - return true; - } - - case QEvent::EnabledChange: - case QEvent::FontChange: - case QEvent::PaletteChange: - case QEvent::LocaleChange: - case QEvent::ReadOnlyChange: - case QEvent::ParentChange: - case QEvent::ContentsRectChange: - { - changeEvent( event ); - return true; - } - case QskEvent::GeometryChange: - { - geometryChangeEvent( static_cast< QskGeometryChangeEvent* >( event ) ); - return true; - } - case QskEvent::WindowChange: - { - windowChangeEvent( static_cast< QskWindowChangeEvent* >( event ) ); - return true; + break; } case QskEvent::Gesture: { gestureEvent( static_cast< QskGestureEvent* >( event ) ); return true; } - default: - { - if ( d_func()->maybeGesture( this, event ) ) - return true; - } } + if ( d_func()->maybeGesture( this, event ) ) + return true; + return Inherited::event( event ); } @@ -1700,7 +768,7 @@ bool QskControl::childMouseEventFilter( QQuickItem* item, QEvent* event ) if ( itm == this ) return false; - QScopedPointer clonedEvent( + QScopedPointer< QMouseEvent > clonedEvent( QQuickWindowPrivate::cloneMouseEvent( me, &pos ) ); return d_func()->maybeGesture( itm, clonedEvent.data() ); @@ -1713,19 +781,12 @@ bool QskControl::childMouseEventFilter( QQuickItem* item, QEvent* event ) return d_func()->maybeGesture( item, event ); } -bool QskControl::gestureFilter( QQuickItem* item, QEvent* event ) +bool QskControl::gestureFilter( QQuickItem*, QEvent* ) { - Q_UNUSED( item ) - Q_UNUSED( event ) - return false; } -void QskControl::windowChangeEvent( QskWindowChangeEvent* ) -{ -} - -void QskControl::geometryChangeEvent( QskGeometryChangeEvent* ) +void QskControl::gestureEvent( QskGestureEvent* ) { } @@ -1741,177 +802,31 @@ void QskControl::hoverLeaveEvent( QHoverEvent* event ) setSkinStateFlag( Hovered, false ); } -void QskControl::changeEvent( QEvent* ) -{ -} - -void QskControl::gestureEvent( QskGestureEvent* ) -{ -} - -void QskControl::classBegin() -{ - Inherited::classBegin(); -} - -void QskControl::componentComplete() -{ -#if defined( QT_DEBUG ) - if ( qobject_cast< const QQuickBasePositioner* >( parent() ) ) - { - if ( d_func()->controlFlags & QskControl::DeferredLayout ) - { - qWarning( "QskControl in DeferredLayout mode under control of a positioner" ); - } - } -#endif - Inherited::componentComplete(); -} - -bool QskControl::maybeUnresized() const -{ - Q_D( const QskControl ); - - if ( d->width <= 0.0 && d->height <= 0.0 ) - { - /* - Unfortunately the list of items to-be-polished is not processed - in top/down order and we might run into updatePolish() before - having a proper size. But when the parentItem() is waiting - for to-be-polished, we assume, that we will be resized then - and run into another updatePolish() then. - */ - if ( d->polishOnResize && qskIsPolishScheduled( parentItem() ) ) - return true; - } - - return false; -} - -void QskControl::releaseResources() -{ - Inherited::releaseResources(); - - // QQuickItem::derefWindow runs over the children between - // calling releaseResources and itemChange. So we need to have - // a reference count to know, when we have processed all - // sequences to be able to provide the correct "oldWindow" - // in the WindowChange event. - - qskReleasedWindowCounter->setWindow( window() ); -} - void QskControl::itemChange( QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& value ) { - Inherited::itemChange( change, value ); - - Q_D( QskControl ); - - switch ( change ) + switch ( static_cast< int >( change ) ) { case QQuickItem::ItemParentHasChanged: { if ( value.item ) { - if ( !d->explicitLocale ) - qskResolveLocale( this ); + if ( !d_func()->explicitLocale ) + QskControlPrivate::resolveLocale( this ); } +#if 1 // not necessarily correct, when parent != parentItem ??? qskSendEventTo( this, QEvent::ParentChange ); +#endif break; } case QQuickItem::ItemChildAddedChange: { - if ( d->autoLayoutChildren && !qskIsTransparentForPositioner( value.item ) ) + if ( autoLayoutChildren() && !qskIsTransparentForPositioner( value.item ) ) polish(); - break; - } -#if 0 - case QQuickItem::ItemChildRemovedChange: - { - // TODO ... - break; - } -#endif - case QQuickItem::ItemVisibleHasChanged: - { -#if 1 - /* - ~QQuickItem sends QQuickItem::ItemVisibleHasChanged recursively - to all childItems. When being a child ( not only a childItem() ) - we are short before being destructed too and any updates - done here are totally pointless. TODO ... - */ -#endif - if ( value.boolValue ) - { - if ( d->blockedPolish ) - polish(); - - if ( d->controlFlags & QskControl::DeferredUpdate ) - { - if ( d->dirtyAttributes && ( d->flags & QQuickItem::ItemHasContents ) ) - update(); - } - } - else - { - if ( d->controlFlags & QskControl::CleanupOnVisibility ) - cleanupNodes(); - - d->isInitiallyPainted = false; - } - - if ( parentItem() && parentItem()->isVisible() ) - { - /* - Layout code might consider the visiblility of the children - and therefore needs to be updated. Posting a statement about - changed layout constraints has this effect, but is not correct. - The right way to go would be to create show/hide events and to - handle them, where visibility of the children matters. - TODO ... - */ - - layoutConstraintChanged(); - } - - break; - } - case QQuickItem::ItemSceneChange: - { - if ( value.window ) - { - if ( d->controlFlags & QskControl::DeferredUpdate ) - qskFilterWindow( value.window ); - } - -#if 1 - auto oldWindow = qskReleasedWindowCounter->window(); - if ( oldWindow && ( oldWindow->activeFocusItem() == this ) ) - { - /* - Removing an item from the scene might result in - changes of the active focus item. Unfortunately the corresponding - FocusIn/Out events are sent, while the item tree is in an - invalid state. - When having event handlers, that do modifications of the focus - ( f.e. assigning the local focus, inside of a focus scope ) - we might end up with having a dangling pointer for - oldWindow->activeFocusItem(). - */ - QQuickWindowPrivate::get( oldWindow )->clearFocusInScope( - qskNearestFocusScope( this ), this, Qt::OtherFocusReason ); - } -#endif - - QskWindowChangeEvent event( oldWindow, value.window ); - QCoreApplication::sendEvent( this, &event ); - break; } case QQuickItem::ItemActiveFocusHasChanged: @@ -1919,34 +834,21 @@ void QskControl::itemChange( QQuickItem::ItemChange change, setSkinStateFlag( Focused, hasActiveFocus() ); break; } -#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 ) - case QQuickItem::ItemEnabledHasChanged: - { - qskSendEventTo( this, QEvent::EnabledChange ); - break; - } -#endif - default: - { - break; - } } + + Inherited::itemChange( change, value ); } void QskControl::geometryChanged( const QRectF& newGeometry, const QRectF& oldGeometry ) { - Inherited::geometryChanged( newGeometry, oldGeometry ); - - if ( newGeometry.size() != oldGeometry.size() ) + if ( d_func()->autoLayoutChildren ) { - Q_D( const QskControl ); - if ( d->polishOnResize || d->autoLayoutChildren ) + if ( newGeometry.size() != oldGeometry.size() ) polish(); } - QskGeometryChangeEvent event( newGeometry, oldGeometry ); - QCoreApplication::sendEvent( this, &event ); + Inherited::geometryChanged( newGeometry, oldGeometry ); } void QskControl::windowDeactivateEvent() @@ -1955,46 +857,16 @@ void QskControl::windowDeactivateEvent() Inherited::windowDeactivateEvent(); } -void QskControl::layoutConstraintChanged() +void QskControl::updateItemPolish() { - Q_D( QskControl ); - - if ( !d->blockLayoutRequestEvents ) + if ( d_func()->autoLayoutChildren && !maybeUnresized() ) { - if ( auto item = parentItem() ) - qskSendEventTo( item, QEvent::LayoutRequest ); - - /* - We don't send further LayoutRequest events until someone - actively requests a layout relevant information - */ - d->blockLayoutRequestEvents = true; - } -} - -void QskControl::updatePolish() -{ - Q_D( QskControl ); - - if ( d->controlFlags & QskControl::DeferredPolish ) - { - if ( !isVisible() ) - { - d->blockedPolish = true; - return; - } - } - - d->blockedPolish = false; - - if ( d->autoLayoutChildren && !maybeUnresized() ) - { - const QRectF rect = layoutRect(); + const auto rect = layoutRect(); const auto children = childItems(); for ( auto child : children ) { - if ( !QQuickItemPrivate::get( child )->isTransparentForPositioner() ) + if ( !qskIsTransparentForPositioner( child ) ) { // rect = QskLayoutConstraint::itemRect( info.item, rect, ... ); qskSetItemGeometry( child, rect ); @@ -2002,90 +874,19 @@ void QskControl::updatePolish() } } - if ( !d->isInitiallyPainted ) - { - /* - We should find a better way for identifying, when - an item is about to be shown, than making it dependend - from polishing and the existence of scene graph nodes. TODO ... - */ - aboutToShow(); - } - + updateResources(); updateLayout(); } -QSGNode* QskControl::updatePaintNode( QSGNode* node, UpdatePaintNodeData* data ) +QSGNode* QskControl::updateItemPaintNode( QSGNode* node ) { - Q_UNUSED( data ); - - Q_D( QskControl ); - - Q_ASSERT( isVisible() || !( d->controlFlags & QskControl::DeferredUpdate ) ); - - if ( !d->isInitiallyPainted ) - d->isInitiallyPainted = true; - - if ( d->clearPreviousNodes ) - { - delete node; - node = nullptr; - - d->clearPreviousNodes = false; - } - if ( node == nullptr ) node = new QSGNode; - updateNode( node ); + QskSkinnable::updateNode( node ); return node; } -void QskControl::cleanupNodes() -{ - Q_D( QskControl ); - if ( d->itemNodeInstance == nullptr ) - return; - - // setting the dirty flags, so that nodes will be recreated - // the next time we participate in a scene graph update - - if ( !d->itemNodeInstance->matrix().isIdentity() ) - d->dirtyAttributes |= QQuickItemPrivate::Position; - - if ( d->extra.isAllocated() ) - { - if ( d->extra->clipNode ) - d->dirtyAttributes |= QQuickItemPrivate::Clip; - - if ( d->extra->opacityNode ) - d->dirtyAttributes |= QQuickItemPrivate::OpacityValue; - - if ( d->extra->rootNode ) - d->dirtyAttributes |= QQuickItemPrivate::EffectReference; - } - - if ( d->window ) - { - // putting the nodes on the cleanup list of the window to be deleteted - // in the next cycle of the scene graph - - QQuickWindowPrivate::get( d->window )->cleanup( d->itemNodeInstance ); - } - - // now we can forget about the nodes - - d->itemNodeInstance = nullptr; - d->paintNode = nullptr; - - if ( d->extra.isAllocated() ) - { - d->extra->opacityNode = nullptr; - d->extra->clipNode = nullptr; - d->extra->rootNode = nullptr; - } -} - QskControl* QskControl::owningControl() const { return const_cast< QskControl* >( this ); @@ -2117,11 +918,11 @@ QRectF QskControl::focusIndicatorRect() const return contentsRect(); } -void QskControl::aboutToShow() +void QskControl::updateLayout() { } -void QskControl::updateLayout() +void QskControl::updateResources() { } @@ -2151,24 +952,6 @@ QSizeF QskControl::contentsSizeHint() const return QSizeF( w, h ); } -bool QskControl::isPolishScheduled() const -{ - return d_func()->polishScheduled; -} - -bool QskControl::isUpdateNodeScheduled() const -{ - Q_D( const QskControl ); - - return ( d->dirtyAttributes & QQuickItemPrivate::ContentUpdateMask ) && - ( d->flags & QQuickItem::ItemHasContents ); -} - -bool QskControl::isInitiallyPainted() const -{ - return d_func()->isInitiallyPainted; -} - QVector< QskAspect::Subcontrol > QskControl::subControls() const { using namespace QskAspect; diff --git a/src/controls/QskControl.h b/src/controls/QskControl.h index db19f76d..fddd70c3 100644 --- a/src/controls/QskControl.h +++ b/src/controls/QskControl.h @@ -4,27 +4,23 @@ *****************************************************************************/ #ifndef QSK_CONTROL_H -#define QSK_CONTROL_H 1 +#define QSK_CONTROL_H +#include "QskQuickItem.h" +#include "QskSkinnable.h" #include "QskAspect.h" #include "QskGradient.h" #include "QskSizePolicy.h" -#include "QskSkinnable.h" #include -#include - #include class QskControlPrivate; - -class QskGeometryChangeEvent; -class QskWindowChangeEvent; class QskGestureEvent; template< typename T > class QVector; -class QSK_EXPORT QskControl : public QQuickItem, public QskSkinnable +class QSK_EXPORT QskControl : public QskQuickItem, public QskSkinnable { Q_OBJECT @@ -32,16 +28,10 @@ class QSK_EXPORT QskControl : public QQuickItem, public QskSkinnable WRITE setLocale RESET resetLocale NOTIFY localeChanged FINAL ) Q_PROPERTY( bool autoFillBackground READ autoFillBackground - WRITE setAutoFillBackground NOTIFY controlFlagsChanged FINAL ) + WRITE setAutoFillBackground FINAL ) Q_PROPERTY( bool autoLayoutChildren READ autoLayoutChildren - WRITE setAutoLayoutChildren NOTIFY controlFlagsChanged FINAL ) - - Q_PROPERTY( bool polishOnResize READ polishOnResize - WRITE setPolishOnResize NOTIFY controlFlagsChanged FINAL ) - - Q_PROPERTY( bool transparentForPositioners READ isTransparentForPositioner - WRITE setTransparentForPositioner NOTIFY controlFlagsChanged FINAL ) + WRITE setAutoLayoutChildren FINAL ) Q_PROPERTY( Qt::FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy NOTIFY focusPolicyChanged FINAL ) @@ -49,9 +39,6 @@ class QSK_EXPORT QskControl : public QQuickItem, public QskSkinnable Q_PROPERTY( bool wheelEnabled READ isWheelEnabled WRITE setWheelEnabled NOTIFY wheelEnabledChanged FINAL ) - Q_PROPERTY( bool tabFence READ isTabFence - WRITE setTabFence NOTIFY controlFlagsChanged FINAL ) - Q_PROPERTY( QMarginsF margins READ margins WRITE setMargins RESET resetMargins NOTIFY marginsChanged ) @@ -61,40 +48,17 @@ class QSK_EXPORT QskControl : public QQuickItem, public QskSkinnable Q_PROPERTY( QskSizePolicy sizePolicy READ sizePolicy WRITE setSizePolicy ) Q_PROPERTY( QSizeF minimumSize READ minimumSize WRITE setMinimumSize ) - Q_PROPERTY( QSizeF maximumSize READ maximumSize WRITE setMaximumSize ) - Q_PROPERTY( QSizeF preferredSize READ preferredSize WRITE setPreferredSize ) - Q_PROPERTY( QRectF geometry READ geometry WRITE setGeometry ) - - using Inherited = QQuickItem; + using Inherited = QskQuickItem; public: QSK_STATES( Disabled, Hovered, Focused ) - enum Flag - { - DeferredUpdate = 1 << 0, - DeferredPolish = 1 << 1, - DeferredLayout = 1 << 2, - CleanupOnVisibility = 1 << 3, - - PreferRasterForTextures = 1 << 4, - - DebugForceBackground = 1 << 7, - - LastFlag = DebugForceBackground - }; - - Q_ENUM( Flag ) - Q_DECLARE_FLAGS( Flags, Flag ) - QskControl( QQuickItem* parent = nullptr ); ~QskControl() override; - const char* className() const; - void setMargins( qreal ); void setMargins( const QMarginsF& ); void resetMargins(); @@ -106,9 +70,6 @@ class QSK_EXPORT QskControl : public QQuickItem, public QskSkinnable void resetBackground(); QskGradient background() const; - QRectF geometry() const; - - QRectF rect() const; QRectF contentsRect() const; QRectF layoutRect() const; @@ -122,32 +83,15 @@ class QSK_EXPORT QskControl : public QQuickItem, public QskSkinnable void setAutoFillBackground( bool ); bool autoFillBackground() const; - void setPolishOnResize( bool ); - bool polishOnResize() const; - void setAutoLayoutChildren( bool ); bool autoLayoutChildren() const; - void setTransparentForPositioner( bool ); - bool isTransparentForPositioner() const; - void setWheelEnabled( bool ); bool isWheelEnabled() const; void setFocusPolicy( Qt::FocusPolicy ); Qt::FocusPolicy focusPolicy() const; - void setTabFence( bool ); - bool isTabFence() const; - - void setControlFlags( Flags ); - void resetControlFlags(); - Flags controlFlags() const; - - Q_INVOKABLE void setControlFlag( Flag, bool on = true ); - Q_INVOKABLE void resetControlFlag( Flag ); - Q_INVOKABLE bool testControlFlag( Flag ) const; - void setSizePolicy( QskSizePolicy::Policy, QskSizePolicy::Policy ); void setSizePolicy( QskSizePolicy ); void setSizePolicy( Qt::Orientation, QskSizePolicy::Policy ); @@ -195,24 +139,9 @@ class QSK_EXPORT QskControl : public QQuickItem, public QskSkinnable virtual QSizeF contentsSizeHint() const; - bool isVisibleTo( const QQuickItem* ) const; - QLocale locale() const; void resetLocale(); - void setLayoutMirroring( bool on, bool recursive = false ); - void resetLayoutMirroring(); - bool layoutMirroring() const; - - QSizeF size() const; - QSizeF implicitSize() const; - - void setGeometry( qreal x, qreal y, qreal width, qreal height ); - - bool isPolishScheduled() const; - bool isUpdateNodeScheduled() const; - bool isInitiallyPainted() const; - QVector< QskAspect::Subcontrol > subControls() const; Q_SIGNALS: @@ -220,26 +149,15 @@ class QSK_EXPORT QskControl : public QQuickItem, public QskSkinnable void marginsChanged(); void focusIndicatorRectChanged(); void localeChanged( const QLocale& ); - void controlFlagsChanged(); void focusPolicyChanged(); void wheelEnabledChanged(); public Q_SLOTS: - void setGeometry( const QRectF& ); void setLocale( const QLocale& ); - void show(); - void hide(); - void setVisible( bool ); - - void resetImplicitSize(); - protected: bool event( QEvent* ) override; - virtual void changeEvent( QEvent* ); - virtual void geometryChangeEvent( QskGeometryChangeEvent* ); - virtual void windowChangeEvent( QskWindowChangeEvent* ); virtual void gestureEvent( QskGestureEvent* ); void hoverEnterEvent( QHoverEvent* ) override; @@ -251,61 +169,26 @@ class QSK_EXPORT QskControl : public QQuickItem, public QskSkinnable void itemChange( ItemChange, const ItemChangeData& ) override; void geometryChanged( const QRectF&, const QRectF& ) override; void windowDeactivateEvent() override; - void classBegin() override; - void componentComplete() override; - void releaseResources() override; void initSizePolicy( QskSizePolicy::Policy, QskSizePolicy::Policy ); - void cleanupNodes(); - - virtual void aboutToShow(); // called in updatePolish - virtual void updateLayout(); // called in updatePolish - - bool maybeUnresized() const; + // called from updatePolish + virtual void updateResources(); + virtual void updateLayout(); private: - // don't use boundingRect - it seems to be deprecated - QRectF boundingRect() const override final { return rect(); } - - /* - childrenRect()/childrenRectChanged does not make much sense - in a system, where the parent is responsible for laying out - its children. - */ - void childrenRect() = delete; - void setActiveFocusOnTab( bool ) = delete; // use setFocusPolicy void updateInputMethod( Qt::InputMethodQueries ) = delete; // use qskUpdateInputMethod - QSGNode* updatePaintNode( QSGNode*, UpdatePaintNodeData* ) override final; - void updatePolish() override final; + QSGNode* updateItemPaintNode( QSGNode* ) override final; + void updateItemPolish() override final; QskControl* owningControl() const override final; - void layoutConstraintChanged(); - - void updateControlFlag( uint flag, bool on ); - private: Q_DECLARE_PRIVATE( QskControl ) }; -inline void QskControl::setGeometry( const QRectF& rect ) -{ - setGeometry( rect.x(), rect.y(), rect.width(), rect.height() ); -} - -inline QSizeF QskControl::size() const -{ - return QSizeF( width(), height() ); -} - -inline QSizeF QskControl::implicitSize() const -{ - return QSizeF( implicitWidth(), implicitHeight() ); -} - inline QSizeF QskControl::sizeHint() const { return effectiveSizeHint( Qt::PreferredSize ); @@ -336,7 +219,4 @@ inline const QskControl* qskControlCast( const QObject* object ) return qobject_cast< const QskControl* >( object ); } -Q_DECLARE_OPERATORS_FOR_FLAGS( QskControl::Flags ) -Q_DECLARE_METATYPE( QskControl::Flags ) - #endif diff --git a/src/controls/QskControlPrivate.cpp b/src/controls/QskControlPrivate.cpp new file mode 100644 index 00000000..265c9278 --- /dev/null +++ b/src/controls/QskControlPrivate.cpp @@ -0,0 +1,184 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskControlPrivate.h" +#include "QskSetup.h" +#include "QskLayoutConstraint.h" + +static inline void qskSendEventTo( QObject* object, QEvent::Type type ) +{ + QEvent event( type ); + QCoreApplication::sendEvent( object, &event ); +} + +/* + Qt 5.12: + sizeof( QQuickItemPrivate::ExtraData ) -> 184 + sizeof( QQuickItemPrivate ) -> 320 + + ( these numbers include pointers to optional extra data, but not + the size for the extra data. So the effective memory footprint, + is often even worse ). + + sizeof( QskControlPrivate ) -> sizeof( QQuickItemPrivate ) + 32 + sizeof( QskSkinnable::PrivateData ) -> 40 + + It might be possible to save some bytes, but in the end QskControl + is heavy simply because of deriving from QQuickItem. So without + patching Qt the only way to limit the memory footprint of an application + substantially is to limit the number of QQuickItems. + + That's why QSkinny builds more complex controls from scene graph nodes + instead of doing QQuickItem composition. As this can only be done + in C++ it is kind of obvious, why it is often a bad idea to build + custom controls in QML. + */ + +QskControlPrivate::QskControlPrivate() + : explicitSizeHints( nullptr ) + , sizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Preferred ) + , explicitLocale( false ) + , autoFillBackground( false ) + , autoLayoutChildren( false ) + , focusPolicy( Qt::NoFocus ) + , isWheelEnabled( false ) +{ +} + +QskControlPrivate::~QskControlPrivate() +{ + delete [] explicitSizeHints; +} + +void QskControlPrivate::implicitSizeChanged() +{ + Q_Q( QskControl ); + if ( !q->explicitSizeHint( Qt::PreferredSize ).isValid() ) + { + // when we have no PreferredSize we fall back + // to the implicit size + + layoutConstraintChanged(); + } +} + +QSizeF QskControlPrivate::implicitSizeHint() const +{ + Q_Q( const QskControl ); + + const auto m = q->margins(); + const auto dw = m.left() + m.right(); + const auto dh = m.top() + m.bottom(); + + const auto hint = q->contentsSizeHint(); + + const qreal w = ( hint.width() >= 0 ) ? dw + hint.width() : -1.0; + const qreal h = ( hint.height() >= 0 ) ? dh + hint.height() : -1.0; + + return QSizeF( w, h ); +} + +void QskControlPrivate::setExplicitSizeHint( + Qt::SizeHint whichHint, const QSizeF& size ) +{ + if ( explicitSizeHints == nullptr ) + { + using namespace QskLayoutConstraint; + + explicitSizeHints = new QSizeF[3]; + explicitSizeHints[0] = defaultSizeHints[0]; + explicitSizeHints[1] = defaultSizeHints[1]; + explicitSizeHints[2] = defaultSizeHints[2]; + } + + explicitSizeHints[ whichHint ] = size; +} + +void QskControlPrivate::resetExplicitSizeHint( Qt::SizeHint whichHint ) +{ + if ( explicitSizeHints ) + { + using namespace QskLayoutConstraint; + explicitSizeHints[ whichHint ] = defaultSizeHints[ whichHint ]; + } +} + +QSizeF QskControlPrivate::explicitSizeHint( Qt::SizeHint whichHint ) const +{ + if ( explicitSizeHints ) + return explicitSizeHints[ whichHint ]; + + return QskLayoutConstraint::defaultSizeHints[ whichHint ]; +} + +bool QskControlPrivate::maybeGesture( QQuickItem* child, QEvent* event ) +{ + Q_Q( QskControl ); + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + const auto mouseEvent = static_cast< const QMouseEvent* >( event ); + const QPointF pos = q->mapFromScene( mouseEvent->windowPos() ); + + if ( !q->gestureRect().contains( pos ) ) + return false; + + break; + } + + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::UngrabMouse: + case QEvent::TouchBegin: + case QEvent::TouchCancel: + case QEvent::TouchUpdate: + break; + + default: + return false; + } + + return q->gestureFilter( child, event ); +} + +QSGTransformNode* QskControlPrivate::createTransformNode() +{ + return Inherited::createTransformNode(); +} + +void QskControlPrivate::transformChanged() +{ + Inherited::transformChanged(); +} + +bool QskControlPrivate::inheritLocale( QskControl* control, const QLocale& locale ) +{ + auto d = static_cast< QskControlPrivate* >( QQuickItemPrivate::get( control ) ); + + if ( d->explicitLocale || d->locale == locale ) + return true; + + d->locale = locale; + qskSendEventTo( control, QEvent::LocaleChange ); + + return false; +} + +void QskControlPrivate::resolveLocale( QskControl* control ) +{ + const auto locale = qskSetup->inheritedLocale( control ); + + auto d = static_cast< QskControlPrivate* >( QQuickItemPrivate::get( control ) ); + if ( d->locale != locale ) + { + d->locale = locale; + + qskSendEventTo( control, QEvent::LocaleChange ); + qskSetup->inheritLocale( control, locale ); + } +} diff --git a/src/controls/QskControlPrivate.h b/src/controls/QskControlPrivate.h new file mode 100644 index 00000000..3da5af93 --- /dev/null +++ b/src/controls/QskControlPrivate.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_CONTROL_PRIVATE_H +#define QSK_CONTROL_PRIVATE_H + +#include "QskGlobal.h" +#include "QskControl.h" + +#include "QskQuickItemPrivate.h" + +class QskControlPrivate : public QskQuickItemPrivate +{ + using Inherited = QskQuickItemPrivate; + + public: + static bool inheritLocale( QskControl*, const QLocale& ); + static void resolveLocale( QskControl* ); + + protected: + QskControlPrivate(); + ~QskControlPrivate() override; + + private: + QSGTransformNode* createTransformNode() override; + void transformChanged() override; + + void setExplicitSizeHint( Qt::SizeHint, const QSizeF& ); + void resetExplicitSizeHint( Qt::SizeHint ); + QSizeF explicitSizeHint( Qt::SizeHint ) const; + + bool maybeGesture( QQuickItem*, QEvent* ); + + QSizeF implicitSizeHint() const override final; + void implicitSizeChanged() override final; + + private: + Q_DECLARE_PUBLIC( QskControl ) + + QSizeF* explicitSizeHints; + + QLocale locale; + QskSizePolicy sizePolicy; + + bool explicitLocale : 1; + + bool autoFillBackground : 1; + bool autoLayoutChildren : 1; + + uint focusPolicy : 4; + bool isWheelEnabled : 1; +}; + +#endif diff --git a/src/controls/QskQuickItem.cpp b/src/controls/QskQuickItem.cpp new file mode 100644 index 00000000..9a32b536 --- /dev/null +++ b/src/controls/QskQuickItem.cpp @@ -0,0 +1,842 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskQuickItem.h" +#include "QskQuickItemPrivate.h" +#include "QskQuick.h" +#include "QskEvent.h" +#include "QskSetup.h" +#include "QskSkin.h" +#include "QskDirtyItemFilter.h" + +#include +#include + +#if defined( QT_DEBUG ) +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END +#endif + +#include + +static inline void qskSendEventTo( QObject* object, QEvent::Type type ) +{ + QEvent event( type ); + QCoreApplication::sendEvent( object, &event ); +} + +static inline void qskUpdateControlFlags( + QskQuickItem::Flags flags, QskQuickItem* item ) +{ + auto d = static_cast< QskQuickItemPrivate* >( QskQuickItemPrivate::get( item ) ); + d->updateControlFlags( flags ); +} + +static inline quint16 qskControlFlags() +{ + // we are only interested in the first 8 bits + return static_cast< quint16 >( qskSetup->controlFlags() ); +} + +static inline void qskFilterWindow( QQuickWindow* window ) +{ + if ( window == nullptr ) + return; + + static QskDirtyItemFilter itemFilter; + itemFilter.addWindow( window ); +} + +namespace +{ + class QskQuickItemRegistry + { + public: + QskQuickItemRegistry() + { + /* + Its faster and saves some memory to have this registry instead + of setting up direct connections between qskSetup and each control + */ + QObject::connect( qskSetup, &QskSetup::controlFlagsChanged, + qskSetup, [ this ] { updateControlFlags(); } ); + + QObject::connect( qskSetup, &QskSetup::skinChanged, + qskSetup, [ this ] { updateSkin(); } ); + } + + inline void insert( QskQuickItem* item ) + { + m_items.insert( item ); + } + + inline void remove( QskQuickItem* item ) + { + m_items.erase( item ); + } + + void updateControlFlags() + { + const auto flags = static_cast< QskQuickItem::Flags >( qskControlFlags() ); + + for ( auto item : m_items ) + qskUpdateControlFlags( flags, item ); + } + + void updateSkin() + { + QEvent event( QEvent::StyleChange ); + + for ( auto item : m_items ) + { + event.setAccepted( true ); + QCoreApplication::sendEvent( item, &event ); + } + } + + private: + std::unordered_set< QskQuickItem* > m_items; + }; +} + +namespace +{ + /* + A helper class to store the released window to be able to + put it later into the WindowChange event. + */ + class QskWindowStore + { + public: + QskWindowStore() + : m_refCount( 0 ) + , m_window( nullptr ) + { + } + + void setWindow( QQuickWindow* window ) + { + if ( m_window != window ) + { + m_window = window; + m_refCount = 0; + } + + if ( m_window ) + m_refCount++; + } + + QQuickWindow* window() + { + QQuickWindow* w = m_window; + + if ( m_window ) + { + if ( --m_refCount == 0 ) + m_window = nullptr; + } + + return w; + } + + private: + int m_refCount; + QQuickWindow* m_window; + }; +} + +Q_GLOBAL_STATIC( QskQuickItemRegistry, qskRegistry ) +Q_GLOBAL_STATIC( QskWindowStore, qskReleasedWindowCounter ) + +QskQuickItem::QskQuickItem( QskQuickItemPrivate& dd, QQuickItem* parent ) + : QQuickItem( dd, parent ) +{ + setFlag( QQuickItem::ItemHasContents, true ); + + // since Qt 5.10 we have QQuickItem::ItemEnabledHasChanged +#if QT_VERSION < QT_VERSION_CHECK( 5, 10, 0 ) + /* + Setting up this connections slows down the time needed + for construction by almost 100%. Would be nice to + avoid this penalty also for earlier Qt versions. + */ + connect( this, &QQuickItem::enabledChanged, + [ this ] { qskSendEventTo( this, QEvent::EnabledChange ); } ); +#endif + + Q_D( QskQuickItem ); + if ( d->controlFlags & QskQuickItem::DeferredUpdate ) + qskFilterWindow( window() ); + + qskRegistry->insert( this ); +} + +QskQuickItem::~QskQuickItem() +{ + /* + 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. + */ + d_func()->componentComplete = false; + + if ( qskRegistry ) + qskRegistry->remove( this ); + +#if QT_VERSION < QT_VERSION_CHECK( 5, 10, 0 ) + disconnect( this, &QQuickItem::enabledChanged, nullptr, nullptr ); +#endif +} + +const char* QskQuickItem::className() const +{ + return metaObject()->className(); +} + +void QskQuickItem::classBegin() +{ + Inherited::classBegin(); +} + +void QskQuickItem::componentComplete() +{ +#if defined( QT_DEBUG ) + if ( qobject_cast< const QQuickBasePositioner* >( parent() ) ) + { + if ( d_func()->controlFlags & QskQuickItem::DeferredLayout ) + { + qWarning( "QskQuickItem in DeferredLayout mode under control of a positioner" ); + } + } +#endif + + Inherited::componentComplete(); +} + +void QskQuickItem::releaseResources() +{ + Inherited::releaseResources(); + + // QQuickItem::derefWindow runs over the children between + // calling releaseResources and itemChange. So we need to have + // a reference count to know, when we have processed all + // sequences to be able to provide the correct "oldWindow" + // in the WindowChange event. + + qskReleasedWindowCounter->setWindow( window() ); +} + +void QskQuickItem::setVisible( bool on ) +{ + // QQuickItem::setVisible is no slot + Inherited::setVisible( on ); +} + +void QskQuickItem::show() +{ + Inherited::setVisible( true ); +} + +void QskQuickItem::hide() +{ + Inherited::setVisible( false ); +} + +bool QskQuickItem::isVisibleTo( const QQuickItem* ancestor ) const +{ + return qskIsVisibleTo( this, ancestor ); +} + +void QskQuickItem::setGeometry( qreal x, qreal y, qreal width, qreal height ) +{ + // QQuickItem does not even offer changing the geometry + // in one call - what leads to 2 calls of the updateGeometry + // hook. Grmpf ... + + Q_D( QQuickItem ); + + d->heightValid = true; + d->widthValid = true; + + const QRectF oldRect( d->x, d->y, d->width, d->height ); + + int dirtyType = 0; + + if ( d->x != x || d->y != y ) + { + d->x = x; + d->y = y; + + dirtyType |= QQuickItemPrivate::Position; + } + + if ( d->width != width || d->height != height ) + { + d->height = height; + d->width = width; + + dirtyType |= QQuickItemPrivate::Size; + } + + if ( dirtyType ) + { + if ( dirtyType & QQuickItemPrivate::Position ) + d->dirty( QQuickItemPrivate::Position ); + + if ( dirtyType & QQuickItemPrivate::Size ) + d->dirty( QQuickItemPrivate::Size ); + + /* + Unfortunately geometryChanged is protected and we can't implement + this code as qskSetItemGeometry - further hacking required: TODO ... + */ + + geometryChanged( QRectF( d->x, d->y, d->width, d->height ), oldRect ); + } +} + +QRectF QskQuickItem::rect() const +{ + Q_D( const QQuickItem ); + return QRectF( 0, 0, d->width, d->height ); +} + +QRectF QskQuickItem::geometry() const +{ + Q_D( const QQuickItem ); + return QRectF( d->x, d->y, d->width, d->height ); +} + +void QskQuickItem::setTransparentForPositioner( bool on ) +{ + Q_D( QQuickItem ); + if ( on != d->isTransparentForPositioner() ) + { + d->setTransparentForPositioner( on ); + Q_EMIT itemFlagsChanged(); + } +} + +bool QskQuickItem::isTransparentForPositioner() const +{ + return d_func()->isTransparentForPositioner(); +} + +void QskQuickItem::setTabFence( bool on ) +{ + Q_D( QQuickItem ); + if ( on != d->isTabFence ) + { + d->isTabFence = on; + Q_EMIT itemFlagsChanged(); + } +} + +bool QskQuickItem::isTabFence() const +{ + return d_func()->isTabFence; +} + +void QskQuickItem::setPolishOnResize( bool on ) +{ + Q_D( QskQuickItem ); + if ( on != d->polishOnResize ) + { + d->polishOnResize = on; + polish(); + + Q_EMIT itemFlagsChanged(); + } +} + +bool QskQuickItem::polishOnResize() const +{ + return d_func()->polishOnResize; +} + +bool QskQuickItem::layoutMirroring() const +{ + return d_func()->effectiveLayoutMirror; +} + +void QskQuickItem::setLayoutMirroring( bool on, bool recursive ) +{ + // Again we have to deal with an existing API made for QML, + // that is weired for C++: LayoutMirroring/QQuickLayoutMirroringAttached + // Internally it is managed by 5(!) different flags - condolences + // to the poor guy who has been sentenced to maintain this. + + // Anyway, the code below might achieve the desired behavior without + // breaking the QML path. + + Q_D( QQuickItem ); + + if ( recursive != d->inheritMirrorFromItem ) + { + d->inheritMirrorFromItem = recursive; + d->resolveLayoutMirror(); + } + + d->isMirrorImplicit = false; + + if ( on != d->effectiveLayoutMirror ) + { + d->setLayoutMirror( on ); + if ( recursive ) + d->resolveLayoutMirror(); + } +} + +void QskQuickItem::resetLayoutMirroring() +{ + Q_D( QQuickItem ); + + if ( d && !d->isMirrorImplicit ) + { + d->isMirrorImplicit = true; + // d->inheritMirrorFromItem = false; + d->resolveLayoutMirror(); + } +} + +bool QskQuickItem::isPolishScheduled() const +{ + return d_func()->polishScheduled; +} + +bool QskQuickItem::isUpdateNodeScheduled() const +{ + Q_D( const QskQuickItem ); + + return ( d->dirtyAttributes & QQuickItemPrivate::ContentUpdateMask ) && + ( d->flags & QQuickItem::ItemHasContents ); +} + +bool QskQuickItem::isInitiallyPainted() const +{ + return d_func()->isInitiallyPainted; +} + +bool QskQuickItem::maybeUnresized() const +{ + Q_D( const QskQuickItem ); + + if ( d->width <= 0.0 && d->height <= 0.0 ) + { + /* + Unfortunately the list of items to-be-polished is not processed + in top/down order and we might run into updatePolish() before + having a proper size. But when the parentItem() is waiting + for to-be-polished, we assume, that we will be resized then + and run into another updatePolish() then. + */ + if ( d->polishOnResize && qskIsPolishScheduled( parentItem() ) ) + return true; + } + + return false; +} + +QskQuickItem::Flags QskQuickItem::controlFlags() const +{ + return QskQuickItem::Flags( d_func()->controlFlags ); +} + +void QskQuickItem::setControlFlags( Flags flags ) +{ + Q_D( QskQuickItem ); + + // set all bits in the mask + d->controlFlagsMask = std::numeric_limits< quint16 >::max(); + d->updateControlFlags( flags ); +} + +void QskQuickItem::resetControlFlags() +{ + Q_D( QskQuickItem ); + + // clear all bits in the mask + d->controlFlagsMask = 0; + d->updateControlFlags( static_cast< Flags >( qskControlFlags() ) ); +} + +void QskQuickItem::setControlFlag( Flag flag, bool on ) +{ + Q_D( QskQuickItem ); + + d->controlFlagsMask |= flag; + + if ( ( d->controlFlags & flag ) != on ) + { + updateControlFlag( flag, on ); + Q_EMIT controlFlagsChanged(); + } +} + +void QskQuickItem::resetControlFlag( Flag flag ) +{ + Q_D( QskQuickItem ); + + d->controlFlagsMask &= ~flag; + + const bool on = qskSetup->testControlFlag( static_cast< QskSetup::Flag >( flag ) ); + + if ( ( d->controlFlags & flag ) != on ) + { + updateControlFlag( flag, on ); + Q_EMIT controlFlagsChanged(); + } +} + +bool QskQuickItem::testControlFlag( Flag flag ) const +{ + return d_func()->controlFlags & flag; +} + +void QskQuickItem::updateControlFlag( uint flag, bool on ) +{ + Q_D( QskQuickItem ); + + if ( ( flag > std::numeric_limits< quint16 >::max() ) || + ( bool( d->controlFlags & flag ) == on ) ) + { + return; + } + + if ( on ) + d->controlFlags |= flag; + else + d->controlFlags &= ~flag; + + switch ( flag ) + { + case QskQuickItem::DeferredUpdate: + { + if ( on ) + { + qskFilterWindow( window() ); + } + else + { + if ( !isVisible() ) + update(); + } + + break; + } + case QskQuickItem::DeferredPolish: + { + if ( !on && d->blockedPolish ) + polish(); + + break; + } + case QskQuickItem::DeferredLayout: + { + if ( !on ) + { + // Update the implicitSize and rebind the size to it. + // Having set the size explicitly gets lost. + + d->widthValid = d->heightValid = false; + d->updateImplicitSize( false ); + } + + break; + } + case QskQuickItem::CleanupOnVisibility: + { + if ( on && !isVisible() ) + d->cleanupNodes(); + + break; + } + case QskQuickItem::DebugForceBackground: + { + // no need to mark it dirty + if ( flags() & QQuickItem::ItemHasContents ) + update(); + break; + } + default: + break; + } +} + +void QskQuickItem::resetImplicitSize() +{ + Q_D( QskQuickItem ); + + if ( d->controlFlags & QskQuickItem::DeferredLayout ) + { + d->blockedImplicitSize = true; + d->layoutConstraintChanged(); + } + else + { + d->updateImplicitSize( true ); + } +} + +bool QskQuickItem::event( QEvent* event ) +{ + const int eventType = event->type(); + + switch( eventType ) + { + case QEvent::StyleChange: + { + d_func()->clearPreviousNodes = true; + + resetImplicitSize(); + polish(); + + if ( flags() & QQuickItem::ItemHasContents ) + update(); + + changeEvent( event ); + return true; + } + case QEvent::ContentsRectChange: + { + if ( polishOnResize() ) + polish(); + + changeEvent( event ); + return true; + } + case QEvent::EnabledChange: + case QEvent::FontChange: + case QEvent::PaletteChange: + case QEvent::LocaleChange: + case QEvent::ReadOnlyChange: + case QEvent::ParentChange: + { + changeEvent( event ); + return true; + } + case QskEvent::GeometryChange: + { + geometryChangeEvent( static_cast< QskGeometryChangeEvent* >( event ) ); + return true; + } + case QskEvent::WindowChange: + { + windowChangeEvent( static_cast< QskWindowChangeEvent* >( event ) ); + return true; + } + case QEvent::LayoutRequest: + { + if ( d_func()->polishOnResize ) + polish(); + + return true; + } + } + + return Inherited::event( event ); +} + +void QskQuickItem::windowChangeEvent( QskWindowChangeEvent* ) +{ +} + +void QskQuickItem::geometryChangeEvent( QskGeometryChangeEvent* ) +{ +} + +void QskQuickItem::changeEvent( QEvent* ) +{ +} + +void QskQuickItem::itemChange( QQuickItem::ItemChange change, + const QQuickItem::ItemChangeData& value ) +{ + switch ( change ) + { + case QQuickItem::ItemSceneChange: + { + if ( value.window ) + { + Q_D( const QskQuickItem ); + if ( d->controlFlags & QskQuickItem::DeferredUpdate ) + qskFilterWindow( value.window ); + } + +#if 1 + auto oldWindow = qskReleasedWindowCounter->window(); + if ( oldWindow && ( oldWindow->activeFocusItem() == this ) ) + { + /* + Removing an item from the scene might result in + changes of the active focus item. Unfortunately the corresponding + FocusIn/Out events are sent, while the item tree is in an + invalid state. + When having event handlers, that do modifications of the focus + ( f.e. assigning the local focus, inside of a focus scope ) + we might end up with having a dangling pointer for + oldWindow->activeFocusItem(). + */ + QQuickWindowPrivate::get( oldWindow )->clearFocusInScope( + qskNearestFocusScope( this ), this, Qt::OtherFocusReason ); + } +#endif + + QskWindowChangeEvent event( oldWindow, value.window ); + QCoreApplication::sendEvent( this, &event ); + + break; + } +#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 ) + case QQuickItem::ItemEnabledHasChanged: + { + qskSendEventTo( this, QEvent::EnabledChange ); + break; + } +#endif + case QQuickItem::ItemVisibleHasChanged: + { + Q_D( QskQuickItem ); +#if 1 + /* + ~QQuickItem sends QQuickItem::ItemVisibleHasChanged recursively + to all childItems. When being a child ( not only a childItem() ) + we are short before being destructed too and any updates + done here are totally pointless. TODO ... + */ +#endif + if ( value.boolValue ) + { + if ( d->blockedPolish ) + polish(); + + if ( d->controlFlags & QskQuickItem::DeferredUpdate ) + { + if ( d->dirtyAttributes && ( d->flags & QQuickItem::ItemHasContents ) ) + update(); + } + } + else + { + if ( d->controlFlags & QskQuickItem::CleanupOnVisibility ) + d->cleanupNodes(); + + d->isInitiallyPainted = false; + } + + if ( parentItem() && parentItem()->isVisible() ) + { + /* + Layout code might consider the visiblility of the children + and therefore needs to be updated. Posting a statement about + changed layout constraints has this effect, but is not correct. + The right way to go would be to create show/hide events and to + handle them, where visibility of the children matters. + TODO ... + */ + + d->layoutConstraintChanged(); + } + + break; + } + + + case QQuickItem::ItemParentHasChanged: + case QQuickItem::ItemChildAddedChange: + case QQuickItem::ItemChildRemovedChange: + case ItemOpacityHasChanged: + case ItemActiveFocusHasChanged: + case ItemRotationHasChanged: + case ItemAntialiasingHasChanged: + case ItemDevicePixelRatioHasChanged: + { + break; + } + } + + Inherited::itemChange( change, value ); +} + +void QskQuickItem::geometryChanged( + const QRectF& newGeometry, const QRectF& oldGeometry ) +{ + Inherited::geometryChanged( newGeometry, oldGeometry ); + + Q_D( const QskQuickItem ); + if ( !d->polishScheduled && d->polishOnResize ) + { + if ( newGeometry.size() != oldGeometry.size() ) + polish(); + } + + QskGeometryChangeEvent event( newGeometry, oldGeometry ); + QCoreApplication::sendEvent( this, &event ); +} + +void QskQuickItem::updatePolish() +{ + Q_D( QskQuickItem ); + + if ( d->controlFlags & QskQuickItem::DeferredPolish ) + { + if ( !isVisible() ) + { + d->blockedPolish = true; + return; + } + } + + d->blockedPolish = false; + + if ( !d->isInitiallyPainted ) + { + /* + We should find a better way for identifying, when + an item is about to be shown, than making it dependend + from polishing and the existence of scene graph nodes. TODO ... + */ + aboutToShow(); + } + + updateItemPolish(); +} + +void QskQuickItem::aboutToShow() +{ +} + +void QskQuickItem::updateItemPolish() +{ +} + +QSGNode* QskQuickItem::updatePaintNode( QSGNode* node, UpdatePaintNodeData* data ) +{ + Q_UNUSED( data ); + + Q_D( QskQuickItem ); + + Q_ASSERT( isVisible() || !( d->controlFlags & QskQuickItem::DeferredUpdate ) ); + + d->isInitiallyPainted = true; + + if ( d->clearPreviousNodes ) + { + delete node; + node = nullptr; + + d->clearPreviousNodes = false; + } + + return updateItemPaintNode( node ); +} + +QSGNode* QskQuickItem::updateItemPaintNode( QSGNode* node ) +{ + return node; +} + +#include "moc_QskQuickItem.cpp" diff --git a/src/controls/QskQuickItem.h b/src/controls/QskQuickItem.h new file mode 100644 index 00000000..bd8c8bcc --- /dev/null +++ b/src/controls/QskQuickItem.h @@ -0,0 +1,162 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_QUICK_ITEM_H +#define QSK_QUICK_ITEM_H + +#include "QskGlobal.h" +#include + +class QskQuickItemPrivate; +class QskGeometryChangeEvent; +class QskWindowChangeEvent; + +class QSK_EXPORT QskQuickItem : public QQuickItem +{ + Q_OBJECT + + Q_PROPERTY( QRectF geometry READ geometry WRITE setGeometry ) + + Q_PROPERTY( bool transparentForPositioners READ isTransparentForPositioner + WRITE setTransparentForPositioner NOTIFY itemFlagsChanged ) + + Q_PROPERTY( bool tabFence READ isTabFence + WRITE setTabFence NOTIFY itemFlagsChanged ) + + Q_PROPERTY( bool polishOnResize READ polishOnResize + WRITE setPolishOnResize NOTIFY itemFlagsChanged FINAL ) + + using Inherited = QQuickItem; + + public: + enum Flag + { + DeferredUpdate = 1 << 0, + DeferredPolish = 1 << 1, + DeferredLayout = 1 << 2, + CleanupOnVisibility = 1 << 3, + + PreferRasterForTextures = 1 << 4, + + DebugForceBackground = 1 << 7, + + LastFlag = DebugForceBackground + }; + + Q_ENUM( Flag ) + Q_DECLARE_FLAGS( Flags, Flag ) + + ~QskQuickItem() override; + + const char* className() const; + + bool isVisibleTo( const QQuickItem* ) const; + + QRectF geometry() const; + QRectF rect() const; + QSizeF size() const; + QSizeF implicitSize() const; + + void setGeometry( qreal x, qreal y, qreal width, qreal height ); + + void setPolishOnResize( bool ); + bool polishOnResize() const; + + void setTransparentForPositioner( bool ); + bool isTransparentForPositioner() const; + + void setTabFence( bool ); + bool isTabFence() const; + + void setLayoutMirroring( bool on, bool recursive = false ); + void resetLayoutMirroring(); + bool layoutMirroring() const; + + void setControlFlags( Flags ); + void resetControlFlags(); + Flags controlFlags() const; + + Q_INVOKABLE void setControlFlag( Flag, bool on = true ); + Q_INVOKABLE void resetControlFlag( Flag ); + Q_INVOKABLE bool testControlFlag( Flag ) const; + + void classBegin() override; + void componentComplete() override; + void releaseResources() override; + + bool isPolishScheduled() const; + bool isUpdateNodeScheduled() const; + bool isInitiallyPainted() const; + + bool maybeUnresized() const; + + Q_SIGNALS: + void itemFlagsChanged(); + void controlFlagsChanged(); + + public Q_SLOTS: + void setGeometry( const QRectF& ); + + void show(); + void hide(); + void setVisible( bool ); + + void resetImplicitSize(); + + protected: + QskQuickItem( QskQuickItemPrivate&, QQuickItem* = nullptr ); + + bool event( QEvent* ) override; + + virtual void changeEvent( QEvent* ); + virtual void geometryChangeEvent( QskGeometryChangeEvent* ); + virtual void windowChangeEvent( QskWindowChangeEvent* ); + + void itemChange( ItemChange, const ItemChangeData& ) override; + void geometryChanged( const QRectF&, const QRectF& ) override; + + virtual void aboutToShow(); // called in updatePolish + + private: + // don't use boundingRect - it seems to be deprecated + QRectF boundingRect() const override final { return rect(); } + + /* + childrenRect()/childrenRectChanged does not make much sense + in a system, where the parent is responsible for laying out + its children. + */ + void childrenRect() = delete; + + void updateControlFlag( uint flag, bool on ); + + QSGNode* updatePaintNode( QSGNode*, UpdatePaintNodeData* ) override final; + virtual QSGNode* updateItemPaintNode( QSGNode* ); + + void updatePolish() override final; + virtual void updateItemPolish(); + + Q_DECLARE_PRIVATE( QskQuickItem ) +}; + +inline void QskQuickItem::setGeometry( const QRectF& rect ) +{ + setGeometry( rect.x(), rect.y(), rect.width(), rect.height() ); +} + +inline QSizeF QskQuickItem::size() const +{ + return QSizeF( width(), height() ); +} + +inline QSizeF QskQuickItem::implicitSize() const +{ + return QSizeF( implicitWidth(), implicitHeight() ); +} + +Q_DECLARE_OPERATORS_FOR_FLAGS( QskQuickItem::Flags ) +Q_DECLARE_METATYPE( QskQuickItem::Flags ) + +#endif diff --git a/src/controls/QskQuickItemPrivate.cpp b/src/controls/QskQuickItemPrivate.cpp new file mode 100644 index 00000000..6bae2311 --- /dev/null +++ b/src/controls/QskQuickItemPrivate.cpp @@ -0,0 +1,242 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskQuickItemPrivate.h" +#include "QskSetup.h" + +static inline quint16 qskControlFlags() +{ + // we are only interested in the first 8 bits + return static_cast< quint16 >( qskSetup->controlFlags() ); +} + +static inline void qskSendEventTo( QObject* object, QEvent::Type type ) +{ + QEvent event( type ); + QCoreApplication::sendEvent( object, &event ); +} + +QskQuickItemPrivate::QskQuickItemPrivate() + : controlFlags( qskControlFlags() ) + , controlFlagsMask( 0 ) + , polishOnResize( false ) + , blockedPolish( false ) + , blockedImplicitSize( true ) + , clearPreviousNodes( false ) + , isInitiallyPainted( false ) + , blockLayoutRequestEvents( true ) +{ + if ( controlFlags & QskQuickItem::DeferredLayout ) + { + /* + In general the geometry of an item should be the job of + the parent - unfortunatly not done by Qt Quick + probably in the spirit of "making things easier". + + To avoid potentially expensive calculations happening + too often and early QskControl blocks updates of + the implicitSize and any auto resizing of the control + according to it. + + There should be no strong reason for using concepts + like Positioners, that rely on implicit resizing, + but to make it working: the DeferredLayout flag needs to be disabled. + */ + + widthValid = heightValid = true; + } +} + +QskQuickItemPrivate::~QskQuickItemPrivate() +{ +} + +void QskQuickItemPrivate::mirrorChange() +{ + Q_Q( QskQuickItem ); + qskSendEventTo( q, QEvent::LayoutDirectionChange ); +} + +void QskQuickItemPrivate::updateControlFlags( QskQuickItem::Flags flags ) +{ + const auto oldFlags = controlFlags; + const auto newFlags = static_cast< quint16 >( flags ); + + if ( oldFlags != newFlags ) + { + Q_Q( QskQuickItem ); + + const auto numBits = qCountTrailingZeroBits( + static_cast< quint32 >( QskQuickItem::LastFlag ) ); + + for ( quint32 i = 0; i <= numBits; ++i ) + { + const quint32 flag = ( 1 << i ); + q->updateControlFlag( flag, flags & flag ); + } + + Q_EMIT q->controlFlagsChanged(); + } +} + +void QskQuickItemPrivate::layoutConstraintChanged() +{ + if ( !blockLayoutRequestEvents ) + { + Q_Q( QskQuickItem ); + if ( auto item = q->parentItem() ) + qskSendEventTo( item, QEvent::LayoutRequest ); + + /* + We don't send further LayoutRequest events until someone + actively requests a layout relevant information + */ + blockLayoutRequestEvents = true; + } +} + +void QskQuickItemPrivate::implicitSizeChanged() +{ + layoutConstraintChanged(); +} + +qreal QskQuickItemPrivate::getImplicitWidth() const +{ + if ( blockedImplicitSize ) + { + auto that = const_cast< QskQuickItemPrivate* >( this ); + that->updateImplicitSize( false ); + } + + return implicitWidth; +} + +qreal QskQuickItemPrivate::getImplicitHeight() const +{ + if ( blockedImplicitSize ) + { + auto that = const_cast< QskQuickItemPrivate* >( this ); + that->updateImplicitSize( false ); + } + + return implicitHeight; +} + +void QskQuickItemPrivate::updateImplicitSize( bool doNotify ) +{ + blockedImplicitSize = false; + + const auto hint = implicitSizeHint(); + setImplicitSize( hint.width(), hint.height(), doNotify ); +} + +void QskQuickItemPrivate::setImplicitSize( qreal w, qreal h, bool doNotify ) +{ + const bool doWidth = ( w != implicitWidth ); + const bool doHeight = ( h != implicitHeight ); + + if ( !( doWidth || doHeight ) ) + return; // nothing to do + + implicitWidth = w; + implicitHeight = h; + + if ( !( widthValid && heightValid ) ) + { + // auto adjusting the size + + const qreal oldWidth = width; + const qreal oldHeight = height; + + if ( doWidth && !widthValid ) + width = qMax( w, qreal( 0.0 ) ); + + if ( doHeight && !heightValid ) + height = qMax( h, qreal( 0.0 ) ); + + if ( ( width != oldWidth ) || ( height != oldHeight ) ) + { + dirty( QQuickItemPrivate::Size ); + + const QRectF oldRect( x, y, oldWidth, oldHeight ); + const QRectF newRect( x, y, width, height ); + + Q_Q( QskQuickItem ); + q->geometryChanged( newRect, oldRect ); + } + } + + if ( doNotify ) + { + // calling implicitSizeChanged only once, TODO ... + + if ( doWidth ) + Inherited::implicitWidthChanged(); + + if ( doHeight ) + Inherited::implicitHeightChanged(); + } +} + +void QskQuickItemPrivate::implicitWidthChanged() +{ + Inherited::implicitWidthChanged(); + + blockedImplicitSize = false; + implicitSizeChanged(); +} + +void QskQuickItemPrivate::implicitHeightChanged() +{ + Inherited::implicitWidthChanged(); + + blockedImplicitSize = false; + implicitSizeChanged(); +} + +void QskQuickItemPrivate::cleanupNodes() +{ + if ( itemNodeInstance == nullptr ) + return; + + // setting the dirty flags, so that nodes will be recreated + // the next time we participate in a scene graph update + + if ( !itemNodeInstance->matrix().isIdentity() ) + dirtyAttributes |= QQuickItemPrivate::Position; + + if ( extra.isAllocated() ) + { + if ( extra->clipNode ) + dirtyAttributes |= QQuickItemPrivate::Clip; + + if ( extra->opacityNode ) + dirtyAttributes |= QQuickItemPrivate::OpacityValue; + + if ( extra->rootNode ) + dirtyAttributes |= QQuickItemPrivate::EffectReference; + } + + if ( window ) + { + // putting the nodes on the cleanup list of the window to be deleteted + // in the next cycle of the scene graph + + QQuickWindowPrivate::get( window )->cleanup( itemNodeInstance ); + } + + // now we can forget about the nodes + + itemNodeInstance = nullptr; + paintNode = nullptr; + + if ( extra.isAllocated() ) + { + extra->opacityNode = nullptr; + extra->clipNode = nullptr; + extra->rootNode = nullptr; + } +} + diff --git a/src/controls/QskQuickItemPrivate.h b/src/controls/QskQuickItemPrivate.h new file mode 100644 index 00000000..8c05fe03 --- /dev/null +++ b/src/controls/QskQuickItemPrivate.h @@ -0,0 +1,64 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_QUICK_ITEM_PRIVATE_H +#define QSK_QUICK_ITEM_PRIVATE_H + +#include "QskGlobal.h" +#include "QskQuickItem.h" + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +class QskQuickItemPrivate : public QQuickItemPrivate +{ + using Inherited = QQuickItemPrivate; + + protected: + QskQuickItemPrivate(); + ~QskQuickItemPrivate() override; + + public: + void updateControlFlags( QskQuickItem::Flags ); + + protected: + void layoutConstraintChanged(); + virtual void implicitSizeChanged(); + virtual QSizeF implicitSizeHint() const = 0; + + private: + void cleanupNodes(); + void mirrorChange() override; + + qreal getImplicitWidth() const override final; + qreal getImplicitHeight() const override final; + + void implicitWidthChanged() override final; + void implicitHeightChanged() override final; + + void updateImplicitSize( bool doNotify ); + + void setImplicitSize( qreal width, qreal height, bool doNotify ); + + private: + Q_DECLARE_PUBLIC( QskQuickItem ) + + quint16 controlFlags; + quint16 controlFlagsMask; + + bool polishOnResize : 1; + + bool blockedPolish : 1; + bool blockedImplicitSize : 1; + bool clearPreviousNodes : 1; + + bool isInitiallyPainted : 1; + + protected: + mutable bool blockLayoutRequestEvents : 1; +}; + +#endif diff --git a/src/controls/QskSetup.cpp b/src/controls/QskSetup.cpp index 31941613..68bbb8dd 100644 --- a/src/controls/QskSetup.cpp +++ b/src/controls/QskSetup.cpp @@ -5,6 +5,7 @@ #include "QskSetup.h" #include "QskControl.h" +#include "QskControlPrivate.h" #include "QskGraphicProviderMap.h" #include "QskObjectTree.h" #include "QskSkin.h" @@ -73,7 +74,6 @@ static void qskApplicationFilter() Q_CONSTRUCTOR_FUNCTION( qskApplicationHook ) Q_COREAPP_STARTUP_FUNCTION( qskApplicationFilter ) -extern bool qskInheritLocale( QskControl*, const QLocale& ); extern bool qskInheritLocale( QskWindow*, const QLocale& ); namespace @@ -90,7 +90,7 @@ namespace bool setImplicitValue( QskControl* control, const QLocale& locale ) override { - return qskInheritLocale( control, locale ); + return QskControlPrivate::inheritLocale( control, locale ); } bool setImplicitValue( QskWindow* window, diff --git a/src/src.pro b/src/src.pro index 1f3d49b2..b187faaa 100644 --- a/src/src.pro +++ b/src/src.pro @@ -111,6 +111,7 @@ HEADERS += \ controls/QskBox.h \ controls/QskBoxSkinlet.h \ controls/QskControl.h \ + controls/QskControlPrivate.h \ controls/QskDirtyItemFilter.h \ controls/QskEvent.h \ controls/QskFlickAnimator.h \ @@ -133,6 +134,8 @@ HEADERS += \ controls/QskPushButton.h \ controls/QskPushButtonSkinlet.h \ controls/QskQuick.h \ + controls/QskQuickItem.h \ + controls/QskQuickItemPrivate.h \ controls/QskRangeControl.h \ controls/QskScrollArea.h \ controls/QskScrollView.h \ @@ -175,6 +178,7 @@ SOURCES += \ controls/QskBox.cpp \ controls/QskBoxSkinlet.cpp \ controls/QskControl.cpp \ + controls/QskControlPrivate.cpp \ controls/QskDirtyItemFilter.cpp \ controls/QskEvent.cpp \ controls/QskFlickAnimator.cpp \ @@ -197,6 +201,8 @@ SOURCES += \ controls/QskPushButton.cpp \ controls/QskPushButtonSkinlet.cpp \ controls/QskQuick.cpp \ + controls/QskQuickItem.cpp \ + controls/QskQuickItemPrivate.cpp \ controls/QskRangeControl.cpp \ controls/QskScrollArea.cpp \ controls/QskScrollView.cpp \