File indexing completed on 2024-05-05 12:19:34
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "pagerouter.h" 0008 #include "loggingcategory.h" 0009 #include <QJSEngine> 0010 #include <QJSValue> 0011 #include <QJsonObject> 0012 #include <QJsonValue> 0013 #include <QQmlProperty> 0014 #include <QQuickWindow> 0015 #include <QTimer> 0016 #include <qqmlpropertymap.h> 0017 0018 ParsedRoute *parseRoute(QJSValue value) 0019 { 0020 if (value.isUndefined()) { 0021 return new ParsedRoute{}; 0022 } else if (value.isString()) { 0023 return new ParsedRoute{value.toString(), QVariant()}; 0024 } else { 0025 auto map = value.toVariant().value<QVariantMap>(); 0026 map.remove(QStringLiteral("route")); 0027 map.remove(QStringLiteral("data")); 0028 return new ParsedRoute{value.property(QStringLiteral("route")).toString(), // 0029 value.property(QStringLiteral("data")).toVariant(), 0030 map, 0031 false}; 0032 } 0033 } 0034 0035 QList<ParsedRoute *> parseRoutes(QJSValue values) 0036 { 0037 QList<ParsedRoute *> ret; 0038 if (values.isArray()) { 0039 const auto valuesList = values.toVariant().toList(); 0040 for (const auto &route : valuesList) { 0041 if (route.toString() != QString()) { 0042 ret << new ParsedRoute{route.toString(), QVariant(), QVariantMap(), false, nullptr}; 0043 } else if (route.canConvert<QVariantMap>()) { 0044 auto map = route.value<QVariantMap>(); 0045 auto copy = map; 0046 copy.remove(QStringLiteral("route")); 0047 copy.remove(QStringLiteral("data")); 0048 0049 ret << new ParsedRoute{map.value(QStringLiteral("route")).toString(), map.value(QStringLiteral("data")), copy, false, nullptr}; 0050 } 0051 } 0052 } else { 0053 ret << parseRoute(values); 0054 } 0055 return ret; 0056 } 0057 0058 PageRouter::PageRouter(QQuickItem *parent) 0059 : QObject(parent) 0060 , m_paramMap(new QQmlPropertyMap) 0061 , m_cache() 0062 , m_preload() 0063 { 0064 connect(this, &PageRouter::pageStackChanged, [=]() { 0065 connect(m_pageStack, &ColumnView::currentIndexChanged, this, &PageRouter::currentIndexChanged); 0066 }); 0067 } 0068 0069 QQmlListProperty<PageRoute> PageRouter::routes() 0070 { 0071 return QQmlListProperty<PageRoute>(this, nullptr, appendRoute, routeCount, route, clearRoutes); 0072 } 0073 0074 void PageRouter::appendRoute(QQmlListProperty<PageRoute> *prop, PageRoute *route) 0075 { 0076 auto router = qobject_cast<PageRouter *>(prop->object); 0077 router->m_routes.append(route); 0078 } 0079 0080 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0081 int PageRouter::routeCount(QQmlListProperty<PageRoute> *prop) 0082 #else 0083 qsizetype PageRouter::routeCount(QQmlListProperty<PageRoute> *prop) 0084 #endif 0085 { 0086 auto router = qobject_cast<PageRouter *>(prop->object); 0087 return router->m_routes.length(); 0088 } 0089 0090 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0091 PageRoute *PageRouter::route(QQmlListProperty<PageRoute> *prop, int index) 0092 #else 0093 PageRoute *PageRouter::route(QQmlListProperty<PageRoute> *prop, qsizetype index) 0094 #endif 0095 { 0096 auto router = qobject_cast<PageRouter *>(prop->object); 0097 return router->m_routes[index]; 0098 } 0099 0100 void PageRouter::clearRoutes(QQmlListProperty<PageRoute> *prop) 0101 { 0102 auto router = qobject_cast<PageRouter *>(prop->object); 0103 router->m_routes.clear(); 0104 } 0105 0106 PageRouter::~PageRouter() 0107 { 0108 } 0109 0110 void PageRouter::classBegin() 0111 { 0112 } 0113 0114 void PageRouter::componentComplete() 0115 { 0116 if (m_pageStack == nullptr) { 0117 qCCritical(KirigamiLog) 0118 << "PageRouter should be created with a ColumnView. Not doing so is undefined behaviour, and is likely to result in a crash upon further " 0119 "interaction."; 0120 } else { 0121 Q_EMIT pageStackChanged(); 0122 m_currentRoutes.clear(); 0123 push(parseRoute(initialRoute())); 0124 } 0125 } 0126 0127 bool PageRouter::routesContainsKey(const QString &key) const 0128 { 0129 for (auto route : m_routes) { 0130 if (route->name() == key) { 0131 return true; 0132 } 0133 } 0134 return false; 0135 } 0136 0137 QQmlComponent *PageRouter::routesValueForKey(const QString &key) const 0138 { 0139 for (auto route : m_routes) { 0140 if (route->name() == key) { 0141 return route->component(); 0142 } 0143 } 0144 return nullptr; 0145 } 0146 0147 bool PageRouter::routesCacheForKey(const QString &key) const 0148 { 0149 for (auto route : m_routes) { 0150 if (route->name() == key) { 0151 return route->cache(); 0152 } 0153 } 0154 return false; 0155 } 0156 0157 int PageRouter::routesCostForKey(const QString &key) const 0158 { 0159 for (auto route : m_routes) { 0160 if (route->name() == key) { 0161 return route->cost(); 0162 } 0163 } 0164 return -1; 0165 } 0166 0167 // It would be nice if this could surgically update the 0168 // param map instead of doing this brute force approach, 0169 // but this seems to work well enough, and prematurely 0170 // optimising stuff is pretty bad if it isn't found as 0171 // a performance bottleneck. 0172 void PageRouter::reevaluateParamMapProperties() 0173 { 0174 QStringList currentKeys; 0175 0176 for (auto item : m_currentRoutes) { 0177 for (auto key : item->properties.keys()) { 0178 currentKeys << key; 0179 0180 auto &value = item->properties[key]; 0181 m_paramMap->insert(key, value); 0182 } 0183 } 0184 0185 for (auto key : m_paramMap->keys()) { 0186 if (!currentKeys.contains(key)) { 0187 m_paramMap->clear(key); 0188 } 0189 } 0190 } 0191 0192 void PageRouter::push(ParsedRoute *route) 0193 { 0194 Q_ASSERT(route); 0195 if (!routesContainsKey(route->name)) { 0196 qCCritical(KirigamiLog) << "Route" << route->name << "not defined"; 0197 return; 0198 } 0199 if (routesCacheForKey(route->name)) { 0200 auto push = [route, this](ParsedRoute *item) { 0201 m_currentRoutes << item; 0202 0203 for (auto it = route->properties.begin(); it != route->properties.end(); it++) { 0204 item->item->setProperty(qUtf8Printable(it.key()), it.value()); 0205 item->properties[it.key()] = it.value(); 0206 } 0207 reevaluateParamMapProperties(); 0208 0209 m_pageStack->addItem(item->item); 0210 }; 0211 auto item = m_cache.take(qMakePair(route->name, route->hash())); 0212 if (item && item->item) { 0213 push(item); 0214 return; 0215 } 0216 item = m_preload.take(qMakePair(route->name, route->hash())); 0217 if (item && item->item) { 0218 push(item); 0219 return; 0220 } 0221 } 0222 auto context = qmlContext(this); 0223 auto component = routesValueForKey(route->name); 0224 auto createAndPush = [component, context, route, this]() { 0225 // We use beginCreate and completeCreate to allow 0226 // for a PageRouterAttached to find its parent 0227 // on construction time. 0228 auto item = component->beginCreate(context); 0229 if (item == nullptr) { 0230 return; 0231 } 0232 item->setParent(this); 0233 auto qqItem = qobject_cast<QQuickItem *>(item); 0234 if (!qqItem) { 0235 qCCritical(KirigamiLog) << "Route" << route->name << "is not an item! This is undefined behaviour and will likely crash your application."; 0236 } 0237 for (auto it = route->properties.begin(); it != route->properties.end(); it++) { 0238 qqItem->setProperty(qUtf8Printable(it.key()), it.value()); 0239 } 0240 route->setItem(qqItem); 0241 route->cache = routesCacheForKey(route->name); 0242 m_currentRoutes << route; 0243 reevaluateParamMapProperties(); 0244 0245 auto attached = qobject_cast<PageRouterAttached *>(qmlAttachedPropertiesObject<PageRouter>(item, true)); 0246 attached->m_router = this; 0247 component->completeCreate(); 0248 m_pageStack->addItem(qqItem); 0249 m_pageStack->setCurrentIndex(m_currentRoutes.length() - 1); 0250 }; 0251 0252 if (component->status() == QQmlComponent::Ready) { 0253 createAndPush(); 0254 } else if (component->status() == QQmlComponent::Loading) { 0255 connect(component, &QQmlComponent::statusChanged, [=](QQmlComponent::Status status) { 0256 // Loading can only go to Ready or Error. 0257 if (status != QQmlComponent::Ready) { 0258 qCCritical(KirigamiLog) << "Failed to push route:" << component->errors(); 0259 } 0260 createAndPush(); 0261 }); 0262 } else { 0263 qCCritical(KirigamiLog) << "Failed to push route:" << component->errors(); 0264 } 0265 } 0266 0267 QJSValue PageRouter::initialRoute() const 0268 { 0269 return m_initialRoute; 0270 } 0271 0272 void PageRouter::setInitialRoute(QJSValue value) 0273 { 0274 m_initialRoute = value; 0275 } 0276 0277 void PageRouter::navigateToRoute(QJSValue route) 0278 { 0279 auto incomingRoutes = parseRoutes(route); 0280 QList<ParsedRoute *> resolvedRoutes; 0281 0282 if (incomingRoutes.length() <= m_currentRoutes.length()) { 0283 resolvedRoutes = m_currentRoutes.mid(0, incomingRoutes.length()); 0284 } else { 0285 resolvedRoutes = m_currentRoutes; 0286 resolvedRoutes.reserve(incomingRoutes.length() - m_currentRoutes.length()); 0287 } 0288 0289 for (int i = 0; i < incomingRoutes.length(); i++) { 0290 auto incoming = incomingRoutes.at(i); 0291 Q_ASSERT(incoming); 0292 if (i >= resolvedRoutes.length()) { 0293 resolvedRoutes.append(incoming); 0294 } else { 0295 auto current = resolvedRoutes.value(i); 0296 Q_ASSERT(current); 0297 auto props = incoming->properties; 0298 if (current->name != incoming->name || current->data != incoming->data) { 0299 resolvedRoutes.replace(i, incoming); 0300 } 0301 resolvedRoutes[i]->properties.clear(); 0302 for (auto it = props.constBegin(); it != props.constEnd(); it++) { 0303 resolvedRoutes[i]->properties.insert(it.key(), it.value()); 0304 } 0305 } 0306 } 0307 0308 for (const auto &route : std::as_const(m_currentRoutes)) { 0309 if (!resolvedRoutes.contains(route)) { 0310 placeInCache(route); 0311 } 0312 } 0313 0314 m_pageStack->clear(); 0315 m_currentRoutes.clear(); 0316 for (auto toPush : std::as_const(resolvedRoutes)) { 0317 push(toPush); 0318 } 0319 reevaluateParamMapProperties(); 0320 Q_EMIT navigationChanged(); 0321 } 0322 0323 void PageRouter::bringToView(QJSValue route) 0324 { 0325 if (route.isNumber()) { 0326 auto index = route.toNumber(); 0327 m_pageStack->setCurrentIndex(index); 0328 } else { 0329 auto parsed = parseRoute(route); 0330 auto index = 0; 0331 for (auto currentRoute : std::as_const(m_currentRoutes)) { 0332 if (currentRoute->name == parsed->name && currentRoute->data == parsed->data) { 0333 m_pageStack->setCurrentIndex(index); 0334 return; 0335 } 0336 index++; 0337 } 0338 qCWarning(KirigamiLog) << "Route" << parsed->name << "with data" << parsed->data << "is not on the current stack of routes."; 0339 } 0340 } 0341 0342 bool PageRouter::routeActive(QJSValue route) 0343 { 0344 auto parsed = parseRoutes(route); 0345 if (parsed.length() > m_currentRoutes.length()) { 0346 return false; 0347 } 0348 for (int i = 0; i < parsed.length(); i++) { 0349 if (parsed[i]->name != m_currentRoutes[i]->name) { 0350 return false; 0351 } 0352 if (parsed[i]->data.isValid()) { 0353 if (parsed[i]->data != m_currentRoutes[i]->data) { 0354 return false; 0355 } 0356 } 0357 } 0358 return true; 0359 } 0360 0361 void PageRouter::pushRoute(QJSValue route) 0362 { 0363 push(parseRoute(route)); 0364 Q_EMIT navigationChanged(); 0365 } 0366 0367 void PageRouter::popRoute() 0368 { 0369 m_pageStack->pop(m_currentRoutes.last()->item); 0370 placeInCache(m_currentRoutes.last()); 0371 m_currentRoutes.removeLast(); 0372 reevaluateParamMapProperties(); 0373 Q_EMIT navigationChanged(); 0374 } 0375 0376 QVariant PageRouter::dataFor(QObject *object) 0377 { 0378 auto pointer = object; 0379 auto qqiPointer = qobject_cast<QQuickItem *>(object); 0380 QHash<QQuickItem *, ParsedRoute *> routes; 0381 for (auto route : std::as_const(m_cache.items)) { 0382 routes[route->item] = route; 0383 } 0384 for (auto route : std::as_const(m_preload.items)) { 0385 routes[route->item] = route; 0386 } 0387 for (auto route : std::as_const(m_currentRoutes)) { 0388 routes[route->item] = route; 0389 } 0390 while (qqiPointer != nullptr) { 0391 const auto keys = routes.keys(); 0392 for (auto item : keys) { 0393 if (item == qqiPointer) { 0394 return routes[item]->data; 0395 } 0396 } 0397 qqiPointer = qqiPointer->parentItem(); 0398 } 0399 while (pointer != nullptr) { 0400 const auto keys = routes.keys(); 0401 for (auto item : keys) { 0402 if (item == pointer) { 0403 return routes[item]->data; 0404 } 0405 } 0406 pointer = pointer->parent(); 0407 } 0408 return QVariant(); 0409 } 0410 0411 bool PageRouter::isActive(QObject *object) 0412 { 0413 auto pointer = object; 0414 while (pointer != nullptr) { 0415 auto index = 0; 0416 for (auto route : std::as_const(m_currentRoutes)) { 0417 if (route->item == pointer) { 0418 return m_pageStack->currentIndex() == index; 0419 } 0420 index++; 0421 } 0422 pointer = pointer->parent(); 0423 } 0424 qCWarning(KirigamiLog) << "Object" << object << "not in current routes"; 0425 return false; 0426 } 0427 0428 PageRouterAttached *PageRouter::qmlAttachedProperties(QObject *object) 0429 { 0430 auto attached = new PageRouterAttached(object); 0431 return attached; 0432 } 0433 0434 QSet<QObject *> flatParentTree(QObject *object) 0435 { 0436 // See below comment in Climber::climbObjectParents for why this is here. 0437 static const QMetaObject *metaObject = QMetaType::metaObjectForType(QMetaType::type("QQuickItem*")); 0438 QSet<QObject *> ret; 0439 // Use an inline struct type so that climbItemParents and climbObjectParents 0440 // can call each other 0441 struct Climber { 0442 void climbItemParents(QSet<QObject *> &out, QQuickItem *item) 0443 { 0444 auto parent = item->parentItem(); 0445 while (parent != nullptr) { 0446 out << parent; 0447 climbObjectParents(out, parent); 0448 parent = parent->parentItem(); 0449 } 0450 } 0451 void climbObjectParents(QSet<QObject *> &out, QObject *object) 0452 { 0453 auto parent = object->parent(); 0454 while (parent != nullptr) { 0455 out << parent; 0456 // We manually call metaObject()->inherits() and 0457 // use a reinterpret cast because qobject_cast seems 0458 // to have stability issues here due to mutable 0459 // pointer mechanics. 0460 if (parent->metaObject()->inherits(metaObject)) { 0461 climbItemParents(out, reinterpret_cast<QQuickItem *>(parent)); 0462 } 0463 parent = parent->parent(); 0464 } 0465 } 0466 }; 0467 Climber climber; 0468 if (qobject_cast<QQuickItem *>(object)) { 0469 climber.climbItemParents(ret, qobject_cast<QQuickItem *>(object)); 0470 } 0471 climber.climbObjectParents(ret, object); 0472 return ret; 0473 } 0474 0475 void PageRouter::preload(ParsedRoute *route) 0476 { 0477 for (auto preloaded : std::as_const(m_preload.items)) { 0478 if (preloaded->equals(route)) { 0479 delete route; 0480 return; 0481 } 0482 } 0483 if (!routesContainsKey(route->name)) { 0484 qCCritical(KirigamiLog) << "Route" << route->name << "not defined"; 0485 delete route; 0486 return; 0487 } 0488 auto context = qmlContext(this); 0489 auto component = routesValueForKey(route->name); 0490 auto createAndCache = [component, context, route, this]() { 0491 auto item = component->beginCreate(context); 0492 item->setParent(this); 0493 auto qqItem = qobject_cast<QQuickItem *>(item); 0494 if (!qqItem) { 0495 qCCritical(KirigamiLog) << "Route" << route->name << "is not an item! This is undefined behaviour and will likely crash your application."; 0496 } 0497 for (auto it = route->properties.begin(); it != route->properties.end(); it++) { 0498 qqItem->setProperty(qUtf8Printable(it.key()), it.value()); 0499 } 0500 route->setItem(qqItem); 0501 route->cache = routesCacheForKey(route->name); 0502 auto attached = qobject_cast<PageRouterAttached *>(qmlAttachedPropertiesObject<PageRouter>(item, true)); 0503 attached->m_router = this; 0504 component->completeCreate(); 0505 if (!route->cache) { 0506 qCCritical(KirigamiLog) << "Route" << route->name << "is being preloaded despite it not having caching enabled."; 0507 delete route; 0508 return; 0509 } 0510 auto string = route->name; 0511 auto hash = route->hash(); 0512 m_preload.insert(qMakePair(string, hash), route, routesCostForKey(route->name)); 0513 }; 0514 0515 if (component->status() == QQmlComponent::Ready) { 0516 createAndCache(); 0517 } else if (component->status() == QQmlComponent::Loading) { 0518 connect(component, &QQmlComponent::statusChanged, [=](QQmlComponent::Status status) { 0519 // Loading can only go to Ready or Error. 0520 if (status != QQmlComponent::Ready) { 0521 qCCritical(KirigamiLog) << "Failed to push route:" << component->errors(); 0522 } 0523 createAndCache(); 0524 }); 0525 } else { 0526 qCCritical(KirigamiLog) << "Failed to push route:" << component->errors(); 0527 } 0528 } 0529 0530 void PageRouter::unpreload(ParsedRoute *route) 0531 { 0532 ParsedRoute *toDelete = nullptr; 0533 for (auto preloaded : std::as_const(m_preload.items)) { 0534 if (preloaded->equals(route)) { 0535 toDelete = preloaded; 0536 } 0537 } 0538 if (toDelete != nullptr) { 0539 m_preload.take(qMakePair(toDelete->name, toDelete->hash())); 0540 delete toDelete; 0541 } 0542 delete route; 0543 } 0544 0545 void PreloadRouteGroup::handleChange() 0546 { 0547 if (!(m_parent->m_router)) { 0548 qCCritical(KirigamiLog) << "PreloadRouteGroup does not have a parent PageRouter"; 0549 return; 0550 } 0551 auto r = m_parent->m_router; 0552 auto parsed = parseRoute(m_route); 0553 if (m_when) { 0554 r->preload(parsed); 0555 } else { 0556 r->unpreload(parsed); 0557 } 0558 } 0559 0560 PreloadRouteGroup::~PreloadRouteGroup() 0561 { 0562 if (m_parent->m_router) { 0563 m_parent->m_router->unpreload(parseRoute(m_route)); 0564 } 0565 } 0566 0567 void PageRouterAttached::findParent() 0568 { 0569 QQuickItem *parent = qobject_cast<QQuickItem *>(this->parent()); 0570 while (parent != nullptr) { 0571 auto attached = qobject_cast<PageRouterAttached *>(qmlAttachedPropertiesObject<PageRouter>(parent, false)); 0572 if (attached != nullptr && attached->m_router != nullptr) { 0573 m_router = attached->m_router; 0574 Q_EMIT routerChanged(); 0575 Q_EMIT dataChanged(); 0576 Q_EMIT isCurrentChanged(); 0577 Q_EMIT navigationChanged(); 0578 break; 0579 } 0580 parent = parent->parentItem(); 0581 } 0582 } 0583 0584 void PageRouterAttached::navigateToRoute(QJSValue route) 0585 { 0586 if (m_router) { 0587 m_router->navigateToRoute(route); 0588 } else { 0589 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0590 return; 0591 } 0592 } 0593 0594 bool PageRouterAttached::routeActive(QJSValue route) 0595 { 0596 if (m_router) { 0597 return m_router->routeActive(route); 0598 } else { 0599 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0600 return false; 0601 } 0602 } 0603 0604 void PageRouterAttached::pushRoute(QJSValue route) 0605 { 0606 if (m_router) { 0607 m_router->pushRoute(route); 0608 } else { 0609 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0610 return; 0611 } 0612 } 0613 0614 void PageRouterAttached::popRoute() 0615 { 0616 if (m_router) { 0617 m_router->popRoute(); 0618 } else { 0619 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0620 return; 0621 } 0622 } 0623 0624 void PageRouterAttached::bringToView(QJSValue route) 0625 { 0626 if (m_router) { 0627 m_router->bringToView(route); 0628 } else { 0629 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0630 return; 0631 } 0632 } 0633 0634 QVariant PageRouterAttached::data() const 0635 { 0636 if (m_router) { 0637 return m_router->dataFor(parent()); 0638 } else { 0639 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0640 return QVariant(); 0641 } 0642 } 0643 0644 bool PageRouterAttached::isCurrent() const 0645 { 0646 if (m_router) { 0647 return m_router->isActive(parent()); 0648 } else { 0649 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0650 return false; 0651 } 0652 } 0653 0654 bool PageRouterAttached::watchedRouteActive() 0655 { 0656 if (m_router) { 0657 return m_router->routeActive(m_watchedRoute); 0658 } else { 0659 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0660 return false; 0661 } 0662 } 0663 0664 void PageRouterAttached::setWatchedRoute(QJSValue route) 0665 { 0666 m_watchedRoute = route; 0667 Q_EMIT watchedRouteChanged(); 0668 } 0669 0670 QJSValue PageRouterAttached::watchedRoute() 0671 { 0672 return m_watchedRoute; 0673 } 0674 0675 void PageRouterAttached::pushFromHere(QJSValue route) 0676 { 0677 if (m_router) { 0678 m_router->pushFromObject(parent(), route); 0679 } else { 0680 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0681 } 0682 } 0683 0684 void PageRouterAttached::replaceFromHere(QJSValue route) 0685 { 0686 if (m_router) { 0687 m_router->pushFromObject(parent(), route, true); 0688 } else { 0689 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0690 } 0691 } 0692 0693 void PageRouterAttached::popFromHere() 0694 { 0695 if (m_router) { 0696 m_router->pushFromObject(parent(), QJSValue()); 0697 } else { 0698 qCCritical(KirigamiLog) << "PageRouterAttached does not have a parent PageRouter"; 0699 } 0700 } 0701 0702 void PageRouter::placeInCache(ParsedRoute *route) 0703 { 0704 Q_ASSERT(route); 0705 if (!route->cache) { 0706 delete route; 0707 return; 0708 } 0709 auto string = route->name; 0710 auto hash = route->hash(); 0711 m_cache.insert(qMakePair(string, hash), route, routesCostForKey(route->name)); 0712 } 0713 0714 void PageRouter::pushFromObject(QObject *object, QJSValue inputRoute, bool replace) 0715 { 0716 const auto parsed = parseRoutes(inputRoute); 0717 const auto objects = flatParentTree(object); 0718 0719 for (const auto &obj : objects) { 0720 bool popping = false; 0721 for (auto route : std::as_const(m_currentRoutes)) { 0722 if (popping) { 0723 m_currentRoutes.removeAll(route); 0724 reevaluateParamMapProperties(); 0725 placeInCache(route); 0726 continue; 0727 } 0728 if (route->item == obj) { 0729 m_pageStack->pop(route->item); 0730 if (replace) { 0731 m_currentRoutes.removeAll(route); 0732 reevaluateParamMapProperties(); 0733 m_pageStack->removeItem(route->item); 0734 } 0735 popping = true; 0736 } 0737 } 0738 if (popping) { 0739 if (!inputRoute.isUndefined()) { 0740 for (auto route : parsed) { 0741 push(route); 0742 } 0743 } 0744 Q_EMIT navigationChanged(); 0745 return; 0746 } 0747 } 0748 qCWarning(KirigamiLog) << "Object" << object << "not in current routes"; 0749 } 0750 0751 QJSValue PageRouter::currentRoutes() const 0752 { 0753 auto engine = qjsEngine(this); 0754 auto ret = engine->newArray(m_currentRoutes.length()); 0755 for (int i = 0; i < m_currentRoutes.length(); ++i) { 0756 auto object = engine->newObject(); 0757 object.setProperty(QStringLiteral("route"), m_currentRoutes[i]->name); 0758 object.setProperty(QStringLiteral("data"), engine->toScriptValue(m_currentRoutes[i]->data)); 0759 auto keys = m_currentRoutes[i]->properties.keys(); 0760 for (auto key : keys) { 0761 object.setProperty(key, engine->toScriptValue(m_currentRoutes[i]->properties[key])); 0762 } 0763 ret.setProperty(i, object); 0764 } 0765 return ret; 0766 } 0767 0768 PageRouterAttached::PageRouterAttached(QObject *parent) 0769 : QObject(parent) 0770 , m_preload(new PreloadRouteGroup(this)) 0771 { 0772 findParent(); 0773 auto item = qobject_cast<QQuickItem *>(parent); 0774 if (item != nullptr) { 0775 connect(item, &QQuickItem::windowChanged, this, [this]() { 0776 findParent(); 0777 }); 0778 connect(item, &QQuickItem::parentChanged, this, [this]() { 0779 findParent(); 0780 }); 0781 } 0782 } 0783 0784 #include "moc_pagerouter.cpp"