diff options
| author | Mitch Curtis <mitch.curtis@qt.io> | 2023-06-22 14:36:23 +0800 |
|---|---|---|
| committer | Mitch Curtis <mitch.curtis@qt.io> | 2023-07-05 13:06:53 +0800 |
| commit | 1baa7fc3e7956773e6da6d062bc0f1c84a5ec3f3 (patch) | |
| tree | 7c78dcd5379539631be37f56bc187da88208f347 | |
| parent | 7d0ec8910f864426ac53aeb12bea505c59f7125a (diff) | |
StackView: add strongly-typed replace functions
[ChangeLog][Controls][StackView] Added strongly-typed
replaceCurrentItem functions. These can be compiled to C++
by the QML Compiler.
Task-number: QTBUG-112475
Change-Id: I5de7d712cd1839ed3e6f1f7229bd2e5c896fd6bd
Reviewed-by: Mitch Curtis <mitch.curtis@qt.io>
| -rw-r--r-- | src/quickcontrols/doc/src/includes/qquickstackview.qdocinc | 16 | ||||
| -rw-r--r-- | src/quicktemplates/qquickstackview.cpp | 210 | ||||
| -rw-r--r-- | src/quicktemplates/qquickstackview_p.cpp | 41 | ||||
| -rw-r--r-- | src/quicktemplates/qquickstackview_p.h | 11 | ||||
| -rw-r--r-- | src/quicktemplates/qquickstackview_p_p.h | 1 | ||||
| -rw-r--r-- | tests/auto/quickcontrols/controls/data/tst_stackview.qml | 45 |
6 files changed, 283 insertions, 41 deletions
diff --git a/src/quickcontrols/doc/src/includes/qquickstackview.qdocinc b/src/quickcontrols/doc/src/includes/qquickstackview.qdocinc index 0456a8d723..16d4e7374c 100644 --- a/src/quickcontrols/doc/src/includes/qquickstackview.qdocinc +++ b/src/quickcontrols/doc/src/includes/qquickstackview.qdocinc @@ -13,3 +13,19 @@ operations: \value StackView.ReplaceTransition An operation with replace transitions. \value StackView.PopTransition An operation with pop transitions. //! [operation-values] + +//! [optional-properties-after-each-item] +The optional properties arguments come after each item, and specify a +map of initial property values. For dynamically created items, these values +are applied before the creation is finalized. This is more efficient than +setting property values after creation, particularly where large sets of +property values are defined, and also allows property bindings to be set +up (using \l{Qt::binding}{Qt.binding()}) before the item is created. +//! [optional-properties-after-each-item] + +//! [replaceCurrentItem] +Pops \l currentItem from the stack and pushes \a \1. If the optional +\a operation is specified, the relevant transition will be used. If the +optional \a properties are specified, they will be applied to the item. +Returns the item that became current. +//! [replaceCurrentItem] diff --git a/src/quicktemplates/qquickstackview.cpp b/src/quicktemplates/qquickstackview.cpp index 2db2e914b6..3bd2f6cfbb 100644 --- a/src/quicktemplates/qquickstackview.cpp +++ b/src/quicktemplates/qquickstackview.cpp @@ -843,6 +843,9 @@ 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) @@ -934,11 +937,7 @@ void QQuickStackView::replace(QQmlV4Function *args) \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. - The optional properties arguments come after each element, and specify a - map of initial property values. For dynamically created items, these values - are applied before the creation is finalized. This is more efficient than - setting property values after creation, particularly where large sets of - property values are defined. + \include qquickstackview.qdocinc optional-properties-after-each-item \code stackView.push([item, rectComponent, Qt.resolvedUrl("MyItem.qml")]) @@ -987,41 +986,7 @@ QQuickItem *QQuickStackView::pushItems(QList<QQuickStackViewArg> args, Operation QScopedValueRollback<bool> modifyingElements(d->modifyingElements, true); QScopedValueRollback<QString> operationNameRollback(d->operation, operationName); - 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 (d->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(this) << "Properties must come after an Item, Component or URL"; - return nullptr; - } - - QQuickStackElement *element = QQuickStackElement::fromStackViewArg(this, arg); - QV4::ExecutionEngine *v4Engine = qmlEngine(this)->handle(); - element->properties.set(v4Engine, v4Engine->fromVariant(properties)); - element->qmlCallingContext.set(v4Engine, v4Engine->qmlContext()); - stackElements.append(element); - } + const QList<QQuickStackElement *> stackElements = d->parseElements(args); #if QT_CONFIG(quick_viewtransitions) QQuickStackElement *exit = nullptr; @@ -1209,6 +1174,171 @@ QQuickItem *QQuickStackView::popCurrentItem(Operation operation) } /*! + \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 diff --git a/src/quicktemplates/qquickstackview_p.cpp b/src/quicktemplates/qquickstackview_p.cpp index f4b7a42e2d..5d9694906f 100644 --- a/src/quicktemplates/qquickstackview_p.cpp +++ b/src/quicktemplates/qquickstackview_p.cpp @@ -109,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) { diff --git a/src/quicktemplates/qquickstackview_p.h b/src/quicktemplates/qquickstackview_p.h index aa67af8edb..eb9c2b033d 100644 --- a/src/quicktemplates/qquickstackview_p.h +++ b/src/quicktemplates/qquickstackview_p.h @@ -53,7 +53,7 @@ public: #endif private: - friend class QQuickStackView; + friend class QQuickStackViewPrivate; friend class QQuickStackElement; QQuickItem *mItem = nullptr; @@ -159,6 +159,15 @@ public: 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; diff --git a/src/quicktemplates/qquickstackview_p_p.h b/src/quicktemplates/qquickstackview_p_p.h index ac1d5c326f..6acfaa93a1 100644 --- a/src/quicktemplates/qquickstackview_p_p.h +++ b/src/quicktemplates/qquickstackview_p_p.h @@ -50,6 +50,7 @@ public: void setCurrentItem(QQuickStackElement *element); QList<QQuickStackElement *> parseElements(int from, QQmlV4Function *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); diff --git a/tests/auto/quickcontrols/controls/data/tst_stackview.qml b/tests/auto/quickcontrols/controls/data/tst_stackview.qml index 00bf3e3829..3ab6e1d9c2 100644 --- a/tests/auto/quickcontrols/controls/data/tst_stackview.qml +++ b/tests/auto/quickcontrols/controls/data/tst_stackview.qml @@ -720,6 +720,51 @@ TestCase { compare(control.currentItem, item8) } + function test_replaceNew() { + let control = createTemporaryObject(stackViewComponent, testCase) + verify(control) + + // replace(item) - replace currentItem + let item1 = itemComponent.createObject(control, {objectName:"1"}) + compare(control.replaceCurrentItem(item1, {}, StackView.Immediate), item1) + compare(control.depth, 1) + compare(control.currentItem, item1) + + // replace([item]) - replace currentItem + let item2 = itemComponent.createObject(control, {objectName:"2"}) + compare(control.replaceCurrentItem(item2, {}, StackView.Immediate), item2) + compare(control.depth, 1) + compare(control.currentItem, item2) + + // replace(item, {properties}) - replace currentItem + let item3 = itemComponent.createObject(control) + compare(control.replaceCurrentItem(item3, {objectName:"3"}, StackView.Immediate), item3) + compare(item3.objectName, "3") + compare(control.depth, 1) + compare(control.currentItem, item3) + + // replace([item, {properties}]) - replace currentItem + let item4 = itemComponent.createObject(control) + compare(control.replaceCurrentItem([item4, {objectName:"4"}], StackView.Immediate), item4) + compare(item4.objectName, "4") + compare(control.depth, 1) + compare(control.currentItem, item4) + + // replace(component, {properties}) - replace currentItem + let item5 = control.replaceCurrentItem(itemComponent, {objectName:"5"}, StackView.Immediate) + compare(control.currentItem, item5) + compare(item5.objectName, "5") + compare(control.depth, 1) + compare(control.currentItem, item5) + + // replace([component, {properties}]) - replace currentItem + let item6 = control.replaceCurrentItem([itemComponent, {objectName:"6"}], StackView.Immediate) + compare(control.currentItem, item6) + compare(item6.objectName, "6") + compare(control.depth, 1) + compare(control.currentItem, item6) + } + function test_clear() { var control = createTemporaryObject(stackViewComponent, testCase) verify(control) |
