7 #include "pagerouter.h"
8 #include "loggingcategory.h"
11 #include <QJsonObject>
13 #include <QQmlProperty>
14 #include <QQuickWindow>
16 #include <qqmlpropertymap.h>
18 ParsedRoute *parseRoute(
QJSValue value)
21 return new ParsedRoute{};
26 map.remove(QStringLiteral(
"route"));
27 map.remove(QStringLiteral(
"data"));
29 value.
property(QStringLiteral(
"data")).toVariant(),
39 const auto valuesList =
values.toVariant().toList();
40 for (
const auto &route : valuesList) {
41 if (route.toString() !=
QString()) {
42 ret <<
new ParsedRoute{route.toString(),
QVariant(), QVariantMap(),
false,
nullptr};
43 }
else if (route.canConvert<QVariantMap>()) {
44 auto map = route.value<QVariantMap>();
46 copy.remove(QStringLiteral(
"route"));
47 copy.remove(QStringLiteral(
"data"));
49 ret <<
new ParsedRoute{
map.value(QStringLiteral(
"route")).
toString(),
map.value(QStringLiteral(
"data")), copy,
false,
nullptr};
53 ret << parseRoute(values);
64 connect(
this, &PageRouter::pageStackChanged, [=]() {
65 connect(m_pageStack, &ColumnView::currentIndexChanged,
this, &PageRouter::currentIndexChanged);
76 auto router = qobject_cast<PageRouter *>(prop->object);
77 router->m_routes.append(route);
80 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
86 auto router = qobject_cast<PageRouter *>(prop->object);
87 return router->m_routes.length();
90 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
96 auto router = qobject_cast<PageRouter *>(prop->object);
97 return router->m_routes[index];
102 auto router = qobject_cast<PageRouter *>(prop->object);
103 router->m_routes.clear();
106 PageRouter::~PageRouter()
110 void PageRouter::classBegin()
114 void PageRouter::componentComplete()
116 if (m_pageStack ==
nullptr) {
117 qCCritical(KirigamiLog)
118 <<
"PageRouter should be created with a ColumnView. Not doing so is undefined behaviour, and is likely to result in a crash upon further "
121 Q_EMIT pageStackChanged();
122 m_currentRoutes.clear();
123 push(parseRoute(initialRoute()));
127 bool PageRouter::routesContainsKey(
const QString &key)
const
129 for (
auto route : m_routes) {
130 if (route->
name() == key) {
139 for (
auto route : m_routes) {
140 if (route->
name() == key) {
147 bool PageRouter::routesCacheForKey(
const QString &key)
const
149 for (
auto route : m_routes) {
150 if (route->
name() == key) {
151 return route->
cache();
157 int PageRouter::routesCostForKey(
const QString &key)
const
159 for (
auto route : m_routes) {
160 if (route->
name() == key) {
161 return route->
cost();
172 void PageRouter::reevaluateParamMapProperties()
176 for (
auto item : m_currentRoutes) {
177 for (
auto key : item->properties.keys()) {
180 auto &value = item->properties[key];
181 m_paramMap->insert(key, value);
185 for (
auto key : m_paramMap->keys()) {
187 m_paramMap->clear(key);
192 void PageRouter::push(ParsedRoute *route)
195 if (!routesContainsKey(route->name)) {
196 qCCritical(KirigamiLog) <<
"Route" << route->name <<
"not defined";
199 if (routesCacheForKey(route->name)) {
200 auto push = [route,
this](ParsedRoute *item) {
201 m_currentRoutes << item;
203 for (
auto it = route->properties.begin(); it != route->properties.end(); it++) {
204 item->item->setProperty(qUtf8Printable(it.key()), it.value());
205 item->properties[it.key()] = it.value();
207 reevaluateParamMapProperties();
209 m_pageStack->addItem(item->item);
211 auto item = m_cache.take(qMakePair(route->name, route->hash()));
212 if (item && item->item) {
216 item = m_preload.take(qMakePair(route->name, route->hash()));
217 if (item && item->item) {
222 auto context = qmlContext(
this);
223 auto component = routesValueForKey(route->name);
224 auto createAndPush = [component, context, route,
this]() {
228 auto item = component->beginCreate(context);
229 if (item ==
nullptr) {
232 item->setParent(
this);
233 auto qqItem = qobject_cast<QQuickItem *>(item);
235 qCCritical(KirigamiLog) <<
"Route" << route->name <<
"is not an item! This is undefined behaviour and will likely crash your application.";
237 for (
auto it = route->properties.begin(); it != route->properties.end(); it++) {
238 qqItem->setProperty(qUtf8Printable(it.key()), it.value());
240 route->setItem(qqItem);
241 route->cache = routesCacheForKey(route->name);
242 m_currentRoutes << route;
243 reevaluateParamMapProperties();
245 auto attached = qobject_cast<PageRouterAttached *>(qmlAttachedPropertiesObject<PageRouter>(item,
true));
246 attached->m_router =
this;
247 component->completeCreate();
248 m_pageStack->addItem(qqItem);
249 m_pageStack->setCurrentIndex(m_currentRoutes.length() - 1);
258 qCCritical(KirigamiLog) <<
"Failed to push route:" << component->errors();
263 qCCritical(KirigamiLog) <<
"Failed to push route:" << component->errors();
269 return m_initialRoute;
272 void PageRouter::setInitialRoute(
QJSValue value)
274 m_initialRoute = value;
279 auto incomingRoutes = parseRoutes(route);
282 if (incomingRoutes.length() <= m_currentRoutes.length()) {
283 resolvedRoutes = m_currentRoutes.
mid(0, incomingRoutes.length());
285 resolvedRoutes = m_currentRoutes;
286 resolvedRoutes.
reserve(incomingRoutes.length() - m_currentRoutes.length());
289 for (
int i = 0; i < incomingRoutes.length(); i++) {
290 auto incoming = incomingRoutes.at(i);
292 if (i >= resolvedRoutes.
length()) {
293 resolvedRoutes.
append(incoming);
295 auto current = resolvedRoutes.
value(i);
297 auto props = incoming->properties;
298 if (current->name != incoming->name || current->data != incoming->data) {
299 resolvedRoutes.
replace(i, incoming);
301 resolvedRoutes[i]->properties.
clear();
302 for (
auto it = props.constBegin(); it != props.constEnd(); it++) {
303 resolvedRoutes[i]->properties.
insert(it.key(), it.value());
308 for (
const auto &route : std::as_const(m_currentRoutes)) {
309 if (!resolvedRoutes.
contains(route)) {
314 m_pageStack->clear();
315 m_currentRoutes.clear();
316 for (
auto toPush : std::as_const(resolvedRoutes)) {
319 reevaluateParamMapProperties();
320 Q_EMIT navigationChanged();
327 m_pageStack->setCurrentIndex(index);
329 auto parsed = parseRoute(route);
331 for (
auto currentRoute : std::as_const(m_currentRoutes)) {
332 if (currentRoute->name == parsed->name && currentRoute->data == parsed->data) {
333 m_pageStack->setCurrentIndex(index);
338 qCWarning(KirigamiLog) <<
"Route" << parsed->name <<
"with data" << parsed->data <<
"is not on the current stack of routes.";
344 auto parsed = parseRoutes(route);
345 if (parsed.length() > m_currentRoutes.length()) {
348 for (
int i = 0; i < parsed.length(); i++) {
349 if (parsed[i]->name != m_currentRoutes[i]->name) {
352 if (parsed[i]->data.isValid()) {
353 if (parsed[i]->data != m_currentRoutes[i]->data) {
363 push(parseRoute(route));
364 Q_EMIT navigationChanged();
369 m_pageStack->pop(m_currentRoutes.last()->item);
370 placeInCache(m_currentRoutes.last());
371 m_currentRoutes.removeLast();
372 reevaluateParamMapProperties();
373 Q_EMIT navigationChanged();
378 auto pointer = object;
379 auto qqiPointer = qobject_cast<QQuickItem *>(
object);
381 for (
auto route : std::as_const(m_cache.items)) {
382 routes[route->item] = route;
384 for (
auto route : std::as_const(m_preload.items)) {
385 routes[route->item] = route;
387 for (
auto route : std::as_const(m_currentRoutes)) {
388 routes[route->item] = route;
390 while (qqiPointer !=
nullptr) {
391 const auto keys = routes.
keys();
392 for (
auto item : keys) {
393 if (item == qqiPointer) {
394 return routes[item]->data;
397 qqiPointer = qqiPointer->parentItem();
399 while (pointer !=
nullptr) {
400 const auto keys = routes.
keys();
401 for (
auto item : keys) {
402 if (item == pointer) {
403 return routes[item]->data;
406 pointer = pointer->parent();
411 bool PageRouter::isActive(
QObject *
object)
413 auto pointer = object;
414 while (pointer !=
nullptr) {
416 for (
auto route : std::as_const(m_currentRoutes)) {
417 if (route->item == pointer) {
418 return m_pageStack->currentIndex() == index;
422 pointer = pointer->
parent();
424 qCWarning(KirigamiLog) <<
"Object" <<
object <<
"not in current routes";
445 while (parent !=
nullptr) {
447 climbObjectParents(out, parent);
448 parent = parent->parentItem();
453 auto parent =
object->
parent();
454 while (parent !=
nullptr) {
460 if (parent->metaObject()->inherits(metaObject)) {
461 climbItemParents(out,
reinterpret_cast<QQuickItem *
>(parent));
463 parent = parent->parent();
468 if (qobject_cast<QQuickItem *>(
object)) {
469 climber.climbItemParents(ret, qobject_cast<QQuickItem *>(
object));
471 climber.climbObjectParents(ret,
object);
475 void PageRouter::preload(ParsedRoute *route)
477 for (
auto preloaded : std::as_const(m_preload.items)) {
478 if (preloaded->equals(route)) {
483 if (!routesContainsKey(route->name)) {
484 qCCritical(KirigamiLog) <<
"Route" << route->name <<
"not defined";
488 auto context = qmlContext(
this);
489 auto component = routesValueForKey(route->name);
490 auto createAndCache = [component, context, route,
this]() {
491 auto item = component->beginCreate(context);
493 auto qqItem = qobject_cast<QQuickItem *>(item);
495 qCCritical(KirigamiLog) <<
"Route" << route->name <<
"is not an item! This is undefined behaviour and will likely crash your application.";
497 for (
auto it = route->properties.begin(); it != route->properties.end(); it++) {
498 qqItem->setProperty(qUtf8Printable(it.key()), it.value());
500 route->setItem(qqItem);
501 route->cache = routesCacheForKey(route->name);
502 auto attached = qobject_cast<PageRouterAttached *>(qmlAttachedPropertiesObject<PageRouter>(item,
true));
503 attached->m_router =
this;
504 component->completeCreate();
506 qCCritical(KirigamiLog) <<
"Route" << route->name <<
"is being preloaded despite it not having caching enabled.";
510 auto string = route->name;
511 auto hash = route->hash();
512 m_preload.insert(qMakePair(
string, hash), route, routesCostForKey(route->name));
521 qCCritical(KirigamiLog) <<
"Failed to push route:" << component->errors();
526 qCCritical(KirigamiLog) <<
"Failed to push route:" << component->errors();
530 void PageRouter::unpreload(ParsedRoute *route)
532 ParsedRoute *toDelete =
nullptr;
533 for (
auto preloaded : std::as_const(m_preload.items)) {
534 if (preloaded->equals(route)) {
535 toDelete = preloaded;
538 if (toDelete !=
nullptr) {
539 m_preload.take(qMakePair(toDelete->name, toDelete->hash()));
545 void PreloadRouteGroup::handleChange()
547 if (!(m_parent->m_router)) {
548 qCCritical(KirigamiLog) <<
"PreloadRouteGroup does not have a parent PageRouter";
551 auto r = m_parent->m_router;
552 auto parsed = parseRoute(m_route);
556 r->unpreload(parsed);
560 PreloadRouteGroup::~PreloadRouteGroup()
562 if (m_parent->m_router) {
563 m_parent->m_router->unpreload(parseRoute(m_route));
567 void PageRouterAttached::findParent()
569 QQuickItem *parent = qobject_cast<QQuickItem *>(this->parent());
570 while (parent !=
nullptr) {
571 auto attached = qobject_cast<PageRouterAttached *>(qmlAttachedPropertiesObject<PageRouter>(parent,
false));
572 if (attached !=
nullptr && attached->m_router !=
nullptr) {
573 m_router = attached->m_router;
574 Q_EMIT routerChanged();
575 Q_EMIT dataChanged();
576 Q_EMIT isCurrentChanged();
577 Q_EMIT navigationChanged();
587 m_router->navigateToRoute(route);
589 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
597 return m_router->routeActive(route);
599 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
607 m_router->pushRoute(route);
609 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
617 m_router->popRoute();
619 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
624 void PageRouterAttached::bringToView(
QJSValue route)
627 m_router->bringToView(route);
629 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
637 return m_router->dataFor(parent());
639 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
647 return m_router->isActive(parent());
649 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
657 return m_router->routeActive(m_watchedRoute);
659 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
664 void PageRouterAttached::setWatchedRoute(
QJSValue route)
666 m_watchedRoute = route;
667 Q_EMIT watchedRouteChanged();
672 return m_watchedRoute;
678 m_router->pushFromObject(parent(), route);
680 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
687 m_router->pushFromObject(parent(), route,
true);
689 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
696 m_router->pushFromObject(parent(),
QJSValue());
698 qCCritical(KirigamiLog) <<
"PageRouterAttached does not have a parent PageRouter";
702 void PageRouter::placeInCache(ParsedRoute *route)
709 auto string = route->name;
710 auto hash = route->hash();
711 m_cache.insert(qMakePair(
string, hash), route, routesCostForKey(route->name));
714 void PageRouter::pushFromObject(
QObject *
object,
QJSValue inputRoute,
bool replace)
716 const auto parsed = parseRoutes(inputRoute);
717 const auto objects = flatParentTree(
object);
719 for (
const auto &obj : objects) {
720 bool popping =
false;
721 for (
auto route : std::as_const(m_currentRoutes)) {
723 m_currentRoutes.removeAll(route);
724 reevaluateParamMapProperties();
728 if (route->item == obj) {
729 m_pageStack->pop(route->item);
731 m_currentRoutes.removeAll(route);
732 reevaluateParamMapProperties();
733 m_pageStack->removeItem(route->item);
740 for (
auto route : parsed) {
744 Q_EMIT navigationChanged();
748 qCWarning(KirigamiLog) <<
"Object" <<
object <<
"not in current routes";
753 auto engine = qjsEngine(
this);
754 auto ret = engine->newArray(m_currentRoutes.length());
755 for (
int i = 0; i < m_currentRoutes.length(); ++i) {
756 auto object = engine->newObject();
757 object.setProperty(QStringLiteral(
"route"), m_currentRoutes[i]->name);
758 object.setProperty(QStringLiteral(
"data"), engine->toScriptValue(m_currentRoutes[i]->data));
759 auto keys = m_currentRoutes[i]->properties.keys();
760 for (
auto key : keys) {
761 object.setProperty(key, engine->toScriptValue(m_currentRoutes[i]->properties[key]));
763 ret.setProperty(i,
object);
768 PageRouterAttached::PageRouterAttached(
QObject *parent)
773 auto item = qobject_cast<QQuickItem *>(
parent);
774 if (item !=
nullptr) {