diff options
Diffstat (limited to 'src/quicktemplates')
176 files changed, 6119 insertions, 1443 deletions
diff --git a/src/quicktemplates/CMakeLists.txt b/src/quicktemplates/CMakeLists.txt index 57f4897b53..6a98ce2f89 100644 --- a/src/quicktemplates/CMakeLists.txt +++ b/src/quicktemplates/CMakeLists.txt @@ -26,9 +26,6 @@ qt_internal_add_qml_module(QuickTemplates2 qquickbuttongroup.cpp qquickbuttongroup_p.h qquickcheckbox.cpp qquickcheckbox_p.h qquickcheckdelegate.cpp qquickcheckdelegate_p.h - qquickcombobox.cpp qquickcombobox_p.h - qquickcontainer.cpp qquickcontainer_p.h - qquickcontainer_p_p.h qquickcontentitem.cpp qquickcontentitem_p.h qquickcontrol.cpp qquickcontrol_p.h qquickcontrol_p_p.h @@ -37,10 +34,6 @@ qt_internal_add_qml_module(QuickTemplates2 qquickdeferredpointer_p_p.h qquickdelaybutton.cpp qquickdelaybutton_p.h qquickdial.cpp qquickdial_p.h - qquickdialog.cpp qquickdialog_p.h - qquickdialog_p_p.h - qquickdialogbuttonbox.cpp qquickdialogbuttonbox_p.h - qquickdialogbuttonbox_p_p.h qquickdrawer.cpp qquickdrawer_p.h qquickdrawer_p_p.h qquickframe.cpp qquickframe_p.h @@ -52,15 +45,13 @@ qt_internal_add_qml_module(QuickTemplates2 qquickitemdelegate_p_p.h qquicklabel.cpp qquicklabel_p.h qquicklabel_p_p.h - qquickmenu.cpp qquickmenu_p.h - qquickmenu_p_p.h - qquickmenubar.cpp qquickmenubar_p.h - qquickmenubar_p_p.h - qquickmenubaritem.cpp qquickmenubaritem_p.h - qquickmenubaritem_p_p.h - qquickmenuitem.cpp qquickmenuitem_p.h - qquickmenuitem_p_p.h qquickmenuseparator.cpp qquickmenuseparator_p.h + qquicknativeicon_p.h + qquicknativeicon.cpp + qquicknativeiconloader_p.h + qquicknativeiconloader.cpp + qquicknativemenuitem_p.h + qquicknativemenuitem.cpp qquickoverlay.cpp qquickoverlay_p.h qquickoverlay_p_p.h qquickpage.cpp qquickpage_p.h @@ -76,6 +67,8 @@ qt_internal_add_qml_module(QuickTemplates2 qquickpopupitem_p_p.h qquickpopuppositioner.cpp qquickpopuppositioner_p_p.h + qquickpopupwindow.cpp + qquickpopupwindow_p_p.h qquickpresshandler.cpp qquickpresshandler_p_p.h qquickprogressbar.cpp qquickprogressbar_p.h @@ -91,20 +84,15 @@ qt_internal_add_qml_module(QuickTemplates2 qquickshortcutcontext_p_p.h qquickslider.cpp qquickslider_p.h qquickspinbox.cpp qquickspinbox_p.h - qquicksplitview.cpp qquicksplitview_p.h qquickstackelement.cpp qquickstackelement_p_p.h - qquickstacktransition.cpp - qquickstacktransition_p_p.h qquickstackview.cpp qquickstackview_p.cpp qquickstackview_p.h qquickstackview_p_p.h qquickswipe_p.h qquickswipedelegate.cpp qquickswipedelegate_p.h qquickswipedelegate_p_p.h - qquickswipeview.cpp qquickswipeview_p.h qquickswitch.cpp qquickswitch_p.h qquickswitchdelegate.cpp qquickswitchdelegate_p.h - qquicktabbar.cpp qquicktabbar_p.h qquicktabbutton.cpp qquicktabbutton_p.h qquicktextarea.cpp qquicktextarea_p.h qquicktextarea_p_p.h @@ -119,6 +107,8 @@ qt_internal_add_qml_module(QuickTemplates2 qquickvelocitycalculator.cpp qquickvelocitycalculator_p_p.h qtquicktemplates2global.cpp qtquicktemplates2global_p.h + NO_UNITY_BUILD_SOURCES + qquickpopupitem.cpp # redefinition of 'contentItemName' (from qquickcontrol.cpp) DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII @@ -133,9 +123,7 @@ qt_internal_add_qml_module(QuickTemplates2 Qt::Core Qt::Gui Qt::Quick - GENERATE_CPP_EXPORTS - GENERATE_PRIVATE_CPP_EXPORTS -) + ) qt_internal_extend_target(QuickTemplates2 CONDITION TARGET Qt::QmlModels LIBRARIES @@ -151,6 +139,31 @@ qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_accessibility accessible/qaccessiblequickpage.cpp accessible/qaccessiblequickpage_p.h ) +qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_quicktemplates2_container + SOURCES + qquickcontainer.cpp qquickcontainer_p.h + qquickcontainer_p_p.h + qquickdialog.cpp qquickdialog_p.h + qquickdialog_p_p.h + qquickdialogbuttonbox.cpp qquickdialogbuttonbox_p.h + qquickdialogbuttonbox_p_p.h + qquickmenubar.cpp qquickmenubar_p.h + qquickmenubar_p_p.h + qquickmenubaritem.cpp qquickmenubaritem_p.h + qquickmenubaritem_p_p.h + qquicksplitview.cpp qquicksplitview_p.h + qquickswipeview.cpp qquickswipeview_p.h + qquicktabbar.cpp qquicktabbar_p.h +) + +qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_qml_object_model + SOURCES + qquickmenu.cpp qquickmenu_p.h + qquickmenu_p_p.h + qquickmenuitem.cpp qquickmenuitem_p.h + qquickmenuitem_p_p.h +) + qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_quick_tableview SOURCES qquickheaderview.cpp qquickheaderview_p.h @@ -182,6 +195,18 @@ qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_quicktemplates2_c qquickweeknumbermodel.cpp qquickweeknumbermodel_p.h ) +qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_quick_viewtransitions + SOURCES + qquickstacktransition.cpp + qquickstacktransition_p_p.h +) + +qt_internal_extend_target(QuickTemplates2 CONDITION QT_FEATURE_qml_delegate_model + SOURCES + qquickcombobox.cpp + qquickcombobox_p.h +) + qt_internal_extend_Target(qtquicktemplates2plugin SOURCES qtquicktemplates2plugin.cpp diff --git a/src/quicktemplates/configure.cmake b/src/quicktemplates/configure.cmake index 3b9cb00fc2..77683e8105 100644 --- a/src/quicktemplates/configure.cmake +++ b/src/quicktemplates/configure.cmake @@ -31,6 +31,13 @@ qt_feature("quicktemplates2-calendar" PRIVATE SECTION "Quick Templates 2" LABEL "Calendar support" PURPOSE "Provides calendar types." + CONDITION QT_FEATURE_itemmodel +) +qt_feature("quicktemplates2-container" PRIVATE + SECTION "Quick Templates 2" + LABEL "Container controls support" + PURPOSE "Provides support for Container and its sub-classes." + CONDITION QT_FEATURE_qml_object_model ) qt_configure_add_summary_section(NAME "Qt Quick Templates 2") qt_configure_add_summary_entry(ARGS "quicktemplates2-hover") diff --git a/src/quicktemplates/qquickabstractbutton.cpp b/src/quicktemplates/qquickabstractbutton.cpp index e30c090379..ff2d9715c1 100644 --- a/src/quicktemplates/qquickabstractbutton.cpp +++ b/src/quicktemplates/qquickabstractbutton.cpp @@ -3,6 +3,7 @@ #include "qquickabstractbutton_p.h" #include "qquickabstractbutton_p_p.h" +#include "qquickactiongroup_p.h" #include "qquickbuttongroup_p.h" #include "qquickaction_p.h" #include "qquickaction_p_p.h" @@ -17,6 +18,8 @@ #include <QtGui/private/qguiapplication_p.h> #include <QtGui/qpa/qplatformtheme.h> #include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuick/private/qquickaccessibleattached_p.h> #include <QtQml/qqmllist.h> QT_BEGIN_NAMESPACE @@ -62,6 +65,8 @@ QT_BEGIN_NAMESPACE \qmlsignal QtQuick.Controls::AbstractButton::clicked() This signal is emitted when the button is interactively clicked by the user via touch, mouse, or keyboard. + + \sa click(), animateClick(), {Call a C++ function from QML when a Button is clicked} */ /*! @@ -84,6 +89,30 @@ QT_BEGIN_NAMESPACE This signal is emitted when the button is interactively double clicked by the user via touch or mouse. */ +void QQuickAbstractButtonPrivate::init() +{ + Q_Q(QQuickAbstractButton); + q->setActiveFocusOnTab(true); +#ifdef Q_OS_MACOS + q->setFocusPolicy(Qt::TabFocus); +#else + q->setFocusPolicy(Qt::StrongFocus); +#endif + q->setAcceptedMouseButtons(Qt::LeftButton); +#if QT_CONFIG(quicktemplates2_multitouch) + q->setAcceptTouchEvents(true); +#endif +#if QT_CONFIG(cursor) + q->setCursor(Qt::ArrowCursor); +#endif + setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); +} + +QPointF QQuickAbstractButtonPrivate::centerPressPoint() const +{ + return QPointF(qRound(width / 2), qRound(height / 2)); +} + void QQuickAbstractButtonPrivate::setPressPoint(const QPointF &point) { pressPoint = point; @@ -362,8 +391,6 @@ void QQuickAbstractButtonPrivate::toggle(bool value) emit q->toggled(); } -static inline QString indicatorName() { return QStringLiteral("indicator"); } - void QQuickAbstractButtonPrivate::cancelIndicator() { Q_Q(QQuickAbstractButton); @@ -413,7 +440,7 @@ QQuickAbstractButton *QQuickAbstractButtonPrivate::findCheckedButton() const { Q_Q(const QQuickAbstractButton); if (group) - return qobject_cast<QQuickAbstractButton *>(group->checkedButton()); + return group->checkedButton(); const QList<QQuickAbstractButton *> buttons = findExclusiveButtons(); // TODO: A singular QRadioButton can be unchecked, which seems logical, @@ -457,45 +484,29 @@ QList<QQuickAbstractButton *> QQuickAbstractButtonPrivate::findExclusiveButtons( QQuickAbstractButton::QQuickAbstractButton(QQuickItem *parent) : QQuickControl(*(new QQuickAbstractButtonPrivate), parent) { - setActiveFocusOnTab(true); -#ifdef Q_OS_MACOS - setFocusPolicy(Qt::TabFocus); -#else - setFocusPolicy(Qt::StrongFocus); -#endif - setAcceptedMouseButtons(Qt::LeftButton); -#if QT_CONFIG(quicktemplates2_multitouch) - setAcceptTouchEvents(true); -#endif -#if QT_CONFIG(cursor) - setCursor(Qt::ArrowCursor); -#endif + Q_D(QQuickAbstractButton); + d->init(); } QQuickAbstractButton::QQuickAbstractButton(QQuickAbstractButtonPrivate &dd, QQuickItem *parent) : QQuickControl(dd, parent) { - setActiveFocusOnTab(true); -#ifdef Q_OS_MACOS - setFocusPolicy(Qt::TabFocus); -#else - setFocusPolicy(Qt::StrongFocus); -#endif - setAcceptedMouseButtons(Qt::LeftButton); -#if QT_CONFIG(quicktemplates2_multitouch) - setAcceptTouchEvents(true); -#endif -#if QT_CONFIG(cursor) - setCursor(Qt::ArrowCursor); -#endif + Q_D(QQuickAbstractButton); + d->init(); } QQuickAbstractButton::~QQuickAbstractButton() { Q_D(QQuickAbstractButton); d->removeImplicitSizeListener(d->indicator); - if (d->group) - d->group->removeButton(this); + if (d->group) { + auto *attached = qobject_cast<QQuickButtonGroupAttached *>( + qmlAttachedPropertiesObject<QQuickButtonGroup>(this, false)); + if (attached) + attached->setGroup(nullptr); + else + d->group->removeButton(this); + } #if QT_CONFIG(shortcut) d->ungrabShortcut(); #endif @@ -810,7 +821,7 @@ void QQuickAbstractButton::setIcon(const QQuickIcon &icon) \header \li Display \li Result \row \li \c AbstractButton.IconOnly \li \image qtquickcontrols-button-icononly.png \row \li \c AbstractButton.TextOnly \li \image qtquickcontrols-button-textonly.png - \row \li \c AbstractButton.TextBesideIcon \li \image qtquickcontrols-button-textbesideicon.png + \row \li \c AbstractButton.TextBesideIcon (default) \li \image qtquickcontrols-button-textbesideicon.png \row \li \c AbstractButton.TextUnderIcon \li \image qtquickcontrols-button-textundericon.png \endtable @@ -880,6 +891,12 @@ void QQuickAbstractButton::setAction(QQuickAction *action) setEnabled(action->isEnabled()); } +#if QT_CONFIG(accessibility) + auto attached = qobject_cast<QQuickAccessibleAttached*>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(this, true)); + Q_ASSERT(attached); + attached->setProxying(qobject_cast<QQuickAccessibleAttached*>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(action, true))); +#endif + d->action = action; if (oldText != text()) @@ -1042,6 +1059,8 @@ qreal QQuickAbstractButton::implicitIndicatorHeight() const \qmlmethod void QtQuick.Controls::AbstractButton::toggle() Toggles the checked state of the button. + + \sa click(), animateClick() */ void QQuickAbstractButton::toggle() { @@ -1049,6 +1068,81 @@ void QQuickAbstractButton::toggle() setChecked(!d->checked); } +/*! + \since Qt 6.8 + \qmlmethod void QtQuick.Controls::AbstractButton::click() + + Simulates the button being clicked with no delay between press and release. + + All signals associated with a click are emitted as appropriate. + + If the \l focusPolicy includes \c Qt.ClickFocus, \l activeFocus will + become \c true. + + This function does nothing if the button is \l {enabled}{disabled}. + + Calling this function again before the button is released resets + the release timer. + + \sa animateClick(), pressed(), released(), clicked() +*/ +void QQuickAbstractButton::click() +{ + Q_D(QQuickAbstractButton); + if (!isEnabled()) + return; + + // QQuickItemPrivate::deliverPointerEvent calls setFocusIfNeeded on real clicks, + // so we need to do it ourselves. + const bool setFocusOnPress = !QGuiApplication::styleHints()->setFocusOnTouchRelease(); + if (setFocusOnPress && focusPolicy() & Qt::ClickFocus) + forceActiveFocus(Qt::MouseFocusReason); + + const QPointF eventPos(d->width / 2, d->height / 2); + d->handlePress(eventPos, 0); + d->handleRelease(eventPos, 0); +} + +/*! + \since Qt 6.8 + \qmlmethod void QtQuick.Controls::AbstractButton::animateClick() + + Simulates the button being clicked, with a 100 millisecond delay + between press and release, animating its visual state in the + process. + + All signals associated with a click are emitted as appropriate. + + If the \l focusPolicy includes \c Qt.ClickFocus, \l activeFocus will + become \c true. + + This function does nothing if the button is \l {enabled}{disabled}. + + Calling this function again before the button is released resets + the release timer. + + \sa click(), pressed(), released(), clicked() +*/ +void QQuickAbstractButton::animateClick() +{ + Q_D(QQuickAbstractButton); + if (!isEnabled()) + return; + + // See comment in click() for why we do this. + const bool setFocusOnPress = !QGuiApplication::styleHints()->setFocusOnTouchRelease(); + if (setFocusOnPress && focusPolicy() & Qt::ClickFocus) + forceActiveFocus(Qt::MouseFocusReason); + + // If the timer was already running, kill it so we can restart it. + if (d->animateTimer != 0) + killTimer(d->animateTimer); + else + d->handlePress(QPointF(d->width / 2, d->height / 2), 0); + + d->animateTimer = startTimer(100); +} + void QQuickAbstractButton::componentComplete() { Q_D(QQuickAbstractButton); @@ -1084,7 +1178,7 @@ void QQuickAbstractButton::keyPressEvent(QKeyEvent *event) Q_D(QQuickAbstractButton); QQuickControl::keyPressEvent(event); if (d->acceptKeyClick(static_cast<Qt::Key>(event->key()))) { - d->setPressPoint(QPoint(qRound(width() / 2), qRound(height() / 2))); + d->setPressPoint(d->centerPressPoint()); setPressed(true); if (d->autoRepeat) @@ -1114,6 +1208,11 @@ void QQuickAbstractButton::keyReleaseEvent(QKeyEvent *event) void QQuickAbstractButton::mousePressEvent(QMouseEvent *event) { + if (!(event->buttons() & Qt::LeftButton)) { + event->ignore(); + return; + } + Q_D(QQuickAbstractButton); d->pressButtons = event->buttons(); QQuickControl::mousePressEvent(event); @@ -1121,9 +1220,10 @@ void QQuickAbstractButton::mousePressEvent(QMouseEvent *event) void QQuickAbstractButton::mouseDoubleClickEvent(QMouseEvent *event) { + Q_UNUSED(event); Q_D(QQuickAbstractButton); if (d->isDoubleClickConnected()) { - QQuickControl::mouseDoubleClickEvent(event); + // don't call QQuickItem::mouseDoubleClickEvent(): it would ignore() emit doubleClicked(); d->wasDoubleClick = true; } @@ -1143,6 +1243,12 @@ void QQuickAbstractButton::timerEvent(QTimerEvent *event) emit released(); d->trigger(); emit pressed(); + } else if (event->timerId() == d->animateTimer) { + const bool setFocusOnRelease = QGuiApplication::styleHints()->setFocusOnTouchRelease(); + if (setFocusOnRelease && focusPolicy() & Qt::ClickFocus) + forceActiveFocus(Qt::MouseFocusReason); + d->handleRelease(QPointF(d->width / 2, d->height / 2), 0); + d->animateTimer = 0; } } @@ -1188,8 +1294,21 @@ void QQuickAbstractButton::buttonChange(ButtonChange change) void QQuickAbstractButton::nextCheckState() { Q_D(QQuickAbstractButton); - if (d->checkable && (!d->checked || d->findCheckedButton() != this)) - d->toggle(!d->checked); + if (!d->checkable) + return; + + if (d->checked) { + if (d->findCheckedButton() == this) + return; + if (d->action) { + // For non-exclusive groups checkedAction is null + if (const auto group = QQuickActionPrivate::get(d->action)->group) + if (group->checkedAction() == d->action) + return; + } + } + + d->toggle(!d->checked); } #if QT_CONFIG(accessibility) diff --git a/src/quicktemplates/qquickabstractbutton_p.h b/src/quicktemplates/qquickabstractbutton_p.h index 8907cfefd8..c9e7407920 100644 --- a/src/quicktemplates/qquickabstractbutton_p.h +++ b/src/quicktemplates/qquickabstractbutton_p.h @@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE class QQuickAction; class QQuickAbstractButtonPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickAbstractButton : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickAbstractButton : public QQuickControl { Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText RESET resetText NOTIFY textChanged FINAL) @@ -119,6 +119,8 @@ public: public Q_SLOTS: void toggle(); + Q_REVISION(6, 8) void click(); + Q_REVISION(6, 8) void animateClick(); Q_SIGNALS: void pressed(); @@ -188,6 +190,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickAbstractButton) - #endif // QQUICKABSTRACTBUTTON_P_H diff --git a/src/quicktemplates/qquickabstractbutton_p_p.h b/src/quicktemplates/qquickabstractbutton_p_p.h index 9fcb5f5583..0d5eb65940 100644 --- a/src/quicktemplates/qquickabstractbutton_p_p.h +++ b/src/quicktemplates/qquickabstractbutton_p_p.h @@ -21,12 +21,14 @@ # include <QtGui/qkeysequence.h> #endif +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE class QQuickAction; class QQuickButtonGroup; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickAbstractButtonPrivate : public QQuickControlPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickAbstractButtonPrivate : public QQuickControlPrivate { public: Q_DECLARE_PUBLIC(QQuickAbstractButton) @@ -36,6 +38,7 @@ public: return button->d_func(); } + QPointF centerPressPoint() const; void setPressPoint(const QPointF &point); void setMovePoint(const QPointF &point); @@ -65,6 +68,7 @@ public: void actionTextChange(); void setText(const QString &text, bool isExplicit); + void init(); void updateEffectiveIcon(); @@ -99,6 +103,7 @@ public: int repeatTimer = 0; int repeatDelay = AUTO_REPEAT_DELAY; int repeatInterval = AUTO_REPEAT_INTERVAL; + int animateTimer = 0; #if QT_CONFIG(shortcut) int shortcutId = 0; QKeySequence shortcut; diff --git a/src/quicktemplates/qquickaction.cpp b/src/quicktemplates/qquickaction.cpp index 466501226d..f49393bdb1 100644 --- a/src/quicktemplates/qquickaction.cpp +++ b/src/quicktemplates/qquickaction.cpp @@ -6,6 +6,8 @@ #include "qquickactiongroup_p.h" #include "qquickshortcutcontext_p_p.h" +#include <QtCore/qpointer.h> +#include <QtCore/qloggingcategory.h> #include <QtGui/qevent.h> #if QT_CONFIG(shortcut) # include <QtGui/private/qshortcutmap_p.h> @@ -15,6 +17,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcAction, "qt.quick.controls.action") + /*! \qmltype Action \inherits QtObject @@ -314,6 +318,7 @@ QQuickAction::QQuickAction(QObject *parent) QQuickAction::~QQuickAction() { Q_D(QQuickAction); + qCDebug(lcAction) << "destroying" << this << d->text; if (d->group) d->group->removeAction(this); diff --git a/src/quicktemplates/qquickaction_p.h b/src/quicktemplates/qquickaction_p.h index c4dd818af6..db985f750d 100644 --- a/src/quicktemplates/qquickaction_p.h +++ b/src/quicktemplates/qquickaction_p.h @@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE class QQuickIcon; class QQuickActionPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickAction : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickAction : public QObject { Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL) @@ -92,6 +92,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickAction) - #endif // QQUICKACTION_P_H diff --git a/src/quicktemplates/qquickactiongroup.cpp b/src/quicktemplates/qquickactiongroup.cpp index 5e088f3924..1c41dd5ff0 100644 --- a/src/quicktemplates/qquickactiongroup.cpp +++ b/src/quicktemplates/qquickactiongroup.cpp @@ -11,6 +11,8 @@ #include "qquickaction_p.h" #include "qquickaction_p_p.h" +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE /*! diff --git a/src/quicktemplates/qquickactiongroup_p.h b/src/quicktemplates/qquickactiongroup_p.h index 2cd8edbe77..1dd19df1e8 100644 --- a/src/quicktemplates/qquickactiongroup_p.h +++ b/src/quicktemplates/qquickactiongroup_p.h @@ -26,7 +26,7 @@ class QQuickActionGroupPrivate; class QQuickActionGroupAttached; class QQuickActionGroupAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickActionGroup : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickActionGroup : public QObject { Q_OBJECT Q_PROPERTY(QQuickAction *checkedAction READ checkedAction WRITE setCheckedAction NOTIFY checkedActionChanged FINAL) @@ -73,7 +73,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_updateCurrent()) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickActionGroupAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickActionGroupAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickActionGroup *group READ group WRITE setGroup NOTIFY groupChanged FINAL) @@ -94,6 +94,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickActionGroup) - #endif // QQUICKACTIONGROUP_P_H diff --git a/src/quicktemplates/qquickapplicationwindow.cpp b/src/quicktemplates/qquickapplicationwindow.cpp index 582b5dd31a..c10eec6bb8 100644 --- a/src/quicktemplates/qquickapplicationwindow.cpp +++ b/src/quicktemplates/qquickapplicationwindow.cpp @@ -8,8 +8,12 @@ #include "qquicktextarea_p.h" #include "qquicktextfield_p.h" #include "qquicktoolbar_p.h" +#include "qquicktooltip_p.h" +#include <private/qtquicktemplates2-config_p.h> +#if QT_CONFIG(quicktemplates2_container) #include "qquicktabbar_p.h" #include "qquickdialogbuttonbox_p.h" +#endif #include "qquickdeferredexecute_p_p.h" #include "qquickdeferredpointer_p_p.h" @@ -87,18 +91,13 @@ QT_BEGIN_NAMESPACE static const QQuickItemPrivate::ChangeTypes ItemChanges = QQuickItemPrivate::Visibility | QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickApplicationWindowPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickApplicationWindowPrivate : public QQuickWindowQmlImplPrivate , public QQuickItemChangeListener { Q_DECLARE_PUBLIC(QQuickApplicationWindow) public: - QQuickApplicationWindowPrivate() - { - complete = true; - } - static QQuickApplicationWindowPrivate *get(QQuickApplicationWindow *window) { return window->d_func(); @@ -106,12 +105,14 @@ public: QQmlListProperty<QObject> contentData(); + void updateHasBackgroundFlags(); void relayout(); void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; void itemVisibilityChanged(QQuickItem *item) override; void itemImplicitWidthChanged(QQuickItem *item) override; void itemImplicitHeightChanged(QQuickItem *item) override; + QPalette windowPalette() const override { return defaultPalette(); } void updateFont(const QFont &f); inline void setFont_helper(const QFont &f) { @@ -135,9 +136,15 @@ public: // Update regular children QQuickWindowPrivate::updateChildrenPalettes(parentPalette); - // And cover one special case - for (auto &&popup : q_func()->findChildren<QQuickPopup *>()) - QQuickPopupPrivate::get(popup)->updateContentPalettes(parentPalette); + // And cover special cases + for (auto &&child : q_func()->findChildren<QObject *>()) { + if (auto *popup = qobject_cast<QQuickPopup *>(child)) + QQuickPopupPrivate::get(popup)->updateContentPalettes(parentPalette); + else if (auto *toolTipAttached = qobject_cast<QQuickToolTipAttached *>(child)) { + if (auto *toolTip = toolTipAttached->toolTip()) + QQuickPopupPrivate::get(toolTip)->updateContentPalettes(parentPalette); + } + } } QQuickDeferredPointer<QQuickItem> background; @@ -149,6 +156,8 @@ public: QLocale locale; QQuickItem *activeFocusControl = nullptr; bool insideRelayout = false; + bool hasBackgroundWidth = false; + bool hasBackgroundHeight = false; }; static void layoutItem(QQuickItem *item, qreal y, qreal width) @@ -164,10 +173,20 @@ static void layoutItem(QQuickItem *item, qreal y, qreal width) } } +void QQuickApplicationWindowPrivate::updateHasBackgroundFlags() +{ + if (!background) + return; + + QQuickItemPrivate *backgroundPrivate = QQuickItemPrivate::get(background); + hasBackgroundWidth = backgroundPrivate->widthValid(); + hasBackgroundHeight = backgroundPrivate->heightValid(); +} + void QQuickApplicationWindowPrivate::relayout() { Q_Q(QQuickApplicationWindow); - if (!complete || insideRelayout) + if (!componentComplete || insideRelayout) return; QScopedValueRollback<bool> guard(insideRelayout, true); @@ -185,23 +204,23 @@ void QQuickApplicationWindowPrivate::relayout() layoutItem(footer, content->height(), q->width()); if (background) { - QQuickItemPrivate *p = QQuickItemPrivate::get(background); - if (!p->widthValid() && qFuzzyIsNull(background->x())) { + if (!hasBackgroundWidth && qFuzzyIsNull(background->x())) background->setWidth(q->width()); - p->widthValidFlag = false; - } - if (!p->heightValid() && qFuzzyIsNull(background->y())) { + if (!hasBackgroundHeight && qFuzzyIsNull(background->y())) background->setHeight(q->height()); - p->heightValidFlag = false; - } } } void QQuickApplicationWindowPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) { - Q_UNUSED(item); - Q_UNUSED(change); Q_UNUSED(diff); + + if (!insideRelayout && item == background && change.sizeChange()) { + // Any time the background is resized (excluding our own resizing), + // we should respect it if it's explicit by storing the values of the flags. + updateHasBackgroundFlags(); + } + relayout(); } @@ -280,8 +299,6 @@ void QQuickApplicationWindowPrivate::contentData_append(QQmlListProperty<QObject QQuickPopupPrivate::get(popup)->setWindow(static_cast<QQuickApplicationWindow *>(prop->data)); } -static inline QString backgroundName() { return QStringLiteral("background"); } - void QQuickApplicationWindowPrivate::cancelBackground() { Q_Q(QQuickApplicationWindow); @@ -296,8 +313,12 @@ void QQuickApplicationWindowPrivate::executeBackground(bool complete) if (!background || complete) quickBeginDeferred(q, backgroundName(), background); - if (complete) + if (complete) { quickCompleteDeferred(q, backgroundName(), background); + // See comment in setBackground for why we do this here. + updateHasBackgroundFlags(); + relayout(); + } } QQuickApplicationWindow::QQuickApplicationWindow(QWindow *parent) @@ -359,14 +380,29 @@ void QQuickApplicationWindow::setBackground(QQuickItem *background) if (!d->background.isExecuting()) d->cancelBackground(); + if (d->background) { + d->hasBackgroundWidth = false; + d->hasBackgroundHeight = false; + } QQuickControlPrivate::hideOldItem(d->background); + d->background = background; + if (background) { background->setParentItem(QQuickWindow::contentItem()); + if (qFuzzyIsNull(background->z())) background->setZ(-1); - if (isComponentComplete()) - d->relayout(); + + // If the background hasn't finished executing then we don't know if its width and height + // are valid or not, and so relayout would see that they haven't been set yet and override + // any bindings the user might have. + if (!d->background.isExecuting()) { + d->updateHasBackgroundFlags(); + + if (isComponentComplete()) + d->relayout(); + } } if (!d->background.isExecuting()) emit backgroundChanged(); @@ -418,10 +454,12 @@ void QQuickApplicationWindow::setHeader(QQuickItem *header) header->setZ(1); if (QQuickToolBar *toolBar = qobject_cast<QQuickToolBar *>(header)) toolBar->setPosition(QQuickToolBar::Header); +#if QT_CONFIG(quicktemplates2_container) else if (QQuickTabBar *tabBar = qobject_cast<QQuickTabBar *>(header)) tabBar->setPosition(QQuickTabBar::Header); else if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(header)) buttonBox->setPosition(QQuickDialogButtonBox::Header); +#endif } if (isComponentComplete()) d->relayout(); @@ -473,10 +511,12 @@ void QQuickApplicationWindow::setFooter(QQuickItem *footer) footer->setZ(1); if (QQuickToolBar *toolBar = qobject_cast<QQuickToolBar *>(footer)) toolBar->setPosition(QQuickToolBar::Footer); +#if QT_CONFIG(quicktemplates2_container) else if (QQuickTabBar *tabBar = qobject_cast<QQuickTabBar *>(footer)) tabBar->setPosition(QQuickTabBar::Footer); else if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(footer)) buttonBox->setPosition(QQuickDialogButtonBox::Footer); +#endif } if (isComponentComplete()) d->relayout(); @@ -685,13 +725,13 @@ void QQuickApplicationWindow::setMenuBar(QQuickItem *menuBar) bool QQuickApplicationWindow::isComponentComplete() const { Q_D(const QQuickApplicationWindow); - return d->complete; + return d->componentComplete; } void QQuickApplicationWindow::classBegin() { Q_D(QQuickApplicationWindow); - d->complete = false; + d->componentComplete = false; QQuickWindowQmlImpl::classBegin(); d->resolveFont(); } @@ -699,7 +739,7 @@ void QQuickApplicationWindow::classBegin() void QQuickApplicationWindow::componentComplete() { Q_D(QQuickApplicationWindow); - d->complete = true; + d->componentComplete = true; d->executeBackground(true); QQuickWindowQmlImpl::componentComplete(); d->relayout(); diff --git a/src/quicktemplates/qquickapplicationwindow_p.h b/src/quicktemplates/qquickapplicationwindow_p.h index 283b192b81..23a3a197ac 100644 --- a/src/quicktemplates/qquickapplicationwindow_p.h +++ b/src/quicktemplates/qquickapplicationwindow_p.h @@ -27,7 +27,7 @@ class QQuickApplicationWindowPrivate; class QQuickApplicationWindowAttached; class QQuickApplicationWindowAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickApplicationWindow : public QQuickWindowQmlImpl +class Q_QUICKTEMPLATES2_EXPORT QQuickApplicationWindow : public QQuickWindowQmlImpl { Q_OBJECT Q_PROPERTY(QQuickItem *background READ background WRITE setBackground NOTIFY backgroundChanged FINAL) @@ -99,7 +99,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_updateActiveFocus()) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickApplicationWindowAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickApplicationWindowAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickApplicationWindow *window READ window NOTIFY windowChanged FINAL) @@ -133,32 +133,6 @@ private: Q_DECLARE_PRIVATE(QQuickApplicationWindowAttached) }; -struct QWindowForeign2 -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QWindow) - QML_ADDED_IN_VERSION(2, 0) -}; - -struct QQuickWindowForeign -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QQuickWindow) - QML_ADDED_IN_VERSION(2, 0) -}; - -struct QQuickWindowQmlImplForeign -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QQuickWindowQmlImpl) - QML_ADDED_IN_VERSION(2, 2) -}; - QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickApplicationWindow) - #endif // QQUICKAPPLICATIONWINDOW_P_H diff --git a/src/quicktemplates/qquickbusyindicator_p.h b/src/quicktemplates/qquickbusyindicator_p.h index 46206b7d78..357d327eb8 100644 --- a/src/quicktemplates/qquickbusyindicator_p.h +++ b/src/quicktemplates/qquickbusyindicator_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickBusyIndicatorPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickBusyIndicator : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickBusyIndicator : public QQuickControl { Q_OBJECT Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged FINAL) @@ -53,6 +53,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickBusyIndicator) - #endif // QQUICKBUSYINDICATOR_P_H diff --git a/src/quicktemplates/qquickbutton_p.h b/src/quicktemplates/qquickbutton_p.h index 1092274ee6..32ebeb563d 100644 --- a/src/quicktemplates/qquickbutton_p.h +++ b/src/quicktemplates/qquickbutton_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickButtonPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickButton : public QQuickAbstractButton +class Q_QUICKTEMPLATES2_EXPORT QQuickButton : public QQuickAbstractButton { Q_OBJECT Q_PROPERTY(bool highlighted READ isHighlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL) @@ -54,6 +54,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickButton) - #endif // QQUICKBUTTON_P_H diff --git a/src/quicktemplates/qquickbuttongroup.cpp b/src/quicktemplates/qquickbuttongroup.cpp index 89a64720dc..bb5787a3d2 100644 --- a/src/quicktemplates/qquickbuttongroup.cpp +++ b/src/quicktemplates/qquickbuttongroup.cpp @@ -102,7 +102,9 @@ QT_BEGIN_NAMESPACE \code ButtonGroup { buttons: column.children - onClicked: console.log("clicked:", button.text) + onClicked: button => { + console.log("clicked:", button.text) + } } Column { @@ -143,9 +145,15 @@ public: void QQuickButtonGroupPrivate::clear() { for (QQuickAbstractButton *button : std::as_const(buttons)) { - QQuickAbstractButtonPrivate::get(button)->group = nullptr; - QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, this, &QQuickButtonGroupPrivate::buttonClicked); - QObjectPrivate::disconnect(button, &QQuickAbstractButton::checkedChanged, this, &QQuickButtonGroupPrivate::_q_updateCurrent); + auto *attached = qobject_cast<QQuickButtonGroupAttached *>( + qmlAttachedPropertiesObject<QQuickButtonGroup>(button, false)); + if (attached) { + attached->setGroup(nullptr); + } else { + QQuickAbstractButtonPrivate::get(button)->group = nullptr; + QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, this, &QQuickButtonGroupPrivate::buttonClicked); + QObjectPrivate::disconnect(button, &QQuickAbstractButton::checkedChanged, this, &QQuickButtonGroupPrivate::_q_updateCurrent); + } } buttons.clear(); } @@ -500,11 +508,12 @@ void QQuickButtonGroupAttached::setGroup(QQuickButtonGroup *group) if (d->group == group) return; + auto *button = qobject_cast<QQuickAbstractButton *>(parent()); if (d->group) - d->group->removeButton(qobject_cast<QQuickAbstractButton*>(parent())); + d->group->removeButton(button); d->group = group; if (group) - group->addButton(qobject_cast<QQuickAbstractButton*>(parent())); + group->addButton(button); emit groupChanged(); } diff --git a/src/quicktemplates/qquickbuttongroup_p.h b/src/quicktemplates/qquickbuttongroup_p.h index 6bdd619c1c..a7ac05b01f 100644 --- a/src/quicktemplates/qquickbuttongroup_p.h +++ b/src/quicktemplates/qquickbuttongroup_p.h @@ -27,7 +27,7 @@ class QQuickButtonGroupPrivate; class QQuickButtonGroupAttached; class QQuickButtonGroupAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickButtonGroup : public QObject, public QQmlParserStatus +class Q_QUICKTEMPLATES2_EXPORT QQuickButtonGroup : public QObject, public QQmlParserStatus { Q_OBJECT Q_PROPERTY(QQuickAbstractButton *checkedButton READ checkedButton WRITE setCheckedButton NOTIFY checkedButtonChanged FINAL) @@ -84,7 +84,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_updateCurrent()) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickButtonGroupAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickButtonGroupAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickButtonGroup *group READ group WRITE setGroup NOTIFY groupChanged FINAL) @@ -105,6 +105,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickButtonGroup) - #endif // QQUICKBUTTONGROUP_P_H diff --git a/src/quicktemplates/qquickcalendar_p.h b/src/quicktemplates/qquickcalendar_p.h index 37dac5595a..dbee98a737 100644 --- a/src/quicktemplates/qquickcalendar_p.h +++ b/src/quicktemplates/qquickcalendar_p.h @@ -50,6 +50,4 @@ public: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickCalendar) - #endif // QQUICKCALENDAR_P_H diff --git a/src/quicktemplates/qquickcalendarmodel.cpp b/src/quicktemplates/qquickcalendarmodel.cpp index 0bac0969e0..bb3e2a7796 100644 --- a/src/quicktemplates/qquickcalendarmodel.cpp +++ b/src/quicktemplates/qquickcalendarmodel.cpp @@ -42,9 +42,9 @@ public: { } - static int getCount(const QDate& from, const QDate &to); + static int getCount(QDate from, QDate to); - void populate(const QDate &from, const QDate &to, bool force = false); + void populate(QDate from, QDate to, bool force = false); bool complete; QDate from; @@ -52,24 +52,26 @@ public: int count; }; -int QQuickCalendarModelPrivate::getCount(const QDate& from, const QDate &to) +// Returns the number of months we need to display for both from and to to be shown, +// or zero if from is in a later month than to, or either is invalid. +int QQuickCalendarModelPrivate::getCount(QDate from, QDate to) { if (!from.isValid() || !to.isValid()) return 0; - QDate f(from.year(), from.month(), 1); - QDate t(to.year(), to.month(), to.daysInMonth()); - int days = f.daysTo(t); - if (days < 0) + const QCalendar gregorian; + Q_ASSERT(gregorian.isGregorian()); + const QCalendar::YearMonthDay &f = gregorian.partsFromDate(from); + const QCalendar::YearMonthDay &t = gregorian.partsFromDate(to); + Q_ASSERT(f.isValid() && t.isValid()); // ... because from and to are valid. + if (f.year > t.year || (f.year == t.year && f.month > t.month)) return 0; - QDate r = QDate(1, 1, 1).addDays(days); - int years = r.year() - 1; - int months = r.month() - 1; - return 12 * years + months + (r.day() / t.day()); + // Count from's month and every subsequent month until to's: + return 1 + t.month + 12 * (t.year - f.year) - f.month; } -void QQuickCalendarModelPrivate::populate(const QDate &f, const QDate &t, bool force) +void QQuickCalendarModelPrivate::populate(QDate f, QDate t, bool force) { Q_Q(QQuickCalendarModel); if (!force && f == from && t == to) @@ -102,7 +104,7 @@ QDate QQuickCalendarModel::from() const return d->from; } -void QQuickCalendarModel::setFrom(const QDate &from) +void QQuickCalendarModel::setFrom(QDate from) { Q_D(QQuickCalendarModel); if (d->from != from) { @@ -124,7 +126,7 @@ QDate QQuickCalendarModel::to() const return d->to; } -void QQuickCalendarModel::setTo(const QDate &to) +void QQuickCalendarModel::setTo(QDate to) { Q_D(QQuickCalendarModel); if (d->to != to) { @@ -162,7 +164,7 @@ int QQuickCalendarModel::yearAt(int index) const Returns the model index of the specified \a date. */ -int QQuickCalendarModel::indexOf(const QDate &date) const +int QQuickCalendarModel::indexOf(QDate date) const { Q_D(const QQuickCalendarModel); return d->getCount(d->from, date) - 1; diff --git a/src/quicktemplates/qquickcalendarmodel_p.h b/src/quicktemplates/qquickcalendarmodel_p.h index e9dbf6d075..face2b29cf 100644 --- a/src/quicktemplates/qquickcalendarmodel_p.h +++ b/src/quicktemplates/qquickcalendarmodel_p.h @@ -39,14 +39,14 @@ public: explicit QQuickCalendarModel(QObject *parent = nullptr); QDate from() const; - void setFrom(const QDate &from); + void setFrom(QDate from); QDate to() const; - void setTo(const QDate &to); + void setTo(QDate to); Q_INVOKABLE int monthAt(int index) const; Q_INVOKABLE int yearAt(int index) const; - Q_INVOKABLE int indexOf(const QDate &date) const; + Q_INVOKABLE int indexOf(QDate date) const; Q_INVOKABLE int indexOf(int year, int month) const; enum { @@ -74,6 +74,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickCalendarModel) - #endif // QQUICKCALENDARMODEL_P_H diff --git a/src/quicktemplates/qquickcheckbox.cpp b/src/quicktemplates/qquickcheckbox.cpp index 21038ccdb9..539e89f2d0 100644 --- a/src/quicktemplates/qquickcheckbox.cpp +++ b/src/quicktemplates/qquickcheckbox.cpp @@ -69,8 +69,6 @@ class QQuickCheckBoxPrivate : public QQuickAbstractButtonPrivate Q_DECLARE_PUBLIC(QQuickCheckBox) public: - void setNextCheckState(const QJSValue &callback); - QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::CheckBox); } bool tristate = false; @@ -78,13 +76,6 @@ public: QJSValue nextCheckState; }; -void QQuickCheckBoxPrivate::setNextCheckState(const QJSValue &callback) -{ - Q_Q(QQuickCheckBox); - nextCheckState = callback; - emit q->nextCheckStateChanged(); -} - QQuickCheckBox::QQuickCheckBox(QQuickItem *parent) : QQuickAbstractButton(*(new QQuickCheckBoxPrivate), parent) { @@ -150,6 +141,19 @@ void QQuickCheckBox::setCheckState(Qt::CheckState state) emit checkedChanged(); } +QJSValue QQuickCheckBox::getNextCheckState() const +{ + Q_D(const QQuickCheckBox); + return d->nextCheckState; +} + +void QQuickCheckBox::setNextCheckState(const QJSValue &callback) +{ + Q_D(QQuickCheckBox); + d->nextCheckState = callback; + emit nextCheckStateChanged(); +} + QFont QQuickCheckBox::defaultFont() const { return QQuickTheme::font(QQuickTheme::CheckBox); diff --git a/src/quicktemplates/qquickcheckbox_p.h b/src/quicktemplates/qquickcheckbox_p.h index 5adeb44ede..b1a779dacd 100644 --- a/src/quicktemplates/qquickcheckbox_p.h +++ b/src/quicktemplates/qquickcheckbox_p.h @@ -21,13 +21,14 @@ QT_BEGIN_NAMESPACE class QQuickCheckBoxPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickCheckBox : public QQuickAbstractButton +class Q_QUICKTEMPLATES2_EXPORT QQuickCheckBox : public QQuickAbstractButton { Q_OBJECT Q_PROPERTY(bool tristate READ isTristate WRITE setTristate NOTIFY tristateChanged FINAL) Q_PROPERTY(Qt::CheckState checkState READ checkState WRITE setCheckState NOTIFY checkStateChanged FINAL) // 2.4 (Qt 5.11) - Q_PRIVATE_PROPERTY(QQuickCheckBox::d_func(), QJSValue nextCheckState MEMBER nextCheckState WRITE setNextCheckState NOTIFY nextCheckStateChanged FINAL REVISION(2, 4)) + Q_PROPERTY(QJSValue nextCheckState READ getNextCheckState WRITE setNextCheckState NOTIFY + nextCheckStateChanged FINAL REVISION(2, 4)) QML_NAMED_ELEMENT(CheckBox) QML_ADDED_IN_VERSION(2, 0) @@ -39,6 +40,8 @@ public: Qt::CheckState checkState() const; void setCheckState(Qt::CheckState state); + QJSValue getNextCheckState() const; + void setNextCheckState(const QJSValue &callback); Q_SIGNALS: void tristateChanged(); @@ -59,6 +62,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickCheckBox) - #endif // QQUICKCHECKBOX_P_H diff --git a/src/quicktemplates/qquickcheckdelegate_p.h b/src/quicktemplates/qquickcheckdelegate_p.h index 8c7c3e8c74..8e40ef68e7 100644 --- a/src/quicktemplates/qquickcheckdelegate_p.h +++ b/src/quicktemplates/qquickcheckdelegate_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickCheckDelegatePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickCheckDelegate : public QQuickItemDelegate +class Q_QUICKTEMPLATES2_EXPORT QQuickCheckDelegate : public QQuickItemDelegate { Q_OBJECT Q_PROPERTY(bool tristate READ isTristate WRITE setTristate NOTIFY tristateChanged FINAL) @@ -63,6 +63,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickCheckDelegate) - #endif // QQUICKCHECKDELEGATE_P_H diff --git a/src/quicktemplates/qquickcombobox.cpp b/src/quicktemplates/qquickcombobox.cpp index 31eb97e31d..c55e988b74 100644 --- a/src/quicktemplates/qquickcombobox.cpp +++ b/src/quicktemplates/qquickcombobox.cpp @@ -23,7 +23,9 @@ #include <QtQuick/private/qquickevents_p_p.h> #include <QtQuick/private/qquicktextinput_p.h> #include <QtQuick/private/qquicktextinput_p_p.h> +#if QT_CONFIG(quick_itemview) #include <QtQuick/private/qquickitemview_p.h> +#endif QT_BEGIN_NAMESPACE @@ -209,6 +211,7 @@ public: void hidePopup(bool accept); void togglePopup(bool accept); void popupVisibleChanged(); + void popupDestroyed(); void itemClicked(); void itemHovered(); @@ -253,6 +256,7 @@ public: void itemImplicitWidthChanged(QQuickItem *item) override; void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; void setInputMethodHints(Qt::InputMethodHints hints, bool force = false); @@ -342,14 +346,18 @@ void QQuickComboBoxPrivate::popupVisibleChanged() if (isPopupVisible()) QGuiApplication::inputMethod()->reset(); +#if QT_CONFIG(quick_itemview) QQuickItemView *itemView = popup->findChild<QQuickItemView *>(); if (itemView) itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange); +#endif updateHighlightedIndex(); +#if QT_CONFIG(quick_itemview) if (itemView) itemView->positionViewAtIndex(highlightedIndex, QQuickItemView::Beginning); +#endif if (!hasDown) { q->setDown(pressed || isPopupVisible()); @@ -357,6 +365,13 @@ void QQuickComboBoxPrivate::popupVisibleChanged() } } +void QQuickComboBoxPrivate::popupDestroyed() +{ + Q_Q(QQuickComboBox); + popup = nullptr; + emit q->popupChanged(); +} + void QQuickComboBoxPrivate::itemClicked() { Q_Q(QQuickComboBox); @@ -381,8 +396,10 @@ void QQuickComboBoxPrivate::itemHovered() if (index != -1) { setHighlightedIndex(index, Highlight); +#if QT_CONFIG(quick_itemview) if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>()) itemView->positionViewAtIndex(index, QQuickItemView::Contain); +#endif } } @@ -767,8 +784,6 @@ void QQuickComboBoxPrivate::handleUngrab() q->setPressed(false); } -static inline QString indicatorName() { return QStringLiteral("indicator"); } - void QQuickComboBoxPrivate::cancelIndicator() { Q_Q(QQuickComboBox); @@ -833,6 +848,16 @@ void QQuickComboBoxPrivate::itemImplicitHeightChanged(QQuickItem *item) emit q->implicitIndicatorHeightChanged(); } +void QQuickComboBoxPrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickComboBox); + QQuickControlPrivate::itemDestroyed(item); + if (item == indicator) { + indicator = nullptr; + emit q->indicatorChanged(); + } +} + qreal QQuickComboBoxPrivate::getContentWidth() const { if (componentComplete) { @@ -929,6 +954,7 @@ QQuickComboBox::QQuickComboBox(QQuickItem *parent) #endif Q_D(QQuickComboBox); d->setInputMethodHints(Qt::ImhNoPredictiveText, true); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); } QQuickComboBox::~QQuickComboBox() @@ -1333,6 +1359,7 @@ void QQuickComboBox::setPopup(QQuickPopup *popup) d->cancelPopup(); if (d->popup) { + QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::destroyed, d, &QQuickComboBoxPrivate::popupDestroyed); QObjectPrivate::disconnect(d->popup.data(), &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged); QQuickComboBoxPrivate::hideOldPopup(d->popup); } @@ -1340,9 +1367,14 @@ void QQuickComboBox::setPopup(QQuickPopup *popup) QQuickPopupPrivate::get(popup)->allowVerticalFlip = true; popup->setClosePolicy(QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutsideParent); QObjectPrivate::connect(popup, &QQuickPopup::visibleChanged, d, &QQuickComboBoxPrivate::popupVisibleChanged); + // QQuickPopup does not derive from QQuickItemChangeListener, so we cannot use + // QQuickItemChangeListener::itemDestroyed so we have to use QObject::destroyed + QObjectPrivate::connect(popup, &QQuickPopup::destroyed, d, &QQuickComboBoxPrivate::popupDestroyed); +#if QT_CONFIG(quick_itemview) if (QQuickItemView *itemView = popup->findChild<QQuickItemView *>()) itemView->setHighlightRangeMode(QQuickItemView::NoHighlightRange); +#endif } d->popup = popup; if (!d->popup.isExecuting()) @@ -2056,7 +2088,7 @@ void QQuickComboBox::keyReleaseEvent(QKeyEvent *event) if (!isEditable()) { const auto buttonPressKeys = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::ButtonPressKeys).value<QList<Qt::Key>>(); if (buttonPressKeys.contains(key)) { - if (!isEditable()) + if (!isEditable() && isPressed()) d->togglePopup(true); setPressed(false); event->accept(); diff --git a/src/quicktemplates/qquickcombobox_p.h b/src/quicktemplates/qquickcombobox_p.h index 303eef21b0..2ef5dc776c 100644 --- a/src/quicktemplates/qquickcombobox_p.h +++ b/src/quicktemplates/qquickcombobox_p.h @@ -16,8 +16,11 @@ // #include <QtCore/qloggingcategory.h> +#include <QtQmlModels/private/qtqmlmodels-config_p.h> #include <QtQuickTemplates2/private/qquickcontrol_p.h> +QT_REQUIRE_CONFIG(qml_delegate_model); + QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcItemManagement) @@ -27,7 +30,7 @@ class QQuickPopup; class QQmlInstanceModel; class QQuickComboBoxPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickComboBox : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickComboBox : public QQuickControl { Q_OBJECT Q_PROPERTY(int count READ count NOTIFY countChanged FINAL) @@ -240,6 +243,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickComboBox) - #endif // QQUICKCOMBOBOX_P_H diff --git a/src/quicktemplates/qquickcontainer.cpp b/src/quicktemplates/qquickcontainer.cpp index eefc9161e9..115168083c 100644 --- a/src/quicktemplates/qquickcontainer.cpp +++ b/src/quicktemplates/qquickcontainer.cpp @@ -116,7 +116,7 @@ QT_BEGIN_NAMESPACE Container does not provide any default visualization. It is used to implement such containers as \l SwipeView and \l TabBar. When implementing a custom container, the most important part of the API is \l contentModel, which provides - the contained items in a way that it can be used as a delegate model for item + the contained items in a way that it can be used as an object model for item views and repeaters. \code @@ -167,6 +167,7 @@ void QQuickContainerPrivate::init() QObject::connect(contentModel, &QQmlObjectModel::childrenChanged, q, &QQuickContainer::contentChildrenChanged); connect(q, &QQuickControl::implicitContentWidthChanged, this, &QQuickContainerPrivate::updateContentWidth); connect(q, &QQuickControl::implicitContentHeightChanged, this, &QQuickContainerPrivate::updateContentHeight); + setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred); } void QQuickContainerPrivate::cleanup() @@ -256,7 +257,8 @@ void QQuickContainerPrivate::moveItem(int from, int to, QQuickItem *item) void QQuickContainerPrivate::removeItem(int index, QQuickItem *item) { Q_Q(QQuickContainer); - if (!q->isContent(item)) + const bool item_inDestructor = QQuickItemPrivate::get(item)->inDestructor; + if (!item_inDestructor && !q->isContent(item)) return; contentData.removeOne(item); @@ -271,8 +273,11 @@ void QQuickContainerPrivate::removeItem(int index, QQuickItem *item) currentChanged = true; } - QQuickItemPrivate::get(item)->removeItemChangeListener(this, changeTypes); - item->setParentItem(nullptr); + if (!item_inDestructor) { + // already handled by ~QQuickItem + QQuickItemPrivate::get(item)->removeItemChangeListener(this, changeTypes); + item->setParentItem(nullptr); + } contentModel->remove(index); --count; diff --git a/src/quicktemplates/qquickcontainer_p.h b/src/quicktemplates/qquickcontainer_p.h index 7fdf606571..7a71d5bdf6 100644 --- a/src/quicktemplates/qquickcontainer_p.h +++ b/src/quicktemplates/qquickcontainer_p.h @@ -18,11 +18,13 @@ #include <QtQuickTemplates2/private/qquickcontrol_p.h> #include <QtQml/qqmllist.h> +QT_REQUIRE_CONFIG(quicktemplates2_container); + QT_BEGIN_NAMESPACE class QQuickContainerPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickContainer : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickContainer : public QQuickControl { Q_OBJECT Q_PROPERTY(int count READ count NOTIFY countChanged FINAL) @@ -103,6 +105,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickContainer) - #endif // QQUICKCONTAINER_P_H diff --git a/src/quicktemplates/qquickcontainer_p_p.h b/src/quicktemplates/qquickcontainer_p_p.h index 84fe62a75d..a486e8cba7 100644 --- a/src/quicktemplates/qquickcontainer_p_p.h +++ b/src/quicktemplates/qquickcontainer_p_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickContainerPrivate : public QQuickControlPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickContainerPrivate : public QQuickControlPrivate { public: Q_DECLARE_PUBLIC(QQuickContainer) diff --git a/src/quicktemplates/qquickcontentitem_p.h b/src/quicktemplates/qquickcontentitem_p.h index 446489ee70..77f7f14c64 100644 --- a/src/quicktemplates/qquickcontentitem_p.h +++ b/src/quicktemplates/qquickcontentitem_p.h @@ -20,7 +20,7 @@ QT_BEGIN_NAMESPACE -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickContentItem : public QQuickItem +class Q_QUICKTEMPLATES2_EXPORT QQuickContentItem : public QQuickItem { Q_OBJECT diff --git a/src/quicktemplates/qquickcontrol.cpp b/src/quicktemplates/qquickcontrol.cpp index 764b6ca003..667d778180 100644 --- a/src/quicktemplates/qquickcontrol.cpp +++ b/src/quicktemplates/qquickcontrol.cpp @@ -107,6 +107,8 @@ Q_LOGGING_CATEGORY(lcItemManagement, "qt.quick.controls.control.itemmanagement") } \endcode + Wheel events are consumed by controls if \l wheelEnabled is \c true. + \sa ApplicationWindow, Container, {Using Qt Quick Controls types in property declarations} */ @@ -150,21 +152,8 @@ bool QQuickControlPrivate::acceptTouch(const QTouchEvent::TouchPoint &point) } #endif -static void setActiveFocus(QQuickControl *control, Qt::FocusReason reason) -{ - QQuickControlPrivate *d = QQuickControlPrivate::get(control); - if (d->subFocusItem && d->window && d->flags & QQuickItem::ItemIsFocusScope) - QQuickWindowPrivate::get(d->window)->clearFocusInScope(control, d->subFocusItem, reason); - control->forceActiveFocus(reason); -} - bool QQuickControlPrivate::handlePress(const QPointF &, ulong) { - Q_Q(QQuickControl); - if ((focusPolicy & Qt::ClickFocus) == Qt::ClickFocus && !QGuiApplication::styleHints()->setFocusOnTouchRelease()) { - setActiveFocus(q, Qt::MouseFocusReason); - return true; - } return true; } @@ -181,14 +170,8 @@ bool QQuickControlPrivate::handleMove(const QPointF &point, ulong) bool QQuickControlPrivate::handleRelease(const QPointF &, ulong) { - Q_Q(QQuickControl); - bool accepted = true; - if ((focusPolicy & Qt::ClickFocus) == Qt::ClickFocus && QGuiApplication::styleHints()->setFocusOnTouchRelease()) { - setActiveFocus(q, Qt::MouseFocusReason); - accepted = true; - } touchId = -1; - return accepted; + return true; } void QQuickControlPrivate::handleUngrab() @@ -352,12 +335,22 @@ void QQuickControlPrivate::resizeBackground() bool changeHeight = false; if (((!p->widthValid() || !extra.isAllocated() || !extra->hasBackgroundWidth) && qFuzzyIsNull(background->x())) || (extra.isAllocated() && (extra->hasLeftInset || extra->hasRightInset))) { - background->setX(getLeftInset()); + const auto leftInset = getLeftInset(); + if (!qt_is_nan(leftInset) && p->x.valueBypassingBindings() != leftInset) { + // We bypass the binding here to prevent it from being removed + p->x.setValueBypassingBindings(leftInset); + p->dirty(DirtyType::Position); + } changeWidth = !p->width.hasBinding(); } if (((!p->heightValid() || !extra.isAllocated() || !extra->hasBackgroundHeight) && qFuzzyIsNull(background->y())) || (extra.isAllocated() && (extra->hasTopInset || extra->hasBottomInset))) { - background->setY(getTopInset()); + const auto topInset = getTopInset(); + if (!qt_is_nan(topInset) && p->y.valueBypassingBindings() != topInset) { + // We bypass the binding here to prevent it from being removed + p->y.setValueBypassingBindings(topInset); + p->dirty(DirtyType::Position); + } changeHeight = !p->height.hasBinding(); } if (changeHeight || changeWidth) { @@ -404,8 +397,7 @@ void QQuickControlPrivate::setContentItem_helper(QQuickItem *item, bool notify) QQuickItem *oldContentItem = contentItem; if (oldContentItem) { disconnect(oldContentItem, &QQuickItem::baselineOffsetChanged, this, &QQuickControlPrivate::updateBaselineOffset); - if (oldContentItem) - QQuickItemPrivate::get(oldContentItem)->removeItemChangeListener(this, QQuickControlPrivate::Focus); + QQuickItemPrivate::get(oldContentItem)->removeItemChangeListener(this, QQuickControlPrivate::Focus); removeImplicitSizeListener(oldContentItem); } @@ -751,8 +743,6 @@ void QQuickControlPrivate::executeContentItem(bool complete) quickCompleteDeferred(q, contentItemName(), contentItem); } -static inline QString backgroundName() { return QStringLiteral("background"); } - void QQuickControlPrivate::cancelBackground() { Q_Q(QQuickControl); @@ -802,13 +792,25 @@ void QQuickControlPrivate::hideOldItem(QQuickItem *item) Named "unhide" because it's used for cases where an item that was previously hidden by \l hideOldItem() wants to be shown by a control again, such as a ScrollBar in ScrollView. + + \a visibility controls the visibility of \a item, as there + may have been bindings that controlled visibility, such as + with a typical ScrollBar.qml implementation: + + \code + visible: control.policy !== T.ScrollBar.AlwaysOff + \endcode + + In the future we could try to save the binding for the visible + property (using e.g. QQmlAnyBinding::takeFrom), but for now we + keep it simple and just allow restoring an equivalent literal value. */ -void QQuickControlPrivate::unhideOldItem(QQuickControl *control, QQuickItem *item) +void QQuickControlPrivate::unhideOldItem(QQuickControl *control, QQuickItem *item, UnhideVisibility visibility) { Q_ASSERT(item); qCDebug(lcItemManagement) << "unhiding old item" << item; - item->setVisible(true); + item->setVisible(visibility == UnhideVisibility::Show); item->setParentItem(control); #if QT_CONFIG(accessibility) @@ -906,7 +908,20 @@ void QQuickControlPrivate::itemFocusChanged(QQuickItem *item, Qt::FocusReason re { Q_Q(QQuickControl); if (item == contentItem || item == q) - q->setFocusReason(reason); + setLastFocusChangeReason(reason); +} + +bool QQuickControlPrivate::setLastFocusChangeReason(Qt::FocusReason reason) +{ + Q_Q(QQuickControl); + Qt::FocusReason oldReason = static_cast<Qt::FocusReason>(focusReason); + const auto focusReasonChanged = QQuickItemPrivate::setLastFocusChangeReason(reason); + if (focusReasonChanged) + emit q->focusReasonChanged(); + if (isKeyFocusReason(oldReason) != isKeyFocusReason(reason)) + emit q->visualFocusChanged(); + + return focusReasonChanged; } QQuickControl::QQuickControl(QQuickItem *parent) @@ -962,7 +977,7 @@ void QQuickControl::itemChange(QQuickItem::ItemChange change, const QQuickItem:: } break; case ItemActiveFocusHasChanged: - if (isKeyFocusReason(d->focusReason)) + if (isKeyFocusReason(static_cast<Qt::FocusReason>(d->focusReason))) emit visualFocusChanged(); break; default: @@ -1352,61 +1367,37 @@ bool QQuickControl::isMirrored() const } /*! - \qmlproperty enumeration QtQuick.Controls::Control::focusPolicy - - This property determines the way the control accepts focus. - - \value Qt.TabFocus The control accepts focus by tabbing. - \value Qt.ClickFocus The control accepts focus by clicking. - \value Qt.StrongFocus The control accepts focus by both tabbing and clicking. - \value Qt.WheelFocus The control accepts focus by tabbing, clicking, and using the mouse wheel. - \value Qt.NoFocus The control does not accept focus. -*/ -Qt::FocusPolicy QQuickControl::focusPolicy() const -{ - Q_D(const QQuickControl); - uint policy = d->focusPolicy; - if (activeFocusOnTab()) - policy |= Qt::TabFocus; - return static_cast<Qt::FocusPolicy>(policy); -} + \qmlproperty enumeration QtQuick.Controls::Control::focusReason + \readonly -void QQuickControl::setFocusPolicy(Qt::FocusPolicy policy) -{ - Q_D(QQuickControl); - if (d->focusPolicy == policy) - return; + This property holds the reason of the last focus change. - d->focusPolicy = policy; - setActiveFocusOnTab(policy & Qt::TabFocus); - emit focusPolicyChanged(); -} + \note This property does not indicate whether the item has \l {Item::activeFocus} + {active focus}, but the reason why the item either gained or lost focus. -/*! - \qmlproperty enumeration QtQuick.Controls::Control::focusReason - \readonly + \value Qt.MouseFocusReason A mouse action occurred. + \value Qt.TabFocusReason The Tab key was pressed. + \value Qt.BacktabFocusReason A Backtab occurred. The input for this may include the Shift or Control keys; e.g. Shift+Tab. + \value Qt.ActiveWindowFocusReason The window system made this window either active or inactive. + \value Qt.PopupFocusReason The application opened/closed a pop-up that grabbed/released the keyboard focus. + \value Qt.ShortcutFocusReason The user typed a label's buddy shortcut + \value Qt.MenuBarFocusReason The menu bar took focus. + \value Qt.OtherFocusReason Another reason, usually application-specific. - \include qquickcontrol-focusreason.qdocinc + \sa Item::activeFocus \sa visualFocus */ Qt::FocusReason QQuickControl::focusReason() const { Q_D(const QQuickControl); - return d->focusReason; + return d->lastFocusChangeReason(); } void QQuickControl::setFocusReason(Qt::FocusReason reason) { Q_D(QQuickControl); - if (d->focusReason == reason) - return; - - Qt::FocusReason oldReason = d->focusReason; - d->focusReason = reason; - emit focusReasonChanged(); - if (isKeyFocusReason(oldReason) != isKeyFocusReason(reason)) - emit visualFocusChanged(); + d->setLastFocusChangeReason(reason); } /*! @@ -1426,7 +1417,7 @@ void QQuickControl::setFocusReason(Qt::FocusReason reason) bool QQuickControl::hasVisualFocus() const { Q_D(const QQuickControl); - return d->activeFocus && isKeyFocusReason(d->focusReason); + return d->activeFocus && isKeyFocusReason(static_cast<Qt::FocusReason>(d->focusReason)); } /*! @@ -1988,13 +1979,11 @@ QFont QQuickControl::defaultFont() const void QQuickControl::focusInEvent(QFocusEvent *event) { QQuickItem::focusInEvent(event); - setFocusReason(event->reason()); } void QQuickControl::focusOutEvent(QFocusEvent *event) { QQuickItem::focusOutEvent(event); - setFocusReason(event->reason()); } #if QT_CONFIG(quicktemplates2_hover) @@ -2092,9 +2081,6 @@ void QQuickControl::touchUngrabEvent() void QQuickControl::wheelEvent(QWheelEvent *event) { Q_D(QQuickControl); - if ((d->focusPolicy & Qt::WheelFocus) == Qt::WheelFocus) - setActiveFocus(this, Qt::MouseFocusReason); - event->setAccepted(d->wheelEnabled); } #endif @@ -2175,12 +2161,13 @@ QAccessible::Role QQuickControl::accessibleRole() const void QQuickControl::accessibilityActiveChanged(bool active) { + Q_D(QQuickControl); if (!active) return; QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(this, true)); Q_ASSERT(accessibleAttached); - accessibleAttached->setRole(accessibleRole()); + accessibleAttached->setRole(d->effectiveAccessibleRole()); } #endif diff --git a/src/quicktemplates/qquickcontrol_p.h b/src/quicktemplates/qquickcontrol_p.h index a607a159f3..bf17cf10bc 100644 --- a/src/quicktemplates/qquickcontrol_p.h +++ b/src/quicktemplates/qquickcontrol_p.h @@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE class QQuickControlPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickControl : public QQuickItem +class Q_QUICKTEMPLATES2_EXPORT QQuickControl : public QQuickItem { Q_OBJECT Q_PROPERTY(QFont font READ font WRITE setFont RESET resetFont NOTIFY fontChanged FINAL) @@ -39,7 +39,7 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickControl : public QQuickItem Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing RESET resetSpacing NOTIFY spacingChanged FINAL) Q_PROPERTY(QLocale locale READ locale WRITE setLocale RESET resetLocale NOTIFY localeChanged FINAL) Q_PROPERTY(bool mirrored READ isMirrored NOTIFY mirroredChanged FINAL) - Q_PROPERTY(Qt::FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy NOTIFY focusPolicyChanged FINAL) + QT6_ONLY(Q_PROPERTY(Qt::FocusPolicy focusPolicy READ focusPolicy WRITE setFocusPolicy NOTIFY focusPolicyChanged FINAL)) Q_PROPERTY(Qt::FocusReason focusReason READ focusReason WRITE setFocusReason NOTIFY focusReasonChanged FINAL) Q_PROPERTY(bool visualFocus READ hasVisualFocus NOTIFY visualFocusChanged FINAL) Q_PROPERTY(bool hovered READ isHovered NOTIFY hoveredChanged FINAL) @@ -104,9 +104,6 @@ public: bool isMirrored() const; - Qt::FocusPolicy focusPolicy() const; - void setFocusPolicy(Qt::FocusPolicy policy); - Qt::FocusReason focusReason() const; void setFocusReason(Qt::FocusReason reason); @@ -174,9 +171,8 @@ Q_SIGNALS: void bottomPaddingChanged(); void spacingChanged(); void localeChanged(); - void mirroredChanged(); - void focusPolicyChanged(); void focusReasonChanged(); + void mirroredChanged(); void visualFocusChanged(); void hoveredChanged(); void hoverEnabledChanged(); @@ -256,32 +252,6 @@ private: Q_DECLARE_PRIVATE(QQuickControl) }; -struct QQuickItemForeign -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QQuickItem) - QML_ADDED_IN_VERSION(2, 3) -}; - -struct QQuickColorGroupForeign -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QQuickColorGroup) - QML_ADDED_IN_VERSION(6, 0) -}; - -struct QQuickPaletteForeign -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QQuickPalette) - QML_ADDED_IN_VERSION(6, 0) -}; - QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickControl) - #endif // QQUICKCONTROL_P_H diff --git a/src/quicktemplates/qquickcontrol_p_p.h b/src/quicktemplates/qquickcontrol_p_p.h index d19c76dfe3..24d0507670 100644 --- a/src/quicktemplates/qquickcontrol_p_p.h +++ b/src/quicktemplates/qquickcontrol_p_p.h @@ -35,7 +35,7 @@ Q_DECLARE_LOGGING_CATEGORY(lcItemManagement) class QQuickAccessibleAttached; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickControlPrivate : public QQuickItemPrivate, public QQuickItemChangeListener +class Q_QUICKTEMPLATES2_EXPORT QQuickControlPrivate : public QQuickItemPrivate, public QQuickItemChangeListener #if QT_CONFIG(accessibility) , public QAccessible::ActivationObserver #endif @@ -130,8 +130,14 @@ public: virtual void cancelBackground(); virtual void executeBackground(bool complete = false); + enum class UnhideVisibility { + Show, + Hide + }; + static void hideOldItem(QQuickItem *item); - static void unhideOldItem(QQuickControl *control, QQuickItem *item); + static void unhideOldItem(QQuickControl *control, QQuickItem *item, + UnhideVisibility visibility = UnhideVisibility::Show); void updateBaselineOffset(); @@ -149,6 +155,8 @@ public: void itemDestroyed(QQuickItem *item) override; void itemFocusChanged(QQuickItem *item, Qt::FocusReason reason) override; + bool setLastFocusChangeReason(Qt::FocusReason) override; + virtual qreal getContentWidth() const; virtual qreal getContentHeight() const; @@ -200,8 +208,6 @@ public: qreal spacing = 0; QLocale locale; QFont resolvedFont; - Qt::FocusPolicy focusPolicy = Qt::NoFocus; - Qt::FocusReason focusReason = Qt::OtherFocusReason; QQuickDeferredPointer<QQuickItem> background; QQuickDeferredPointer<QQuickItem> contentItem; }; diff --git a/src/quicktemplates/qquickdayofweekmodel_p.h b/src/quicktemplates/qquickdayofweekmodel_p.h index ba80a43077..f3df4edab8 100644 --- a/src/quicktemplates/qquickdayofweekmodel_p.h +++ b/src/quicktemplates/qquickdayofweekmodel_p.h @@ -59,6 +59,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickDayOfWeekModel) - #endif // QQUICKDAYOFWEEKMODEL_P_H diff --git a/src/quicktemplates/qquickdayofweekrow_p.h b/src/quicktemplates/qquickdayofweekrow_p.h index 3b5c062196..7c2a2398fe 100644 --- a/src/quicktemplates/qquickdayofweekrow_p.h +++ b/src/quicktemplates/qquickdayofweekrow_p.h @@ -56,6 +56,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickDayOfWeekRow) - #endif // QQUICKDAYOFWEEKROW_P_H diff --git a/src/quicktemplates/qquickdeferredexecute.cpp b/src/quicktemplates/qquickdeferredexecute.cpp index 239b0fa6a6..19870a5636 100644 --- a/src/quicktemplates/qquickdeferredexecute.cpp +++ b/src/quicktemplates/qquickdeferredexecute.cpp @@ -30,6 +30,9 @@ static bool beginDeferred(QQmlEnginePrivate *enginePriv, const QQmlProperty &pro QQmlData *ddata = QQmlData::get(object); Q_ASSERT(!ddata->deferredData.isEmpty()); + if (!ddata->propertyCache) + ddata->propertyCache = QQmlMetaType::propertyCache(object->metaObject()); + int propertyIndex = property.index(); int wasInProgress = enginePriv->inProgressCreations; diff --git a/src/quicktemplates/qquickdeferredexecute_p_p.h b/src/quicktemplates/qquickdeferredexecute_p_p.h index beecb3636b..31d0f887cf 100644 --- a/src/quicktemplates/qquickdeferredexecute_p_p.h +++ b/src/quicktemplates/qquickdeferredexecute_p_p.h @@ -27,9 +27,9 @@ class QString; class QObject; namespace QtQuickPrivate { - Q_QUICKTEMPLATES2_PRIVATE_EXPORT void beginDeferred(QObject *object, const QString &property, QQuickUntypedDeferredPointer *delegate, bool isOwnState); - Q_QUICKTEMPLATES2_PRIVATE_EXPORT void cancelDeferred(QObject *object, const QString &property); - Q_QUICKTEMPLATES2_PRIVATE_EXPORT void completeDeferred(QObject *object, const QString &property, QQuickUntypedDeferredPointer *delegate); + Q_QUICKTEMPLATES2_EXPORT void beginDeferred(QObject *object, const QString &property, QQuickUntypedDeferredPointer *delegate, bool isOwnState); + Q_QUICKTEMPLATES2_EXPORT void cancelDeferred(QObject *object, const QString &property); + Q_QUICKTEMPLATES2_EXPORT void completeDeferred(QObject *object, const QString &property, QQuickUntypedDeferredPointer *delegate); } template<typename T> diff --git a/src/quicktemplates/qquickdeferredpointer_p_p.h b/src/quicktemplates/qquickdeferredpointer_p_p.h index cd5a87e508..e1fa85e2ee 100644 --- a/src/quicktemplates/qquickdeferredpointer_p_p.h +++ b/src/quicktemplates/qquickdeferredpointer_p_p.h @@ -113,10 +113,10 @@ class QQuickDeferredPointer : public QQuickUntypedDeferredPointer { Q_DISABLE_COPY_MOVE(QQuickDeferredPointer) public: - QQuickDeferredPointer() = default; + Q_NODISCARD_CTOR QQuickDeferredPointer() = default; ~QQuickDeferredPointer() = default; - QQuickDeferredPointer(T *v) : QQuickUntypedDeferredPointer(v) {} + Q_NODISCARD_CTOR QQuickDeferredPointer(T *v) : QQuickUntypedDeferredPointer(v) {} QQuickDeferredPointer<T> &operator=(T *o) { QQuickUntypedDeferredPointer::operator=(o); return *this; diff --git a/src/quicktemplates/qquickdelaybutton_p.h b/src/quicktemplates/qquickdelaybutton_p.h index ea8a8b6720..3e648c4739 100644 --- a/src/quicktemplates/qquickdelaybutton_p.h +++ b/src/quicktemplates/qquickdelaybutton_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QQuickTransition; class QQuickDelayButtonPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDelayButton : public QQuickAbstractButton +class Q_QUICKTEMPLATES2_EXPORT QQuickDelayButton : public QQuickAbstractButton { Q_OBJECT Q_PROPERTY(int delay READ delay WRITE setDelay NOTIFY delayChanged FINAL) @@ -62,6 +62,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickDelayButton) - #endif // QQUICKDELAYBUTTON_P_H diff --git a/src/quicktemplates/qquickdial.cpp b/src/quicktemplates/qquickdial.cpp index 4fd3a15de0..3f95bd901d 100644 --- a/src/quicktemplates/qquickdial.cpp +++ b/src/quicktemplates/qquickdial.cpp @@ -57,10 +57,30 @@ QT_BEGIN_NAMESPACE by the user by either touch, mouse, or keys. */ -static const qreal startAngleRadians = (M_PI * 2.0) * (4.0 / 6.0); -static const qreal startAngle = -140; -static const qreal endAngleRadians = (M_PI * 2.0) * (5.0 / 6.0); -static const qreal endAngle = 140; +/*! + \qmlsignal QtQuick.Controls::Dial::wrapped(Dial.WrapDirection direction) + \since 6.6 + + This signal is emitted when the dial wraps around, i.e. goes beyond its + maximum value to its minimum value, or vice versa. It is only emitted when + \l wrap is \c true. + The \a direction argument specifies the direction of the full rotation and + will be one of the following arguments: + + \value Dial.Clockwise The dial wrapped in clockwise direction. + \value Dial.CounterClockwise The dial wrapped in counterclockwise direction. +*/ + +// The user angle is the clockwise angle between the position and the vertical +// y-axis (12 o clock position). +// Using radians for logic (atan2(...)) and degree for user interface +constexpr qreal toUserAngleDeg(qreal logicAngleRad) { + // minus to turn clockwise, add 90 deg clockwise + return -logicAngleRad / M_PI * 180. + 90; +} + +static const qreal defaultStartAngle = -140; +static const qreal defaultEndAngle = 140; class QQuickDialPrivate : public QQuickControlPrivate { @@ -74,7 +94,7 @@ public: qreal linearPositionAt(const QPointF &point) const; void setPosition(qreal position); void updatePosition(); - bool isLargeChange(const QPointF &eventPos, qreal proposedPosition) const; + bool isLargeChange(qreal proposedPosition) const; bool isHorizontalOrVertical() const; bool handlePress(const QPointF &point, ulong timestamp) override; @@ -87,10 +107,14 @@ public: void updateAllValuesAreInteger(); + void maybeEmitWrapAround(qreal pos); + qreal from = 0; qreal to = 1; qreal value = 0; qreal position = 0; + qreal startAngle = defaultStartAngle; + qreal endAngle = defaultEndAngle; qreal angle = startAngle; qreal stepSize = 0; QPointF pressPoint; @@ -140,13 +164,34 @@ qreal QQuickDialPrivate::circularPositionAt(const QPointF &point) const { qreal yy = height / 2.0 - point.y(); qreal xx = point.x() - width / 2.0; - qreal angle = (xx || yy) ? std::atan2(yy, xx) : 0; + qreal alpha = (xx || yy) ? toUserAngleDeg(std::atan2(yy, xx)) : 0; + + // Move around the circle to reach the interval. + if (alpha < startAngle && alpha + 360. < endAngle) + alpha += 360.; + else if (alpha >= endAngle && alpha - 360. >= startAngle) + alpha -= 360.; + + // If wrap is on and we are out of the interval [startAngle, endAngle], + // we want to jump to the closest border to make it feel nice and responsive + if ((alpha < startAngle || alpha > endAngle) && wrap) { + if (abs(alpha - startAngle) > abs(endAngle - alpha - 360.)) + alpha += 360.; + else if (abs(alpha - startAngle - 360.) < abs(endAngle - alpha)) + alpha -= 360.; + } - if (angle < M_PI / -2) - angle = angle + M_PI * 2; + // If wrap is off, + // we want to stay as close as possible to the current angle. + // This is important to allow easy setting of boundary values (0,1) + if (!wrap) { + if (abs(angle - alpha) > abs(angle - (alpha + 360.))) + alpha += 360.; + if (abs(angle - alpha) > abs(angle - (alpha - 360.))) + alpha -= 360.; + } - qreal normalizedAngle = (startAngleRadians - angle) / endAngleRadians; - return normalizedAngle; + return (alpha - startAngle) / (endAngle - startAngle); } qreal QQuickDialPrivate::linearPositionAt(const QPointF &point) const @@ -179,12 +224,13 @@ void QQuickDialPrivate::setPosition(qreal pos) { Q_Q(QQuickDial); pos = qBound<qreal>(qreal(0), pos, qreal(1)); - if (qFuzzyCompare(position, pos)) + const qreal alpha = startAngle + pos * qAbs(endAngle - startAngle); + if (qFuzzyCompare(position, pos) && qFuzzyCompare(angle, alpha)) return; + angle = alpha; position = pos; - angle = startAngle + position * qAbs(endAngle - startAngle); emit q->positionChanged(); emit q->angleChanged(); @@ -198,9 +244,11 @@ void QQuickDialPrivate::updatePosition() setPosition(pos); } -bool QQuickDialPrivate::isLargeChange(const QPointF &eventPos, qreal proposedPosition) const +bool QQuickDialPrivate::isLargeChange(qreal proposedPosition) const { - return qAbs(proposedPosition - position) >= qreal(0.5) && eventPos.y() >= height / 2; + if (endAngle - startAngle < 180.0) + return false; + return qAbs(proposedPosition - position) > qreal(0.5); } bool QQuickDialPrivate::isHorizontalOrVertical() const @@ -223,11 +271,13 @@ bool QQuickDialPrivate::handleMove(const QPointF &point, ulong timestamp) Q_Q(QQuickDial); QQuickControlPrivate::handleMove(point, timestamp); const qreal oldPos = position; - qreal pos = positionAt(point); + qreal pos = qBound(0.0, positionAt(point), 1.0); if (snapMode == QQuickDial::SnapAlways) pos = snapPosition(pos); - if (wrap || isHorizontalOrVertical() || !isLargeChange(point, pos)) { + maybeEmitWrapAround(pos); + + if (wrap || isHorizontalOrVertical() || !isLargeChange(pos)) { if (live) q->setValue(valueAt(pos)); else @@ -248,7 +298,9 @@ bool QQuickDialPrivate::handleRelease(const QPointF &point, ulong timestamp) if (snapMode != QQuickDial::NoSnap) pos = snapPosition(pos); - if (wrap || isHorizontalOrVertical() || !isLargeChange(point, pos)) + maybeEmitWrapAround(pos); + + if (wrap || isHorizontalOrVertical() || !isLargeChange(pos)) q->setValue(valueAt(pos)); if (!qFuzzyCompare(pos, oldPos)) emit q->moved(); @@ -272,8 +324,6 @@ void QQuickDialPrivate::handleUngrab() q->setPressed(false); } -static inline QString handleName() { return QStringLiteral("handle"); } - void QQuickDialPrivate::cancelHandle() { Q_Q(QQuickDial); @@ -303,6 +353,14 @@ void QQuickDialPrivate::updateAllValuesAreInteger() allValuesAreInteger = areRepresentableAsInteger(to, from, stepSize) && stepSize != 0.0; } +void QQuickDialPrivate::maybeEmitWrapAround(qreal pos) +{ + Q_Q(QQuickDial); + + if (wrap && isLargeChange(pos)) + emit q->wrapped((pos < q->position()) ? QQuickDial::Clockwise : QQuickDial::CounterClockwise); +} + QQuickDial::QQuickDial(QQuickItem *parent) : QQuickControl(*(new QQuickDialPrivate), parent) { @@ -314,6 +372,8 @@ QQuickDial::QQuickDial(QQuickItem *parent) #if QT_CONFIG(cursor) setCursor(Qt::ArrowCursor); #endif + Q_D(QQuickDial); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred); } /*! @@ -422,11 +482,12 @@ qreal QQuickDial::position() const \qmlproperty real QtQuick.Controls::Dial::angle \readonly - This property holds the angle of the handle. + This property holds the clockwise angle of the handle in degrees. - The range is from \c -140 degrees to \c 140 degrees. + The angle is zero at the 12 o'clock position and the range is from + \l startAngle to \c endAngle. - \sa position + \sa position, startAngle, endAngle */ qreal QQuickDial::angle() const { @@ -469,6 +530,129 @@ void QQuickDial::setStepSize(qreal step) emit stepSizeChanged(); } + +/*! + \qmlproperty real QtQuick.Controls::Dial::startAngle + \since 6.6 + + This property holds the starting angle of the dial in degrees. + + This is the \l angle the dial will have for its minimum value, i.e. \l from. + The \l startAngle has to be smaller than the \l endAngle, larger than -360 + and larger or equal to the \l endAngle - 360 degrees. + + \sa endAngle, angle +*/ +qreal QQuickDial::startAngle() const +{ + Q_D(const QQuickDial); + return d->startAngle; +} + +void QQuickDial::setStartAngle(qreal startAngle) +{ + Q_D(QQuickDial); + if (!d->componentComplete) { + // Binding evaluation order can cause warnings with certain combinations + // of start and end angles, so delay the actual setting until after component completion. + // Store the requested value in the existing member to avoid the need for an extra one. + d->startAngle = startAngle; + return; + } + + if (qFuzzyCompare(d->startAngle, startAngle)) + return; + + // do not allow to change direction + if (startAngle >= d->endAngle) { + qmlWarning(this) << "startAngle (" << startAngle + << ") cannot be greater than or equal to endAngle (" << d->endAngle << ")"; + return; + } + + // Keep the interval around 0 + if (startAngle <= -360.) { + qmlWarning(this) << "startAngle (" << startAngle << ") cannot be less than or equal to -360"; + return; + } + + // keep the interval [startAngle, endAngle] unique + if (startAngle < d->endAngle - 360.) { + qmlWarning(this) << "Difference between startAngle (" << startAngle + << ") and endAngle (" << d->endAngle << ") cannot be greater than 360." + << " Changing endAngle to avoid overlaps."; + d->endAngle = startAngle + 360.; + emit endAngleChanged(); + } + + d->startAngle = startAngle; + // changing the startAngle will change the angle + // if the value is kept constant + d->updatePosition(); + emit startAngleChanged(); +} + +/*! + \qmlproperty real QtQuick.Controls::Dial::endAngle + \since 6.6 + + This property holds the end angle of the dial in degrees. + + This is the \l angle the dial will have for its maximum value, i.e. \l to. + The \l endAngle has to be bigger than the \l startAngle, smaller than 720 + and smaller or equal than the \l startAngle + 360 degrees. + + \sa endAngle, angle +*/ +qreal QQuickDial::endAngle() const +{ + Q_D(const QQuickDial); + return d->endAngle; + +} + +void QQuickDial::setEndAngle(qreal endAngle) +{ + Q_D(QQuickDial); + if (!d->componentComplete) { + // Binding evaluation order can cause warnings with certain combinations + // of start and end angles, so delay the actual setting until after component completion. + // Store the requested value in the existing member to avoid the need for an extra one. + d->endAngle = endAngle; + return; + } + + if (qFuzzyCompare(d->endAngle, endAngle)) + return; + + if (endAngle <= d->startAngle) { + qmlWarning(this) << "endAngle (" << endAngle + << ") cannot be less than or equal to startAngle (" << d->startAngle << ")"; + return; + } + + // Keep the interval around 0 + if (endAngle >= 720.) { + qmlWarning(this) << "endAngle (" << endAngle << ") cannot be greater than or equal to 720"; + return; + } + + // keep the interval [startAngle, endAngle] unique + if (endAngle > d->startAngle + 360.) { + qmlWarning(this) << "Difference between startAngle (" << d->startAngle + << ") and endAngle (" << endAngle << ") cannot be greater than 360." + << " Changing startAngle to avoid overlaps."; + d->startAngle = endAngle - 360.; + emit startAngleChanged(); + } + + d->endAngle = endAngle; + // changing the startAngle will change the angle + // if the value is kept constant + d->updatePosition(); + emit endAngleChanged(); +} + /*! \qmlproperty enumeration QtQuick.Controls::Dial::snapMode @@ -808,6 +992,21 @@ void QQuickDial::componentComplete() Q_D(QQuickDial); d->executeHandle(true); QQuickControl::componentComplete(); + + // Set the (delayed) start and end angles, if necessary (see the setters for more info). + if (!qFuzzyCompare(d->startAngle, defaultStartAngle)) { + const qreal startAngle = d->startAngle; + // Temporarily set it to something else so that it sees that it has changed. + d->startAngle = defaultStartAngle; + setStartAngle(startAngle); + } + + if (!qFuzzyCompare(d->endAngle, defaultEndAngle)) { + const qreal endAngle = d->endAngle; + d->endAngle = defaultEndAngle; + setEndAngle(endAngle); + } + setValue(d->value); d->updatePosition(); } diff --git a/src/quicktemplates/qquickdial_p.h b/src/quicktemplates/qquickdial_p.h index c716b9671b..2008261214 100644 --- a/src/quicktemplates/qquickdial_p.h +++ b/src/quicktemplates/qquickdial_p.h @@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE class QQuickDialAttached; class QQuickDialPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDial : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickDial : public QQuickControl { Q_OBJECT Q_PROPERTY(qreal from READ from WRITE setFrom NOTIFY fromChanged FINAL) @@ -32,6 +32,8 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDial : public QQuickControl Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) Q_PROPERTY(qreal position READ position NOTIFY positionChanged FINAL) Q_PROPERTY(qreal angle READ angle NOTIFY angleChanged FINAL) + Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged FINAL REVISION(6, 6)) + Q_PROPERTY(qreal endAngle READ endAngle WRITE setEndAngle NOTIFY endAngleChanged FINAL REVISION(6, 6)) Q_PROPERTY(qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) Q_PROPERTY(SnapMode snapMode READ snapMode WRITE setSnapMode NOTIFY snapModeChanged FINAL) Q_PROPERTY(bool wrap READ wrap WRITE setWrap NOTIFY wrapChanged FINAL) @@ -64,6 +66,12 @@ public: qreal stepSize() const; void setStepSize(qreal step); + qreal startAngle() const; + void setStartAngle(qreal startAngle); + + qreal endAngle() const; + void setEndAngle(qreal endAngle); + enum SnapMode { NoSnap, SnapAlways, @@ -81,6 +89,12 @@ public: }; Q_ENUM(InputMode) + enum WrapDirection { + Clockwise, + CounterClockwise + }; + Q_ENUM(WrapDirection) + bool wrap() const; void setWrap(bool wrap); @@ -118,6 +132,9 @@ Q_SIGNALS: Q_REVISION(2, 2) void liveChanged(); // 2.5 (Qt 5.12) Q_REVISION(2, 5) void inputModeChanged(); + Q_REVISION(6, 6) void startAngleChanged(); + Q_REVISION(6, 6) void endAngleChanged(); + Q_REVISION(6, 6) void wrapped(WrapDirection); protected: void keyPressEvent(QKeyEvent *event) override; @@ -145,6 +162,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickDial) - #endif // QQUICKDIAL_P_H diff --git a/src/quicktemplates/qquickdialog.cpp b/src/quicktemplates/qquickdialog.cpp index 300ad4301f..7a1c5d513b 100644 --- a/src/quicktemplates/qquickdialog.cpp +++ b/src/quicktemplates/qquickdialog.cpp @@ -6,6 +6,7 @@ #include "qquickdialogbuttonbox_p.h" #include "qquickabstractbutton_p.h" #include "qquickpopupitem_p_p.h" +#include "qquickpopupwindow_p_p.h" QT_BEGIN_NAMESPACE @@ -25,6 +26,10 @@ QT_BEGIN_NAMESPACE \image qtquickcontrols-page-wireframe.png + The \l {Popup::}{padding} properties only affect the contentItem. Use the + \l {Popup::}{spacing} property to affect the space between header, + contentItem and footer. + By default, Dialogs have \l {QQuickItem::}{focus}. \section1 Dialog Title and Buttons @@ -160,6 +165,11 @@ void QQuickDialogPrivate::handleClick(QQuickAbstractButton *button) } } +Qt::WindowFlags QQuickDialogPrivate::popupWindowType() const +{ + return Qt::Dialog; +} + QQuickDialog::QQuickDialog(QObject *parent) : QQuickDialog(*(new QQuickDialogPrivate), parent) { @@ -172,8 +182,6 @@ QQuickDialog::QQuickDialog(QQuickDialogPrivate &dd, QObject *parent) // Dialogs should get active focus when opened so that e.g. Cancel closes them. setFocus(true); - - QObject::connect(d->popupItem, &QQuickPopupItem::titleChanged, this, &QQuickDialog::titleChanged); QObject::connect(d->popupItem, &QQuickPopupItem::headerChanged, this, &QQuickDialog::headerChanged); QObject::connect(d->popupItem, &QQuickPopupItem::footerChanged, this, &QQuickDialog::footerChanged); QObject::connect(d->popupItem, &QQuickPopupItem::implicitHeaderWidthChanged, this, &QQuickDialog::implicitHeaderWidthChanged); @@ -185,7 +193,6 @@ QQuickDialog::QQuickDialog(QQuickDialogPrivate &dd, QObject *parent) QQuickDialog::~QQuickDialog() { Q_D(QQuickDialog); - QObject::disconnect(d->popupItem, &QQuickPopupItem::titleChanged, this, &QQuickDialog::titleChanged); QObject::disconnect(d->popupItem, &QQuickPopupItem::headerChanged, this, &QQuickDialog::headerChanged); QObject::disconnect(d->popupItem, &QQuickPopupItem::footerChanged, this, &QQuickDialog::footerChanged); QObject::disconnect(d->popupItem, &QQuickPopupItem::implicitHeaderWidthChanged, this, &QQuickDialog::implicitHeaderWidthChanged); @@ -214,13 +221,22 @@ QQuickDialog::~QQuickDialog() QString QQuickDialog::title() const { Q_D(const QQuickDialog); - return d->popupItem->title(); + return d->m_title; } void QQuickDialog::setTitle(const QString &title) { Q_D(QQuickDialog); - d->popupItem->setTitle(title); + if (d->m_title == title) + return; + d->m_title = title; + + if (d->popupWindow) + d->popupWindow->setTitle(title); + else + d->popupItem->setTitle(title); + + emit titleChanged(); } /*! @@ -484,6 +500,12 @@ qreal QQuickDialog::implicitFooterHeight() const return d->popupItem->implicitFooterHeight(); } +void QQuickDialog::setOpacity(qreal opacity) +{ + Q_D(QQuickDialog); + QQuickPopup::setOpacity(d->popupWindow ? qreal(!qFuzzyIsNull(opacity)) : opacity); +} + /*! \qmlmethod void QtQuick.Controls::Dialog::accept() diff --git a/src/quicktemplates/qquickdialog_p.h b/src/quicktemplates/qquickdialog_p.h index a350c5d923..c28e687b79 100644 --- a/src/quicktemplates/qquickdialog_p.h +++ b/src/quicktemplates/qquickdialog_p.h @@ -19,11 +19,13 @@ #include <QtQuickTemplates2/private/qquickpopup_p.h> #include <QtGui/qpa/qplatformdialoghelper.h> +QT_REQUIRE_CONFIG(quicktemplates2_container); + QT_BEGIN_NAMESPACE class QQuickDialogPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialog : public QQuickPopup +class Q_QUICKTEMPLATES2_EXPORT QQuickDialog : public QQuickPopup { Q_OBJECT Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL) @@ -72,6 +74,8 @@ public: qreal implicitFooterWidth() const; qreal implicitFooterHeight() const; + void setOpacity(qreal opacity) override; + public Q_SLOTS: virtual void accept(); virtual void reject(); @@ -111,6 +115,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickDialog) - #endif // QQUICKDIALOG_P_H diff --git a/src/quicktemplates/qquickdialog_p_p.h b/src/quicktemplates/qquickdialog_p_p.h index 2d0121892a..9b0e5d177a 100644 --- a/src/quicktemplates/qquickdialog_p_p.h +++ b/src/quicktemplates/qquickdialog_p_p.h @@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE class QQuickAbstractButton; class QQuickDialogButtonBox; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogPrivate : public QQuickPopupPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickDialogPrivate : public QQuickPopupPrivate { public: Q_DECLARE_PUBLIC(QQuickDialog) @@ -40,6 +40,8 @@ public: virtual void handleReject(); virtual void handleClick(QQuickAbstractButton *button); + Qt::WindowFlags popupWindowType() const override; + int result = 0; QString title; QQuickDialogButtonBox *buttonBox = nullptr; diff --git a/src/quicktemplates/qquickdialogbuttonbox.cpp b/src/quicktemplates/qquickdialogbuttonbox.cpp index 989c9b4506..b7c486c472 100644 --- a/src/quicktemplates/qquickdialogbuttonbox.cpp +++ b/src/quicktemplates/qquickdialogbuttonbox.cpp @@ -383,10 +383,8 @@ QQuickAbstractButton *QQuickDialogButtonBoxPrivate::createStandardButton(QPlatfo QQmlContext *creationContext = delegate->creationContext(); if (!creationContext) creationContext = qmlContext(q); - QQmlContext *context = new QQmlContext(creationContext, q); - context->setContextObject(q); - QObject *object = delegate->beginCreate(context); + QObject *object = delegate->beginCreate(creationContext); QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(object); if (button) { QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(button, true)); @@ -457,6 +455,7 @@ QQuickDialogButtonBox::QQuickDialogButtonBox(QQuickItem *parent) Q_D(QQuickDialogButtonBox); d->changeTypes |= QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight; d->buttonLayout = platformButtonLayout(); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); } QQuickDialogButtonBox::~QQuickDialogButtonBox() @@ -480,7 +479,7 @@ QQuickDialogButtonBox::~QQuickDialogButtonBox() Possible values: \value DialogButtonBox.Header The button box is at the top, as a window or page header. - \value DialogButtonBox.Footer The button box is at the bottom, as a window or page header. + \value DialogButtonBox.Footer The button box is at the bottom, as a window or page footer. The default value is \c Footer. @@ -515,6 +514,13 @@ void QQuickDialogButtonBox::setPosition(Position position) \value Qt.AlignTop The buttons are aligned to the top. \value Qt.AlignVCenter The buttons are vertically centered. \value Qt.AlignBottom The buttons are aligned to the bottom. + + The default value is \c undefined. + + \note This property assumes a horizontal layout of the buttons. The + DialogButtonBox for the \l {iOS Style}{iOS style} uses a vertical layout + when there are more than two buttons, and if set to a value other than + \c undefined, the layout of its buttons will be done horizontally. */ Qt::Alignment QQuickDialogButtonBox::alignment() const { diff --git a/src/quicktemplates/qquickdialogbuttonbox_p.h b/src/quicktemplates/qquickdialogbuttonbox_p.h index 02cdd3bb63..73b0af9953 100644 --- a/src/quicktemplates/qquickdialogbuttonbox_p.h +++ b/src/quicktemplates/qquickdialogbuttonbox_p.h @@ -19,6 +19,8 @@ #include <QtQuickTemplates2/private/qquickcontainer_p.h> #include <QtGui/qpa/qplatformdialoghelper.h> +QT_REQUIRE_CONFIG(quicktemplates2_container); + QT_BEGIN_NAMESPACE class QQmlComponent; @@ -26,7 +28,7 @@ class QQuickDialogButtonBoxPrivate; class QQuickDialogButtonBoxAttached; class QQuickDialogButtonBoxAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBox : public QQuickContainer +class Q_QUICKTEMPLATES2_EXPORT QQuickDialogButtonBox : public QQuickContainer { Q_OBJECT Q_PROPERTY(Position position READ position WRITE setPosition NOTIFY positionChanged FINAL) @@ -35,9 +37,9 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBox : public QQuickCont Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) // 2.5 (Qt 5.12) Q_PROPERTY(QPlatformDialogHelper::ButtonLayout buttonLayout READ buttonLayout WRITE setButtonLayout RESET resetButtonLayout NOTIFY buttonLayoutChanged FINAL REVISION(2, 5)) - Q_FLAGS(QPlatformDialogHelper::StandardButtons) QML_NAMED_ELEMENT(DialogButtonBox) QML_ATTACHED(QQuickDialogButtonBoxAttached) + QML_EXTENDED_NAMESPACE(QPlatformDialogHelper) QML_ADDED_IN_VERSION(2, 1) public: @@ -66,9 +68,6 @@ public: static QQuickDialogButtonBoxAttached *qmlAttachedProperties(QObject *object); - // 2.5 (Qt 5.12) - Q_ENUMS(QPlatformDialogHelper::ButtonLayout) - QPlatformDialogHelper::ButtonLayout buttonLayout() const; void setButtonLayout(QPlatformDialogHelper::ButtonLayout layout); void resetButtonLayout(); @@ -108,12 +107,11 @@ private: Q_DECLARE_PRIVATE(QQuickDialogButtonBox) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBoxAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickDialogButtonBoxAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickDialogButtonBox *buttonBox READ buttonBox NOTIFY buttonBoxChanged FINAL) Q_PROPERTY(QPlatformDialogHelper::ButtonRole buttonRole READ buttonRole WRITE setButtonRole NOTIFY buttonRoleChanged FINAL) - Q_ENUMS(QPlatformDialogHelper::ButtonRole) public: explicit QQuickDialogButtonBoxAttached(QObject *parent = nullptr); @@ -134,6 +132,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickDialogButtonBox) - #endif // QQUICKDIALOGBUTTONBOX_P_H diff --git a/src/quicktemplates/qquickdialogbuttonbox_p_p.h b/src/quicktemplates/qquickdialogbuttonbox_p_p.h index f2d9ff6b2d..b4b8af840f 100644 --- a/src/quicktemplates/qquickdialogbuttonbox_p_p.h +++ b/src/quicktemplates/qquickdialogbuttonbox_p_p.h @@ -20,7 +20,7 @@ QT_BEGIN_NAMESPACE -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBoxPrivate : public QQuickContainerPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickDialogButtonBoxPrivate : public QQuickContainerPrivate { public: Q_DECLARE_PUBLIC(QQuickDialogButtonBox) diff --git a/src/quicktemplates/qquickdrawer.cpp b/src/quicktemplates/qquickdrawer.cpp index 54b4eedaaa..678d434629 100644 --- a/src/quicktemplates/qquickdrawer.cpp +++ b/src/quicktemplates/qquickdrawer.cpp @@ -12,6 +12,7 @@ #include <QtQuick/private/qquickwindow_p.h> #include <QtQuick/private/qquickanimation_p.h> #include <QtQuick/private/qquicktransition_p.h> +#include <QtQuickTemplates2/private/qquickoverlay_p.h> QT_BEGIN_NAMESPACE @@ -127,7 +128,7 @@ QT_BEGIN_NAMESPACE Drawer can be configured as a non-closable persistent side panel by making the Drawer \l {Popup::modal}{non-modal} and \l {interactive} - {non-interactive}. See the \l {Qt Quick Controls 2 - Side Panel}{Side Panel} + {non-interactive}. See the \l {Qt Quick Controls 2 - Gallery}{Gallery} example for more details. \note On some platforms, certain edges may be reserved for system @@ -164,15 +165,25 @@ qreal QQuickDrawerPrivate::positionAt(const QPointF &point) const if (!window) return 0; - switch (edge) { + auto size = QSizeF(q->width(), q->height()); + + switch (effectiveEdge()) { case Qt::TopEdge: - return point.y() / q->height(); + if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + size.transpose(); + return point.y() / size.height(); case Qt::LeftEdge: - return point.x() / q->width(); + if (edge == Qt::TopEdge || edge == Qt::BottomEdge) + size.transpose(); + return point.x() / size.width(); case Qt::RightEdge: - return (window->width() - point.x()) / q->width(); + if (edge == Qt::TopEdge || edge == Qt::BottomEdge) + size.transpose(); + return (window->width() - point.x()) / size.width(); case Qt::BottomEdge: - return (window->height() - point.y()) / q->height(); + if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + size.transpose(); + return (window->height() - point.y()) / size.height(); default: return 0; } @@ -216,22 +227,24 @@ void QQuickDrawerPositioner::reposition() QQuickPopupPositioner::reposition(); } -void QQuickDrawerPrivate::showOverlay() +void QQuickDrawerPrivate::showDimmer() { // managed in setPosition() } -void QQuickDrawerPrivate::hideOverlay() +void QQuickDrawerPrivate::hideDimmer() { // managed in setPosition() } -void QQuickDrawerPrivate::resizeOverlay() +void QQuickDrawerPrivate::resizeDimmer() { if (!dimmer || !window) return; - QRectF geometry(0, 0, window->width(), window->height()); + const QQuickOverlay *overlay = QQuickOverlay::overlay(window); + + QRectF geometry(0, 0, overlay ? overlay->width() : 0, overlay ? overlay->height() : 0); if (edge == Qt::LeftEdge || edge == Qt::RightEdge) { geometry.setY(popupItem->y()); @@ -245,17 +258,18 @@ void QQuickDrawerPrivate::resizeOverlay() dimmer->setSize(geometry.size()); } -static bool isWithinDragMargin(const QQuickDrawer *drawer, const QPointF &pos) +bool QQuickDrawerPrivate::isWithinDragMargin(const QPointF &pos) const { - switch (drawer->edge()) { + Q_Q(const QQuickDrawer); + switch (effectiveEdge()) { case Qt::LeftEdge: - return pos.x() <= drawer->dragMargin(); + return pos.x() <= q->dragMargin(); case Qt::RightEdge: - return pos.x() >= drawer->window()->width() - drawer->dragMargin(); + return pos.x() >= q->window()->width() - q->dragMargin(); case Qt::TopEdge: - return pos.y() <= drawer->dragMargin(); + return pos.y() <= q->dragMargin(); case Qt::BottomEdge: - return pos.y() >= drawer->window()->height() - drawer->dragMargin(); + return pos.y() >= q->window()->height() - q->dragMargin(); default: Q_UNREACHABLE(); break; @@ -265,14 +279,13 @@ static bool isWithinDragMargin(const QQuickDrawer *drawer, const QPointF &pos) bool QQuickDrawerPrivate::startDrag(QEvent *event) { - Q_Q(QQuickDrawer); delayedEnterTransition = false; if (!window || !interactive || dragMargin < 0.0 || qFuzzyIsNull(dragMargin)) return false; switch (event->type()) { case QEvent::MouseButtonPress: - if (QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); isWithinDragMargin(q, mouseEvent->scenePosition())) { + if (QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event); isWithinDragMargin(mouseEvent->scenePosition())) { // watch future events and grab the mouse once it has moved // sufficiently fast or far (in grabMouse). delayedEnterTransition = true; @@ -287,7 +300,7 @@ bool QQuickDrawerPrivate::startDrag(QEvent *event) case QEvent::TouchUpdate: { auto *touchEvent = static_cast<QTouchEvent *>(event); for (const QTouchEvent::TouchPoint &point : touchEvent->points()) { - if (point.state() == QEventPoint::Pressed && isWithinDragMargin(q, point.scenePosition())) { + if (point.state() == QEventPoint::Pressed && isWithinDragMargin(point.scenePosition())) { delayedEnterTransition = true; touchEvent->addPassiveGrabber(point, popupItem); handleTouchEvent(window->contentItem(), touchEvent); @@ -325,10 +338,11 @@ bool QQuickDrawerPrivate::grabMouse(QQuickItem *item, QMouseEvent *event) // larger threshold to avoid being too eager to steal touch (QTBUG-50045) const int threshold = qMax(20, QGuiApplication::styleHints()->startDragDistance() + 5); bool overThreshold = false; + Qt::Edge effEdge = effectiveEdge(); if (position > 0 || dragMargin > 0) { const bool xOverThreshold = QQuickWindowPrivate::dragOverThreshold(movePoint.x() - pressPoint.x(), Qt::XAxis, event, threshold); const bool yOverThreshold = QQuickWindowPrivate::dragOverThreshold(movePoint.y() - pressPoint.y(), Qt::YAxis, event, threshold); - if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + if (effEdge == Qt::LeftEdge || effEdge == Qt::RightEdge) overThreshold = xOverThreshold && !yOverThreshold; else overThreshold = yOverThreshold && !xOverThreshold; @@ -336,7 +350,7 @@ bool QQuickDrawerPrivate::grabMouse(QQuickItem *item, QMouseEvent *event) // Don't be too eager to steal presses outside the drawer (QTBUG-53929) if (overThreshold && qFuzzyCompare(position, qreal(1.0)) && !contains(movePoint)) { - if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + if (effEdge == Qt::LeftEdge || effEdge == Qt::RightEdge) overThreshold = qAbs(movePoint.x() - q->width()) < dragMargin; else overThreshold = qAbs(movePoint.y() - q->height()) < dragMargin; @@ -377,10 +391,11 @@ bool QQuickDrawerPrivate::grabTouch(QQuickItem *item, QTouchEvent *event) // QStyleHints::startDragDistance for dragging. Drawer uses a bit // larger threshold to avoid being too eager to steal touch (QTBUG-50045) const int threshold = qMax(20, QGuiApplication::styleHints()->startDragDistance() + 5); + const Qt::Edge effEdge = effectiveEdge(); if (position > 0 || dragMargin > 0) { const bool xOverThreshold = QQuickWindowPrivate::dragOverThreshold(movePoint.x() - pressPoint.x(), Qt::XAxis, &point, threshold); const bool yOverThreshold = QQuickWindowPrivate::dragOverThreshold(movePoint.y() - pressPoint.y(), Qt::YAxis, &point, threshold); - if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + if (effEdge == Qt::LeftEdge || effEdge == Qt::RightEdge) overThreshold = xOverThreshold && !yOverThreshold; else overThreshold = yOverThreshold && !xOverThreshold; @@ -388,7 +403,7 @@ bool QQuickDrawerPrivate::grabTouch(QQuickItem *item, QTouchEvent *event) // Don't be too eager to steal presses outside the drawer (QTBUG-53929) if (overThreshold && qFuzzyCompare(position, qreal(1.0)) && !contains(movePoint)) { - if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + if (effEdge == Qt::LeftEdge || effEdge == Qt::RightEdge) overThreshold = qAbs(movePoint.x() - q->width()) < dragMargin; else overThreshold = qAbs(movePoint.y() - q->height()) < dragMargin; @@ -421,8 +436,6 @@ static const qreal openCloseVelocityThreshold = 300; // interactive control. bool QQuickDrawerPrivate::blockInput(QQuickItem *item, const QPointF &point) const { - Q_Q(const QQuickDrawer); - // We want all events, if mouse/touch is already grabbed. if (popupItem->keepMouseGrab() || popupItem->keepTouchGrab()) return true; @@ -436,7 +449,7 @@ bool QQuickDrawerPrivate::blockInput(QQuickItem *item, const QPointF &point) con return false; // Accept all events within drag area. - if (isWithinDragMargin(q, point)) + if (isWithinDragMargin(point)) return true; // Accept all other events if drawer is modal. @@ -487,9 +500,9 @@ bool QQuickDrawerPrivate::handleRelease(QQuickItem *item, const QPointF &point, } velocityCalculator.stopMeasuring(point, timestamp); - + Qt::Edge effEdge = effectiveEdge(); qreal velocity = 0; - if (edge == Qt::LeftEdge || edge == Qt::RightEdge) + if (effEdge == Qt::LeftEdge || effEdge == Qt::RightEdge) velocity = velocityCalculator.velocity().x(); else velocity = velocityCalculator.velocity().y(); @@ -502,7 +515,7 @@ bool QQuickDrawerPrivate::handleRelease(QQuickItem *item, const QPointF &point, // - bottom/right edge: negative velocity opens, positive velocity closes // // => invert the velocity for bottom and right edges, for the threshold comparison below - if (edge == Qt::RightEdge || edge == Qt::BottomEdge) + if (effEdge == Qt::RightEdge || effEdge == Qt::BottomEdge) velocity = -velocity; if (position > 0.7 || velocity > openCloseVelocityThreshold) { @@ -510,7 +523,7 @@ bool QQuickDrawerPrivate::handleRelease(QQuickItem *item, const QPointF &point, } else if (position < 0.3 || velocity < -openCloseVelocityThreshold) { transitionManager.transitionExit(); } else { - switch (edge) { + switch (effEdge) { case Qt::LeftEdge: if (point.x() - pressPoint.x() > 0) transitionManager.transitionEnter(); @@ -583,6 +596,12 @@ bool QQuickDrawerPrivate::prepareExitTransition() return QQuickPopupPrivate::prepareExitTransition(); } +QQuickPopup::PopupType QQuickDrawerPrivate::resolvedPopupType() const +{ + // For now, a drawer will always be shown in-scene + return QQuickPopup::Item; +} + bool QQuickDrawerPrivate::setEdge(Qt::Edge e) { Q_Q(QQuickDrawer); @@ -641,6 +660,31 @@ Qt::Edge QQuickDrawer::edge() const return d->edge; } +Qt::Edge QQuickDrawerPrivate::effectiveEdge() const +{ + auto realEdge = edge; + qreal rotation = window->contentItem()->rotation(); + const bool clockwise = rotation > 0; + while (qAbs(rotation) >= 90) { + rotation -= clockwise ? 90 : -90; + switch (realEdge) { + case Qt::LeftEdge: + realEdge = clockwise ? Qt::TopEdge : Qt::BottomEdge; + break; + case Qt::TopEdge: + realEdge = clockwise ? Qt::RightEdge : Qt::LeftEdge; + break; + case Qt::RightEdge: + realEdge = clockwise ? Qt::BottomEdge : Qt::TopEdge; + break; + case Qt::BottomEdge: + realEdge = clockwise ? Qt::LeftEdge : Qt::RightEdge; + break; + } + } + return realEdge; +} + void QQuickDrawer::setEdge(Qt::Edge edge) { Q_D(QQuickDrawer); @@ -799,7 +843,7 @@ void QQuickDrawer::geometryChange(const QRectF &newGeometry, const QRectF &oldGe { Q_D(QQuickDrawer); QQuickPopup::geometryChange(newGeometry, oldGeometry); - d->resizeOverlay(); + d->resizeDimmer(); } QT_END_NAMESPACE diff --git a/src/quicktemplates/qquickdrawer_p.h b/src/quicktemplates/qquickdrawer_p.h index ec42117f20..a8255e992e 100644 --- a/src/quicktemplates/qquickdrawer_p.h +++ b/src/quicktemplates/qquickdrawer_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickDrawerPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDrawer : public QQuickPopup +class Q_QUICKTEMPLATES2_EXPORT QQuickDrawer : public QQuickPopup { Q_OBJECT Q_PROPERTY(Qt::Edge edge READ edge WRITE setEdge NOTIFY edgeChanged FINAL) @@ -73,6 +73,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickDrawer) - #endif // QQUICKDRAWER_P_H diff --git a/src/quicktemplates/qquickdrawer_p_p.h b/src/quicktemplates/qquickdrawer_p_p.h index 224e557839..18646463b5 100644 --- a/src/quicktemplates/qquickdrawer_p_p.h +++ b/src/quicktemplates/qquickdrawer_p_p.h @@ -35,9 +35,9 @@ public: qreal positionAt(const QPointF &point) const; QQuickPopupPositioner *getPositioner() override; - void showOverlay() override; - void hideOverlay() override; - void resizeOverlay() override; + void showDimmer() override; + void hideDimmer() override; + void resizeDimmer() override; bool startDrag(QEvent *event); bool grabMouse(QQuickItem *item, QMouseEvent *event); @@ -54,7 +54,11 @@ public: bool prepareEnterTransition() override; bool prepareExitTransition() override; + QQuickPopup::PopupType resolvedPopupType() const override; + bool setEdge(Qt::Edge edge); + Qt::Edge effectiveEdge() const; + bool isWithinDragMargin(const QPointF &point) const; Qt::Edge edge = Qt::LeftEdge; qreal offset = 0; diff --git a/src/quicktemplates/qquickframe_p.h b/src/quicktemplates/qquickframe_p.h index 94c98685f4..6500001fa6 100644 --- a/src/quicktemplates/qquickframe_p.h +++ b/src/quicktemplates/qquickframe_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickFramePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickFrame : public QQuickPane +class Q_QUICKTEMPLATES2_EXPORT QQuickFrame : public QQuickPane { Q_OBJECT QML_NAMED_ELEMENT(Frame) @@ -44,6 +44,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickFrame) - #endif // QQUICKFRAME_P_H diff --git a/src/quicktemplates/qquickframe_p_p.h b/src/quicktemplates/qquickframe_p_p.h index bb7eabf12a..8f882a26e4 100644 --- a/src/quicktemplates/qquickframe_p_p.h +++ b/src/quicktemplates/qquickframe_p_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickFrame; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickFramePrivate : public QQuickPanePrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickFramePrivate : public QQuickPanePrivate { }; diff --git a/src/quicktemplates/qquickgroupbox.cpp b/src/quicktemplates/qquickgroupbox.cpp index 0fa2d1a6d8..72ce1b42cb 100644 --- a/src/quicktemplates/qquickgroupbox.cpp +++ b/src/quicktemplates/qquickgroupbox.cpp @@ -61,6 +61,7 @@ public: void itemImplicitWidthChanged(QQuickItem *item) override; void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::GroupBox); } @@ -104,6 +105,16 @@ void QQuickGroupBoxPrivate::itemImplicitHeightChanged(QQuickItem *item) emit q->implicitLabelHeightChanged(); } +void QQuickGroupBoxPrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickGroupBox); + QQuickFramePrivate::itemDestroyed(item); + if (item == label) { + label = nullptr; + emit q->labelChanged(); + } +} + QQuickGroupBox::QQuickGroupBox(QQuickItem *parent) : QQuickFrame(*(new QQuickGroupBoxPrivate), parent) { diff --git a/src/quicktemplates/qquickgroupbox_p.h b/src/quicktemplates/qquickgroupbox_p.h index 5fc6284fea..0b2a5a0e52 100644 --- a/src/quicktemplates/qquickgroupbox_p.h +++ b/src/quicktemplates/qquickgroupbox_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickGroupBoxPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickGroupBox : public QQuickFrame +class Q_QUICKTEMPLATES2_EXPORT QQuickGroupBox : public QQuickFrame { Q_OBJECT Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL) @@ -71,6 +71,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickGroupBox) - #endif // QQUICKGROUPBOX_P_H diff --git a/src/quicktemplates/qquickheaderview.cpp b/src/quicktemplates/qquickheaderview.cpp index 0d9bc5588c..dedf3a23e1 100644 --- a/src/quicktemplates/qquickheaderview.cpp +++ b/src/quicktemplates/qquickheaderview.cpp @@ -11,19 +11,9 @@ \inherits TableView \brief Provides a horizontal header view to accompany a \l TableView. - A HorizontalHeaderView provides labeling of the columns of a \l TableView. - To add a horizontal header to a TableView, bind the - \l {HorizontalHeaderView::syncView} {syncView} property to the TableView: + \include qquickheaderview.qdocinc {detailed-description} {HorizontalHeaderView} - \snippet qtquickcontrols-headerview-simple.qml horizontal - - The header displays data from the {syncView}'s model by default, but can - also have its own model. If the model is a QAbstractTableModel, then - the header will display the model's horizontal headerData(); otherwise, - the model's data(). - - A HorizontalHeaderView will have - \l {resizableColumns}{TableView::resizableColumns} set to \c true by default. + \sa VerticalHeaderView */ /*! @@ -31,117 +21,61 @@ \inqmlmodule QtQuick.Controls \ingroup qtquickcontrols-containers \inherits TableView - \brief Provides a vertical header view to accompany a \l TableView. - - A VerticalHeaderView provides labeling of the rows of a \l TableView. - To add a vertical header to a TableView, bind the - \l {VerticalHeaderView::syncView} {syncView} property to the TableView: + \brief Offers a vertical header view to accompany a \l TableView. - \snippet qtquickcontrols-headerview-simple.qml vertical + \include qquickheaderview.qdocinc {detailed-description} {VerticalHeaderView} - The header displays data from the {syncView}'s model by default, but can - also have its own model. If the model is a QAbstractTableModel, then - the header will display the model's vertical headerData(); otherwise, - the model's data(). - - A VerticalHeaderView will have - \l {resizableRows}{TableView::resizableRows} set to \c true by default. + \sa HorizontalHeaderView */ /*! - \qmlproperty TableView QtQuick::HorizontalHeaderView::syncView - - This property holds the TableView to synchronize with. + \qmlproperty TableView QtQuick.Controls::HorizontalHeaderView::syncView - Once this property is bound to another TableView, both header and table - will synchronize with regard to column widths, column spacing, and flicking - horizontally. - - If the \l model is not explicitly set, then the header will use the syncView's - model to label the columns. - - \sa model TableView + \include qquickheaderview.qdocinc {syncView} {horizontally} */ /*! - \qmlproperty TableView QtQuick::VerticalHeaderView::syncView - - This property holds the TableView to synchronize with. - - Once this property is bound to another TableView, both header and table - will synchronize with regard to row heights, row spacing, and flicking - vertically. - - If the \l model is not explicitly set, then the header will use the syncView's - model to label the rows. + \qmlproperty TableView QtQuick.Controls::VerticalHeaderView::syncView - \sa model TableView + \include qquickheaderview.qdocinc {syncView} {vertically} */ /*! - \qmlproperty QVariant QtQuick::HorizontalHeaderView::model + \qmlproperty QVariant QtQuick.Controls::HorizontalHeaderView::model - This property holds the model providing data for the horizontal header view. - - When model is not explicitly set, the header will use the syncView's - model once syncView is set. - - If model is a QAbstractTableModel, its horizontal headerData() will - be accessed. - - If model is a QAbstractItemModel other than QAbstractTableModel, model's data() - will be accessed. - - Otherwise, the behavior is same as setting TableView::model. - - \sa TableView {TableView::model} {model} QAbstractTableModel + \include qquickheaderview.qdocinc {model} {horizontal} */ /*! - \qmlproperty QVariant QtQuick::VerticalHeaderView::model - - This property holds the model providing data for the vertical header view. - - When model is not explicitly set, it will be synchronized with syncView's model - once syncView is set. - - If model is a QAbstractTableModel, its vertical headerData() will - be accessed. - - If model is a QAbstractItemModel other than QAbstractTableModel, model's data() - will be accessed. + \qmlproperty QVariant QtQuick.Controls::VerticalHeaderView::model - Otherwise, the behavior is same as setting TableView::model. - - \sa TableView {TableView::model} {model} QAbstractTableModel + \include qquickheaderview.qdocinc {model} {vertical} */ /*! - \qmlproperty QString QtQuick::HorizontalHeaderView::textRole - - This property holds the model role used to display text in each header cell. + \qmlproperty QString QtQuick.Controls::HorizontalHeaderView::textRole - When the model has multiple roles, textRole can be set to determine which - role should be displayed. + \include qquickheaderview.qdocinc {textRole} +*/ - If model is a QAbstractItemModel then it will default to "display"; otherwise - it is empty. +/*! + \qmlproperty QString QtQuick.Controls::VerticalHeaderView::textRole - \sa QAbstractItemModel::roleNames() + \include qquickheaderview.qdocinc {textRole} */ /*! - \qmlproperty QString QtQuick::VerticalHeaderView::textRole - - This property holds the model role used to display text in each header cell. + \qmlproperty bool QtQuick.Controls::HorizontalHeaderView::movableColumns + \since 6.8 - When the model has multiple roles, textRole can be set to determine which - role should be displayed. + \include qquickheaderview.qdocinc {movableColumns} +*/ - If model is a QAbstractItemModel then it will default to "display"; otherwise - it is empty. +/*! + \qmlproperty bool QtQuick.Controls::VerticalHeaderView::movableRows + \since 6.8 - \sa QAbstractItemModel::roleNames() + \include qquickheaderview.qdocinc {movableRows} */ QT_BEGIN_NAMESPACE @@ -234,36 +168,61 @@ void QQuickHeaderViewBasePrivate::syncModel() void QQuickHeaderViewBasePrivate::syncSyncView() { - Q_Q(QQuickHeaderViewBase); if (assignedSyncDirection != orientation()) { qmlWarning(q_func()) << "Setting syncDirection other than Qt::" << QVariant::fromValue(orientation()).toString() << " is invalid."; assignedSyncDirection = orientation(); } - if (assignedSyncView) { - QBoolBlocker fixupGuard(inUpdateContentSize, true); - if (orientation() == Qt::Horizontal) { - q->setLeftMargin(assignedSyncView->leftMargin()); - q->setRightMargin(assignedSyncView->rightMargin()); - } else { - q->setTopMargin(assignedSyncView->topMargin()); - q->setBottomMargin(assignedSyncView->bottomMargin()); - } - } QQuickTableViewPrivate::syncSyncView(); } +QAbstractItemModel *QQuickHeaderViewBasePrivate::selectionSourceModel() +{ + // Our proxy model shares no common model items with HeaderView.model. So + // selections done in HeaderView cannot be represented in an ItemSelectionModel + // that is shared with the syncView (and for the same reason, the mapping functions + // modelIndex(cell) and cellAtIndex(index) have not been overridden either). + // Instead, we set the internal proxy model as selection source model. + return &m_headerDataProxyModel; +} + +int QQuickHeaderViewBasePrivate::logicalRowIndex(const int visualIndex) const +{ + return (m_headerDataProxyModel.orientation() == Qt::Horizontal) ? visualIndex : QQuickTableViewPrivate::logicalRowIndex(visualIndex); +} + +int QQuickHeaderViewBasePrivate::logicalColumnIndex(const int visualIndex) const +{ + return (m_headerDataProxyModel.orientation() == Qt::Vertical) ? visualIndex : QQuickTableViewPrivate::logicalColumnIndex(visualIndex); +} + +int QQuickHeaderViewBasePrivate::visualRowIndex(const int logicalIndex) const +{ + return (m_headerDataProxyModel.orientation() == Qt::Horizontal) ? logicalIndex : QQuickTableViewPrivate::visualRowIndex(logicalIndex); +} + +int QQuickHeaderViewBasePrivate::visualColumnIndex(const int logicalIndex) const +{ + return (m_headerDataProxyModel.orientation() == Qt::Vertical) ? logicalIndex : QQuickTableViewPrivate::visualColumnIndex(logicalIndex); +} + QQuickHeaderViewBase::QQuickHeaderViewBase(Qt::Orientation orient, QQuickItem *parent) : QQuickTableView(*(new QQuickHeaderViewBasePrivate), parent) { - d_func()->setOrientation(orient); + Q_D(QQuickHeaderViewBase); + d->m_headerDataProxyModel.m_headerView = this; + d->setSizePolicy(orient == Qt::Horizontal ? QLayoutPolicy::Preferred : QLayoutPolicy::Fixed, + orient == Qt::Horizontal ? QLayoutPolicy::Fixed : QLayoutPolicy::Preferred); + d->setOrientation(orient); setSyncDirection(orient); } QQuickHeaderViewBase::QQuickHeaderViewBase(QQuickHeaderViewBasePrivate &dd, QQuickItem *parent) : QQuickTableView(dd, parent) { + Q_D(QQuickHeaderViewBase); + d->m_headerDataProxyModel.m_headerView = this; } QQuickHeaderViewBase::~QQuickHeaderViewBase() @@ -384,6 +343,24 @@ bool QHeaderDataProxyModel::hasChildren(const QModelIndex &parent) const return false; } +QHash<int, QByteArray> QHeaderDataProxyModel::roleNames() const +{ + using namespace Qt::Literals::StringLiterals; + + auto names = m_model ? m_model->roleNames() : QAbstractItemModel::roleNames(); + if (m_headerView) { + QString textRole = m_headerView->textRole(); + if (textRole.isEmpty()) + textRole = u"display"_s; + if (!names.values().contains(textRole.toUtf8().constData())) { + qmlWarning(m_headerView).nospace() << "The 'textRole' property contains a role that doesn't exist in the model: " + << textRole << ". Check your model's roleNames() implementation"; + } + } + + return names; +} + QVariant QHeaderDataProxyModel::variantValue() const { return QVariant::fromValue(static_cast<QObject *>(const_cast<QHeaderDataProxyModel *>(this))); @@ -472,6 +449,30 @@ QQuickHorizontalHeaderView::QQuickHorizontalHeaderView(QQuickItem *parent) QQuickHorizontalHeaderView::~QQuickHorizontalHeaderView() { + Q_D(QQuickHorizontalHeaderView); + d->destroySectionDragHandler(); +} + +bool QQuickHorizontalHeaderView::movableColumns() const +{ + Q_D(const QQuickHorizontalHeaderView); + return d->m_movableColumns; +} + +void QQuickHorizontalHeaderView::setMovableColumns(bool movableColumns) +{ + Q_D(QQuickHorizontalHeaderView); + if (d->m_movableColumns == movableColumns) + return; + + d->m_movableColumns = movableColumns; + + if (d->m_movableColumns) + d->initSectionDragHandler(Qt::Horizontal); + else + d->destroySectionDragHandler(); + + emit movableColumnsChanged(); } QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickItem *parent) @@ -483,6 +484,30 @@ QQuickVerticalHeaderView::QQuickVerticalHeaderView(QQuickItem *parent) QQuickVerticalHeaderView::~QQuickVerticalHeaderView() { + Q_D(QQuickVerticalHeaderView); + d->destroySectionDragHandler(); +} + +bool QQuickVerticalHeaderView::movableRows() const +{ + Q_D(const QQuickVerticalHeaderView); + return d->m_movableRows ; +} + +void QQuickVerticalHeaderView::setMovableRows(bool movableRows) +{ + Q_D(QQuickVerticalHeaderView); + if (d->m_movableRows == movableRows) + return; + + d->m_movableRows = movableRows; + + if (d->m_movableRows) + d->initSectionDragHandler(Qt::Vertical); + else + d->destroySectionDragHandler(); + + emit movableRowsChanged(); } QQuickHorizontalHeaderViewPrivate::QQuickHorizontalHeaderViewPrivate() = default; diff --git a/src/quicktemplates/qquickheaderview_p.h b/src/quicktemplates/qquickheaderview_p.h index 8e4ea10a57..ba123a3c3e 100644 --- a/src/quicktemplates/qquickheaderview_p.h +++ b/src/quicktemplates/qquickheaderview_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QQuickHeaderViewBase; class QQuickHeaderViewBasePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickHeaderViewBase : public QQuickTableView +class Q_QUICKTEMPLATES2_EXPORT QQuickHeaderViewBase : public QQuickTableView { Q_OBJECT Q_DECLARE_PRIVATE(QQuickHeaderViewBase) @@ -48,10 +48,11 @@ private: }; class QQuickHorizontalHeaderViewPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickHorizontalHeaderView : public QQuickHeaderViewBase +class Q_QUICKTEMPLATES2_EXPORT QQuickHorizontalHeaderView : public QQuickHeaderViewBase { Q_OBJECT Q_DECLARE_PRIVATE(QQuickHorizontalHeaderView) + Q_PROPERTY(bool movableColumns READ movableColumns WRITE setMovableColumns NOTIFY movableColumnsChanged REVISION(6, 8) FINAL) QML_NAMED_ELEMENT(HorizontalHeaderView) QML_ADDED_IN_VERSION(2, 15) @@ -59,6 +60,12 @@ public: QQuickHorizontalHeaderView(QQuickItem *parent = nullptr); ~QQuickHorizontalHeaderView() override; + bool movableColumns() const; + void setMovableColumns(bool movableColumns); + +Q_SIGNALS: + Q_REVISION(6, 8) void movableColumnsChanged(); + protected: QQuickHorizontalHeaderView(QQuickHorizontalHeaderViewPrivate &dd, QQuickItem *parent); @@ -67,10 +74,11 @@ private: }; class QQuickVerticalHeaderViewPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickVerticalHeaderView : public QQuickHeaderViewBase +class Q_QUICKTEMPLATES2_EXPORT QQuickVerticalHeaderView : public QQuickHeaderViewBase { Q_OBJECT Q_DECLARE_PRIVATE(QQuickVerticalHeaderView) + Q_PROPERTY(bool movableRows READ movableRows WRITE setMovableRows NOTIFY movableRowsChanged REVISION(6, 8) FINAL) QML_NAMED_ELEMENT(VerticalHeaderView) QML_ADDED_IN_VERSION(2, 15) @@ -78,6 +86,12 @@ public: QQuickVerticalHeaderView(QQuickItem *parent = nullptr); ~QQuickVerticalHeaderView() override; + bool movableRows() const; + void setMovableRows(bool movableRows); + +Q_SIGNALS: + Q_REVISION(6, 8) void movableRowsChanged(); + protected: QQuickVerticalHeaderView(QQuickVerticalHeaderViewPrivate &dd, QQuickItem *parent); @@ -85,17 +99,6 @@ private: Q_DISABLE_COPY(QQuickVerticalHeaderView) }; -struct QQuickTableViewForeign -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QQuickTableView) - QML_ADDED_IN_VERSION(2, 14) -}; - QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickHorizontalHeaderView) -QML_DECLARE_TYPE(QQuickVerticalHeaderView) - #endif // QQUICKHEADERVIEW_P_H diff --git a/src/quicktemplates/qquickheaderview_p_p.h b/src/quicktemplates/qquickheaderview_p_p.h index fe33cb0190..63abd58aa3 100644 --- a/src/quicktemplates/qquickheaderview_p_p.h +++ b/src/quicktemplates/qquickheaderview_p_p.h @@ -25,7 +25,7 @@ QT_BEGIN_NAMESPACE -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QHeaderDataProxyModel : public QAbstractItemModel +class Q_QUICKTEMPLATES2_EXPORT QHeaderDataProxyModel : public QAbstractItemModel { Q_OBJECT Q_DISABLE_COPY(QHeaderDataProxyModel) @@ -44,11 +44,14 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + QHash<int, QByteArray> roleNames() const override; inline QVariant variantValue() const; inline Qt::Orientation orientation() const; inline void setOrientation(Qt::Orientation o); + QQuickHeaderViewBase *m_headerView = nullptr; + private: inline void connectToModel(); inline void disconnectFromModel(); @@ -70,6 +73,7 @@ public: void setModelImpl(const QVariant &newModel) override; void syncModel() override; void syncSyncView() override; + QAbstractItemModel *selectionSourceModel() override; protected: QHeaderDataProxyModel m_headerDataProxyModel; @@ -84,6 +88,11 @@ protected: QStack<SectionSize> m_hiddenSectionSizes; bool m_modelExplicitlySetByUser = false; QString m_textRole; + + int logicalRowIndex(const int visualIndex) const final; + int logicalColumnIndex(const int visualIndex) const final; + int visualRowIndex(const int logicalIndex) const final; + int visualColumnIndex(const int logicalIndex) const final; }; class QQuickHorizontalHeaderViewPrivate : public QQuickHeaderViewBasePrivate @@ -92,6 +101,8 @@ class QQuickHorizontalHeaderViewPrivate : public QQuickHeaderViewBasePrivate public: QQuickHorizontalHeaderViewPrivate(); ~QQuickHorizontalHeaderViewPrivate(); + + bool m_movableColumns = false; }; class QQuickVerticalHeaderViewPrivate : public QQuickHeaderViewBasePrivate @@ -100,6 +111,8 @@ class QQuickVerticalHeaderViewPrivate : public QQuickHeaderViewBasePrivate public: QQuickVerticalHeaderViewPrivate(); ~QQuickVerticalHeaderViewPrivate(); + + bool m_movableRows = false; }; QT_END_NAMESPACE diff --git a/src/quicktemplates/qquickicon_p.h b/src/quicktemplates/qquickicon_p.h index 737b28ad89..5f513ea7d3 100644 --- a/src/quicktemplates/qquickicon_p.h +++ b/src/quicktemplates/qquickicon_p.h @@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE class QQuickIconPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickIcon +class Q_QUICKTEMPLATES2_EXPORT QQuickIcon { Q_GADGET Q_PROPERTY(QString name READ name WRITE setName RESET resetName FINAL) diff --git a/src/quicktemplates/qquickindicatorbutton_p.cpp b/src/quicktemplates/qquickindicatorbutton_p.cpp index 1d95a9e631..22c13b9e4e 100644 --- a/src/quicktemplates/qquickindicatorbutton_p.cpp +++ b/src/quicktemplates/qquickindicatorbutton_p.cpp @@ -8,8 +8,6 @@ QT_BEGIN_NAMESPACE class QQuickIndicatorButton; -static inline QString indicatorName() { return QStringLiteral("indicator"); } - void QQuickIndicatorButtonPrivate::cancelIndicator() { Q_Q(QQuickIndicatorButton); @@ -33,6 +31,14 @@ QQuickIndicatorButton::QQuickIndicatorButton(QObject *parent) { } +QQuickIndicatorButton::~QQuickIndicatorButton() +{ + Q_D(QQuickIndicatorButton); + QQuickControl *parentControl = static_cast<QQuickControl *>(parent()); + if (parentControl) + QQuickControlPrivate::get(parentControl)->removeImplicitSizeListener(d->indicator); +} + bool QQuickIndicatorButton::isPressed() const { Q_D(const QQuickIndicatorButton); diff --git a/src/quicktemplates/qquickindicatorbutton_p.h b/src/quicktemplates/qquickindicatorbutton_p.h index be5259211d..98b08a6030 100644 --- a/src/quicktemplates/qquickindicatorbutton_p.h +++ b/src/quicktemplates/qquickindicatorbutton_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QQuickIndicatorButtonPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickIndicatorButton : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickIndicatorButton : public QObject { Q_OBJECT Q_PROPERTY(bool pressed READ isPressed WRITE setPressed NOTIFY pressedChanged FINAL) @@ -38,6 +38,7 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickIndicatorButton : public QObject public: explicit QQuickIndicatorButton(QObject *parent); + ~QQuickIndicatorButton() override; bool isPressed() const; void setPressed(bool pressed); diff --git a/src/quicktemplates/qquickitemdelegate.cpp b/src/quicktemplates/qquickitemdelegate.cpp index 7b0a1c12ad..d4abad9ce2 100644 --- a/src/quicktemplates/qquickitemdelegate.cpp +++ b/src/quicktemplates/qquickitemdelegate.cpp @@ -58,8 +58,11 @@ QQuickItemDelegate::QQuickItemDelegate(QQuickItemDelegatePrivate &dd, QQuickItem id: listView model: 10 delegate: ItemDelegate { - text: modelData + text: index highlighted: ListView.isCurrentItem + + required property int index + onClicked: listView.currentIndex = index } } diff --git a/src/quicktemplates/qquickitemdelegate_p.h b/src/quicktemplates/qquickitemdelegate_p.h index 4716edc849..8236410fb3 100644 --- a/src/quicktemplates/qquickitemdelegate_p.h +++ b/src/quicktemplates/qquickitemdelegate_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickItemDelegatePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickItemDelegate : public QQuickAbstractButton +class Q_QUICKTEMPLATES2_EXPORT QQuickItemDelegate : public QQuickAbstractButton { Q_OBJECT Q_PROPERTY(bool highlighted READ isHighlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL) @@ -54,6 +54,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickItemDelegate) - #endif // QQUICKITEMDELEGATE_P_H diff --git a/src/quicktemplates/qquickitemdelegate_p_p.h b/src/quicktemplates/qquickitemdelegate_p_p.h index f1af3daf94..cf223e6b8b 100644 --- a/src/quicktemplates/qquickitemdelegate_p_p.h +++ b/src/quicktemplates/qquickitemdelegate_p_p.h @@ -19,7 +19,7 @@ QT_BEGIN_NAMESPACE -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickItemDelegatePrivate : public QQuickAbstractButtonPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickItemDelegatePrivate : public QQuickAbstractButtonPrivate { Q_DECLARE_PUBLIC(QQuickItemDelegate) diff --git a/src/quicktemplates/qquicklabel.cpp b/src/quicktemplates/qquicklabel.cpp index 116884a46f..ee8723d755 100644 --- a/src/quicktemplates/qquicklabel.cpp +++ b/src/quicktemplates/qquicklabel.cpp @@ -190,7 +190,7 @@ void QQuickLabelPrivate::accessibilityActiveChanged(bool active) Q_Q(QQuickLabel); QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true)); Q_ASSERT(accessibleAttached); - accessibleAttached->setRole(accessibleRole()); + accessibleAttached->setRole(effectiveAccessibleRole()); maybeSetAccessibleName(text); } @@ -211,8 +211,6 @@ void QQuickLabelPrivate::maybeSetAccessibleName(const QString &name) } #endif -static inline QString backgroundName() { return QStringLiteral("background"); } - void QQuickLabelPrivate::cancelBackground() { Q_Q(QQuickLabel); diff --git a/src/quicktemplates/qquicklabel_p.h b/src/quicktemplates/qquicklabel_p.h index 5e564baad7..bef65ee8ef 100644 --- a/src/quicktemplates/qquicklabel_p.h +++ b/src/quicktemplates/qquicklabel_p.h @@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE class QQuickLabelPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickLabel : public QQuickText +class Q_QUICKTEMPLATES2_EXPORT QQuickLabel : public QQuickText { Q_OBJECT Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) // override @@ -93,16 +93,6 @@ private: Q_DECLARE_PRIVATE(QQuickLabel) }; -struct QQuickTemplatesTextForeign -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QQuickText) - QML_ADDED_IN_VERSION(2, 3) -}; - QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickLabel) - #endif // QQUICKLABEL_P_H diff --git a/src/quicktemplates/qquickmenu.cpp b/src/quicktemplates/qquickmenu.cpp index ea37af1885..9580dea2c0 100644 --- a/src/quicktemplates/qquickmenu.cpp +++ b/src/quicktemplates/qquickmenu.cpp @@ -4,18 +4,26 @@ #include "qquickmenu_p.h" #include "qquickmenu_p_p.h" #include "qquickmenuitem_p_p.h" +#include <private/qtquicktemplates2-config_p.h> +#if QT_CONFIG(quicktemplates2_container) #include "qquickmenubaritem_p.h" -#include "qquickmenubar_p.h" +#include "qquickmenubar_p_p.h" +#endif +#include "qquickmenuseparator_p.h" +#include "qquicknativemenuitem_p.h" #include "qquickpopupitem_p_p.h" #include "qquickpopuppositioner_p_p.h" #include "qquickaction_p.h" +#include <QtCore/qloggingcategory.h> #include <QtGui/qevent.h> #include <QtGui/qcursor.h> #if QT_CONFIG(shortcut) #include <QtGui/qkeysequence.h> #endif #include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/qpa/qplatformtheme.h> +#include <QtGui/private/qhighdpiscaling_p.h> #include <QtGui/private/qguiapplication_p.h> #include <QtQml/qqmlcontext.h> #include <QtQml/qqmlcomponent.h> @@ -26,12 +34,16 @@ #include <private/qqmlobjectmodel_p.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickitemchangelistener_p.h> -#include <QtQuick/private/qquickitemview_p.h> #include <QtQuick/private/qquickevents_p_p.h> +#include <QtQuick/private/qquicklistview_p.h> +#include <QtQuick/private/qquickrendercontrol_p.h> #include <QtQuick/private/qquickwindow_p.h> QT_BEGIN_NAMESPACE +Q_STATIC_LOGGING_CATEGORY(lcMenu, "qt.quick.controls.menu") +Q_STATIC_LOGGING_CATEGORY(lcNativeMenus, "qt.quick.controls.nativemenus") + // copied from qfusionstyle.cpp static const int SUBMENU_DELAY = 225; @@ -45,7 +57,13 @@ static const int SUBMENU_DELAY = 225; \ingroup qtquickcontrols-popups \brief Menu popup that can be used as a context menu or popup menu. - \image qtquickcontrols-menu.png + \table + \row + \li \image qtquickcontrols-menu-native.png + \caption Native macOS menu. + \li \image qtquickcontrols-menu.png + \caption Non-native \l {Material Style}{Material style} menu. + \endtable Menu has two main use cases: \list @@ -107,6 +125,16 @@ static const int SUBMENU_DELAY = 225; } \endcode + If the button should also close the menu when clicked, use the + \c Popup.CloseOnPressOutsideParent flag: + \code + onClicked: menu.visible = !menu.visible + + Menu { + // ... + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent + \endcode + Since QtQuick.Controls 2.3 (Qt 5.10), it is also possible to create sub-menus and declare Action objects inside Menu: @@ -172,6 +200,82 @@ static const int SUBMENU_DELAY = 225; \sa {Customizing Menu}, MenuItem, {Menu Controls}, {Popup Controls}, {Dynamic QML Object Creation from JavaScript} + + \section1 Menu types + + Since Qt 6.8, a menu offers three different implementations, depending on the + platform. You can choose which one should be preferred by setting + \l popupType. This will let you control if a menu should be shown as a separate + window, as an item inside the parent window, or as a native menu. You can read + more about these options \l{Popup type}{here.} + + Whether a menu will be able to use the preferred type depends on the platform. + \c Popup.Item is supported on all platforms, but \c Popup.Window and \c Popup.Native + are normally only supported on desktop platforms. Additionally, if the menu is inside + a \l {Native menu bars}{native menubar}, the menu will be native as well. And if + the menu is a sub-menu inside another menu, the parent (or root) menu will decide the type. + + \section2 Limitations when using native menus + + When setting \l popupType to \c Popup.Native, there are some limitations and + differences compared to using \c Popup.Item and \c Popup.Window. + + \section3 API differences + + When using native menus, only a subset of the Menu API is supported on all platforms: + + \list + \li \l {Popup::}{x} + \li \l {Popup::}{y} + \li \l {Popup::}{visible} + \li \l {Popup::}{opened} + \li \l title + \li \l count + \li \l {Popup::}{contentData} + \li \l {Popup::}{contentChildren} (visual children will not be visible) + \li \l contentModel + \li \l {Popup::}{open()} + \li \l {Popup::}{popup()} + \li \l {Popup::}{close()} + \li \l {Popup::}{opened()} + \li \l {Popup::}{closed()} + \li \l {Popup::}{aboutToShow()} + \li \l {Popup::}{aboutToHide()} + \endlist + + In addition, showing a popup (using for example \l {Popup::}{open()} or \l {Popup::}{popup()} + will, on some platforms, be a blocking call. This means that the call will not return + before the menu is closed again, which can affect the logic in your application. This is + especially important to take into consideration if your application is targeting multiple + platforms, and as such, sometimes run on platforms where native menus are not supported. + In that case the popupType will fall back to \c Popup.Item, for example, and calls to + \l {Popup::}{open()} will not be blocking. + + Items like \l MenuItem will still react to clicks in the corresponding + native menu item by emitting signals, for example, but will be replaced by + their native counterpart. + + \section3 Rendering differences + + Native menus are implemented using the available native menu APIs on the platform. + Those menus, and all of their contents, will therefore be rendered by the platform, and + not by QML. This means that the \l delegate will \e not be used for rendering. It will, + however, always be instantiated (but hidden), so that functions such as + \l {Component.onCompleted()} execute regardless of platform and \l popupType. + + \section3 Supported platforms + + Native menus are currently supported on the following platforms: + + \list + \li Android + \li iOS + \li Linux (only available as a stand-alone context menu when running with the GTK+ platform theme) + \li macOS + \li Windows + \endlist + + \sa {Popup type}, popupType */ /*! @@ -185,6 +289,8 @@ static const int SUBMENU_DELAY = 225; The default value is \c true. + \include qquickmenu.qdocinc non-native-only-property + \sa {Popup::}{activeFocus} */ @@ -218,6 +324,282 @@ void QQuickMenuPrivate::init() contentModel = new QQmlObjectModel(q); } +QQuickMenu *QQuickMenuPrivate::rootMenu() const +{ + Q_Q(const QQuickMenu); + const QQuickMenu *rootMenu = q; + const QObject *p = q->parent(); + while (p) { + if (auto menu = qobject_cast<const QQuickMenu *>(p)) + rootMenu = menu; + p = p->parent(); + } + + return const_cast<QQuickMenu *>(rootMenu); +} + + QQuickPopup::PopupType QQuickMenuPrivate::resolvedPopupType() const +{ + // The resolved popup type is decided by the root + // menu (which can be this menu, unless it's a child menu). + QQuickMenuPrivate *root_d = QQuickMenuPrivate::get(rootMenu()); + + // If the root menu is native, then so should we. We assume here that + // the root menu is always shown and created first, before we try to + // show and create a child menu. + if (root_d->maybeNativeHandle()) + return QQuickPopup::PopupType::Native; + + return root_d->QQuickPopupPrivate::resolvedPopupType(); +} + +bool QQuickMenuPrivate::useNativeMenu() const +{ + // If we're inside a MenuBar, it'll decide whether or not we + // should be native or not. Otherwise, the root menu (which + // might be this menu) will decide. + QQuickMenu *root = rootMenu(); + if (auto menuBar = QQuickMenuPrivate::get(root)->menuBar.get()) + return QQuickMenuBarPrivate::get(menuBar)->useNativeMenu(q_func()); + return root->popupType() == QQuickPopup::Native; +} + +QPlatformMenu *QQuickMenuPrivate::nativeHandle() +{ + Q_ASSERT(handle || useNativeMenu()); + if (!handle && !triedToCreateNativeMenu) + createNativeMenu(); + return handle.get(); +} + +QPlatformMenu *QQuickMenuPrivate::maybeNativeHandle() const +{ + return handle.get(); +} + +bool QQuickMenuPrivate::createNativeMenu() +{ + Q_ASSERT(!handle); + Q_Q(QQuickMenu); + qCDebug(lcNativeMenus) << "createNativeMenu called on" << q; + + if (auto menuBar = QQuickMenuPrivate::get(rootMenu())->menuBar) { + auto menuBarPrivate = QQuickMenuBarPrivate::get(menuBar); + if (menuBarPrivate->useNativeMenuBar()) { + qCDebug(lcNativeMenus) << "- creating native menu from native menubar"; + if (QPlatformMenuBar *menuBarHandle = menuBarPrivate->nativeHandle()) + handle.reset(menuBarHandle->createMenu()); + } + } + + if (!handle) { + QPlatformMenu *parentMenuHandle(parentMenu ? get(parentMenu)->handle.get() : nullptr); + if (parentMenu && parentMenuHandle) { + qCDebug(lcNativeMenus) << "- creating native sub-menu"; + handle.reset(parentMenuHandle->createSubMenu()); + } else { + qCDebug(lcNativeMenus) << "- creating native menu"; + handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformMenu()); + } + } + + triedToCreateNativeMenu = true; + + if (!handle) + return false; + + q->connect(handle.get(), &QPlatformMenu::aboutToShow, q, [q, this](){ + emit q->aboutToShow(); + visible = true; + emit q->visibleChanged(); + emit q->openedChanged(); + opened(); + }); + q->connect(handle.get(), &QPlatformMenu::aboutToHide, q, [q, this](){ + qCDebug(lcNativeMenus) << "QPlatformMenu::aboutToHide called; about to call setVisible(false) on Menu"; + emit q->aboutToHide(); + visible = false; + emit q->visibleChanged(); + emit q->openedChanged(); + emit q->closed(); + }); + + recursivelyCreateNativeMenuItems(q); + syncWithNativeMenu(); + + return true; +} + +QString nativeMenuItemListToString(const QList<QQuickNativeMenuItem *> &nativeItems) +{ + if (nativeItems.isEmpty()) + return QStringLiteral("(Empty)"); + + QString str; + QTextStream debug(&str); + for (const auto *nativeItem : nativeItems) + debug << nativeItem->debugText() << ", "; + // Remove trailing space and comma. + if (!nativeItems.isEmpty()) + str.chop(2); + return str; +} + +void QQuickMenuPrivate::syncWithNativeMenu() +{ + Q_Q(QQuickMenu); + if (!complete || !handle) + return; + + qCDebug(lcNativeMenus).nospace() << "syncWithNativeMenu called on " << q + << " (complete: " << complete << " visible: " << visible << ") - " + << "syncing " << nativeItems.size() << " item(s)..."; + + // TODO: call this function when any of the variables below change + + handle->setText(title); + handle->setEnabled(q->isEnabled()); + handle->setMinimumWidth(q->implicitWidth()); +// nativeHandle->setMenuType(m_type); + handle->setFont(q->font()); + + // Note: the QQuickMenu::visible property is used to open or close the menu. + // This is in contrast to QPlatformMenu::visible, which tells if the menu + // should be visible in the menubar or not (if it belongs to one). To control + // if a QPlatformMenu should be open, we instead use QPlatformMenu::showPopup() + // and dismiss(). As such, we don't want to call handle->setVisible(visible) + // from this function since we always want the menu to be visible in the menubar + // (if it belongs to one). The currently only way to hide a menu from a menubar is + // to instead call MenuBar.removeMenu(menu). + +// if (m_menuBar && m_menuBar->handle()) +// m_menuBar->handle()->syncMenu(handle); +//#if QT_CONFIG(systemtrayicon) +// else if (m_systemTrayIcon && m_systemTrayIcon->handle()) +// m_systemTrayIcon->handle()->updateMenu(handle); +//#endif + + for (QQuickNativeMenuItem *item : std::as_const(nativeItems)) { + qCDebug(lcNativeMenus) << "- syncing" << item << "action" << item->action() + << "sub-menu" << item->subMenu() << item->debugText(); + item->sync(); + } + + qCDebug(lcNativeMenus) << "... finished syncing" << q; +} + +void QQuickMenuPrivate::removeNativeMenu() +{ + // Remove the native menu, including it's native menu items + Q_Q(QQuickMenu); + const int qtyItemsToRemove = nativeItems.size(); + if (qtyItemsToRemove != 0) + Q_ASSERT(q->count() == qtyItemsToRemove); + for (int i = 0; i < qtyItemsToRemove; ++i) + removeNativeItem(0); + Q_ASSERT(nativeItems.isEmpty()); + + // removeNativeItem will take care of destroying sub-menus and resetting their native data, + // but as the root menu, we have to take care of our own. + resetNativeData(); +} + +void QQuickMenuPrivate::syncWithUseNativeMenu() +{ + Q_Q(QQuickMenu); + // Users can change AA_DontUseNativeMenuWindows while a menu is visible, + // but the changes won't take affect until the menu is re-opened. + if (q->isVisible() || parentMenu) + return; + + if (maybeNativeHandle() && !useNativeMenu()) { + // Switch to a non-native menu by removing the native menu and its native items. + // Note that there's nothing to do if a native menu was requested but we failed to create it. + removeNativeMenu(); + } else if (useNativeMenu()) { + Q_ASSERT(nativeItems.isEmpty()); + // Try to create a native menu. + nativeHandle(); + } +} + +/*! + \internal + + Recursively destroys native sub-menus of \a menu. + + This function checks if each native item in \c menu has a sub-menu, + and if so: + \list + \li Calls itself with that sub-menu + \li Resets the item's data (important to avoid accessing a deleted QQuickAction + when printing in QQuickNativeMenuItem's destructor) + \li Deletes (eventually) the native item + \endlist + + Similar (besides the recursion) to removeNativeItem(), except that + we can avoid repeated calls to syncWithNativeMenu(). +*/ +void QQuickMenuPrivate::recursivelyDestroyNativeSubMenus(QQuickMenu *menu) +{ + auto *menuPrivate = QQuickMenuPrivate::get(menu); + Q_ASSERT(menuPrivate->handle); + qCDebug(lcNativeMenus) << "recursivelyDestroyNativeSubMenus called with" << menu << "..."; + + while (!menuPrivate->nativeItems.isEmpty()) { + std::unique_ptr<QQuickNativeMenuItem> item(menuPrivate->nativeItems.takeFirst()); + qCDebug(lcNativeMenus) << "- taking and destroying" << item->debugText(); + if (QQuickMenu *subMenu = item->subMenu()) + recursivelyDestroyNativeSubMenus(subMenu); + + if (item->handle()) + menuPrivate->handle->removeMenuItem(item->handle()); + } + + menuPrivate->resetNativeData(); + + qCDebug(lcNativeMenus) << "... finished destroying native sub-menus of" << menu; +} + +static QWindow *effectiveWindow(QWindow *window, QPoint *offset) +{ + QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(window); + if (quickWindow) { + QWindow *renderWindow = QQuickRenderControl::renderWindowFor(quickWindow, offset); + if (renderWindow) + return renderWindow; + } + return window; +} + +void QQuickMenuPrivate::setNativeMenuVisible(bool visible) +{ + Q_Q(QQuickMenu); + qCDebug(lcNativeMenus) << "setNativeMenuVisible called with visible" << visible; + if (visible) + emit q->aboutToShow(); + else + emit q->aboutToHide(); + + this->visible = visible; + syncWithNativeMenu(); + + QPoint offset; + QWindow *window = effectiveWindow(qGuiApp->topLevelWindows().first(), &offset); + + if (visible) { + lastDevicePixelRatio = window->devicePixelRatio(); + + const QPointF globalPos = parentItem->mapToGlobal(x, y); + const QPoint windowPos = window->mapFromGlobal(globalPos.toPoint()); + QRect targetRect(windowPos, QSize(0, 0)); + handle->showPopup(window, QHighDpi::toNativePixels(targetRect, window), + /*menuItem ? menuItem->handle() : */nullptr); + } else { + handle->dismiss(); + } +} + QQuickItem *QQuickMenuPrivate::itemAt(int index) const { return qobject_cast<QQuickItem *>(contentModel->get(index)); @@ -225,6 +607,9 @@ QQuickItem *QQuickMenuPrivate::itemAt(int index) const void QQuickMenuPrivate::insertItem(int index, QQuickItem *item) { + qCDebug(lcMenu) << "insert called with index" << index << "item" << item; + + Q_Q(QQuickMenu); contentData.append(item); item->setParentItem(contentItem); QQuickItemPrivate::get(item)->setCulled(true); // QTBUG-53262 @@ -236,23 +621,90 @@ void QQuickMenuPrivate::insertItem(int index, QQuickItem *item) QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(item); if (menuItem) { - Q_Q(QQuickMenu); QQuickMenuItemPrivate::get(menuItem)->setMenu(q); if (QQuickMenu *subMenu = menuItem->subMenu()) QQuickMenuPrivate::get(subMenu)->setParentMenu(q); QObjectPrivate::connect(menuItem, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered); + QObjectPrivate::connect(menuItem, &QQuickMenuItem::implicitTextPaddingChanged, this, &QQuickMenuPrivate::updateTextPadding); + QObjectPrivate::connect(menuItem, &QQuickMenuItem::visibleChanged, this, &QQuickMenuPrivate::updateTextPadding); QObjectPrivate::connect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged); QObjectPrivate::connect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered); } + + if (maybeNativeHandle() && complete) + maybeCreateAndInsertNativeItem(index, item); + + if (lcMenu().isDebugEnabled()) + printContentModelItems(); + + updateTextPadding(); +} + +void QQuickMenuPrivate::maybeCreateAndInsertNativeItem(int index, QQuickItem *item) +{ + Q_Q(QQuickMenu); + Q_ASSERT(complete); + Q_ASSERT_X(handle, Q_FUNC_INFO, qPrintable(QString::fromLatin1( + "Expected %1 to be using a native menu").arg(QDebug::toString(q)))); + std::unique_ptr<QQuickNativeMenuItem> nativeMenuItem(QQuickNativeMenuItem::createFromNonNativeItem(q, item)); + if (!nativeMenuItem) { + // TODO: fall back to non-native menu + qmlWarning(q) << "Native menu failed to create a native menu item for item at index" << index; + return; + } + + nativeItems.insert(index, nativeMenuItem.get()); + + // Having a QQuickNativeMenuItem doesn't mean that we were able to create a native handle: + // it could be e.g. a Rectangle. See comment in QQuickNativeMenuItem::createFromNonNativeItem. + if (nativeMenuItem->handle()) { + QQuickNativeMenuItem *before = nativeItems.value(index + 1); + handle->insertMenuItem(nativeMenuItem->handle(), before ? before->handle() : nullptr); + qCDebug(lcNativeMenus) << "inserted native menu item at index" << index + << "before" << (before ? before->debugText() : QStringLiteral("null")); + + if (nativeMenuItem->subMenu() && QQuickMenuPrivate::get(nativeMenuItem->subMenu())->nativeItems.count() + < nativeMenuItem->subMenu()->count()) { + // We're inserting a sub-menu item, and it hasn't had native items added yet, + // which probably means it's a menu that's been added back in after being removed + // with takeMenu(). Sub-menus added for the first time have their native items already + // constructed by virtue of contentData_append. Sub-menus that are removed always + // have their native items destroyed and removed too. + recursivelyCreateNativeMenuItems(nativeMenuItem->subMenu()); + } + } + + nativeMenuItem.release(); + + qCDebug(lcNativeMenus) << "nativeItems now contains the following items:" + << nativeMenuItemListToString(nativeItems); } void QQuickMenuPrivate::moveItem(int from, int to) { contentModel->move(from, to); + + if (maybeNativeHandle()) + nativeItems.move(from, to); } -void QQuickMenuPrivate::removeItem(int index, QQuickItem *item) +/*! + \internal + + Removes the specified \a item, potentially destroying it depending on + \a destructionPolicy. + + \note the native menu item is destroyed regardless of the destruction + policy, because it's an implementation detail and hence is not created by + or available to the user. +*/ +void QQuickMenuPrivate::removeItem(int index, QQuickItem *item, DestructionPolicy destructionPolicy) { + qCDebug(lcMenu) << "removeItem called with index" << index << "item" << item; + + if (maybeNativeHandle()) + removeNativeItem(index); + contentData.removeOne(item); QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed | QQuickItemPrivate::Parent); @@ -266,9 +718,79 @@ void QQuickMenuPrivate::removeItem(int index, QQuickItem *item) if (QQuickMenu *subMenu = menuItem->subMenu()) QQuickMenuPrivate::get(subMenu)->setParentMenu(nullptr); QObjectPrivate::disconnect(menuItem, &QQuickMenuItem::triggered, this, &QQuickMenuPrivate::onItemTriggered); + QObjectPrivate::disconnect(menuItem, &QQuickMenuItem::implicitTextPaddingChanged, this, &QQuickMenuPrivate::updateTextPadding); + QObjectPrivate::disconnect(menuItem, &QQuickMenuItem::visibleChanged, this, &QQuickMenuPrivate::updateTextPadding); QObjectPrivate::disconnect(menuItem, &QQuickItem::activeFocusChanged, this, &QQuickMenuPrivate::onItemActiveFocusChanged); QObjectPrivate::disconnect(menuItem, &QQuickControl::hoveredChanged, this, &QQuickMenuPrivate::onItemHovered); } + + if (destructionPolicy == DestructionPolicy::Destroy) + item->deleteLater(); + + if (lcMenu().isDebugEnabled()) + printContentModelItems(); +} + +void QQuickMenuPrivate::removeNativeItem(int index) +{ + // Either we're still using native menus and are removing item(s), or we've switched + // to a non-native menu; either way, we should actually have items to remove before we're called. + Q_ASSERT(handle); + Q_ASSERT_X(index >= 0 && index < nativeItems.size(), Q_FUNC_INFO, qPrintable(QString::fromLatin1( + "index %1 is less than 0 or greater than or equal to %2").arg(index).arg(nativeItems.size()))); + + // We can delete the item synchronously because there aren't any external (e.g. QML) + // references to it. + std::unique_ptr<QQuickNativeMenuItem> nativeItem(nativeItems.takeAt(index)); + qCDebug(lcNativeMenus) << "removing native item" << nativeItem->debugText() << "at index" << index + << "from" << q_func() << "..."; + if (QQuickMenu *subMenu = nativeItem->subMenu()) + recursivelyDestroyNativeSubMenus(subMenu); + + if (nativeItem->handle()) { + handle->removeMenuItem(nativeItem->handle()); + syncWithNativeMenu(); + } + + qCDebug(lcNativeMenus).nospace() << "... after removing item at index " << index + << ", nativeItems now contains the following items: " << nativeMenuItemListToString(nativeItems); +} + +void QQuickMenuPrivate::resetNativeData() +{ + qCDebug(lcNativeMenus) << "resetNativeData called on" << q_func(); + handle.reset(); + triedToCreateNativeMenu = false; +} + +void QQuickMenuPrivate::recursivelyCreateNativeMenuItems(QQuickMenu *menu) +{ + auto *menuPrivate = QQuickMenuPrivate::get(menu); + // If we're adding a sub-menu, we need to ensure its handle has been created + // before trying to create native items for it. + if (!menuPrivate->triedToCreateNativeMenu) + menuPrivate->createNativeMenu(); + + const int qtyItemsToCreate = menuPrivate->contentModel->count(); + if (menuPrivate->nativeItems.count() == qtyItemsToCreate) + return; + + qCDebug(lcNativeMenus) << "recursively creating" << qtyItemsToCreate << "menu item(s) for" << menu; + Q_ASSERT(menuPrivate->nativeItems.count() == 0); + for (int i = 0; i < qtyItemsToCreate; ++i) { + QQuickItem *item = menu->itemAt(i); + menuPrivate->maybeCreateAndInsertNativeItem(i, item); + auto *menuItem = qobject_cast<QQuickMenuItem *>(item); + if (menuItem && menuItem->subMenu()) + recursivelyCreateNativeMenuItems(menuItem->subMenu()); + } +} + +void QQuickMenuPrivate::printContentModelItems() const +{ + qCDebug(lcMenu) << "contentModel now contains:"; + for (int i = 0; i < contentModel->count(); ++i) + qCDebug(lcMenu) << "-" << itemAt(i); } QQuickItem *QQuickMenuPrivate::beginCreateItem() @@ -403,18 +925,29 @@ QQuickPopupPositioner *QQuickMenuPrivate::getPositioner() void QQuickMenuPositioner::reposition() { QQuickMenu *menu = static_cast<QQuickMenu *>(popup()); - QQuickMenuPrivate *p = QQuickMenuPrivate::get(menu); - if (p->parentMenu) { - if (p->cascade) { - if (p->popupItem->isMirrored()) - menu->setPosition(QPointF(-menu->width() - p->parentMenu->leftPadding() + menu->overlap(), -menu->topPadding())); - else if (p->parentItem) - menu->setPosition(QPointF(p->parentItem->width() + p->parentMenu->rightPadding() - menu->overlap(), -menu->topPadding())); + QQuickMenuPrivate *menu_d = QQuickMenuPrivate::get(menu); + + if (QQuickMenu *parentMenu = menu_d->parentMenu) { + if (menu_d->cascade) { + // Align the menu to the frame of the parent menu, minus overlap. The position + // should be in the coordinate system of the parentItem. + if (menu_d->popupItem->isMirrored()) { + menu->setPosition({-menu->width() + - parentMenu->leftPadding() + parentMenu->leftInset() + + menu->overlap(), + menu->topInset() - menu->topPadding()}); + } else if (menu_d->parentItem) { + menu->setPosition({menu_d->parentItem->width() + + parentMenu->rightPadding() - parentMenu->rightInset() + - menu->overlap(), + menu->topInset() - menu->topPadding()}); + } } else { - menu->setPosition(QPointF(p->parentMenu->x() + (p->parentMenu->width() - menu->width()) / 2, - p->parentMenu->y() + (p->parentMenu->height() - menu->height()) / 2)); + menu->setPosition(QPointF(parentMenu->x() + (parentMenu->width() - menu->width()) / 2, + parentMenu->y() + (parentMenu->height() - menu->height()) / 2)); } } + QQuickPopupPositioner::reposition(); } @@ -462,6 +995,36 @@ bool QQuickMenuPrivate::blockInput(QQuickItem *item, const QPointF &point) const return (cascade && parentMenu && contains(point)) || QQuickPopupPrivate::blockInput(item, point); } +/*! \internal + QQuickPopupWindow::event() calls this to handle the release event of a + menu drag-press-release gesture, because the \a eventPoint does not have + a grabber within the popup window. This override finds and activates the + appropriate menu item, as if it had been pressed and released. + Returns true on success, to indicate that handling \a eventPoint is done. + */ +bool QQuickMenuPrivate::handleReleaseWithoutGrab(const QEventPoint &eventPoint) +{ + if (!contains(eventPoint.scenePosition())) + return false; + + QQuickMenuItem *menuItem = nullptr; + // Usually, hover events have occurred, and currentIndex is set. + // If not, use eventPoint.position() for picking. + if (currentIndex < 0) { + auto *list = qobject_cast<QQuickListView *>(contentItem); + if (!list) + return false; + menuItem = qobject_cast<QQuickMenuItem *>(list->itemAt(eventPoint.position().x(), eventPoint.position().y())); + } else { + menuItem = qobject_cast<QQuickMenuItem *>(itemAt(currentIndex)); + } + if (Q_LIKELY(menuItem)) { + menuItem->animateClick(); + return true; + } + return false; +} + void QQuickMenuPrivate::onItemHovered() { Q_Q(QQuickMenu); @@ -516,6 +1079,30 @@ void QQuickMenuPrivate::onItemActiveFocusChanged() setCurrentIndex(indexOfItem, control ? control->focusReason() : Qt::OtherFocusReason); } +void QQuickMenuPrivate::updateTextPadding() +{ + Q_Q(QQuickMenu); + if (!complete) + return; + + qreal padding = 0; + for (int i = 0; i < q->count(); ++i) { + if (const auto menuItem = qobject_cast<QQuickMenuItem *>(itemAt(i))) + if (menuItem->isVisible()) + padding = qMax(padding, menuItem->implicitTextPadding()); + } + + if (padding == textPadding) + return; + + textPadding = padding; + + for (int i = 0; i < q->count(); ++i) { + if (const auto menuItem = qobject_cast<QQuickMenuItem *>(itemAt(i))) + emit menuItem->textPaddingChanged(); + } +} + QQuickMenu *QQuickMenuPrivate::currentSubMenu() const { if (!currentItem) @@ -571,11 +1158,13 @@ void QQuickMenuPrivate::propagateKeyEvent(QKeyEvent *event) if (QQuickMenuItem *menuItem = qobject_cast<QQuickMenuItem *>(parentItem)) { if (QQuickMenu *menu = menuItem->menu()) QQuickMenuPrivate::get(menu)->propagateKeyEvent(event); +#if QT_CONFIG(quicktemplates2_container) } else if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(parentItem)) { if (QQuickMenuBar *menuBar = menuBarItem->menuBar()) { event->accept(); QCoreApplication::sendEvent(menuBar, event); } +#endif } } @@ -727,11 +1316,26 @@ QQuickMenu::QQuickMenu(QObject *parent) QQuickMenu::~QQuickMenu() { Q_D(QQuickMenu); - // We have to do this to ensure that the change listeners are removed. - // It's too late to do this in ~QQuickMenuPrivate, as contentModel has already - // been destroyed before that is called. + qCDebug(lcNativeMenus) << "destroying" << this + << "item count:" + << d->contentModel->count() + << "native item count:" << d->nativeItems.count(); + // We have to remove items to ensure that our change listeners on the item + // are removed. It's too late to do this in ~QQuickMenuPrivate, as + // contentModel has already been destroyed before that is called. + // Destruction isn't necessary for the QQuickItems themselves, but it is + // required for the native menus (see comment in removeItem()). while (d->contentModel->count() > 0) - d->removeItem(0, d->itemAt(0)); + d->removeItem(0, d->itemAt(0), QQuickMenuPrivate::DestructionPolicy::Destroy); + + if (d->contentItem) { + QQuickItemPrivate::get(d->contentItem)->removeItemChangeListener(d, QQuickItemPrivate::Children); + QQuickItemPrivate::get(d->contentItem)->removeItemChangeListener(d, QQuickItemPrivate::Geometry); + + const auto children = d->contentItem->childItems(); + for (QQuickItem *child : std::as_const(children)) + QQuickItemPrivate::get(child)->removeItemChangeListener(d, QQuickItemPrivate::SiblingOrder); + } } /*! @@ -774,8 +1378,9 @@ void QQuickMenu::insertItem(int index, QQuickItem *item) if (oldIndex != -1) { if (oldIndex < index) --index; - if (oldIndex != index) + if (oldIndex != index) { d->moveItem(oldIndex, index); + } } else { d->insertItem(index, item); } @@ -815,8 +1420,7 @@ void QQuickMenu::removeItem(QQuickItem *item) if (index == -1) return; - d->removeItem(index, item); - item->deleteLater(); + d->removeItem(index, item, QQuickMenuPrivate::DestructionPolicy::Destroy); } /*! @@ -930,6 +1534,7 @@ QQuickMenu *QQuickMenu::takeMenu(int index) d->removeItem(index, item); item->deleteLater(); + return subMenu; } @@ -943,11 +1548,18 @@ QQuickMenu *QQuickMenu::takeMenu(int index) QQuickAction *QQuickMenu::actionAt(int index) const { Q_D(const QQuickMenu); - QQuickAbstractButton *item = qobject_cast<QQuickAbstractButton *>(d->itemAt(index)); - if (!item) - return nullptr; + if (!const_cast<QQuickMenuPrivate *>(d)->maybeNativeHandle()) { + QQuickAbstractButton *item = qobject_cast<QQuickAbstractButton *>(d->itemAt(index)); + if (!item) + return nullptr; + + return item->action(); + } else { + if (index < 0 || index >= d->nativeItems.size()) + return nullptr; - return item->action(); + return d->nativeItems.at(index)->action(); + } } /*! @@ -1026,6 +1638,43 @@ QQuickAction *QQuickMenu::takeAction(int index) return action; } +bool QQuickMenu::isVisible() const +{ + Q_D(const QQuickMenu); + if (d->maybeNativeHandle()) + return d->visible; + return QQuickPopup::isVisible(); +} + +void QQuickMenu::setVisible(bool visible) +{ + Q_D(QQuickMenu); + if (visible == d->visible) + return; + if (visible && !parentItem()) { + qmlWarning(this) << "cannot show menu: parent is null"; + return; + } + + if (visible && ((d->useNativeMenu() && !d->maybeNativeHandle()) + || (!d->useNativeMenu() && d->maybeNativeHandle()))) { + // We've been made visible, and our actual native state doesn't match our requested state, + // which means AA_DontUseNativeMenuWindows was set while we were visible or had a parent. + // Try to sync our state again now that we're about to be re-opened. + qCDebug(lcNativeMenus) << "setVisible called - useNativeMenu:" << d->useNativeMenu() + << "maybeNativeHandle:" << d->maybeNativeHandle(); + d->syncWithUseNativeMenu(); + } + if (d->maybeNativeHandle()) { + d->setNativeMenuVisible(visible); + return; + } + + // Either the native menu wasn't wanted, or it couldn't be created; + // show the non-native menu. + QQuickPopup::setVisible(visible); +} + /*! \qmlproperty model QtQuick.Controls::Menu::contentModel \readonly @@ -1095,12 +1744,14 @@ QString QQuickMenu::title() const return d->title; } -void QQuickMenu::setTitle(QString &title) +void QQuickMenu::setTitle(const QString &title) { Q_D(QQuickMenu); if (title == d->title) return; d->title = title; + if (d->handle) + d->handle->setText(title); emit titleChanged(title); } @@ -1116,7 +1767,9 @@ void QQuickMenu::setTitle(QString &title) \include qquickicon.qdocinc grouped-properties - \sa text, display, {Icons in Qt Quick Controls} + \include qquickmenu.qdocinc non-native-only-property + + \sa AbstractButton::text, AbstractButton::display, {Icons in Qt Quick Controls} */ QQuickIcon QQuickMenu::icon() const @@ -1147,6 +1800,8 @@ void QQuickMenu::setIcon(const QQuickIcon &icon) \note Changing the value of the property has no effect while the menu is open. + \include qquickmenu.qdocinc non-native-only-property + \sa overlap */ bool QQuickMenu::cascade() const @@ -1187,6 +1842,8 @@ void QQuickMenu::resetCascade() \note Changing the value of the property has no effect while the menu is open. + \include qquickmenu.qdocinc non-native-only-property + \sa cascade */ qreal QQuickMenu::overlap() const @@ -1219,6 +1876,9 @@ void QQuickMenu::setOverlap(qreal overlap) } \endcode + \note delegates will only be visible when using a \l {Native Menus} + {non-native Menu}. + \sa Action */ QQmlComponent *QQuickMenu::delegate() const @@ -1245,6 +1905,8 @@ void QQuickMenu::setDelegate(QQmlComponent *delegate) Menu items can be highlighted by mouse hover or keyboard navigation. + \include qquickmenu.qdocinc non-native-only-property + \sa MenuItem::highlighted */ int QQuickMenu::currentIndex() const @@ -1284,10 +1946,10 @@ void QQuickMenu::popup(QQuickItem *menuItem) #endif // As a fallback, center the menu over its parent item. - if (pos.isNull && d->parentItem) + if (!pos.isValid() && d->parentItem) pos = QPointF((d->parentItem->width() - width()) / 2, (d->parentItem->height() - height()) / 2); - popup(pos.isNull ? QPointF() : pos.value, menuItem); + popup(pos.isValid() ? pos.value() : QPointF(), menuItem); } void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem) @@ -1302,6 +1964,9 @@ void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem) if (menuItem) d->setCurrentIndex(d->contentModel->indexOf(menuItem, nullptr), Qt::PopupFocusReason); + else + d->setCurrentIndex(-1, Qt::PopupFocusReason); + open(); } @@ -1313,7 +1978,9 @@ void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem) Opens the menu at the mouse cursor on desktop platforms that have a mouse cursor available, and otherwise centers the menu over its \a parent item. - The menu can be optionally aligned to a specific menu \a item. + The menu can be optionally aligned to a specific menu \a item. This item will + then become \l {currentIndex}{current.} If no \a item is specified, \l currentIndex + will be set to \c -1. \sa Popup::open() */ @@ -1326,7 +1993,9 @@ void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem) Opens the menu at the specified position \a pos in the popups coordinate system, that is, a coordinate relative to its \a parent item. - The menu can be optionally aligned to a specific menu \a item. + The menu can be optionally aligned to a specific menu \a item. This item will + then become \l {currentIndex}{current.} If no \a item is specified, \l currentIndex + will be set to \c -1. \sa Popup::open() */ @@ -1339,11 +2008,13 @@ void QQuickMenu::popup(const QPointF &pos, QQuickItem *menuItem) Opens the menu at the specified position \a x, \a y in the popups coordinate system, that is, a coordinate relative to its \a parent item. - The menu can be optionally aligned to a specific menu \a item. + The menu can be optionally aligned to a specific menu \a item. This item will + then become \l {currentIndex}{current.} If no \a item is specified, \l currentIndex + will be set to \c -1. \sa dismiss(), Popup::open() */ -void QQuickMenu::popup(QQmlV4Function *args) +void QQuickMenu::popup(QQmlV4FunctionPtr args) { Q_D(QQuickMenu); const int len = args->length(); @@ -1388,7 +2059,7 @@ void QQuickMenu::popup(QQmlV4Function *args) pos = QPointF(xArg->asDouble(), yArg->asDouble()); } - if (pos.isNull && (len >= 2 || (!parentItem && len >= 1))) { + if (!pos.isValid() && (len >= 2 || (!parentItem && len >= 1))) { // point pos QV4::ScopedValue posArg(scope, (*args)[parentItem ? 1 : 0]); const QVariant var = QV4::ExecutionEngine::toVariant(posArg, QMetaType {}); @@ -1399,10 +2070,10 @@ void QQuickMenu::popup(QQmlV4Function *args) if (parentItem) setParentItem(parentItem); - if (pos.isNull) - popup(menuItem); - else + if (pos.isValid()) popup(pos, menuItem); + else + popup(menuItem); } /*! @@ -1411,9 +2082,10 @@ void QQuickMenu::popup(QQmlV4Function *args) Closes all menus in the hierarchy that this menu belongs to. - \note Unlike \l {Popup::}{close()} that only closes a menu and its sub-menus, - \c dismiss() closes the whole hierarchy of menus, including the parent menus. - In practice, \c close() is suitable e.g. for implementing navigation in a + \note Unlike \l {Popup::}{close()} that only closes a menu and its + sub-menus (when using \l {Native Menus}{non-native menus}), \c dismiss() + closes the whole hierarchy of menus, including the parent menus. In + practice, \c close() is suitable e.g. for implementing navigation in a hierarchy of menus, and \c dismiss() is the appropriate method for closing the whole hierarchy of menus. @@ -1433,6 +2105,8 @@ void QQuickMenu::componentComplete() Q_D(QQuickMenu); QQuickPopup::componentComplete(); d->resizeItems(); + d->updateTextPadding(); + d->syncWithUseNativeMenu(); } void QQuickMenu::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) @@ -1457,12 +2131,16 @@ void QQuickMenu::itemChange(QQuickItem::ItemChange change, const QQuickItem::Ite Q_D(QQuickMenu); QQuickPopup::itemChange(change, data); - if (change == QQuickItem::ItemVisibleHasChanged) { + switch (change) { + case QQuickItem::ItemVisibleHasChanged: if (!data.boolValue && d->cascade) { // Ensure that when the menu isn't visible, there's no current item // the next time it's opened. d->setCurrentIndex(-1, Qt::OtherFocusReason); } + break; + default: + break; } } diff --git a/src/quicktemplates/qquickmenu_p.h b/src/quicktemplates/qquickmenu_p.h index 351272ed05..ed08537fdb 100644 --- a/src/quicktemplates/qquickmenu_p.h +++ b/src/quicktemplates/qquickmenu_p.h @@ -17,10 +17,13 @@ #include <QtQml/qqmllist.h> #include <QtQml/qqml.h> +#include <QtQmlModels/private/qtqmlmodelsglobal_p.h> #include "qquickpopup_p.h" #include <QtQuickTemplates2/private/qquickicon_p.h> +QT_REQUIRE_CONFIG(qml_object_model); + QT_BEGIN_NAMESPACE class QQuickAction; @@ -28,7 +31,7 @@ class QQmlComponent; class QQuickMenuItem; class QQuickMenuPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenu : public QQuickPopup +class Q_QUICKTEMPLATES2_EXPORT QQuickMenu : public QQuickPopup { Q_OBJECT Q_PROPERTY(QVariant contentModel READ contentModel CONSTANT FINAL) @@ -60,7 +63,7 @@ public: QQmlListProperty<QObject> contentData(); QString title() const; - void setTitle(QString &title); + void setTitle(const QString &title); QQuickIcon icon() const; void setIcon(const QQuickIcon &icon); @@ -94,10 +97,13 @@ public: Q_REVISION(2, 3) Q_INVOKABLE void removeAction(QQuickAction *action); Q_REVISION(2, 3) Q_INVOKABLE QQuickAction *takeAction(int index); + bool isVisible() const override; + void setVisible(bool visible) override; + void popup(QQuickItem *menuItem = nullptr); void popup(const QPointF &pos, QQuickItem *menuItem = nullptr); - Q_REVISION(2, 3) Q_INVOKABLE void popup(QQmlV4Function *args); + Q_REVISION(2, 3) Q_INVOKABLE void popup(QQmlV4FunctionPtr args); Q_REVISION(2, 3) Q_INVOKABLE void dismiss(); protected: @@ -133,6 +139,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickMenu) - #endif // QQUICKMENU_P_H diff --git a/src/quicktemplates/qquickmenu_p_p.h b/src/quicktemplates/qquickmenu_p_p.h index 509614d2d1..be607c37c9 100644 --- a/src/quicktemplates/qquickmenu_p_p.h +++ b/src/quicktemplates/qquickmenu_p_p.h @@ -18,6 +18,8 @@ #include <QtCore/qlist.h> #include <QtCore/qpointer.h> +#include <QtGui/qpa/qplatformmenu.h> + #include <QtQuickTemplates2/private/qquickmenu_p.h> #include <QtQuickTemplates2/private/qquickpopup_p_p.h> @@ -27,8 +29,10 @@ class QQuickAction; class QQmlComponent; class QQmlObjectModel; class QQuickMenuItem; +class QQuickNativeMenuItem; +class QQuickMenuBar; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuPrivate : public QQuickPopupPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickMenuPrivate : public QQuickPopupPrivate { public: Q_DECLARE_PUBLIC(QQuickMenu) @@ -42,10 +46,33 @@ public: void init(); + QPlatformMenu *nativeHandle(); + QPlatformMenu *maybeNativeHandle() const; + QQuickMenu *rootMenu() const; + bool useNativeMenu() const; + bool createNativeMenu(); + void removeNativeMenu(); + void syncWithNativeMenu(); + void syncWithUseNativeMenu(); + static void recursivelyDestroyNativeSubMenus(QQuickMenu *menu); + void setNativeMenuVisible(bool visible); + QQuickItem *itemAt(int index) const; void insertItem(int index, QQuickItem *item); + void maybeCreateAndInsertNativeItem(int index, QQuickItem *item); void moveItem(int from, int to); - void removeItem(int index, QQuickItem *item); + enum class DestructionPolicy { + Destroy, + DoNotDestroy + }; + void removeItem(int index, QQuickItem *item, + DestructionPolicy destructionPolicy = DestructionPolicy::DoNotDestroy); + void removeNativeItem(int index); + void resetNativeData(); + + static void recursivelyCreateNativeMenuItems(QQuickMenu *menu); + + void printContentModelItems() const; QQuickItem *beginCreateItem(); void completeCreateItem(); @@ -66,10 +93,12 @@ public: bool prepareEnterTransition() override; bool prepareExitTransition() override; bool blockInput(QQuickItem *item, const QPointF &point) const override; + bool handleReleaseWithoutGrab(const QEventPoint &eventPoint) override; void onItemHovered(); void onItemTriggered(); void onItemActiveFocusChanged(); + void updateTextPadding(); QQuickMenu *currentSubMenu() const; void setParentMenu(QQuickMenu *parent); @@ -92,11 +121,14 @@ public: static void contentData_clear(QQmlListProperty<QObject> *prop); QPalette defaultPalette() const override; + virtual QQuickPopup::PopupType resolvedPopupType() const override; bool cascade = false; + bool triedToCreateNativeMenu = false; int hoverTimer = 0; int currentIndex = -1; qreal overlap = 0; + qreal textPadding = 0; QPointer<QQuickMenu> parentMenu; QPointer<QQuickMenuItem> currentItem; QQuickItem *contentItem = nullptr; // TODO: cleanup @@ -105,6 +137,12 @@ public: QQmlComponent *delegate = nullptr; QString title; QQuickIcon icon; + + // For native menu support. + std::unique_ptr<QPlatformMenu> handle = nullptr; + QList<QQuickNativeMenuItem *> nativeItems; + QPointer<QQuickMenuBar> menuBar; + qreal lastDevicePixelRatio = 0; }; QT_END_NAMESPACE diff --git a/src/quicktemplates/qquickmenubar.cpp b/src/quicktemplates/qquickmenubar.cpp index d8ecbb6626..542f2562c1 100644 --- a/src/quicktemplates/qquickmenubar.cpp +++ b/src/quicktemplates/qquickmenubar.cpp @@ -42,16 +42,29 @@ QT_BEGIN_NAMESPACE \l {removeMenu}{remove}, and \l {takeMenu}{take} menus dynamically. The menus in a menu bar can be accessed using \l menuAt(). + \section1 Native menu bars + + Since Qt 6.8, a MenuBar is implemented as a native menu bar on \macos. As a + result, all Menus, MenuItems and MenuBarItems within a MenuBar will also be native. + While this has the advantage that everything will look native, it also comes with the + disadvantage that the delegates set on the mentioned controls will not be used + for rendering. + If a native MenuBar is not wanted, you can set + \l {Qt::AA_DontUseNativeMenuBar}{QGuiApplication::setAttribute(Qt::AA_DontUseNativeMenuBar)} + to disable it. + \sa {Customizing MenuBar}, Menu, MenuBarItem, {Menu Controls}, {Focus Management in Qt Quick Controls} */ -QQuickItem *QQuickMenuBarPrivate::beginCreateItem(QQuickMenu *menu) +Q_LOGGING_CATEGORY(lcMenuBar, "qt.quick.controls.menubar") + +static const char* kCreatedFromDelegate = "_qt_createdFromDelegate"; + +QQuickItem *QQuickMenuBarPrivate::createItemFromDelegate() { Q_Q(QQuickMenuBar); - if (!delegate) - return nullptr; - + Q_ASSERT(delegate); QQmlContext *context = delegate->creationContext(); if (!context) context = qmlContext(q); @@ -63,45 +76,94 @@ QQuickItem *QQuickMenuBarPrivate::beginCreateItem(QQuickMenu *menu) return nullptr; } - if (QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(item)) - menuBarItem->setMenu(menu); - item->setParentItem(q); QQml_setParent_noEvent(item, q); + delegate->completeCreate(); return item; } -void QQuickMenuBarPrivate::completeCreateItem() +QQuickMenuBarItem *QQuickMenuBarPrivate::createMenuBarItem(QQuickMenu *menu) { - if (!delegate) - return; + Q_Q(QQuickMenuBar); - delegate->completeCreate(); + QQuickMenuBarItem *menuBarItem = nullptr; + if (delegate) { + QQuickItem *item = createItemFromDelegate(); + menuBarItem = qobject_cast<QQuickMenuBarItem *>(item); + if (!menuBarItem) { + qmlWarning(q) << "cannot insert menu: the delegate is not a MenuBarItem."; + delete item; + } + } + + if (!menuBarItem) { + // When we fail to create a delegate item, create a hidden placeholder + // instead. This is needed, since we store the menus inside the container + // using MenuBarItem. And without a MenuBarItem, we would therefore lose + // the menu, even if the delegate is changed later. + qCDebug(lcMenuBar) << "creating hidden placeholder MenuBarItem for:" << menu->title(); + menuBarItem = new QQuickMenuBarItem(q); + menuBarItem->setParentItem(q); + menuBarItem->setVisible(false); + } + + menuBarItem->setMenu(menu); + + // Tag the menuBarItem, so that we know which container items to change if the + // delegate is changed. This is needed since you can add MenuBarItems directly + // to the menu bar, which should not change when the delegate changes. + menuBarItem->setProperty(kCreatedFromDelegate, true); + + return menuBarItem; } -QQuickItem *QQuickMenuBarPrivate::createItem(QQuickMenu *menu) +void QQuickMenuBarPrivate::openCurrentMenu() { - QQuickItem *item = beginCreateItem(menu); - completeCreateItem(); - return item; + if (!currentItem || currentMenuOpen) + return; + QQuickMenu *menu = currentItem->menu(); + if (!menu || menu->isOpened()) + return; + +#ifdef Q_OS_MACOS + // On macOS, the menu should open underneath the MenuBar + Q_Q(QQuickMenuBar); + const QPointF posInParentItem = q->mapToItem(currentItem, {currentItem->x(), q->height()}); +#else + // On other platforms, it should open underneath the MenuBarItem + const QPointF posInParentItem{0, currentItem->y() + currentItem->height()}; +#endif + + // Store explicit if the current menu is logically supposed to be open. + // menu->isVisible() is async when using top-level menus, and will not become + // "true" before the menu is actually shown by the OS. This will cause us to + // lose track of if a menu is (supposed to be) open, if relying on menu->isVisible(). + currentMenuOpen = true; + + // The position should be the coordinate system of the parent item. Note that + // the parentItem() of a menu will be the MenuBarItem (currentItem), and not the + // MenuBar (even if parent() usually points to the MenuBar). + menu->popup(posInParentItem); } -void QQuickMenuBarPrivate::toggleCurrentMenu(bool visible, bool activate) +void QQuickMenuBarPrivate::closeCurrentMenu() { - if (!currentItem || visible == popupMode) + if (!currentItem || !currentMenuOpen) return; - + currentMenuOpen = false; QQuickMenu *menu = currentItem->menu(); + QScopedValueRollback triggerRollback(closingCurrentMenu, true); + menu->dismiss(); +} - triggering = true; - popupMode = visible; - if (menu) - menu->setVisible(visible); - if (!visible) - currentItem->forceActiveFocus(); - else if (menu && activate) - menu->setCurrentIndex(0); - triggering = false; +void QQuickMenuBarPrivate::activateMenuItem(int index) +{ + if (!currentItem) + return; + QQuickMenu *menu = currentItem->menu(); + if (!menu) + return; + menu->setCurrentIndex(index); } void QQuickMenuBarPrivate::activateItem(QQuickMenuBarItem *item) @@ -109,23 +171,20 @@ void QQuickMenuBarPrivate::activateItem(QQuickMenuBarItem *item) if (currentItem == item) return; + const bool stayOpen = currentMenuOpen; + if (currentItem) { currentItem->setHighlighted(false); - if (popupMode) { - if (QQuickMenu *menu = currentItem->menu()) - menu->dismiss(); - } - } - - if (item) { - item->setHighlighted(true); - if (popupMode) { - if (QQuickMenu *menu = item->menu()) - menu->open(); - } + closeCurrentMenu(); } currentItem = item; + + if (currentItem) { + currentItem->setHighlighted(true); + if (stayOpen) + openCurrentMenu(); + } } void QQuickMenuBarPrivate::activateNextItem() @@ -162,19 +221,34 @@ void QQuickMenuBarPrivate::onItemTriggered() return; if (item == currentItem) { - toggleCurrentMenu(!popupMode, false); + if (currentMenuOpen) { + closeCurrentMenu(); + currentItem->forceActiveFocus(); + } else { + openCurrentMenu(); + } } else { - popupMode = true; activateItem(item); + openCurrentMenu(); } } -void QQuickMenuBarPrivate::onMenuAboutToHide() +void QQuickMenuBarPrivate::onMenuAboutToHide(QQuickMenu *menu) { - if (triggering || !currentItem || (currentItem->isHovered() && currentItem->isEnabled()) || !currentItem->isHighlighted()) + if (closingCurrentMenu) { + // We only react on a menu closing if it's + // initiated from outside of QQuickMenuBar. + return; + } + + if (!currentItem || currentItem->menu() != menu) + return; + + currentMenuOpen = false; + + if (!currentItem->isHighlighted() || currentItem->isHovered()) return; - popupMode = false; activateItem(nullptr); } @@ -220,14 +294,32 @@ void QQuickMenuBarPrivate::itemImplicitHeightChanged(QQuickItem *item) void QQuickMenuBarPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj) { - QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object); - if (QQuickMenu *menu = qobject_cast<QQuickMenu *>(obj)) - obj = QQuickMenuBarPrivate::get(menuBar)->createItem(menu); + auto menuBar = static_cast<QQuickMenuBar *>(prop->object); + auto menuBarPriv = QQuickMenuBarPrivate::get(menuBar); + + if (auto *menu = qobject_cast<QQuickMenu *>(obj)) { + QQuickMenuBarItem *delegateItem = menuBarPriv->createMenuBarItem(menu); + menuBarPriv->insertMenu(menuBar->count(), menu, delegateItem); + QQuickContainerPrivate::contentData_append(prop, delegateItem); + return; + } + + if (auto *menuBarItem = qobject_cast<QQuickMenuBarItem *>(obj)) { + menuBarPriv->insertMenu(menuBar->count(), menuBarItem->menu(), menuBarItem); + QQuickContainerPrivate::contentData_append(prop, menuBarItem); + return; + } + QQuickContainerPrivate::contentData_append(prop, obj); } void QQuickMenuBarPrivate::menus_append(QQmlListProperty<QQuickMenu> *prop, QQuickMenu *obj) { + // This function is only called if the application assigns a list of menus + // directly to the 'menus' property. Otherwise, contentData_append is used. + // Since the functions belonging to the 'menus' list anyway returns data from + // the menuBar, calls such as "menuBar.menus.length" works as expected + // regardless of how the menus were added. QQuickMenuBar *menuBar = static_cast<QQuickMenuBar *>(prop->object); menuBar->addMenu(obj); } @@ -255,6 +347,283 @@ QPalette QQuickMenuBarPrivate::defaultPalette() const return QQuickTheme::palette(QQuickTheme::MenuBar); } +QWindow* QQuickMenuBarPrivate::window() const +{ + Q_Q(const QQuickMenuBar); + QObject *obj = q->parent(); + while (obj) { + if (QWindow *window = qobject_cast<QWindow *>(obj)) + return window; + QQuickItem *item = qobject_cast<QQuickItem *>(obj); + if (item && item->window()) + return item->window(); + obj = obj->parent(); + } + return nullptr; +} + +int QQuickMenuBarPrivate::menuIndex(QQuickMenu *menu) const +{ + Q_Q(const QQuickMenuBar); + for (int i = 0; i < q->count(); ++i) { + if (q->menuAt(i) == menu) + return i; + } + + return -1; +} + +QPlatformMenuBar* QQuickMenuBarPrivate::nativeHandle() const +{ + return handle.get(); +} + +void QQuickMenuBarPrivate::insertNativeMenu(QQuickMenu *menu) +{ + Q_Q(QQuickMenuBar); + Q_ASSERT(handle); + Q_ASSERT(menu); + + QPlatformMenu *insertBeforeHandle = nullptr; + + // This function assumes that the QQuickMenuBarItem that corresponds to \a menu + // has already been added to the container at the correct index. So we search for + // it, to determine where to insert it in the native menubar. Since the QPA API + // expects a pointer to the QPlatformMenu that comes after it, we need to search + // for that one as well, since some MenuBarItems in the container can be hidden. + bool foundInContainer = false; + for (int i = 0; i < q->count(); ++i) { + if (q->menuAt(i) != menu) + continue; + foundInContainer = true; + + for (int j = i + 1; j < q->count(); ++j) { + insertBeforeHandle = QQuickMenuPrivate::get(q->menuAt(j))->maybeNativeHandle(); + if (insertBeforeHandle) + break; + } + + break; + } + + Q_ASSERT(foundInContainer); + QQuickMenuPrivate *menuPrivate = QQuickMenuPrivate::get(menu); + if (QPlatformMenu *menuHandle = menuPrivate->nativeHandle()) { + qCDebug(lcMenuBar) << "insert native menu:" << menu->title() << menuHandle << "before:" << insertBeforeHandle; + handle->insertMenu(menuPrivate->nativeHandle(), insertBeforeHandle); + } else { + qmlWarning(q) << "failed to create native menu for:" << menu->title(); + } +} + +void QQuickMenuBarPrivate::removeNativeMenu(QQuickMenu *menu) +{ + Q_ASSERT(handle); + Q_ASSERT(menu); + + QQuickMenuPrivate *menuPrivate = QQuickMenuPrivate::get(menu); + if (!menuPrivate->maybeNativeHandle()) + return; + + qCDebug(lcMenuBar) << "remove native menu:" << menu << menu->title(); + handle->removeMenu(menuPrivate->nativeHandle()); + menuPrivate->removeNativeMenu(); +} + +void QQuickMenuBarPrivate::syncMenuBarItemVisibilty(QQuickMenuBarItem *menuBarItem) +{ + if (!handle) { + // We only need to update visibility on native menu bar items + return; + } + + QQuickMenu *menu = menuBarItem->menu(); + if (!menu) + return; + QQuickMenuPrivate *menuPrivate = QQuickMenuPrivate::get(menu); + + if (menuBarItem->isVisible()) { + Q_ASSERT(!menuPrivate->maybeNativeHandle()); + insertNativeMenu(menu); + } else { + if (menuPrivate->maybeNativeHandle()) + removeNativeMenu(menu); + } +} + +void QQuickMenuBarPrivate::insertMenu(int index, QQuickMenu *menu, QQuickMenuBarItem *menuBarItem) +{ + Q_Q(QQuickMenuBar); + if (!menu) { + qmlWarning(q) << "cannot insert menu: menu is null."; + return; + } + + auto menuPrivate = QQuickMenuPrivate::get(menu); + menuPrivate->menuBar = q; + + QObject::connect(menuBarItem, &QQuickMenuBarItem::visibleChanged, [this, menuBarItem]{ + syncMenuBarItemVisibilty(menuBarItem); + }); + + // Always insert menu into the container, even when using a native + // menubar, so that container API such as 'count' and 'itemAt' + // continues to work as expected. + q->insertItem(index, menuBarItem); + + // Create or remove a native (QPlatformMenu) menu. Note that we should only create + // a native menu if it's supposed to be visible in the menu bar. + if (menuBarItem->isVisible()) { + if (handle) + insertNativeMenu(menu); + } else { + if (menuPrivate->maybeNativeHandle()) { + // If the menu was added from an explicit call to addMenu(m), it will have been + // created before we enter here. And in that case, QQuickMenuBar::useNativeMenu(m) + // was never called, and a QPlatformMenu might have been created for it. In that + // case, we remove it again now, since the menu is not supposed to be visible in + // the menu bar. + menuPrivate->removeNativeMenu(); + } + } +} + +QQuickMenu *QQuickMenuBarPrivate::takeMenu(int index) +{ + Q_Q(QQuickMenuBar); + QQuickItem *item = q->itemAt(index); + Q_ASSERT(item); + QQuickMenuBarItem *menuBarItem = qobject_cast<QQuickMenuBarItem *>(item); + if (!menuBarItem) { + qmlWarning(q) << "cannot take/remove menu: item at index " << index << " is not a MenuBarItem."; + return nullptr; + } + QQuickMenu *menu = menuBarItem->menu(); + if (!menu) { + qmlWarning(q) << "cannot take/remove menu: MenuBarItem.menu at index " << index << " is null."; + return nullptr; + } + + // Dismiss the menu if it's open. Otherwise, when we now remove it from + // the menubar, it will stay open without the user being able to dismiss + // it (at least if it's non-native). + menu->dismiss(); + + if (item == currentItem) + activateItem(nullptr); + + if (QQuickMenuPrivate::get(menu)->maybeNativeHandle()) + removeNativeMenu(menu); + + removeItem(index, item); + + // Delete the MenuBarItem. This will also cause the menu to be deleted by + // the garbage collector, unless other QML references are being held to it. + // Note: We might consider leaving it to the garbage collector to also + // delete the MenuBarItem in the future. + item->deleteLater(); + + QQuickMenuPrivate::get(menu)->menuBar = nullptr; + menuBarItem->disconnect(q); + + return menu; +} + +bool QQuickMenuBarPrivate::useNativeMenuBar() const +{ + // We current only use native menu bars on macOS. Especially, the + // QPA menu bar for Windows is old and unused, and looks broken and non-native. +#ifdef Q_OS_MACOS + return !QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuBar); +#else + return false; +#endif +} + +bool QQuickMenuBarPrivate::useNativeMenu(const QQuickMenu *menu) const +{ + Q_Q(const QQuickMenuBar); + if (!useNativeMenuBar()) + return false; + + // Since we cannot hide a QPlatformMenu, we have to avoid + // creating it if it shouldn't be visible in the menu bar. + for (int i = 0; i < q->count(); ++i) { + if (q->menuAt(i) == menu) + return itemAt(i)->isVisible(); + } + + return true; +} + +void QQuickMenuBarPrivate::syncNativeMenuBarVisible() +{ + Q_Q(QQuickMenuBar); + if (!componentComplete) + return; + + const bool shouldBeVisible = q->isVisible() && useNativeMenuBar(); + qCDebug(lcMenuBar) << "syncNativeMenuBarVisible called - q->isVisible()" << q->isVisible() + << "useNativeMenuBar()" << useNativeMenuBar() << "handle" << handle.get(); + if (shouldBeVisible && !handle) + createNativeMenuBar(); + else if (!shouldBeVisible && handle) + removeNativeMenuBar(); +} + +void QQuickMenuBarPrivate::createNativeMenuBar() +{ + Q_Q(QQuickMenuBar); + Q_ASSERT(!handle); + qCDebug(lcMenuBar) << "creating native menubar"; + + handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformMenuBar()); + if (!handle) { + qCDebug(lcMenuBar) << "QPlatformTheme failed to create a QPlatformMenuBar!"; + return; + } + + handle->handleReparent(window()); + qCDebug(lcMenuBar) << "native menubar parented to window:" << handle->parentWindow(); + + // Add all the native menus. We need to do this right-to-left + // because of the QPA API (insertBefore). + for (int i = q->count() - 1; i >= 0; --i) { + if (QQuickMenu *menu = q->menuAt(i)) { + if (useNativeMenu(menu)) + insertNativeMenu(menu); + } + } + + // Hide the non-native menubar and set it's height to 0. The + // latter will cause a relayout to happen in ApplicationWindow + // which effectively removes the menubar from the contentItem. + setCulled(true); + q->setHeight(0); +} + +void QQuickMenuBarPrivate::removeNativeMenuBar() +{ + Q_Q(QQuickMenuBar); + Q_ASSERT(handle); + qCDebug(lcMenuBar) << "removing native menubar"; + + // Remove all native menus. + for (int i = 0; i < q->count(); ++i) { + if (QQuickMenu *menu = q->menuAt(i)) + removeNativeMenu(menu); + } + + // Delete the menubar + handle.reset(); + + // Show the non-native menubar and reset it's height. The + // latter will cause a relayout to happen in ApplicationWindow + // which will effectively add the menubar to the contentItem. + setCulled(false); + q->resetHeight(); +} + QQuickMenuBar::QQuickMenuBar(QQuickItem *parent) : QQuickContainer(*(new QQuickMenuBarPrivate), parent) { @@ -264,6 +633,13 @@ QQuickMenuBar::QQuickMenuBar(QQuickItem *parent) setFocusPolicy(Qt::ClickFocus); } +QQuickMenuBar::~QQuickMenuBar() +{ + Q_D(QQuickMenuBar); + if (d->handle) + d->removeNativeMenuBar(); +} + /*! \qmlproperty Component QtQuick.Controls::MenuBar::delegate @@ -285,6 +661,21 @@ void QQuickMenuBar::setDelegate(QQmlComponent *delegate) return; d->delegate = delegate; + + for (int i = count() - 1; i >= 0; --i) { + auto item = itemAt(i); + if (!item->property(kCreatedFromDelegate).toBool()) + continue; + + QQuickMenuBarItem *menuBarItem = static_cast<QQuickMenuBarItem *>(item); + if (QQuickMenu *menu = menuBarItem->menu()) { + removeMenu(menu); + d->insertMenu(i, menu, d->createMenuBarItem(menu)); + } else { + removeItem(menuBarItem); + } + } + emit delegateChanged(); } @@ -310,7 +701,12 @@ QQuickMenu *QQuickMenuBar::menuAt(int index) const void QQuickMenuBar::addMenu(QQuickMenu *menu) { Q_D(QQuickMenuBar); - addItem(d->createItem(menu)); + if (d->menuIndex(menu) >= 0) { + qmlWarning(this) << "cannot add menu: '" << menu->title() << "' is already in the MenuBar."; + return; + } + + d->insertMenu(count(), menu, d->createMenuBarItem(menu)); } /*! @@ -321,54 +717,52 @@ void QQuickMenuBar::addMenu(QQuickMenu *menu) void QQuickMenuBar::insertMenu(int index, QQuickMenu *menu) { Q_D(QQuickMenuBar); - insertItem(index, d->createItem(menu)); + if (d->menuIndex(menu) >= 0) { + qmlWarning(this) << "cannot insert menu: '" << menu->title() << "' is already in the MenuBar."; + return; + } + + d->insertMenu(index, menu, d->createMenuBarItem(menu)); } /*! \qmlmethod void QtQuick.Controls::MenuBar::removeMenu(Menu menu) - Removes and destroys the specified \a menu. + Removes specified \a menu. If the menu is \l {QQuickMenu::popup(QQmlV4Function *)}{open}, + it will first be \l {QQuickMenu::dismiss()}{dismissed.} + The \a menu will eventually be deleted by the garbage collector when the + application no longer holds any QML references to it. */ void QQuickMenuBar::removeMenu(QQuickMenu *menu) { Q_D(QQuickMenuBar); - if (!menu) + const int index = d->menuIndex(menu); + if (index < 0) { + qmlWarning(this) << "cannot remove menu: '" << menu->title() << "' is not in the MenuBar."; return; - - const int count = d->contentModel->count(); - for (int i = 0; i < count; ++i) { - QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(itemAt(i)); - if (!item || item->menu() != menu) - continue; - - removeItem(item); - break; } - menu->deleteLater(); + d->takeMenu(index); } /*! \qmlmethod Menu QtQuick.Controls::MenuBar::takeMenu(int index) - Removes and returns the menu at \a index. - - \note The ownership of the item is transferred to the caller. + Removes and returns the menu at \a index. If the menu is + \l {QQuickMenu::popup(QQmlV4Function *)}{open}, it will first be + \l {QQuickMenu::dismiss()}{dismissed.} + The menu will eventually be deleted by the garbage collector when the + application no longer holds any QML references to it. */ QQuickMenu *QQuickMenuBar::takeMenu(int index) { Q_D(QQuickMenuBar); - QQuickMenuBarItem *item = qobject_cast<QQuickMenuBarItem *>(itemAt(index)); - if (!item) - return nullptr; - - QQuickMenu *menu = item->menu(); - if (!menu) + if (index < 0 || index > count() - 1) { + qmlWarning(this) << "index out of range: " << index; return nullptr; + } - d->removeItem(index, item); - item->deleteLater(); - return menu; + return d->takeMenu(index); } /*! @@ -488,11 +882,12 @@ void QQuickMenuBar::keyPressEvent(QKeyEvent *event) switch (event->key()) { case Qt::Key_Up: - d->toggleCurrentMenu(false, false); + d->closeCurrentMenu(); break; case Qt::Key_Down: - d->toggleCurrentMenu(true, true); + d->openCurrentMenu(); + d->activateMenuItem(0); break; case Qt::Key_Left: @@ -502,6 +897,7 @@ void QQuickMenuBar::keyPressEvent(QKeyEvent *event) else d->activatePreviousItem(); break; + // This is triggered when no popup is open but a menu bar item is highlighted and has focus. case Qt::Key_Escape: if (d->currentItem) { d->activateItem(nullptr); @@ -516,7 +912,8 @@ void QQuickMenuBar::keyPressEvent(QKeyEvent *event) if (auto *item = qobject_cast<QQuickMenuBarItem *>(d->itemAt(i))) { if (item->shortcut() == mnemonic) { d->activateItem(item); - d->toggleCurrentMenu(true, true); + d->openCurrentMenu(); + d->activateMenuItem(0); } } } @@ -549,7 +946,7 @@ void QQuickMenuBar::hoverLeaveEvent(QHoverEvent *event) { Q_D(QQuickMenuBar); QQuickContainer::hoverLeaveEvent(event); - if (!d->popupMode && d->currentItem) + if (!d->currentMenuOpen && d->currentItem) d->activateItem(nullptr); } @@ -572,6 +969,10 @@ void QQuickMenuBar::itemChange(QQuickItem::ItemChange change, const QQuickItem:: d->windowContentItem->installEventFilter(this); } break; + case ItemVisibleHasChanged: + qCDebug(lcMenuBar) << "visibility of" << this << "changed to" << isVisible(); + d->syncNativeMenuBarVisible(); + break; default: break; } @@ -586,7 +987,7 @@ void QQuickMenuBar::itemAdded(int index, QQuickItem *item) QObjectPrivate::connect(menuBarItem, &QQuickControl::hoveredChanged, d, &QQuickMenuBarPrivate::onItemHovered); QObjectPrivate::connect(menuBarItem, &QQuickMenuBarItem::triggered, d, &QQuickMenuBarPrivate::onItemTriggered); if (QQuickMenu *menu = menuBarItem->menu()) - QObjectPrivate::connect(menu, &QQuickPopup::aboutToHide, d, &QQuickMenuBarPrivate::onMenuAboutToHide); + connect(menu, &QQuickPopup::aboutToHide, [this, menu]{ d_func()->onMenuAboutToHide(menu); }); } d->updateImplicitContentSize(); emit menusChanged(); @@ -607,12 +1008,19 @@ void QQuickMenuBar::itemRemoved(int index, QQuickItem *item) QObjectPrivate::disconnect(menuBarItem, &QQuickControl::hoveredChanged, d, &QQuickMenuBarPrivate::onItemHovered); QObjectPrivate::disconnect(menuBarItem, &QQuickMenuBarItem::triggered, d, &QQuickMenuBarPrivate::onItemTriggered); if (QQuickMenu *menu = menuBarItem->menu()) - QObjectPrivate::disconnect(menu, &QQuickPopup::aboutToHide, d, &QQuickMenuBarPrivate::onMenuAboutToHide); + menu->disconnect(this); } d->updateImplicitContentSize(); emit menusChanged(); } +void QQuickMenuBar::componentComplete() +{ + Q_D(QQuickMenuBar); + QQuickContainer::componentComplete(); + d->syncNativeMenuBarVisible(); +} + QFont QQuickMenuBar::defaultFont() const { return QQuickTheme::font(QQuickTheme::MenuBar); diff --git a/src/quicktemplates/qquickmenubar_p.h b/src/quicktemplates/qquickmenubar_p.h index 2b206a41b1..f053ff6b49 100644 --- a/src/quicktemplates/qquickmenubar_p.h +++ b/src/quicktemplates/qquickmenubar_p.h @@ -17,12 +17,14 @@ #include <QtQuickTemplates2/private/qquickcontainer_p.h> +QT_REQUIRE_CONFIG(quicktemplates2_container); + QT_BEGIN_NAMESPACE class QQuickMenu; class QQuickMenuBarPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuBar : public QQuickContainer +class Q_QUICKTEMPLATES2_EXPORT QQuickMenuBar : public QQuickContainer { Q_OBJECT Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) @@ -33,6 +35,7 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuBar : public QQuickContainer public: explicit QQuickMenuBar(QQuickItem *parent = nullptr); + ~QQuickMenuBar() override; QQmlComponent *delegate() const; void setDelegate(QQmlComponent *delegate); @@ -59,6 +62,8 @@ protected: void itemMoved(int index, QQuickItem *item) override; void itemRemoved(int index, QQuickItem *item) override; + void componentComplete() override; + QFont defaultFont() const override; #if QT_CONFIG(accessibility) @@ -72,6 +77,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickMenuBar) - #endif // QQUICKMENUBAR_P_H diff --git a/src/quicktemplates/qquickmenubar_p_p.h b/src/quicktemplates/qquickmenubar_p_p.h index 2ac3af00ed..98b234e1c8 100644 --- a/src/quicktemplates/qquickmenubar_p_p.h +++ b/src/quicktemplates/qquickmenubar_p_p.h @@ -18,12 +18,15 @@ #include <QtQuickTemplates2/private/qquickmenubar_p.h> #include <QtQuickTemplates2/private/qquickcontainer_p_p.h> +#include <QtCore/qpointer.h> +#include <QtGui/qpa/qplatformmenu.h> + QT_BEGIN_NAMESPACE class QQmlComponent; class QQuickMenuBarItem; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuBarPrivate : public QQuickContainerPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickMenuBarPrivate : public QQuickContainerPrivate { public: Q_DECLARE_PUBLIC(QQuickMenuBar) @@ -36,19 +39,36 @@ public: QQmlListProperty<QQuickMenu> menus(); QQmlListProperty<QObject> contentData(); - QQuickItem *beginCreateItem(QQuickMenu *menu); - void completeCreateItem(); + QQuickItem *createItemFromDelegate(); + QQuickMenuBarItem *createMenuBarItem(QQuickMenu *menu); - QQuickItem *createItem(QQuickMenu *menu); + void openCurrentMenu(); + void closeCurrentMenu(); + void activateMenuItem(int index); - void toggleCurrentMenu(bool visible, bool activate); void activateItem(QQuickMenuBarItem *item); void activateNextItem(); void activatePreviousItem(); void onItemHovered(); void onItemTriggered(); - void onMenuAboutToHide(); + void onMenuAboutToHide(QQuickMenu *menu); + + void insertMenu(int index, QQuickMenu *menu, QQuickMenuBarItem *delegateItem); + QQuickMenu *takeMenu(int index); + void insertNativeMenu(QQuickMenu *menu); + void removeNativeMenu(QQuickMenu *menu); + void syncMenuBarItemVisibilty(QQuickMenuBarItem *menuBarItem); + + QWindow *window() const; + int menuIndex(QQuickMenu *menu) const; + + QPlatformMenuBar *nativeHandle() const; + bool useNativeMenuBar() const; + bool useNativeMenu(const QQuickMenu *menu) const; + void syncNativeMenuBarVisible(); + void createNativeMenuBar(); + void removeNativeMenuBar(); qreal getContentWidth() const override; qreal getContentHeight() const override; @@ -65,12 +85,15 @@ public: QPalette defaultPalette() const override; - bool popupMode = false; - bool triggering = false; + bool closingCurrentMenu = false; bool altPressed = false; + bool currentMenuOpen = false; QQmlComponent *delegate = nullptr; QPointer<QQuickMenuBarItem> currentItem; QPointer<QQuickItem> windowContentItem; + +private: + std::unique_ptr<QPlatformMenuBar> handle; }; QT_END_NAMESPACE diff --git a/src/quicktemplates/qquickmenubaritem.cpp b/src/quicktemplates/qquickmenubaritem.cpp index 03e8c60f03..68aff1e62d 100644 --- a/src/quicktemplates/qquickmenubaritem.cpp +++ b/src/quicktemplates/qquickmenubaritem.cpp @@ -46,6 +46,43 @@ void QQuickMenuBarItemPrivate::setMenuBar(QQuickMenuBar *newMenuBar) emit q->menuBarChanged(); } +bool QQuickMenuBarItemPrivate::handlePress(const QPointF &point, ulong timestamp) +{ + Q_Q(QQuickMenuBarItem); + const bool handled = QQuickAbstractButtonPrivate::handlePress(point, timestamp); + if (!handled) + return false; + + const bool wasTouchPress = touchId != -1; + if (!wasTouchPress) { + // Open the menu when it's a mouse press. + emit q->triggered(); + } + + return true; +} + +bool QQuickMenuBarItemPrivate::handleRelease(const QPointF &point, ulong timestamp) +{ + Q_Q(QQuickMenuBarItem); + const bool wasTouchPress = touchId != -1; + const bool handled = QQuickAbstractButtonPrivate::handleRelease(point, timestamp); + if (!handled) + return false; + + if (wasDoubleClick || !wasTouchPress) { + // Don't open the menu on mouse release, as it should be done on press. + return handled; + } + + if (wasTouchPress) { + // Open the menu. + emit q->triggered(); + } + + return true; +} + QPalette QQuickMenuBarItemPrivate::defaultPalette() const { return QQuickTheme::palette(QQuickTheme::MenuBar); @@ -55,7 +92,7 @@ QQuickMenuBarItem::QQuickMenuBarItem(QQuickItem *parent) : QQuickAbstractButton(*(new QQuickMenuBarItemPrivate), parent) { setFocusPolicy(Qt::NoFocus); - connect(this, &QQuickAbstractButton::clicked, this, &QQuickMenuBarItem::triggered); + d_func()->setSizePolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Fixed); } /*! @@ -129,6 +166,50 @@ void QQuickMenuBarItem::setHighlighted(bool highlighted) emit highlightedChanged(); } +bool QQuickMenuBarItem::event(QEvent *event) +{ +#if QT_CONFIG(shortcut) + Q_D(QQuickMenuBarItem); + if (event->type() == QEvent::Shortcut) { + auto *shortcutEvent = static_cast<QShortcutEvent *>(event); + if (shortcutEvent->shortcutId() == d->shortcutId) { + d->trigger(); + emit triggered(); + return true; + } + } +#endif + return QQuickControl::event(event); +} + +void QQuickMenuBarItem::keyPressEvent(QKeyEvent *event) +{ + Q_D(QQuickMenuBarItem); + QQuickControl::keyPressEvent(event); + if (d->acceptKeyClick(static_cast<Qt::Key>(event->key()))) { + d->setPressPoint(d->centerPressPoint()); + setPressed(true); + emit pressed(); + event->accept(); + } +} + +void QQuickMenuBarItem::keyReleaseEvent(QKeyEvent *event) +{ + Q_D(QQuickMenuBarItem); + QQuickControl::keyReleaseEvent(event); + if (d->pressed && d->acceptKeyClick(static_cast<Qt::Key>(event->key()))) { + setPressed(false); + emit released(); + d->trigger(); + // We override these event functions so that we can emit triggered here. + // We can't just connect clicked to triggered, because that would cause mouse clicks + // to open the menu, when only presses should. + emit triggered(); + event->accept(); + } +} + void QQuickMenuBarItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_D(QQuickMenuBarItem); diff --git a/src/quicktemplates/qquickmenubaritem_p.h b/src/quicktemplates/qquickmenubaritem_p.h index 87faba2e35..c101d1f580 100644 --- a/src/quicktemplates/qquickmenubaritem_p.h +++ b/src/quicktemplates/qquickmenubaritem_p.h @@ -17,13 +17,15 @@ #include <QtQuickTemplates2/private/qquickabstractbutton_p.h> +QT_REQUIRE_CONFIG(quicktemplates2_container); + QT_BEGIN_NAMESPACE class QQuickMenu; class QQuickMenuBar; class QQuickMenuBarItemPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuBarItem : public QQuickAbstractButton +class Q_QUICKTEMPLATES2_EXPORT QQuickMenuBarItem : public QQuickAbstractButton { Q_OBJECT Q_PROPERTY(QQuickMenuBar *menuBar READ menuBar NOTIFY menuBarChanged FINAL) @@ -50,6 +52,10 @@ Q_SIGNALS: void highlightedChanged(); protected: + bool event(QEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; QFont defaultFont() const override; @@ -65,6 +71,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickMenuBarItem) - #endif // QQUICKMENUBARITEM_P_H diff --git a/src/quicktemplates/qquickmenubaritem_p_p.h b/src/quicktemplates/qquickmenubaritem_p_p.h index c2dcdd4222..b6f70cc274 100644 --- a/src/quicktemplates/qquickmenubaritem_p_p.h +++ b/src/quicktemplates/qquickmenubaritem_p_p.h @@ -35,6 +35,9 @@ public: void setMenuBar(QQuickMenuBar *menuBar); + bool handlePress(const QPointF &point, ulong timestamp) override; + bool handleRelease(const QPointF &point, ulong timestamp) override; + QPalette defaultPalette() const override; bool highlighted = false; diff --git a/src/quicktemplates/qquickmenuitem.cpp b/src/quicktemplates/qquickmenuitem.cpp index c5b0ea2753..32a0719150 100644 --- a/src/quicktemplates/qquickmenuitem.cpp +++ b/src/quicktemplates/qquickmenuitem.cpp @@ -3,7 +3,7 @@ #include "qquickmenuitem_p.h" #include "qquickmenuitem_p_p.h" -#include "qquickmenu_p.h" +#include "qquickmenu_p_p.h" #include "qquickdeferredexecute_p_p.h" #include <QtGui/qpa/qplatformtheme.h> @@ -56,6 +56,44 @@ QT_BEGIN_NAMESPACE \sa {Customizing Menu}, Menu, {Menu Controls} */ +/*! + \qmlproperty bool QtQuick.Controls::MenuItem::textPadding + \readonly + \since 6.8 + + This property holds the maximum \l implicitTextPadding found + among all the menu items inside the same \l menu. + + This property can be used by the style to ensure that all MenuItems + inside the same Menu end up aligned with respect to the \l text. + + A \l Menu can consist of meny different MenuItems, some can be checkable, + some can have an icon, and some will just contain text. And very often, + a style wants to make sure that the text inside all of them ends up + left-aligned (or right-aligned for \l mirrored items). + By letting each MenuItem assign its own minimum text padding to + \l implicitTextPadding (taking icons and checkmarks into account), but + using \l textPadding to actually position the \l text, all MenuItems should + end up being aligned + + In order for this to work, all MenuItems should set \l implicitTextPadding + to be the minimum space needed from the left edge of the \l contentItem to + the text. + + \sa implicitTextPadding +*/ + +/*! + \qmlproperty bool QtQuick.Controls::MenuItem::implicitTextPadding + \since 6.8 + + This property holds the minimum space needed from the left edge of the + \l contentItem to the text. It's used to calculate a common \l textPadding + among all the MenuItems inside a \l Menu. + + \sa textPadding +*/ + void QQuickMenuItemPrivate::setMenu(QQuickMenu *newMenu) { Q_Q(QQuickMenuItem); @@ -240,6 +278,26 @@ QFont QQuickMenuItem::defaultFont() const return QQuickTheme::font(QQuickTheme::Menu); } +qreal QQuickMenuItem::implicitTextPadding() const +{ + return d_func()->implicitTextPadding; +} + +void QQuickMenuItem::setImplicitTextPadding(qreal newImplicitTextPadding) +{ + Q_D(QQuickMenuItem); + if (qFuzzyCompare(d->implicitTextPadding, newImplicitTextPadding)) + return; + d->implicitTextPadding = newImplicitTextPadding; + emit implicitTextPaddingChanged(); +} + +qreal QQuickMenuItem::textPadding() const +{ + Q_D(const QQuickMenuItem); + return d->menu ? QQuickMenuPrivate::get(d->menu)->textPadding : 0; +} + #if QT_CONFIG(accessibility) QAccessible::Role QQuickMenuItem::accessibleRole() const { @@ -247,6 +305,25 @@ QAccessible::Role QQuickMenuItem::accessibleRole() const } #endif +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QQuickMenuItem *menuItem) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + if (!menuItem) { + debug << "QQuickMenuItem(nullptr)"; + return debug; + } + + debug << menuItem->metaObject()->className() << '(' << static_cast<const void *>(menuItem); + if (!menuItem->objectName().isEmpty()) + debug << ", name=" << menuItem->objectName(); + debug << ", text=" << menuItem->text(); + debug << ')'; + return debug; +} +#endif // QT_NO_DEBUG_STREAM + QT_END_NAMESPACE #include "moc_qquickmenuitem_p.cpp" diff --git a/src/quicktemplates/qquickmenuitem_p.h b/src/quicktemplates/qquickmenuitem_p.h index f38c437dbd..786b6f8ae6 100644 --- a/src/quicktemplates/qquickmenuitem_p.h +++ b/src/quicktemplates/qquickmenuitem_p.h @@ -16,13 +16,16 @@ // #include <QtQuickTemplates2/private/qquickabstractbutton_p.h> +#include <QtQmlModels/private/qtqmlmodelsglobal_p.h> + +QT_REQUIRE_CONFIG(qml_object_model); QT_BEGIN_NAMESPACE class QQuickMenu; class QQuickMenuItemPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuItem : public QQuickAbstractButton +class Q_QUICKTEMPLATES2_EXPORT QQuickMenuItem : public QQuickAbstractButton { Q_OBJECT Q_PROPERTY(bool highlighted READ isHighlighted WRITE setHighlighted NOTIFY highlightedChanged FINAL) @@ -30,6 +33,8 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuItem : public QQuickAbstractBut Q_PROPERTY(QQuickItem *arrow READ arrow WRITE setArrow NOTIFY arrowChanged FINAL REVISION(2, 3)) Q_PROPERTY(QQuickMenu *menu READ menu NOTIFY menuChanged FINAL REVISION(2, 3)) Q_PROPERTY(QQuickMenu *subMenu READ subMenu NOTIFY subMenuChanged FINAL REVISION(2, 3)) + Q_PROPERTY(qreal implicitTextPadding READ implicitTextPadding WRITE setImplicitTextPadding NOTIFY implicitTextPaddingChanged REVISION(6, 8)) + Q_PROPERTY(qreal textPadding READ textPadding NOTIFY textPaddingChanged REVISION(6, 8)) Q_CLASSINFO("DeferredPropertyNames", "arrow,background,contentItem,indicator") QML_NAMED_ELEMENT(MenuItem) QML_ADDED_IN_VERSION(2, 0) @@ -47,6 +52,10 @@ public: QQuickMenu *menu() const; QQuickMenu *subMenu() const; + qreal textPadding() const; + qreal implicitTextPadding() const; + void setImplicitTextPadding(qreal newImplicitTextPadding); + Q_SIGNALS: void triggered(); void highlightedChanged(); @@ -54,6 +63,8 @@ Q_SIGNALS: Q_REVISION(2, 3) void arrowChanged(); Q_REVISION(2, 3) void menuChanged(); Q_REVISION(2, 3) void subMenuChanged(); + Q_REVISION(6, 8) void implicitTextPaddingChanged(); + Q_REVISION(6, 8) void textPaddingChanged(); protected: void componentComplete() override; @@ -69,8 +80,10 @@ private: Q_DECLARE_PRIVATE(QQuickMenuItem) }; -QT_END_NAMESPACE +#ifndef QT_NO_DEBUG_STREAM +Q_QUICKTEMPLATES2_EXPORT QDebug operator<<(QDebug debug, const QQuickMenuItem *menuItem); +#endif -QML_DECLARE_TYPE(QQuickMenuItem) +QT_END_NAMESPACE #endif // QQUICKMENUITEM_P_H diff --git a/src/quicktemplates/qquickmenuitem_p_p.h b/src/quicktemplates/qquickmenuitem_p_p.h index 63bcfa33f6..3ef4981570 100644 --- a/src/quicktemplates/qquickmenuitem_p_p.h +++ b/src/quicktemplates/qquickmenuitem_p_p.h @@ -48,6 +48,7 @@ public: QQuickDeferredPointer<QQuickItem> arrow; QQuickMenu *menu = nullptr; QQuickMenu *subMenu = nullptr; + qreal implicitTextPadding; }; QT_END_NAMESPACE diff --git a/src/quicktemplates/qquickmenuseparator.cpp b/src/quicktemplates/qquickmenuseparator.cpp index d9aa770ca5..0766151d1c 100644 --- a/src/quicktemplates/qquickmenuseparator.cpp +++ b/src/quicktemplates/qquickmenuseparator.cpp @@ -33,7 +33,7 @@ QT_BEGIN_NAMESPACE \sa {Customizing Menu}, Menu, {Separator Controls} */ -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuSeparatorPrivate : public QQuickControlPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickMenuSeparatorPrivate : public QQuickControlPrivate { Q_DECLARE_PUBLIC(QQuickMenuSeparator) diff --git a/src/quicktemplates/qquickmenuseparator_p.h b/src/quicktemplates/qquickmenuseparator_p.h index abf3221461..ec7517ee90 100644 --- a/src/quicktemplates/qquickmenuseparator_p.h +++ b/src/quicktemplates/qquickmenuseparator_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QQuickMenuSeparator; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickMenuSeparator : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickMenuSeparator : public QQuickControl { Q_OBJECT QML_NAMED_ELEMENT(MenuSeparator) @@ -44,6 +44,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickMenuSeparator) - #endif // QQUICKMENUSEPARATOR_P_H diff --git a/src/quicktemplates/qquickmonthgrid.cpp b/src/quicktemplates/qquickmonthgrid.cpp index a470017676..957c9b8f93 100644 --- a/src/quicktemplates/qquickmonthgrid.cpp +++ b/src/quicktemplates/qquickmonthgrid.cpp @@ -92,8 +92,6 @@ public: bool handleRelease(const QPointF &point, ulong timestamp) override; void handleUngrab() override; - static void setContextProperty(QQuickItem *item, const QString &name, const QVariant &value); - QString title; QVariant source; QDate pressedDate; @@ -141,7 +139,6 @@ void QQuickMonthGridPrivate::updatePress(const QPointF &pos) Q_Q(QQuickMonthGrid); clearPress(false); pressedItem = cellAt(pos); - setContextProperty(pressedItem, QStringLiteral("pressed"), true); pressedDate = dateOf(pressedItem); if (pressedDate.isValid()) emit q->pressed(pressedDate); @@ -150,7 +147,6 @@ void QQuickMonthGridPrivate::updatePress(const QPointF &pos) void QQuickMonthGridPrivate::clearPress(bool clicked) { Q_Q(QQuickMonthGrid); - setContextProperty(pressedItem, QStringLiteral("pressed"), false); if (pressedDate.isValid()) { emit q->released(pressedDate); if (clicked) @@ -190,16 +186,6 @@ void QQuickMonthGridPrivate::handleUngrab() clearPress(false); } -void QQuickMonthGridPrivate::setContextProperty(QQuickItem *item, const QString &name, const QVariant &value) -{ - QQmlContext *context = qmlContext(item); - if (context && context->isValid()) { - context = context->parentContext(); - if (context && context->isValid()) - context->setContextProperty(name, value); - } -} - QQuickMonthGrid::QQuickMonthGrid(QQuickItem *parent) : QQuickControl(*(new QQuickMonthGridPrivate), parent) { @@ -357,13 +343,6 @@ void QQuickMonthGrid::componentComplete() { Q_D(QQuickMonthGrid); QQuickControl::componentComplete(); - if (d->contentItem) { - const auto childItems = d->contentItem->childItems(); - for (QQuickItem *child : childItems) { - if (!QQuickItemPrivate::get(child)->isTransparentForPositioner()) - d->setContextProperty(child, QStringLiteral("pressed"), false); - } - } d->resizeItems(); } diff --git a/src/quicktemplates/qquickmonthgrid_p.h b/src/quicktemplates/qquickmonthgrid_p.h index 9c5ffad030..e7e4421e50 100644 --- a/src/quicktemplates/qquickmonthgrid_p.h +++ b/src/quicktemplates/qquickmonthgrid_p.h @@ -58,10 +58,10 @@ Q_SIGNALS: void titleChanged(); void delegateChanged(); - void pressed(const QDate &date); - void released(const QDate &date); - void clicked(const QDate &date); - void pressAndHold(const QDate &date); + void pressed(QDate date); + void released(QDate date); + void clicked(QDate date); + void pressAndHold(QDate date); protected: void componentComplete() override; @@ -79,6 +79,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickMonthGrid) - #endif // QQUICKMONTHGRID_P_H diff --git a/src/quicktemplates/qquickmonthmodel.cpp b/src/quicktemplates/qquickmonthmodel.cpp index 0910ea9be7..60d5e6fce2 100644 --- a/src/quicktemplates/qquickmonthmodel.cpp +++ b/src/quicktemplates/qquickmonthmodel.cpp @@ -138,7 +138,7 @@ QDate QQuickMonthModel::dateAt(int index) const return d->dates.value(index); } -int QQuickMonthModel::indexOf(const QDate &date) const +int QQuickMonthModel::indexOf(QDate date) const { Q_D(const QQuickMonthModel); if (date < d->dates.first() || date > d->dates.last()) diff --git a/src/quicktemplates/qquickmonthmodel_p.h b/src/quicktemplates/qquickmonthmodel_p.h index 8d1c0fc062..e629a7f8d1 100644 --- a/src/quicktemplates/qquickmonthmodel_p.h +++ b/src/quicktemplates/qquickmonthmodel_p.h @@ -50,7 +50,7 @@ public: void setTitle(const QString &title); Q_INVOKABLE QDate dateAt(int index) const; - Q_INVOKABLE int indexOf(const QDate &date) const; + Q_INVOKABLE int indexOf(QDate date) const; enum { DateRole = Qt::UserRole + 1, @@ -78,6 +78,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickMonthModel) - #endif // QQUICKMONTHMODEL_P_H diff --git a/src/quicktemplates/qquicknativeicon.cpp b/src/quicktemplates/qquicknativeicon.cpp new file mode 100644 index 0000000000..dfc8a4cc7e --- /dev/null +++ b/src/quicktemplates/qquicknativeicon.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquicknativeicon_p.h" + +QT_BEGIN_NAMESPACE + +QUrl QQuickNativeIcon::source() const +{ + return m_source; +} + +void QQuickNativeIcon::setSource(const QUrl& source) +{ + m_source = source; +} + +QString QQuickNativeIcon::name() const +{ + return m_name; +} + +void QQuickNativeIcon::setName(const QString& name) +{ + m_name = name; +} + +bool QQuickNativeIcon::isMask() const +{ + return m_mask; +} + +void QQuickNativeIcon::setMask(bool mask) +{ + m_mask = mask; +} + +bool QQuickNativeIcon::operator==(const QQuickNativeIcon &other) const +{ + return m_source == other.m_source && m_name == other.m_name && m_mask == other.m_mask; +} + +bool QQuickNativeIcon::operator!=(const QQuickNativeIcon &other) const +{ + return !(*this == other); +} + +QT_END_NAMESPACE + +#include "moc_qquicknativeicon_p.cpp" diff --git a/src/quicktemplates/qquicknativeicon_p.h b/src/quicktemplates/qquicknativeicon_p.h new file mode 100644 index 0000000000..db0625954a --- /dev/null +++ b/src/quicktemplates/qquicknativeicon_p.h @@ -0,0 +1,57 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKNATIVEICON_P_H +#define QQUICKNATIVEICON_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qurl.h> +#include <QtCore/qstring.h> + +#include <QtQml/qqmlengine.h> +#include <QtCore/private/qglobal_p.h> + +QT_BEGIN_NAMESPACE + +class QObject; + +class QQuickNativeIcon +{ + Q_GADGET + QML_ANONYMOUS + Q_PROPERTY(QUrl source READ source WRITE setSource FINAL) + Q_PROPERTY(QString name READ name WRITE setName FINAL) + Q_PROPERTY(bool mask READ isMask WRITE setMask FINAL) + +public: + QUrl source() const; + void setSource(const QUrl &source); + + QString name() const; + void setName(const QString &name); + + bool isMask() const; + void setMask(bool mask); + + bool operator==(const QQuickNativeIcon &other) const; + bool operator!=(const QQuickNativeIcon &other) const; + +private: + bool m_mask = false; + QUrl m_source; + QString m_name; +}; + +QT_END_NAMESPACE + +#endif // QQUICKNATIVEICON_P_H diff --git a/src/quicktemplates/qquicknativeiconloader.cpp b/src/quicktemplates/qquicknativeiconloader.cpp new file mode 100644 index 0000000000..8b5dd54257 --- /dev/null +++ b/src/quicktemplates/qquicknativeiconloader.cpp @@ -0,0 +1,66 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquicknativeiconloader_p.h" + +#include <QtCore/qobject.h> +#include <QtCore/qmetaobject.h> +#include <QtQml/qqml.h> + +QT_BEGIN_NAMESPACE + +QQuickNativeIconLoader::QQuickNativeIconLoader(int slot, QObject *parent) + : m_parent(parent), + m_slot(slot), + m_enabled(false) +{ + Q_ASSERT(slot != -1 && parent); +} + +bool QQuickNativeIconLoader::isEnabled() const +{ + return m_enabled; +} + +void QQuickNativeIconLoader::setEnabled(bool enabled) +{ + m_enabled = enabled; + if (m_enabled) + loadIcon(); +} + +QIcon QQuickNativeIconLoader::toQIcon() const +{ + const QIcon fallback = QPixmap::fromImage(image()); + return QIcon::fromTheme(m_icon.name(), fallback); +} + +QQuickIcon QQuickNativeIconLoader::icon() const +{ + return m_icon; +} + +void QQuickNativeIconLoader::setIcon(const QQuickIcon &icon) +{ + m_icon = icon; + if (m_enabled) + loadIcon(); +} + +void QQuickNativeIconLoader::loadIcon() +{ + if (m_icon.source().isEmpty()) { + clear(m_parent); + } else { + load(qmlEngine(m_parent), m_icon.source()); + if (m_slot != -1 && isLoading()) { + connectFinished(m_parent, m_slot); + m_slot = -1; + } + } + + if (!isLoading()) + m_parent->metaObject()->method(m_slot).invoke(m_parent); +} + +QT_END_NAMESPACE diff --git a/src/quicktemplates/qquicknativeiconloader_p.h b/src/quicktemplates/qquicknativeiconloader_p.h new file mode 100644 index 0000000000..063178dc47 --- /dev/null +++ b/src/quicktemplates/qquicknativeiconloader_p.h @@ -0,0 +1,54 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKNATIVEICONLOADER_P_H +#define QQUICKNATIVEICONLOADER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qurl.h> +#include <QtCore/qstring.h> +#include <QtGui/qicon.h> +#include <QtQuick/private/qquickpixmap_p.h> +#include <QtQuickTemplates2/private/qquickicon_p.h> + +#include "qquicknativeicon_p.h" + +QT_BEGIN_NAMESPACE + +class QObject; + +class QQuickNativeIconLoader : public QQuickPixmap +{ +public: + QQuickNativeIconLoader(int slot, QObject *parent); + + bool isEnabled() const; + void setEnabled(bool enabled); + + QIcon toQIcon() const; + + QQuickIcon icon() const; + void setIcon(const QQuickIcon &icon); + +private: + void loadIcon(); + + QObject *m_parent; + int m_slot; + bool m_enabled; + QQuickIcon m_icon; +}; + +QT_END_NAMESPACE + +#endif // QQUICKNATIVEICONLOADER_P_H diff --git a/src/quicktemplates/qquicknativemenuitem.cpp b/src/quicktemplates/qquicknativemenuitem.cpp new file mode 100644 index 0000000000..727fb87aa8 --- /dev/null +++ b/src/quicktemplates/qquicknativemenuitem.cpp @@ -0,0 +1,329 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquickmenuitem_p.h" + +#include <QtCore/qloggingcategory.h> +//#include <QtGui/qicon.h> +//#if QT_CONFIG(shortcut) +//#include <QtGui/qkeysequence.h> +//#endif +#include <QtGui/qpa/qplatformmenu.h> +#include <QtGui/qpa/qplatformtheme.h> +#include <QtGui/private/qguiapplication_p.h> +//#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h> +#include <QtQuickTemplates2/private/qquickabstractbutton_p_p.h> +#include <QtQuickTemplates2/private/qquickaction_p.h> +#include <QtQuickTemplates2/private/qquickmenu_p_p.h> +#include <QtQuickTemplates2/private/qquickmenuseparator_p.h> +#include <QtQuickTemplates2/private/qquicknativeiconloader_p.h> +#include <QtQuickTemplates2/private/qquicknativemenuitem_p.h> +#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcNativeMenuItem, "qt.quick.controls.nativemenuitem") + +/*! + \class QQuickNativeMenuItem + \brief A native menu item. + \since 6.7 + \internal + + Creates a native menu item from an Action/MenuItem/Menu, + and syncs the properties and signals. It can also represent a + MenuSeparator. + + \sa Menu, Action +*/ + +QQuickNativeMenuItem *QQuickNativeMenuItem::createFromNonNativeItem( + QQuickMenu *parentMenu, QQuickItem *nonNativeItem) +{ + auto *menuItem = qobject_cast<QQuickMenuItem *>(nonNativeItem); + Type type = Type::Unknown; + if (menuItem) { + if (menuItem->action()) { + type = Type::Action; + } else if (menuItem->subMenu()) { + type = Type::SubMenu; + } else { + // It's a plain MenuItem, rather than a MenuItem created by us for an Action or Menu. + type = Type::MenuItem; + } + } else if (qobject_cast<QQuickMenuSeparator *>(nonNativeItem)) { + type = Type::Separator; + } + + std::unique_ptr<QQuickNativeMenuItem> nativeMenuItemPtr(new QQuickNativeMenuItem( + parentMenu, nonNativeItem, type)); + if (type == Type::Unknown) { + // It's not a Menu/Action/MenuSeparator that we're dealing with, but we still need + // to create the QQuickNativeMenu item for it so that our container has the same + // indices as the menu's contentModel. + return nativeMenuItemPtr.release(); + } + + qCDebug(lcNativeMenuItem) << "attemping to create native menu item for" + << nativeMenuItemPtr->debugText(); + auto *parentMenuPrivate = QQuickMenuPrivate::get(parentMenu); + nativeMenuItemPtr->m_handle.reset(parentMenuPrivate->handle->createMenuItem()); + if (!nativeMenuItemPtr->m_handle) + nativeMenuItemPtr->m_handle.reset(QGuiApplicationPrivate::platformTheme()->createPlatformMenuItem()); + if (!nativeMenuItemPtr->m_handle) + return nullptr; + + auto *nativeMenuItem = nativeMenuItemPtr.release(); + switch (type) { + case Type::Action: + // Ensure that the action is triggered when the user clicks on a native menu item. + connect(nativeMenuItem->m_handle.get(), &QPlatformMenuItem::activated, + nativeMenuItem->action(), [nativeMenuItem, parentMenu](){ + nativeMenuItem->action()->trigger(parentMenu); + }); + // Handle programmatic changes in the Action. + connect(nativeMenuItem->action(), &QQuickAction::textChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + connect(nativeMenuItem->action(), &QQuickAction::iconChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + connect(nativeMenuItem->action(), &QQuickAction::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + connect(nativeMenuItem->action(), &QQuickAction::checkedChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + connect(nativeMenuItem->action(), &QQuickAction::checkableChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + break; + case Type::SubMenu: + nativeMenuItem->m_handle->setMenu(QQuickMenuPrivate::get( + nativeMenuItem->subMenu())->handle.get()); + + // Handle programmatic changes in the Menu. + connect(nativeMenuItem->subMenu(), &QQuickMenu::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + connect(nativeMenuItem->subMenu(), &QQuickMenu::titleChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + break; + case Type::MenuItem: + // Ensure that the MenuItem is clicked when the user clicks on a native menu item. + connect(nativeMenuItem->m_handle.get(), &QPlatformMenuItem::activated, + menuItem, [menuItem](){ + // This changes the checked state, which we need when syncing but also to ensure that + // the user can still use MenuItem's API even though they can't actually interact with it. + menuItem->toggle(); + // The same applies here: allow users to respond to the MenuItem's clicked signal. + QQuickAbstractButtonPrivate::get(menuItem)->click(); + }); + // Handle programmatic changes in the MenuItem. + connect(menuItem, &QQuickMenuItem::textChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + connect(menuItem, &QQuickMenuItem::iconChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + connect(menuItem, &QQuickMenuItem::enabledChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + connect(menuItem, &QQuickMenuItem::checkedChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + connect(menuItem, &QQuickMenuItem::checkableChanged, nativeMenuItem, &QQuickNativeMenuItem::sync); + break; + case Type::Separator: + case Type::Unknown: + break; + } + + return nativeMenuItem; +} + +QQuickNativeMenuItem::QQuickNativeMenuItem(QQuickMenu *parentMenu, QQuickItem *nonNativeItem, + QQuickNativeMenuItem::Type type) + : QObject(parentMenu) + , m_parentMenu(parentMenu) + , m_nonNativeItem(nonNativeItem) + , m_type(type) +{ +} + +QQuickNativeMenuItem::~QQuickNativeMenuItem() +{ + qCDebug(lcNativeMenuItem) << "destroying" << this << debugText(); +} + +QQuickAction *QQuickNativeMenuItem::action() const +{ + return m_type == Type::Action ? qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->action() : nullptr; +} + +QQuickMenu *QQuickNativeMenuItem::subMenu() const +{ + return m_type == Type::SubMenu ? qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->subMenu() : nullptr; +} + +QQuickMenuSeparator *QQuickNativeMenuItem::separator() const +{ + return m_type == Type::Separator ? qobject_cast<QQuickMenuSeparator *>(m_nonNativeItem) : nullptr; +} + +QPlatformMenuItem *QQuickNativeMenuItem::handle() const +{ + return m_handle.get(); +} + +void QQuickNativeMenuItem::sync() +{ + if (m_type == Type::Unknown) + return; + + if (m_syncing) + return; + + QScopedValueRollback recursionGuard(m_syncing, true); + + const auto *action = this->action(); + const auto *separator = this->separator(); + auto *subMenu = this->subMenu(); + auto *menuItem = qobject_cast<QQuickMenuItem *>(m_nonNativeItem); + + // Store the values in variables so that we can use it in the debug output below. + const bool enabled = action ? action->isEnabled() + : subMenu ? subMenu->isEnabled() + : menuItem && menuItem->isEnabled(); + m_handle->setEnabled(enabled); +// m_handle->setVisible(isVisible()); + + const bool isSeparator = separator != nullptr; + m_handle->setIsSeparator(isSeparator); + + const bool checkable = action ? action->isCheckable() : menuItem && menuItem->isCheckable(); + m_handle->setCheckable(checkable); + + const bool checked = action ? action->isChecked() : menuItem && menuItem->isChecked(); + m_handle->setChecked(checked); + + m_handle->setRole(QPlatformMenuItem::TextHeuristicRole); + + const QString text = action ? action->text() + : subMenu ? subMenu->title() + : menuItem ? menuItem->text() : QString(); + m_handle->setText(text); + +// m_handle->setFont(m_font); +// m_handle->setHasExclusiveGroup(m_group && m_group->isExclusive()); + m_handle->setHasExclusiveGroup(false); + + const QQuickIcon icon = effectiveIcon(); + const auto *menuPrivate = QQuickMenuPrivate::get(m_parentMenu); + const auto *window = qGuiApp->topLevelWindows().first(); + // We should reload the icon if the window's DPR has changed, regardless if its properties have changed. + // We can't check for ItemDevicePixelRatioHasChanged in QQuickMenu::itemChange, + // because that isn't sent when the menu isn't visible, and will never + // be sent for native menus. We instead store lastDevicePixelRatio in QQuickMenu + // (to avoid storing it for each menu item) and set it whenever it's opened. + const bool dprChanged = !qFuzzyCompare(window->devicePixelRatio(), menuPrivate->lastDevicePixelRatio); + if (!icon.isEmpty() && (icon != iconLoader()->icon() || dprChanged)) { + // This will load the icon, which will call sync() recursively, hence the m_syncing check. + reloadIcon(); + } + + if (subMenu) { + // Sync first as dynamically created menus may need to get the handle recreated. + auto *subMenuPrivate = QQuickMenuPrivate::get(subMenu); + subMenuPrivate->syncWithNativeMenu(); + if (subMenuPrivate->handle) + m_handle->setMenu(subMenuPrivate->handle.get()); + } + +#if QT_CONFIG(shortcut) + if (action) + m_handle->setShortcut(action->shortcut()); +#endif + + if (m_parentMenu) { + auto *menuPrivate = QQuickMenuPrivate::get(m_parentMenu); + if (menuPrivate->handle) + menuPrivate->handle->syncMenuItem(m_handle.get()); + } + + qCDebug(lcNativeMenuItem) << "sync called on" << debugText() << "handle" << m_handle.get() + << "enabled:" << enabled << "isSeparator" << isSeparator << "checkable" << checkable + << "checked" << checked << "text" << text; +} + +QQuickIcon QQuickNativeMenuItem::effectiveIcon() const +{ + if (const auto *action = this->action()) + return action->icon(); + if (const auto *subMenu = this->subMenu()) + return subMenu->icon(); + if (const auto *menuItem = qobject_cast<QQuickMenuItem *>(m_nonNativeItem)) + return menuItem->icon(); + return {}; +} + +QQuickNativeIconLoader *QQuickNativeMenuItem::iconLoader() const +{ + if (!m_iconLoader) { + QQuickNativeMenuItem *that = const_cast<QQuickNativeMenuItem *>(this); + static int slot = staticMetaObject.indexOfSlot("updateIcon()"); + m_iconLoader = new QQuickNativeIconLoader(slot, that); + // Qt Labs Platform's QQuickMenuItem would call m_iconLoader->setEnabled(m_complete) here, + // but since QQuickMenuPrivate::maybeCreateAndInsertNativeItem asserts that the menu's + // completed loading, we can just set it to true. + m_iconLoader->setEnabled(true); + } + return m_iconLoader; +} + +void QQuickNativeMenuItem::reloadIcon() +{ + iconLoader()->setIcon(effectiveIcon()); + m_handle->setIcon(iconLoader()->toQIcon()); +} + +void QQuickNativeMenuItem::updateIcon() +{ + sync(); +} + +void QQuickNativeMenuItem::addShortcut() +{ +#if QT_CONFIG(shortcut) + const auto *action = this->action(); + const QKeySequence sequence = action ? action->shortcut() : QKeySequence(); + if (!sequence.isEmpty() && action->isEnabled()) { + m_shortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, sequence, + Qt::WindowShortcut, QQuickShortcutContext::matcher); + } else { + m_shortcutId = -1; + } +#endif +} + +void QQuickNativeMenuItem::removeShortcut() +{ +#if QT_CONFIG(shortcut) + if (m_shortcutId == -1) + return; + + QKeySequence sequence; + switch (m_type) { + case Type::Action: + sequence = action()->shortcut(); + break; + default: + // TODO + break; + } + + QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(m_shortcutId, this, sequence); +#endif +} + +QString QQuickNativeMenuItem::debugText() const +{ + switch (m_type) { + case Type::Action: + return QString::fromLatin1("Action(text = %1)").arg(action()->text()); + case Type::SubMenu: + return QString::fromLatin1("Sub-menu(title = %1)").arg(subMenu()->title()); + case Type::MenuItem: + return QString::fromLatin1("MenuItem(text = %1)").arg( + qobject_cast<QQuickMenuItem *>(m_nonNativeItem)->text()); + case Type::Separator: + return QStringLiteral("Separator"); + case Type::Unknown: + return QStringLiteral("(Unknown)"); + } + + Q_UNREACHABLE(); +} + +QT_END_NAMESPACE + +#include "moc_qquicknativemenuitem_p.cpp" diff --git a/src/quicktemplates/qquicknativemenuitem_p.h b/src/quicktemplates/qquicknativemenuitem_p.h new file mode 100644 index 0000000000..421d84df29 --- /dev/null +++ b/src/quicktemplates/qquicknativemenuitem_p.h @@ -0,0 +1,81 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKNATIVEMENUITEM_P_H +#define QQUICKNATIVEMENUITEM_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> +#include <QtQuickTemplates2/private/qquickicon_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickAction; +class QQuickNativeIconLoader; +class QQuickMenu; +class QQuickMenuSeparator; +class QPlatformMenuItem; + +class Q_QUICKTEMPLATES2_EXPORT QQuickNativeMenuItem : public QObject +{ + Q_OBJECT + +public: + static QQuickNativeMenuItem *createFromNonNativeItem( + QQuickMenu *parentMenu, QQuickItem *nonNativeItem); + ~QQuickNativeMenuItem(); + + QQuickAction *action() const; + QQuickMenu *subMenu() const; + QQuickMenuSeparator *separator() const; + QPlatformMenuItem *handle() const; + void sync(); + + QQuickIcon effectiveIcon() const; + QQuickNativeIconLoader *iconLoader() const; + void reloadIcon(); + + QString debugText() const; + +private Q_SLOTS: + void updateIcon(); + +private: + enum class Type { + Unknown, + // It's an Action or a MenuItem with an Action. + Action, + // It's a MenuItem without an Action. + MenuItem, + Separator, + SubMenu + }; + + explicit QQuickNativeMenuItem(QQuickMenu *parentMenu, QQuickItem *nonNativeItem, Type type); + + void addShortcut(); + void removeShortcut(); + + QQuickMenu *m_parentMenu = nullptr; + QQuickItem *m_nonNativeItem = nullptr; + Type m_type = Type::Unknown; + mutable QQuickNativeIconLoader *m_iconLoader = nullptr; + std::unique_ptr<QPlatformMenuItem> m_handle = nullptr; + int m_shortcutId = -1; + bool m_syncing = false; +}; + +QT_END_NAMESPACE + +#endif // QQUICKNATIVEMENUITEM_P_H diff --git a/src/quicktemplates/qquickoverlay.cpp b/src/quicktemplates/qquickoverlay.cpp index bbbde24602..36fa669d22 100644 --- a/src/quicktemplates/qquickoverlay.cpp +++ b/src/quicktemplates/qquickoverlay.cpp @@ -9,6 +9,7 @@ #include "qquickdrawer_p.h" #include "qquickdrawer_p_p.h" #include "qquickapplicationwindow_p.h" +#include <QtGui/qpainterpath.h> #include <QtQml/qqmlinfo.h> #include <QtQml/qqmlproperty.h> #include <QtQml/qqmlcomponent.h> @@ -66,6 +67,11 @@ void QQuickOverlayPrivate::itemGeometryChanged(QQuickItem *, QQuickGeometryChang updateGeometry(); } +void QQuickOverlayPrivate::itemRotationChanged(QQuickItem *) +{ + updateGeometry(); +} + bool QQuickOverlayPrivate::startDrag(QEvent *event, const QPointF &pos) { Q_Q(QQuickOverlay); @@ -256,38 +262,15 @@ void QQuickOverlayPrivate::setMouseGrabberPopup(QQuickPopup *popup) void QQuickOverlayPrivate::updateGeometry() { Q_Q(QQuickOverlay); - if (!window) + if (!window || !window->contentItem()) return; - QPointF pos; - QSizeF size = window->size(); - qreal rotation = 0; - - switch (window->contentOrientation()) { - case Qt::PrimaryOrientation: - case Qt::PortraitOrientation: - size = window->size(); - break; - case Qt::LandscapeOrientation: - rotation = 90; - pos = QPointF((size.width() - size.height()) / 2, -(size.width() - size.height()) / 2); - size.transpose(); - break; - case Qt::InvertedPortraitOrientation: - rotation = 180; - break; - case Qt::InvertedLandscapeOrientation: - rotation = 270; - pos = QPointF((size.width() - size.height()) / 2, -(size.width() - size.height()) / 2); - size.transpose(); - break; - default: - break; - } + const QSizeF size = window->contentItem()->size(); + const QPointF pos(-(size.width() - window->size().width()) / 2, + -(size.height() - window->size().height()) / 2); q->setSize(size); q->setPosition(pos); - q->setRotation(rotation); } QQuickOverlay::QQuickOverlay(QQuickItem *parent) @@ -307,7 +290,8 @@ QQuickOverlay::QQuickOverlay(QQuickItem *parent) QQuickItemPrivate::get(parent)->addItemChangeListener(d, QQuickItemPrivate::Geometry); if (QQuickWindow *window = parent->window()) { window->installEventFilter(this); - QObjectPrivate::connect(window, &QWindow::contentOrientationChanged, d, &QQuickOverlayPrivate::updateGeometry); + if (QQuickItem *contentItem = window->contentItem()) + QQuickItemPrivate::get(contentItem)->addItemChangeListener(d, QQuickItemPrivate::Rotation); } } } @@ -315,8 +299,13 @@ QQuickOverlay::QQuickOverlay(QQuickItem *parent) QQuickOverlay::~QQuickOverlay() { Q_D(QQuickOverlay); - if (QQuickItem *parent = parentItem()) + if (QQuickItem *parent = parentItem()) { QQuickItemPrivate::get(parent)->removeItemChangeListener(d, QQuickItemPrivate::Geometry); + if (QQuickWindow *window = parent->window()) { + if (QQuickItem *contentItem = window->contentItem()) + QQuickItemPrivate::get(contentItem)->removeItemChangeListener(d, QQuickItemPrivate::Rotation); + } + } } QQmlComponent *QQuickOverlay::modal() const @@ -392,7 +381,7 @@ void QQuickOverlay::geometryChange(const QRectF &newGeometry, const QRectF &oldG Q_D(QQuickOverlay); QQuickItem::geometryChange(newGeometry, oldGeometry); for (QQuickPopup *popup : std::as_const(d->allPopups)) - QQuickPopupPrivate::get(popup)->resizeOverlay(); + QQuickPopupPrivate::get(popup)->resizeDimmer(); } void QQuickOverlay::mousePressEvent(QMouseEvent *event) @@ -522,6 +511,12 @@ bool QQuickOverlay::eventFilter(QObject *object, QEvent *event) // Make sure to accept the touch event in order to receive the consequent // touch events, to be able to close non-modal popups on release outside. event->accept(); + // Since we eat the event, QQuickWindow::event never sees it to clean up the + // grabber states. So we have to do so explicitly. + if (QQuickWindow *window = parentItem() ? parentItem()->window() : nullptr) { + QQuickWindowPrivate *d = QQuickWindowPrivate::get(window); + d->clearGrabbers(static_cast<QPointerEvent *>(event)); + } return true; #endif @@ -567,7 +562,18 @@ bool QQuickOverlay::eventFilter(QObject *object, QEvent *event) if (targetItems.isEmpty()) break; + QQuickItem * const dimmerItem = property("_q_dimmerItem").value<QQuickItem *>(); QQuickItem * const topItem = targetItems.first(); + + QQuickItem *item = topItem; + while ((item = item->parentItem())) { + if (qobject_cast<QQuickPopupItem *>(item)) + break; + } + + if (!item && dimmerItem != topItem && isAncestorOf(topItem)) + break; + const auto popups = d->stackingOrderPopups(); // Eat the event if receiver topItem is not a child of a popup before // the first modal popup. @@ -575,8 +581,8 @@ bool QQuickOverlay::eventFilter(QObject *object, QEvent *event) const QQuickItem *popupItem = popup->popupItem(); if (!popupItem) continue; - // if we reach a popup that contains the item, deliver the event - if (popupItem->isAncestorOf(topItem)) + // if current popup item matches with any popup in stack, deliver the event + if (popupItem == item) break; // if the popup doesn't contain the item but is modal, eat the event if (popup->overlayEvent(topItem, we)) diff --git a/src/quicktemplates/qquickoverlay_p.h b/src/quicktemplates/qquickoverlay_p.h index 47f7e62615..16f0ba09db 100644 --- a/src/quicktemplates/qquickoverlay_p.h +++ b/src/quicktemplates/qquickoverlay_p.h @@ -25,7 +25,7 @@ class QQuickOverlayPrivate; class QQuickOverlayAttached; class QQuickOverlayAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickOverlay : public QQuickItem +class Q_QUICKTEMPLATES2_EXPORT QQuickOverlay : public QQuickItem { Q_OBJECT Q_PROPERTY(QQmlComponent *modal READ modal WRITE setModal NOTIFY modalChanged FINAL) @@ -76,7 +76,7 @@ private: Q_DECLARE_PRIVATE(QQuickOverlay) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickOverlayAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickOverlayAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickOverlay *overlay READ overlay NOTIFY overlayChanged FINAL) @@ -108,6 +108,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickOverlay) - #endif // QQUICKOVERLAY_P_H diff --git a/src/quicktemplates/qquickoverlay_p_p.h b/src/quicktemplates/qquickoverlay_p_p.h index 38c214804f..3d96540d45 100644 --- a/src/quicktemplates/qquickoverlay_p_p.h +++ b/src/quicktemplates/qquickoverlay_p_p.h @@ -20,6 +20,8 @@ #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickitemchangelistener_p.h> +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE class QQuickPopup; @@ -54,6 +56,7 @@ public: QList<QQuickPopup *> stackingOrderDrawers() const; void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) override; + void itemRotationChanged(QQuickItem *item) override; void updateGeometry(); @@ -64,6 +67,7 @@ public: // QQuickDrawer by the time removePopup is called. QList<QQuickPopup *> allDrawers; QPointer<QQuickPopup> mouseGrabberPopup; + QPointer<QQuickItem> lastActiveFocusItem; }; QT_END_NAMESPACE diff --git a/src/quicktemplates/qquickpage.cpp b/src/quicktemplates/qquickpage.cpp index 28348c2518..9f19f5bae3 100644 --- a/src/quicktemplates/qquickpage.cpp +++ b/src/quicktemplates/qquickpage.cpp @@ -3,9 +3,11 @@ #include "qquickpage_p.h" #include "qquickpage_p_p.h" -#include "qquicktabbar_p.h" #include "qquicktoolbar_p.h" +#if QT_CONFIG(quicktemplates2_container) +#include "qquicktabbar_p.h" #include "qquickdialogbuttonbox_p.h" +#endif QT_BEGIN_NAMESPACE @@ -75,23 +77,27 @@ namespace { Footer }; +Q_STATIC_ASSERT(int(Header) == int(QQuickToolBar::Header)); +Q_STATIC_ASSERT(int(Footer) == int(QQuickToolBar::Footer)); + +#if QT_CONFIG(quicktemplates2_container) Q_STATIC_ASSERT(int(Header) == int(QQuickTabBar::Header)); Q_STATIC_ASSERT(int(Footer) == int(QQuickTabBar::Footer)); - Q_STATIC_ASSERT(int(Header) == int(QQuickToolBar::Header)); - Q_STATIC_ASSERT(int(Footer) == int(QQuickToolBar::Footer)); - Q_STATIC_ASSERT(int(Header) == int(QQuickDialogButtonBox::Header)); Q_STATIC_ASSERT(int(Footer) == int(QQuickDialogButtonBox::Footer)); +#endif static void setPos(QQuickItem *item, Position position) { if (QQuickToolBar *toolBar = qobject_cast<QQuickToolBar *>(item)) toolBar->setPosition(static_cast<QQuickToolBar::Position>(position)); +#if QT_CONFIG(quicktemplates2_container) else if (QQuickTabBar *tabBar = qobject_cast<QQuickTabBar *>(item)) tabBar->setPosition(static_cast<QQuickTabBar::Position>(position)); else if (QQuickDialogButtonBox *buttonBox = qobject_cast<QQuickDialogButtonBox *>(item)) buttonBox->setPosition(static_cast<QQuickDialogButtonBox::Position>(position)); +#endif } } @@ -110,8 +116,10 @@ void QQuickPagePrivate::relayout() contentItem->setHeight(q->availableHeight() - hh - fh - hsp - fsp); } - if (header) + if (header) { + header->setY(0); header->setWidth(q->width()); + } if (footer) { footer->setY(q->height() - footer->height()); @@ -272,6 +280,11 @@ void QQuickPage::setTitle(const QString &title) emit titleChanged(); } +void QQuickPage::resetTitle() +{ + setTitle(QString()); +} + /*! \qmlproperty Item QtQuick.Controls::Page::header diff --git a/src/quicktemplates/qquickpage_p.h b/src/quicktemplates/qquickpage_p.h index b7ce7e9bc9..7eaa443146 100644 --- a/src/quicktemplates/qquickpage_p.h +++ b/src/quicktemplates/qquickpage_p.h @@ -22,10 +22,10 @@ QT_BEGIN_NAMESPACE class QQuickPagePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPage : public QQuickPane +class Q_QUICKTEMPLATES2_EXPORT QQuickPage : public QQuickPane { Q_OBJECT - Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged FINAL) + Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged RESET resetTitle FINAL) Q_PROPERTY(QQuickItem *header READ header WRITE setHeader NOTIFY headerChanged FINAL) Q_PROPERTY(QQuickItem *footer READ footer WRITE setFooter NOTIFY footerChanged FINAL) // 2.5 (Qt 5.12) @@ -42,6 +42,7 @@ public: QString title() const; void setTitle(const QString &title); + void resetTitle(); QQuickItem *header() const; void setHeader(QQuickItem *header); @@ -85,6 +86,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickPage) - #endif // QQUICKPAGE_P_H diff --git a/src/quicktemplates/qquickpageindicator.cpp b/src/quicktemplates/qquickpageindicator.cpp index b0bc5188fe..8b77b79e9f 100644 --- a/src/quicktemplates/qquickpageindicator.cpp +++ b/src/quicktemplates/qquickpageindicator.cpp @@ -177,6 +177,13 @@ QQuickPageIndicator::QQuickPageIndicator(QQuickItem *parent) { } +QQuickPageIndicator::~QQuickPageIndicator() +{ + Q_D(QQuickPageIndicator); + if (d->contentItem) + QQuickItemPrivate::get(d->contentItem)->removeItemChangeListener(d, QQuickItemPrivate::Children); +} + /*! \qmlproperty int QtQuick.Controls::PageIndicator::count diff --git a/src/quicktemplates/qquickpageindicator_p.h b/src/quicktemplates/qquickpageindicator_p.h index d6c648716c..db4d83fad7 100644 --- a/src/quicktemplates/qquickpageindicator_p.h +++ b/src/quicktemplates/qquickpageindicator_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QQmlComponent; class QQuickPageIndicatorPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPageIndicator : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickPageIndicator : public QQuickControl { Q_OBJECT Q_PROPERTY(int count READ count WRITE setCount NOTIFY countChanged FINAL) @@ -34,6 +34,7 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPageIndicator : public QQuickContro public: explicit QQuickPageIndicator(QQuickItem *parent = nullptr); + ~QQuickPageIndicator() override; int count() const; void setCount(int count); @@ -71,6 +72,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickPageIndicator) - #endif // QQUICKPAGEINDICATOR_P_H diff --git a/src/quicktemplates/qquickpane.cpp b/src/quicktemplates/qquickpane.cpp index 9f14843db4..02f32e720e 100644 --- a/src/quicktemplates/qquickpane.cpp +++ b/src/quicktemplates/qquickpane.cpp @@ -30,6 +30,10 @@ Q_LOGGING_CATEGORY(lcPane, "qt.quick.controls.pane") Pane's \l[QtQuickControls2]{Control::}{contentItem}. Items created dynamically need to be explicitly parented to the \c contentItem. + As mentioned in \l {Event Handling}, Pane does not let click and touch + events through to items beneath it. If \l [QML] {Control::}{wheelEnabled} + is \c true, the same applies to mouse wheel events. + \section1 Content Sizing If only a single item is used within a Pane, it will resize to fit the @@ -104,6 +108,7 @@ void QQuickPanePrivate::init() #endif connect(q, &QQuickControl::implicitContentWidthChanged, this, &QQuickPanePrivate::updateContentWidth); connect(q, &QQuickControl::implicitContentHeightChanged, this, &QQuickPanePrivate::updateContentHeight); + setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred); } QList<QQuickItem *> QQuickPanePrivate::contentChildItems() const @@ -139,14 +144,25 @@ void QQuickPanePrivate::itemImplicitHeightChanged(QQuickItem *item) updateImplicitContentHeight(); } +void QQuickPanePrivate::itemDestroyed(QQuickItem *item) +{ + // Do this check before calling the base class implementation, as that clears contentItem. + if (item == firstChild) + firstChild = nullptr; + + QQuickControlPrivate::itemDestroyed(item); +} + void QQuickPanePrivate::contentChildrenChange() { Q_Q(QQuickPane); - QQuickItem *newFirstChild = contentChildItems().value(0); + + QQuickItem *newFirstChild = getFirstChild(); + if (newFirstChild != firstChild) { if (firstChild) removeImplicitSizeListener(firstChild); - if (newFirstChild) + if (newFirstChild && newFirstChild != contentItem) addImplicitSizeListener(newFirstChild); firstChild = newFirstChild; } @@ -171,6 +187,16 @@ qreal QQuickPanePrivate::getContentWidth() const return 0; } +QQuickItem* QQuickPanePrivate::getFirstChild() const +{ + // The first child varies depending on how the content item is declared. + // If it's declared as a child of the Pane, it will be parented to the + // default QQuickContentItem. If it's assigned to the contentItem property + // directly, QQuickControl::contentItem will be used. + return (qobject_cast<QQuickContentItem *>(contentItem) + ? contentChildItems().value(0) : contentItem.data()); +} + qreal QQuickPanePrivate::getContentHeight() const { if (!contentItem) diff --git a/src/quicktemplates/qquickpane_p.h b/src/quicktemplates/qquickpane_p.h index d059e629d4..a2a5cbbff6 100644 --- a/src/quicktemplates/qquickpane_p.h +++ b/src/quicktemplates/qquickpane_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QQuickPanePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPane : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickPane : public QQuickControl { Q_OBJECT Q_PROPERTY(qreal contentWidth READ contentWidth WRITE setContentWidth RESET resetContentWidth NOTIFY contentWidthChanged FINAL) @@ -69,6 +69,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickPane) - #endif // QQUICKPANE_P_H diff --git a/src/quicktemplates/qquickpane_p_p.h b/src/quicktemplates/qquickpane_p_p.h index 7a8e68a068..71da80090a 100644 --- a/src/quicktemplates/qquickpane_p_p.h +++ b/src/quicktemplates/qquickpane_p_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickPane; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPanePrivate : public QQuickControlPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickPanePrivate : public QQuickControlPrivate { public: Q_DECLARE_PUBLIC(QQuickPane) @@ -31,6 +31,7 @@ public: virtual QQmlListProperty<QObject> contentData(); virtual QQmlListProperty<QQuickItem> contentChildren(); virtual QList<QQuickItem *> contentChildItems() const; + virtual QQuickItem *getFirstChild() const; QQuickItem *getContentItem() override; @@ -39,6 +40,7 @@ public: void itemImplicitWidthChanged(QQuickItem *item) override; void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; void contentChildrenChange(); diff --git a/src/quicktemplates/qquickpopup.cpp b/src/quicktemplates/qquickpopup.cpp index 5b26789951..1af14d37ce 100644 --- a/src/quicktemplates/qquickpopup.cpp +++ b/src/quicktemplates/qquickpopup.cpp @@ -5,22 +5,28 @@ #include "qquickpopup_p_p.h" #include "qquickpopupanchors_p.h" #include "qquickpopupitem_p_p.h" +#include "qquickpopupwindow_p_p.h" #include "qquickpopuppositioner_p_p.h" #include "qquickapplicationwindow_p.h" #include "qquickoverlay_p_p.h" #include "qquickcontrol_p_p.h" +#if QT_CONFIG(quicktemplates2_container) #include "qquickdialog_p.h" +#endif #include <QtCore/qloggingcategory.h> #include <QtQml/qqmlinfo.h> #include <QtQuick/qquickitem.h> +#include <QtQuick/private/qquickaccessibleattached_p.h> #include <QtQuick/private/qquicktransition_p.h> #include <QtQuick/private/qquickitem_p.h> +#include <qpa/qplatformintegration.h> +#include <private/qguiapplication_p.h> QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcDimmer, "qt.quick.controls.popup.dimmer") -Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") +Q_LOGGING_CATEGORY(lcQuickPopup, "qt.quick.controls.popup") /*! \qmltype Popup @@ -36,8 +42,8 @@ Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") used with \l Window or \l ApplicationWindow. \qml - import QtQuick.Window 2.2 - import QtQuick.Controls 2.12 + import QtQuick.Window + import QtQuick.Controls ApplicationWindow { id: window @@ -63,10 +69,6 @@ Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") } \endqml - In order to ensure that a popup is displayed above other items in the - scene, it is recommended to use ApplicationWindow. ApplicationWindow also - provides background dimming effects. - Popup does not provide a layout of its own, but requires you to position its contents, for instance by creating a \l RowLayout or a \l ColumnLayout. @@ -118,6 +120,57 @@ Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") } \endcode + \section1 Popup type + + Since Qt 6.8, some popups, such as \l Menu, offer three different implementations, + depending on the platform. You can choose which one you prefer by setting \l popupType. + + Whether a popup will be able to use the preferred type depends on the platform. + \c Popup.Item is supported on all platforms, but \c Popup.Window and \c Popup.Native + are normally only supported on desktop platforms. Additionally, if a popup is a + \l Menu inside a \l {Native menu bars}{native menubar}, the menu will be native as + well. And if the menu is a sub-menu inside another menu, the parent (or root) menu + will decide the type. + + \section2 Showing a popup as an item + + By setting \l popupType to \c Popup.Item, the popup will \e not be shown as a separate + window, but as an item inside the same scene as the parent. This item is parented + to that scene's \l{Overlay::overlay}{overlay}, and styled to look like an actual window. + + This option is especially useful on platforms that doesn't support multiple windows. + This was also the only option before Qt 6.8. + + In order to ensure that a popup is displayed above other items in the + scene, it is recommended to use ApplicationWindow. ApplicationWindow also + provides background dimming effects. + + \section2 Showing a popup as a separate window + + By setting \l popupType to \c Popup.Window, the popup will be shown inside a top-level + \l {QQuickWindow}{window} configured with the \l Qt::Popup flag. Using a window to show a + popup has the advantage that the popup will float on top of the parent window, and + can be placed outside of its geometry. The popup will otherwise look the same as when + using \c Popup.Item, that is, it will use the same QML delegates and styling as + when using \c Popup.Item. + + \note If the platform doesn't support \c Popup.Window, \c Popup.Item will be used as fallback. + + \section2 Showing a native popup + + By setting \l popupType to \c Popup.Native, the popup will be shown using a platform + native popup window. This window, and all its contents, will be rendered by the + platform, and not by QML. This means that the QML delegates assigned to the popup + will \e not be used for rendering. If you for example + use this option on a \l Menu, it will be implemented using platform-specific + menu APIs. This will normally make the popup look and feel more native than for example + \c Popup.Window, but at the same time, suffer from platform limitations and differences + related to appearance and behavior. Such limitations are documented in more detail + in the subclasses that are affected, such as for a + \l {Limitations when using native menus}{Menu}). + + \note If the platform doesn't support \c Popup.Native, \c Popup.Window will be used as fallback. + \section1 Popup Sizing If only a single item is used within a Popup, it will resize to fit the @@ -169,6 +222,36 @@ Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") } \endcode + \note When using \l {Popup Items}, the popup's \l{contentItem}{content item} gets parented to the + \l{Overlay::overlay}{overlay}, and does not live within the popup's parent. + Because of that, a \l{Item::scale}{scale} applied to the tree in which + the popup lives does not apply to the visual popup. To make the popup + of e.g. a \l{ComboBox} follow the scale of the combobox, apply the same scale + to the \l{Overlay::overlay}{overlay} as well: + + \code + Window { + property double scaleFactor: 2.0 + + Scale { + id: scale + xScale: scaleFactor + yScale: scaleFactor + } + Item { + id: scaledContent + transform: scale + + ComboBox { + id: combobox + // ... + } + } + + Overlay.overlay.transform: scale + } + \endcode + \section1 Popup Positioning Similar to items in Qt Quick, Popup's \l x and \l y coordinates are @@ -186,6 +269,47 @@ Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") To ensure that the popup is positioned within the bounds of the enclosing window, the \l margins property can be set to a non-negative value. + \section1 Showing Non-Child Items in Front of Popup + + In cases where \l {Showing a popup as an item}{popup windows} are not being used, + Popup sets its contentItem's \l{qtquick-visualcanvas-visualparent.html}{visual parent} + to be the window's \l{Overlay::overlay}{overlay}, in order to ensure that + the popup appears in front of everything else in the scene. + In some cases, it might be useful to put an item in front of a popup, + such as a \l [QML QtVirtualKeyboard] {InputPanel} {virtual keyboard}. + This can be done by setting the item's parent to the overlay, + and giving the item a positive z value. The same result can also be + achieved by waiting until the popup is opened, before re-parenting the item + to the overlay. + + \omit + This shouldn't be a snippet, since we don't want VKB to be a dependency to controls. + \endomit + \qml + Popup { + id: popup + visible: true + anchors.centerIn: parent + margins: 10 + closePolicy: Popup.CloseOnEscape + ColumnLayout { + TextField { + placeholderText: qsTr("Username") + } + TextField { + placeholderText: qsTr("Password") + echoMode: TextInput.Password + } + } + } + InputPanel { + parent: Overlay.overlay + width: parent.width + y: popup.y + popup.topMargin + (window.activeFocusItem?.y ?? 0) + (window.activeFocusItem?.height ?? 0) + z: 1 + } + \endqml + \section1 Popup Transitions Since Qt 5.15.3 the following properties are restored to their original values from before @@ -220,6 +344,50 @@ Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") \endlist \sa {Popup Controls}, {Customizing Popup}, ApplicationWindow + + \section1 Property Propagation + + Popup inherits fonts, palettes and attached properties through its parent + window, not its \l {Visual Parent}{object or visual parent}: + + \snippet qtquickcontrols-popup-property-propagation.qml file + + \image qtquickcontrols-basic-popup-property-propagation.png + + In addition, popups do not propagate their properties to child popups. This + behavior is modelled on Qt Widgets, where a \c Qt::Popup widget is a + top-level window. Top-level windows do not propagate their properties to + child windows. + + Certain derived types like ComboBox are typically implemented in such a way + that the popup is considered an integral part of the control, and as such, + may inherit things like attached properties. For example, in the + \l {Material Style}{Material style} ComboBox, the theme and other attached + properties are explicitly inherited by the Popup from the ComboBox itself: + + \code + popup: T.Popup { + // ... + + Material.theme: control.Material.theme + Material.accent: control.Material.accent + Material.primary: control.Material.primary + } + \endcode + + So, to ensure that a child popup has the same property values as its parent + popup, explicitly set those properties: + + \code + Popup { + id: parentPopup + // ... + + Popup { + palette: parentPopup.palette + } + } + \endcode */ /*! @@ -254,6 +422,18 @@ Q_LOGGING_CATEGORY(lcPopup, "qt.quick.controls.popup") \sa closed() */ +QQuickItem *QQuickPopup::findParentItem() const +{ + QObject *obj = parent(); + while (obj) { + QQuickItem *item = qobject_cast<QQuickItem *>(obj); + if (item) + return item; + obj = obj->parent(); + } + return nullptr; +} + const QQuickPopup::ClosePolicy QQuickPopupPrivate::DefaultClosePolicy = QQuickPopup::CloseOnEscape | QQuickPopup::CloseOnPressOutside; QQuickPopupPrivate::QQuickPopupPrivate() @@ -266,7 +446,6 @@ void QQuickPopupPrivate::init() Q_Q(QQuickPopup); popupItem = new QQuickPopupItem(q); popupItem->setVisible(false); - q->setParentItem(qobject_cast<QQuickItem *>(parent)); QObject::connect(popupItem, &QQuickControl::paddingChanged, q, &QQuickPopup::paddingChanged); QObject::connect(popupItem, &QQuickControl::backgroundChanged, q, &QQuickPopup::backgroundChanged); QObject::connect(popupItem, &QQuickControl::contentItemChanged, q, &QQuickPopup::contentItemChanged); @@ -279,9 +458,11 @@ void QQuickPopupPrivate::init() void QQuickPopupPrivate::closeOrReject() { Q_Q(QQuickPopup); +#if QT_CONFIG(quicktemplates2_container) if (QQuickDialog *dialog = qobject_cast<QQuickDialog*>(q)) dialog->reject(); else +#endif q->close(); touchId = -1; } @@ -340,7 +521,7 @@ bool QQuickPopupPrivate::blockInput(QQuickItem *item, const QPointF &point) cons // a) outside a non-modal popup, // b) to popup children/content, or // b) outside a modal popups's background dimming - return modal && !popupItem->isAncestorOf(item) && (!dimmer || dimmer->contains(dimmer->mapFromScene(point))); + return modal && ((popupItem != item) && !popupItem->isAncestorOf(item)) && (!dimmer || dimmer->contains(dimmer->mapFromScene(point))); } bool QQuickPopupPrivate::handlePress(QQuickItem *item, const QPointF &point, ulong timestamp) @@ -348,7 +529,16 @@ bool QQuickPopupPrivate::handlePress(QQuickItem *item, const QPointF &point, ulo Q_UNUSED(timestamp); pressPoint = point; outsidePressed = !contains(point); - outsideParentPressed = outsidePressed && parentItem && !parentItem->contains(parentItem->mapFromScene(point)); + + if (outsidePressed && parentItem) { + // Note that the parentItem (e.g a menuBarItem, in case of a MenuBar) will + // live inside another window when using popup windows. We therefore need to + // map to and from global. + const QPointF globalPoint = item->mapToGlobal(point); + const QPointF localPoint = parentItem->mapFromGlobal(globalPoint); + outsideParentPressed = !parentItem->contains(localPoint); + } + tryClose(point, QQuickPopup::CloseOnPressOutside | QQuickPopup::CloseOnPressOutsideParent); return blockInput(item, point); } @@ -410,6 +600,54 @@ bool QQuickPopupPrivate::handleHoverEvent(QQuickItem *item, QHoverEvent *event) } } +QPointF QQuickPopupPrivate::dropShadowOffset() const +{ + // If the popupWindowType has Qt::FramelessWindowHint set, it means + // that the background delegate is responsible for drawing the window + // frame. And to make room for a drop-shadow in that case, the delegate + // can be shifted into the popup using insets, to allow the shadow to + // be drawn between the edge of the window and the edge of the frame. + // This function will report the size of that shadow, to ensure that we + // place the popup such that the top-left corner of the background + // frame ends up at the requested position, rather then the top-left + // corner of the drop-shadow. If the insets are negative, the shadow + // is drawn on the outside of the popup, and the corner of the frame + // should already be at 0, 0. + if (popupWindowType() & Qt::FramelessWindowHint) { + return { + popupItem->leftInset() > 0 ? popupItem->leftInset() : 0, + popupItem->topInset() > 0 ? popupItem->topInset() : 0 + }; + } + + return {0, 0}; +} + +void QQuickPopupPrivate::setEffectivePosFromWindowPos(const QPointF &windowPos) +{ + // Popup operates internally with three different positions; requested + // position, effective position, and window position. The first is the + // position requested by the application, and the second is where the popup + // is actually placed. The reason for placing it on a different position than + // the one requested, is to keep it inside the window (in case of Popup.Item), + // or the screen (in case of Popup.Window). + // Additionally, since a popup can set Qt::FramelessWindowHint and draw the + // window frame from the background delegate, the effective position in that + // case is adjusted to be the top-left corner of the background delegate, rather + // than the top-left corner of the window. This allowes the background delegate + // to render a drop-shadow between the edge of the window and the background frame. + // Finally, the window position is the actual position of the window, including + // any drop-shadow effects. This posision can be calculated by taking + // the effective position and subtract the dropShadowOffset(). + Q_Q(QQuickPopup); + const QPointF oldEffectivePos = effectivePos; + effectivePos = windowPos + dropShadowOffset(); + if (!qFuzzyCompare(oldEffectivePos.x(), effectivePos.x())) + emit q->xChanged(); + if (!qFuzzyCompare(oldEffectivePos.y(), effectivePos.y())) + emit q->yChanged(); +} + #if QT_CONFIG(quicktemplates2_multitouch) bool QQuickPopupPrivate::handleTouchEvent(QQuickItem *item, QTouchEvent *event) { @@ -458,37 +696,21 @@ bool QQuickPopupPrivate::prepareEnterTransition() return false; if (transitionState != EnterTransition) { - QQuickOverlay *overlay = QQuickOverlay::overlay(window); - const auto popupStack = QQuickOverlayPrivate::get(overlay)->stackingOrderPopups(); - popupItem->setParentItem(overlay); - // if there is a stack of popups, and the current top popup item belongs to an - // ancestor of this popup, then make sure that this popup's item is at the top - // of the stack. - const QQuickPopup *topPopup = popupStack.isEmpty() ? nullptr : popupStack.first(); - const QObject *ancestor = q; - while (ancestor && topPopup) { - if (ancestor == topPopup) - break; - ancestor = ancestor->parent(); - } - if (topPopup && topPopup != q && ancestor) { - QQuickItem *topPopupItem = popupStack.first()->popupItem(); - popupItem->stackAfter(topPopupItem); - // If the popup doesn't have an explicit z value set, set it to be at least as - // high as the current top popup item so that later opened popups are on top. - if (!hasZ) - popupItem->setZ(qMax(topPopupItem->z(), popupItem->z())); - } + visible = true; + adjustPopupItemParentAndWindow(); if (dim) createOverlay(); - showOverlay(); + showDimmer(); emit q->aboutToShow(); - visible = true; transitionState = EnterTransition; - popupItem->setVisible(true); getPositioner()->setParentItem(parentItem); emit q->visibleChanged(); + QQuickOverlay *overlay = QQuickOverlay::overlay(window); + auto *overlayPrivate = QQuickOverlayPrivate::get(overlay); + if (overlayPrivate->lastActiveFocusItem.isNull()) + overlayPrivate->lastActiveFocusItem = window->activeFocusItem(); + if (focus) popupItem->setFocus(true, Qt::PopupFocusReason); } @@ -501,6 +723,8 @@ bool QQuickPopupPrivate::prepareExitTransition() if (transitionState == ExitTransition && transitionManager.isRunning()) return false; + Q_ASSERT(popupItem); + // We need to cache the original scale and opacity values so we can reset it after // the exit transition is done so they have the original values again prevScale = popupItem->scale(); @@ -509,12 +733,15 @@ bool QQuickPopupPrivate::prepareExitTransition() if (transitionState != ExitTransition) { // The setFocus(false) call below removes any active focus before we're // able to check it in finalizeExitTransition. - if (!hadActiveFocusBeforeExitTransition) - hadActiveFocusBeforeExitTransition = popupItem->hasActiveFocus(); + if (!hadActiveFocusBeforeExitTransition) { + const auto *da = QQuickItemPrivate::get(popupItem)->deliveryAgentPrivate(); + hadActiveFocusBeforeExitTransition = popupItem->hasActiveFocus() || (da && da->focusTargetItem() == popupItem); + } + if (focus) popupItem->setFocus(false, Qt::PopupFocusReason); transitionState = ExitTransition; - hideOverlay(); + hideDimmer(); emit q->aboutToHide(); emit q->openedChanged(); } @@ -525,7 +752,7 @@ void QQuickPopupPrivate::finalizeEnterTransition() { Q_Q(QQuickPopup); transitionState = NoTransition; - getPositioner()->reposition(); + reposition(); emit q->openedChanged(); opened(); } @@ -534,11 +761,11 @@ void QQuickPopupPrivate::finalizeExitTransition() { Q_Q(QQuickPopup); getPositioner()->setParentItem(nullptr); - if (popupItem) { + if (popupItem && !popupWindow) { popupItem->setParentItem(nullptr); popupItem->setVisible(false); } - destroyOverlay(); + destroyDimmer(); if (hadActiveFocusBeforeExitTransition && window) { // restore focus to the next popup in chain, or to the window content if there are no other popups open @@ -556,15 +783,21 @@ void QQuickPopupPrivate::finalizeExitTransition() if (nextFocusPopup) { nextFocusPopup->forceActiveFocus(Qt::PopupFocusReason); } else { - QQuickApplicationWindow *applicationWindow = qobject_cast<QQuickApplicationWindow*>(window); - if (applicationWindow) - applicationWindow->contentItem()->setFocus(true, Qt::PopupFocusReason); - else - window->contentItem()->setFocus(true, Qt::PopupFocusReason); + auto *appWindow = qobject_cast<QQuickApplicationWindow*>(window); + auto *contentItem = appWindow ? appWindow->contentItem() : window->contentItem(); + auto *overlay = QQuickOverlay::overlay(window); + auto *overlayPrivate = QQuickOverlayPrivate::get(overlay); + if (!contentItem->scopedFocusItem() + && !overlayPrivate->lastActiveFocusItem.isNull()) { + overlayPrivate->lastActiveFocusItem->setFocus(true, Qt::OtherFocusReason); + } else { + contentItem->setFocus(true, Qt::PopupFocusReason); + } + overlayPrivate->lastActiveFocusItem = nullptr; } } - visible = false; + adjustPopupItemParentAndWindow(); transitionState = NoTransition; hadActiveFocusBeforeExitTransition = false; emit q->visibleChanged(); @@ -581,6 +814,11 @@ void QQuickPopupPrivate::opened() emit q->opened(); } +Qt::WindowFlags QQuickPopupPrivate::popupWindowType() const +{ + return Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint; +} + QMarginsF QQuickPopupPrivate::getMargins() const { Q_Q(const QQuickPopup); @@ -736,6 +974,86 @@ QPalette QQuickPopupPrivate::defaultPalette() const return QQuickTheme::palette(QQuickTheme::System); } +QQuickPopup::PopupType QQuickPopupPrivate::resolvedPopupType() const +{ + // Whether or not the resolved popup type ends up the same as the preferred popup type + // depends on platform capabilities, the popup subclass, and sometimes also the location + // of the popup in the parent hierarchy (menus). This function can therefore be overridden + // to return the actual popup type that should be used, based on the knowledge the popup + // has just before it's about to be shown. + + // PopupType::Native is not directly supported by QQuickPopup (only by subclasses). + // So for that case, we fall back to use PopupType::Window, if supported. + if (m_popupType == QQuickPopup::PopupType::Window + || m_popupType == QQuickPopup::PopupType::Native) { + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::Capability::MultipleWindows)) + return QQuickPopup::PopupType::Window; + } + + return QQuickPopup::PopupType::Item; +} + +bool QQuickPopupPrivate::usePopupWindow() const +{ + return resolvedPopupType() == QQuickPopup::PopupType::Window; +} + +void QQuickPopupPrivate::adjustPopupItemParentAndWindow() +{ + Q_Q(QQuickPopup); + QQuickOverlay *overlay = QQuickOverlay::overlay(window); + + if (visible && popupWindowDirty) { + popupItem->setParentItem(overlay); + if (popupWindow) { + popupWindow->deleteLater(); + popupWindow = nullptr; + } + popupWindowDirty = false; + } + + if (usePopupWindow()) { + if (!popupWindow) { + popupWindow = new QQuickPopupWindow(q, window); + popupWindow->setWidth(popupItem->width()); + popupWindow->setHeight(popupItem->height()); + popupWindow->setModality(modal ? Qt::ApplicationModal : Qt::NonModal); + popupItem->resetTitle(); + popupWindow->setTitle(m_title); + popupItem->setParentItem(popupWindow->contentItem()); + popupItem->forceActiveFocus(Qt::PopupFocusReason); + } + popupItem->setVisible(true); + popupWindow->setVisible(visible); + } else { + if (visible) { + popupItem->setParentItem(overlay); + const auto popupStack = QQuickOverlayPrivate::get(overlay)->stackingOrderPopups(); + // if there is a stack of popups, and the current top popup item belongs to an + // ancestor of this popup, then make sure that this popup's item is at the top + // of the stack. + const QQuickPopup *topPopup = popupStack.isEmpty() ? nullptr : popupStack.first(); + const QObject *ancestor = q; + while (ancestor && topPopup) { + if (ancestor == topPopup) + break; + ancestor = ancestor->parent(); + } + if (topPopup && topPopup != q && ancestor) { + QQuickItem *topPopupItem = popupStack.first()->popupItem(); + popupItem->stackAfter(topPopupItem); + // If the popup doesn't have an explicit z value set, set it to be at least as + // high as the current top popup item so that later opened popups are on top. + if (!hasZ) + popupItem->setZ(qMax(topPopupItem->z(), popupItem->z())); + } + } + + popupItem->setTitle(m_title); + popupItem->setVisible(visible); + } +} + static QQuickItem *createDimmer(QQmlComponent *component, QQuickPopup *popup, QQuickItem *parent) { QQuickItem *item = nullptr; @@ -752,7 +1070,6 @@ static QQuickItem *createDimmer(QQmlComponent *component, QQuickPopup *popup, QQ item = new QQuickItem; if (item) { - item->setOpacity(popup->isVisible() ? 1.0 : 0.0); item->setParentItem(parent); item->stackBefore(popup->popupItem()); item->setZ(popup->z()); @@ -794,12 +1111,24 @@ void QQuickPopupPrivate::createOverlay() if (!component) component = modal ? overlay->modal() : overlay->modeless(); - if (!dimmer) + if (!dimmer) { dimmer = createDimmer(component, q, overlay); - resizeOverlay(); + if (!dimmer) + return; + // We cannot update explicitDimmerOpacity when dimmer's opacity changes, + // as it is expected to do so when we fade the dimmer in and out in + // show/hideDimmer, and any binding of the dimmer's opacity will be + // implicitly broken anyway. + explicitDimmerOpacity = dimmer->opacity(); + // initially fully transparent, showDimmer fades the dimmer in. + dimmer->setOpacity(0); + if (q->isVisible()) + showDimmer(); + } + resizeDimmer(); } -void QQuickPopupPrivate::destroyOverlay() +void QQuickPopupPrivate::destroyDimmer() { if (dimmer) { qCDebug(lcDimmer) << "destroying dimmer" << dimmer; @@ -815,7 +1144,7 @@ void QQuickPopupPrivate::destroyOverlay() void QQuickPopupPrivate::toggleOverlay() { - destroyOverlay(); + destroyDimmer(); if (dim) createOverlay(); } @@ -829,27 +1158,29 @@ void QQuickPopupPrivate::updateContentPalettes(const QPalette& parentPalette) QQuickItemPrivate::get(popupItem)->updateChildrenPalettes(parentPalette); } -void QQuickPopupPrivate::showOverlay() +void QQuickPopupPrivate::showDimmer() { // use QQmlProperty instead of QQuickItem::setOpacity() to trigger QML Behaviors if (dim && dimmer) - QQmlProperty::write(dimmer, QStringLiteral("opacity"), 1.0); + QQmlProperty::write(dimmer, QStringLiteral("opacity"), explicitDimmerOpacity); } -void QQuickPopupPrivate::hideOverlay() +void QQuickPopupPrivate::hideDimmer() { // use QQmlProperty instead of QQuickItem::setOpacity() to trigger QML Behaviors if (dim && dimmer) QQmlProperty::write(dimmer, QStringLiteral("opacity"), 0.0); } -void QQuickPopupPrivate::resizeOverlay() +void QQuickPopupPrivate::resizeDimmer() { if (!dimmer) return; - qreal w = window ? window->width() : 0; - qreal h = window ? window->height() : 0; + const QQuickOverlay *overlay = QQuickOverlay::overlay(window); + + qreal w = overlay ? overlay->width() : 0; + qreal h = overlay ? overlay->height() : 0; dimmer->setSize(QSizeF(w, h)); } @@ -896,6 +1227,8 @@ QQuickPopup::QQuickPopup(QObject *parent) { Q_D(QQuickPopup); d->init(); + // By default, allow popup to move beyond window edges + d->relaxEdgeConstraint = true; } QQuickPopup::QQuickPopup(QQuickPopupPrivate &dd, QObject *parent) @@ -909,6 +1242,13 @@ QQuickPopup::~QQuickPopup() { Q_D(QQuickPopup); d->inDestructor = true; + + QQuickItem *currentContentItem = d->popupItem->d_func()->contentItem.data(); + if (currentContentItem) { + disconnect(currentContentItem, &QQuickItem::childrenChanged, + this, &QQuickPopup::contentChildrenChanged); + } + setParentItem(nullptr); // If the popup is destroyed before the exit transition finishes, @@ -956,8 +1296,7 @@ void QQuickPopup::close() */ qreal QQuickPopup::x() const { - Q_D(const QQuickPopup); - return d->effectiveX; + return d_func()->effectivePos.x(); } void QQuickPopup::setX(qreal x) @@ -975,8 +1314,7 @@ void QQuickPopup::setX(qreal x) */ qreal QQuickPopup::y() const { - Q_D(const QQuickPopup); - return d->effectiveY; + return d_func()->effectivePos.y(); } void QQuickPopup::setY(qreal y) @@ -987,8 +1325,7 @@ void QQuickPopup::setY(qreal y) QPointF QQuickPopup::position() const { - Q_D(const QQuickPopup); - return QPointF(d->effectiveX, d->effectiveY); + return d_func()->effectivePos; } void QQuickPopup::setPosition(const QPointF &pos) @@ -1024,6 +1361,9 @@ void QQuickPopup::setPosition(const QPointF &pos) of an already open popup, then it will be stacked on top of its parent. This ensures that children are never hidden under their parents. + If the popup has its own window, the z-value will determine the window + stacking order instead. + The default z-value is \c 0. \sa x, y @@ -1038,9 +1378,13 @@ void QQuickPopup::setZ(qreal z) { Q_D(QQuickPopup); d->hasZ = true; - if (qFuzzyCompare(z, d->popupItem->z())) + bool previousZ = d->popupWindow ? d->popupWindow->z() : d->popupItem->z(); + if (qFuzzyCompare(z, previousZ)) return; - d->popupItem->setZ(z); + if (d->popupWindow) + d->popupWindow->setZ(z); + else + d->popupItem->setZ(z); emit zChanged(); } @@ -1066,7 +1410,16 @@ void QQuickPopup::setWidth(qreal width) { Q_D(QQuickPopup); d->hasWidth = true; - d->popupItem->setWidth(width); + + // QQuickPopupWindow::setWidth() triggers a window resize event. + // This will cause QQuickPopupWindow::resizeEvent() to resize + // the popupItem. QQuickPopupItem::geometryChanged() calls QQuickPopup::geometryChange(), + // which emits widthChanged(). + + if (d->popupWindow) + d->popupWindow->setWidth(width); + else + d->popupItem->setWidth(width); } void QQuickPopup::resetWidth() @@ -1096,7 +1449,16 @@ void QQuickPopup::setHeight(qreal height) { Q_D(QQuickPopup); d->hasHeight = true; - d->popupItem->setHeight(height); + + // QQuickPopupWindow::setHeight() triggers a window resize event. + // This will cause QQuickPopupWindow::resizeEvent() to resize + // the popupItem. QQuickPopupItem::geometryChanged() calls QQuickPopup::geometryChange(), + // which emits heightChanged(). + + if (d->popupWindow) + d->popupWindow->setHeight(height); + else + d->popupItem->setHeight(height); } void QQuickPopup::resetHeight() @@ -1682,8 +2044,18 @@ void QQuickPopup::setParentItem(QQuickItem *parent) if (parent) { QObjectPrivate::connect(parent, &QQuickItem::windowChanged, d, &QQuickPopupPrivate::setWindow); QQuickItemPrivate::get(d->parentItem)->addItemChangeListener(d, QQuickItemPrivate::Destroyed); - } else if (!d->inDestructor) { - // NOTE: if setParentItem is called from the dtor, this bypasses virtual dispatch and calls QQuickPopup::close() directly + } else if (d->inDestructor) { + d->destroyDimmer(); + } else { + // Reset transition manager state when its parent window destroyed + if (!d->window && d->transitionManager.isRunning()) { + if (d->transitionState == QQuickPopupPrivate::EnterTransition) + d->finalizeEnterTransition(); + else if (d->transitionState == QQuickPopupPrivate::ExitTransition) + d->finalizeExitTransition(); + } + // NOTE: if setParentItem is called from the dtor, this bypasses virtual dispatch and calls + // QQuickPopup::close() directly close(); } d->setWindow(parent ? parent->window() : nullptr); @@ -1695,7 +2067,7 @@ void QQuickPopup::resetParentItem() if (QQuickWindow *window = qobject_cast<QQuickWindow *>(parent())) setParentItem(window->contentItem()); else - setParentItem(qobject_cast<QQuickItem *>(parent())); + setParentItem(findParentItem()); } /*! @@ -1760,8 +2132,18 @@ void QQuickPopup::setContentItem(QQuickItem *item) { Q_D(QQuickPopup); // See comment in setBackground for why we do this. - QQuickControlPrivate::warnIfCustomizationNotSupported(this, item, QStringLiteral("background")); + QQuickControlPrivate::warnIfCustomizationNotSupported(this, item, QStringLiteral("contentItem")); + QQuickItem *oldContentItem = d->complete ? d->popupItem->d_func()->contentItem.data() + : nullptr; + if (oldContentItem) + disconnect(oldContentItem, &QQuickItem::childrenChanged, this, &QQuickPopup::contentChildrenChanged); d->popupItem->setContentItem(item); + if (d->complete) { + QQuickItem *newContentItem = d->popupItem->d_func()->contentItem.data(); + connect(newContentItem, &QQuickItem::childrenChanged, this, &QQuickPopup::contentChildrenChanged); + if (oldContentItem != newContentItem) + emit contentChildrenChanged(); + } } /*! @@ -1816,17 +2198,18 @@ QQmlListProperty<QQuickItem> QQuickPopupPrivate::contentChildren() \qmlproperty bool QtQuick.Controls::Popup::clip This property holds whether clipping is enabled. The default value is \c false. + Clipping only works when the popup isn't in its own window. */ bool QQuickPopup::clip() const { Q_D(const QQuickPopup); - return d->popupItem->clip(); + return d->popupItem->clip() && !d->usePopupWindow(); } void QQuickPopup::setClip(bool clip) { Q_D(QQuickPopup); - if (clip == d->popupItem->clip()) + if (clip == d->popupItem->clip() || d->usePopupWindow()) return; d->popupItem->setClip(clip); emit clipChanged(); @@ -1906,6 +2289,7 @@ void QQuickPopup::setModal(bool modal) if (d->modal == modal) return; d->modal = modal; + d->popupWindowDirty = true; if (d->complete && d->visible) d->toggleOverlay(); emit modalChanged(); @@ -1977,14 +2361,15 @@ void QQuickPopup::setVisible(bool visible) if (d->visible == visible && d->transitionState != QQuickPopupPrivate::ExitTransition) return; - if (d->complete) { - if (visible) - d->transitionManager.transitionEnter(); - else - d->transitionManager.transitionExit(); - } else { + if (!d->complete || (visible && !d->window)) { d->visible = visible; + return; } + + if (visible) + d->transitionManager.transitionEnter(); + else + d->transitionManager.transitionExit(); } /*! @@ -2435,6 +2820,57 @@ void QQuickPopup::resetBottomInset() d->popupItem->resetBottomInset(); } + +/*! + \qmlproperty enumeration QtQuick.Controls::Popup::popupType + \since 6.8 + + This property determines the type of popup that is preferred. + + Available options: + \value Item The popup will be embedded into the \l{Popup Items}{same scene as the parent}, without the use of a separate window. + \value Window The popup will be presented in a \l {Popup Windows}{separate window}. If the platform doesn't support multiple windows, + \c Popup.Item will be used instead. + \value Native The popup will be native to the platform. If the platform doesn't support native popups, + \c Popup.Window will be used instead. + + Whether a popup will be able to use the preferred type depends on the platform. + \c Popup.Item is supported on all platforms, but \c Popup.Window and \c Popup.Native + are normally only supported on desktop platforms. Additionally, if a popup is a + \l Menu inside a \l {Native menu bars}{native menubar}, the menu will be native as + well. And if the menu is a sub-menu inside another menu, the parent (or root) menu + will decide the type. + + \note The default value is platform dependent, and can change in future versions of Qt. + Therefore, if you want to make sure that for example \c Popup.Item is always used on all + platforms, you should set it explicitly. + + You can set this property to \c undefined, to restore its value back to its default type. + + \sa {Popup type} +*/ +QQuickPopup::PopupType QQuickPopup::popupType() const +{ + Q_D(const QQuickPopup); + return d->m_popupType; +} + +void QQuickPopup::setPopupType(PopupType popupType) +{ + Q_D(QQuickPopup); + if (d->m_popupType == popupType) + return; + + d->m_popupType = popupType; + + emit popupTypeChanged(); +} + +void QQuickPopup::resetPopupType() +{ + setPopupType(PopupType::Item); +} + /*! \since QtQuick.Controls 2.3 (Qt 5.10) \qmlproperty palette QtQuick.Controls::Popup::palette @@ -2505,7 +2941,7 @@ void QQuickPopup::classBegin() void QQuickPopup::componentComplete() { Q_D(QQuickPopup); - qCDebug(lcPopup) << "componentComplete" << this; + qCDebug(lcQuickPopup) << "componentComplete" << this; if (!parentItem()) resetParentItem(); @@ -2514,6 +2950,11 @@ void QQuickPopup::componentComplete() d->complete = true; d->popupItem->componentComplete(); + + if (auto currentContentItem = d->popupItem->d_func()->contentItem.data()) { + connect(currentContentItem, &QQuickItem::childrenChanged, + this, &QQuickPopup::contentChildrenChanged); + } } bool QQuickPopup::isComponentComplete() const @@ -2660,7 +3101,7 @@ void QQuickPopup::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) void QQuickPopup::contentSizeChange(const QSizeF &newSize, const QSizeF &oldSize) { - qCDebug(lcPopup) << "contentSizeChange called on" << this << "with newSize" << newSize << "oldSize" << oldSize; + qCDebug(lcQuickPopup) << "contentSizeChange called on" << this << "with newSize" << newSize << "oldSize" << oldSize; if (!qFuzzyCompare(newSize.width(), oldSize.width())) emit contentWidthChanged(); if (!qFuzzyCompare(newSize.height(), oldSize.height())) @@ -2677,7 +3118,7 @@ void QQuickPopup::fontChange(const QFont &newFont, const QFont &oldFont) void QQuickPopup::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_D(QQuickPopup); - qCDebug(lcPopup) << "geometryChange called on" << this << "with newGeometry" << newGeometry << "oldGeometry" << oldGeometry; + qCDebug(lcQuickPopup) << "geometryChange called on" << this << "with newGeometry" << newGeometry << "oldGeometry" << oldGeometry; d->reposition(); if (!qFuzzyCompare(newGeometry.width(), oldGeometry.width())) { emit widthChanged(); @@ -2769,6 +3210,19 @@ QFont QQuickPopup::defaultFont() const } #if QT_CONFIG(accessibility) +QAccessible::Role QQuickPopup::effectiveAccessibleRole() const +{ + auto *attached = qmlAttachedPropertiesObject<QQuickAccessibleAttached>(this, false); + + auto role = QAccessible::NoRole; + if (auto *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(attached)) + role = accessibleAttached->role(); + if (role == QAccessible::NoRole) + role = accessibleRole(); + + return role; +} + QAccessible::Role QQuickPopup::accessibleRole() const { return QAccessible::Dialog; diff --git a/src/quicktemplates/qquickpopup_p.h b/src/quicktemplates/qquickpopup_p.h index ee9003d627..1f388d8adb 100644 --- a/src/quicktemplates/qquickpopup_p.h +++ b/src/quicktemplates/qquickpopup_p.h @@ -38,7 +38,7 @@ class QQuickPopupAnchors; class QQuickPopupPrivate; class QQuickTransition; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopup : public QObject, public QQmlParserStatus +class Q_QUICKTEMPLATES2_EXPORT QQuickPopup : public QObject, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) @@ -101,6 +101,7 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopup : public QObject, public QQml Q_PROPERTY(qreal leftInset READ leftInset WRITE setLeftInset RESET resetLeftInset NOTIFY leftInsetChanged FINAL REVISION(2, 5)) Q_PROPERTY(qreal rightInset READ rightInset WRITE setRightInset RESET resetRightInset NOTIFY rightInsetChanged FINAL REVISION(2, 5)) Q_PROPERTY(qreal bottomInset READ bottomInset WRITE setBottomInset RESET resetBottomInset NOTIFY bottomInsetChanged FINAL REVISION(2, 5)) + Q_PROPERTY(PopupType popupType READ popupType WRITE setPopupType RESET resetPopupType NOTIFY popupTypeChanged FINAL REVISION(6, 8)) Q_CLASSINFO("DeferredPropertyNames", "background,contentItem") Q_CLASSINFO("DefaultProperty", "contentData") QML_NAMED_ELEMENT(Popup) @@ -222,11 +223,11 @@ public: void setDim(bool dim); void resetDim(); - bool isVisible() const; + virtual bool isVisible() const; virtual void setVisible(bool visible); qreal opacity() const; - void setOpacity(qreal opacity); + virtual void setOpacity(qreal opacity); qreal scale() const; void setScale(qreal scale); @@ -311,6 +312,17 @@ public: void setBottomInset(qreal inset); void resetBottomInset(); + enum PopupType { + Item, + Window, + Native + }; + Q_ENUM(PopupType) + + PopupType popupType() const; + void setPopupType(PopupType); + void resetPopupType(); + public Q_SLOTS: void open(); void close(); @@ -378,6 +390,7 @@ Q_SIGNALS: Q_REVISION(2, 5) void leftInsetChanged(); Q_REVISION(2, 5) void rightInsetChanged(); Q_REVISION(2, 5) void bottomInsetChanged(); + Q_REVISION(6, 8) void popupTypeChanged(); protected: QQuickPopup(QQuickPopupPrivate &dd, QObject *parent); @@ -419,7 +432,10 @@ protected: virtual QFont defaultFont() const; #if QT_CONFIG(accessibility) + QAccessible::Role effectiveAccessibleRole() const; +private: virtual QAccessible::Role accessibleRole() const; +protected: virtual void accessibilityActiveChanged(bool active); #endif @@ -430,8 +446,11 @@ protected: bool setAccessibleProperty(const char *propertyName, const QVariant &value); private: + QQuickItem *findParentItem() const; + Q_DISABLE_COPY(QQuickPopup) Q_DECLARE_PRIVATE(QQuickPopup) + friend class QQuickPopupWindow; friend class QQuickPopupItem; friend class QQuickOverlay; friend class QQuickOverlayPrivate; @@ -441,6 +460,4 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QQuickPopup::ClosePolicy) QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickPopup) - #endif // QQUICKPOPUP_P_H diff --git a/src/quicktemplates/qquickpopup_p_p.h b/src/quicktemplates/qquickpopup_p_p.h index eabe2df001..48d6fc32c2 100644 --- a/src/quicktemplates/qquickpopup_p_p.h +++ b/src/quicktemplates/qquickpopup_p_p.h @@ -25,6 +25,8 @@ #include <QtQuick/private/qquicktransitionmanager_p_p.h> #include <QtQuick/private/qquickitem_p.h> +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE class QQuickTransition; @@ -32,10 +34,11 @@ class QQuickTransitionManager; class QQuickPopup; class QQuickPopupAnchors; class QQuickPopupItem; +class QQuickPopupWindow; class QQuickPopupPrivate; class QQuickPopupPositioner; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupTransitionManager : public QQuickTransitionManager +class Q_QUICKTEMPLATES2_EXPORT QQuickPopupTransitionManager : public QQuickTransitionManager { public: QQuickPopupTransitionManager(QQuickPopupPrivate *popup); @@ -50,7 +53,7 @@ private: QQuickPopupPrivate *popup = nullptr; }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickPopupPrivate : public QObjectPrivate , public QQuickItemChangeListener , public QQuickPaletteProviderPrivateBase<QQuickPopup, QQuickPopupPrivate> @@ -82,6 +85,7 @@ public: virtual bool handlePress(QQuickItem* item, const QPointF &point, ulong timestamp); virtual bool handleMove(QQuickItem* item, const QPointF &point, ulong timestamp); virtual bool handleRelease(QQuickItem* item, const QPointF &point, ulong timestamp); + virtual bool handleReleaseWithoutGrab(const QEventPoint &) { return false; } virtual void handleUngrab(); bool handleMouseEvent(QQuickItem *item, QMouseEvent *event); @@ -90,15 +94,22 @@ public: bool handleTouchEvent(QQuickItem *item, QTouchEvent *event); #endif + QPointF dropShadowOffset() const; + void setEffectivePosFromWindowPos(const QPointF &windowPos); void reposition(); + bool usePopupWindow() const; + void adjustPopupItemParentAndWindow(); void createOverlay(); - void destroyOverlay(); + void destroyDimmer(); void toggleOverlay(); void updateContentPalettes(const QPalette& parentPalette); - virtual void showOverlay(); - virtual void hideOverlay(); - virtual void resizeOverlay(); + + virtual QQuickPopup::PopupType resolvedPopupType() const; + + virtual void showDimmer(); + virtual void hideDimmer(); + virtual void resizeDimmer(); virtual bool prepareEnterTransition(); virtual bool prepareExitTransition(); @@ -107,6 +118,8 @@ public: virtual void opened(); + virtual Qt::WindowFlags popupWindowType() const; + QMarginsF getMargins() const; void setTopMargin(qreal value, bool reset = false); @@ -154,11 +167,12 @@ public: bool outsidePressed = false; bool outsideParentPressed = false; bool inDestructor = false; + bool relaxEdgeConstraint = false; + bool popupWindowDirty = false; int touchId = -1; qreal x = 0; qreal y = 0; - qreal effectiveX = 0; - qreal effectiveY = 0; + QPointF effectivePos; qreal margins = -1; qreal topMargin = 0; qreal leftMargin = 0; @@ -173,13 +187,17 @@ public: QQuickTransition *enter = nullptr; QQuickTransition *exit = nullptr; QQuickPopupItem *popupItem = nullptr; + QQuickPopupWindow *popupWindow = nullptr; QQuickPopupPositioner *positioner = nullptr; QList<QQuickStateAction> enterActions; QList<QQuickStateAction> exitActions; QQuickPopupTransitionManager transitionManager; QQuickPopupAnchors *anchors = nullptr; + qreal explicitDimmerOpacity = 0; qreal prevOpacity = 0; qreal prevScale = 0; + QString m_title; + QQuickPopup::PopupType m_popupType = QQuickPopup::Item; friend class QQuickPopupTransitionManager; }; diff --git a/src/quicktemplates/qquickpopupanchors_p.h b/src/quicktemplates/qquickpopupanchors_p.h index 589e2d6c8b..db94939673 100644 --- a/src/quicktemplates/qquickpopupanchors_p.h +++ b/src/quicktemplates/qquickpopupanchors_p.h @@ -26,10 +26,10 @@ class QQuickItem; class QQuickPopupAnchorsPrivate; class QQuickPopup; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupAnchors : public QObject, public QQuickItemChangeListener +class Q_QUICKTEMPLATES2_EXPORT QQuickPopupAnchors : public QObject, public QQuickItemChangeListener { Q_OBJECT - Q_PROPERTY(QQuickItem *centerIn READ centerIn WRITE setCenterIn RESET resetCenterIn NOTIFY centerInChanged) + Q_PROPERTY(QQuickItem *centerIn READ centerIn WRITE setCenterIn RESET resetCenterIn NOTIFY centerInChanged FINAL) QML_ANONYMOUS QML_ADDED_IN_VERSION(2, 5) @@ -53,6 +53,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickPopupAnchors) - #endif // QQUICKPOPUPANCHORS_P_H diff --git a/src/quicktemplates/qquickpopupitem.cpp b/src/quicktemplates/qquickpopupitem.cpp index 157046ca51..e60e4c5d77 100644 --- a/src/quicktemplates/qquickpopupitem.cpp +++ b/src/quicktemplates/qquickpopupitem.cpp @@ -22,6 +22,11 @@ QQuickPopupItemPrivate::QQuickPopupItemPrivate(QQuickPopup *popup) isTabFence = true; } +QQuickPopupItemPrivate *QQuickPopupItemPrivate::get(QQuickPopupItem *popupItem) +{ + return popupItem->d_func(); +} + void QQuickPopupItemPrivate::implicitWidthChanged() { qCDebug(lcPopupItem).nospace() << "implicitWidthChanged called on " << q_func() << "; new implicitWidth is " << implicitWidth; @@ -71,8 +76,6 @@ void QQuickPopupItemPrivate::executeContentItem(bool complete) quickCompleteDeferred(popup, contentItemName(), contentItem); } -static inline QString backgroundName() { return QStringLiteral("background"); } - void QQuickPopupItemPrivate::cancelBackground() { quickCancelDeferred(popup, backgroundName()); @@ -143,6 +146,18 @@ QPalette QQuickPopupItemPrivate::parentPalette(const QPalette &fallbackPalette) return QQuickPopupPrivate::get(popup)->parentPalette(fallbackPalette); } +bool QQuickPopupItem::contains(const QPointF &point) const +{ + Q_D(const QQuickPopupItem); + // A popup will often contain a drop shadow. And when determining if a point + // is inside the popup, we want to exclude that shadow from the test, and only + // consider the background rect. + const QRectF backgroundRect = boundingRect().adjusted( + d->popup->leftInset(), d->popup->topInset(), + -d->popup->rightInset(), -d->popup->bottomInset()); + return backgroundRect.contains(point); +} + void QQuickPopupItem::updatePolish() { Q_D(QQuickPopupItem); @@ -310,7 +325,7 @@ QFont QQuickPopupItem::defaultFont() const QAccessible::Role QQuickPopupItem::accessibleRole() const { Q_D(const QQuickPopupItem); - return d->popup->accessibleRole(); + return d->popup->effectiveAccessibleRole(); } void QQuickPopupItem::accessibilityActiveChanged(bool active) diff --git a/src/quicktemplates/qquickpopupitem_p_p.h b/src/quicktemplates/qquickpopupitem_p_p.h index 77b7695421..cb0d2709cd 100644 --- a/src/quicktemplates/qquickpopupitem_p_p.h +++ b/src/quicktemplates/qquickpopupitem_p_p.h @@ -23,13 +23,15 @@ QT_BEGIN_NAMESPACE class QQuickPopup; class QQuickPopupItemPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupItem : public QQuickPage +class Q_QUICKTEMPLATES2_EXPORT QQuickPopupItem : public QQuickPage { Q_OBJECT public: explicit QQuickPopupItem(QQuickPopup *popup); + bool contains(const QPointF &point) const override; + protected: void updatePolish() override; @@ -74,13 +76,15 @@ private: friend class QQuickPopup; }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupItemPrivate : public QQuickPagePrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickPopupItemPrivate : public QQuickPagePrivate { Q_DECLARE_PUBLIC(QQuickPopupItem) public: QQuickPopupItemPrivate(QQuickPopup *popup); + static QQuickPopupItemPrivate *get(QQuickPopupItem *popupItem); + void implicitWidthChanged() override; void implicitHeightChanged() override; diff --git a/src/quicktemplates/qquickpopuppositioner.cpp b/src/quicktemplates/qquickpopuppositioner.cpp index aecbc7373c..a7e1029a77 100644 --- a/src/quicktemplates/qquickpopuppositioner.cpp +++ b/src/quicktemplates/qquickpopuppositioner.cpp @@ -5,6 +5,7 @@ #include "qquickpopuppositioner_p_p.h" #include "qquickpopupanchors_p.h" #include "qquickpopupitem_p_p.h" +#include "qquickpopupwindow_p_p.h" #include "qquickpopup_p_p.h" #include <QtCore/qloggingcategory.h> @@ -72,7 +73,40 @@ void QQuickPopupPositioner::setParentItem(QQuickItem *parent) void QQuickPopupPositioner::reposition() { - QQuickItem *popupItem = m_popup->popupItem(); + auto p = QQuickPopupPrivate::get(popup()); + QQuickPopupItem *popupItem = static_cast<QQuickPopupItem *>(m_popup->popupItem()); + + if (p->usePopupWindow()) { + QPointF requestedPos(p->x, p->y); + // Shift the window position a bit back, so that the top-left of the + // background frame ends up at the requested position. + QPointF windowPos = requestedPos - p->dropShadowOffset(); + + if (!p->popupWindow || !p->parentItem) { + // If we don't have a popupWindow, set a temporary effective pos. Otherwise + // wait for a callback to QQuickPopupWindow::handlePopupPositionChangeFromWindowSystem() + // from setting p->popupWindow->setPosition() below. + p->setEffectivePosFromWindowPos(windowPos); + return; + } + + const QQuickItem *centerInParent = p->anchors ? p->getAnchors()->centerIn() : nullptr; + const QQuickOverlay *centerInOverlay = qobject_cast<const QQuickOverlay *>(centerInParent); + + if (centerInParent == p->parentItem || centerInOverlay) { + windowPos = centerInOverlay ? QPoint(qRound(centerInOverlay->width() / 2.0), qRound(centerInOverlay->height() / 2.0)) + : QPoint(qRound(p->parentItem->width() / 2.0), qRound(p->parentItem->height() / 2.0)); + windowPos -= QPoint(qRound(p->popupItem->width() / 2.0), qRound(p->popupItem->height() / 2.0)); + + } else if (centerInParent) + qmlWarning(popup()) << "Popup can only be centered within its immediate parent or Overlay.overlay"; + + const QPointF globalCoords = p->parentItem->mapToGlobal(windowPos.x(), windowPos.y()); + p->popupWindow->setPosition(globalCoords.x(), globalCoords.y()); + p->popupItem->setPosition({0, 0}); + return; + } + if (!popupItem->isVisible()) return; @@ -90,14 +124,13 @@ void QQuickPopupPositioner::reposition() bool widthAdjusted = false; bool heightAdjusted = false; - QQuickPopupPrivate *p = QQuickPopupPrivate::get(m_popup); const QQuickItem *centerInParent = p->anchors ? p->getAnchors()->centerIn() : nullptr; const QQuickOverlay *centerInOverlay = qobject_cast<const QQuickOverlay*>(centerInParent); QRectF rect(!centerInParent ? p->allowHorizontalMove ? p->x : popupItem->x() : 0, !centerInParent ? p->allowVerticalMove ? p->y : popupItem->y() : 0, - !p->hasWidth && iw > 0 ? iw : w, - !p->hasHeight && ih > 0 ? ih : h); + !p->hasWidth && iw > 0 ? iw : w, !p->hasHeight && ih > 0 ? ih : h); + bool relaxEdgeConstraint = p->relaxEdgeConstraint; if (m_parentItem) { // m_parentItem is the parent that the popup should open in, // and popupItem()->parentItem() is the overlay, so the mapToItem() calls below @@ -110,6 +143,8 @@ void QQuickPopupPositioner::reposition() if (centerInOverlay) { rect.moveCenter(QPointF(qRound(centerInOverlay->width() / 2.0), qRound(centerInOverlay->height() / 2.0))); + // Popup cannot be moved outside window bounds when its centered with overlay + relaxEdgeConstraint = false; } else { const QPointF parentItemCenter = QPointF(qRound(m_parentItem->width() / 2), qRound(m_parentItem->height() / 2)); rect.moveCenter(m_parentItem->mapToItem(popupItem->parentItem(), parentItemCenter)); @@ -124,8 +159,6 @@ void QQuickPopupPositioner::reposition() qMax<qreal>(0.0, margins.top()), p->window->width() - qMax<qreal>(0.0, margins.left()) - qMax<qreal>(0.0, margins.right()), p->window->height() - qMax<qreal>(0.0, margins.top()) - qMax<qreal>(0.0, margins.bottom())); - if (p->window->contentOrientation() == Qt::LandscapeOrientation || p->window->contentOrientation() == Qt::InvertedLandscapeOrientation) - bounds = bounds.transposed(); // if the popup doesn't fit horizontally inside the window, try flipping it around (left <-> right) if (p->allowHorizontalFlip && (rect.left() < bounds.left() || rect.right() > bounds.right())) { @@ -170,12 +203,17 @@ void QQuickPopupPositioner::reposition() } // as a last resort, adjust the width to fit the window + // Negative margins don't require resize as popup not pushed within + // the boundary. But otherwise, retain existing behavior of resizing + // for items, such as menus, which enables flip. if (p->allowHorizontalResize) { - if (rect.left() < bounds.left()) { + if ((margins.left() >= 0 || !relaxEdgeConstraint) + && (rect.left() < bounds.left())) { rect.setLeft(bounds.left()); widthAdjusted = true; } - if (rect.right() > bounds.right()) { + if ((margins.right() >= 0 || !relaxEdgeConstraint) + && (rect.right() > bounds.right())) { rect.setRight(bounds.right()); widthAdjusted = true; } @@ -198,12 +236,17 @@ void QQuickPopupPositioner::reposition() } // as a last resort, adjust the height to fit the window + // Negative margins don't require resize as popup not pushed within + // the boundary. But otherwise, retain existing behavior of resizing + // for items, such as menus, which enables flip. if (p->allowVerticalResize) { - if (rect.top() < bounds.top()) { + if ((margins.top() >= 0 || !relaxEdgeConstraint) + && (rect.top() < bounds.top())) { rect.setTop(bounds.top()); heightAdjusted = true; } - if (rect.bottom() > bounds.bottom()) { + if ((margins.bottom() >= 0 || !relaxEdgeConstraint) + && (rect.bottom() > bounds.bottom())) { rect.setBottom(bounds.bottom()); heightAdjusted = true; } @@ -219,21 +262,20 @@ void QQuickPopupPositioner::reposition() m_positioning = true; - popupItem->setPosition(rect.topLeft()); + // Shift the "window" a bit back, so that the top-left of the + // background frame ends up at the requested position. + const QPointF windowPos = rect.topLeft() - p->dropShadowOffset(); + popupItem->setPosition(windowPos); // If the popup was assigned a parent, rect will be in scene coordinates, // so we need to map its top left back to item coordinates. // However, if centering within the overlay, the coordinates will be relative // to the window, so we don't need to do anything. - const QPointF effectivePos = m_parentItem && !centerInOverlay ? m_parentItem->mapFromScene(rect.topLeft()) : rect.topLeft(); - if (!qFuzzyCompare(p->effectiveX, effectivePos.x())) { - p->effectiveX = effectivePos.x(); - emit m_popup->xChanged(); - } - if (!qFuzzyCompare(p->effectiveY, effectivePos.y())) { - p->effectiveY = effectivePos.y(); - emit m_popup->yChanged(); - } + // The same applies to popups that are in their own dedicated window. + if (m_parentItem && !centerInOverlay) + p->setEffectivePosFromWindowPos(m_parentItem->mapFromScene(windowPos)); + else + p->setEffectivePosFromWindowPos(windowPos); if (!p->hasWidth && widthAdjusted && rect.width() > 0) { popupItem->setWidth(rect.width() / m_popupScale); diff --git a/src/quicktemplates/qquickpopuppositioner_p_p.h b/src/quicktemplates/qquickpopuppositioner_p_p.h index c8e20b4b28..1ec8234469 100644 --- a/src/quicktemplates/qquickpopuppositioner_p_p.h +++ b/src/quicktemplates/qquickpopuppositioner_p_p.h @@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE class QQuickItem; class QQuickPopup; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickPopupPositioner : public QQuickItemChangeListener +class Q_QUICKTEMPLATES2_EXPORT QQuickPopupPositioner : public QQuickItemChangeListener { public: explicit QQuickPopupPositioner(QQuickPopup *popup); diff --git a/src/quicktemplates/qquickpopupwindow.cpp b/src/quicktemplates/qquickpopupwindow.cpp new file mode 100644 index 0000000000..f773c5fdeb --- /dev/null +++ b/src/quicktemplates/qquickpopupwindow.cpp @@ -0,0 +1,266 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquickpopupwindow_p_p.h" +#include "qquickcombobox_p.h" +#include "qquickpopup_p.h" +#include "qquickpopup_p_p.h" +#include "qquickmenu_p_p.h" +#include "qquickmenubar_p_p.h" +#include "qquickpopupitem_p_p.h" +#include <QtGui/private/qguiapplication_p.h> + +#include <QtCore/qloggingcategory.h> +#include <QtGui/private/qeventpoint_p.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickwindowmodule_p.h> +#include <QtQuick/private/qquickwindowmodule_p_p.h> +#include <qpa/qplatformwindow_p.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPopupWindow, "qt.quick.controls.popup.window") + +class QQuickPopupWindowPrivate : public QQuickWindowQmlImplPrivate +{ + Q_DECLARE_PUBLIC(QQuickPopupWindow) + +public: + QPointer<QQuickItem> m_popupItem; + QPointer<QQuickPopup> m_popup; + + void forwardEventToParentMenuOrMenuBar(QEvent *event); +}; + +QQuickPopupWindow::QQuickPopupWindow(QQuickPopup *popup, QWindow *parent) + : QQuickWindowQmlImpl(*(new QQuickPopupWindowPrivate), nullptr) +{ + Q_D(QQuickPopupWindow); + + d->m_popup = popup; + d->m_popupItem = popup->popupItem(); + setTransientParent(parent); + + connect(d->m_popup, &QQuickPopup::windowChanged, this, &QQuickPopupWindow::windowChanged); + connect(d->m_popup, &QQuickPopup::implicitWidthChanged, this, &QQuickPopupWindow::implicitWidthChanged); + connect(d->m_popup, &QQuickPopup::implicitHeightChanged, this, &QQuickPopupWindow::implicitHeightChanged); + connect(d->m_popup->window(), &QWindow::xChanged, this, &QQuickPopupWindow::parentWindowXChanged); + connect(d->m_popup->window(), &QWindow::yChanged, this, &QQuickPopupWindow::parentWindowYChanged); + + setWidth(d->m_popupItem->implicitWidth()); + setHeight(d->m_popupItem->implicitHeight()); + + const auto flags = QQuickPopupPrivate::get(popup)->popupWindowType(); + + // For popup windows, we'll need to draw everything, in order to have enough control over the styling. + if (flags & Qt::Popup) + setColor(QColorConstants::Transparent); + + setFlags(flags); + + qCDebug(lcPopupWindow) << "Created popup window with flags: " << flags; +} + +QQuickPopupWindow::~QQuickPopupWindow() +{ + Q_D(QQuickPopupWindow); + disconnect(d->m_popup, &QQuickPopup::windowChanged, this, &QQuickPopupWindow::windowChanged); + disconnect(d->m_popup, &QQuickPopup::implicitWidthChanged, this, &QQuickPopupWindow::implicitWidthChanged); + disconnect(d->m_popup, &QQuickPopup::implicitHeightChanged, this, &QQuickPopupWindow::implicitHeightChanged); + disconnect(d->m_popup->window(), &QWindow::xChanged, this, &QQuickPopupWindow::parentWindowXChanged); + disconnect(d->m_popup->window(), &QWindow::yChanged, this, &QQuickPopupWindow::parentWindowYChanged); +} + +QQuickPopup *QQuickPopupWindow::popup() const +{ + Q_D(const QQuickPopupWindow); + return d->m_popup; +} + +void QQuickPopupWindow::hideEvent(QHideEvent *e) +{ + Q_D(QQuickPopupWindow); + QQuickWindow::hideEvent(e); + if (QQuickPopup *popup = d->m_popup) { + QQuickPopupPrivate::get(popup)->visible = false; + emit popup->visibleChanged(); + } +} + +void QQuickPopupWindow::moveEvent(QMoveEvent *e) +{ + handlePopupPositionChangeFromWindowSystem(e->pos()); +} + +void QQuickPopupWindow::resizeEvent(QResizeEvent *e) +{ + Q_D(QQuickPopupWindow); + QQuickWindowQmlImpl::resizeEvent(e); + + if (!d->m_popupItem) + return; + + qCDebug(lcPopupWindow) << "A window system event changed the popup's size to be " << e->size(); + d->m_popupItem->setWidth(e->size().width()); + d->m_popupItem->setHeight(e->size().height()); +} + +/*! \internal + We want to handle menus specially, compared to other popups. For menus, we + want all open parent menus and sub menus that belong together to almost + act as a single popup WRT hover event delivery. This will allow the user to + hover and highlight MenuItems inside all of them, not just the leaf menu. + This function will therefore find the menu, or menu bar, under the event's + position, and forward the event to it. But note that this forwarding will + happen in parallel with normal event delivery (we don't eat the events), as + we don't want to break the delivery to e.g grabbers. + */ +void QQuickPopupWindowPrivate::forwardEventToParentMenuOrMenuBar(QEvent *event) +{ + Q_Q(QQuickPopupWindow); + + if (!event->isPointerEvent()) + return; + auto menu = qobject_cast<QQuickMenu *>(q->popup()); + if (!menu) + return; + + auto *pe = static_cast<QPointerEvent *>(event); + const QPointF globalPos = pe->points().first().globalPosition(); + + // If there is a Menu or a MenuBar under the mouse, resolve its window. + QQuickPopupWindow *targetPopupWindow = nullptr; + QQuickWindow *targetWindow = nullptr; + + QObject *menuParent = menu; + while (menuParent) { + if (auto parentMenu = qobject_cast<QQuickMenu *>(menuParent)) { + QQuickPopupWindow *popupWindow = QQuickMenuPrivate::get(parentMenu)->popupWindow; + auto popup_d = QQuickPopupPrivate::get(popupWindow->popup()); + QPointF scenePos = popupWindow->contentItem()->mapFromGlobal(globalPos); + if (popup_d->contains(scenePos)) { + targetPopupWindow = popupWindow; + targetWindow = popupWindow; + break; + } + } else if (auto menuBar = qobject_cast<QQuickMenuBar *>(menuParent)) { + const QPointF menuBarPos = menuBar->mapFromGlobal(globalPos); + if (menuBar->contains(menuBarPos)) + targetWindow = menuBar->window(); + break; + } + + menuParent = menuParent->parent(); + } + + if (!targetPopupWindow) { + if (pe->isBeginEvent()) { + // A QQuickPopupWindow can be bigger than the Popup itself, to make room + // for a drop-shadow. Close all popups if the user clicks either on the + // shadow or outside the window. + QGuiApplicationPrivate::closeAllPopups(); + return; + } + } + + if (!targetWindow) + return; + + if (pe->isUpdateEvent()){ + // Forward move events to the target window + const auto scenePos = pe->point(0).scenePosition(); + const auto translatedScenePos = targetWindow->mapFromGlobal(globalPos); + QMutableEventPoint::setScenePosition(pe->point(0), translatedScenePos); + auto *grabber = pe->exclusiveGrabber(pe->point(0)); + + if (grabber) { + // Temporarily disable the grabber, to stop the delivery agent inside + // targetWindow from forwarding the event to an item outside the menu + // or menubar. This is especially important to support a press on e.g + // a MenuBarItem, followed by a drag-and-release on top of a MenuItem. + pe->setExclusiveGrabber(pe->point(0), nullptr); + } + + qCDebug(lcPopupWindow) << "forwarding" << pe << "to popup menu:" << targetWindow; + QQuickWindowPrivate::get(targetWindow)->deliveryAgent->event(pe); + + // Restore the event before we return + QMutableEventPoint::setScenePosition(pe->point(0), scenePos); + if (grabber) + pe->setExclusiveGrabber(pe->point(0), grabber); + } else if (pe->isEndEvent()) { + // To support opening a Menu on press (e.g on a MenuBarItem), followed by + // a drag and release on a MenuItem inside the Menu, we ask the Menu to + // perform a click on the active MenuItem, if any. + if (targetPopupWindow) + QQuickPopupPrivate::get(targetPopupWindow->popup())->handleReleaseWithoutGrab(pe->point(0)); + } +} + +bool QQuickPopupWindow::event(QEvent *e) +{ + Q_D(QQuickPopupWindow); + d->forwardEventToParentMenuOrMenuBar(e); + + return QQuickWindowQmlImpl::event(e); +} + +void QQuickPopupWindow::windowChanged(QWindow *window) +{ + if (window) { + connect(window, &QWindow::xChanged, this, &QQuickPopupWindow::parentWindowXChanged); + connect(window, &QWindow::yChanged, this, &QQuickPopupWindow::parentWindowYChanged); + } +} + +QPoint QQuickPopupWindow::global2Local(const QPoint &pos) const +{ + Q_D(const QQuickPopupWindow); + QQuickPopup *popup = d->m_popup; + Q_ASSERT(popup); + const QPoint scenePos = popup->window()->mapFromGlobal(pos); + // Popup's coordinates are relative to the nearest parent item. + return popup->parentItem() ? popup->parentItem()->mapFromScene(scenePos).toPoint() : scenePos; +} + +void QQuickPopupWindow::parentWindowXChanged(int newX) +{ + const auto popupLocalPos = global2Local({x(), y()}); + handlePopupPositionChangeFromWindowSystem({newX + popupLocalPos.x(), y()}); +} + +void QQuickPopupWindow::parentWindowYChanged(int newY) +{ + const auto popupLocalPos = global2Local({x(), y()}); + handlePopupPositionChangeFromWindowSystem({x(), newY + popupLocalPos.y()}); +} + +void QQuickPopupWindow::handlePopupPositionChangeFromWindowSystem(const QPoint &pos) +{ + Q_D(QQuickPopupWindow); + QQuickPopup *popup = d->m_popup; + if (!popup) + return; + QQuickPopupPrivate *popupPrivate = QQuickPopupPrivate::get(popup); + + const auto windowPos = global2Local(pos); + qCDebug(lcPopupWindow) << "A window system event changed the popup's position to be " << windowPos; + popupPrivate->setEffectivePosFromWindowPos(windowPos); +} + +void QQuickPopupWindow::implicitWidthChanged() +{ + Q_D(const QQuickPopupWindow); + if (auto popup = d->m_popup) + setWidth(popup->implicitWidth()); +} + +void QQuickPopupWindow::implicitHeightChanged() +{ + Q_D(const QQuickPopupWindow); + if (auto popup = d->m_popup) + setHeight(popup->implicitHeight()); +} + +QT_END_NAMESPACE + diff --git a/src/quicktemplates/qquickpopupwindow_p_p.h b/src/quicktemplates/qquickpopupwindow_p_p.h new file mode 100644 index 0000000000..0b9842c059 --- /dev/null +++ b/src/quicktemplates/qquickpopupwindow_p_p.h @@ -0,0 +1,57 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKPOPUPWINDOW_P_P_H +#define QQUICKPOPUPWINDOW_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtQuick/private/qquickwindowmodule_p.h> +#include <QtQuickTemplates2/private/qtquicktemplates2global_p.h> + +QT_BEGIN_NAMESPACE + +class QQuickPopup; +class QQuickPopupWindowPrivate; + +class Q_QUICKTEMPLATES2_EXPORT QQuickPopupWindow : public QQuickWindowQmlImpl +{ + Q_OBJECT + QML_ANONYMOUS + +public: + explicit QQuickPopupWindow(QQuickPopup *popup, QWindow *parent = nullptr); + ~QQuickPopupWindow(); + QQuickPopup *popup() const; + +protected: + void hideEvent(QHideEvent *e) override; + void moveEvent(QMoveEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + bool event(QEvent *e) override; + +private: + void windowChanged(QWindow *window); + QPoint global2Local(const QPoint& pos) const; + void parentWindowXChanged(int newX); + void parentWindowYChanged(int newY); + void handlePopupPositionChangeFromWindowSystem(const QPoint &pos); + void implicitWidthChanged(); + void implicitHeightChanged(); + + Q_DISABLE_COPY(QQuickPopupWindow) + Q_DECLARE_PRIVATE(QQuickPopupWindow) +}; + +QT_END_NAMESPACE + +#endif // QQUICKPOPUPWINDOW_P_P_H diff --git a/src/quicktemplates/qquickpresshandler.cpp b/src/quicktemplates/qquickpresshandler.cpp index d752bb36f1..fc569a81ca 100644 --- a/src/quicktemplates/qquickpresshandler.cpp +++ b/src/quicktemplates/qquickpresshandler.cpp @@ -19,7 +19,7 @@ void QQuickPressHandler::mousePressEvent(QMouseEvent *event) pressPos = event->position(); if (Qt::LeftButton == (event->buttons() & Qt::LeftButton)) { timer.start(QGuiApplication::styleHints()->mousePressAndHoldInterval(), control); - delayedMousePressEvent = new QMouseEvent(event->type(), event->position().toPoint(), event->globalPosition().toPoint(), + delayedMousePressEvent = std::make_unique<QMouseEvent>(event->type(), event->position().toPoint(), event->globalPosition().toPoint(), event->button(), event->buttons(), event->modifiers(), event->pointingDevice()); } else { timer.stop(); @@ -86,10 +86,7 @@ QT_WARNING_POP void QQuickPressHandler::clearDelayedMouseEvent() { - if (delayedMousePressEvent) { - delete delayedMousePressEvent; - delayedMousePressEvent = 0; - } + delayedMousePressEvent.reset(); } bool QQuickPressHandler::isActive() diff --git a/src/quicktemplates/qquickpresshandler_p_p.h b/src/quicktemplates/qquickpresshandler_p_p.h index adbed66a30..72d78aae66 100644 --- a/src/quicktemplates/qquickpresshandler_p_p.h +++ b/src/quicktemplates/qquickpresshandler_p_p.h @@ -19,6 +19,8 @@ #include <QtCore/qbasictimer.h> #include <QtCore/private/qglobal_p.h> +#include <memory> + QT_BEGIN_NAMESPACE class QQuickItem; @@ -44,7 +46,7 @@ struct QQuickPressHandler int pressAndHoldSignalIndex = -1; int pressedSignalIndex = -1; int releasedSignalIndex = -1; - QMouseEvent *delayedMousePressEvent = nullptr; + std::unique_ptr<QMouseEvent> delayedMousePressEvent = nullptr; }; QT_END_NAMESPACE diff --git a/src/quicktemplates/qquickprogressbar.cpp b/src/quicktemplates/qquickprogressbar.cpp index c42a62b4fe..77be68873c 100644 --- a/src/quicktemplates/qquickprogressbar.cpp +++ b/src/quicktemplates/qquickprogressbar.cpp @@ -65,6 +65,8 @@ public: QQuickProgressBar::QQuickProgressBar(QQuickItem *parent) : QQuickControl(*(new QQuickProgressBarPrivate), parent) { + Q_D(QQuickProgressBar); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); } /*! diff --git a/src/quicktemplates/qquickprogressbar_p.h b/src/quicktemplates/qquickprogressbar_p.h index 99e356b525..576515e4ad 100644 --- a/src/quicktemplates/qquickprogressbar_p.h +++ b/src/quicktemplates/qquickprogressbar_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickProgressBarPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickProgressBar : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickProgressBar : public QQuickControl { Q_OBJECT Q_PROPERTY(qreal from READ from WRITE setFrom NOTIFY fromChanged FINAL) @@ -74,6 +74,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickProgressBar) - #endif // QQUICKPROGRESSBAR_P_H diff --git a/src/quicktemplates/qquickradiobutton.cpp b/src/quicktemplates/qquickradiobutton.cpp index a943da9de6..c49bf3b1cd 100644 --- a/src/quicktemplates/qquickradiobutton.cpp +++ b/src/quicktemplates/qquickradiobutton.cpp @@ -58,7 +58,7 @@ QT_BEGIN_NAMESPACE \sa ButtonGroup, {Customizing RadioButton}, {Button Controls}, RadioDelegate */ -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRadioButtonPrivate : public QQuickAbstractButtonPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickRadioButtonPrivate : public QQuickAbstractButtonPrivate { Q_DECLARE_PUBLIC(QQuickRadioButton) diff --git a/src/quicktemplates/qquickradiobutton_p.h b/src/quicktemplates/qquickradiobutton_p.h index 8d70290270..d7f4c74c7b 100644 --- a/src/quicktemplates/qquickradiobutton_p.h +++ b/src/quicktemplates/qquickradiobutton_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QQuickRadioButtonPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRadioButton : public QQuickAbstractButton +class Q_QUICKTEMPLATES2_EXPORT QQuickRadioButton : public QQuickAbstractButton { Q_OBJECT QML_NAMED_ELEMENT(RadioButton) @@ -41,6 +41,4 @@ protected: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickRadioButton) - #endif // QQUICKRADIOBUTTON_P_H diff --git a/src/quicktemplates/qquickradiodelegate.cpp b/src/quicktemplates/qquickradiodelegate.cpp index 2c390ad487..7ec22a6301 100644 --- a/src/quicktemplates/qquickradiodelegate.cpp +++ b/src/quicktemplates/qquickradiodelegate.cpp @@ -59,7 +59,7 @@ QT_BEGIN_NAMESPACE \sa {Customizing RadioDelegate}, {Delegate Controls}, RadioButton */ -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRadioDelegatePrivate : public QQuickItemDelegatePrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickRadioDelegatePrivate : public QQuickItemDelegatePrivate { Q_DECLARE_PUBLIC(QQuickRadioDelegate) diff --git a/src/quicktemplates/qquickradiodelegate_p.h b/src/quicktemplates/qquickradiodelegate_p.h index 574c87032c..337711c73f 100644 --- a/src/quicktemplates/qquickradiodelegate_p.h +++ b/src/quicktemplates/qquickradiodelegate_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickRadioDelegatePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRadioDelegate : public QQuickItemDelegate +class Q_QUICKTEMPLATES2_EXPORT QQuickRadioDelegate : public QQuickItemDelegate { Q_OBJECT QML_NAMED_ELEMENT(RadioDelegate) @@ -43,6 +43,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickRadioDelegate) - #endif // QQUICKRADIODELEGATE_P_H diff --git a/src/quicktemplates/qquickrangeslider.cpp b/src/quicktemplates/qquickrangeslider.cpp index ff15a9194e..d97dfdff90 100644 --- a/src/quicktemplates/qquickrangeslider.cpp +++ b/src/quicktemplates/qquickrangeslider.cpp @@ -117,8 +117,6 @@ void QQuickRangeSliderNodePrivate::updatePosition(bool ignoreOtherPosition) setPosition(pos, ignoreOtherPosition); } -static inline QString handleName() { return QStringLiteral("handle"); } - void QQuickRangeSliderNodePrivate::cancelHandle() { Q_Q(QQuickRangeSliderNode); @@ -227,6 +225,8 @@ void QQuickRangeSliderNode::setHandle(QQuickItem *handle) if (d->handle == handle) return; + QQuickControlPrivate::warnIfCustomizationNotSupported(d->slider, handle, QStringLiteral("handle")); + if (!d->handle.isExecuting()) d->cancelHandle(); @@ -356,6 +356,7 @@ public: void itemImplicitWidthChanged(QQuickItem *item) override; void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; bool live = true; qreal from = defaultFrom; @@ -485,8 +486,14 @@ bool QQuickRangeSliderPrivate::handlePress(const QPointF &point, ulong timestamp if (hitNode) { hitNode->setPressed(true); - if (QQuickItem *handle = hitNode->handle()) + if (QQuickItem *handle = hitNode->handle()) { handle->setZ(1); + + // A specific handle was hit, so it should get focus, rather than the default + // (first handle) that gets focus whenever the RangeSlider itself does - see focusInEvent(). + if (focusPolicy & Qt::ClickFocus) + handle->forceActiveFocus(Qt::MouseFocusReason); + } QQuickRangeSliderNodePrivate::get(hitNode)->touchId = touchId; } if (otherNode) { @@ -528,22 +535,21 @@ bool QQuickRangeSliderPrivate::handleRelease(const QPointF &point, ulong timesta return true; QQuickRangeSliderNodePrivate *pressedNodePrivate = QQuickRangeSliderNodePrivate::get(pressedNode); - if (q->keepMouseGrab() || q->keepTouchGrab()) { - const qreal oldPos = pressedNode->position(); - qreal pos = positionAt(q, pressedNode->handle(), point); - if (snapMode != QQuickRangeSlider::NoSnap) - pos = snapPosition(q, pos); - qreal val = valueAt(q, pos); - if (!qFuzzyCompare(val, pressedNode->value())) - pressedNode->setValue(val); - else if (snapMode != QQuickRangeSlider::NoSnap) - pressedNodePrivate->setPosition(pos); - q->setKeepMouseGrab(false); - q->setKeepTouchGrab(false); + const qreal oldPos = pressedNode->position(); + qreal pos = positionAt(q, pressedNode->handle(), point); + if (snapMode != QQuickRangeSlider::NoSnap) + pos = snapPosition(q, pos); + qreal val = valueAt(q, pos); + if (!qFuzzyCompare(val, pressedNode->value())) + pressedNode->setValue(val); + else if (snapMode != QQuickRangeSlider::NoSnap) + pressedNodePrivate->setPosition(pos); + q->setKeepMouseGrab(false); + q->setKeepTouchGrab(false); + + if (!qFuzzyCompare(pressedNode->position(), oldPos)) + emit pressedNode->moved(); - if (!qFuzzyCompare(pressedNode->position(), oldPos)) - emit pressedNode->moved(); - } pressedNode->setPressed(false); pressedNodePrivate->touchId = -1; return true; @@ -597,14 +603,29 @@ void QQuickRangeSliderPrivate::itemImplicitHeightChanged(QQuickItem *item) emit second->implicitHandleHeightChanged(); } +void QQuickRangeSliderPrivate::itemDestroyed(QQuickItem *item) +{ + QQuickControlPrivate::itemDestroyed(item); + if (item == first->handle()) + first->setHandle(nullptr); + else if (item == second->handle()) + second->setHandle(nullptr); +} + QQuickRangeSlider::QQuickRangeSlider(QQuickItem *parent) : QQuickControl(*(new QQuickRangeSliderPrivate), parent) { Q_D(QQuickRangeSlider); d->first = new QQuickRangeSliderNode(0.0, this); d->second = new QQuickRangeSliderNode(1.0, this); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); setFlag(QQuickItem::ItemIsFocusScope); +#ifdef Q_OS_MACOS + setFocusPolicy(Qt::TabFocus); +#else + setFocusPolicy(Qt::StrongFocus); +#endif setAcceptedMouseButtons(Qt::LeftButton); #if QT_CONFIG(quicktemplates2_multitouch) setAcceptTouchEvents(true); @@ -959,6 +980,11 @@ void QQuickRangeSlider::setOrientation(Qt::Orientation orientation) if (d->orientation == orientation) return; + if (orientation == Qt::Horizontal) + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); + else + d->setSizePolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Preferred); + d->orientation = orientation; emit orientationChanged(); } diff --git a/src/quicktemplates/qquickrangeslider_p.h b/src/quicktemplates/qquickrangeslider_p.h index 577bbe165f..772e4abcda 100644 --- a/src/quicktemplates/qquickrangeslider_p.h +++ b/src/quicktemplates/qquickrangeslider_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QQuickRangeSliderPrivate; class QQuickRangeSliderNode; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRangeSlider : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickRangeSlider : public QQuickControl { Q_OBJECT Q_PROPERTY(qreal from READ from WRITE setFrom NOTIFY fromChanged FINAL) @@ -126,7 +126,7 @@ private: class QQuickRangeSliderNodePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRangeSliderNode : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickRangeSliderNode : public QObject { Q_OBJECT Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) @@ -191,6 +191,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickRangeSlider) - #endif // QQUICKRANGESLIDER_P_H diff --git a/src/quicktemplates/qquickroundbutton_p.h b/src/quicktemplates/qquickroundbutton_p.h index bf589b673e..9a30ebe9a8 100644 --- a/src/quicktemplates/qquickroundbutton_p.h +++ b/src/quicktemplates/qquickroundbutton_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickRoundButtonPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickRoundButton : public QQuickButton +class Q_QUICKTEMPLATES2_EXPORT QQuickRoundButton : public QQuickButton { Q_OBJECT Q_PROPERTY(qreal radius READ radius WRITE setRadius RESET resetRadius NOTIFY radiusChanged FINAL) @@ -48,6 +48,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickRoundButton) - #endif // QQUICKROUNDBUTTON_P_H diff --git a/src/quicktemplates/qquickscrollbar.cpp b/src/quicktemplates/qquickscrollbar.cpp index 67fc8116a6..75de0363e9 100644 --- a/src/quicktemplates/qquickscrollbar.cpp +++ b/src/quicktemplates/qquickscrollbar.cpp @@ -122,9 +122,9 @@ QT_BEGIN_NAMESPACE \sa ScrollIndicator, ScrollView, {Customizing ScrollBar}, {Indicator Controls} */ -static const QQuickItemPrivate::ChangeTypes changeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed; -static const QQuickItemPrivate::ChangeTypes horizontalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitHeight; -static const QQuickItemPrivate::ChangeTypes verticalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitWidth; +static const QQuickItemPrivate::ChangeTypes QsbChangeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed; +static const QQuickItemPrivate::ChangeTypes QsbHorizontalChangeTypes = QsbChangeTypes | QQuickItemPrivate::ImplicitHeight; +static const QQuickItemPrivate::ChangeTypes QsbVerticalChangeTypes = QsbChangeTypes | QQuickItemPrivate::ImplicitWidth; QQuickScrollBarPrivate::VisualArea QQuickScrollBarPrivate::visualArea() const { @@ -368,6 +368,7 @@ QQuickScrollBar::QQuickScrollBar(QQuickItem *parent) Q_D(QQuickScrollBar); d->decreaseVisual = new QQuickIndicatorButton(this); d->increaseVisual = new QQuickIndicatorButton(this); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); setKeepMouseGrab(true); setAcceptedMouseButtons(Qt::LeftButton); #if QT_CONFIG(quicktemplates2_multitouch) @@ -409,13 +410,11 @@ void QQuickScrollBar::setSize(qreal size) size = qBound(0.0, size, 1.0); if (qFuzzyCompare(d->size, size)) return; - d->size = size; - auto oldVisualArea = d->visualArea(); - d->size = qBound(0.0, size, 1.0); + const auto oldVisualArea = d->visualArea(); + d->size = size; if (d->size + d->position > 1.0) { - setPosition(1.0 - d->size); - oldVisualArea = d->visualArea(); + d->setPosition(1.0 - d->size, false); } if (isComponentComplete()) @@ -450,15 +449,22 @@ qreal QQuickScrollBar::position() const void QQuickScrollBar::setPosition(qreal position) { Q_D(QQuickScrollBar); - if (!qt_is_finite(position) || qFuzzyCompare(d->position, position)) + d->setPosition(position); +} + +void QQuickScrollBarPrivate::setPosition(qreal newPosition, bool notifyVisualChange) +{ + Q_Q(QQuickScrollBar); + if (!qt_is_finite(newPosition) || qFuzzyCompare(position, newPosition)) return; - auto oldVisualArea = d->visualArea(); - d->position = position; - if (isComponentComplete()) - d->resizeContent(); - emit positionChanged(); - d->visualAreaChange(d->visualArea(), oldVisualArea); + auto oldVisualArea = visualArea(); + position = newPosition; + if (q->isComponentComplete()) + resizeContent(); + emit q->positionChanged(); + if (notifyVisualChange) + visualAreaChange(visualArea(), oldVisualArea); } /*! @@ -567,6 +573,11 @@ void QQuickScrollBar::setOrientation(Qt::Orientation orientation) if (d->orientation == orientation) return; + if (orientation == Qt::Horizontal) + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); + else + d->setSizePolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Preferred); + d->orientation = orientation; if (isComponentComplete()) d->resizeContent(); @@ -656,7 +667,17 @@ void QQuickScrollBar::resetInteractive() The following example keeps the vertical scroll bar always visible: - \snippet qtquickcontrols-scrollbar-policy.qml 1 + \snippet qtquickcontrols-scrollbar-policy-alwayson.qml 1 + + Styles may use this property in combination with the \l active property + in order to implement transient scroll bars. Transient scroll bars are + hidden shortly after the last interaction event (hover or press). This + is typically done by animating the opacity of the scroll bar. To override + this behavior, set the policy to \c ScrollBar.AlwaysOn or + \c ScrollBar.AlwaysOff, depending on the size of the content compared to + its view. For example, for a vertical \l ListView: + + \snippet qtquickcontrols-scrollbar-policy-alwayson-when-needed.qml 1 */ QQuickScrollBar::Policy QQuickScrollBar::policy() const { @@ -735,6 +756,7 @@ void QQuickScrollBar::setMinimumSize(qreal minimumSize) /*! \since QtQuick.Controls 2.4 (Qt 5.11) \qmlproperty real QtQuick.Controls::ScrollBar::visualSize + \readonly This property holds the effective visual size of the scroll bar, which may be limited by the \l {minimumSize}{minimum size}. @@ -750,6 +772,7 @@ qreal QQuickScrollBar::visualSize() const /*! \since QtQuick.Controls 2.4 (Qt 5.11) \qmlproperty real QtQuick.Controls::ScrollBar::visualPosition + \readonly This property holds the effective visual position of the scroll bar, which may be limited by the \l {minimumSize}{minimum size}. @@ -942,11 +965,11 @@ void QQuickScrollBarAttachedPrivate::initHorizontal() // If a scroll bar was previously hidden (due to e.g. setting a new contentItem // on a ScrollView), we need to make sure that we un-hide it. - // We don't bother checking if the item is actually the old one, because - // if it's not, all of the things the function does (setting parent, visibility, etc.) - // should be no-ops anyway. - if (auto control = qobject_cast<QQuickControl*>(q_func()->parent())) - QQuickControlPrivate::unhideOldItem(control, horizontal); + if (auto control = qobject_cast<QQuickControl*>(q_func()->parent())) { + const auto visibility = horizontal->policy() != QQuickScrollBar::AlwaysOff + ? QQuickControlPrivate::UnhideVisibility::Show : QQuickControlPrivate::UnhideVisibility::Hide; + QQuickControlPrivate::unhideOldItem(control, horizontal, visibility); + } layoutHorizontal(); horizontal->setSize(area->property("widthRatio").toReal()); @@ -969,8 +992,11 @@ void QQuickScrollBarAttachedPrivate::initVertical() if (parent && parent == flickable->parentItem()) vertical->stackAfter(flickable); - if (auto control = qobject_cast<QQuickControl*>(q_func()->parent())) - QQuickControlPrivate::unhideOldItem(control, vertical); + if (auto control = qobject_cast<QQuickControl*>(q_func()->parent())) { + const auto visibility = vertical->policy() != QQuickScrollBar::AlwaysOff + ? QQuickControlPrivate::UnhideVisibility::Show : QQuickControlPrivate::UnhideVisibility::Hide; + QQuickControlPrivate::unhideOldItem(control, vertical, visibility); + } layoutVertical(); vertical->setSize(area->property("heightRatio").toReal()); @@ -1152,11 +1178,11 @@ QQuickScrollBarAttached::~QQuickScrollBarAttached() { Q_D(QQuickScrollBarAttached); if (d->horizontal) { - QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, horizontalChangeTypes); + QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, QsbHorizontalChangeTypes); d->horizontal = nullptr; } if (d->vertical) { - QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, verticalChangeTypes); + QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, QsbVerticalChangeTypes); d->vertical = nullptr; } d->setFlickable(nullptr); @@ -1189,7 +1215,7 @@ void QQuickScrollBarAttached::setHorizontal(QQuickScrollBar *horizontal) return; if (d->horizontal) { - QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, horizontalChangeTypes); + QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, QsbHorizontalChangeTypes); QObjectPrivate::disconnect(d->horizontal, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollHorizontal); if (d->flickable) @@ -1203,7 +1229,7 @@ void QQuickScrollBarAttached::setHorizontal(QQuickScrollBar *horizontal) horizontal->setParentItem(qobject_cast<QQuickItem *>(parent())); horizontal->setOrientation(Qt::Horizontal); - QQuickItemPrivate::get(horizontal)->addItemChangeListener(d, horizontalChangeTypes); + QQuickItemPrivate::get(horizontal)->addItemChangeListener(d, QsbHorizontalChangeTypes); QObjectPrivate::connect(horizontal, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollHorizontal); if (d->flickable) @@ -1239,7 +1265,7 @@ void QQuickScrollBarAttached::setVertical(QQuickScrollBar *vertical) return; if (d->vertical) { - QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, verticalChangeTypes); + QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, QsbVerticalChangeTypes); QObjectPrivate::disconnect(d->vertical, &QQuickScrollBar::mirroredChanged, d, &QQuickScrollBarAttachedPrivate::mirrorVertical); QObjectPrivate::disconnect(d->vertical, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollVertical); @@ -1254,7 +1280,7 @@ void QQuickScrollBarAttached::setVertical(QQuickScrollBar *vertical) vertical->setParentItem(qobject_cast<QQuickItem *>(parent())); vertical->setOrientation(Qt::Vertical); - QQuickItemPrivate::get(vertical)->addItemChangeListener(d, verticalChangeTypes); + QQuickItemPrivate::get(vertical)->addItemChangeListener(d, QsbVerticalChangeTypes); QObjectPrivate::connect(vertical, &QQuickScrollBar::mirroredChanged, d, &QQuickScrollBarAttachedPrivate::mirrorVertical); QObjectPrivate::connect(vertical, &QQuickScrollBar::positionChanged, d, &QQuickScrollBarAttachedPrivate::scrollVertical); diff --git a/src/quicktemplates/qquickscrollbar_p.h b/src/quicktemplates/qquickscrollbar_p.h index 6e4eade1f8..b43cef1734 100644 --- a/src/quicktemplates/qquickscrollbar_p.h +++ b/src/quicktemplates/qquickscrollbar_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QQuickScrollBarAttached; class QQuickScrollBarPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollBar : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickScrollBar : public QQuickControl { Q_OBJECT Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY sizeChanged FINAL) @@ -156,7 +156,7 @@ private: class QQuickScrollBarAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollBarAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickScrollBarAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickScrollBar *horizontal READ horizontal WRITE setHorizontal NOTIFY horizontalChanged FINAL) @@ -183,6 +183,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickScrollBar) - #endif // QQUICKSCROLLBAR_P_H diff --git a/src/quicktemplates/qquickscrollbar_p_p.h b/src/quicktemplates/qquickscrollbar_p_p.h index cad54882d2..2a834ce71e 100644 --- a/src/quicktemplates/qquickscrollbar_p_p.h +++ b/src/quicktemplates/qquickscrollbar_p_p.h @@ -45,6 +45,7 @@ public: qreal logicalPosition(qreal position) const; + void setPosition(qreal position, bool notifyVisualChange = true); qreal snapPosition(qreal position) const; qreal positionAt(const QPointF &point) const; void setInteractive(bool interactive); diff --git a/src/quicktemplates/qquickscrollindicator.cpp b/src/quicktemplates/qquickscrollindicator.cpp index 3a691852bc..9928503e86 100644 --- a/src/quicktemplates/qquickscrollindicator.cpp +++ b/src/quicktemplates/qquickscrollindicator.cpp @@ -93,9 +93,9 @@ QT_BEGIN_NAMESPACE \sa ScrollBar, {Customizing ScrollIndicator}, {Indicator Controls} */ -static const QQuickItemPrivate::ChangeTypes changeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed; -static const QQuickItemPrivate::ChangeTypes horizontalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitHeight; -static const QQuickItemPrivate::ChangeTypes verticalChangeTypes = changeTypes | QQuickItemPrivate::ImplicitWidth; +static const QQuickItemPrivate::ChangeTypes QsiChangeTypes = QQuickItemPrivate::Geometry | QQuickItemPrivate::Destroyed; +static const QQuickItemPrivate::ChangeTypes QsiHorizontalChangeTypes = QsiChangeTypes | QQuickItemPrivate::ImplicitHeight; +static const QQuickItemPrivate::ChangeTypes QsiVerticalChangeTypes = QsiChangeTypes | QQuickItemPrivate::ImplicitWidth; class QQuickScrollIndicatorPrivate : public QQuickControlPrivate { @@ -168,6 +168,8 @@ void QQuickScrollIndicatorPrivate::resizeContent() QQuickScrollIndicator::QQuickScrollIndicator(QQuickItem *parent) : QQuickControl(*(new QQuickScrollIndicatorPrivate), parent) { + Q_D(QQuickScrollIndicator); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); } QQuickScrollIndicatorAttached *QQuickScrollIndicator::qmlAttachedProperties(QObject *object) @@ -295,6 +297,11 @@ void QQuickScrollIndicator::setOrientation(Qt::Orientation orientation) if (d->orientation == orientation) return; + if (orientation == Qt::Horizontal) + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); + else + d->setSizePolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Preferred); + d->orientation = orientation; if (isComponentComplete()) d->resizeContent(); @@ -496,9 +503,9 @@ QQuickScrollIndicatorAttached::~QQuickScrollIndicatorAttached() Q_D(QQuickScrollIndicatorAttached); if (d->flickable) { if (d->horizontal) - QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, horizontalChangeTypes); + QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, QsiHorizontalChangeTypes); if (d->vertical) - QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d,verticalChangeTypes); + QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, QsiVerticalChangeTypes); // NOTE: Use removeItemChangeListener(Geometry) instead of updateOrRemoveGeometryChangeListener(Size). // The latter doesn't remove the listener but only resets its types. Thus, it leaves behind a dangling // pointer on destruction. @@ -533,7 +540,7 @@ void QQuickScrollIndicatorAttached::setHorizontal(QQuickScrollIndicator *horizon return; if (d->horizontal && d->flickable) { - QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, horizontalChangeTypes); + QQuickItemPrivate::get(d->horizontal)->removeItemChangeListener(d, QsiHorizontalChangeTypes); QObjectPrivate::disconnect(d->flickable, &QQuickFlickable::movingHorizontallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateHorizontal); // TODO: export QQuickFlickableVisibleArea @@ -549,7 +556,7 @@ void QQuickScrollIndicatorAttached::setHorizontal(QQuickScrollIndicator *horizon horizontal->setParentItem(d->flickable); horizontal->setOrientation(Qt::Horizontal); - QQuickItemPrivate::get(horizontal)->addItemChangeListener(d, horizontalChangeTypes); + QQuickItemPrivate::get(horizontal)->addItemChangeListener(d, QsiHorizontalChangeTypes); QObjectPrivate::connect(d->flickable, &QQuickFlickable::movingHorizontallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateHorizontal); // TODO: export QQuickFlickableVisibleArea @@ -591,7 +598,7 @@ void QQuickScrollIndicatorAttached::setVertical(QQuickScrollIndicator *vertical) return; if (d->vertical && d->flickable) { - QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, verticalChangeTypes); + QQuickItemPrivate::get(d->vertical)->removeItemChangeListener(d, QsiVerticalChangeTypes); QObjectPrivate::disconnect(d->flickable, &QQuickFlickable::movingVerticallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateVertical); // TODO: export QQuickFlickableVisibleArea @@ -607,7 +614,7 @@ void QQuickScrollIndicatorAttached::setVertical(QQuickScrollIndicator *vertical) vertical->setParentItem(d->flickable); vertical->setOrientation(Qt::Vertical); - QQuickItemPrivate::get(vertical)->addItemChangeListener(d, verticalChangeTypes); + QQuickItemPrivate::get(vertical)->addItemChangeListener(d, QsiVerticalChangeTypes); QObjectPrivate::connect(d->flickable, &QQuickFlickable::movingVerticallyChanged, d, &QQuickScrollIndicatorAttachedPrivate::activateVertical); // TODO: export QQuickFlickableVisibleArea diff --git a/src/quicktemplates/qquickscrollindicator_p.h b/src/quicktemplates/qquickscrollindicator_p.h index 1af6407690..e6647c77ab 100644 --- a/src/quicktemplates/qquickscrollindicator_p.h +++ b/src/quicktemplates/qquickscrollindicator_p.h @@ -23,7 +23,7 @@ class QQuickFlickable; class QQuickScrollIndicatorAttached; class QQuickScrollIndicatorPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollIndicator : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickScrollIndicator : public QQuickControl { Q_OBJECT Q_PROPERTY(qreal size READ size WRITE setSize NOTIFY sizeChanged FINAL) @@ -96,7 +96,7 @@ private: class QQuickScrollIndicatorAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollIndicatorAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickScrollIndicatorAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickScrollIndicator *horizontal READ horizontal WRITE setHorizontal NOTIFY horizontalChanged FINAL) @@ -123,6 +123,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickScrollIndicator) - #endif // QQUICKSCROLLINDICATOR_P_H diff --git a/src/quicktemplates/qquickscrollview.cpp b/src/quicktemplates/qquickscrollview.cpp index 8a352b7f61..5f7c7bf2fd 100644 --- a/src/quicktemplates/qquickscrollview.cpp +++ b/src/quicktemplates/qquickscrollview.cpp @@ -98,11 +98,17 @@ public: QQmlListProperty<QObject> contentData() override; QQmlListProperty<QQuickItem> contentChildren() override; QList<QQuickItem *> contentChildItems() const override; + QQuickItem* getFirstChild() const override; QQuickItem *getContentItem() override; - QQuickFlickable *ensureFlickable(bool content); - bool setFlickable(QQuickFlickable *flickable, bool content); + enum class ContentItemFlag { + DoNotSet, + Set + }; + + QQuickFlickable *ensureFlickable(ContentItemFlag contentItemFlag); + bool setFlickable(QQuickFlickable *flickable, ContentItemFlag contentItemFlag); void flickableContentWidthChanged(); void flickableContentHeightChanged(); @@ -127,10 +133,17 @@ public: void itemImplicitWidthChanged(QQuickItem *item) override; + void updateScrollBarWidth(); + void updateScrollBarHeight(); + + void disconnectScrollBarSignals(QQuickScrollBarAttachedPrivate *scrollBar); bool wasTouched = false; QQuickFlickable *flickable = nullptr; bool flickableHasExplicitContentWidth = true; bool flickableHasExplicitContentHeight = true; + bool isUpdatingScrollBar = false; + qreal effectiveScrollBarWidth = 0; + qreal effectiveScrollBarHeight = 0; }; QList<QQuickItem *> QQuickScrollViewPrivate::contentChildItems() const @@ -145,15 +158,23 @@ QQuickItem *QQuickScrollViewPrivate::getContentItem() { if (!contentItem) executeContentItem(); - return ensureFlickable(false); + // This function is called by QQuickControl::contentItem() to lazily create + // a contentItem, so we don't need to try to set it again. + return ensureFlickable(ContentItemFlag::DoNotSet); } -QQuickFlickable *QQuickScrollViewPrivate::ensureFlickable(bool content) +QQuickItem* QQuickScrollViewPrivate::getFirstChild() const +{ + return contentChildItems().value(0); +} + +QQuickFlickable *QQuickScrollViewPrivate::ensureFlickable(ContentItemFlag contentItemFlag) { Q_Q(QQuickScrollView); if (!flickable) { flickableHasExplicitContentWidth = false; flickableHasExplicitContentHeight = false; + // Pass ourselves as the Flickable's parent item. auto flickable = new QQuickFlickable(q); // We almost always want to clip the flickable so that flickable // contents doesn't show up outside the scrollview. The only time @@ -163,12 +184,64 @@ QQuickFlickable *QQuickScrollViewPrivate::ensureFlickable(bool content) // child inside the scrollview, and control clipping on it explicit. flickable->setClip(true); flickable->setPixelAligned(true); - setFlickable(flickable, content); + setFlickable(flickable, contentItemFlag); } return flickable; } -bool QQuickScrollViewPrivate::setFlickable(QQuickFlickable *item, bool content) +void QQuickScrollViewPrivate::updateScrollBarWidth() +{ + Q_Q(QQuickScrollView); + qreal oldEffectiveScrollBarWidth = effectiveScrollBarWidth; + if (auto *vBar = verticalScrollBar()) { + if (vBar->policy() == QQuickScrollBar::AlwaysOff || !vBar->isVisible()) + effectiveScrollBarWidth = 0; + else + effectiveScrollBarWidth = vBar->width(); + } + if (effectiveScrollBarWidth != oldEffectiveScrollBarWidth) { + if (!isUpdatingScrollBar) { + QScopedValueRollback<bool> rollback(isUpdatingScrollBar, true); + emit q->effectiveScrollBarWidthChanged(); + } + } +} + +void QQuickScrollViewPrivate::updateScrollBarHeight() +{ + Q_Q(QQuickScrollView); + qreal oldEffectiveScrollBarHeight = effectiveScrollBarHeight; + if (auto *hBar = horizontalScrollBar()) { + if (hBar->policy() == QQuickScrollBar::AlwaysOff || !hBar->isVisible()) + effectiveScrollBarHeight = 0; + else + effectiveScrollBarHeight = hBar->height(); + } + if (effectiveScrollBarHeight != oldEffectiveScrollBarHeight) { + if (!isUpdatingScrollBar) { + QScopedValueRollback<bool> rollback(isUpdatingScrollBar, true); + emit q->effectiveScrollBarHeightChanged(); + } + + } +} + +void QQuickScrollViewPrivate::disconnectScrollBarSignals(QQuickScrollBarAttachedPrivate *scrollBar) +{ + if (!scrollBar) + return; + + if (scrollBar->vertical) { + QObjectPrivate::disconnect(scrollBar->vertical, &QQuickScrollBar::policyChanged, this, &QQuickScrollViewPrivate::updateScrollBarWidth); + QObjectPrivate::disconnect(scrollBar->vertical, &QQuickScrollBar::visibleChanged, this, &QQuickScrollViewPrivate::updateScrollBarWidth); + } + if (scrollBar->horizontal) { + QObjectPrivate::disconnect(scrollBar->horizontal, &QQuickScrollBar::policyChanged, this, &QQuickScrollViewPrivate::updateScrollBarHeight); + QObjectPrivate::disconnect(scrollBar->horizontal, &QQuickScrollBar::visibleChanged, this, &QQuickScrollViewPrivate::updateScrollBarHeight); + } +} + +bool QQuickScrollViewPrivate::setFlickable(QQuickFlickable *item, ContentItemFlag contentItemFlag) { Q_Q(QQuickScrollView); if (item == flickable) @@ -179,8 +252,11 @@ bool QQuickScrollViewPrivate::setFlickable(QQuickFlickable *item, bool content) if (flickable) { flickable->removeEventFilter(q); - if (attached) - QQuickScrollBarAttachedPrivate::get(attached)->setFlickable(nullptr); + if (attached) { + auto *scrollBar = QQuickScrollBarAttachedPrivate::get(attached); + scrollBar->setFlickable(nullptr); + disconnectScrollBarSignals(scrollBar); + } QObjectPrivate::disconnect(flickable->contentItem(), &QQuickItem::childrenChanged, this, &QQuickPanePrivate::contentChildrenChange); QObjectPrivate::disconnect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickScrollViewPrivate::flickableContentWidthChanged); @@ -188,7 +264,7 @@ bool QQuickScrollViewPrivate::setFlickable(QQuickFlickable *item, bool content) } flickable = item; - if (content) + if (contentItemFlag == ContentItemFlag::Set) q->setContentItem(flickable); if (flickable) { @@ -202,8 +278,18 @@ bool QQuickScrollViewPrivate::setFlickable(QQuickFlickable *item, bool content) else flickableContentHeightChanged(); - if (attached) - QQuickScrollBarAttachedPrivate::get(attached)->setFlickable(flickable); + if (attached) { + auto *scrollBar = QQuickScrollBarAttachedPrivate::get(attached); + scrollBar->setFlickable(flickable); + if (scrollBar->vertical) { + QObjectPrivate::connect(scrollBar->vertical, &QQuickScrollBar::policyChanged, this, &QQuickScrollViewPrivate::updateScrollBarWidth); + QObjectPrivate::connect(scrollBar->vertical, &QQuickScrollBar::visibleChanged, this, &QQuickScrollViewPrivate::updateScrollBarWidth); + } + if (scrollBar->horizontal) { + QObjectPrivate::connect(scrollBar->horizontal, &QQuickScrollBar::policyChanged, this, &QQuickScrollViewPrivate::updateScrollBarHeight); + QObjectPrivate::connect(scrollBar->horizontal, &QQuickScrollBar::visibleChanged, this, &QQuickScrollViewPrivate::updateScrollBarHeight); + } + } QObjectPrivate::connect(flickable->contentItem(), &QQuickItem::childrenChanged, this, &QQuickPanePrivate::contentChildrenChange); QObjectPrivate::connect(flickable, &QQuickFlickable::contentWidthChanged, this, &QQuickScrollViewPrivate::flickableContentWidthChanged); @@ -305,11 +391,14 @@ void QQuickScrollViewPrivate::setScrollBarsInteractive(bool interactive) void QQuickScrollViewPrivate::contentData_append(QQmlListProperty<QObject> *prop, QObject *obj) { QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); - if (!p->flickable && p->setFlickable(qobject_cast<QQuickFlickable *>(obj), true)) + // If we don't yet have a flickable assigned, and this object is a Flickable, + // make it our contentItem. + if (!p->flickable && p->setFlickable(qobject_cast<QQuickFlickable *>(obj), ContentItemFlag::Set)) return; - QQuickFlickable *flickable = p->ensureFlickable(true); + QQuickFlickable *flickable = p->ensureFlickable(ContentItemFlag::Set); Q_ASSERT(flickable); + // Add the object that was declared as a child of us as a child object of the Flickable. QQmlListProperty<QObject> data = flickable->flickableData(); data.append(&data, obj); } @@ -348,10 +437,11 @@ void QQuickScrollViewPrivate::contentChildren_append(QQmlListProperty<QQuickItem { QQuickScrollViewPrivate *p = static_cast<QQuickScrollViewPrivate *>(prop->data); if (!p->flickable) - p->setFlickable(qobject_cast<QQuickFlickable *>(item), true); + p->setFlickable(qobject_cast<QQuickFlickable *>(item), ContentItemFlag::Set); - QQuickFlickable *flickable = p->ensureFlickable(true); + QQuickFlickable *flickable = p->ensureFlickable(ContentItemFlag::Set); Q_ASSERT(flickable); + // Add the item that was declared as a child of us as a child item of the Flickable's contentItem. QQmlListProperty<QQuickItem> children = flickable->flickableChildren(); children.append(&children, item); } @@ -406,6 +496,48 @@ QQuickScrollView::QQuickScrollView(QQuickItem *parent) setWheelEnabled(true); } +QQuickScrollView::~QQuickScrollView() +{ + Q_D(QQuickScrollView); + QQuickScrollBarAttached *attached = qobject_cast<QQuickScrollBarAttached *>(qmlAttachedPropertiesObject<QQuickScrollBar>(this, false)); + if (attached) { + auto *scrollBar = QQuickScrollBarAttachedPrivate::get(attached); + d->disconnectScrollBarSignals(scrollBar); + } +} + +/*! + \qmlproperty real QtQuick.Controls::ScrollView::effectiveScrollBarWidth + \since 6.6 + + This property holds the effective width of the vertical scrollbar. + When the scrollbar policy is \c QQuickScrollBar::AlwaysOff or the scrollbar + is not visible, this property is \c 0. + + \sa {ScrollBar::policy} +*/ +qreal QQuickScrollView::effectiveScrollBarWidth() +{ + Q_D(QQuickScrollView); + return d->effectiveScrollBarWidth; +} + +/*! + \qmlproperty real QtQuick.Controls::ScrollView::effectiveScrollBarHeight + \since 6.6 + + This property holds the effective height of the horizontal scrollbar. + When the scrollbar policy is \c QQuickScrollBar::AlwaysOff or the scrollbar + is not visible, this property is \c 0. + + \sa {ScrollBar::policy} +*/ +qreal QQuickScrollView::effectiveScrollBarHeight() +{ + Q_D(QQuickScrollView); + return d->effectiveScrollBarHeight; +} + /*! \qmlproperty list<QtObject> QtQuick.Controls::ScrollView::contentData \qmldefault @@ -543,7 +675,7 @@ void QQuickScrollView::componentComplete() Q_D(QQuickScrollView); QQuickPane::componentComplete(); if (!d->contentItem) - d->ensureFlickable(true); + d->ensureFlickable(QQuickScrollViewPrivate::ContentItemFlag::Set); } void QQuickScrollView::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) @@ -557,7 +689,25 @@ void QQuickScrollView::contentItemChange(QQuickItem *newItem, QQuickItem *oldIte auto newItemAsFlickable = qobject_cast<QQuickFlickable *>(newItem); if (newItem && !newItemAsFlickable) qmlWarning(this) << "ScrollView only supports Flickable types as its contentItem"; - d->setFlickable(newItemAsFlickable, false); + // This is called by QQuickControlPrivate::setContentItem_helper, so no need to + // try to set it as the contentItem. + d->setFlickable(newItemAsFlickable, QQuickScrollViewPrivate::ContentItemFlag::DoNotSet); + // We do, however, need to set us as its parent item, as setContentItem_helper will only + // do so if the item doesn't already have a parent. If newItem wasn't declared as our + // child and was instead imperatively assigned, it may already have a parent item, + // which we'll need to override. + if (newItem) { + newItem->setParentItem(this); + + // Make sure that the scroll bars are stacked in front of the flickable, + // otherwise events won't get through to them. + QQuickScrollBar *verticalBar = d->verticalScrollBar(); + if (verticalBar) + verticalBar->stackAfter(newItem); + QQuickScrollBar *horizontalBar = d->horizontalScrollBar(); + if (horizontalBar) + horizontalBar->stackAfter(newItem); + } } QQuickPane::contentItemChange(newItem, oldItem); } @@ -573,10 +723,14 @@ void QQuickScrollView::contentSizeChange(const QSizeF &newSize, const QSizeF &ol // exception is if the application has assigned a content size // directly to the scrollview, which will then win even if the // application has assigned something else to the flickable. - if (d->hasContentWidth || !d->flickableHasExplicitContentWidth) + if (d->hasContentWidth || !d->flickableHasExplicitContentWidth) { d->flickable->setContentWidth(newSize.width()); - if (d->hasContentHeight || !d->flickableHasExplicitContentHeight) + d->updateScrollBarWidth(); + } + if (d->hasContentHeight || !d->flickableHasExplicitContentHeight) { d->flickable->setContentHeight(newSize.height()); + d->updateScrollBarHeight(); + } } } diff --git a/src/quicktemplates/qquickscrollview_p.h b/src/quicktemplates/qquickscrollview_p.h index 927de4365c..6cdafd5199 100644 --- a/src/quicktemplates/qquickscrollview_p.h +++ b/src/quicktemplates/qquickscrollview_p.h @@ -22,14 +22,19 @@ QT_BEGIN_NAMESPACE class QQuickScrollViewPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickScrollView : public QQuickPane +class Q_QUICKTEMPLATES2_EXPORT QQuickScrollView : public QQuickPane { Q_OBJECT QML_NAMED_ELEMENT(ScrollView) QML_ADDED_IN_VERSION(2, 2) + Q_PROPERTY(qreal effectiveScrollBarWidth READ effectiveScrollBarWidth NOTIFY effectiveScrollBarWidthChanged FINAL REVISION(6, 6)) + Q_PROPERTY(qreal effectiveScrollBarHeight READ effectiveScrollBarHeight NOTIFY effectiveScrollBarHeightChanged FINAL REVISION(6, 6)) public: explicit QQuickScrollView(QQuickItem *parent = nullptr); + ~QQuickScrollView(); + qreal effectiveScrollBarWidth(); + qreal effectiveScrollBarHeight(); protected: bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; @@ -44,6 +49,10 @@ protected: QAccessible::Role accessibleRole() const override; #endif +Q_SIGNALS: + Q_REVISION(6, 6) void effectiveScrollBarWidthChanged(); + Q_REVISION(6, 6) void effectiveScrollBarHeightChanged(); + private: Q_DISABLE_COPY(QQuickScrollView) Q_DECLARE_PRIVATE(QQuickScrollView) @@ -51,6 +60,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickScrollView) - #endif // QQUICKSCROLLVIEW_P_H diff --git a/src/quicktemplates/qquickselectionrectangle.cpp b/src/quicktemplates/qquickselectionrectangle.cpp index a2adc43dcd..5f8dff758e 100644 --- a/src/quicktemplates/qquickselectionrectangle.cpp +++ b/src/quicktemplates/qquickselectionrectangle.cpp @@ -81,7 +81,7 @@ QT_BEGIN_NAMESPACE The handle is not hidden by default when a selection is removed. Instead, this is the responsibility of the delegate, to open up for custom fade-out animations. The easiest way to ensure that the handle - ends up hidden, is to simply bind \l visible to the the \l active + ends up hidden, is to simply bind \l {Item::}{visible} to the \l active state of the SelectionRectangle: \qml @@ -109,7 +109,7 @@ QT_BEGIN_NAMESPACE The handle is not hidden by default when a selection is removed. Instead, this is the responsibility of the delegate, to open up for custom fade-out animations. The easiest way to ensure that the handle - ends up hidden, is to simply bind \l visible to the the \l active + ends up hidden, is to simply bind \l {Item::}{visible} to the \l active state of the SelectionRectangle: \qml @@ -144,14 +144,14 @@ QT_BEGIN_NAMESPACE */ /*! - \qmlattachedproperty SelectionRectangle QtQuick::SelectionRectangle::control + \qmlattachedproperty SelectionRectangle QtQuick.Controls::SelectionRectangle::control This attached property holds the SelectionRectangle that manages the delegate instance. It is attached to each handle instance. */ /*! - \qmlattachedproperty bool QtQuick::SelectionRectangle::dragging + \qmlattachedproperty bool QtQuick.Controls::SelectionRectangle::dragging This attached property will be \c true if the user is dragging on the handle. It is attached to each handle instance. @@ -170,56 +170,108 @@ QQuickSelectionRectanglePrivate::QQuickSelectionRectanglePrivate() else m_selectable->setSelectionEndPos(m_scrollToPoint); updateHandles(); - const QSizeF dist = m_selectable->scrollTowardsSelectionPoint(m_scrollToPoint, m_scrollSpeed); + const QSizeF dist = m_selectable->scrollTowardsPoint(m_scrollToPoint, m_scrollSpeed); m_scrollToPoint.rx() += dist.width() > 0 ? m_scrollSpeed.width() : -m_scrollSpeed.width(); m_scrollToPoint.ry() += dist.height() > 0 ? m_scrollSpeed.height() : -m_scrollSpeed.height(); m_scrollSpeed = QSizeF(qAbs(dist.width() * 0.007), qAbs(dist.height() * 0.007)); }); - QObject::connect(m_tapHandler, &QQuickTapHandler::tapped, [this] { - updateActiveState(false); - }); + QObject::connect(m_tapHandler, &QQuickTapHandler::pressedChanged, [this]() { + if (!m_tapHandler->isPressed()) + return; + if (m_effectiveSelectionMode != QQuickSelectionRectangle::Drag) + return; - QObject::connect(m_tapHandler, &QQuickTapHandler::longPressed, [this]() { const QPointF pos = m_tapHandler->point().pressPosition(); const auto modifiers = m_tapHandler->point().modifiers(); + if (modifiers & ~(Qt::ControlModifier | Qt::ShiftModifier)) + return; + + if (modifiers & Qt::ShiftModifier) { + // Extend the selection towards the pressed cell. If there is no + // existing selection, start a new selection from the current item + // to the pressed item. + if (!m_active) { + if (!m_selectable->startSelection(pos, modifiers)) + return; + m_selectable->setSelectionStartPos(QPoint{-1, -1}); + } + m_selectable->setSelectionEndPos(pos); + updateHandles(); + updateActiveState(true); + } else if (modifiers & Qt::ControlModifier) { + // Select a single cell, but keep the old selection (unless + // m_selectable->startSelection(pos. modifiers) returns false, which + // it will if selectionMode only allows a single selection). + if (handleUnderPos(pos) != nullptr) { + // Don't allow press'n'hold to start a new + // selection if it started on top of a handle. + return; + } - if (!m_selectable->startSelection(pos)) + if (!m_selectable->startSelection(pos, modifiers)) + return; + m_selectable->setSelectionStartPos(pos); + m_selectable->setSelectionEndPos(pos); + updateHandles(); + updateActiveState(true); + } + }); + + QObject::connect(m_tapHandler, &QQuickTapHandler::longPressed, [this]() { + if (m_effectiveSelectionMode != QQuickSelectionRectangle::PressAndHold) return; + + const QPointF pos = m_tapHandler->point().pressPosition(); + const auto modifiers = m_tapHandler->point().modifiers(); if (handleUnderPos(pos) != nullptr) { // Don't allow press'n'hold to start a new // selection if it started on top of a handle. return; } - if (!m_alwaysAcceptPressAndHold) { - if (m_selectionMode == QQuickSelectionRectangle::Auto) { - // In Auto mode, we only accept press and hold from touch - if (m_tapHandler->point().device()->pointerType() != QPointingDevice::PointerType::Finger) + + if (modifiers == Qt::ShiftModifier) { + // Extend the selection towards the pressed cell. If there is no + // existing selection, start a new selection from the current item + // to the pressed item. + if (!m_active) { + if (!m_selectable->startSelection(pos, modifiers)) return; - } else if (m_selectionMode != QQuickSelectionRectangle::PressAndHold) { - return; + m_selectable->setSelectionStartPos(QPoint{-1, -1}); } + m_selectable->setSelectionEndPos(pos); + updateHandles(); + updateActiveState(true); + } else { + // Select a single cell. m_selectable->startSelection() will decide + // if the existing selection should also be cleared. + if (!m_selectable->startSelection(pos, modifiers)) + return; + m_selectable->setSelectionStartPos(pos); + m_selectable->setSelectionEndPos(pos); + updateHandles(); + updateActiveState(true); } - - if (!modifiers.testFlag(Qt::ShiftModifier)) - m_selectable->clearSelection(); - m_selectable->setSelectionStartPos(pos); - m_selectable->setSelectionEndPos(pos); - updateHandles(); - updateActiveState(true); }); QObject::connect(m_dragHandler, &QQuickDragHandler::activeChanged, [this]() { + Q_ASSERT(m_effectiveSelectionMode == QQuickSelectionRectangle::Drag); const QPointF startPos = m_dragHandler->centroid().pressPosition(); const QPointF dragPos = m_dragHandler->centroid().position(); const auto modifiers = m_dragHandler->centroid().modifiers(); + if (modifiers & ~(Qt::ControlModifier | Qt::ShiftModifier)) + return; if (m_dragHandler->active()) { - if (!m_selectable->startSelection(startPos)) - return; - if (!modifiers.testFlag(Qt::ShiftModifier)) - m_selectable->clearSelection(); - m_selectable->setSelectionStartPos(startPos); + // Start a new selection unless there is an active selection + // already, and one of the relevant modifiers are being held. + // In that case we continue to extend the active selection instead. + const bool modifiersHeld = modifiers & (Qt::ControlModifier | Qt::ShiftModifier); + if (!m_active || !modifiersHeld) { + if (!m_selectable->startSelection(startPos, modifiers)) + return; + m_selectable->setSelectionStartPos(startPos); + } m_selectable->setSelectionEndPos(dragPos); m_draggedHandle = nullptr; updateHandles(); @@ -248,7 +300,7 @@ void QQuickSelectionRectanglePrivate::scrollTowardsPos(const QPointF &pos) if (m_scrollTimer.isActive()) return; - const QSizeF dist = m_selectable->scrollTowardsSelectionPoint(m_scrollToPoint, m_scrollSpeed); + const QSizeF dist = m_selectable->scrollTowardsPoint(m_scrollToPoint, m_scrollSpeed); if (!dist.isNull()) m_scrollTimer.start(1); } @@ -409,6 +461,25 @@ void QQuickSelectionRectanglePrivate::connectToTarget() if (const auto flickable = qobject_cast<QQuickFlickable *>(m_target)) { connect(flickable, &QQuickFlickable::interactiveChanged, this, &QQuickSelectionRectanglePrivate::updateSelectionMode); } + + // Add a callback function that tells if the selection was + // modified outside of the actions taken by SelectionRectangle. + m_selectable->setCallback([this](QQuickSelectable::CallBackFlag flag){ + switch (flag) { + case QQuickSelectable::CallBackFlag::CancelSelection: + // The selection is either cleared, or can no longer be + // represented as a rectangle with two selection handles. + updateActiveState(false); + break; + case QQuickSelectable::CallBackFlag::SelectionRectangleChanged: + // The selection has changed, but the selection is still + // rectangular and without holes. + updateHandles(); + break; + default: + Q_UNREACHABLE(); + } + }); } void QQuickSelectionRectanglePrivate::updateSelectionMode() @@ -419,26 +490,33 @@ void QQuickSelectionRectanglePrivate::updateSelectionMode() m_tapHandler->setEnabled(enabled); if (m_selectionMode == QQuickSelectionRectangle::Auto) { - if (qobject_cast<QQuickScrollView *>(m_target->parentItem())) { + if (m_target && qobject_cast<QQuickScrollView *>(m_target->parentItem())) { // ScrollView allows flicking with touch, but not with mouse. So we do // the same here: you can drag to select with a mouse, but not with touch. + m_effectiveSelectionMode = QQuickSelectionRectangle::Drag; m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse); m_dragHandler->setEnabled(enabled); } else if (const auto flickable = qobject_cast<QQuickFlickable *>(m_target)) { - m_dragHandler->setEnabled(enabled && !flickable->isInteractive()); + if (enabled && !flickable->isInteractive()) { + m_effectiveSelectionMode = QQuickSelectionRectangle::Drag; + m_dragHandler->setEnabled(true); + } else { + m_effectiveSelectionMode = QQuickSelectionRectangle::PressAndHold; + m_dragHandler->setEnabled(false); + } } else { + m_effectiveSelectionMode = QQuickSelectionRectangle::Drag; m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::Mouse); m_dragHandler->setEnabled(enabled); } } else if (m_selectionMode == QQuickSelectionRectangle::Drag) { + m_effectiveSelectionMode = QQuickSelectionRectangle::Drag; m_dragHandler->setAcceptedDevices(QInputDevice::DeviceType::AllDevices); m_dragHandler->setEnabled(enabled); } else { + m_effectiveSelectionMode = QQuickSelectionRectangle::PressAndHold; m_dragHandler->setEnabled(false); } - - // If you can't select using a drag, we always accept a PressAndHold - m_alwaysAcceptPressAndHold = !m_dragHandler->enabled(); } QQuickSelectionRectangleAttached *QQuickSelectionRectanglePrivate::getAttachedObject(const QObject *object) const @@ -480,6 +558,7 @@ void QQuickSelectionRectangle::setTarget(QQuickItem *target) d->m_tapHandler->setParent(this); d->m_dragHandler->setParent(this); d->m_target->disconnect(this); + d->m_selectable->setCallback(nullptr); } d->m_target = target; diff --git a/src/quicktemplates/qquickselectionrectangle_p.h b/src/quicktemplates/qquickselectionrectangle_p.h index 2cf3c22062..5a1cf7e041 100644 --- a/src/quicktemplates/qquickselectionrectangle_p.h +++ b/src/quicktemplates/qquickselectionrectangle_p.h @@ -18,13 +18,15 @@ #include <QtQuick/qquickitem.h> #include <QtQuickTemplates2/private/qquickcontrol_p.h> +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE class QQuickSelectionRectanglePrivate; class QQuickSelectable; class QQuickSelectionRectangleAttached; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSelectionRectangle : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickSelectionRectangle : public QQuickControl { Q_OBJECT Q_PROPERTY(SelectionMode selectionMode READ selectionMode WRITE setSelectionMode NOTIFY selectionModeChanged FINAL) @@ -77,7 +79,7 @@ private: Q_DECLARE_PRIVATE(QQuickSelectionRectangle) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSelectionRectangleAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickSelectionRectangleAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickSelectionRectangle *control READ control NOTIFY controlChanged FINAL) @@ -105,6 +107,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickSelectionRectangle) - #endif // QQUICKSELECTIONRECTANGLE_P_H diff --git a/src/quicktemplates/qquickselectionrectangle_p_p.h b/src/quicktemplates/qquickselectionrectangle_p_p.h index 4144550aa7..d053b8bf42 100644 --- a/src/quicktemplates/qquickselectionrectangle_p_p.h +++ b/src/quicktemplates/qquickselectionrectangle_p_p.h @@ -66,7 +66,7 @@ public: QSizeF m_scrollSpeed = QSizeF(1, 1); QQuickSelectionRectangle::SelectionMode m_selectionMode = QQuickSelectionRectangle::Auto; - bool m_alwaysAcceptPressAndHold = false; + QQuickSelectionRectangle::SelectionMode m_effectiveSelectionMode = QQuickSelectionRectangle::Drag; bool m_enabled = true; bool m_active = false; diff --git a/src/quicktemplates/qquickshortcutcontext.cpp b/src/quicktemplates/qquickshortcutcontext.cpp index 2d07eb44f9..503eb270a7 100644 --- a/src/quicktemplates/qquickshortcutcontext.cpp +++ b/src/quicktemplates/qquickshortcutcontext.cpp @@ -4,8 +4,11 @@ #include "qquickshortcutcontext_p_p.h" #include "qquickoverlay_p_p.h" #include "qquicktooltip_p.h" +#include <QtQmlModels/private/qtqmlmodels-config_p.h> +#if QT_CONFIG(qml_object_model) #include "qquickmenu_p.h" #include "qquickmenu_p_p.h" +#endif #include "qquickpopup_p.h" #include <QtCore/qloggingcategory.h> @@ -27,6 +30,9 @@ static bool isBlockedByPopup(QQuickItem *item) if (qobject_cast<QQuickToolTip *>(popup)) continue; // ignore tooltips (QTBUG-60492) if (popup->isModal() || popup->closePolicy() & QQuickPopup::CloseOnEscape) { + qCDebug(lcContextMatcher) << popup << "is modal or has a CloseOnEscape policy;" + << "if the following are both true," << item << "will be blocked by it:" + << (item != popup->popupItem()) << !popup->popupItem()->isAncestorOf(item); return item != popup->popupItem() && !popup->popupItem()->isAncestorOf(item); } } @@ -50,6 +56,7 @@ bool QQuickShortcutContext::matcher(QObject *obj, Qt::ShortcutContext context) obj = popup->window(); item = popup->popupItem(); +#if QT_CONFIG(qml_object_model) if (!obj) { // The popup has no associated window (yet). However, sub-menus, // unlike top-level menus, will not have an associated window @@ -61,15 +68,16 @@ bool QQuickShortcutContext::matcher(QObject *obj, Qt::ShortcutContext context) obj = parentMenu->window(); } } +#endif break; } obj = obj->parent(); } if (QWindow *renderWindow = QQuickRenderControl::renderWindowFor(qobject_cast<QQuickWindow *>(obj))) obj = renderWindow; - qCDebug(lcContextMatcher) << "obj" << obj << "focusWindow" << QGuiApplication::focusWindow() + qCDebug(lcContextMatcher) << "obj" << obj << "item" << item << "focusWindow" << QGuiApplication::focusWindow() << "!isBlockedByPopup(item)" << !isBlockedByPopup(item); - return obj && obj == QGuiApplication::focusWindow() && !isBlockedByPopup(item); + return obj && qobject_cast<QWindow*>(obj)->isActive() && !isBlockedByPopup(item); default: return false; } diff --git a/src/quicktemplates/qquickshortcutcontext_p_p.h b/src/quicktemplates/qquickshortcutcontext_p_p.h index efeb541dda..4dcd96d54f 100644 --- a/src/quicktemplates/qquickshortcutcontext_p_p.h +++ b/src/quicktemplates/qquickshortcutcontext_p_p.h @@ -22,7 +22,7 @@ QT_BEGIN_NAMESPACE class QObject; -struct Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickShortcutContext +struct Q_QUICKTEMPLATES2_EXPORT QQuickShortcutContext { static bool matcher(QObject *object, Qt::ShortcutContext context); }; diff --git a/src/quicktemplates/qquickslider.cpp b/src/quicktemplates/qquickslider.cpp index 70317cd231..04b5589524 100644 --- a/src/quicktemplates/qquickslider.cpp +++ b/src/quicktemplates/qquickslider.cpp @@ -74,6 +74,7 @@ public: void itemImplicitWidthChanged(QQuickItem *item) override; void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; qreal from = 0; qreal to = 1; @@ -202,8 +203,6 @@ void QQuickSliderPrivate::handleUngrab() q->setPressed(false); } -static inline QString handleName() { return QStringLiteral("handle"); } - void QQuickSliderPrivate::cancelHandle() { Q_Q(QQuickSlider); @@ -238,9 +237,21 @@ void QQuickSliderPrivate::itemImplicitHeightChanged(QQuickItem *item) emit q->implicitHandleHeightChanged(); } +void QQuickSliderPrivate::itemDestroyed(QQuickItem *item) +{ + Q_Q(QQuickSlider); + QQuickControlPrivate::itemDestroyed(item); + if (item == handle) { + handle = nullptr; + emit q->handleChanged(); + } +} + QQuickSlider::QQuickSlider(QQuickItem *parent) : QQuickControl(*(new QQuickSliderPrivate), parent) { + Q_D(QQuickSlider); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); setActiveFocusOnTab(true); #ifdef Q_OS_MACOS setFocusPolicy(Qt::TabFocus); @@ -524,6 +535,11 @@ void QQuickSlider::setOrientation(Qt::Orientation orientation) if (d->orientation == orientation) return; + if (orientation == Qt::Horizontal) + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); + else + d->setSizePolicy(QLayoutPolicy::Fixed, QLayoutPolicy::Preferred); + d->orientation = orientation; emit orientationChanged(); } diff --git a/src/quicktemplates/qquickslider_p.h b/src/quicktemplates/qquickslider_p.h index 8452d4dad9..4374a6b05d 100644 --- a/src/quicktemplates/qquickslider_p.h +++ b/src/quicktemplates/qquickslider_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickSliderPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSlider : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickSlider : public QQuickControl { Q_OBJECT Q_PROPERTY(qreal from READ from WRITE setFrom NOTIFY fromChanged FINAL) @@ -152,6 +152,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickSlider) - #endif // QQUICKSLIDER_P_H diff --git a/src/quicktemplates/qquickspinbox.cpp b/src/quicktemplates/qquickspinbox.cpp index c825724fc8..6c5d47b69d 100644 --- a/src/quicktemplates/qquickspinbox.cpp +++ b/src/quicktemplates/qquickspinbox.cpp @@ -2,17 +2,14 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qquickspinbox_p.h" -#include "qquickcontrol_p_p.h" -#include "qquickindicatorbutton_p.h" -#include "qquickdeferredexecute_p_p.h" -#include <QtGui/qguiapplication.h> -#include <QtGui/qstylehints.h> +#include <private/qquickcontrol_p_p.h> +#include <private/qquickindicatorbutton_p.h> +#include <private/qquicktextinput_p.h> + +#include <private/qqmlengine_p.h> #include <QtQml/qqmlinfo.h> -#include <QtQml/private/qqmllocale_p.h> -#include <QtQml/private/qqmlengine_p.h> -#include <QtQuick/private/qquicktextinput_p.h> QT_BEGIN_NAMESPACE @@ -60,6 +57,10 @@ static const int AUTO_REPEAT_INTERVAL = 100; \snippet qtquickcontrols-spinbox-double.qml 1 + A prefix and suffix can be added using regular expressions: + + \snippet qtquickcontrols-spinbox-prefix.qml 1 + \sa Tumbler, {Customizing SpinBox}, {Focus Management in Qt Quick Controls} */ @@ -88,8 +89,9 @@ public: int effectiveStepSize() const; - void updateDisplayText(bool modified = false); - void setDisplayText(const QString &displayText, bool modified = false); + void updateDisplayText(); + void setDisplayText(const QString &displayText); + void contentItemTextChanged(); bool upEnabled() const; void updateUpEnabled(); @@ -108,10 +110,15 @@ public: void itemImplicitWidthChanged(QQuickItem *item) override; void itemImplicitHeightChanged(QQuickItem *item) override; + void itemDestroyed(QQuickItem *item) override; + + QString evaluateTextFromValue(int val) const; + int evaluateValueFromText(const QString &text) const; QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::SpinBox); } bool editable = false; + bool live = false; bool wrap = false; int from = 0; int to = 99; @@ -148,20 +155,10 @@ int QQuickSpinBoxPrivate::boundValue(int value, bool wrap) const void QQuickSpinBoxPrivate::updateValue() { - Q_Q(QQuickSpinBox); if (contentItem) { QVariant text = contentItem->property("text"); if (text.isValid()) { - int val = 0; - QQmlEngine *engine = qmlEngine(q); - if (engine && valueFromText.isCallable()) { - QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); - QJSValue loc = QJSValuePrivate::fromReturnedValue(QQmlLocale::wrap(v4, locale)); - val = valueFromText.call(QJSValueList() << text.toString() << loc).toInt(); - } else { - val = locale.toInt(text.toString()); - } - setValue(val, /* allowWrap = */ false, /* modified = */ true); + setValue(evaluateValueFromText(text.toString()), /* allowWrap = */ false, /* modified = */ true); } } } @@ -183,7 +180,7 @@ bool QQuickSpinBoxPrivate::setValue(int newValue, bool allowWrap, bool modified) const bool emitSignals = (value != correctedValue); value = correctedValue; - updateDisplayText(modified); + updateDisplayText(); updateUpEnabled(); updateDownEnabled(); @@ -217,32 +214,49 @@ int QQuickSpinBoxPrivate::effectiveStepSize() const return from > to ? -1 * stepSize : stepSize; } -void QQuickSpinBoxPrivate::updateDisplayText(bool modified) +void QQuickSpinBoxPrivate::updateDisplayText() { - Q_Q(QQuickSpinBox); - QString text; - QQmlEngine *engine = qmlEngine(q); - if (engine && textFromValue.isCallable()) { - QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); - QJSValue loc = QJSValuePrivate::fromReturnedValue(QQmlLocale::wrap(v4, locale)); - text = textFromValue.call(QJSValueList() << value << loc).toString(); - } else { - text = locale.toString(value); - } - setDisplayText(text, modified); + setDisplayText(evaluateTextFromValue(value)); } -void QQuickSpinBoxPrivate::setDisplayText(const QString &text, bool modified) +void QQuickSpinBoxPrivate::setDisplayText(const QString &text) { Q_Q(QQuickSpinBox); - if (!modified && displayText == text) + if (displayText == text) return; displayText = text; emit q->displayTextChanged(); } +void QQuickSpinBoxPrivate::contentItemTextChanged() +{ + Q_Q(QQuickSpinBox); + + QQuickTextInput *inputTextItem = qobject_cast<QQuickTextInput *>(q->contentItem()); + if (!inputTextItem) + return; + QString text = inputTextItem->text(); +#if QT_CONFIG(validator) + if (validator) + validator->fixup(text); +#endif + + if (live) { + const int enteredVal = evaluateValueFromText(text); + const int correctedValue = boundValue(enteredVal, false); + if (correctedValue == enteredVal && correctedValue != value) { + // If live is true and the text is valid change the value + // setValue will set the displayText for us. + q->setValue(correctedValue); + return; + } + } + // If live is false or the value is not valid, just set the displayText + setDisplayText(text); +} + bool QQuickSpinBoxPrivate::upEnabled() const { const QQuickItem *upIndicator = up->indicator(); @@ -352,13 +366,15 @@ bool QQuickSpinBoxPrivate::handleRelease(const QPointF &point, ulong timestamp) int oldValue = value; if (up->isPressed()) { - up->setPressed(false); if (repeatTimer <= 0 && ui && ui->contains(ui->mapFromItem(q, point))) q->increase(); + // Retain pressed state until after increasing is done in case user code binds stepSize + // to up/down.pressed. + up->setPressed(false); } else if (down->isPressed()) { - down->setPressed(false); if (repeatTimer <= 0 && di && di->contains(di->mapFromItem(q, point))) q->decrease(); + down->setPressed(false); } if (value != oldValue) emit q->valueModified(); @@ -397,12 +413,62 @@ void QQuickSpinBoxPrivate::itemImplicitHeightChanged(QQuickItem *item) emit down->implicitIndicatorHeightChanged(); } +void QQuickSpinBoxPrivate::itemDestroyed(QQuickItem *item) +{ + QQuickControlPrivate::itemDestroyed(item); + if (item == up->indicator()) + up->setIndicator(nullptr); + else if (item == down->indicator()) + down->setIndicator(nullptr); +} + + +QString QQuickSpinBoxPrivate::evaluateTextFromValue(int val) const +{ + Q_Q(const QQuickSpinBox); + + QString text; + QQmlEngine *engine = qmlEngine(q); + if (engine && textFromValue.isCallable()) { + QJSValue loc; +#if QT_CONFIG(qml_locale) + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + loc = QJSValuePrivate::fromReturnedValue( + v4->fromData(QMetaType::fromType<QLocale>(), &locale)); +#endif + text = textFromValue.call(QJSValueList() << val << loc).toString(); + } else { + text = locale.toString(val); + } + return text; +} + +int QQuickSpinBoxPrivate::evaluateValueFromText(const QString &text) const +{ + Q_Q(const QQuickSpinBox); + int value; + QQmlEngine *engine = qmlEngine(q); + if (engine && valueFromText.isCallable()) { + QJSValue loc; +#if QT_CONFIG(qml_locale) + QV4::ExecutionEngine *v4 = QQmlEnginePrivate::getV4Engine(engine); + loc = QJSValuePrivate::fromReturnedValue( + v4->fromData(QMetaType::fromType<QLocale>(), &locale)); +#endif + value = valueFromText.call(QJSValueList() << text << loc).toInt(); + } else { + value = locale.toInt(text); + } + return value; +} + QQuickSpinBox::QQuickSpinBox(QQuickItem *parent) : QQuickControl(*(new QQuickSpinBoxPrivate), parent) { Q_D(QQuickSpinBox); d->up = new QQuickIndicatorButton(this); d->down = new QQuickIndicatorButton(this); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Fixed); setFlag(ItemIsFocusScope); setFiltersChildMouseEvents(true); @@ -550,6 +616,41 @@ void QQuickSpinBox::setEditable(bool editable) emit editableChanged(); } +/*! + \qmlproperty bool QtQuick.Controls::SpinBox::live + \since 6.6 + + This property holds whether the \l value is updated when the user edits the + \l displayText. The default value is \c false. If this property is \c true and + the value entered by the user is valid and within the bounds of the spinbox + [\l from, \l to], the value of the SpinBox will be set. If this property is + \c false or the value entered by the user is outside the boundaries, the + value will not be updated until the enter or return keys are pressed, or the + input field loses focus. + + \sa editable, displayText +*/ +bool QQuickSpinBox::isLive() const +{ + Q_D(const QQuickSpinBox); + return d->live; +} + +void QQuickSpinBox::setLive(bool live) +{ + Q_D(QQuickSpinBox); + if (d->live == live) + return; + + d->live = live; + + //make sure to update the value when changing to live + if (live) + d->contentItemTextChanged(); + + emit liveChanged(); +} + #if QT_CONFIG(validator) /*! \qmlproperty Validator QtQuick.Controls::SpinBox::validator @@ -890,16 +991,18 @@ void QQuickSpinBox::keyPressEvent(QKeyEvent *event) switch (event->key()) { case Qt::Key_Up: if (d->upEnabled()) { - d->increase(true); + // Update the pressed state before increasing/decreasing in case user code binds + // stepSize to up/down.pressed. d->up->setPressed(true); + d->increase(true); event->accept(); } break; case Qt::Key_Down: if (d->downEnabled()) { - d->decrease(true); d->down->setPressed(true); + d->decrease(true); event->accept(); } break; @@ -988,20 +1091,24 @@ void QQuickSpinBox::itemChange(ItemChange change, const ItemChangeData &value) void QQuickSpinBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) { Q_D(QQuickSpinBox); - if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(oldItem)) + if (QQuickTextInput *oldInput = qobject_cast<QQuickTextInput *>(oldItem)) { disconnect(oldInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickSpinBox::inputMethodComposingChanged); + QObjectPrivate::disconnect(oldInput, &QQuickTextInput::textChanged, d, &QQuickSpinBoxPrivate::contentItemTextChanged); + } if (newItem) { newItem->setActiveFocusOnTab(true); if (d->activeFocus) - newItem->forceActiveFocus(d->focusReason); + newItem->forceActiveFocus(static_cast<Qt::FocusReason>(d->focusReason)); #if QT_CONFIG(cursor) if (d->editable) newItem->setCursor(Qt::IBeamCursor); #endif - if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(newItem)) + if (QQuickTextInput *newInput = qobject_cast<QQuickTextInput *>(newItem)) { connect(newInput, &QQuickTextInput::inputMethodComposingChanged, this, &QQuickSpinBox::inputMethodComposingChanged); + QObjectPrivate::connect(newInput, &QQuickTextInput::textChanged, d, &QQuickSpinBoxPrivate::contentItemTextChanged); + } } } diff --git a/src/quicktemplates/qquickspinbox_p.h b/src/quicktemplates/qquickspinbox_p.h index a50e77c171..c9547f405b 100644 --- a/src/quicktemplates/qquickspinbox_p.h +++ b/src/quicktemplates/qquickspinbox_p.h @@ -24,7 +24,7 @@ class QValidator; class QQuickSpinBoxPrivate; class QQuickIndicatorButton; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSpinBox : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickSpinBox : public QQuickControl { Q_OBJECT Q_PROPERTY(int from READ from WRITE setFrom NOTIFY fromChanged FINAL) @@ -32,6 +32,8 @@ class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSpinBox : public QQuickControl Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged FINAL) Q_PROPERTY(int stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged FINAL) Q_PROPERTY(bool editable READ isEditable WRITE setEditable NOTIFY editableChanged FINAL) + Q_PROPERTY(bool live READ isLive WRITE setLive NOTIFY liveChanged FINAL REVISION(6, 6)) + #if QT_CONFIG(validator) Q_PROPERTY(QValidator *validator READ validator WRITE setValidator NOTIFY validatorChanged FINAL) #endif @@ -68,6 +70,9 @@ public: bool isEditable() const; void setEditable(bool editable); + bool isLive() const; + void setLive(bool live); + #if QT_CONFIG(validator) QValidator *validator() const; void setValidator(QValidator *validator); @@ -105,6 +110,7 @@ Q_SIGNALS: void valueChanged(); void stepSizeChanged(); void editableChanged(); + Q_REVISION(6, 6) void liveChanged(); #if QT_CONFIG(validator) void validatorChanged(); #endif @@ -151,6 +157,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickSpinBox) - #endif // QQUICKSPINBOX_P_H diff --git a/src/quicktemplates/qquicksplitview.cpp b/src/quicktemplates/qquicksplitview.cpp index 0bf1b79cac..67d8845917 100644 --- a/src/quicktemplates/qquicksplitview.cpp +++ b/src/quicktemplates/qquicksplitview.cpp @@ -221,10 +221,13 @@ QT_BEGIN_NAMESPACE \sa SplitHandle, {Customizing SplitView}, {Container Controls} */ -Q_LOGGING_CATEGORY(qlcQQuickSplitView, "qt.quick.controls.splitview") -Q_LOGGING_CATEGORY(qlcQQuickSplitViewPointer, "qt.quick.controls.splitview.pointer") -Q_LOGGING_CATEGORY(qlcQQuickSplitViewState, "qt.quick.controls.splitview.state") +Q_STATIC_LOGGING_CATEGORY(qlcQQuickSplitView, "qt.quick.controls.splitview") +Q_STATIC_LOGGING_CATEGORY(qlcQQuickSplitViewPointer, "qt.quick.controls.splitview.pointer") +Q_STATIC_LOGGING_CATEGORY(qlcQQuickSplitViewState, "qt.quick.controls.splitview.state") +/* + Updates m_fillIndex to be between 0 .. (item count - 1). +*/ void QQuickSplitViewPrivate::updateFillIndex() { const int count = contentModel->count(); @@ -232,10 +235,9 @@ void QQuickSplitViewPrivate::updateFillIndex() qCDebug(qlcQQuickSplitView) << "looking for fillWidth/Height item amongst" << count << "items"; - m_fillIndex = -1; - int i = 0; + int fillIndex = -1; int lastVisibleIndex = -1; - for (; i < count; ++i) { + for (int i = 0; i < count; ++i) { QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); if (!item->isVisible()) continue; @@ -248,19 +250,21 @@ void QQuickSplitViewPrivate::updateFillIndex() continue; if ((horizontal && attached->fillWidth()) || (!horizontal && attached->fillHeight())) { - m_fillIndex = i; - qCDebug(qlcQQuickSplitView) << "found fillWidth/Height item at index" << m_fillIndex; + fillIndex = i; + qCDebug(qlcQQuickSplitView) << "found fillWidth/Height item at index" << fillIndex; break; } } - if (m_fillIndex == -1) { - // If there was no item with fillWidth/fillHeight set, m_fillIndex will be -1, - // and we'll set it to the last visible item. + if (fillIndex == -1) { + // If there was no item with fillWidth/fillHeight set, fillIndex will be -1, + // and we'll set m_fillIndex to the last visible item. // If there was an item with fillWidth/fillHeight set, we were already done and this will be skipped. - m_fillIndex = lastVisibleIndex != -1 ? lastVisibleIndex : count - 1; - qCDebug(qlcQQuickSplitView) << "found no fillWidth/Height item; using last item at index" << m_fillIndex; + fillIndex = lastVisibleIndex != -1 ? lastVisibleIndex : count - 1; + qCDebug(qlcQQuickSplitView) << "found no fillWidth/Height item; using last item at index" << fillIndex; } + // Take new fillIndex into use. + m_fillIndex = fillIndex; } /* @@ -325,11 +329,14 @@ void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &use // The handle shouldn't cross other handles, so use the right edge of // the first handle to the left as the left edge. qreal leftEdge = 0; - if (m_pressedHandleIndex - 1 >= 0) { - const QQuickItem *leftHandle = m_handleItems.at(m_pressedHandleIndex - 1); - leftEdge = horizontal - ? leftHandle->x() + leftHandle->width() - : leftHandle->y() + leftHandle->height(); + for (int i = m_pressedHandleIndex - 1; i >= 0; --i) { + const QQuickItem *nextHandleToTheLeft = m_handleItems.at(i); + if (nextHandleToTheLeft->isVisible()) { + leftEdge = horizontal + ? nextHandleToTheLeft->x() + nextHandleToTheLeft->width() + : nextHandleToTheLeft->y() + nextHandleToTheLeft->height(); + break; + } } // The mouse can be clicked anywhere in the handle, and if we don't account for @@ -344,35 +351,9 @@ void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &use const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); const qreal newItemSize = newHandlePos - leftEdge; - // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth - // will be used on the next layout (when it's no longer being resized). - if (!attached) { - // Force the attached object to be created since we rely on it. - attached = qobject_cast<QQuickSplitViewAttached*>( - qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); - } - - /* - Users could conceivably respond to size changes in items by setting attached - SplitView properties: - - onWidthChanged: if (width < 10) secondItem.SplitView.preferredWidth = 100 - - We handle this by doing another layout after the current layout if the - attached/implicit size properties are set during this layout. However, we also - need to set preferredWidth/Height here (for reasons mentioned in the comment above), - but we don't want this to count as a request for a delayed layout, so we guard against it. - */ - m_ignoreNextLayoutRequest = true; - - if (horizontal) - attached->setPreferredWidth(newItemSize); - else - attached->setPreferredHeight(newItemSize); - - // We still need to use requestedWidth in the setWidth() call below, + // We still need to use requestedSize in the width/height call below, // because sizeData has already been calculated and now contains an old - // effectivePreferredWidth value. + // effectivePreferredWidth/Height value. requestedSize = newItemSize; qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item @@ -405,22 +386,7 @@ void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &use const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); const qreal newItemSize = rightEdge - (newHandlePos + pressedHandleSize); - // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth - // will be used on the next layout (when it's no longer being resized). - if (!attached) { - // Force the attached object to be created since we rely on it. - attached = qobject_cast<QQuickSplitViewAttached*>( - qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); - } - - m_ignoreNextLayoutRequest = true; - - if (horizontal) - attached->setPreferredWidth(newItemSize); - else - attached->setPreferredHeight(newItemSize); - - // We still need to use requestedSize in the setWidth()/setHeight() call below, + // We still need to use requestedSize in the width/height call below, // because sizeData has already been calculated and now contains an old // effectivePreferredWidth/Height value. requestedSize = newItemSize; @@ -443,34 +409,43 @@ void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &use } if (index != m_fillIndex) { + LayoutData layoutData; if (horizontal) { - item->setWidth(qBound( - sizeData.effectiveMinimumWidth, - requestedSize, - sizeData.effectiveMaximumWidth)); - item->setHeight(height); + layoutData.width = qBound( + sizeData.effectiveMinimumWidth, + requestedSize, + sizeData.effectiveMaximumWidth); + layoutData.height = height; } else { - item->setWidth(width); - item->setHeight(qBound( - sizeData.effectiveMinimumHeight, - requestedSize, - sizeData.effectiveMaximumHeight)); + layoutData.width = width; + layoutData.height = qBound( + sizeData.effectiveMinimumHeight, + requestedSize, + sizeData.effectiveMaximumHeight); } - qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized split item " << item - << " (effective" - << " minW=" << sizeData.effectiveMinimumWidth - << ", minH=" << sizeData.effectiveMinimumHeight - << ", prfW=" << sizeData.effectivePreferredWidth - << ", prfH=" << sizeData.effectivePreferredHeight - << ", maxW=" << sizeData.effectiveMaximumWidth - << ", maxH=" << sizeData.effectiveMaximumHeight << ")"; + // Mark that this item has been manually resized. After this + // we can override the preferredWidth & preferredHeight + if (isBeingResized) + layoutData.wasResizedByHandle = true; + + m_layoutData.insert(item, layoutData); + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": calculated the following size data for split item " << item + << ": eminW=" << sizeData.effectiveMinimumWidth + << ", eminH=" << sizeData.effectiveMinimumHeight + << ", eprfW=" << sizeData.effectivePreferredWidth + << ", eprfH=" << sizeData.effectivePreferredHeight + << ", emaxW=" << sizeData.effectiveMaximumWidth + << ", emaxH=" << sizeData.effectiveMaximumHeight + << ", w=" << layoutData.width + << ", h=" << layoutData.height << ""; // Keep track of how much space has been used so far. if (horizontal) - usedWidth += item->width(); + usedWidth += layoutData.width; else - usedHeight += item->height(); + usedHeight += layoutData.height; } else if (indexBeingResizedDueToDrag != m_fillIndex) { // The fill item is resized afterwards, outside of the loop. qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": skipping fill item as we resize it last"; @@ -526,20 +501,26 @@ void QQuickSplitViewPrivate::layoutResizeFillItem(QQuickItem *fillItem, const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false)); const auto fillSizeData = effectiveSizeData(fillItemPrivate, attached); + + LayoutData layoutData; if (isHorizontal()) { - fillItem->setWidth(qBound( - fillSizeData.effectiveMinimumWidth, - width - usedWidth, - fillSizeData.effectiveMaximumWidth)); - fillItem->setHeight(height); + layoutData.width = qBound( + fillSizeData.effectiveMinimumWidth, + width - usedWidth, + fillSizeData.effectiveMaximumWidth); + layoutData.height = height; + usedWidth += layoutData.width; } else { - fillItem->setWidth(width); - fillItem->setHeight(qBound( - fillSizeData.effectiveMinimumHeight, - height - usedHeight, - fillSizeData.effectiveMaximumHeight)); + layoutData.width = width; + layoutData.height = qBound( + fillSizeData.effectiveMinimumHeight, + height - usedHeight, + fillSizeData.effectiveMaximumHeight); + usedHeight += layoutData.height; } + m_layoutData.insert(fillItem, layoutData); + qCDebug(qlcQQuickSplitView).nospace() << " - " << m_fillIndex << ": resized split fill item " << fillItem << " (effective" << " minW=" << fillSizeData.effectiveMinimumWidth @@ -549,6 +530,100 @@ void QQuickSplitViewPrivate::layoutResizeFillItem(QQuickItem *fillItem, } /* + Limit the sizes if needed and apply them into items. +*/ +void QQuickSplitViewPrivate::limitAndApplySizes(qreal usedWidth, qreal usedHeight) +{ + const int count = contentModel->count(); + const bool horizontal = isHorizontal(); + + const qreal maxSize = horizontal ? width : height; + const qreal usedSize = horizontal ? usedWidth : usedHeight; + if (usedSize > maxSize) { + qCDebug(qlcQQuickSplitView).nospace() << "usedSize " << usedSize << " is greater than maxSize " + << maxSize << "; reducing size of non-filled items from right to left / bottom to top"; + + // If items don't fit, reduce the size of non-filled items from + // right to left / bottom to top. At this point filled item is + // already at its minimum size or usedSize wouldn't be > maxSize. + qreal delta = usedSize - maxSize; + for (int index = count - 1; index >= 0; --index) { + if (index == m_fillIndex) + continue; + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(index)); + if (!item->isVisible()) + continue; + + const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + const auto sizeData = effectiveSizeData(itemPrivate, attached); + const qreal maxReduce = horizontal ? + m_layoutData[item].width - sizeData.effectiveMinimumWidth : + m_layoutData[item].height - sizeData.effectiveMinimumHeight; + + const qreal reduce = std::min(maxReduce, delta); + if (horizontal) + m_layoutData[item].width -= reduce; + else + m_layoutData[item].height -= reduce; + + delta -= reduce; + if (delta <= 0) { + // Now all the items fit, so continue + break; + } + } + } + + qCDebug(qlcQQuickSplitView).nospace() << " applying new sizes to " << count << " items (excluding hidden items)"; + + // Apply the new sizes into items + for (int index = 0; index < count; ++index) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(index)); + if (!item->isVisible()) + continue; + + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + LayoutData layoutData = m_layoutData.value(item); + if (layoutData.wasResizedByHandle) { + // Modify the preferredWidth/Height, otherwise the original implicit/preferred size + // will be used on the next layout (when it's no longer being resized). + if (!attached) { + // Force the attached object to be created since we rely on it. + attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); + } + /* + Users could conceivably respond to size changes in items by setting attached + SplitView properties: + + onWidthChanged: if (width < 10) secondItem.SplitView.preferredWidth = 100 + + We handle this by doing another layout after the current layout if the + attached/implicit size properties are set during this layout. However, we also + need to set preferredWidth/Height here, otherwise the original implicit/preferred sizes + will be used on the next layout (when it's no longer being resized). + But we don't want this to count as a request for a delayed layout, so we guard against it. + */ + m_ignoreNextLayoutRequest = true; + if (horizontal) + attached->setPreferredWidth(layoutData.width); + else + attached->setPreferredHeight(layoutData.height); + } + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized item " << item << " from " + << item->width() << "x" << item->height() << " to " + << layoutData.width << "x" << layoutData.height; + + item->setWidth(layoutData.width); + item->setHeight(layoutData.height); + } +} + +/* Positions items by laying them out in a row or column. Items that aren't visible are skipped. @@ -621,6 +696,19 @@ void QQuickSplitViewPrivate::requestLayout() q->polish(); } +/* + Layout steps are (horizontal SplitView as an example): + 1) layoutResizeSplitItems: Gives each non-filled item its preferredWidth + or if not set, implicitWidth. Sizes are kept between effectiveMinimumWidth + and effectiveMaximumWidth and stored into layoutData for now. + 2) layoutResizeFillItem: Gives filled item all the remaining space. Size is + kept between effectiveMinimumWidth and effectiveMaximumWidth and stored + into layoutData for now. + 3) limitAndApplySizes: If we have used more space than SplitView item has, + start reducing non-filled item sizes from right-to-left. Reduce them up + to minimumWidth or until SplitView item width is reached. Finally set the + new item sizes from layoutData. +*/ void QQuickSplitViewPrivate::layout() { if (!componentComplete) @@ -648,13 +736,15 @@ void QQuickSplitViewPrivate::layout() qCDebug(qlcQQuickSplitView) << "laying out" << count << "split items" << (horizontal ? "horizontally" : "vertically") << "in SplitView" << q_func(); + // Total sizes of items used during the layout operation. qreal usedWidth = 0; qreal usedHeight = 0; int indexBeingResizedDueToDrag = -1; + m_layoutData.clear(); qCDebug(qlcQQuickSplitView) << " resizing:"; - // First, resize the items. We need to do this first because otherwise fill + // First, resize the non-filled items. We need to do this first because otherwise fill // items would take up all of the remaining space as soon as they are encountered. layoutResizeSplitItems(usedWidth, usedHeight, indexBeingResizedDueToDrag); @@ -666,6 +756,9 @@ void QQuickSplitViewPrivate::layout() QQuickItem *fillItem = qobject_cast<QQuickItem*>(contentModel->object(m_fillIndex)); layoutResizeFillItem(fillItem, usedWidth, usedHeight, indexBeingResizedDueToDrag); + // Reduce the sizes still if needed and apply them into items. + limitAndApplySizes(usedWidth, usedHeight); + qCDebug(qlcQQuickSplitView) << " positioning:"; // Position the items. @@ -1129,13 +1222,19 @@ void QQuickSplitView::setOrientation(Qt::Orientation orientation) return; d->m_orientation = orientation; - d->resizeHandles(); + #if QT_CONFIG(cursor) for (QQuickItem *handleItem : d->m_handleItems) d->updateCursorHandle(handleItem); #endif - d->requestLayout(); emit orientationChanged(); + + // Do this after emitting orientationChanged so that the bindings in QML + // update the implicit size in time. + d->resizeHandles(); + // This is queued (via polish) anyway, but to make our intentions clear, + // do it afterwards too. + d->requestLayout(); } /*! @@ -1359,7 +1458,6 @@ void QQuickSplitView::componentComplete() { Q_D(QQuickSplitView); QQuickControl::componentComplete(); - d->resizeHandles(); d->updateFillIndex(); d->updatePolish(); } @@ -1912,9 +2010,10 @@ void QQuickSplitViewAttached::resetMaximumHeight() This attached property controls whether the item takes the remaining space in the split view after all other items have been laid out. - By default, the last visible child of the split view will have this set, + By default, the last visible child of the split view will fill the view, but it can be changed by explicitly setting \c fillWidth to \c true on - another item. + another item. If multiple items have \c fillWidth set to \c true, the + left-most item will fill the view. The width of a split item with \c fillWidth set to \c true is still restricted within its \l minimumWidth and \l maximumWidth. @@ -1947,9 +2046,10 @@ void QQuickSplitViewAttached::setFillWidth(bool fill) This attached property controls whether the item takes the remaining space in the split view after all other items have been laid out. - By default, the last visible child of the split view will have this set, + By default, the last visible child of the split view will fill the view, but it can be changed by explicitly setting \c fillHeight to \c true on - another item. + another item. If multiple items have \c fillHeight set to \c true, the + top-most item will fill the view. The height of a split item with \c fillHeight set to \c true is still restricted within its \l minimumHeight and \l maximumHeight. diff --git a/src/quicktemplates/qquicksplitview_p.h b/src/quicktemplates/qquicksplitview_p.h index 12bba3a856..0828142a4e 100644 --- a/src/quicktemplates/qquicksplitview_p.h +++ b/src/quicktemplates/qquicksplitview_p.h @@ -18,6 +18,8 @@ #include <QtQuickTemplates2/private/qquickcontainer_p.h> #include <QtQml/qqmllist.h> +QT_REQUIRE_CONFIG(quicktemplates2_container); + QT_BEGIN_NAMESPACE class QQuickSplitViewPrivate; @@ -26,7 +28,7 @@ class QQuickSplitViewAttachedPrivate; class QQuickSplitHandleAttached; class QQuickSplitHandleAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitView : public QQuickContainer +class Q_QUICKTEMPLATES2_EXPORT QQuickSplitView : public QQuickContainer { Q_OBJECT Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) @@ -86,7 +88,7 @@ private: Q_DECLARE_PRIVATE(QQuickSplitView) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitViewAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickSplitViewAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickSplitView *view READ view NOTIFY viewChanged FINAL) @@ -156,7 +158,7 @@ private: Q_DECLARE_PRIVATE(QQuickSplitViewAttached) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitHandleAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickSplitHandleAttached : public QObject { Q_OBJECT Q_PROPERTY(bool hovered READ isHovered NOTIFY hoveredChanged FINAL) @@ -185,8 +187,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickSplitView) - -QML_DECLARE_TYPE(QQuickSplitHandleAttached) - #endif // QQUICKSPLITVIEW_P_H diff --git a/src/quicktemplates/qquicksplitview_p_p.h b/src/quicktemplates/qquicksplitview_p_p.h index 86316ecfe9..d2b0c35668 100644 --- a/src/quicktemplates/qquicksplitview_p_p.h +++ b/src/quicktemplates/qquicksplitview_p_p.h @@ -23,7 +23,7 @@ class QQuickSplitView; class QQuickSplitViewAttached; class QQuickSplitHandleAttached; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSplitViewPrivate : public QQuickContainerPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickSplitViewPrivate : public QQuickContainerPrivate { Q_DECLARE_PUBLIC(QQuickSplitView) @@ -31,6 +31,7 @@ public: void updateFillIndex(); void layoutResizeSplitItems(qreal &usedWidth, qreal &usedHeight, int &indexBeingResizedDueToDrag); void layoutResizeFillItem(QQuickItem *fillItem, qreal &usedWidth, qreal &usedHeight, int indexBeingResizedDueToDrag); + void limitAndApplySizes(qreal usedWidth, qreal usedHeight); void layoutPositionItems(const QQuickItem *fillItem); void requestLayout(); void layout(); @@ -59,6 +60,13 @@ public: qreal effectiveMaximumHeight; }; + // Used during the layout + struct LayoutData { + qreal width = 0; + qreal height = 0; + bool wasResizedByHandle = false; + }; + EffectiveSizeData effectiveSizeData(const QQuickItemPrivate *itemPrivate, const QQuickSplitViewAttached *attached) const; @@ -80,6 +88,7 @@ public: Qt::Orientation m_orientation = Qt::Horizontal; QQmlComponent *m_handle = nullptr; QList<QQuickItem*> m_handleItems; + QHash<QQuickItem*, LayoutData> m_layoutData; int m_hoveredHandleIndex = -1; int m_pressedHandleIndex = -1; int m_nextVisibleIndexAfterPressedHandle = -1; diff --git a/src/quicktemplates/qquickstackelement.cpp b/src/quicktemplates/qquickstackelement.cpp index f8001a2980..f6a5ffdd74 100644 --- a/src/quicktemplates/qquickstackelement.cpp +++ b/src/quicktemplates/qquickstackelement.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qquickstackelement_p_p.h" +#include "qquickstackview_p.h" #include "qquickstackview_p_p.h" #include <QtQml/qqmlinfo.h> @@ -15,6 +16,7 @@ QT_BEGIN_NAMESPACE +#if QT_CONFIG(quick_viewtransitions) static QQuickStackViewAttached *attachedStackObject(QQuickStackElement *element) { QQuickStackViewAttached *attached = qobject_cast<QQuickStackViewAttached *>(qmlAttachedPropertiesObject<QQuickStackView>(element->item, false)); @@ -22,6 +24,7 @@ static QQuickStackViewAttached *attachedStackObject(QQuickStackElement *element) QQuickStackViewAttachedPrivate::get(attached)->element = element; return attached; } +#endif class QQuickStackIncubator : public QQmlIncubator { @@ -44,18 +47,23 @@ private: }; QQuickStackElement::QQuickStackElement() +#if QT_CONFIG(quick_viewtransitions) : QQuickItemViewTransitionableItem(nullptr) +#endif { } QQuickStackElement::~QQuickStackElement() { +#if QT_CONFIG(quick_viewtransitions) if (item) QQuickItemPrivate::get(item)->removeItemChangeListener(this, QQuickItemPrivate::Destroyed); +#endif if (ownComponent) delete component; +#if QT_CONFIG(quick_viewtransitions) QQuickStackViewAttached *attached = attachedStackObject(this); if (item) { if (ownItem) { @@ -79,6 +87,7 @@ QQuickStackElement::~QQuickStackElement() if (attached) emit attached->removed(); +#endif } QQuickStackElement *QQuickStackElement::fromString(const QString &str, QQuickStackView *view, QString *error) @@ -110,9 +119,36 @@ QQuickStackElement *QQuickStackElement::fromObject(QObject *object, QQuickStackV QQuickStackElement *element = new QQuickStackElement; element->component = qobject_cast<QQmlComponent *>(object); +#if QT_CONFIG(quick_viewtransitions) element->item = qobject_cast<QQuickItem *>(object); if (element->item) element->originalParent = element->item->parentItem(); +#endif + return element; +} + +QQuickStackElement *QQuickStackElement::fromStackViewArg(QQuickStackView *view, QQuickStackViewArg arg) +{ + QQuickStackElement *element = new QQuickStackElement; +#if QT_CONFIG(quick_viewtransitions) + element->item = arg.mItem; + if (element->item) { + element->originalParent = element->item->parentItem(); + + Q_ASSERT(!arg.mComponent); + Q_ASSERT(!arg.mUrl.isValid()); + } else +#endif + if (arg.mComponent) { + element->component = arg.mComponent; + + Q_ASSERT(!arg.mUrl.isValid()); + } else if (arg.mUrl.isValid()) { + element->component = new QQmlComponent(qmlEngine(view), arg.mUrl, view); + element->ownComponent = true; + } else { + qFatal("No Item, Component or URL set on arg passed to fromStrictArg"); + } return element; } @@ -177,7 +213,9 @@ void QQuickStackElement::initialize(RequiredProperties *requiredProperties) QV4::ScopedValue ipv(scope, properties.value()); QV4::Scoped<QV4::QmlContext> qmlContext(scope, qmlCallingContext.value()); QV4::ScopedValue qmlObject(scope, QV4::QObjectWrapper::wrap(v4, item)); - QQmlComponentPrivate::setInitialProperties(v4, qmlContext, qmlObject, ipv, requiredProperties, item); + QQmlComponentPrivate::setInitialProperties( + v4, qmlContext, qmlObject, ipv, requiredProperties, item, + component ? QQmlComponentPrivate::get(component)->state.creator() : nullptr); properties.clear(); } @@ -202,9 +240,11 @@ void QQuickStackElement::setIndex(int value) return; index = value; +#if QT_CONFIG(quick_viewtransitions) QQuickStackViewAttached *attached = attachedStackObject(this); if (attached) emit attached->indexChanged(); +#endif } void QQuickStackElement::setView(QQuickStackView *value) @@ -213,9 +253,11 @@ void QQuickStackElement::setView(QQuickStackView *value) return; view = value; +#if QT_CONFIG(quick_viewtransitions) QQuickStackViewAttached *attached = attachedStackObject(this); if (attached) emit attached->viewChanged(); +#endif } void QQuickStackElement::setStatus(QQuickStackView::Status value) @@ -224,6 +266,7 @@ void QQuickStackElement::setStatus(QQuickStackView::Status value) return; status = value; +#if QT_CONFIG(quick_viewtransitions) QQuickStackViewAttached *attached = attachedStackObject(this); if (!attached) return; @@ -247,17 +290,25 @@ void QQuickStackElement::setStatus(QQuickStackView::Status value) } emit attached->statusChanged(); +#endif } void QQuickStackElement::setVisible(bool visible) { +#if QT_CONFIG(quick_viewtransitions) QQuickStackViewAttached *attached = attachedStackObject(this); - if (!item || (attached && QQuickStackViewAttachedPrivate::get(attached)->explicitVisible)) +#endif + if (!item +#if QT_CONFIG(quick_viewtransitions) + || (attached && QQuickStackViewAttachedPrivate::get(attached)->explicitVisible) +#endif + ) return; item->setVisible(visible); } +#if QT_CONFIG(quick_viewtransitions) void QQuickStackElement::transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget) { if (transitioner) @@ -294,10 +345,13 @@ void QQuickStackElement::completeTransition(QQuickTransition *quickTransition) { QQuickItemViewTransitionableItem::completeTransition(quickTransition); } +#endif void QQuickStackElement::itemDestroyed(QQuickItem *) { +#if QT_CONFIG(quick_viewtransitions) item = nullptr; +#endif } QT_END_NAMESPACE diff --git a/src/quicktemplates/qquickstackelement_p_p.h b/src/quicktemplates/qquickstackelement_p_p.h index 00b150805d..5af8149d91 100644 --- a/src/quicktemplates/qquickstackelement_p_p.h +++ b/src/quicktemplates/qquickstackelement_p_p.h @@ -17,10 +17,14 @@ #include <QtQuickTemplates2/private/qquickstackview_p.h> #include <QtQuickTemplates2/private/qquickcontrol_p_p.h> +#if QT_CONFIG(quick_viewtransitions) #include <QtQuick/private/qquickitemviewtransition_p.h> +#endif #include <QtQuick/private/qquickitemchangelistener_p.h> #include <QtQml/private/qv4persistent_p.h> +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE class QQmlContext; @@ -28,7 +32,11 @@ class QQmlComponent; struct QQuickStackTransition; class RequiredProperties; -class QQuickStackElement : public QQuickItemViewTransitionableItem, public QQuickItemChangeListener +class QQuickStackElement : +#if QT_CONFIG(quick_viewtransitions) + public QQuickItemViewTransitionableItem, +#endif + public QQuickItemChangeListener { QQuickStackElement(); @@ -37,6 +45,7 @@ public: static QQuickStackElement *fromString(const QString &str, QQuickStackView *view, QString *error); static QQuickStackElement *fromObject(QObject *object, QQuickStackView *view, QString *error); + static QQuickStackElement *fromStackViewArg(QQuickStackView *view, QQuickStackViewArg arg); bool load(QQuickStackView *parent); void incubate(QObject *object, RequiredProperties *requiredProperties); @@ -47,10 +56,12 @@ public: void setStatus(QQuickStackView::Status status); void setVisible(bool visible); +#if QT_CONFIG(quick_viewtransitions) void transitionNextReposition(QQuickItemViewTransitioner *transitioner, QQuickItemViewTransitioner::TransitionType type, bool asTarget); bool prepareTransition(QQuickItemViewTransitioner *transitioner, const QRectF &viewBounds); void startTransition(QQuickItemViewTransitioner *transitioner, QQuickStackView::Status status); void completeTransition(QQuickTransition *quickTransition); +#endif void itemDestroyed(QQuickItem *item) override; @@ -67,6 +78,9 @@ public: QQuickStackView::Status status = QQuickStackView::Inactive; QV4::PersistentValue properties; QV4::PersistentValue qmlCallingContext; +#if !QT_CONFIG(quick_viewtransitions) + QQuickItem *item; +#endif }; QT_END_NAMESPACE diff --git a/src/quicktemplates/qquickstacktransition_p_p.h b/src/quicktemplates/qquickstacktransition_p_p.h index e68f1bb7a1..d42e1981c0 100644 --- a/src/quicktemplates/qquickstacktransition_p_p.h +++ b/src/quicktemplates/qquickstacktransition_p_p.h @@ -15,6 +15,10 @@ // We mean it. // +#include <QtQuick/private/qtquickglobal_p.h> + +QT_REQUIRE_CONFIG(quick_viewtransitions); + #include <QtQuickTemplates2/private/qquickstackview_p.h> #include <QtQuick/private/qquickitemviewtransition_p.h> diff --git a/src/quicktemplates/qquickstackview.cpp b/src/quicktemplates/qquickstackview.cpp index d09f55f6a4..2e0109863a 100644 --- a/src/quicktemplates/qquickstackview.cpp +++ b/src/quicktemplates/qquickstackview.cpp @@ -4,7 +4,9 @@ #include "qquickstackview_p.h" #include "qquickstackview_p_p.h" #include "qquickstackelement_p_p.h" +#if QT_CONFIG(quick_viewtransitions) #include "qquickstacktransition_p_p.h" +#endif #include <QtCore/qscopedvaluerollback.h> #include <QtQml/qjsvalue.h> @@ -16,6 +18,39 @@ QT_BEGIN_NAMESPACE +QQuickStackViewArg::QQuickStackViewArg(QQuickItem *item) + : mItem(item) +{ +} + +QQuickStackViewArg::QQuickStackViewArg(const QUrl &url) + : mUrl(url) +{ +} + +QQuickStackViewArg::QQuickStackViewArg(QQmlComponent *component) + : mComponent(component) +{ +} + +QQuickStackViewArg::QQuickStackViewArg(const QVariantMap &properties) + : mProperties(properties) +{ +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const QQuickStackViewArg &arg) +{ + QDebugStateSaver saver(debug); + debug.nospace() << "QQuickStackViewArg(" + << "mItem=" << arg.mItem + << " mComponent=" << arg.mComponent + << " mUrl=" << arg.mUrl + << ")"; + return debug; +} +#endif + /*! \qmltype StackView \inherits Control @@ -349,15 +384,20 @@ QQuickStackView::QQuickStackView(QQuickItem *parent) : QQuickControl(*(new QQuickStackViewPrivate), parent) { setFlag(ItemIsFocusScope); + + Q_D(QQuickStackView); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred); } QQuickStackView::~QQuickStackView() { Q_D(QQuickStackView); +#if QT_CONFIG(quick_viewtransitions) if (d->transitioner) { d->transitioner->setChangeListener(nullptr); delete d->transitioner; } +#endif qDeleteAll(d->removing); qDeleteAll(d->removed); qDeleteAll(d->elements); @@ -517,13 +557,17 @@ QQuickItem *QQuickStackView::find(const QJSValue &callback, LoadBehavior behavio \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1). \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1). - If no operation is provided, \c PushTransition will be used. + If no operation is provided, \c Immediate will be used if the stack is + empty, and \c PushTransition otherwise. \note Items that already exist in the stack are not pushed. + \note If you are \l {The QML script compiler}{compiling QML}, use the + strongly-typed \l pushItem or \l pushItems functions instead. + \sa initialItem, {Pushing Items} */ -void QQuickStackView::push(QQmlV4Function *args) +void QQuickStackView::push(QQmlV4FunctionPtr args) { Q_D(QQuickStackView); const QString operationName = QStringLiteral("push"); @@ -543,21 +587,23 @@ void QQuickStackView::push(QQmlV4Function *args) QV4::ExecutionEngine *v4 = args->v4engine(); QV4::Scope scope(v4); +#if QT_CONFIG(quick_viewtransitions) Operation operation = d->elements.isEmpty() ? Immediate : PushTransition; QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); if (lastArg->isInt32()) operation = static_cast<Operation>(lastArg->toInt32()); +#endif QStringList errors; QList<QQuickStackElement *> elements = d->parseElements(0, args, &errors); // Remove any items that are already in the stack, as they can't be in two places at once. - for (int i = 0; i < elements.size(); ) { - QQuickStackElement *element = elements.at(i); - if (element->item && d->findElement(element->item)) - elements.removeAt(i); - else - ++i; - } + // not using erase_if, as we have to delete the elements first + auto removeIt = std::remove_if(elements.begin(), elements.end(), [&](QQuickStackElement *element) { + return element->item && d->findElement(element->item); + }); + for (auto it = removeIt, end = elements.end(); it != end; ++it) + delete *it; + elements.erase(removeIt, elements.end()); if (!errors.isEmpty() || elements.isEmpty()) { if (!errors.isEmpty()) { @@ -570,17 +616,21 @@ void QQuickStackView::push(QQmlV4Function *args) return; } +#if QT_CONFIG(quick_viewtransitions) QQuickStackElement *exit = nullptr; if (!d->elements.isEmpty()) exit = d->elements.top(); +#endif int oldDepth = d->elements.size(); if (d->pushElements(elements)) { d->depthChange(d->elements.size(), oldDepth); QQuickStackElement *enter = d->elements.top(); +#if QT_CONFIG(quick_viewtransitions) d->startTransition(QQuickStackTransition::pushEnter(operation, enter, this), QQuickStackTransition::pushExit(operation, exit, this), operation == Immediate); +#endif d->setCurrentItem(enter); } @@ -625,9 +675,13 @@ void QQuickStackView::push(QQmlV4Function *args) stackView.pop(null) \endcode + \note If you are \l {The QML script compiler}{compiling QML}, use the + strongly-typed \l popToItem, \l popToIndex or \l popCurrentItem functions + instead. + \sa clear(), {Popping Items}, {Unwinding Items via Pop} */ -void QQuickStackView::pop(QQmlV4Function *args) +void QQuickStackView::pop(QQmlV4FunctionPtr args) { Q_D(QQuickStackView); const QString operationName = QStringLiteral("pop"); @@ -663,7 +717,7 @@ void QQuickStackView::pop(QQmlV4Function *args) enter = d->findElement(item); if (!enter) { if (item != d->currentItem) - d->warn(QStringLiteral("unknown argument: ") + value->toQString()); // TODO: safe? + d->warn(QStringLiteral("can't find item to pop: ") + value->toQString()); args->setReturnValue(QV4::Encode::null()); d->elements.push(exit); // restore return; @@ -671,14 +725,16 @@ void QQuickStackView::pop(QQmlV4Function *args) } } +#if QT_CONFIG(quick_viewtransitions) Operation operation = PopTransition; if (argc > 0) { QV4::ScopedValue lastArg(scope, (*args)[argc - 1]); if (lastArg->isInt32()) operation = static_cast<Operation>(lastArg->toInt32()); } +#endif - QQuickItem *previousItem = nullptr; + QPointer<QQuickItem> previousItem; if (d->popElements(enter)) { if (exit) { @@ -687,9 +743,11 @@ void QQuickStackView::pop(QQmlV4Function *args) previousItem = exit->item; } d->depthChange(d->elements.size(), oldDepth); +#if QT_CONFIG(quick_viewtransitions) d->startTransition(QQuickStackTransition::popExit(operation, exit, this), QQuickStackTransition::popEnter(operation, enter, this), operation == Immediate); +#endif d->setCurrentItem(enter); } @@ -759,7 +817,8 @@ void QQuickStackView::pop(QQmlV4Function *args) \value StackView.ReplaceTransition An operation with replace transitions (since QtQuick.Controls 2.1). \value StackView.PopTransition An operation with pop transitions (since QtQuick.Controls 2.1). - If no operation is provided, \c ReplaceTransition will be used. + If no operation is provided, \c Immediate will be used if the stack is + empty, and \c ReplaceTransition otherwise. The following example illustrates the use of push and pop transitions with replace(). @@ -789,9 +848,12 @@ void QQuickStackView::pop(QQmlV4Function *args) } \endcode + \note If you are \l {The QML script compiler}{compiling QML}, use the + strongly-typed \l replaceCurrentItem functions instead. + \sa push(), {Replacing Items} */ -void QQuickStackView::replace(QQmlV4Function *args) +void QQuickStackView::replace(QQmlV4FunctionPtr args) { Q_D(QQuickStackView); const QString operationName = QStringLiteral("replace"); @@ -812,10 +874,12 @@ void QQuickStackView::replace(QQmlV4Function *args) QV4::ExecutionEngine *v4 = args->v4engine(); QV4::Scope scope(v4); +#if QT_CONFIG(quick_viewtransitions) Operation operation = d->elements.isEmpty() ? Immediate : ReplaceTransition; QV4::ScopedValue lastArg(scope, (*args)[args->length() - 1]); if (lastArg->isInt32()) operation = static_cast<Operation>(lastArg->toInt32()); +#endif QQuickStackElement *target = nullptr; QV4::ScopedValue firstArg(scope, (*args)[0]); @@ -849,9 +913,11 @@ void QQuickStackView::replace(QQmlV4Function *args) d->removing.insert(exit); } QQuickStackElement *enter = d->elements.top(); +#if QT_CONFIG(quick_viewtransitions) d->startTransition(QQuickStackTransition::replaceExit(operation, exit, this), QQuickStackTransition::replaceEnter(operation, enter, this), operation == Immediate); +#endif d->setCurrentItem(enter); } @@ -864,6 +930,422 @@ void QQuickStackView::replace(QQmlV4Function *args) } /*! + \qmlmethod Item QtQuick.Controls::StackView::pushItems(items, operation) + \since 6.7 + + Pushes \a items onto the stack using an optional \a operation, and + optionally applies a set of properties on each element. \a items is an array + of elements. Each element can be + an \l Item, \l Component, or \l [QML] url and can be followed by an optional + properties argument (see below). Returns the item that became + current (the last item). + + StackView creates an instance automatically if the pushed element is a + \l Component or \l [QML] url, and the instance will be destroyed when it is + popped off the stack. See \l {Item Ownership} for more information. + + \include qquickstackview.qdocinc optional-properties-after-each-item + + \code + stackView.push([item, rectComponent, Qt.resolvedUrl("MyItem.qml")]) + + // With properties: + stackView.pushItems([ + item, { "color": "red" }, + rectComponent, { "color": "green" }, + Qt.resolvedUrl("MyItem.qml"), { "color": "blue" } + ]) + + // With properties for only some items: + stackView.pushItems([ + item, { "color": "yellow" }, + rectComponent + ]) + \endcode + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c PushTransition will be used. + + To push a single item, use the relevant \c pushItem function: + \list + \li \l {stackview-pushitem-item-overload} + {pushItem}(item, properties, operation) + \li \l {stackview-pushitem-component-overload} + {pushItem}(component, properties, operation) + \li \l {stackview-pushitem-url-overload} + {pushItem}(url, properties, operation) + \endlist + + \note Items that already exist in the stack are not pushed. + + \sa initialItem, pushItem, {Pushing Items} +*/ +QQuickItem *QQuickStackView::pushItems(QList<QQuickStackViewArg> args, Operation operation) +{ + Q_D(QQuickStackView); + const QString operationName = QStringLiteral("pushItem"); + if (d->modifyingElements) { + d->warnOfInterruption(operationName); + return nullptr; + } + + QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true); + QScopedValueRollback<QString> operationNameRollback(d->operation, operationName); + + const QList<QQuickStackElement *> stackElements = d->parseElements(args); + +#if QT_CONFIG(quick_viewtransitions) + QQuickStackElement *exit = nullptr; + if (!d->elements.isEmpty()) + exit = d->elements.top(); +#endif + + const int oldDepth = d->elements.size(); + if (d->pushElements(stackElements)) { + d->depthChange(d->elements.size(), oldDepth); + QQuickStackElement *enter = d->elements.top(); +#if QT_CONFIG(quick_viewtransitions) + d->startTransition(QQuickStackTransition::pushEnter(operation, enter, this), + QQuickStackTransition::pushExit(operation, exit, this), + operation == Immediate); +#endif + d->setCurrentItem(enter); + } + + return d->currentItem; +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::pushItem(item, properties, operation) + \keyword stackview-pushitem-item-overload + \since 6.7 + + Pushes an \a item onto the stack, optionally applying a set of + \a properties, using the optional \a operation. Returns the item that + became current (the last item). + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c PushTransition will be used. + + To push several items onto the stack, use \l pushItems(). + + \sa initialItem, {Pushing Items} +*/ +QQuickItem *QQuickStackView::pushItem(QQuickItem *item, const QVariantMap &properties, Operation operation) +{ + return pushItems({ item, properties }, operation); +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::pushItem(component, properties, operation) + \overload pushItem() + \keyword stackview-pushitem-component-overload + \since 6.7 + + Pushes a \a component onto the stack, optionally applying a set of + \a properties, using the optional \a operation. Returns the item that + became current (the last item). + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c PushTransition will be used. + + To push several items onto the stack, use \l pushItems(). + + \sa initialItem, {Pushing Items} +*/ +QQuickItem *QQuickStackView::pushItem(QQmlComponent *component, const QVariantMap &properties, Operation operation) +{ + return pushItems({ component, properties }, operation); +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::pushItem(url, properties, operation) + \overload pushItem() + \keyword stackview-pushitem-url-overload + \since 6.7 + + Pushes a \a url onto the stack, optionally applying a set of + \a properties, using the optional \a operation. Returns the item that + became current (the last item). + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c PushTransition will be used. + + To push several items onto the stack, use \l pushItems(). + + \sa initialItem, {Pushing Items} +*/ +QQuickItem *QQuickStackView::pushItem(const QUrl &url, const QVariantMap &properties, Operation operation) +{ + return pushItems({ url, properties }, operation); +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::popToItem(item, operation) + \since 6.7 + + Pops all items down to (but not including) \a item. Returns the last item + removed from the stack. + + If \a item is \c null, a warning is produced and \c null is returned. + + \include qquickstackview.qdocinc pop-ownership + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c PopTransition will be used. + + \code + stackView.popToItem(someItem, StackView.Immediate) + \endcode + + \sa clear(), {Popping Items}, {Unwinding Items via Pop} +*/ +QQuickItem *QQuickStackView::popToItem(QQuickItem *item, Operation operation) +{ + Q_D(QQuickStackView); + return d->popToItem(item, operation, QQuickStackViewPrivate::CurrentItemPolicy::DoNotPop); +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::popToIndex(index, operation) + \since 6.7 + + Pops all items down to (but not including) \a index. Returns the last item + removed from the stack. + + If \a index is out of bounds, a warning is produced and \c null is + returned. + + \include qquickstackview.qdocinc pop-ownership + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c PopTransition will be used. + + \code + stackView.popToIndex(stackView.depth - 2, StackView.Immediate) + \endcode + + \sa clear(), {Popping Items}, {Unwinding Items via Pop} +*/ +QQuickItem *QQuickStackView::popToIndex(int index, Operation operation) +{ + Q_D(QQuickStackView); + if (index < 0 || index >= d->elements.size()) { + d->warn(QString::fromLatin1("popToIndex: index %1 is out of bounds (%2 item(s))") + .arg(index).arg(d->elements.size())); + return nullptr; + } + + if (index == d->elements.size() - 1) { + // This would pop down to the current item, which is a no-op. + return nullptr; + } + + QQuickStackElement *element = d->elements.at(index); + element->load(this); + return d->popToItem(element->item, operation, QQuickStackViewPrivate::CurrentItemPolicy::Pop); +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::popCurrentItem(operation) + \since 6.7 + + Pops \l currentItem from the stack. Returns the last item removed from the + stack, or \c null if \l depth was \c 1. + + \include qquickstackview.qdocinc pop-ownership + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c PopTransition will be used. + + This function is equivalent to \c popToIndex(stackView.currentIndex - 1). + + \sa clear(), {Popping Items}, {Unwinding Items via Pop} +*/ +QQuickItem *QQuickStackView::popCurrentItem(Operation operation) +{ + Q_D(QQuickStackView); + if (d->elements.size() == 1) { + auto lastItemRemoved = d->elements.last()->item; + clear(operation); + return lastItemRemoved; + } + return d->popToItem(d->currentItem, operation, QQuickStackViewPrivate::CurrentItemPolicy::Pop); +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::replaceCurrentItem(items, operation) + \keyword stackview-replacecurrentitem-items-overload + \since 6.7 + + Pops \l currentItem from the stack and pushes \a items. If the optional + \a operation is specified, the relevant transition will be used. Each item + can be followed by an optional set of properties that will be applied to + that item. Returns the item that became current. + + \include qquickstackview.qdocinc optional-properties-after-each-item + + \include qquickstackview.qdocinc pop-ownership + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c ReplaceTransition will be used. + + \code + stackView.replaceCurrentItem([item, rectComponent, Qt.resolvedUrl("MyItem.qml")]) + + // With properties: + stackView.replaceCurrentItem([ + item, { "color": "red" }, + rectComponent, { "color": "green" }, + Qt.resolvedUrl("MyItem.qml"), { "color": "blue" } + ]) + \endcode + + To push a single item, use the relevant overload: + \list + \li \l {stackview-replacecurrentitem-item-overload} + {replaceCurrentItem}(item, properties, operation) + \li \l {stackview-replacecurrentitem-component-overload} + {replaceCurrentItem}(component, properties, operation) + \li \l {stackview-replacecurrentitem-url-overload} + {replaceCurrentItem}(url, properties, operation) + \endlist + + \sa push(), {Replacing Items} +*/ +QQuickItem *QQuickStackView::replaceCurrentItem(const QList<QQuickStackViewArg> &args, + Operation operation) +{ + Q_D(QQuickStackView); + const QString operationName = QStringLiteral("replace"); + if (d->modifyingElements) { + d->warnOfInterruption(operationName); + return nullptr; + } + + QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true); + QScopedValueRollback<QString> operationNameRollback(d->operation, operationName); + + QQuickStackElement *currentElement = !d->elements.isEmpty() ? d->elements.top() : nullptr; + + const QList<QQuickStackElement *> stackElements = d->parseElements(args); + + int oldDepth = d->elements.size(); + QQuickStackElement* exit = nullptr; + if (!d->elements.isEmpty()) + exit = d->elements.pop(); + + const bool successfullyReplaced = exit != currentElement + ? d->replaceElements(currentElement, stackElements) + : d->pushElements(stackElements); + if (successfullyReplaced) { + d->depthChange(d->elements.size(), oldDepth); + if (exit) { + exit->removal = true; + d->removing.insert(exit); + } + QQuickStackElement *enter = d->elements.top(); +#if QT_CONFIG(quick_viewtransitions) + d->startTransition(QQuickStackTransition::replaceExit(operation, exit, this), + QQuickStackTransition::replaceEnter(operation, enter, this), + operation == Immediate); +#endif + d->setCurrentItem(enter); + } + + return d->currentItem; +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::replaceCurrentItem(item, properties, operation) + \overload replaceCurrentItem() + \keyword stackview-replacecurrentitem-item-overload + \since 6.7 + + \include qquickstackview.qdocinc {replaceCurrentItem} {item} + + \include qquickstackview.qdocinc pop-ownership + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c ReplaceTransition will be used. + + To push several items onto the stack, use + \l {stackview-replacecurrentitem-items-overload} + {replaceCurrentItem}(items, operation). + + \sa {Replacing Items} +*/ +QQuickItem *QQuickStackView::replaceCurrentItem(QQuickItem *item, const QVariantMap &properties, + Operation operation) +{ + const QList<QQuickStackViewArg> args = { QQuickStackViewArg(item), QQuickStackViewArg(properties) }; + return replaceCurrentItem(args, operation); +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::replaceCurrentItem(component, properties, operation) + \overload replaceCurrentItem() + \keyword stackview-replacecurrentitem-component-overload + \since 6.7 + + \include qquickstackview.qdocinc {replaceCurrentItem} {component} + + \include qquickstackview.qdocinc pop-ownership + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c ReplaceTransition will be used. + + To push several items onto the stack, use + \l {stackview-replacecurrentitem-items-overload} + {replaceCurrentItem}(items, operation). + + \sa {Replacing Items} +*/ +QQuickItem *QQuickStackView::replaceCurrentItem(QQmlComponent *component, const QVariantMap &properties, + Operation operation) +{ + const QList<QQuickStackViewArg> args = { QQuickStackViewArg(component), QQuickStackViewArg(properties) }; + return replaceCurrentItem(args, operation); +} + +/*! + \qmlmethod Item QtQuick.Controls::StackView::replaceCurrentItem(url, properties, operation) + \keyword stackview-replacecurrentitem-url-overload + \overload replaceCurrentItem() + \since 6.7 + + \include qquickstackview.qdocinc {replaceCurrentItem} {url} + + \include qquickstackview.qdocinc pop-ownership + + \include qquickstackview.qdocinc operation-values + + If no operation is provided, \c ReplaceTransition will be used. + + To push several items onto the stack, use + \l {stackview-replacecurrentitem-items-overload} + {replaceCurrentItem}(items, operation). + + \sa {Replacing Items} +*/ +QQuickItem *QQuickStackView::replaceCurrentItem(const QUrl &url, const QVariantMap &properties, + Operation operation) +{ + const QList<QQuickStackViewArg> args = { QQuickStackViewArg(url), QQuickStackViewArg(properties) }; + return replaceCurrentItem(args, operation); +} + +/*! \since QtQuick.Controls 2.3 (Qt 5.10) \qmlproperty bool QtQuick.Controls::StackView::empty \readonly @@ -894,6 +1376,9 @@ bool QQuickStackView::isEmpty() const */ void QQuickStackView::clear(Operation operation) { +#if !QT_CONFIG(quick_viewtransitions) + Q_UNUSED(operation) +#endif Q_D(QQuickStackView); if (d->elements.isEmpty()) return; @@ -904,8 +1389,11 @@ void QQuickStackView::clear(Operation operation) return; } + const int oldDepth = d->elements.size(); + QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true); QScopedValueRollback<QString> operationNameRollback(d->operation, operationName); +#if QT_CONFIG(quick_viewtransitions) if (operation != Immediate) { QQuickStackElement *exit = d->elements.pop(); exit->removal = true; @@ -913,8 +1401,8 @@ void QQuickStackView::clear(Operation operation) d->startTransition(QQuickStackTransition::popExit(operation, exit, this), QQuickStackTransition::popEnter(operation, nullptr, this), false); } +#endif - int oldDepth = d->elements.size(); d->setCurrentItem(nullptr); qDeleteAll(d->elements); d->elements.clear(); @@ -945,6 +1433,7 @@ void QQuickStackView::setInitialItem(const QJSValue &item) d->initialItem = item; } +#if QT_CONFIG(quick_viewtransitions) /*! \qmlproperty Transition QtQuick.Controls::StackView::popEnter @@ -1106,6 +1595,7 @@ void QQuickStackView::setReplaceExit(QQuickTransition *exit) d->transitioner->moveDisplacedTransition = exit; emit replaceExitChanged(); } +#endif void QQuickStackView::componentComplete() { diff --git a/src/quicktemplates/qquickstackview_p.cpp b/src/quicktemplates/qquickstackview_p.cpp index 99127d84b6..0288ff1f4b 100644 --- a/src/quicktemplates/qquickstackview_p.cpp +++ b/src/quicktemplates/qquickstackview_p.cpp @@ -3,8 +3,11 @@ #include "qquickstackview_p_p.h" #include "qquickstackelement_p_p.h" +#if QT_CONFIG(quick_viewtransitions) #include "qquickstacktransition_p_p.h" +#endif +#include <QtCore/qscopedvaluerollback.h> #include <QtQml/qqmlinfo.h> #include <QtQml/qqmllist.h> #include <QtQml/private/qv4qmlcontext_p.h> @@ -46,7 +49,7 @@ void QQuickStackViewPrivate::setCurrentItem(QQuickStackElement *element) emit q->currentItemChanged(); } -static bool initProperties(QQuickStackElement *element, const QV4::Value &props, QQmlV4Function *args) +static bool initProperties(QQuickStackElement *element, const QV4::Value &props, QQmlV4FunctionPtr args) { if (props.isObject()) { const QV4::QObjectWrapper *wrapper = props.as<QV4::QObjectWrapper>(); @@ -60,7 +63,7 @@ static bool initProperties(QQuickStackElement *element, const QV4::Value &props, return false; } -QList<QQuickStackElement *> QQuickStackViewPrivate::parseElements(int from, QQmlV4Function *args, QStringList *errors) +QList<QQuickStackElement *> QQuickStackViewPrivate::parseElements(int from, QQmlV4FunctionPtr args, QStringList *errors) { QV4::ExecutionEngine *v4 = args->v4engine(); auto context = v4->callingQmlContext(); @@ -106,6 +109,47 @@ QList<QQuickStackElement *> QQuickStackViewPrivate::parseElements(int from, QQml return elements; } +QList<QQuickStackElement *> QQuickStackViewPrivate::parseElements(const QList<QQuickStackViewArg> &args) +{ + Q_Q(QQuickStackView); + QList<QQuickStackElement *> stackElements; + for (int i = 0; i < args.size(); ++i) { + const QQuickStackViewArg &arg = args.at(i); + QVariantMap properties; + // Look ahead at the next arg in case it contains properties for this + // Item/Component/URL. + if (i < args.size() - 1) { + const QQuickStackViewArg &nextArg = args.at(i + 1); + // If mProperties isn't empty, the user passed properties. + // If it is empty, but mItem, mComponent and mUrl also are, + // then they passed an empty property map. + if (!nextArg.mProperties.isEmpty() + || (!nextArg.mItem && !nextArg.mComponent && !nextArg.mUrl.isValid())) { + properties = nextArg.mProperties; + ++i; + } + } + + // Remove any items that are already in the stack, as they can't be in two places at once. + if (findElement(arg.mItem)) + continue; + + // We look ahead one index for each Item/Component/URL, so if this arg is + // a property map, the user has passed two or more in a row. + if (!arg.mProperties.isEmpty()) { + qmlWarning(q) << "Properties must come after an Item, Component or URL"; + return {}; + } + + QQuickStackElement *element = QQuickStackElement::fromStackViewArg(q, arg); + QV4::ExecutionEngine *v4Engine = qmlEngine(q)->handle(); + element->properties.set(v4Engine, v4Engine->fromVariant(properties)); + element->qmlCallingContext.set(v4Engine, v4Engine->qmlContext()); + stackElements.append(element); + } + return stackElements; +} + QQuickStackElement *QQuickStackViewPrivate::findElement(QQuickItem *item) const { if (item) { @@ -204,6 +248,80 @@ bool QQuickStackViewPrivate::replaceElements(QQuickStackElement *target, const Q return pushElements(elems); } +QQuickItem *QQuickStackViewPrivate::popToItem(QQuickItem *item, QQuickStackView::Operation operation, CurrentItemPolicy currentItemPolicy) +{ + const QString operationName = QStringLiteral("pop"); + if (modifyingElements) { + warnOfInterruption(operationName); + return nullptr; + } + + QScopedValueRollback<bool> modifyingElementsRollback(modifyingElements, true); + QScopedValueRollback<QString> operationNameRollback(this->operation, operationName); + if (elements.isEmpty()) { + warn(QStringLiteral("no items to pop")); + return nullptr; + } + + if (!item) { + warn(QStringLiteral("item cannot be null")); + return nullptr; + } + + const int oldDepth = elements.size(); + QQuickStackElement *exit = elements.pop(); + // top() here will be the item below the previously current item, since we just popped above. + QQuickStackElement *enter = elements.top(); + + bool nothingToDo = false; + if (item != currentItem) { + if (!item) { + // Popping down to the first item. + enter = elements.value(0); + } else { + // Popping down to an arbitrary item. + enter = findElement(item); + if (!enter) { + warn(QStringLiteral("can't find item to pop: ") + QDebug::toString(item)); + nothingToDo = true; + } + } + } else { + if (currentItemPolicy == CurrentItemPolicy::DoNotPop) { + // popToItem was called with the currentItem, which means there are no items + // to pop because it's already at the top. + nothingToDo = true; + } + // else: popToItem was called by popCurrentItem, and so we _should_ pop. + } + if (nothingToDo) { + // Restore the element we popped earlier. + elements.push(exit); + return nullptr; + } + + QQuickItem *previousItem = nullptr; + if (popElements(enter)) { + if (exit) { + exit->removal = true; + removing.insert(exit); + previousItem = exit->item; + } + depthChange(elements.size(), oldDepth); +#if QT_CONFIG(quick_viewtransitions) + Q_Q(QQuickStackView); + startTransition(QQuickStackTransition::popExit(operation, exit, q), + QQuickStackTransition::popEnter(operation, enter, q), + operation == QQuickStackView::Immediate); +#else + Q_UNUSED(operation); +#endif + setCurrentItem(enter); + } + return previousItem; +} + +#if QT_CONFIG(quick_viewtransitions) void QQuickStackViewPrivate::ensureTransitioner() { if (!transitioner) { @@ -301,6 +419,7 @@ void QQuickStackViewPrivate::viewItemTransitionFinished(QQuickItemViewTransition removing.remove(element); } +#endif void QQuickStackViewPrivate::setBusy(bool b) { diff --git a/src/quicktemplates/qquickstackview_p.h b/src/quicktemplates/qquickstackview_p.h index b36976a395..8606759a7c 100644 --- a/src/quicktemplates/qquickstackview_p.h +++ b/src/quicktemplates/qquickstackview_p.h @@ -15,30 +15,67 @@ // We mean it. // +#include <QtCore/qdebug.h> #include <QtQuickTemplates2/private/qquickcontrol_p.h> QT_BEGIN_NAMESPACE -class QQmlV4Function; class QQuickTransition; class QQuickStackElement; class QQuickStackViewPrivate; class QQuickStackViewAttached; class QQuickStackViewAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickStackView : public QQuickControl +/*! + \internal + + Input from the user that is turned into QQuickStackElements. + + This was added to support the QML-compiler-friendly pushElement[s] + functions. +*/ +class QQuickStackViewArg +{ + Q_GADGET + QML_CONSTRUCTIBLE_VALUE + QML_ANONYMOUS + +public: + QQuickStackViewArg() = default; + Q_INVOKABLE QQuickStackViewArg(QQuickItem *item); + Q_INVOKABLE QQuickStackViewArg(const QUrl &url); + Q_INVOKABLE QQuickStackViewArg(QQmlComponent *component); + Q_INVOKABLE QQuickStackViewArg(const QVariantMap &properties); + +#ifndef QT_NO_DEBUG_STREAM + friend QDebug operator<<(QDebug debug, const QQuickStackViewArg &arg); +#endif + +private: + friend class QQuickStackViewPrivate; + friend class QQuickStackElement; + + QQuickItem *mItem = nullptr; + QQmlComponent *mComponent = nullptr; + QUrl mUrl; + QVariantMap mProperties; +}; + +class Q_QUICKTEMPLATES2_EXPORT QQuickStackView : public QQuickControl { Q_OBJECT Q_PROPERTY(bool busy READ isBusy NOTIFY busyChanged FINAL) Q_PROPERTY(int depth READ depth NOTIFY depthChanged FINAL) Q_PROPERTY(QQuickItem *currentItem READ currentItem NOTIFY currentItemChanged FINAL) Q_PROPERTY(QJSValue initialItem READ initialItem WRITE setInitialItem FINAL) +#if QT_CONFIG(quick_viewtransitions) Q_PROPERTY(QQuickTransition *popEnter READ popEnter WRITE setPopEnter NOTIFY popEnterChanged FINAL) Q_PROPERTY(QQuickTransition *popExit READ popExit WRITE setPopExit NOTIFY popExitChanged FINAL) Q_PROPERTY(QQuickTransition *pushEnter READ pushEnter WRITE setPushEnter NOTIFY pushEnterChanged FINAL) Q_PROPERTY(QQuickTransition *pushExit READ pushExit WRITE setPushExit NOTIFY pushExitChanged FINAL) Q_PROPERTY(QQuickTransition *replaceEnter READ replaceEnter WRITE setReplaceEnter NOTIFY replaceEnterChanged FINAL) Q_PROPERTY(QQuickTransition *replaceExit READ replaceExit WRITE setReplaceExit NOTIFY replaceExitChanged FINAL) +#endif // 2.3 (Qt 5.10) Q_PROPERTY(bool empty READ isEmpty NOTIFY emptyChanged FINAL REVISION(2, 3)) QML_NAMED_ELEMENT(StackView) @@ -66,6 +103,7 @@ public: QJSValue initialItem() const; void setInitialItem(const QJSValue &item); +#if QT_CONFIG(quick_viewtransitions) QQuickTransition *popEnter() const; void setPopEnter(QQuickTransition *enter); @@ -83,6 +121,7 @@ public: QQuickTransition *replaceExit() const; void setReplaceExit(QQuickTransition *exit); +#endif enum LoadBehavior { DontLoad, @@ -90,8 +129,8 @@ public: }; Q_ENUM(LoadBehavior) - Q_INVOKABLE QQuickItem *get(int index, LoadBehavior behavior = DontLoad); - Q_INVOKABLE QQuickItem *find(const QJSValue &callback, LoadBehavior behavior = DontLoad); + Q_INVOKABLE QQuickItem *get(int index, QQuickStackView::LoadBehavior behavior = DontLoad); + Q_INVOKABLE QQuickItem *find(const QJSValue &callback, QQuickStackView::LoadBehavior behavior = DontLoad); enum Operation { Transition = -1, // ### Deprecated in Qt 6; remove in Qt 7. @@ -102,9 +141,31 @@ public: }; Q_ENUM(Operation) - Q_INVOKABLE void push(QQmlV4Function *args); - Q_INVOKABLE void pop(QQmlV4Function *args); - Q_INVOKABLE void replace(QQmlV4Function *args); + Q_INVOKABLE void push(QQmlV4FunctionPtr args); + Q_INVOKABLE void pop(QQmlV4FunctionPtr args); + Q_INVOKABLE void replace(QQmlV4FunctionPtr args); + + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *pushItems(QList<QQuickStackViewArg> args, + Operation operation = Immediate); + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *pushItem(QQuickItem *item, const QVariantMap &properties = {}, + Operation operation = Immediate); + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *pushItem(QQmlComponent *component, const QVariantMap &properties = {}, + Operation operation = Immediate); + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *pushItem(const QUrl &url, const QVariantMap &properties = {}, + Operation operation = Immediate); + + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *popToItem(QQuickItem *item, Operation operation = PopTransition); + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *popToIndex(int index, Operation operation = PopTransition); + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *popCurrentItem(Operation operation = PopTransition); + + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *replaceCurrentItem(const QList<QQuickStackViewArg> &args, + Operation operation = ReplaceTransition); + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *replaceCurrentItem(QQuickItem *item, + const QVariantMap &properties = {}, Operation operation = ReplaceTransition); + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *replaceCurrentItem(QQmlComponent *component, + const QVariantMap &properties = {}, Operation operation = ReplaceTransition); + Q_REVISION(6, 7) Q_INVOKABLE QQuickItem *replaceCurrentItem(const QUrl &url, + const QVariantMap &properties = {}, Operation operation = ReplaceTransition); // 2.3 (Qt 5.10) bool isEmpty() const; @@ -116,12 +177,14 @@ Q_SIGNALS: void busyChanged(); void depthChanged(); void currentItemChanged(); +#if QT_CONFIG(quick_viewtransitions) void popEnterChanged(); void popExitChanged(); void pushEnterChanged(); void pushExitChanged(); void replaceEnterChanged(); void replaceExitChanged(); +#endif // 2.3 (Qt 5.10) Q_REVISION(2, 3) void emptyChanged(); @@ -143,7 +206,7 @@ private: Q_DECLARE_PRIVATE(QQuickStackView) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickStackViewAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickStackViewAttached : public QObject { Q_OBJECT Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL) @@ -185,6 +248,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickStackView) - #endif // QQUICKSTACKVIEW_P_H diff --git a/src/quicktemplates/qquickstackview_p_p.h b/src/quicktemplates/qquickstackview_p_p.h index 2b86458edc..7d6ee50b1b 100644 --- a/src/quicktemplates/qquickstackview_p_p.h +++ b/src/quicktemplates/qquickstackview_p_p.h @@ -17,7 +17,9 @@ #include <QtQuickTemplates2/private/qquickstackview_p.h> #include <QtQuickTemplates2/private/qquickcontrol_p_p.h> +#if QT_CONFIG(quick_viewtransitions) #include <QtQuick/private/qquickitemviewtransition_p.h> +#endif #include <QtQuick/private/qquickitemchangelistener_p.h> #include <QtQml/private/qv4value_p.h> #include <QtQml/private/qqmlcontextdata_p.h> @@ -29,7 +31,10 @@ QT_BEGIN_NAMESPACE class QQuickStackElement; struct QQuickStackTransition; -class QQuickStackViewPrivate : public QQuickControlPrivate, public QQuickItemViewTransitionChangeListener +class QQuickStackViewPrivate : public QQuickControlPrivate +#if QT_CONFIG(quick_viewtransitions) + , public QQuickItemViewTransitionChangeListener +#endif { Q_DECLARE_PUBLIC(QQuickStackView) @@ -44,7 +49,8 @@ public: void setCurrentItem(QQuickStackElement *element); - QList<QQuickStackElement *> parseElements(int from, QQmlV4Function *args, QStringList *errors); + QList<QQuickStackElement *> parseElements(int from, QQmlV4FunctionPtr args, QStringList *errors); + QList<QQuickStackElement *> parseElements(const QList<QQuickStackViewArg> &args); QQuickStackElement *findElement(QQuickItem *item) const; QQuickStackElement *findElement(const QV4::Value &value) const; QQuickStackElement *createElement(const QV4::Value &value, const QQmlRefPointer<QQmlContextData> &context, QString *error); @@ -53,11 +59,19 @@ public: bool popElements(QQuickStackElement *element); bool replaceElements(QQuickStackElement *element, const QList<QQuickStackElement *> &elements); + enum class CurrentItemPolicy { + DoNotPop, + Pop + }; + QQuickItem *popToItem(QQuickItem *item, QQuickStackView::Operation operation, CurrentItemPolicy currentItemPolicy); + +#if QT_CONFIG(quick_viewtransitions) void ensureTransitioner(); void startTransition(const QQuickStackTransition &first, const QQuickStackTransition &second, bool immediate); void completeTransition(QQuickStackElement *element, QQuickTransition *transition, QQuickStackView::Status status); void viewItemTransitionFinished(QQuickItemViewTransitionableItem *item) override; +#endif void setBusy(bool busy); void depthChange(int newDepth, int oldDepth); @@ -69,10 +83,15 @@ public: QSet<QQuickStackElement*> removing; QList<QQuickStackElement*> removed; QStack<QQuickStackElement *> elements; +#if QT_CONFIG(quick_viewtransitions) QQuickItemViewTransitioner *transitioner = nullptr; +#endif }; -class QQuickStackViewAttachedPrivate : public QObjectPrivate, public QQuickItemChangeListener +class QQuickStackViewAttachedPrivate : public QObjectPrivate +//#if QT_CONFIG(quick_viewtransitions) + , public QQuickItemChangeListener +//#endif { Q_DECLARE_PUBLIC(QQuickStackViewAttached) @@ -82,7 +101,9 @@ public: return attached->d_func(); } +//#if QT_CONFIG(quick_viewtransitions) void itemParentChanged(QQuickItem *item, QQuickItem *parent) override; +//#endif bool explicitVisible = false; QQuickStackElement *element = nullptr; diff --git a/src/quicktemplates/qquickswipe_p.h b/src/quicktemplates/qquickswipe_p.h index 4694948763..44eadcb584 100644 --- a/src/quicktemplates/qquickswipe_p.h +++ b/src/quicktemplates/qquickswipe_p.h @@ -26,7 +26,7 @@ class QQuickItem; class QQuickTransition; class QQuickSwipePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipe : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickSwipe : public QObject { Q_OBJECT Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged FINAL) diff --git a/src/quicktemplates/qquickswipedelegate.cpp b/src/quicktemplates/qquickswipedelegate.cpp index 24ca772b06..36782fee8c 100644 --- a/src/quicktemplates/qquickswipedelegate.cpp +++ b/src/quicktemplates/qquickswipedelegate.cpp @@ -68,7 +68,7 @@ QT_BEGIN_NAMESPACE \image qtquickcontrols-swipedelegate-behind.gif - \sa {Customizing SwipeDelegate}, {Delegate Controls}, {Qt Quick Controls 2 - Swipe to Remove}{Swipe to Remove Example} + \sa {Customizing SwipeDelegate}, {Delegate Controls}, {Qt Quick Controls 2 - Gallery}{Gallery Example} */ namespace { @@ -194,6 +194,7 @@ QQuickItem *QQuickSwipePrivate::createDelegateItem(QQmlComponent *component) if (item) { item->setParentItem(control); component->completeCreate(); + QJSEngine::setObjectOwnership(item, QJSEngine::JavaScriptOwnership); } return item; } @@ -770,12 +771,18 @@ bool QQuickSwipeDelegatePrivate::handleMouseMoveEvent(QQuickItem *item, QMouseEv if (!swipePrivate->left && !swipePrivate->right && !swipePrivate->behind) return false; + if (item != q && swipePrivate->complete) { + // If the delegate is swiped open, send the event to the exposed item, + // in case it's an interactive child (like a Button). + const auto posInItem = item->mapToItem(q, event->position().toPoint()); + forwardMouseEvent(event, item, posInItem); + } + // Don't handle move events for the control if it wasn't pressed. if (item == q && !pressed) return false; - const QPointF mappedEventPos = item->mapToItem(q, event->position().toPoint()); - const qreal distance = (mappedEventPos - pressPoint).x(); + const qreal distance = (event->globalPosition() - event->points().first().globalPressPosition()).x(); if (!q->keepMouseGrab()) { // We used to use the custom threshold that QQuickDrawerPrivate::grabMouse used, // but since it's larger than what Flickable uses, it results in Flickable @@ -998,6 +1005,33 @@ QPalette QQuickSwipeDelegatePrivate::defaultPalette() const return QQuickTheme::palette(QQuickTheme::ListView); } +/*! \internal + Recursively search right and/or left item tree of swipe delegate for any item that + contains the \a event position. + + Returns the first such item found, otherwise \c nullptr. +*/ +QQuickItem *QQuickSwipeDelegatePrivate::getPressedItem(QQuickItem *childItem, QMouseEvent *event) const +{ + if (!childItem || !event) + return nullptr; + + QQuickItem *item = nullptr; + + if (childItem->acceptedMouseButtons() && + childItem->contains(childItem->mapFromScene(event->scenePosition()))) { + item = childItem; + } else { + const auto &childItems = childItem->childItems(); + for (const auto &child: childItems) { + if ((item = getPressedItem(child, event))) + break; + } + } + + return item; +} + QQuickSwipeDelegate::QQuickSwipeDelegate(QQuickItem *parent) : QQuickItemDelegate(*(new QQuickSwipeDelegatePrivate(this)), parent) { @@ -1240,17 +1274,11 @@ void QQuickSwipeDelegate::mousePressEvent(QMouseEvent *event) swipePrivate->velocityCalculator.startMeasuring(event->position().toPoint(), event->timestamp()); if (swipePrivate->complete) { - auto item = d->swipe.rightItem(); - if (item && item->contains(item->mapFromScene(event->scenePosition()))) { - d->pressedItem = item; - d->handleMousePressEvent(item, event); - } else { - item = d->swipe.leftItem(); - if (item && item->contains(item->mapFromScene(event->scenePosition()))) { - d->pressedItem = item; - d->handleMousePressEvent(item, event); - } - } + d->pressedItem = d->getPressedItem(d->swipe.rightItem(), event); + if (!d->pressedItem) + d->pressedItem = d->getPressedItem(d->swipe.leftItem(), event); + if (d->pressedItem) + d->handleMousePressEvent(d->pressedItem, event); } } diff --git a/src/quicktemplates/qquickswipedelegate_p.h b/src/quicktemplates/qquickswipedelegate_p.h index 2fadabe09f..fae6c6136d 100644 --- a/src/quicktemplates/qquickswipedelegate_p.h +++ b/src/quicktemplates/qquickswipedelegate_p.h @@ -24,7 +24,7 @@ class QQuickSwipeDelegatePrivate; class QQuickSwipeDelegateAttached; class QQuickSwipeDelegateAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipeDelegate : public QQuickItemDelegate +class Q_QUICKTEMPLATES2_EXPORT QQuickSwipeDelegate : public QQuickItemDelegate { Q_OBJECT Q_PROPERTY(QQuickSwipe *swipe READ swipe CONSTANT FINAL) @@ -64,7 +64,7 @@ private: Q_DECLARE_PRIVATE(QQuickSwipeDelegate) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipeDelegateAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickSwipeDelegateAttached : public QObject { Q_OBJECT Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged FINAL) @@ -86,7 +86,6 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickSwipeDelegate) Q_DECLARE_METATYPE(QQuickSwipeDelegate::Side) #endif // QQUICKSWIPEDELEGATE_P_H diff --git a/src/quicktemplates/qquickswipedelegate_p_p.h b/src/quicktemplates/qquickswipedelegate_p_p.h index 4963bb0ff7..78034badd3 100644 --- a/src/quicktemplates/qquickswipedelegate_p_p.h +++ b/src/quicktemplates/qquickswipedelegate_p_p.h @@ -34,6 +34,7 @@ public: bool handleMouseReleaseEvent(QQuickItem *item, QMouseEvent *event); void forwardMouseEvent(QMouseEvent *event, QQuickItem *destination, QPointF localPos); bool attachedObjectsSetPressed(QQuickItem *item, QPointF scenePos, bool pressed, bool cancel = false); + QQuickItem *getPressedItem(QQuickItem *childItem, QMouseEvent *event) const; void resizeContent() override; void resizeBackground() override; diff --git a/src/quicktemplates/qquickswipeview.cpp b/src/quicktemplates/qquickswipeview.cpp index fe647f5764..425f7a5e1a 100644 --- a/src/quicktemplates/qquickswipeview.cpp +++ b/src/quicktemplates/qquickswipeview.cpp @@ -77,7 +77,7 @@ class QQuickSwipeViewPrivate : public QQuickContainerPrivate Q_DECLARE_PUBLIC(QQuickSwipeView) public: - void resizeItem(QQuickItem *item); + void resizeItem(int index, QQuickItem *item); void resizeItems(); static QQuickSwipeViewPrivate *get(QQuickSwipeView *view); @@ -111,25 +111,29 @@ public: int currentIndex = -1; }; +void QQuickSwipeViewPrivate::resizeItem(int index, QQuickItem *item) +{ + QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; + // TODO: expose QQuickAnchorLine so we can test for other conflicting anchors + if (anchors && (anchors->fill() || anchors->centerIn()) && !item->property("_q_QQuickSwipeView_warned").toBool()) { + qmlWarning(item) << "SwipeView has detected conflicting anchors. Unable to layout the item."; + item->setProperty("_q_QQuickSwipeView_warned", true); + } + if (orientation == Qt::Horizontal) + item->setPosition({index * (contentItem->width() + spacing), 0}); + else + item->setPosition({0, index * (contentItem->height() + spacing)}); + item->setSize(QSizeF(contentItem->width(), contentItem->height())); +} + void QQuickSwipeViewPrivate::resizeItems() { Q_Q(QQuickSwipeView); const int count = q->count(); for (int i = 0; i < count; ++i) { QQuickItem *item = itemAt(i); - if (item) { - QQuickAnchors *anchors = QQuickItemPrivate::get(item)->_anchors; - // TODO: expose QQuickAnchorLine so we can test for other conflicting anchors - if (anchors && (anchors->fill() || anchors->centerIn()) && !item->property("_q_QQuickSwipeView_warned").toBool()) { - qmlWarning(item) << "SwipeView has detected conflicting anchors. Unable to layout the item."; - item->setProperty("_q_QQuickSwipeView_warned", true); - } - if (orientation == Qt::Horizontal) - item->setPosition({i * (contentItem->width() + spacing), 0}); - else - item->setPosition({0, i * (contentItem->height() + spacing)}); - item->setSize(QSizeF(contentItem->width(), contentItem->height())); - } + if (item) + resizeItem(i, item); } } @@ -268,6 +272,13 @@ QQuickSwipeViewAttached *QQuickSwipeView::qmlAttachedProperties(QObject *object) return new QQuickSwipeViewAttached(object); } +void QQuickSwipeView::componentComplete() +{ + Q_D(QQuickSwipeView); + QQuickContainer::componentComplete(); + d->resizeItems(); +} + void QQuickSwipeView::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_D(QQuickSwipeView); @@ -279,7 +290,7 @@ void QQuickSwipeView::itemAdded(int index, QQuickItem *item) { Q_D(QQuickSwipeView); if (isComponentComplete()) - item->setSize(QSizeF(d->contentItem->width(), d->contentItem->height())); + d->resizeItem(index, item); QQuickSwipeViewAttached *attached = qobject_cast<QQuickSwipeViewAttached *>(qmlAttachedPropertiesObject<QQuickSwipeView>(item)); if (attached) QQuickSwipeViewAttachedPrivate::get(attached)->update(this, index); diff --git a/src/quicktemplates/qquickswipeview_p.h b/src/quicktemplates/qquickswipeview_p.h index 84e4b51b2a..758de23ffe 100644 --- a/src/quicktemplates/qquickswipeview_p.h +++ b/src/quicktemplates/qquickswipeview_p.h @@ -17,12 +17,14 @@ #include <QtQuickTemplates2/private/qquickcontainer_p.h> +QT_REQUIRE_CONFIG(quicktemplates2_container); + QT_BEGIN_NAMESPACE class QQuickSwipeViewAttached; class QQuickSwipeViewPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipeView : public QQuickContainer +class Q_QUICKTEMPLATES2_EXPORT QQuickSwipeView : public QQuickContainer { Q_OBJECT // 2.1 (Qt 5.8) @@ -60,6 +62,7 @@ Q_SIGNALS: Q_REVISION(2, 2) void orientationChanged(); protected: + void componentComplete() override; void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; void itemAdded(int index, QQuickItem *item) override; void itemMoved(int index, QQuickItem *item) override; @@ -76,7 +79,7 @@ private: class QQuickSwipeViewAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwipeViewAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickSwipeViewAttached : public QObject { Q_OBJECT Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL) @@ -112,6 +115,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickSwipeView) - #endif // QQUICKSWIPEVIEW_P_H diff --git a/src/quicktemplates/qquickswitch_p.h b/src/quicktemplates/qquickswitch_p.h index 9044b066b9..4ee45e8d50 100644 --- a/src/quicktemplates/qquickswitch_p.h +++ b/src/quicktemplates/qquickswitch_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickSwitchPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwitch : public QQuickAbstractButton +class Q_QUICKTEMPLATES2_EXPORT QQuickSwitch : public QQuickAbstractButton { Q_OBJECT Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged FINAL) @@ -61,6 +61,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickSwitch) - #endif // QQUICKSWITCH_P_H diff --git a/src/quicktemplates/qquickswitchdelegate_p.h b/src/quicktemplates/qquickswitchdelegate_p.h index d6a6c830b3..26823c3fa8 100644 --- a/src/quicktemplates/qquickswitchdelegate_p.h +++ b/src/quicktemplates/qquickswitchdelegate_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickSwitchDelegatePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickSwitchDelegate : public QQuickItemDelegate +class Q_QUICKTEMPLATES2_EXPORT QQuickSwitchDelegate : public QQuickItemDelegate { Q_OBJECT Q_PROPERTY(qreal position READ position WRITE setPosition NOTIFY positionChanged FINAL) @@ -61,6 +61,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickSwitchDelegate) - #endif // QQUICKSWITCHDELEGATE_P_H diff --git a/src/quicktemplates/qquicktabbar_p.h b/src/quicktemplates/qquicktabbar_p.h index f062a01b1a..f0b7ebb12f 100644 --- a/src/quicktemplates/qquicktabbar_p.h +++ b/src/quicktemplates/qquicktabbar_p.h @@ -17,13 +17,15 @@ #include <QtQuickTemplates2/private/qquickcontainer_p.h> +QT_REQUIRE_CONFIG(quicktemplates2_container); + QT_BEGIN_NAMESPACE class QQuickTabBarPrivate; class QQuickTabBarAttached; class QQuickTabBarAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTabBar : public QQuickContainer +class Q_QUICKTEMPLATES2_EXPORT QQuickTabBar : public QQuickContainer { Q_OBJECT Q_PROPERTY(Position position READ position WRITE setPosition NOTIFY positionChanged FINAL) @@ -71,7 +73,7 @@ private: Q_DECLARE_PRIVATE(QQuickTabBar) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTabBarAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickTabBarAttached : public QObject { Q_OBJECT Q_PROPERTY(int index READ index NOTIFY indexChanged FINAL) @@ -97,6 +99,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickTabBar) - #endif // QQUICKTABBAR_P_H diff --git a/src/quicktemplates/qquicktabbutton.cpp b/src/quicktemplates/qquicktabbutton.cpp index 500ee0e81b..1c8402fd23 100644 --- a/src/quicktemplates/qquicktabbutton.cpp +++ b/src/quicktemplates/qquicktabbutton.cpp @@ -31,7 +31,7 @@ QT_BEGIN_NAMESPACE \sa TabBar, {Customizing TabButton}, {Button Controls}, {Navigation Controls} */ -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTabButtonPrivate : public QQuickAbstractButtonPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickTabButtonPrivate : public QQuickAbstractButtonPrivate { Q_DECLARE_PUBLIC(QQuickTabButton) diff --git a/src/quicktemplates/qquicktabbutton_p.h b/src/quicktemplates/qquicktabbutton_p.h index 7b2fbdb2fe..1ba7ac5f5a 100644 --- a/src/quicktemplates/qquicktabbutton_p.h +++ b/src/quicktemplates/qquicktabbutton_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickTabButtonPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTabButton : public QQuickAbstractButton +class Q_QUICKTEMPLATES2_EXPORT QQuickTabButton : public QQuickAbstractButton { Q_OBJECT QML_NAMED_ELEMENT(TabButton) @@ -43,6 +43,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickTabButton) - #endif // QQUICKTABBUTTON_P_H diff --git a/src/quicktemplates/qquicktextarea.cpp b/src/quicktemplates/qquicktextarea.cpp index d43f78a5a3..a53f4ac542 100644 --- a/src/quicktemplates/qquicktextarea.cpp +++ b/src/quicktemplates/qquicktextarea.cpp @@ -121,16 +121,10 @@ using namespace Qt::StringLiterals; QQuickTextAreaPrivate::QQuickTextAreaPrivate() { -#if QT_CONFIG(accessibility) - QAccessible::installActivationObserver(this); -#endif } QQuickTextAreaPrivate::~QQuickTextAreaPrivate() { -#if QT_CONFIG(accessibility) - QAccessible::removeActivationObserver(this); -#endif } void QQuickTextAreaPrivate::setTopInset(qreal value, bool reset) @@ -373,8 +367,8 @@ void QQuickTextAreaPrivate::resizeFlickableContent() if (!flickable) return; - flickable->setContentWidth(q->contentWidth() + q->leftPadding() + q->rightPadding()); - flickable->setContentHeight(q->contentHeight() + q->topPadding() + q->bottomPadding()); + flickable->setContentWidth(q->implicitWidth()); + flickable->setContentHeight(q->implicitHeight()); } void QQuickTextAreaPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff) @@ -420,37 +414,15 @@ void QQuickTextAreaPrivate::implicitHeightChanged() emit q->implicitHeightChanged3(); } -void QQuickTextAreaPrivate::readOnlyChanged(bool isReadOnly) -{ - Q_UNUSED(isReadOnly); -#if QT_CONFIG(accessibility) - if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(q_func())) - accessibleAttached->set_readOnly(isReadOnly); -#endif -} - #if QT_CONFIG(accessibility) void QQuickTextAreaPrivate::accessibilityActiveChanged(bool active) { - if (!active) - return; - - Q_Q(QQuickTextArea); - QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true)); - Q_ASSERT(accessibleAttached); - accessibleAttached->setRole(accessibleRole()); - accessibleAttached->set_readOnly(q->isReadOnly()); - accessibleAttached->setDescription(placeholder); -} - -QAccessible::Role QQuickTextAreaPrivate::accessibleRole() const -{ - return QAccessible::EditableText; + QQuickTextEditPrivate::accessibilityActiveChanged(active); + if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(q_func())) + accessibleAttached->setDescription(placeholder); } #endif -static inline QString backgroundName() { return QStringLiteral("background"); } - void QQuickTextAreaPrivate::cancelBackground() { Q_Q(QQuickTextArea); @@ -500,6 +472,16 @@ QPalette QQuickTextAreaPrivate::defaultPalette() const return QQuickTheme::palette(QQuickTheme::TextArea); } +bool QQuickTextAreaPrivate::setLastFocusChangeReason(Qt::FocusReason reason) +{ + Q_Q(QQuickTextArea); + const auto focusReasonChanged = QQuickItemPrivate::setLastFocusChangeReason(reason); + if (focusReasonChanged) + emit q->focusReasonChanged(); + + return focusReasonChanged; +} + QQuickTextArea::QQuickTextArea(QQuickItem *parent) : QQuickTextEdit(*(new QQuickTextAreaPrivate), parent) { @@ -509,8 +491,6 @@ QQuickTextArea::QQuickTextArea(QQuickItem *parent) d->setImplicitResizeEnabled(false); d->pressHandler.control = this; - QObjectPrivate::connect(this, &QQuickTextEdit::readOnlyChanged, - d, &QQuickTextAreaPrivate::readOnlyChanged); #if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) if (qEnvironmentVariable("QT_QUICK_CONTROLS_TEXT_SELECTION_BEHAVIOR") == u"old"_s) QQuickTextEdit::setOldSelectionDefault(); @@ -667,24 +647,35 @@ void QQuickTextArea::setPlaceholderTextColor(const QColor &color) /*! \qmlproperty enumeration QtQuick.Controls::TextArea::focusReason - \include qquickcontrol-focusreason.qdocinc + This property holds the reason of the last focus change. + + \note This property does not indicate whether the item has \l {Item::activeFocus} + {active focus}, but the reason why the item either gained or lost focus. + + \value Qt.MouseFocusReason A mouse action occurred. + \value Qt.TabFocusReason The Tab key was pressed. + \value Qt.BacktabFocusReason A Backtab occurred. The input for this may include the Shift or Control keys; e.g. Shift+Tab. + \value Qt.ActiveWindowFocusReason The window system made this window either active or inactive. + \value Qt.PopupFocusReason The application opened/closed a pop-up that grabbed/released the keyboard focus. + \value Qt.ShortcutFocusReason The user typed a label's buddy shortcut + \value Qt.MenuBarFocusReason The menu bar took focus. + \value Qt.OtherFocusReason Another reason, usually application-specific. + + \note Prefer \l {Item::focusReason} to this property. */ Qt::FocusReason QQuickTextArea::focusReason() const { Q_D(const QQuickTextArea); - return d->focusReason; + return d->lastFocusChangeReason(); } void QQuickTextArea::setFocusReason(Qt::FocusReason reason) { Q_D(QQuickTextArea); - if (d->focusReason == reason) - return; - - d->focusReason = reason; - emit focusReasonChanged(); + d->setLastFocusChangeReason(reason); } + bool QQuickTextArea::contains(const QPointF &point) const { Q_D(const QQuickTextArea); @@ -928,10 +919,6 @@ void QQuickTextArea::componentComplete() if (!d->explicitHoverEnabled) setAcceptHoverEvents(QQuickControlPrivate::calcHoverEnabled(d->parentItem)); #endif -#if QT_CONFIG(accessibility) - if (QAccessible::isActive()) - d->accessibilityActiveChanged(true); -#endif } void QQuickTextArea::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &value) @@ -1014,13 +1001,11 @@ QSGNode *QQuickTextArea::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * void QQuickTextArea::focusInEvent(QFocusEvent *event) { QQuickTextEdit::focusInEvent(event); - setFocusReason(event->reason()); } void QQuickTextArea::focusOutEvent(QFocusEvent *event) { QQuickTextEdit::focusOutEvent(event); - setFocusReason(event->reason()); } #if QT_CONFIG(quicktemplates2_hover) @@ -1046,7 +1031,7 @@ void QQuickTextArea::mousePressEvent(QMouseEvent *event) d->pressHandler.mousePressEvent(event); if (d->pressHandler.isActive()) { if (d->pressHandler.delayedMousePressEvent) { - QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent); + QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent.get()); d->pressHandler.clearDelayedMouseEvent(); } // Calling the base class implementation will result in QQuickTextControl's @@ -1064,7 +1049,7 @@ void QQuickTextArea::mouseMoveEvent(QMouseEvent *event) d->pressHandler.mouseMoveEvent(event); if (d->pressHandler.isActive()) { if (d->pressHandler.delayedMousePressEvent) { - QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent); + QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent.get()); d->pressHandler.clearDelayedMouseEvent(); } QQuickTextEdit::mouseMoveEvent(event); @@ -1077,7 +1062,7 @@ void QQuickTextArea::mouseReleaseEvent(QMouseEvent *event) d->pressHandler.mouseReleaseEvent(event); if (d->pressHandler.isActive()) { if (d->pressHandler.delayedMousePressEvent) { - QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent); + QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent.get()); d->pressHandler.clearDelayedMouseEvent(); } QQuickTextEdit::mouseReleaseEvent(event); @@ -1088,7 +1073,7 @@ void QQuickTextArea::mouseDoubleClickEvent(QMouseEvent *event) { Q_D(QQuickTextArea); if (d->pressHandler.delayedMousePressEvent) { - QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent); + QQuickTextEdit::mousePressEvent(d->pressHandler.delayedMousePressEvent.get()); d->pressHandler.clearDelayedMouseEvent(); } QQuickTextEdit::mouseDoubleClickEvent(event); diff --git a/src/quicktemplates/qquicktextarea_p.h b/src/quicktemplates/qquicktextarea_p.h index 5bd10af21e..d09f300a9f 100644 --- a/src/quicktemplates/qquicktextarea_p.h +++ b/src/quicktemplates/qquicktextarea_p.h @@ -26,7 +26,7 @@ class QQuickText; class QQuickTextAreaPrivate; class QQuickTextAreaAttached; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTextArea : public QQuickTextEdit +class Q_QUICKTEMPLATES2_EXPORT QQuickTextArea : public QQuickTextEdit { Q_OBJECT Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) // override @@ -155,7 +155,7 @@ private: class QQuickTextAreaAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTextAreaAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickTextAreaAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickTextArea *flickable READ flickable WRITE setFlickable NOTIFY flickableChanged FINAL) @@ -174,16 +174,6 @@ private: Q_DECLARE_PRIVATE(QQuickTextAreaAttached) }; -struct QQuickTextEditForeign -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QQuickTextEdit) - QML_ADDED_IN_VERSION(2, 3) -}; - QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickTextArea) - #endif // QQUICKTEXTAREA_P_H diff --git a/src/quicktemplates/qquicktextarea_p_p.h b/src/quicktemplates/qquicktextarea_p_p.h index 243b350866..849c22b97c 100644 --- a/src/quicktemplates/qquicktextarea_p_p.h +++ b/src/quicktemplates/qquicktextarea_p_p.h @@ -24,18 +24,11 @@ #include <QtQuickTemplates2/private/qquicktextarea_p.h> -#if QT_CONFIG(accessibility) -#include <QtGui/qaccessible.h> -#endif - QT_BEGIN_NAMESPACE class QQuickFlickable; class QQuickTextAreaPrivate : public QQuickTextEditPrivate, public QQuickItemChangeListener -#if QT_CONFIG(accessibility) - , public QAccessible::ActivationObserver -#endif { public: Q_DECLARE_PUBLIC(QQuickTextArea) @@ -92,7 +85,6 @@ public: #if QT_CONFIG(accessibility) void accessibilityActiveChanged(bool active) override; - QAccessible::Role accessibleRole() const override; #endif void cancelBackground(); @@ -104,6 +96,8 @@ public: QPalette defaultPalette() const override; + bool setLastFocusChangeReason(Qt::FocusReason reason) override; + #if QT_CONFIG(quicktemplates2_hover) bool hovered = false; bool explicitHoverEnabled = false; @@ -130,7 +124,6 @@ public: QQuickDeferredPointer<QQuickItem> background; QString placeholder; QColor placeholderColor; - Qt::FocusReason focusReason = Qt::OtherFocusReason; QQuickPressHandler pressHandler; QQuickFlickable *flickable = nullptr; }; diff --git a/src/quicktemplates/qquicktextfield.cpp b/src/quicktemplates/qquicktextfield.cpp index bb62c15ed1..7163f2a302 100644 --- a/src/quicktemplates/qquicktextfield.cpp +++ b/src/quicktemplates/qquicktextfield.cpp @@ -288,7 +288,7 @@ void QQuickTextFieldPrivate::accessibilityActiveChanged(bool active) Q_Q(QQuickTextField); QQuickAccessibleAttached *accessibleAttached = qobject_cast<QQuickAccessibleAttached *>(qmlAttachedPropertiesObject<QQuickAccessibleAttached>(q, true)); Q_ASSERT(accessibleAttached); - accessibleAttached->setRole(accessibleRole()); + accessibleAttached->setRole(effectiveAccessibleRole()); accessibleAttached->set_readOnly(m_readOnly); accessibleAttached->set_passwordEdit((m_echoMode == QQuickTextField::Password || m_echoMode == QQuickTextField::PasswordEchoOnEdit) ? true : false); accessibleAttached->setDescription(placeholder); @@ -300,8 +300,6 @@ QAccessible::Role QQuickTextFieldPrivate::accessibleRole() const } #endif -static inline QString backgroundName() { return QStringLiteral("background"); } - void QQuickTextFieldPrivate::cancelBackground() { Q_Q(QQuickTextField); @@ -366,6 +364,16 @@ QPalette QQuickTextFieldPrivate::defaultPalette() const return QQuickTheme::palette(QQuickTheme::TextField); } +bool QQuickTextFieldPrivate::setLastFocusChangeReason(Qt::FocusReason reason) +{ + Q_Q(QQuickTextField); + const auto focusReasonChanged = QQuickItemPrivate::setLastFocusChangeReason(reason); + if (focusReasonChanged) + emit q->focusReasonChanged(); + + return focusReasonChanged; +} + QQuickTextField::QQuickTextField(QQuickItem *parent) : QQuickTextInput(*(new QQuickTextFieldPrivate), parent) { @@ -525,22 +533,32 @@ void QQuickTextField::setPlaceholderTextColor(const QColor &color) /*! \qmlproperty enumeration QtQuick.Controls::TextField::focusReason - \include qquickcontrol-focusreason.qdocinc + This property holds the reason of the last focus change. + + \note This property does not indicate whether the item has \l {Item::activeFocus} + {active focus}, but the reason why the item either gained or lost focus. + + \value Qt.MouseFocusReason A mouse action occurred. + \value Qt.TabFocusReason The Tab key was pressed. + \value Qt.BacktabFocusReason A Backtab occurred. The input for this may include the Shift or Control keys; e.g. Shift+Tab. + \value Qt.ActiveWindowFocusReason The window system made this window either active or inactive. + \value Qt.PopupFocusReason The application opened/closed a pop-up that grabbed/released the keyboard focus. + \value Qt.ShortcutFocusReason The user typed a label's buddy shortcut + \value Qt.MenuBarFocusReason The menu bar took focus. + \value Qt.OtherFocusReason Another reason, usually application-specific. + + \note Prefer \l {Item::focusReason} to this property. */ Qt::FocusReason QQuickTextField::focusReason() const { Q_D(const QQuickTextField); - return d->focusReason; + return d->lastFocusChangeReason(); } void QQuickTextField::setFocusReason(Qt::FocusReason reason) { Q_D(QQuickTextField); - if (d->focusReason == reason) - return; - - d->focusReason = reason; - emit focusReasonChanged(); + d->setLastFocusChangeReason(reason); } /*! @@ -839,13 +857,11 @@ QSGNode *QQuickTextField::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData void QQuickTextField::focusInEvent(QFocusEvent *event) { QQuickTextInput::focusInEvent(event); - setFocusReason(event->reason()); } void QQuickTextField::focusOutEvent(QFocusEvent *event) { QQuickTextInput::focusOutEvent(event); - setFocusReason(event->reason()); } #if QT_CONFIG(quicktemplates2_hover) @@ -871,7 +887,7 @@ void QQuickTextField::mousePressEvent(QMouseEvent *event) d->pressHandler.mousePressEvent(event); if (d->pressHandler.isActive()) { if (d->pressHandler.delayedMousePressEvent) { - QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent); + QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent.get()); d->pressHandler.clearDelayedMouseEvent(); } if (event->buttons() != Qt::RightButton) @@ -885,7 +901,7 @@ void QQuickTextField::mouseMoveEvent(QMouseEvent *event) d->pressHandler.mouseMoveEvent(event); if (d->pressHandler.isActive()) { if (d->pressHandler.delayedMousePressEvent) { - QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent); + QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent.get()); d->pressHandler.clearDelayedMouseEvent(); } const bool isMouse = QQuickDeliveryAgentPrivate::isEventFromMouseOrTouchpad(event) @@ -904,7 +920,7 @@ void QQuickTextField::mouseReleaseEvent(QMouseEvent *event) d->pressHandler.mouseReleaseEvent(event); if (d->pressHandler.isActive()) { if (d->pressHandler.delayedMousePressEvent) { - QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent); + QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent.get()); d->pressHandler.clearDelayedMouseEvent(); } if (event->buttons() != Qt::RightButton) @@ -916,7 +932,7 @@ void QQuickTextField::mouseDoubleClickEvent(QMouseEvent *event) { Q_D(QQuickTextField); if (d->pressHandler.delayedMousePressEvent) { - QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent); + QQuickTextInput::mousePressEvent(d->pressHandler.delayedMousePressEvent.get()); d->pressHandler.clearDelayedMouseEvent(); } if (event->buttons() != Qt::RightButton) diff --git a/src/quicktemplates/qquicktextfield_p.h b/src/quicktemplates/qquicktextfield_p.h index c5bc9ce0bd..4c38c098ba 100644 --- a/src/quicktemplates/qquicktextfield_p.h +++ b/src/quicktemplates/qquicktextfield_p.h @@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE class QQuickTextFieldPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTextField : public QQuickTextInput +class Q_QUICKTEMPLATES2_EXPORT QQuickTextField : public QQuickTextInput { Q_OBJECT Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontChanged) // override @@ -146,16 +146,6 @@ private: Q_DECLARE_PRIVATE(QQuickTextField) }; -struct QQuickTextFieldForeign -{ - Q_GADGET - QML_ANONYMOUS - QML_FOREIGN(QQuickTextInput) - QML_ADDED_IN_VERSION(2, 2) -}; - QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickTextField) - #endif // QQUICKTEXTFIELD_P_H diff --git a/src/quicktemplates/qquicktextfield_p_p.h b/src/quicktemplates/qquicktextfield_p_p.h index 8c6752776d..ac75460b7e 100644 --- a/src/quicktemplates/qquicktextfield_p_p.h +++ b/src/quicktemplates/qquicktextfield_p_p.h @@ -94,6 +94,8 @@ public: QPalette defaultPalette() const override; + bool setLastFocusChangeReason(Qt::FocusReason reason) override; + #if QT_CONFIG(quicktemplates2_hover) bool hovered = false; bool explicitHoverEnabled = false; @@ -118,7 +120,6 @@ public: QQuickDeferredPointer<QQuickItem> background; QString placeholder; QColor placeholderColor; - Qt::FocusReason focusReason = Qt::OtherFocusReason; QQuickPressHandler pressHandler; }; diff --git a/src/quicktemplates/qquicktheme_p.h b/src/quicktemplates/qquicktheme_p.h index da6254e9bf..96cbd2c0ad 100644 --- a/src/quicktemplates/qquicktheme_p.h +++ b/src/quicktemplates/qquicktheme_p.h @@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE class QQuickThemePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTheme +class Q_QUICKTEMPLATES2_EXPORT QQuickTheme { public: QQuickTheme(); diff --git a/src/quicktemplates/qquicktheme_p_p.h b/src/quicktemplates/qquicktheme_p_p.h index a18957bd0d..f6c9cc39fb 100644 --- a/src/quicktemplates/qquicktheme_p_p.h +++ b/src/quicktemplates/qquicktheme_p_p.h @@ -19,7 +19,7 @@ QT_BEGIN_NAMESPACE -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickThemePrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickThemePrivate { public: static QQuickThemePrivate *get(QQuickTheme *theme) diff --git a/src/quicktemplates/qquicktoolbar_p.h b/src/quicktemplates/qquicktoolbar_p.h index f896361ea0..3079bc78f0 100644 --- a/src/quicktemplates/qquicktoolbar_p.h +++ b/src/quicktemplates/qquicktoolbar_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickToolBarPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolBar : public QQuickPane +class Q_QUICKTEMPLATES2_EXPORT QQuickToolBar : public QQuickPane { Q_OBJECT Q_PROPERTY(Position position READ position WRITE setPosition NOTIFY positionChanged FINAL) @@ -57,6 +57,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickToolBar) - #endif // QQUICKTOOLBAR_P_H diff --git a/src/quicktemplates/qquicktoolbutton.cpp b/src/quicktemplates/qquicktoolbutton.cpp index 3acf8782a1..d857b8ab40 100644 --- a/src/quicktemplates/qquicktoolbutton.cpp +++ b/src/quicktemplates/qquicktoolbutton.cpp @@ -32,7 +32,7 @@ QT_BEGIN_NAMESPACE \sa ToolBar, {Customizing ToolButton}, {Button Controls} */ -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolPrivate : public QQuickButtonPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickToolPrivate : public QQuickButtonPrivate { Q_DECLARE_PUBLIC(QQuickToolButton) diff --git a/src/quicktemplates/qquicktoolbutton_p.h b/src/quicktemplates/qquicktoolbutton_p.h index e470ae816d..2ddd9087c6 100644 --- a/src/quicktemplates/qquicktoolbutton_p.h +++ b/src/quicktemplates/qquicktoolbutton_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickToolButtonPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolButton : public QQuickButton +class Q_QUICKTEMPLATES2_EXPORT QQuickToolButton : public QQuickButton { Q_OBJECT QML_NAMED_ELEMENT(ToolButton) @@ -39,6 +39,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickToolButton) - #endif // QQUICKTOOLBUTTON_P_H diff --git a/src/quicktemplates/qquicktoolseparator_p.h b/src/quicktemplates/qquicktoolseparator_p.h index 85930a3506..04d941566c 100644 --- a/src/quicktemplates/qquicktoolseparator_p.h +++ b/src/quicktemplates/qquicktoolseparator_p.h @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE class QQuickToolSeparatorPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolSeparator : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickToolSeparator : public QQuickControl { Q_OBJECT Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged FINAL) @@ -56,6 +56,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickToolSeparator) - #endif // QQUICKTOOLSEPARATOR_P_H diff --git a/src/quicktemplates/qquicktooltip.cpp b/src/quicktemplates/qquicktooltip.cpp index 2a78e4d344..0493cd5f4e 100644 --- a/src/quicktemplates/qquicktooltip.cpp +++ b/src/quicktemplates/qquicktooltip.cpp @@ -102,6 +102,8 @@ public: void opened() override; + Qt::WindowFlags popupWindowType() const override; + QPalette defaultPalette() const override { return QQuickTheme::palette(QQuickTheme::ToolTip); } int delay = 0; @@ -141,6 +143,11 @@ void QQuickToolTipPrivate::opened() startTimeout(); } +Qt::WindowFlags QQuickToolTipPrivate::popupWindowType() const +{ + return Qt::ToolTip; +} + QQuickToolTip::QQuickToolTip(QQuickItem *parent) : QQuickPopup(*(new QQuickToolTipPrivate), parent) { diff --git a/src/quicktemplates/qquicktooltip_p.h b/src/quicktemplates/qquicktooltip_p.h index 979df38a31..1ac89d8efb 100644 --- a/src/quicktemplates/qquicktooltip_p.h +++ b/src/quicktemplates/qquicktooltip_p.h @@ -23,7 +23,7 @@ class QQuickToolTipPrivate; class QQuickToolTipAttached; class QQuickToolTipAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolTip : public QQuickPopup +class Q_QUICKTEMPLATES2_EXPORT QQuickToolTip : public QQuickPopup { Q_OBJECT Q_PROPERTY(int delay READ delay WRITE setDelay NOTIFY delayChanged FINAL) @@ -74,7 +74,7 @@ private: Q_DECLARE_PRIVATE(QQuickToolTip) }; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickToolTipAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickToolTipAttached : public QObject { Q_OBJECT Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged FINAL) @@ -117,6 +117,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickToolTip) - #endif // QQUICKTOOLTIP_P_H diff --git a/src/quicktemplates/qquicktreeviewdelegate.cpp b/src/quicktemplates/qquicktreeviewdelegate.cpp index ba1a26cb34..32d51975be 100644 --- a/src/quicktemplates/qquicktreeviewdelegate.cpp +++ b/src/quicktemplates/qquicktreeviewdelegate.cpp @@ -7,6 +7,8 @@ #include <QtQuick/private/qquicktaphandler_p.h> #include <QtQuick/private/qquicktreeview_p_p.h> +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE /*! @@ -99,14 +101,14 @@ QT_BEGIN_NAMESPACE \note If you want to disable the default behavior that occurs when the user clicks on the delegate (like changing the current index), you can set - \l {QQuickTableView::pointerNavigationEnabled}{pointerNavigationEnabled} to \c false. + \l {TableView::pointerNavigationEnabled}{pointerNavigationEnabled} to \c false. \section2 Editing nodes in the tree TreeViewDelegate has a default \l {TableView::editDelegate}{edit delegate} assigned. If \l TreeView has \l {TableView::editTriggers}{edit triggers} set, and the \l {TableView::model}{model} has support for \l {Editing cells} {editing model items}, then the user can activate any of - the edit triggers to edit the text of the \l {TableView::current}{current} + the edit triggers to edit the text of the \l {TreeViewDelegate::current}{current} tree node. The default edit delegate will try to use the \c {Qt.EditRole} to read and @@ -138,7 +140,7 @@ QT_BEGIN_NAMESPACE \qmlproperty bool QtQuick.Controls::TreeViewDelegate::isTreeNode This property is \c true if the delegate item draws a node in the tree. - Only one column in the view will be used to to draw the tree, and + Only one column in the view will be used to draw the tree, and therefore, only delegate items in that column will have this property set to \c true. diff --git a/src/quicktemplates/qquicktreeviewdelegate_p.h b/src/quicktemplates/qquicktreeviewdelegate_p.h index aa39b17c10..e56057301e 100644 --- a/src/quicktemplates/qquicktreeviewdelegate_p.h +++ b/src/quicktemplates/qquicktreeviewdelegate_p.h @@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE class QQuickTreeViewDelegatePrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTreeViewDelegate : public QQuickItemDelegate +class Q_QUICKTEMPLATES2_EXPORT QQuickTreeViewDelegate : public QQuickItemDelegate { Q_OBJECT Q_PROPERTY(qreal indentation READ indentation WRITE setIndentation NOTIFY indentationChanged FINAL) diff --git a/src/quicktemplates/qquicktumbler.cpp b/src/quicktemplates/qquicktumbler.cpp index e7f6606de3..71766cc698 100644 --- a/src/quicktemplates/qquicktumbler.cpp +++ b/src/quicktemplates/qquicktumbler.cpp @@ -259,6 +259,9 @@ QPalette QQuickTumblerPrivate::defaultPalette() const QQuickTumbler::QQuickTumbler(QQuickItem *parent) : QQuickControl(*(new QQuickTumblerPrivate), parent) { + Q_D(QQuickTumbler); + d->setSizePolicy(QLayoutPolicy::Preferred, QLayoutPolicy::Preferred); + setActiveFocusOnTab(true); connect(this, SIGNAL(leftPaddingChanged()), this, SLOT(_q_updateItemWidths())); diff --git a/src/quicktemplates/qquicktumbler_p.h b/src/quicktemplates/qquicktumbler_p.h index 02ea3049fd..d2f980aad0 100644 --- a/src/quicktemplates/qquicktumbler_p.h +++ b/src/quicktemplates/qquicktumbler_p.h @@ -24,7 +24,7 @@ QT_BEGIN_NAMESPACE class QQuickTumblerAttached; class QQuickTumblerPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTumbler : public QQuickControl +class Q_QUICKTEMPLATES2_EXPORT QQuickTumbler : public QQuickControl { Q_OBJECT Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged FINAL) @@ -118,7 +118,7 @@ private: class QQuickTumblerAttachedPrivate; -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTumblerAttached : public QObject +class Q_QUICKTEMPLATES2_EXPORT QQuickTumblerAttached : public QObject { Q_OBJECT Q_PROPERTY(QQuickTumbler *tumbler READ tumbler CONSTANT FINAL) @@ -140,6 +140,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickTumbler) - #endif // QQUICKTUMBLER_P_H diff --git a/src/quicktemplates/qquicktumbler_p_p.h b/src/quicktemplates/qquicktumbler_p_p.h index e53381c470..70a611bf0f 100644 --- a/src/quicktemplates/qquicktumbler_p_p.h +++ b/src/quicktemplates/qquicktumbler_p_p.h @@ -18,9 +18,11 @@ #include <QtQuickTemplates2/private/qquickcontrol_p_p.h> #include <QtQuickTemplates2/private/qquicktumbler_p.h> +#include <QtCore/qpointer.h> + QT_BEGIN_NAMESPACE -class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickTumblerPrivate : public QQuickControlPrivate +class Q_QUICKTEMPLATES2_EXPORT QQuickTumblerPrivate : public QQuickControlPrivate { Q_DECLARE_PUBLIC(QQuickTumbler) diff --git a/src/quicktemplates/qquickweeknumbercolumn_p.h b/src/quicktemplates/qquickweeknumbercolumn_p.h index 7ea6255319..6db01a0923 100644 --- a/src/quicktemplates/qquickweeknumbercolumn_p.h +++ b/src/quicktemplates/qquickweeknumbercolumn_p.h @@ -66,6 +66,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickWeekNumberColumn) - #endif // QQUICKWEEKNUMBERCOLUMN_P_H diff --git a/src/quicktemplates/qquickweeknumbermodel_p.h b/src/quicktemplates/qquickweeknumbermodel_p.h index f00fd5a073..ba12f2be16 100644 --- a/src/quicktemplates/qquickweeknumbermodel_p.h +++ b/src/quicktemplates/qquickweeknumbermodel_p.h @@ -67,6 +67,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickWeekNumberModel) - #endif // QQUICKWEEKNUMBERMODEL_P_H diff --git a/src/quicktemplates/qtquicktemplates2global_p.h b/src/quicktemplates/qtquicktemplates2global_p.h index bca84663c6..27b5947994 100644 --- a/src/quicktemplates/qtquicktemplates2global_p.h +++ b/src/quicktemplates/qtquicktemplates2global_p.h @@ -18,12 +18,16 @@ #include <QtCore/qglobal.h> #include <QtQml/private/qqmlglobal_p.h> #include <QtQuickTemplates2/private/qtquicktemplates2-config_p.h> -#include <QtQuickTemplates2/private/qtquicktemplates2exports_p.h> +#include <QtQuickTemplates2/qtquicktemplates2exports.h> QT_BEGIN_NAMESPACE -Q_QUICKTEMPLATES2_PRIVATE_EXPORT void QQuickTemplates_initializeModule(); -Q_QUICKTEMPLATES2_PRIVATE_EXPORT void qml_register_types_QtQuick_Templates(); +Q_QUICKTEMPLATES2_EXPORT void QQuickTemplates_initializeModule(); +Q_QUICKTEMPLATES2_EXPORT void qml_register_types_QtQuick_Templates(); + +[[maybe_unused]] static inline QString backgroundName() { return QStringLiteral("background"); } +[[maybe_unused]] static inline QString handleName() { return QStringLiteral("handle"); } +[[maybe_unused]] static inline QString indicatorName() { return QStringLiteral("indicator"); } QT_END_NAMESPACE |