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 ®ion) 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"