Kirigami2

pagestackattached.cpp
1// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#include "pagestackattached.h"
5
6#include "formlayoutattached.h"
7#include "loggingcategory.h"
8
9#include <QMetaObject>
10#include <QQmlContext>
11#include <QQmlEngine>
12
13using namespace Qt::StringLiterals;
14
15template<typename... Args>
16bool callIfValid(QObject *object, const char *method, Args &&...args)
17{
18 auto metaObject = object->metaObject();
19 auto index = metaObject->indexOfMethod(method);
20 if (index != -1) {
21 auto method = metaObject->method(index);
22 return method.invoke(object, args...);
23 }
24
25 return false;
26}
27
28bool tryCall(QObject *object, QByteArrayView pageRowMethod, QByteArrayView stackViewMethod, const QVariant &page, const QVariantMap &properties)
29{
30 const auto metaObject = object->metaObject();
31
32 QByteArray name = pageRowMethod + "(QVariant,QVariant)";
33 if (auto index = metaObject->indexOfMethod(name.data()); index != -1) {
34 return metaObject->method(index).invoke(object, page, QVariant::fromValue(properties));
35 } else if (QQmlComponent *component = page.value<QQmlComponent *>(); component != nullptr) {
36 return metaObject->invokeMethod(object, stackViewMethod.data(), component, properties);
37 } else if (QQuickItem *item = page.value<QQuickItem *>(); item != nullptr) {
38 return metaObject->invokeMethod(object, stackViewMethod.data(), item, properties);
39 } else if (QUrl url = page.toUrl(); !url.isEmpty()) {
40 return metaObject->invokeMethod(object, stackViewMethod.data(), url, properties);
41 }
42
43 return false;
44}
45
46PageStackAttached::PageStackAttached(QObject *parent)
48{
49 m_parentItem = qobject_cast<QQuickItem *>(parent);
50
51 if (!m_parentItem) {
52 qCDebug(KirigamiLayoutsLog) << "PageStack must be attached to an Item" << parent;
53 return;
54 }
55
56 if (hasStackCapabilities(m_parentItem)) {
57 setPageStack(m_parentItem);
58 } else if (!m_pageStack) {
59 QQuickItem *candidate = m_parentItem->parentItem();
60 while (candidate) {
61 if (hasStackCapabilities(candidate)) {
62 qmlAttachedPropertiesObject<PageStackAttached>(candidate, true);
63 break;
64 }
65 candidate = candidate->parentItem();
66 }
67 }
68
69 initialize();
70}
71
72QQuickItem *PageStackAttached::pageStack() const
73{
74 return m_pageStack;
75}
76
77void PageStackAttached::setPageStack(QQuickItem *pageStack)
78{
79 if (!pageStack || m_pageStack == pageStack || !hasStackCapabilities(pageStack)) {
80 return;
81 }
82
83 m_customStack = true;
84 m_pageStack = pageStack;
85
86 propagatePageStack(pageStack);
87
88 Q_EMIT pageStackChanged();
89}
90
91void PageStackAttached::propagatePageStack(QQuickItem *pageStack)
92{
93 if (!pageStack) {
94 return;
95 }
96
97 if (!m_customStack && m_pageStack != pageStack) {
98 m_pageStack = pageStack;
99 Q_EMIT pageStackChanged();
100 }
101
102 const auto stacks = attachedChildren();
103 for (QQuickAttachedPropertyPropagator *child : stacks) {
104 PageStackAttached *stackAttached = qobject_cast<PageStackAttached *>(child);
105 if (stackAttached) {
106 stackAttached->propagatePageStack(m_pageStack);
107 }
108 }
109}
110
111void PageStackAttached::push(const QVariant &page, const QVariantMap &properties)
112{
113 if (!m_pageStack) {
114 qCWarning(KirigamiLayoutsLog) << "Pushing in an empty PageStackAttached";
115 return;
116 }
117
118 if (!tryCall(m_pageStack, "push", "pushItem", page, properties)) {
119 qCWarning(KirigamiLayoutsLog) << "Invalid parameters to push: " << page << properties;
120 }
121}
122
123void PageStackAttached::replace(const QVariant &page, const QVariantMap &properties)
124{
125 if (!m_pageStack) {
126 qCWarning(KirigamiLayoutsLog) << "replacing in an empty PageStackAttached";
127 return;
128 }
129
130 if (!tryCall(m_pageStack, "replace", "replaceCurrentItem", page, properties)) {
131 qCWarning(KirigamiLayoutsLog) << "Invalid parameters to replace: " << page << properties;
132 }
133}
134
135void PageStackAttached::pop(const QVariant &page)
136{
137 if (!m_pageStack) {
138 qCWarning(KirigamiLayoutsLog) << "Pushing in an empty PageStackAttached";
139 return;
140 }
141
142 if (callIfValid(m_pageStack, "pop(QVariant)", page)) {
143 return;
144 } else if (page.canConvert<QQuickItem *>() && callIfValid(m_pageStack, "popToItem(QQuickItem*)", page.value<QQuickItem *>())) {
145 return;
146 } else if (callIfValid(m_pageStack, "popCurrentItem()")) {
147 return;
148 }
149
150 qCWarning(KirigamiLayoutsLog) << "Pop operation failed on stack" << m_pageStack << "with page" << page;
151}
152
153void PageStackAttached::clear()
154{
155 if (!m_pageStack) {
156 qCWarning(KirigamiLayoutsLog) << "Clearing in an empty PageStackAttached";
157 return;
158 }
159
160 if (!callIfValid(m_pageStack, "clear()")) {
161 qCWarning(KirigamiLayoutsLog) << "Call to clear() failed";
162 }
163}
164
165bool PageStackAttached::hasStackCapabilities(QQuickItem *candidate)
166{
167 // Duck type the candidate in order to see if it can be used as a stack having the expected methods
168 auto metaObject = candidate->metaObject();
169 Q_ASSERT(metaObject);
170
171 auto hasPageRowOrStackViewMethod = [metaObject](QByteArrayView pageRowMethod, QByteArrayView stackViewMethod) -> bool {
172 // For PageRow, we require a single method that takes QVariant,QVariant as argument.
173 QByteArray name = pageRowMethod + "(QVariant,QVariant)";
174 if (metaObject->indexOfMethod(name.data()) != -1) {
175 return true;
176 }
177
178 // For StackView, we require three variants of the method.
179 name = stackViewMethod + "(QQmlComponent*,QVariantMap)";
180 if (metaObject->indexOfMethod(name.data()) == -1) {
181 return false;
182 }
183
184 name = stackViewMethod + "(QQuickItem*,QVariantMap)";
185 if (metaObject->indexOfMethod(name.data()) == -1) {
186 return false;
187 }
188
189 name = stackViewMethod + "(QUrl,QVariantMap)";
190 if (metaObject->indexOfMethod(name.data()) == -1) {
191 return false;
192 }
193
194 return true;
195 };
196
197 if (!hasPageRowOrStackViewMethod("push", "pushItem")) {
198 return false;
199 }
200
201 if (!hasPageRowOrStackViewMethod("replace", "replaceCurrentItem")) {
202 return false;
203 }
204
205 auto index = metaObject->indexOfMethod("pop(QVariant)");
206 if (index == -1) {
207 index = metaObject->indexOfMethod("popToItem(QQuickItem*)");
208 if (index == -1) {
209 return false;
210 }
211 index = metaObject->indexOfMethod("popCurrentItem()");
212 if (index == -1) {
213 return false;
214 }
215 }
216
217 index = metaObject->indexOfMethod("clear()");
218 if (index == -1) {
219 return false;
220 }
221
222 return true;
223}
224
225PageStackAttached *PageStackAttached::qmlAttachedProperties(QObject *object)
226{
227 return new PageStackAttached(object);
228}
229
230void PageStackAttached::attachedParentChange(QQuickAttachedPropertyPropagator *newParent, QQuickAttachedPropertyPropagator *oldParent)
231{
232 Q_UNUSED(oldParent);
233
234 PageStackAttached *stackAttached = qobject_cast<PageStackAttached *>(newParent);
235 if (stackAttached) {
236 propagatePageStack(stackAttached->pageStack());
237 }
238}
239
240#include "moc_pagestackattached.cpp"
This attached property makes possible to access from anywhere the page stack this page was pushed int...
KCRASH_EXPORT void initialize()
QString name(StandardAction id)
KGuiItem properties()
const_pointer data() const const
Q_EMITQ_EMIT
virtual const QMetaObject * metaObject() const const
T qobject_cast(QObject *object)
QQuickAttachedPropertyPropagator(QObject *parent)
QList< QQuickAttachedPropertyPropagator * > attachedChildren() const const
QQuickItem * parentItem() const const
QChar * data()
bool isEmpty() const const
bool canConvert() const const
QVariant fromValue(T &&value)
QUrl toUrl() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 11:53:57 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.