File indexing completed on 2024-04-28 15:27:43

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"