File indexing completed on 2024-05-12 04:42:09

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "mapitem.h"
0008 #include "osmelement.h"
0009 
0010 #include <KOSMIndoorMap/HitDetector>
0011 #include <KOSMIndoorMap/MapCSSParser>
0012 #include <KOSMIndoorMap/OverlaySource>
0013 
0014 #include <QDebug>
0015 #include <QFile>
0016 #include <QGuiApplication>
0017 #include <QPainter>
0018 #include <QPalette>
0019 #include <QQuickWindow>
0020 #include <QStandardPaths>
0021 #include <QTimeZone>
0022 
0023 using namespace KOSMIndoorMap;
0024 
0025 MapItem::MapItem(QQuickItem *parent)
0026     : QQuickPaintedItem(parent)
0027     , m_loader(new MapLoader(this))
0028     , m_view(new View(this))
0029     , m_floorLevelModel(new FloorLevelModel(this))
0030 {
0031     connect(m_loader, &MapLoader::isLoadingChanged, this, &MapItem::clear);
0032     connect(m_loader, &MapLoader::done, this, &MapItem::loaderDone);
0033 
0034     m_view->setScreenSize({100, 100}); // FIXME this breaks view when done too late!
0035     m_controller.setView(m_view);
0036     connect(m_view, &View::floorLevelChanged, this, [this]() { update(); });
0037     connect(m_view, &View::transformationChanged, this, [this]() { update(); });
0038 
0039     setStylesheetName({}); // set default stylesheet
0040 }
0041 
0042 MapItem::~MapItem() = default;
0043 
0044 void MapItem::paint(QPainter *painter)
0045 {
0046     m_controller.updateScene(m_sg);
0047     m_renderer.setPainter(painter);
0048     m_renderer.render(m_sg, m_view);
0049 }
0050 
0051 MapLoader* MapItem::loader() const
0052 {
0053     return m_loader;
0054 }
0055 
0056 View* MapItem::view() const
0057 {
0058     return m_view;
0059 }
0060 
0061 QString MapItem::styleSheetName() const
0062 {
0063     return m_styleSheetName;
0064 }
0065 
0066 void MapItem::setStylesheetName(const QString &styleSheet)
0067 {
0068     QString styleFile;
0069 
0070     if (styleSheet.isEmpty() || styleSheet == QLatin1String("default")) {
0071         if (QGuiApplication::palette().base().color().value() > 128) {
0072             setStylesheetName(QStringLiteral("breeze-light"));
0073         } else {
0074             setStylesheetName(QStringLiteral("breeze-dark"));
0075         }
0076         return;
0077     } else {
0078         styleFile = styleSheet.contains(QLatin1Char(':')) ? QUrl::fromUserInput(styleSheet).toLocalFile() : styleSheet;
0079         if (!QFile::exists(styleFile)) {
0080 #ifndef Q_OS_ANDROID
0081             auto searchPaths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0082 #else
0083             auto searchPaths = QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation);
0084 #endif
0085             searchPaths.push_back(QStringLiteral(":"));
0086             for (const auto &searchPath : std::as_const(searchPaths)) {
0087                 const QString f = searchPath + QLatin1String("/org.kde.kosmindoormap/assets/css/") + styleSheet + QLatin1String(".mapcss");
0088                 if (QFile::exists(f)) {
0089                     qDebug() << "resolved stylesheet name to" << f;
0090                     styleFile = f;
0091                     break;
0092                 }
0093             }
0094         }
0095     }
0096 
0097     if (m_styleSheetName == styleFile) {
0098         return;
0099     }
0100     m_styleSheetName = styleFile;
0101     m_style = MapCSSStyle();
0102 
0103     if (!m_styleSheetName.isEmpty()) {
0104         MapCSSParser cssParser;
0105         m_style = cssParser.parse(m_styleSheetName);
0106 
0107         if (cssParser.hasError()) {
0108             m_errorMessage = cssParser.errorMessage();
0109             Q_EMIT errorChanged();
0110             return;
0111         }
0112         m_errorMessage.clear();
0113         Q_EMIT errorChanged();
0114     }
0115 
0116     m_style.compile(m_data.dataSet());
0117     m_controller.setStyleSheet(&m_style);
0118 
0119     Q_EMIT styleSheetChanged();
0120     update();
0121 }
0122 
0123 FloorLevelModel* MapItem::floorLevelModel() const
0124 {
0125     return m_floorLevelModel;
0126 }
0127 
0128 void MapItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
0129 {
0130     QQuickPaintedItem::geometryChange(newGeometry, oldGeometry);
0131     m_view->setScreenSize(newGeometry.size().toSize());
0132     // the scale factor isn't automatically applied to the paint device, only to the input coordinates
0133     // so we need to handle this manually here
0134     if (window()) {
0135         m_view->setDeviceTransform(QTransform::fromScale(window()->devicePixelRatio(), window()->devicePixelRatio()));
0136     }
0137 }
0138 
0139 void MapItem::loaderDone()
0140 {
0141     m_floorLevelModel->setMapData(nullptr);
0142     m_sg.clear();
0143 
0144     if (!m_loader->hasError()) {
0145         auto data = m_loader->takeData();
0146         if (data.regionCode().isEmpty()) {
0147             data.setRegionCode(m_data.regionCode());
0148         }
0149         data.setTimeZone(m_data.timeZone());
0150         m_data = std::move(data);
0151         m_view->setSceneBoundingBox(m_data.boundingBox());
0152         m_controller.setMapData(m_data);
0153         m_style.compile(m_data.dataSet());
0154         m_controller.setStyleSheet(&m_style);
0155         m_view->setLevel(0);
0156         m_floorLevelModel->setMapData(&m_data);
0157         m_view->floorLevelChanged();
0158         Q_EMIT mapDataChanged();
0159     }
0160 
0161     Q_EMIT errorChanged();
0162     update();
0163 }
0164 
0165 OSMElement MapItem::elementAt(double x, double y) const
0166 {
0167     HitDetector detector;
0168     const auto item = detector.itemAt(QPointF(x, y), m_sg, m_view);
0169     if (item) {
0170         qDebug() << item->element.url();
0171         for (auto it = item->element.tagsBegin(); it != item->element.tagsEnd(); ++it) {
0172             qDebug() << "    " << (*it).key.name() << (*it).value;
0173         }
0174         return OSMElement(item->element);
0175     }
0176     return {};
0177 }
0178 
0179 void MapItem::clear()
0180 {
0181     if (!m_loader->isLoading() || m_sg.items().empty()) {
0182         return;
0183     }
0184 
0185     m_sg.clear();
0186     m_data = MapData();
0187     m_controller.setMapData(m_data);
0188     Q_EMIT mapDataChanged();
0189     Q_EMIT errorChanged();
0190     update();
0191 }
0192 
0193 bool MapItem::hasError() const
0194 {
0195     return !m_errorMessage.isEmpty() || m_loader->hasError();
0196 }
0197 
0198 QString MapItem::errorMessage() const
0199 {
0200     return m_errorMessage.isEmpty() ? m_loader->errorMessage() : m_errorMessage;
0201 }
0202 
0203 MapData MapItem::mapData() const
0204 {
0205     return m_data;
0206 }
0207 
0208 QVariant MapItem::overlaySources() const
0209 {
0210     return m_overlaySources;
0211 }
0212 
0213 void MapItem::setOverlaySources(const QVariant &overlays)
0214 {
0215     const auto oldOwnedOverlays = std::move(m_ownedOverlaySources);
0216 
0217     std::vector<QPointer<AbstractOverlaySource>> sources;
0218     if (overlays.canConvert<QVariantList>()) {
0219         const auto l = overlays.value<QVariantList>();
0220         for (const auto &v : l) {
0221             addOverlaySource(sources, v);
0222         }
0223     } else {
0224         addOverlaySource(sources, overlays);
0225     }
0226 
0227     for (const auto &overlay : sources) {
0228         connect(overlay.data(), &AbstractOverlaySource::update, this, &MapItem::overlayUpdate, Qt::UniqueConnection);
0229         connect(overlay.data(), &AbstractOverlaySource::reset, this, &MapItem::overlayReset, Qt::UniqueConnection);
0230     }
0231 
0232     m_controller.setOverlaySources(std::move(sources));
0233     Q_EMIT overlaySourcesChanged();
0234     update();
0235 }
0236 
0237 void MapItem::addOverlaySource(std::vector<QPointer<AbstractOverlaySource>> &overlaySources, const QVariant &source)
0238 {
0239     const auto obj = source.value<QObject*>();
0240     if (auto model = qobject_cast<QAbstractItemModel*>(obj)) {
0241         auto overlay = std::make_unique<ModelOverlaySource>(model);
0242         overlaySources.push_back(overlay.get());
0243         m_ownedOverlaySources.push_back(std::move(overlay));
0244     } else if (auto source = qobject_cast<AbstractOverlaySource*>(obj)) {
0245         overlaySources.push_back(source);
0246     } else {
0247         qWarning() << "unsupported overlay source:" << source << obj;
0248     }
0249 }
0250 
0251 void MapItem::overlayUpdate()
0252 {
0253     m_controller.overlaySourceUpdated();
0254     update();
0255 }
0256 
0257 void MapItem::overlayReset()
0258 {
0259     m_style.compile(m_data.dataSet());
0260 }
0261 
0262 QString MapItem::region() const
0263 {
0264     return m_data.regionCode();
0265 }
0266 
0267 void MapItem::setRegion(const QString &region)
0268 {
0269     if (m_data.regionCode() == region) {
0270         return;
0271     }
0272 
0273     m_data.setRegionCode(region);
0274     Q_EMIT regionChanged();
0275 }
0276 
0277 QString MapItem::timeZoneId() const
0278 {
0279     return QString::fromUtf8(m_data.timeZone().id());
0280 }
0281 
0282 void MapItem::setTimeZoneId(const QString &tz)
0283 {
0284     const auto tzId = tz.toUtf8();
0285     if (m_data.timeZone().id() == tzId) {
0286         return;
0287     }
0288 
0289     m_data.setTimeZone(QTimeZone(tzId));
0290     Q_EMIT timeZoneChanged();
0291 }
0292 
0293 OSMElement MapItem::hoveredElement() const
0294 {
0295     return OSMElement(m_controller.hoveredElement());
0296 }
0297 
0298 void MapItem::setHoveredElement(const OSMElement &element)
0299 {
0300     if (m_controller.hoveredElement() == element.element()) {
0301         return;
0302     }
0303     m_controller.setHoveredElement(element.element());
0304     Q_EMIT hoveredElementChanged();
0305     update();
0306 }
0307 
0308 #include "moc_mapitem.cpp"