diff --git a/core/utilities/geolocation/geoiface/backends/backend-geonames-rg.cpp b/core/utilities/geolocation/geoiface/backends/backend-geonames-rg.cpp index fe52ef778d..18e514b066 100644 --- a/core/utilities/geolocation/geoiface/backends/backend-geonames-rg.cpp +++ b/core/utilities/geolocation/geoiface/backends/backend-geonames-rg.cpp @@ -1,280 +1,285 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-05-12 * Description : Backend for reverse geocoding using geonames.org (non-US) * * Copyright (C) 2010 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "backend-geonames-rg.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "gpscommon.h" namespace Digikam { /** * @class BackendGeonamesRG * * @brief This class calls Geonames' reverse geocoding service. */ class Q_DECL_HIDDEN GeonamesInternalJobs { public: GeonamesInternalJobs() : netReply(nullptr) { } ~GeonamesInternalJobs() { if (netReply) + { netReply->deleteLater(); + } } QString language; QList request; QByteArray data; QNetworkReply* netReply; }; class Q_DECL_HIDDEN BackendGeonamesRG::Private { public: explicit Private() : itemCounter(0), itemCount(0), mngr(nullptr) { } int itemCounter; int itemCount; QList jobs; QString errorMessage; QNetworkAccessManager* mngr; }; /** * Constructor * @param parent Parent object. */ BackendGeonamesRG::BackendGeonamesRG(QObject* const parent) : RGBackend(parent), d(new Private()) { d->mngr = new QNetworkAccessManager(this); connect(d->mngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); } /** * Destructor */ BackendGeonamesRG::~BackendGeonamesRG() { delete d; } /** * This function calls Geonames's reverse geocoding service for each image. */ void BackendGeonamesRG::nextPhoto() { if (d->jobs.isEmpty()) + { return; + } QUrl netUrl(QLatin1String("http://api.geonames.org/findNearbyPlaceName")); QUrlQuery q(netUrl); q.addQueryItem(QLatin1String("lat"), d->jobs.first().request.first().coordinates.latString()); q.addQueryItem(QLatin1String("lng"), d->jobs.first().request.first().coordinates.lonString()); q.addQueryItem(QLatin1String("lang"), d->jobs.first().language); q.addQueryItem(QLatin1String("username"), QLatin1String("digikam")); netUrl.setQuery(q); QNetworkRequest netRequest(netUrl); netRequest.setRawHeader("User-Agent", getUserAgentName().toLatin1()); d->jobs.first().netReply = d->mngr->get(netRequest); } /** * Takes coordinates from each image and then connects to Open Street Map's reverse geocoding service. * @param rgList A list containing information needed in reverse geocoding process. At this point, it contains only coordinates. * @param language The language in which the data will be returned. */ void BackendGeonamesRG::callRGBackend(const QList& rgList, const QString& language) { d->errorMessage.clear(); for (int i = 0 ; i < rgList.count() ; ++i) { bool foundIt = false; for (int j = 0 ; j < d->jobs.count() ; ++j) { if (d->jobs[j].request.first().coordinates.sameLonLatAs(rgList[i].coordinates)) { d->jobs[j].request << rgList[i]; d->jobs[j].language = language; foundIt = true; break; } } if (!foundIt) { GeonamesInternalJobs newJob; newJob.request << rgList.at(i); newJob.language = language; d->jobs << newJob; } } nextPhoto(); } /** * The data is returned from Open Street Map in a XML. This function translates the XML into a QMap. * @param xmlData The returned XML. */ QMap BackendGeonamesRG::makeQMapFromXML(const QString& xmlData) { QMap mappedData; QString resultString; QDomDocument doc; doc.setContent(xmlData); QDomElement docElem = doc.documentElement(); QDomNode n = docElem.firstChild().firstChild(); while (!n.isNull()) { const QDomElement e = n.toElement(); if (!e.isNull()) { if ((e.tagName().compare(QLatin1String("countryName")) == 0) || (e.tagName().compare(QLatin1String("name")) == 0)) { mappedData.insert(e.tagName(), e.text()); resultString.append(e.tagName() + QLatin1Char(':') + e.text() + QLatin1Char('\n')); } } n = n.nextSibling(); } return mappedData; } /** * @return Error message, if any. */ QString BackendGeonamesRG::getErrorMessage() { return d->errorMessage; } /** * @return Backend name. */ QString BackendGeonamesRG::backendName() { return QLatin1String("Geonames"); } void BackendGeonamesRG::slotFinished(QNetworkReply* reply) { if (reply->error() != QNetworkReply::NoError) { d->errorMessage = reply->errorString(); emit signalRGReady(d->jobs.first().request); reply->deleteLater(); d->jobs.clear(); + return; } for (int i = 0 ; i < d->jobs.count() ; ++i) { if (d->jobs.at(i).netReply == reply) { d->jobs[i].data.append(reply->readAll()); break; } } for (int i = 0 ; i < d->jobs.count() ; ++i) { if (d->jobs.at(i).netReply == reply) { QString dataString; dataString = QString::fromUtf8(d->jobs[i].data.constData(),qstrlen(d->jobs[i].data.constData())); int pos = dataString.indexOf(QLatin1String(" resultMap = makeQMapFromXML(dataString); for (int j = 0 ; j < d->jobs[i].request.count() ; ++j) { d->jobs[i].request[j].rgData = resultMap; } emit signalRGReady(d->jobs[i].request); d->jobs.removeAt(i); if (!d->jobs.isEmpty()) { QTimer::singleShot(500, this, SLOT(nextPhoto())); } break; } } reply->deleteLater(); } void BackendGeonamesRG::cancelRequests() { d->jobs.clear(); d->errorMessage.clear(); } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/backends/backend-geonames-rg.h b/core/utilities/geolocation/geoiface/backends/backend-geonames-rg.h index 8f48b35dbe..b10fecf38f 100644 --- a/core/utilities/geolocation/geoiface/backends/backend-geonames-rg.h +++ b/core/utilities/geolocation/geoiface/backends/backend-geonames-rg.h @@ -1,73 +1,73 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-05-12 * Description : Backend for reverse geocoding using geonames.org (non-US) * * Copyright (C) 2010 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_BACKEND_GEONAMES_RG_H #define DIGIKAM_BACKEND_GEONAMES_RG_H // Qt includes #include #include #include #include #include // Local includes #include "backend-rg.h" #include "digikam_export.h" namespace Digikam { class DIGIKAM_EXPORT BackendGeonamesRG : public RGBackend { Q_OBJECT public: explicit BackendGeonamesRG(QObject* const parent); virtual ~BackendGeonamesRG(); QMap makeQMapFromXML(const QString& xmlData); virtual void callRGBackend(const QList& rgList, const QString& language) override; - virtual QString getErrorMessage() override; - virtual QString backendName() override; - virtual void cancelRequests() override; + virtual QString getErrorMessage() override; + virtual QString backendName() override; + virtual void cancelRequests() override; private Q_SLOTS: void nextPhoto(); void slotFinished(QNetworkReply* reply); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_BACKEND_GEONAMES_RG_H diff --git a/core/utilities/geolocation/geoiface/backends/backend-geonamesUS-rg.cpp b/core/utilities/geolocation/geoiface/backends/backend-geonamesUS-rg.cpp index 67a12c2043..2374c158f2 100644 --- a/core/utilities/geolocation/geoiface/backends/backend-geonamesUS-rg.cpp +++ b/core/utilities/geolocation/geoiface/backends/backend-geonamesUS-rg.cpp @@ -1,283 +1,290 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-05-12 * Description : Backend for reverse geocoding using geonames.org (US-only) * * Copyright (C) 2010 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "backend-geonamesUS-rg.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "gpscommon.h" namespace Digikam { /** * @class BackendGeonamesUSRG * * @brief This class calls Geonames' get address service available only for USA locations. */ class Q_DECL_HIDDEN GeonamesUSInternalJobs { public: GeonamesUSInternalJobs() : netReply(nullptr) { } ~GeonamesUSInternalJobs() { if (netReply) + { netReply->deleteLater(); + } } QString language; QList request; QByteArray data; QNetworkReply* netReply; }; class Q_DECL_HIDDEN BackendGeonamesUSRG::Private { public: explicit Private() : itemCounter(0), itemCount(0), mngr(nullptr) { } int itemCounter; int itemCount; QList jobs; QString errorMessage; QNetworkAccessManager* mngr; }; /** * Constructor * @param Parent object. */ BackendGeonamesUSRG::BackendGeonamesUSRG(QObject* const parent) : RGBackend(parent), d(new Private()) { d->mngr = new QNetworkAccessManager(this); connect(d->mngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); } /** * Destructor */ BackendGeonamesUSRG::~BackendGeonamesUSRG() { delete d; } /** * This slot calls Geonames's get address service for each image. */ void BackendGeonamesUSRG::nextPhoto() { if (d->jobs.isEmpty()) + { return; + } QUrl netUrl(QLatin1String("http://api.geonames.org/findNearestAddress")); QUrlQuery q(netUrl); q.addQueryItem(QLatin1String("lat"), d->jobs.first().request.first().coordinates.latString()); q.addQueryItem(QLatin1String("lng"), d->jobs.first().request.first().coordinates.lonString()); q.addQueryItem(QLatin1String("username"), QLatin1String("digikam")); - // q.addQueryItem(QLatin1String("lang"), d->jobs.first().language); +/* + q.addQueryItem(QLatin1String("lang"), d->jobs.first().language); +*/ netUrl.setQuery(q); QNetworkRequest netRequest(netUrl); netRequest.setRawHeader("User-Agent", getUserAgentName().toLatin1()); d->jobs.first().netReply = d->mngr->get(netRequest); } /** * Takes the coordinate of each image and then connects to Open Street Map's reverse geocoding service. * @param rgList A list containing information needed in reverse geocoding process. At this point, it contains only coordinates. * @param language The language in which the data will be returned. */ void BackendGeonamesUSRG::callRGBackend(const QList& rgList, const QString& language) { d->errorMessage.clear(); for (int i = 0 ; i < rgList.count() ; ++i) { bool foundIt = false; for (int j = 0 ; j < d->jobs.count() ; ++j) { if (d->jobs[j].request.first().coordinates.sameLonLatAs(rgList[i].coordinates)) { d->jobs[j].request << rgList[i]; d->jobs[j].language = language; foundIt = true; break; } } if (!foundIt) { GeonamesUSInternalJobs newJob; newJob.request << rgList.at(i); newJob.language = language; d->jobs << newJob; } } nextPhoto(); } /** * The data is returned from Open Street Map in a XML. This function translates the XML into a QMap. * @param xmlData The returned XML. */ QMap BackendGeonamesUSRG::makeQMapFromXML(const QString& xmlData) { QMap mappedData; QString resultString; QDomDocument doc; doc.setContent(xmlData); QDomElement docElem = doc.documentElement(); QDomNode n = docElem.firstChild().firstChild(); while (!n.isNull()) { const QDomElement e = n.toElement(); if (!e.isNull()) { if ((e.tagName().compare(QLatin1String("adminName2")) == 0) || (e.tagName().compare(QLatin1String("adminName1")) == 0) || (e.tagName().compare(QLatin1String("placename")) == 0)) { mappedData.insert(e.tagName(), e.text()); resultString.append(e.tagName() + QLatin1Char(':') + e.text() + QLatin1Char('\n')); } } n = n.nextSibling(); } return mappedData; } /** * @return Error message, if any. */ QString BackendGeonamesUSRG::getErrorMessage() { return d->errorMessage; } /** * @return Backend name. */ QString BackendGeonamesUSRG::backendName() { return QLatin1String("GeonamesUS"); } void BackendGeonamesUSRG::slotFinished(QNetworkReply* reply) { if (reply->error() != QNetworkReply::NoError) { d->errorMessage = reply->errorString(); emit signalRGReady(d->jobs.first().request); reply->deleteLater(); d->jobs.clear(); + return; } for (int i = 0 ; i < d->jobs.count() ; ++i) { if (d->jobs.at(i).netReply == reply) { d->jobs[i].data.append(reply->readAll()); break; } } for (int i = 0 ; i < d->jobs.count() ; ++i) { if (d->jobs.at(i).netReply == reply) { QString dataString; dataString = QString::fromUtf8(d->jobs[i].data.constData(),qstrlen(d->jobs[i].data.constData())); int pos = dataString.indexOf(QLatin1String(" resultMap = makeQMapFromXML(dataString); for (int j = 0 ; j < d->jobs[i].request.count() ; ++j) { - d->jobs[i].request[j].rgData = resultMap; + d->jobs[i].request[j].rgData = resultMap; } emit signalRGReady(d->jobs[i].request); d->jobs.removeAt(i); if (!d->jobs.isEmpty()) { QTimer::singleShot(500, this, SLOT(nextPhoto())); } break; } } reply->deleteLater(); } void BackendGeonamesUSRG::cancelRequests() { d->jobs.clear(); d->errorMessage.clear(); } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/backends/backend-geonamesUS-rg.h b/core/utilities/geolocation/geoiface/backends/backend-geonamesUS-rg.h index 429587a14c..edd50ad7c7 100644 --- a/core/utilities/geolocation/geoiface/backends/backend-geonamesUS-rg.h +++ b/core/utilities/geolocation/geoiface/backends/backend-geonamesUS-rg.h @@ -1,73 +1,73 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-05-12 * Description : Backend for reverse geocoding using geonames.org (US-only) * * Copyright (C) 2010 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_BACKEND_GEONAMESUS_RG_H #define DIGIKAM_BACKEND_GEONAMESUS_RG_H // Qt includes #include #include #include #include #include // Local includes #include "digikam_export.h" #include "backend-rg.h" namespace Digikam { class DIGIKAM_EXPORT BackendGeonamesUSRG : public RGBackend { Q_OBJECT public: explicit BackendGeonamesUSRG(QObject* const parent); virtual ~BackendGeonamesUSRG(); QMap makeQMapFromXML(const QString& xmlData); virtual void callRGBackend(const QList& rgList, const QString& language) override; - virtual QString getErrorMessage() override; - virtual QString backendName() override; - virtual void cancelRequests() override; + virtual QString getErrorMessage() override; + virtual QString backendName() override; + virtual void cancelRequests() override; private Q_SLOTS: void nextPhoto(); void slotFinished(QNetworkReply* reply); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_BACKEND_GEONAMESUS_RG_H diff --git a/core/utilities/geolocation/geoiface/backends/backend-osm-rg.cpp b/core/utilities/geolocation/geoiface/backends/backend-osm-rg.cpp index 3bc54a91cc..5ebb33b157 100644 --- a/core/utilities/geolocation/geoiface/backends/backend-osm-rg.cpp +++ b/core/utilities/geolocation/geoiface/backends/backend-osm-rg.cpp @@ -1,289 +1,296 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-05-12 * Description : OSM Nominatim backend for Reverse Geocoding * * Copyright (C) 2010 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "backend-osm-rg.h" // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "gpscommon.h" namespace Digikam { class Q_DECL_HIDDEN OsmInternalJobs { public: OsmInternalJobs() : netReply(nullptr) { } ~OsmInternalJobs() { if (netReply) + { netReply->deleteLater(); + } } QString language; QList request; QByteArray data; QNetworkReply* netReply; }; class Q_DECL_HIDDEN BackendOsmRG::Private { public: explicit Private() : mngr(nullptr) { } QList jobs; QString errorMessage; QNetworkAccessManager* mngr; }; /** * @class BackendOsmRG * * @brief This class calls Open Street Map's reverse geocoding service. */ /** * Constructor * @param Parent object. */ BackendOsmRG::BackendOsmRG(QObject* const parent) : RGBackend(parent), d(new Private()) { d->mngr = new QNetworkAccessManager(this); connect(d->mngr, SIGNAL(finished(QNetworkReply*)), this, SLOT(slotFinished(QNetworkReply*))); } /** * Destructor */ BackendOsmRG::~BackendOsmRG() { delete d; } /** * This slot calls Open Street Map's reverse geocoding service for each image. */ void BackendOsmRG::nextPhoto() { if (d->jobs.isEmpty()) + { return; + } QUrl netUrl(QLatin1String("https://nominatim.openstreetmap.org/reverse")); QUrlQuery q(netUrl); q.addQueryItem(QLatin1String("format"), QLatin1String("xml")); q.addQueryItem(QLatin1String("lat"), d->jobs.first().request.first().coordinates.latString()); q.addQueryItem(QLatin1String("lon"), d->jobs.first().request.first().coordinates.lonString()); q.addQueryItem(QLatin1String("zoom"), QLatin1String("18")); q.addQueryItem(QLatin1String("addressdetails"), QLatin1String("1")); q.addQueryItem(QLatin1String("accept-language"), d->jobs.first().language); netUrl.setQuery(q); QNetworkRequest netRequest(netUrl); netRequest.setRawHeader("User-Agent", getUserAgentName().toLatin1()); d->jobs.first().netReply = d->mngr->get(netRequest); } /** * Takes the coordinate of each image and then connects to Open Street Map's reverse geocoding service. * @param rgList A list containing information needed in reverse geocoding process. At this point, it contains only coordinates. * @param language The language in which the data will be returned. */ void BackendOsmRG::callRGBackend(const QList& rgList, const QString& language) { d->errorMessage.clear(); for (int i = 0 ; i < rgList.count() ; ++i) { bool foundIt = false; for (int j = 0 ; j < d->jobs.count() ; ++j) { if (d->jobs[j].request.first().coordinates.sameLonLatAs(rgList[i].coordinates)) { d->jobs[j].request << rgList[i]; d->jobs[j].language = language; foundIt = true; break; } } if (!foundIt) { OsmInternalJobs newJob; newJob.request << rgList.at(i); newJob.language = language; d->jobs << newJob; } } if (!d->jobs.isEmpty()) + { nextPhoto(); + } } /** * The data is returned from Open Street Map in a XML. This function translates the XML into a QMap. * @param xmlData The returned XML. */ QMap BackendOsmRG::makeQMapFromXML(const QString& xmlData) { QString resultString; QMap mappedData; QDomDocument doc; doc.setContent(xmlData); QDomElement docElem = doc.documentElement(); QDomNode n = docElem.lastChild().firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { if ((e.tagName() == QLatin1String("country")) || (e.tagName() == QLatin1String("state")) || (e.tagName() == QLatin1String("state_district")) || (e.tagName() == QLatin1String("county")) || (e.tagName() == QLatin1String("city")) || (e.tagName() == QLatin1String("city_district")) || (e.tagName() == QLatin1String("suburb")) || (e.tagName() == QLatin1String("town")) || (e.tagName() == QLatin1String("village")) || (e.tagName() == QLatin1String("hamlet")) || (e.tagName() == QLatin1String("place")) || (e.tagName() == QLatin1String("road")) || (e.tagName() == QLatin1String("house_number"))) { mappedData.insert(e.tagName(), e.text()); resultString.append(e.tagName() + QLatin1Char(':') + e.text() + QLatin1Char('\n')); } } n = n.nextSibling(); } return mappedData; } /** * @return Error message, if any. */ QString BackendOsmRG::getErrorMessage() { return d->errorMessage; } /** * @return Backend name. */ QString BackendOsmRG::backendName() { return QLatin1String("OSM"); } void BackendOsmRG::slotFinished(QNetworkReply* reply) { if (reply->error() != QNetworkReply::NoError) { d->errorMessage = reply->errorString(); emit signalRGReady(d->jobs.first().request); reply->deleteLater(); d->jobs.clear(); + return; } for (int i = 0 ; i < d->jobs.count() ; ++i) { if (d->jobs.at(i).netReply == reply) { d->jobs[i].data.append(reply->readAll()); break; } } for (int i = 0 ; i < d->jobs.count() ; ++i) { if (d->jobs.at(i).netReply == reply) { QString dataString; dataString = QString::fromUtf8(d->jobs[i].data.constData(), qstrlen(d->jobs[i].data.constData())); int pos = dataString.indexOf(QLatin1String(" resultMap = makeQMapFromXML(dataString); for (int j = 0 ; j < d->jobs[i].request.count() ; ++j) { d->jobs[i].request[j].rgData = resultMap; } emit signalRGReady(d->jobs[i].request); d->jobs.removeAt(i); if (!d->jobs.isEmpty()) { QTimer::singleShot(500, this, SLOT(nextPhoto())); } break; } } reply->deleteLater(); } void BackendOsmRG::cancelRequests() { d->jobs.clear(); d->errorMessage.clear(); } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/backends/backend-osm-rg.h b/core/utilities/geolocation/geoiface/backends/backend-osm-rg.h index 4aef862f46..58a13cad7a 100644 --- a/core/utilities/geolocation/geoiface/backends/backend-osm-rg.h +++ b/core/utilities/geolocation/geoiface/backends/backend-osm-rg.h @@ -1,72 +1,72 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-05-12 * Description : OSM Nominatim backend for Reverse Geocoding * * Copyright (C) 2010 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_BACKEND_OSM_RG_H #define DIGIKAM_BACKEND_OSM_RG_H // Qt includes #include #include #include #include // Local includes #include "backend-rg.h" #include "digikam_export.h" namespace Digikam { class DIGIKAM_EXPORT BackendOsmRG : public RGBackend { Q_OBJECT public: explicit BackendOsmRG(QObject* const parent); virtual ~BackendOsmRG(); QMap makeQMapFromXML(const QString& xmlData); virtual void callRGBackend(const QList& rgList,const QString& language) override; - virtual QString getErrorMessage() override; - virtual QString backendName() override; - virtual void cancelRequests() override; + virtual QString getErrorMessage() override; + virtual QString backendName() override; + virtual void cancelRequests() override; private Q_SLOTS: void nextPhoto(); void slotFinished(QNetworkReply* reply); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_BACKEND_OSM_RG_H diff --git a/core/utilities/geolocation/geoiface/backends/backend-rg.h b/core/utilities/geolocation/geoiface/backends/backend-rg.h index b2ad2e1b4c..8d9526d119 100644 --- a/core/utilities/geolocation/geoiface/backends/backend-rg.h +++ b/core/utilities/geolocation/geoiface/backends/backend-rg.h @@ -1,61 +1,61 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-05-12 * Description : Abstract backend class for reverse geocoding. * * Copyright (C) 2010 by Michael G. Hansen * Copyright (C) 2010 by Gabriel Voicu * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_BACKEND_RG_H #define DIGIKAM_BACKEND_RG_H // local includes #include "gpsitemcontainer.h" #include "digikam_export.h" namespace Digikam { class DIGIKAM_EXPORT RGBackend : public QObject { Q_OBJECT public: explicit RGBackend(QObject* const parent); RGBackend(); virtual ~RGBackend(); virtual void callRGBackend(const QList&, const QString&) = 0; virtual QString getErrorMessage(); virtual QString backendName(); - virtual void cancelRequests() = 0; + virtual void cancelRequests() = 0; Q_SIGNALS: /** * @brief Emitted whenever some items are ready */ void signalRGReady(QList&); }; } // namespace Digikam #endif // DIGIKAM_BACKEND_RG_H diff --git a/core/utilities/geolocation/geoiface/backends/backendgooglemaps.cpp b/core/utilities/geolocation/geoiface/backends/backendgooglemaps.cpp index 357138b68d..6ac4edecdd 100644 --- a/core/utilities/geolocation/geoiface/backends/backendgooglemaps.cpp +++ b/core/utilities/geolocation/geoiface/backends/backendgooglemaps.cpp @@ -1,1444 +1,1597 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-01 * Description : Google-Maps-backend for geolocation interface * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2009-2011 by Michael G. Hansen * Copyright (C) 2014 by Justus Schwartz * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "backendgooglemaps.h" // Qt includes #include #include #include #include #include #include #include // KDE includes #include #include // Marble includes #include // Local includes #include "digikam_config.h" #include "digikam_debug.h" #include "mapwidget.h" #include "abstractmarkertiler.h" #include "geomodelhelper.h" #ifdef HAVE_QWEBENGINE # include "htmlwidget_qwebengine.h" #else # include "htmlwidget.h" #endif namespace Digikam { class Q_DECL_HIDDEN GMInternalWidgetInfo { public: GMInternalWidgetInfo() + : htmlWidget(nullptr) { - htmlWidget = nullptr; } HTMLWidget* htmlWidget; }; } // namespace Digikam Q_DECLARE_METATYPE(Digikam::GMInternalWidgetInfo) namespace Digikam { class Q_DECL_HIDDEN BackendGoogleMaps::Private { public: explicit Private() : htmlWidget(nullptr), htmlWidgetWrapper(nullptr), isReady(false), mapTypeActionGroup(nullptr), floatItemsActionGroup(nullptr), showMapTypeControlAction(nullptr), showNavigationControlAction(nullptr), showScaleControlAction(nullptr), cacheMapType(QLatin1String("ROADMAP")), cacheShowMapTypeControl(true), cacheShowNavigationControl(true), cacheShowScaleControl(true), cacheZoom(8), cacheMaxZoom(0), cacheMinZoom(0), cacheCenter(52.0, 6.0), cacheBounds(), activeState(false), widgetIsDocked(false), trackChangeTracker() { } QPointer htmlWidget; QPointer htmlWidgetWrapper; bool isReady; QActionGroup* mapTypeActionGroup; QActionGroup* floatItemsActionGroup; QAction* showMapTypeControlAction; QAction* showNavigationControlAction; QAction* showScaleControlAction; QString cacheMapType; bool cacheShowMapTypeControl; bool cacheShowNavigationControl; bool cacheShowScaleControl; int cacheZoom; int cacheMaxZoom; int cacheMinZoom; GeoCoordinates cacheCenter; QPair cacheBounds; bool activeState; bool widgetIsDocked; QList trackChangeTracker; }; BackendGoogleMaps::BackendGoogleMaps(const QExplicitlySharedDataPointer& sharedData, QObject* const parent) : MapBackend(sharedData, parent), d(new Private()) { createActions(); } BackendGoogleMaps::~BackendGoogleMaps() { /// @todo Should we leave our widget in this list and not destroy it? /// Maybe for now this should simply be limited to leaving one /// unused widget in the global cache. + GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->removeMyInternalWidgetFromPool(this); if (d->htmlWidgetWrapper) { delete d->htmlWidgetWrapper; } delete d; } void BackendGoogleMaps::createActions() { // actions for selecting the map type: + d->mapTypeActionGroup = new QActionGroup(this); d->mapTypeActionGroup->setExclusive(true); connect(d->mapTypeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotMapTypeActionTriggered(QAction*))); QStringList mapTypes, mapTypesHumanNames; mapTypes << QLatin1String("ROADMAP") << QLatin1String("SATELLITE") << QLatin1String("HYBRID") << QLatin1String("TERRAIN"); mapTypesHumanNames << i18n("Roadmap") << i18n("Satellite") << i18n("Hybrid") << i18n("Terrain"); for (int i = 0 ; i < mapTypes.count() ; ++i) { QAction* const mapTypeAction = new QAction(d->mapTypeActionGroup); mapTypeAction->setData(mapTypes.at(i)); mapTypeAction->setText(mapTypesHumanNames.at(i)); mapTypeAction->setCheckable(true); } // float items: + d->floatItemsActionGroup = new QActionGroup(this); d->floatItemsActionGroup->setExclusive(false); connect(d->floatItemsActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotFloatSettingsTriggered(QAction*))); d->showMapTypeControlAction = new QAction(i18n("Show Map Type Control"), d->floatItemsActionGroup); d->showMapTypeControlAction->setCheckable(true); d->showMapTypeControlAction->setChecked(d->cacheShowMapTypeControl); d->showMapTypeControlAction->setData(QLatin1String("showmaptypecontrol")); d->showNavigationControlAction = new QAction(i18n("Show Navigation Control"), d->floatItemsActionGroup); d->showNavigationControlAction->setCheckable(true); d->showNavigationControlAction->setChecked(d->cacheShowNavigationControl); d->showNavigationControlAction->setData(QLatin1String("shownavigationcontrol")); d->showScaleControlAction = new QAction(i18n("Show Scale Control"), d->floatItemsActionGroup); d->showScaleControlAction->setCheckable(true); d->showScaleControlAction->setChecked(d->cacheShowScaleControl); d->showScaleControlAction->setData(QLatin1String("showscalecontrol")); } QString BackendGoogleMaps::backendName() const { return QLatin1String("googlemaps"); } QString BackendGoogleMaps::backendHumanName() const { return i18n("Google Maps"); } QWidget* BackendGoogleMaps::mapWidget() { if (!d->htmlWidgetWrapper) { GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); GeoIfaceInternalWidgetInfo info; - bool foundReusableWidget = go->getInternalWidgetFromPool(this, &info); + bool foundReusableWidget = go->getInternalWidgetFromPool(this, &info); if (foundReusableWidget) { d->htmlWidgetWrapper = info.widget; const GMInternalWidgetInfo intInfo = info.backendData.value(); d->htmlWidget = intInfo.htmlWidget; } else { // the widget has not been created yet, create it now: + d->htmlWidgetWrapper = new QWidget(); d->htmlWidgetWrapper->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); d->htmlWidget = new HTMLWidget(d->htmlWidgetWrapper); d->htmlWidgetWrapper->resize(400, 400); } connect(d->htmlWidget, SIGNAL(signalJavaScriptReady()), this, SLOT(slotHTMLInitialized())); connect(d->htmlWidget, SIGNAL(signalHTMLEvents(QStringList)), this, SLOT(slotHTMLEvents(QStringList))); connect(d->htmlWidget, SIGNAL(selectionHasBeenMade(Digikam::GeoCoordinates::Pair)), this, SLOT(slotSelectionHasBeenMade(Digikam::GeoCoordinates::Pair))); d->htmlWidget->setSharedGeoIfaceObject(s.data()); d->htmlWidgetWrapper->installEventFilter(this); if (foundReusableWidget) { slotHTMLInitialized(); } else { const QUrl htmlUrl = GeoIfaceGlobalObject::instance()->locateDataFile(QLatin1String("backend-googlemaps.html")); d->htmlWidget->load(htmlUrl); } } return d->htmlWidgetWrapper.data(); } GeoCoordinates BackendGoogleMaps::getCenter() const { return d->cacheCenter; } void BackendGoogleMaps::setCenter(const GeoCoordinates& coordinate) { d->cacheCenter = coordinate; if (isReady()) { QTimer::singleShot(0, this, SLOT(slotSetCenterTimer())); } } void BackendGoogleMaps::slotSetCenterTimer() { d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetCenter(%1, %2);") .arg(d->cacheCenter.latString()) .arg(d->cacheCenter.lonString())); } bool BackendGoogleMaps::isReady() const { return d->isReady; } void BackendGoogleMaps::slotHTMLInitialized() { d->isReady = true; d->htmlWidget->runScript(QString::fromLatin1("kgeomapWidgetResized(%1, %2)") .arg(d->htmlWidgetWrapper->width()) .arg(d->htmlWidgetWrapper->height())); // TODO: call javascript directly here and update action availability in one shot + setMapType(d->cacheMapType); setShowScaleControl(d->cacheShowScaleControl); setShowMapTypeControl(d->cacheShowMapTypeControl); setShowNavigationControl(d->cacheShowNavigationControl); emit signalBackendReadyChanged(backendName()); } void BackendGoogleMaps::zoomIn() { if (!d->isReady) + { return; + } d->htmlWidget->runScript(QLatin1String("kgeomapZoomIn();")); } void BackendGoogleMaps::zoomOut() { if (!d->isReady) + { return; + } d->htmlWidget->runScript(QLatin1String("kgeomapZoomOut();")); } QString BackendGoogleMaps::getMapType() const { return d->cacheMapType; } void BackendGoogleMaps::setMapType(const QString& newMapType) { d->cacheMapType = newMapType; qCDebug(DIGIKAM_GEOIFACE_LOG) << newMapType; if (isReady()) { d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetMapType(\"%1\");").arg(newMapType)); updateZoomMinMaxCache(); updateActionAvailability(); } } void BackendGoogleMaps::slotMapTypeActionTriggered(QAction* action) { const QString newMapType = action->data().toString(); setMapType(newMapType); } void BackendGoogleMaps::addActionsToConfigurationMenu(QMenu* const configurationMenu) { GEOIFACE_ASSERT(configurationMenu!=nullptr); if (!d->isReady) + { return; + } configurationMenu->addSeparator(); // map type actions: + const QList mapTypeActions = d->mapTypeActionGroup->actions(); for (int i = 0 ; i < mapTypeActions.count() ; ++i) { QAction* const mapTypeAction = mapTypeActions.at(i); configurationMenu->addAction(mapTypeAction); } configurationMenu->addSeparator(); // float items visibility: + QMenu* const floatItemsSubMenu = new QMenu(i18n("Float items"), configurationMenu); configurationMenu->addMenu(floatItemsSubMenu); floatItemsSubMenu->addAction(d->showMapTypeControlAction); floatItemsSubMenu->addAction(d->showNavigationControlAction); floatItemsSubMenu->addAction(d->showScaleControlAction); updateActionAvailability(); } void BackendGoogleMaps::saveSettingsToGroup(KConfigGroup* const group) { GEOIFACE_ASSERT(group != nullptr); if (!group) + { return; + } group->writeEntry("GoogleMaps Map Type", getMapType()); group->writeEntry("GoogleMaps Show Scale Control", d->cacheShowScaleControl); group->writeEntry("GoogleMaps Show Map Type Control", d->cacheShowMapTypeControl); group->writeEntry("GoogleMaps Show Navigation Control", d->cacheShowNavigationControl); } void BackendGoogleMaps::readSettingsFromGroup(const KConfigGroup* const group) { GEOIFACE_ASSERT(group != nullptr); if (!group) + { return; + } setMapType(group->readEntry("GoogleMaps Map Type", "ROADMAP")); setShowScaleControl(group->readEntry("GoogleMaps Show Scale Control", true)); setShowMapTypeControl(group->readEntry("GoogleMaps Show Map Type Control", true)); setShowNavigationControl(group->readEntry("GoogleMaps Show Navigation Control", true)); } void BackendGoogleMaps::slotUngroupedModelChanged(const int mindex) { GEOIFACE_ASSERT(isReady()); if (!isReady()) + { return; + } d->htmlWidget->runScript(QString::fromLatin1("kgeomapClearMarkers(%1);").arg(mindex)); // this can happen when a model was removed and we are simply asked to remove its markers - if (mindex>s->ungroupedModels.count()) + + if (mindex > s->ungroupedModels.count()) + { return; + } GeoModelHelper* const modelHelper = s->ungroupedModels.at(mindex); if (!modelHelper) + { return; + } if (!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagVisible)) + { return; + } QAbstractItemModel* const model = modelHelper->model(); for (int row = 0 ; row < model->rowCount() ; ++row) { - const QModelIndex currentIndex = model->index(row, 0); + const QModelIndex currentIndex = model->index(row, 0); const GeoModelHelper::PropertyFlags itemFlags = modelHelper->itemFlags(currentIndex); // TODO: this is untested! We need to make sure the indices stay correct inside the JavaScript part! + if (!itemFlags.testFlag(GeoModelHelper::FlagVisible)) + { continue; + } GeoCoordinates currentCoordinates; + if (!modelHelper->itemCoordinates(currentIndex, ¤tCoordinates)) + { continue; + } // TODO: use the pixmap supplied by the modelHelper + d->htmlWidget->runScript(QString::fromLatin1("kgeomapAddMarker(%1, %2, %3, %4, %5, %6);") .arg(mindex) .arg(row) .arg(currentCoordinates.latString()) .arg(currentCoordinates.lonString()) .arg(itemFlags.testFlag(GeoModelHelper::FlagMovable) ? QLatin1String("true") : QLatin1String("false")) .arg(itemFlags.testFlag(GeoModelHelper::FlagSnaps) ? QLatin1String("true") : QLatin1String("false")) ); QPoint markerCenterPoint; QSize markerSize; QPixmap markerPixmap; QUrl markerUrl; const bool markerHasIcon = modelHelper->itemIcon(currentIndex, &markerCenterPoint, &markerSize, &markerPixmap, &markerUrl); if (markerHasIcon) { if (!markerUrl.isEmpty()) { setMarkerPixmap(mindex, row, markerCenterPoint, markerSize, markerUrl); } else { setMarkerPixmap(mindex, row, markerCenterPoint, markerPixmap); } } } } void BackendGoogleMaps::updateMarkers() { // re-transfer all markers to the javascript-part: + for (int i = 0 ; i < s->ungroupedModels.count() ; ++i) { slotUngroupedModelChanged(i); } } void BackendGoogleMaps::slotHTMLEvents(const QStringList& events) { // for some events, we just note that they appeared and then process them later on: + bool centerProbablyChanged = false; bool mapTypeChanged = false; bool zoomProbablyChanged = false; bool mapBoundsProbablyChanged = false; QIntList movedClusters; QList movedMarkers; QIntList clickedClusters; // TODO: verify that the order of the events is still okay // or that the order does not matter + for (QStringList::const_iterator it = events.constBegin() ; it != events.constEnd() ; ++it) { const QString eventCode = it->left(2); const QString eventParameter = it->mid(2); const QStringList eventParameters = eventParameter.split(QLatin1Char('/')); - if (eventCode == QLatin1String("MT")) + if (eventCode == QLatin1String("MT")) { // map type changed + mapTypeChanged = true; d->cacheMapType = eventParameter; } else if (eventCode == QLatin1String("MB")) - { // NOTE: event currently disabled in javascript part + { + // NOTE: event currently disabled in javascript part // map bounds changed + centerProbablyChanged = true; zoomProbablyChanged = true; mapBoundsProbablyChanged = true; } else if (eventCode == QLatin1String("ZC")) - { // NOTE: event currently disabled in javascript part + { + // NOTE: event currently disabled in javascript part // zoom changed + zoomProbablyChanged = true; mapBoundsProbablyChanged = true; } else if (eventCode == QLatin1String("id")) { // idle after drastic map changes + centerProbablyChanged = true; zoomProbablyChanged = true; mapBoundsProbablyChanged = true; } else if (eventCode == QLatin1String("cm")) { /// @todo buffer this event type! // cluster moved + bool okay = false; const int clusterIndex = eventParameter.toInt(&okay); GEOIFACE_ASSERT(okay); if (!okay) + { continue; + } GEOIFACE_ASSERT(clusterIndex >= 0); GEOIFACE_ASSERT(clusterIndex < s->clusterList.size()); if ((clusterIndex < 0) || (clusterIndex > s->clusterList.size())) + { continue; + } // re-read the marker position: + GeoCoordinates clusterCoordinates; const bool isValid = d->htmlWidget->runScript2Coordinates( QString::fromLatin1("kgeomapGetClusterPosition(%1);").arg(clusterIndex), - &clusterCoordinates - ); + &clusterCoordinates); GEOIFACE_ASSERT(isValid); if (!isValid) + { continue; + } /// @todo this discards the altitude! /// @todo is this really necessary? clusters should be regenerated anyway... + s->clusterList[clusterIndex].coordinates = clusterCoordinates; movedClusters << clusterIndex; } else if (eventCode == QLatin1String("cs")) { /// @todo buffer this event type! // cluster snapped - bool okay = false; + + bool okay = false; const int clusterIndex = eventParameters.first().toInt(&okay); GEOIFACE_ASSERT(okay); if (!okay) + { continue; + } GEOIFACE_ASSERT(clusterIndex >= 0); GEOIFACE_ASSERT(clusterIndex < s->clusterList.size()); if ((clusterIndex < 0) || (clusterIndex > s->clusterList.size())) + { continue; + } // determine to which marker we snapped: + okay = false; const int snapModelId = eventParameters.at(1).toInt(&okay); GEOIFACE_ASSERT(okay); if (!okay) + { continue; + } okay = false; const int snapMarkerId = eventParameters.at(2).toInt(&okay); GEOIFACE_ASSERT(okay); if (!okay) + { continue; + } /// @todo emit signal here or later? + GeoModelHelper* const modelHelper = s->ungroupedModels.at(snapModelId); QAbstractItemModel* const model = modelHelper->model(); QPair snapTargetIndex(snapModelId, model->index(snapMarkerId, 0)); emit signalClustersMoved(QIntList() << clusterIndex, snapTargetIndex); } else if (eventCode == QLatin1String("cc")) { /// @todo buffer this event type! // cluster clicked + bool okay = false; const int clusterIndex = eventParameter.toInt(&okay); GEOIFACE_ASSERT(okay); if (!okay) + { continue; + } GEOIFACE_ASSERT(clusterIndex >= 0); GEOIFACE_ASSERT(clusterIndex < s->clusterList.size()); if ((clusterIndex < 0) || (clusterIndex > s->clusterList.size())) + { continue; + } clickedClusters << clusterIndex; } else if (eventCode == QLatin1String("mm")) { -// // TODO: buffer this event type! -// // marker moved -// bool okay = false; -// const int markerRow = eventParameter.toInt(&okay); -// GEOIFACE_ASSERT(okay); -// -// if (!okay) -// continue; -// -// GEOIFACE_ASSERT(markerRow >= 0); -// GEOIFACE_ASSERT(markerRowspecialMarkersModel->rowCount()); -// -// if ((markerRow<0)||(markerRow>=s->specialMarkersModel->rowCount())) -// continue; -// -// // re-read the marker position: -// GeoCoordinates markerCoordinates; -// const bool isValid = d->htmlWidget->runScript2Coordinates( -// QString::fromLatin1("kgeomapGetMarkerPosition(%1);").arg(markerRow), -// &markerCoordinates -// ); -// -// GEOIFACE_ASSERT(isValid); -// -// if (!isValid) -// continue; -// -// // TODO: this discards the altitude! -// const QModelIndex markerIndex = s->specialMarkersModel->index(markerRow, 0); -// s->specialMarkersModel->setData(markerIndex, QVariant::fromValue(markerCoordinates), s->specialMarkersCoordinatesRole); -// -// movedMarkers << QPersistentModelIndex(markerIndex); +/* + // TODO: buffer this event type! + // marker moved + + bool okay = false; + const int markerRow = eventParameter.toInt(&okay); + GEOIFACE_ASSERT(okay); + + if (!okay) + { + continue; + } + + GEOIFACE_ASSERT(markerRow >= 0); + GEOIFACE_ASSERT(markerRowspecialMarkersModel->rowCount()); + + if ((markerRow<0)||(markerRow>=s->specialMarkersModel->rowCount())) + { + continue; + } + + // re-read the marker position: + + GeoCoordinates markerCoordinates; + const bool isValid = d->htmlWidget->runScript2Coordinates( + QString::fromLatin1("kgeomapGetMarkerPosition(%1);").arg(markerRow), + &markerCoordinates + ); + + GEOIFACE_ASSERT(isValid); + + if (!isValid) + { + continue; + } + + // TODO: this discards the altitude! + + const QModelIndex markerIndex = s->specialMarkersModel->index(markerRow, 0); + s->specialMarkersModel->setData(markerIndex, QVariant::fromValue(markerCoordinates), s->specialMarkersCoordinatesRole); + + movedMarkers << QPersistentModelIndex(markerIndex); +*/ } else if (eventCode == QLatin1String("do")) { // debug output: + qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("javascript:%1").arg(eventParameter); } } if (!movedClusters.isEmpty()) { qCDebug(DIGIKAM_GEOIFACE_LOG) << movedClusters; + emit signalClustersMoved(movedClusters, QPair(-1, QModelIndex())); } if (!movedMarkers.isEmpty()) { qCDebug(DIGIKAM_GEOIFACE_LOG) << movedMarkers; -// emit signalSpecialMarkersMoved(movedMarkers); +/* + emit signalSpecialMarkersMoved(movedMarkers); +*/ } if (!clickedClusters.isEmpty()) { emit signalClustersClicked(clickedClusters); } // now process the buffered events: + if (mapTypeChanged) { updateZoomMinMaxCache(); } if (zoomProbablyChanged && !mapTypeChanged) { d->cacheZoom = d->htmlWidget->runScript(QLatin1String("kgeomapGetZoom();"), false).toInt(); + emit signalZoomChanged(QString::fromLatin1("googlemaps:%1").arg(d->cacheZoom)); } if (centerProbablyChanged && !mapTypeChanged) { // there is nothing we can do if the coordinates are invalid - /*const bool isValid = */d->htmlWidget->runScript2Coordinates(QLatin1String("kgeomapGetCenter();"), &(d->cacheCenter)); +/* + const bool isValid = +*/ + d->htmlWidget->runScript2Coordinates(QLatin1String("kgeomapGetCenter();"), &(d->cacheCenter)); } // update the actions if necessary: + if (zoomProbablyChanged || mapTypeChanged || centerProbablyChanged) { updateActionAvailability(); } if (mapBoundsProbablyChanged) { const QString mapBoundsString = d->htmlWidget->runScript(QLatin1String("kgeomapGetBounds();"), false).toString(); const bool isValid = GeoIfaceHelperParseBoundsString(mapBoundsString, &d->cacheBounds); if (!isValid) { qCDebug(DIGIKAM_GEOIFACE_LOG) << "Invalid map bounds"; } } if (mapBoundsProbablyChanged || !movedClusters.isEmpty()) { s->worldMapWidget->markClustersAsDirty(); s->worldMapWidget->updateClusters(); } } void BackendGoogleMaps::updateClusters() { qCDebug(DIGIKAM_GEOIFACE_LOG) << "start updateclusters"; + // re-transfer the clusters to the map: + GEOIFACE_ASSERT(isReady()); if (!isReady()) + { return; + } // TODO: only update clusters that have actually changed! // re-transfer all markers to the javascript-part: + const bool canMoveItems = !s->showThumbnails && - s->modificationsAllowed && - s->markerModel->tilerFlags().testFlag(AbstractMarkerTiler::FlagMovable); + s->modificationsAllowed && + s->markerModel->tilerFlags().testFlag(AbstractMarkerTiler::FlagMovable); d->htmlWidget->runScript(QLatin1String("kgeomapClearClusters();")); d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetIsInEditMode(%1);").arg(s->showThumbnails?QLatin1String("false" ):QLatin1String("true" ))); for (int currentIndex = 0 ; currentIndex < s->clusterList.size() ; ++currentIndex) { const GeoIfaceCluster& currentCluster = s->clusterList.at(currentIndex); d->htmlWidget->runScript(QString::fromLatin1("kgeomapAddCluster(%1, %2, %3, %4, %5, %6);") .arg(currentIndex) .arg(currentCluster.coordinates.latString()) .arg(currentCluster.coordinates.lonString()) .arg(canMoveItems ? QLatin1String("true") : QLatin1String("false")) .arg(currentCluster.markerCount) .arg(currentCluster.markerSelectedCount) ); // TODO: for now, only set generated pixmaps when not in edit mode // this can be changed once we figure out how to appropriately handle // the selection state changes when a marker is dragged + if (s->showThumbnails) { QPoint clusterCenterPoint; + // TODO: who calculates the override values? + const QPixmap clusterPixmap = s->worldMapWidget->getDecoratedPixmapForCluster(currentIndex, nullptr, nullptr, &clusterCenterPoint); setClusterPixmap(currentIndex, clusterCenterPoint, clusterPixmap); } } + qCDebug(DIGIKAM_GEOIFACE_LOG) << "end updateclusters"; } bool BackendGoogleMaps::screenCoordinates(const GeoCoordinates& coordinates, QPoint* const point) { if (!d->isReady) + { return false; + } const QString pointStringResult = d->htmlWidget->runScript( QString::fromLatin1("kgeomapLatLngToPixel(%1, %2);") .arg(coordinates.latString()) .arg(coordinates.lonString()), - false - ).toString(); - const bool isValid = GeoIfaceHelperParseXYStringToPoint( - pointStringResult, - point); + false).toString(); + + const bool isValid = GeoIfaceHelperParseXYStringToPoint(pointStringResult, point); // TODO: apparently, even points outside the visible area are returned as valid // check whether they are actually visible + return isValid; } bool BackendGoogleMaps::geoCoordinates(const QPoint& point, GeoCoordinates* const coordinates) const { if (!d->isReady) + { return false; + } const bool isValid = d->htmlWidget->runScript2Coordinates( QString::fromLatin1("kgeomapPixelToLatLng(%1, %2);") .arg(point.x()) .arg(point.y()), coordinates); return isValid; } QSize BackendGoogleMaps::mapSize() const { GEOIFACE_ASSERT(d->htmlWidgetWrapper != nullptr); return d->htmlWidgetWrapper->size(); } void BackendGoogleMaps::slotFloatSettingsTriggered(QAction* action) { const QString actionIdString = action->data().toString(); const bool actionState = action->isChecked(); - if (actionIdString == QLatin1String("showmaptypecontrol")) + if (actionIdString == QLatin1String("showmaptypecontrol")) { setShowMapTypeControl(actionState); } else if (actionIdString == QLatin1String("shownavigationcontrol")) { setShowNavigationControl(actionState); } else if (actionIdString == QLatin1String("showscalecontrol")) { setShowScaleControl(actionState); } } void BackendGoogleMaps::setShowScaleControl(const bool state) { d->cacheShowScaleControl = state; if (d->showScaleControlAction) + { d->showScaleControlAction->setChecked(state); + } if (!isReady()) + { return; + } d->htmlWidget->runScript( QString::fromLatin1("kgeomapSetShowScaleControl(%1);") .arg(state ? QLatin1String("true") : QLatin1String("false"))); } void BackendGoogleMaps::setShowNavigationControl(const bool state) { d->cacheShowNavigationControl = state; if (d->showNavigationControlAction) + { d->showNavigationControlAction->setChecked(state); + } if (!isReady()) + { return; + } d->htmlWidget->runScript( QString::fromLatin1("kgeomapSetShowNavigationControl(%1);") .arg(state ? QLatin1String("true") : QLatin1String("false"))); } void BackendGoogleMaps::setShowMapTypeControl(const bool state) { d->cacheShowMapTypeControl = state; if (d->showMapTypeControlAction) + { d->showMapTypeControlAction->setChecked(state); + } if (!isReady()) + { return; + } d->htmlWidget->runScript( QString::fromLatin1("kgeomapSetShowMapTypeControl(%1);") .arg(state ? QLatin1String("true") : QLatin1String("false"))); } void BackendGoogleMaps::slotClustersNeedUpdating() { s->worldMapWidget->updateClusters(); } void BackendGoogleMaps::setZoom(const QString& newZoom) { const QString myZoomString = s->worldMapWidget->convertZoomToBackendZoom(newZoom, QLatin1String("googlemaps")); GEOIFACE_ASSERT(myZoomString.startsWith(QLatin1String("googlemaps:"))); const int myZoom = myZoomString.mid(QString::fromLatin1("googlemaps:").length()).toInt(); d->cacheZoom = myZoom; if (isReady()) { d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetZoom(%1);").arg(d->cacheZoom)); } } QString BackendGoogleMaps::getZoom() const { return QString::fromLatin1("googlemaps:%1").arg(d->cacheZoom); } int BackendGoogleMaps::getMarkerModelLevel() { GEOIFACE_ASSERT(isReady()); if (!isReady()) { return 0; } // get the current zoom level: + const int currentZoom = d->cacheZoom; int tileLevel = 0; - if (currentZoom == 0) { tileLevel = 1; } + + if (currentZoom == 0) { tileLevel = 1; } else if (currentZoom == 1) { tileLevel = 1; } else if (currentZoom == 2) { tileLevel = 1; } else if (currentZoom == 3) { tileLevel = 2; } else if (currentZoom == 4) { tileLevel = 2; } else if (currentZoom == 5) { tileLevel = 3; } else if (currentZoom == 6) { tileLevel = 3; } else if (currentZoom == 7) { tileLevel = 3; } else if (currentZoom == 8) { tileLevel = 4; } else if (currentZoom == 9) { tileLevel = 4; } else if (currentZoom == 10) { tileLevel = 4; } else if (currentZoom == 11) { tileLevel = 4; } else if (currentZoom == 12) { tileLevel = 4; } else if (currentZoom == 13) { tileLevel = 4; } else if (currentZoom == 14) { tileLevel = 5; } else if (currentZoom == 15) { tileLevel = 5; } else if (currentZoom == 16) { tileLevel = 6; } else if (currentZoom == 17) { tileLevel = 7; } else if (currentZoom == 18) { tileLevel = 7; } else if (currentZoom == 19) { tileLevel = 8; } else if (currentZoom == 20) { tileLevel = 9; } else if (currentZoom == 21) { tileLevel = 9; } else if (currentZoom == 22) { tileLevel = 9; } else { tileLevel = TileIndex::MaxLevel-1; } GEOIFACE_ASSERT(tileLevel <= TileIndex::MaxLevel-1); return tileLevel; } GeoCoordinates::PairList BackendGoogleMaps::getNormalizedBounds() { return GeoIfaceHelperNormalizeBounds(d->cacheBounds); } -// void BackendGoogleMaps::updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData) -// { -// if (!isReady()) -// return; -// -// if (!dragData) -// { -// d->htmlWidget->runScript("kgeomapRemoveDragMarker();"); -// } -// else -// { -// d->htmlWidget->runScript(QLatin1String("kgeomapSetDragMarker(%1, %2, %3, %4);") -// .arg(pos.x()) -// .arg(pos.y()) -// .arg(dragData->itemCount) -// .arg(dragData->itemCount) -// ); -// } -// -// // TODO: hide dragged markers on the map -// } -// -// void BackendGoogleMaps::updateDragDropMarkerPosition(const QPoint& pos) -// { -// // TODO: buffer this! -// if (!isReady()) -// return; -// -// d->htmlWidget->runScript(QLatin1String("kgeomapMoveDragMarker(%1, %2);") -// .arg(pos.x()) -// .arg(pos.y()) -// ); -// } +/* +void BackendGoogleMaps::updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData) +{ + if (!isReady()) + { + return; + } + + if (!dragData) + { + d->htmlWidget->runScript("kgeomapRemoveDragMarker();"); + } + else + { + d->htmlWidget->runScript(QLatin1String("kgeomapSetDragMarker(%1, %2, %3, %4);") + .arg(pos.x()) + .arg(pos.y()) + .arg(dragData->itemCount) + .arg(dragData->itemCount) + ); + } + + // TODO: hide dragged markers on the map +} + +void BackendGoogleMaps::updateDragDropMarkerPosition(const QPoint& pos) +{ + // TODO: buffer this! + + if (!isReady()) + { + return; + } + + d->htmlWidget->runScript(QLatin1String("kgeomapMoveDragMarker(%1, %2);") + .arg(pos.x()) + .arg(pos.y()) + ); +} +*/ void BackendGoogleMaps::updateActionAvailability() { if (!d->activeState || !isReady()) { return; } const QString currentMapType = getMapType(); const QList mapTypeActions = d->mapTypeActionGroup->actions(); for (int i = 0 ; i < mapTypeActions.size() ; ++i) { mapTypeActions.at(i)->setChecked(mapTypeActions.at(i)->data().toString()==currentMapType); } s->worldMapWidget->getControlAction(QLatin1String("zoomin"))->setEnabled(true/*d->cacheZoomcacheMaxZoom*/); s->worldMapWidget->getControlAction(QLatin1String("zoomout"))->setEnabled(true/*d->cacheZoom>d->cacheMinZoom*/); } void BackendGoogleMaps::updateZoomMinMaxCache() { // TODO: these functions seem to cause problems, the map is not fully updated after a few calls -// d->cacheMaxZoom = d->htmlWidget->runScript("kgeomapGetMaxZoom();", false).toInt(); -// d->cacheMinZoom = d->htmlWidget->runScript("kgeomapGetMinZoom();", false).toInt(); +/* + d->cacheMaxZoom = d->htmlWidget->runScript("kgeomapGetMaxZoom();", false).toInt(); + d->cacheMinZoom = d->htmlWidget->runScript("kgeomapGetMinZoom();", false).toInt(); +*/ } void BackendGoogleMaps::slotThumbnailAvailableForIndex(const QVariant& index, const QPixmap& pixmap) { qCDebug(DIGIKAM_GEOIFACE_LOG) << index<showThumbnails) + { return; + } // TODO: properly reject pixmaps with the wrong size + const int expectedThumbnailSize = s->worldMapWidget->getUndecoratedThumbnailSize(); if ((pixmap.size().height() > expectedThumbnailSize) || (pixmap.size().width() > expectedThumbnailSize)) + { return; + } // find the cluster which is represented by this index: + for (int i = 0 ; i < s->clusterList.count() ; ++i) { // TODO: use the right sortkey // TODO: let the representativeChooser handle the index comparison + const QVariant representativeMarker = s->worldMapWidget->getClusterRepresentativeMarker(i, s->sortKey); if (s->markerModel->indicesEqual(index, representativeMarker)) { QPoint clusterCenterPoint; + // TODO: who calculates the override values? + const QPixmap clusterPixmap = s->worldMapWidget->getDecoratedPixmapForCluster(i, nullptr, nullptr, &clusterCenterPoint); setClusterPixmap(i, clusterCenterPoint, clusterPixmap); break; } } } void BackendGoogleMaps::setClusterPixmap(const int clusterId, const QPoint& centerPoint, const QPixmap& clusterPixmap) { // decorate the pixmap: + const QPixmap styledPixmap = clusterPixmap; QByteArray bytes; QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); clusterPixmap.save(&buffer, "PNG"); buffer.close(); // http://www.faqs.org/rfcs/rfc2397.html + const QString imageData = QString::fromLatin1("data:image/png;base64,%1").arg(QString::fromLatin1(bytes.toBase64())); d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetClusterPixmap(%1,%5,%6,%2,%3,'%4');") .arg(clusterId) .arg(centerPoint.x()) .arg(centerPoint.y()) .arg(imageData) .arg(clusterPixmap.width()) .arg(clusterPixmap.height()) ); } void BackendGoogleMaps::setMarkerPixmap(const int modelId, const int markerId, const QPoint& centerPoint, const QPixmap& markerPixmap) { QByteArray bytes; QBuffer buffer(&bytes); buffer.open(QIODevice::WriteOnly); markerPixmap.save(&buffer, "PNG"); buffer.close(); // http://www.faqs.org/rfcs/rfc2397.html + const QString imageData = QString::fromLatin1("data:image/png;base64,%1").arg(QString::fromLatin1(bytes.toBase64())); d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetMarkerPixmap(%7,%1,%5,%6,%2,%3,'%4');") .arg(markerId) .arg(centerPoint.x()) .arg(centerPoint.y()) .arg(imageData) .arg(markerPixmap.width()) .arg(markerPixmap.height()) .arg(modelId) ); } void BackendGoogleMaps::setMarkerPixmap(const int modelId, const int markerId, const QPoint& centerPoint, const QSize& iconSize, const QUrl& iconUrl ) { /// @todo Sort the parameters + d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetMarkerPixmap(%7,%1,%5,%6,%2,%3,'%4');") .arg(markerId) .arg(centerPoint.x()) .arg(centerPoint.y()) .arg(iconUrl.url()) /// @todo Escape characters like apostrophe .arg(iconSize.width()) .arg(iconSize.height()) .arg(modelId) ); } bool BackendGoogleMaps::eventFilter(QObject* object, QEvent* event) { if (object == d->htmlWidgetWrapper) { if (event->type() == QEvent::Resize) { QResizeEvent* const resizeEvent = dynamic_cast(event); if (resizeEvent) { // TODO: the map div does not adjust its height properly if height=100%, // therefore we adjust it manually here + if (d->isReady) { d->htmlWidget->runScript(QString::fromLatin1("kgeomapWidgetResized(%1, %2)") .arg(d->htmlWidgetWrapper->width()) .arg(d->htmlWidgetWrapper->height())); } } } } return false; } void BackendGoogleMaps::regionSelectionChanged() { if (!d->htmlWidget) { return; } if (s->hasRegionSelection()) { d->htmlWidget->setSelectionRectangle(s->selectionRectangle); } else { d->htmlWidget->removeSelectionRectangle(); } } void BackendGoogleMaps::mouseModeChanged() { if (!d->htmlWidget) { return; } /// @todo Does htmlwidget read this value from s->currentMouseMode on its own? + d->htmlWidget->mouseModeChanged(s->currentMouseMode); } void BackendGoogleMaps::slotSelectionHasBeenMade(const Digikam::GeoCoordinates::Pair& searchCoordinates) { emit signalSelectionHasBeenMade(searchCoordinates); } void BackendGoogleMaps::centerOn( const Marble::GeoDataLatLonBox& latLonBox, const bool useSaneZoomLevel) { /// @todo Buffer this call if there is no widget or if inactive! + if (!d->htmlWidget) { return; } const qreal boxWest = latLonBox.west(Marble::GeoDataCoordinates::Degree); const qreal boxNorth = latLonBox.north(Marble::GeoDataCoordinates::Degree); const qreal boxEast = latLonBox.east(Marble::GeoDataCoordinates::Degree); const qreal boxSouth = latLonBox.south(Marble::GeoDataCoordinates::Degree); d->htmlWidget->centerOn(boxWest, boxNorth, boxEast, boxSouth, useSaneZoomLevel); qCDebug(DIGIKAM_GEOIFACE_LOG) << getZoom(); } void BackendGoogleMaps::setActive(const bool state) { const bool oldState = d->activeState; d->activeState = state; if (oldState != state) { if (!state && d->htmlWidgetWrapper) { // we should share our widget in the list of widgets in the global object + GeoIfaceInternalWidgetInfo info; info.deleteFunction = deleteInfoFunction; info.widget = d->htmlWidgetWrapper.data(); info.currentOwner = this; info.backendName = backendName(); - info.state = d->widgetIsDocked ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked; + info.state = d->widgetIsDocked ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked + : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked; GMInternalWidgetInfo intInfo; intInfo.htmlWidget = d->htmlWidget.data(); info.backendData.setValue(intInfo); GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->addMyInternalWidgetToPool(info); } if (state && d->htmlWidgetWrapper) { // we should remove our widget from the list of widgets in the global object + GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->removeMyInternalWidgetFromPool(this); /// @todo re-cluster, update markers? + setMapType(d->cacheMapType); setShowScaleControl(d->cacheShowScaleControl); setShowMapTypeControl(d->cacheShowMapTypeControl); setShowNavigationControl(d->cacheShowNavigationControl); setCenter(d->cacheCenter); d->htmlWidget->runScript(QString::fromLatin1("kgeomapSetZoom(%1);").arg(d->cacheZoom)); /// @TODO update tracks more gently + slotTracksChanged(d->trackChangeTracker); d->trackChangeTracker.clear(); } } } void BackendGoogleMaps::releaseWidget(GeoIfaceInternalWidgetInfo* const info) { // clear all tracks + d->htmlWidget->runScript(QString::fromLatin1("kgeomapClearTracks();")); disconnect(d->htmlWidget, SIGNAL(signalJavaScriptReady()), this, SLOT(slotHTMLInitialized())); disconnect(d->htmlWidget, SIGNAL(signalHTMLEvents(QStringList)), this, SLOT(slotHTMLEvents(QStringList))); disconnect(d->htmlWidget, SIGNAL(selectionHasBeenMade(Digikam::GeoCoordinates::Pair)), this, SLOT(slotSelectionHasBeenMade(Digikam::GeoCoordinates::Pair))); d->htmlWidget->setSharedGeoIfaceObject(nullptr); d->htmlWidgetWrapper->removeEventFilter(this); d->htmlWidget = nullptr; d->htmlWidgetWrapper = nullptr; info->currentOwner = nullptr; info->state = GeoIfaceInternalWidgetInfo::InternalWidgetReleased; d->isReady = false; emit signalBackendReadyChanged(backendName()); } void BackendGoogleMaps::mapWidgetDocked(const bool state) { if (d->widgetIsDocked != state) { GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->updatePooledWidgetState(d->htmlWidgetWrapper, state ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked); } d->widgetIsDocked = state; } void BackendGoogleMaps::deleteInfoFunction(GeoIfaceInternalWidgetInfo* const info) { if (info->currentOwner) { qobject_cast(info->currentOwner.data())->releaseWidget(info); } const GMInternalWidgetInfo intInfo = info->backendData.value(); delete intInfo.htmlWidget; delete info->widget.data(); } void BackendGoogleMaps::storeTrackChanges(const TrackManager::TrackChanges trackChanges) { for (int i = 0 ; i < d->trackChangeTracker.count() ; ++i) { if (d->trackChangeTracker.at(i).first == trackChanges.first) { d->trackChangeTracker[i].second = TrackManager::ChangeFlag(d->trackChangeTracker.at(i).second | trackChanges.second); + return; } } d->trackChangeTracker << trackChanges; } void BackendGoogleMaps::slotTrackManagerChanged() { /// @TODO disconnect old track manager /// @TODO mark all tracks as dirty if (s->trackManager) { connect(s->trackManager, SIGNAL(signalTracksChanged(QList)), this, SLOT(slotTracksChanged(QList))); connect(s->trackManager, SIGNAL(signalVisibilityChanged(bool)), this, SLOT(slotTrackVisibilityChanged(bool))); // store all tracks which are already in the manager as changed + const TrackManager::Track::List trackList = s->trackManager->getTrackList(); foreach (const TrackManager::Track& t, trackList) { storeTrackChanges(TrackManager::TrackChanges(t.id, TrackManager::ChangeAdd)); } } } void BackendGoogleMaps::slotTracksChanged(const QList trackChanges) { bool needToTrackChanges = !d->activeState; if (s->trackManager) { needToTrackChanges |= !s->trackManager->getVisibility(); } if (needToTrackChanges) { foreach (const TrackManager::TrackChanges& tc, trackChanges) { storeTrackChanges(tc); } return; } /// @TODO We have to re-read the tracks after being inactive. /// @TODO Tracks have to be cleared in JavaScript every time the /// htmlwidget is passed to another mapwidget. /// @TODO Clearing all tracks and re-adding them takes too long. We /// have to see which track changed, and whether coordinates or only properties changed. + if (!s->trackManager) { // no track manager, clear all tracks + const QVariant successClear = d->htmlWidget->runScript(QString::fromLatin1("kgeomapClearTracks();"), false); return; } foreach (const TrackManager::TrackChanges& tc, trackChanges) { if (tc.second & TrackManager::ChangeRemoved) { d->htmlWidget->runScript(QString::fromLatin1("kgeomapRemoveTrack(%1);").arg(tc.first)); } else { /// @TODO For now, remove the track and re-add it. + d->htmlWidget->runScript(QString::fromLatin1("kgeomapRemoveTrack(%1);").arg(tc.first)); const TrackManager::Track track = s->trackManager->getTrackById(tc.first); if (track.points.count() < 2) { continue; } const QString createTrackScript = QString::fromLatin1("kgeomapCreateTrack(%1,'%2');") .arg(track.id) .arg(track.color.name()); // QColor::name() returns #ff00ff d->htmlWidget->runScript(createTrackScript); QDateTime t1 = QDateTime::currentDateTime(); const int numPointsToPassAtOnce = 1000; for (int coordIdx = 0 ; coordIdx < track.points.count() ; coordIdx += numPointsToPassAtOnce) { /// @TODO Even by passing only a few points each time, we can /// block the UI for a long time. Instead, it may be better /// to call addPointsToTrack via the eventloop repeatedly /// to allow processing of other events. + addPointsToTrack(track.id, track.points, coordIdx, numPointsToPassAtOnce); } QDateTime t2 = QDateTime::currentDateTime(); qCDebug(DIGIKAM_GEOIFACE_LOG) << track.url.fileName() << t1.msecsTo(t2); } } } void BackendGoogleMaps::addPointsToTrack(const quint64 trackId, TrackManager::TrackPoint::List const& track, const int firstPoint, const int nPoints) { QString json; QTextStream jsonBuilder(&json); jsonBuilder << '['; int lastPoint = track.count()-1; if (nPoints > 0) { lastPoint = qMin(firstPoint + nPoints - 1, track.count()-1); } for (int coordIdx = firstPoint ; coordIdx <= lastPoint ; ++coordIdx) { GeoCoordinates const& coordinates = track.at(coordIdx).coordinates; if (coordIdx > firstPoint) { jsonBuilder << ','; } /// @TODO This looks like a lot of text to parse. Is there a more compact way? + jsonBuilder << "{\"lat\":" << coordinates.latString() << "," << "\"lon\":" << coordinates.lonString() << "}"; } jsonBuilder << ']'; const QString addTrackScript = QString::fromLatin1("kgeomapAddToTrack(%1,'%2');").arg(trackId).arg(json); d->htmlWidget->runScript(addTrackScript); } void BackendGoogleMaps::slotTrackVisibilityChanged(const bool newState) { /// @TODO Now we remove all tracks and re-add them on visibility change. /// This is very slow. - if (newState) + + if (newState) { // store all tracks which are already in the manager as changed + const TrackManager::Track::List trackList = s->trackManager->getTrackList(); QList trackChanges; foreach (const TrackManager::Track& t, trackList) { trackChanges << TrackManager::TrackChanges(t.id, TrackManager::ChangeAdd); } slotTracksChanged(trackChanges); } else if (d->htmlWidget) { const QVariant successClear = d->htmlWidget->runScript(QString::fromLatin1("kgeomapClearTracks();"), false); } } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/backends/backendgooglemaps.h b/core/utilities/geolocation/geoiface/backends/backendgooglemaps.h index 25f89e48cf..65f1eb8473 100644 --- a/core/utilities/geolocation/geoiface/backends/backendgooglemaps.h +++ b/core/utilities/geolocation/geoiface/backends/backendgooglemaps.h @@ -1,138 +1,138 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-01 * Description : Google-Maps-backend for geolocation interface * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2009-2011 by Michael G. Hansen * Copyright (C) 2014 by Justus Schwartz * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_BACKEND_GOOGLE_MAPS_H #define DIGIKAM_BACKEND_GOOGLE_MAPS_H // Local includes #include "mapbackend.h" #include "trackmanager.h" #include "digikam_export.h" namespace Digikam { class DIGIKAM_EXPORT BackendGoogleMaps : public MapBackend { Q_OBJECT public: explicit BackendGoogleMaps(const QExplicitlySharedDataPointer& sharedData, QObject* const parent = nullptr); virtual ~BackendGoogleMaps(); virtual QString backendName() const override; virtual QString backendHumanName() const override; virtual QWidget* mapWidget() override; virtual void releaseWidget(GeoIfaceInternalWidgetInfo* const info) override; virtual void mapWidgetDocked(const bool state) override; virtual GeoCoordinates getCenter() const override; virtual void setCenter(const GeoCoordinates& coordinate) override; virtual bool isReady() const override; virtual void zoomIn() override; virtual void zoomOut() override; virtual void saveSettingsToGroup(KConfigGroup* const group) override; virtual void readSettingsFromGroup(const KConfigGroup* const group) override; virtual void addActionsToConfigurationMenu(QMenu* const configurationMenu) override; virtual void updateMarkers() override; virtual void updateClusters() override; virtual bool screenCoordinates(const GeoCoordinates& coordinates, QPoint* const point) override; virtual bool geoCoordinates(const QPoint& point, GeoCoordinates* const coordinates) const override; virtual QSize mapSize() const override; virtual void setZoom(const QString& newZoom) override; virtual QString getZoom() const override; virtual int getMarkerModelLevel() override; virtual GeoCoordinates::PairList getNormalizedBounds() override; - -// virtual void updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData); -// virtual void updateDragDropMarkerPosition(const QPoint& pos); - +/* + virtual void updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData); + virtual void updateDragDropMarkerPosition(const QPoint& pos); +*/ virtual void updateActionAvailability() override; QString getMapType() const; void setMapType(const QString& newMapType); void setShowMapTypeControl(const bool state); void setShowScaleControl(const bool state); void setShowNavigationControl(const bool state); virtual void regionSelectionChanged() override; virtual void mouseModeChanged() override; virtual void centerOn(const Marble::GeoDataLatLonBox& latLonBox, const bool useSaneZoomLevel) override; virtual void setActive(const bool state) override; public Q_SLOTS: virtual void slotClustersNeedUpdating() override; virtual void slotThumbnailAvailableForIndex(const QVariant& index, const QPixmap& pixmap) override; void slotUngroupedModelChanged(const int mindex); protected: bool eventFilter(QObject* object, QEvent* event) override; void createActions(); void setClusterPixmap(const int clusterId, const QPoint& centerPoint, const QPixmap& clusterPixmap); void setMarkerPixmap(const int modelId, const int markerId, const QPoint& centerPoint, const QPixmap& markerPixmap); void setMarkerPixmap(const int modelId, const int markerId, const QPoint& centerPoint, const QSize& iconSize, const QUrl& iconUrl); void storeTrackChanges(const TrackManager::TrackChanges trackChanges); private Q_SLOTS: void slotHTMLInitialized(); void slotSetCenterTimer(); void slotMapTypeActionTriggered(QAction* action); void slotHTMLEvents(const QStringList& eventStrings); void slotFloatSettingsTriggered(QAction* action); void slotSelectionHasBeenMade(const Digikam::GeoCoordinates::Pair& searchCoordinates); void slotTrackManagerChanged() override; void slotTracksChanged(const QList trackChanges); void slotTrackVisibilityChanged(const bool newState); private: void updateZoomMinMaxCache(); static void deleteInfoFunction(GeoIfaceInternalWidgetInfo* const info); void addPointsToTrack(const quint64 trackId, TrackManager::TrackPoint::List const& track, const int firstPoint, const int nPoints); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_BACKEND_GOOGLE_MAPS_H diff --git a/core/utilities/geolocation/geoiface/backends/backendmarble.cpp b/core/utilities/geolocation/geoiface/backends/backendmarble.cpp index ceff2d9432..b9b758a329 100644 --- a/core/utilities/geolocation/geoiface/backends/backendmarble.cpp +++ b/core/utilities/geolocation/geoiface/backends/backendmarble.cpp @@ -1,1915 +1,2007 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-08 * Description : Marble-backend for geolocation interface * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2009-2011 by Michael G. Hansen * Copyright (C) 2014 by Justus Schwartz * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "backendmarble.h" // Qt includes #include #include #include #include // KDE includes #include #include // Marble includes #include #include #include #include #include #include #include #include // Local includes #include "backendmarblelayer.h" #include "abstractmarkertiler.h" #include "mapwidget.h" #include "geomodelhelper.h" #include "trackmanager.h" #include "digikam_debug.h" namespace Digikam { class Q_DECL_HIDDEN BMInternalWidgetInfo { public: explicit BMInternalWidgetInfo() + : bmLayer(nullptr) { - bmLayer = nullptr; } BackendMarbleLayer* bmLayer; }; } // namespace Digikam Q_DECLARE_METATYPE(Digikam::BMInternalWidgetInfo) namespace Digikam { class Q_DECL_HIDDEN BackendMarble::Private { public: explicit Private() : marbleWidget(nullptr), actionGroupMapTheme(nullptr), actionGroupProjection(nullptr), actionGroupFloatItems(nullptr), actionShowCompass(nullptr), actionShowScaleBar(nullptr), actionShowNavigation(nullptr), actionShowOverviewMap(nullptr), cacheMapTheme(QLatin1String("atlas")), cacheProjection(QLatin1String("spherical")), cacheShowCompass(false), cacheShowScaleBar(false), cacheShowNavigation(false), cacheShowOverviewMap(false), cacheZoom(900), havePotentiallyMouseMovingObject(false), haveMouseMovingObject(false), mouseMoveClusterIndex(-1), mouseMoveMarkerIndex(), mouseMoveObjectCoordinates(), mouseMoveCenterOffset(0, 0), dragDropMarkerCount(0), dragDropMarkerPos(), clustersDirtyCacheProjection(), clustersDirtyCacheLat(), clustersDirtyCacheLon(), displayedRectangle(), firstSelectionScreenPoint(), firstSelectionPoint(), activeState(false), widgetIsDocked(false), blockingZoomWhileChangingTheme(false), trackCache(), bmLayer(nullptr) { } QPointer marbleWidget; QActionGroup* actionGroupMapTheme; QActionGroup* actionGroupProjection; QActionGroup* actionGroupFloatItems; QAction* actionShowCompass; QAction* actionShowScaleBar; QAction* actionShowNavigation; QAction* actionShowOverviewMap; QString cacheMapTheme; QString cacheProjection; bool cacheShowCompass; bool cacheShowScaleBar; bool cacheShowNavigation; bool cacheShowOverviewMap; int cacheZoom; bool havePotentiallyMouseMovingObject; bool haveMouseMovingObject; int mouseMoveClusterIndex; QPersistentModelIndex mouseMoveMarkerIndex; GeoCoordinates mouseMoveObjectCoordinates; QPoint mouseMoveCenterOffset; int dragDropMarkerCount; QPoint dragDropMarkerPos; int clustersDirtyCacheProjection; qreal clustersDirtyCacheLat; qreal clustersDirtyCacheLon; GeoCoordinates::Pair displayedRectangle; QPoint firstSelectionScreenPoint; QPoint intermediateSelectionScreenPoint; GeoCoordinates firstSelectionPoint; GeoCoordinates intermediateSelectionPoint; bool activeState; bool widgetIsDocked; bool blockingZoomWhileChangingTheme; QHash trackCache; BackendMarbleLayer* bmLayer; }; BackendMarble::BackendMarble(const QExplicitlySharedDataPointer& sharedData, QObject* const parent) : MapBackend(sharedData, parent), d(new Private()) { createActions(); } BackendMarble::~BackendMarble() { /// @todo Should we leave our widget in this list and not destroy it? + GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->removeMyInternalWidgetFromPool(this); if (d->marbleWidget) { d->marbleWidget->removeLayer(d->bmLayer); delete d->marbleWidget; } delete d; } QString BackendMarble::backendName() const { return QLatin1String("marble"); } QString BackendMarble::backendHumanName() const { return i18n("Marble Virtual Globe"); } QWidget* BackendMarble::mapWidget() { if (!d->marbleWidget) { GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); GeoIfaceInternalWidgetInfo info; if (go->getInternalWidgetFromPool(this, &info)) { d->marbleWidget = qobject_cast(info.widget); const BMInternalWidgetInfo intInfo = info.backendData.value(); d->bmLayer = intInfo.bmLayer; if (d->bmLayer) { d->bmLayer->setBackend(this); } else { qCWarning(DIGIKAM_GEOIFACE_LOG) << "Marble widget instance is null!"; } } else { d->marbleWidget = new Marble::MarbleWidget(); d->bmLayer = new BackendMarbleLayer(this); d->marbleWidget->addLayer(d->bmLayer); } // hide Marble bookmark action in the right button context menu // TODO: check with a new Marble version if the bookmark action position is still valid + if (d->marbleWidget->popupMenu()) { QList actions = d->marbleWidget->popupMenu()->findChildren(); - if (actions.count() > 4 && actions[4]) + if ((actions.count() > 4) && actions[4]) + { actions[4]->setVisible(false); + } } d->marbleWidget->installEventFilter(this); connect(d->marbleWidget, SIGNAL(zoomChanged(int)), this, SLOT(slotMarbleZoomChanged())); // set a backend first /// @todo Do this only if we are set active! + applyCacheToWidget(); emit signalBackendReadyChanged(backendName()); } return d->marbleWidget; } void BackendMarble::releaseWidget(GeoIfaceInternalWidgetInfo* const info) { info->widget->removeEventFilter(this); BMInternalWidgetInfo intInfo = info->backendData.value(); if (intInfo.bmLayer) { intInfo.bmLayer->setBackend(nullptr); } disconnect(d->marbleWidget, SIGNAL(zoomChanged(int)), this, SLOT(slotMarbleZoomChanged())); info->currentOwner = nullptr; info->state = GeoIfaceInternalWidgetInfo::InternalWidgetReleased; d->marbleWidget = nullptr; d->bmLayer = nullptr; emit signalBackendReadyChanged(backendName()); } GeoCoordinates BackendMarble::getCenter() const { if (!d->marbleWidget) { return GeoCoordinates(); } return GeoCoordinates(d->marbleWidget->centerLatitude(), d->marbleWidget->centerLongitude()); } void BackendMarble::setCenter(const GeoCoordinates& coordinate) { if (!d->marbleWidget) { return; } d->marbleWidget->setCenterLatitude(coordinate.lat()); d->marbleWidget->setCenterLongitude(coordinate.lon()); } bool BackendMarble::isReady() const { return d->marbleWidget != nullptr; } void BackendMarble::zoomIn() { if (!d->marbleWidget) { return; } d->marbleWidget->zoomIn(); d->marbleWidget->repaint(); } void BackendMarble::zoomOut() { if (!d->marbleWidget) { return; } d->marbleWidget->zoomOut(); d->marbleWidget->repaint(); } void BackendMarble::createActions() { // map theme: + d->actionGroupMapTheme = new QActionGroup(this); d->actionGroupMapTheme->setExclusive(true); connect(d->actionGroupMapTheme, &QActionGroup::triggered, this, &BackendMarble::slotMapThemeActionTriggered); QAction* const actionAtlas = new QAction(d->actionGroupMapTheme); actionAtlas->setCheckable(true); actionAtlas->setText(i18n("Atlas map")); actionAtlas->setData(QLatin1String("atlas")); QAction* const actionOpenStreetmap = new QAction(d->actionGroupMapTheme); actionOpenStreetmap->setCheckable(true); actionOpenStreetmap->setText(i18n("OpenStreetMap")); actionOpenStreetmap->setData(QLatin1String("openstreetmap")); // projection: + d->actionGroupProjection = new QActionGroup(this); d->actionGroupProjection->setExclusive(true); connect(d->actionGroupProjection, &QActionGroup::triggered, this, &BackendMarble::slotProjectionActionTriggered); QAction* const actionSpherical = new QAction(d->actionGroupProjection); actionSpherical->setCheckable(true); actionSpherical->setText(i18nc("Spherical projection", "Spherical")); actionSpherical->setData(QLatin1String("spherical")); QAction* const actionMercator = new QAction(d->actionGroupProjection); actionMercator->setCheckable(true); actionMercator->setText(i18n("Mercator")); actionMercator->setData(QLatin1String("mercator")); QAction* const actionEquirectangular = new QAction(d->actionGroupProjection); actionEquirectangular->setCheckable(true); actionEquirectangular->setText(i18n("Equirectangular")); actionEquirectangular->setData(QLatin1String("equirectangular")); // float items: + d->actionGroupFloatItems = new QActionGroup(this); d->actionGroupFloatItems->setExclusive(false); connect(d->actionGroupFloatItems, &QActionGroup::triggered, this, &BackendMarble::slotFloatSettingsTriggered); d->actionShowCompass = new QAction(i18n("Show compass"), d->actionGroupFloatItems); d->actionShowCompass->setData(QLatin1String("showcompass")); d->actionShowCompass->setCheckable(true); d->actionGroupFloatItems->addAction(d->actionShowCompass); d->actionShowScaleBar = new QAction(i18n("Show scale bar"), d->actionGroupFloatItems); d->actionShowScaleBar->setData(QLatin1String("showscalebar")); d->actionShowScaleBar->setCheckable(true); d->actionGroupFloatItems->addAction(d->actionShowScaleBar); d->actionShowNavigation = new QAction(i18n("Show navigation"), d->actionGroupFloatItems); d->actionShowNavigation->setData(QLatin1String("shownavigation")); d->actionShowNavigation->setCheckable(true); d->actionGroupFloatItems->addAction(d->actionShowNavigation); d->actionShowOverviewMap = new QAction(i18n("Show overview map"), d->actionGroupFloatItems); d->actionShowOverviewMap->setData(QLatin1String("showoverviewmap")); d->actionShowOverviewMap->setCheckable(true); d->actionGroupFloatItems->addAction(d->actionShowOverviewMap); } void BackendMarble::addActionsToConfigurationMenu(QMenu* const configurationMenu) { GEOIFACE_ASSERT(configurationMenu != nullptr); configurationMenu->addSeparator(); const QList mapThemeActions = d->actionGroupMapTheme->actions(); for (int i = 0 ; i < mapThemeActions.count() ; ++i) { configurationMenu->addAction(mapThemeActions.at(i)); } configurationMenu->addSeparator(); // TODO: we need a parent for this guy! + QMenu* const projectionSubMenu = new QMenu(i18n("Projection"), configurationMenu); configurationMenu->addMenu(projectionSubMenu); const QList projectionActions = d->actionGroupProjection->actions(); for (int i = 0 ; i < projectionActions.count() ; ++i) { projectionSubMenu->addAction(projectionActions.at(i)); } QMenu* const floatItemsSubMenu = new QMenu(i18n("Float items"), configurationMenu); configurationMenu->addMenu(floatItemsSubMenu); const QList floatActions = d->actionGroupFloatItems->actions(); for (int i = 0 ; i < floatActions.count() ; ++i) { floatItemsSubMenu->addAction(floatActions.at(i)); } updateActionAvailability(); } void BackendMarble::slotMapThemeActionTriggered(QAction* action) { setMapTheme(action->data().toString()); } QString BackendMarble::getMapTheme() const { // TODO: read the theme from the marblewidget! + return d->cacheMapTheme; } void BackendMarble::setMapTheme(const QString& newMapTheme) { d->cacheMapTheme = newMapTheme; if (!d->marbleWidget) { return; } // Changing the map theme changes the zoom - we want to try to keep the zoom constant + d->blockingZoomWhileChangingTheme = true; // Remember the zoom from the cache. The zoom of the widget may not have been set yet! + const int oldMarbleZoom = d->cacheZoom; - if (newMapTheme == QLatin1String("atlas")) + if (newMapTheme == QLatin1String("atlas")) { d->marbleWidget->setMapThemeId(QLatin1String("earth/srtm/srtm.dgml")); } else if (newMapTheme == QLatin1String("openstreetmap")) { d->marbleWidget->setMapThemeId(QLatin1String("earth/openstreetmap/openstreetmap.dgml")); } // the float items are reset when the theme is changed: + setShowCompass(d->cacheShowCompass); setShowScaleBar(d->cacheShowScaleBar); setShowNavigation(d->cacheShowNavigation); setShowOverviewMap(d->cacheShowOverviewMap); // make sure the zoom level is within the allowed range + int targetZoomLevel = oldMarbleZoom; - if (oldMarbleZoom > d->marbleWidget->maximumZoom()) + if (oldMarbleZoom > d->marbleWidget->maximumZoom()) { targetZoomLevel = d->marbleWidget->maximumZoom(); } else if (oldMarbleZoom < d->marbleWidget->minimumZoom()) { targetZoomLevel = d->marbleWidget->minimumZoom(); } - if (targetZoomLevel!=oldMarbleZoom) + if (targetZoomLevel != oldMarbleZoom) { // our zoom level had to be adjusted, therefore unblock // the signal now to allow the change to propagate + d->blockingZoomWhileChangingTheme = false; } d->marbleWidget->zoomView(targetZoomLevel); d->blockingZoomWhileChangingTheme = false; updateActionAvailability(); } void BackendMarble::saveSettingsToGroup(KConfigGroup* const group) { GEOIFACE_ASSERT(group != nullptr); if (!group) { return; } group->writeEntry("Marble Map Theme", d->cacheMapTheme); group->writeEntry("Marble Projection", d->cacheProjection); group->writeEntry("Marble Show Compass", d->cacheShowCompass); group->writeEntry("Marble Show Scale Bar", d->cacheShowScaleBar); group->writeEntry("Marble Show Navigation", d->cacheShowNavigation); group->writeEntry("Marble Show Overview Map", d->cacheShowOverviewMap); } void BackendMarble::readSettingsFromGroup(const KConfigGroup* const group) { GEOIFACE_ASSERT(group != nullptr); if (!group) { return; } setMapTheme(group->readEntry("Marble Map Theme", d->cacheMapTheme)); setProjection(group->readEntry("Marble Projection", d->cacheProjection)); setShowCompass(group->readEntry("Marble Show Compass", d->cacheShowCompass)); setShowScaleBar(group->readEntry("Marble Show Scale Bar", d->cacheShowScaleBar)); setShowNavigation(group->readEntry("Marble Show Navigation", d->cacheShowNavigation)); setShowOverviewMap(group->readEntry("Marble Show Overview Map", d->cacheShowOverviewMap)); } void BackendMarble::updateMarkers() { if (!d->marbleWidget) { return; } // just redraw, that's it: + d->marbleWidget->update(); } bool BackendMarble::screenCoordinates(const GeoCoordinates& coordinates, QPoint* const point) { if (!d->marbleWidget) { return false; } if (!coordinates.hasCoordinates()) { return false; } qreal x, y; const bool isVisible = d->marbleWidget->screenCoordinates(coordinates.lon(), coordinates.lat(), x, y); if (!isVisible) { return false; } if (point) { *point = QPoint(x, y); } return true; } bool BackendMarble::geoCoordinates(const QPoint& point, GeoCoordinates* const coordinates) const { if (!d->marbleWidget) { return false; } // apparently, MarbleWidget::GeoCoordinates can return true even if the object is not on the screen // check that the point is in the visible range: + if (!d->marbleWidget->rect().contains(point)) { return false; } qreal lat, lon; const bool isVisible = d->marbleWidget->geoCoordinates(point.x(), point.y(), lon, lat, Marble::GeoDataCoordinates::Degree); if (!isVisible) { return false; } if (coordinates) { *coordinates = GeoCoordinates(lat, lon); } return true; } /** * @brief Replacement for Marble::GeoPainter::drawPixmap which takes a pixel offset * * @param painter Marble::GeoPainter on which to draw the pixmap * @param pixmap Pixmap to be drawn * @param coordinates GeoCoordinates where the image is to be drawn * @param offsetPoint Point in the @p pixmap which should be at @p coordinates */ void BackendMarble::GeoPainter_drawPixmapAtCoordinates(Marble::GeoPainter* const painter, const QPixmap& pixmap, const GeoCoordinates& coordinates, const QPoint& offsetPoint) { // base point starts at the top left of the pixmap // try to convert the coordinates to pixels + QPoint pointOnScreen; if (!screenCoordinates(coordinates, &pointOnScreen)) { return; } // Marble::GeoPainter::drawPixmap(coordinates, pixmap) draws the pixmap centered on coordinates // therefore we calculate the pixel position of the center of the image if its offsetPoint // is to be at pointOnScreen: + const QSize pixmapSize = pixmap.size(); const QPoint pixmapHalfSize = QPoint(pixmapSize.width() / 2, pixmapSize.height() / 2); const QPoint drawPoint = pointOnScreen + pixmapHalfSize - offsetPoint; // now re-calculate the coordinates of the new pixel coordinates: + GeoCoordinates drawGeoCoordinates; if (!geoCoordinates(drawPoint, &drawGeoCoordinates)) { return; } // convert to Marble datatype and draw: + const Marble::GeoDataCoordinates mcoord = drawGeoCoordinates.toMarbleCoordinates(); painter->drawPixmap(mcoord, pixmap); } void BackendMarble::marbleCustomPaint(Marble::GeoPainter* painter) { if (!d->activeState) { return; } // check whether the parameters of the map changed and we may have to update the clusters: + if ((d->clustersDirtyCacheLat != d->marbleWidget->centerLatitude()) || (d->clustersDirtyCacheLon != d->marbleWidget->centerLongitude()) || (d->clustersDirtyCacheProjection != d->marbleWidget->projection())) { /* qCDebug(DIGIKAM_GEOIFACE_LOG) << d->marbleWidget->centerLatitude() << d->marbleWidget->centerLongitude() << d->marbleWidget->projection(); */ d->clustersDirtyCacheLat = d->marbleWidget->centerLatitude(); d->clustersDirtyCacheLon = d->marbleWidget->centerLongitude(); d->clustersDirtyCacheProjection = d->marbleWidget->projection(); s->worldMapWidget->markClustersAsDirty(); } painter->save(); if (s->trackManager) { if (s->trackManager->getVisibility()) { TrackManager::Track::List const& tracks = s->trackManager->getTrackList(); for (int trackIdx = 0 ; trackIdx < tracks.count() ; ++trackIdx) { TrackManager::Track const& track = tracks.at(trackIdx); if (track.points.count() < 2) { continue; } Marble::GeoDataLineString lineString; if (d->trackCache.contains(track.id)) { lineString = d->trackCache.value(track.id); } else { for (int coordIdx = 0 ; coordIdx < track.points.count() ; ++coordIdx) { GeoCoordinates const& coordinates = track.points.at(coordIdx).coordinates; const Marble::GeoDataCoordinates marbleCoordinates = coordinates.toMarbleCoordinates(); lineString << marbleCoordinates; } d->trackCache.insert(track.id, lineString); } /// @TODO looks a bit too thick IMHO when you zoom out. /// Maybe adjust to zoom level? + QColor trackColor = track.color; trackColor.setAlpha(180); painter->setPen(QPen(QBrush(trackColor),5)); painter->drawPolyline(lineString); } } } for (int i = 0 ; i < s->ungroupedModels.count() ; ++i) { GeoModelHelper* const modelHelper = s->ungroupedModels.at(i); if (!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagVisible)) + { continue; + } QAbstractItemModel* const model = modelHelper->model(); // render all visible markers: + for (int row = 0 ; row < model->rowCount() ; ++row) { const QModelIndex currentIndex = model->index(row, 0); GeoCoordinates markerCoordinates; if (!modelHelper->itemCoordinates(currentIndex, &markerCoordinates)) + { continue; + } // is the marker being moved right now? + if (currentIndex == d->mouseMoveMarkerIndex) { markerCoordinates = d->mouseMoveObjectCoordinates; } QPoint markerPoint; if (!screenCoordinates(markerCoordinates, &markerPoint)) { /// @todo This check does not work properly in all cases! // the marker is not visible + continue; } QPoint markerOffsetPoint; QPixmap markerPixmap; const bool haveMarkerPixmap = modelHelper->itemIcon(currentIndex, &markerOffsetPoint, nullptr, &markerPixmap, nullptr); if (!haveMarkerPixmap || markerPixmap.isNull()) { markerPixmap = GeoIfaceGlobalObject::instance()->getStandardMarkerPixmap(); markerOffsetPoint = QPoint(markerPixmap.width() / 2, markerPixmap.height() - 1); } GeoPainter_drawPixmapAtCoordinates(painter, markerPixmap, markerCoordinates, markerOffsetPoint); } } int markersInMovingCluster = 0; if (s->markerModel) { // now for the clusters: + s->worldMapWidget->updateClusters(); for (int i = 0 ; i < s->clusterList.size() ; ++i) { const GeoIfaceCluster& cluster = s->clusterList.at(i); GeoCoordinates clusterCoordinates = cluster.coordinates; int markerCountOverride = cluster.markerCount; GeoGroupState selectionStateOverride = cluster.groupState; if (d->haveMouseMovingObject && (d->mouseMoveClusterIndex >= 0)) { - bool movingSelectedMarkers - = s->clusterList.at(d->mouseMoveClusterIndex).groupState != SelectedNone; + bool movingSelectedMarkers = s->clusterList.at(d->mouseMoveClusterIndex).groupState != SelectedNone; - if (movingSelectedMarkers) + if (movingSelectedMarkers) { markersInMovingCluster += cluster.markerSelectedCount; markerCountOverride -= cluster.markerSelectedCount; selectionStateOverride = SelectedNone; } else if (d->mouseMoveClusterIndex == i) { markerCountOverride = 0; } if (markerCountOverride == 0) + { continue; + } } QPoint clusterPoint; if (!screenCoordinates(clusterCoordinates, &clusterPoint)) { /// @todo This check does not work properly in all cases! // cluster is not visible + continue; } QPoint clusterOffsetPoint; const QPixmap clusterPixmap = s->worldMapWidget->getDecoratedPixmapForCluster(i, &selectionStateOverride, &markerCountOverride, &clusterOffsetPoint); GeoPainter_drawPixmapAtCoordinates(painter, clusterPixmap, clusterCoordinates, clusterOffsetPoint); } } // now render the mouse-moving cluster, if there is one: + if (d->haveMouseMovingObject && (d->mouseMoveClusterIndex >= 0)) { const GeoIfaceCluster& cluster = s->clusterList.at(d->mouseMoveClusterIndex); GeoCoordinates clusterCoordinates = d->mouseMoveObjectCoordinates; int markerCountOverride = (markersInMovingCluster>0)?markersInMovingCluster:cluster.markerCount; GeoGroupState selectionStateOverride = cluster.groupState; QPoint clusterPoint; if (screenCoordinates(clusterCoordinates, &clusterPoint)) { // determine the colors: + QColor fillColor; QColor strokeColor; Qt::PenStyle strokeStyle; QColor labelColor; QString labelText; s->worldMapWidget->getColorInfos(d->mouseMoveClusterIndex, &fillColor, &strokeColor, &strokeStyle, &labelText, &labelColor, &selectionStateOverride, &markerCountOverride); QString pixmapName = fillColor.name().mid(1); if (cluster.groupState == SelectedAll) { pixmapName += QLatin1String("-selected"); } if (cluster.groupState == SelectedSome) { pixmapName += QLatin1String("-someselected"); } const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName); painter->drawPixmap(clusterPoint.x() - markerPixmap.width() / 2, clusterPoint.y() - markerPixmap.height() - 1, markerPixmap); } } // now render the drag-and-drop marker, if there is one: + if (d->dragDropMarkerCount>0) { // determine the colors: + QColor fillColor; QColor strokeColor; Qt::PenStyle strokeStyle; QColor labelColor; QString labelText; s->worldMapWidget->getColorInfos(SelectedAll, d->dragDropMarkerCount, &fillColor, &strokeColor, &strokeStyle, &labelText, &labelColor); QString pixmapName = fillColor.name().mid(1); pixmapName += QLatin1String("-selected"); const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName); painter->drawPixmap(d->dragDropMarkerPos.x() - markerPixmap.width() / 2, d->dragDropMarkerPos.y() - markerPixmap.height() - 1, markerPixmap); } // here we draw the selection rectangle which is being made by the user right now /// @todo merge drawing of the rectangles into one function + if (d->displayedRectangle.first.hasCoordinates()) { drawSearchRectangle(painter, d->displayedRectangle, false); } // draw the current or old search rectangle + if (s->selectionRectangle.first.hasCoordinates()) { drawSearchRectangle(painter, s->selectionRectangle, d->intermediateSelectionPoint.hasCoordinates()); } painter->restore(); } QString BackendMarble::getProjection() const { /// @todo Do we actually need to read out the projection from the widget??? + if (d->marbleWidget) { const Marble::Projection currentProjection = d->marbleWidget->projection(); switch (currentProjection) { case Marble::Equirectangular: d->cacheProjection = QLatin1String("equirectangular"); break; case Marble::Mercator: d->cacheProjection = QLatin1String("mercator"); break; default: case Marble::Spherical: d->cacheProjection = QLatin1String("spherical"); break; } } return d->cacheProjection; } void BackendMarble::setProjection(const QString& newProjection) { d->cacheProjection = newProjection; if (d->marbleWidget) { - if (newProjection == QLatin1String("equirectangular")) + if (newProjection == QLatin1String("equirectangular")) { d->marbleWidget->setProjection(Marble::Equirectangular); } else if (newProjection == QLatin1String("mercator")) { d->marbleWidget->setProjection(Marble::Mercator); } else // "spherical" { d->marbleWidget->setProjection(Marble::Spherical); } } updateActionAvailability(); } void BackendMarble::slotProjectionActionTriggered(QAction* action) { setProjection(action->data().toString()); } void BackendMarble::setShowCompass(const bool state) { d->cacheShowCompass = state; updateActionAvailability(); if (d->marbleWidget) { Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("compass")); if (item) { item->setVisible(state); } } } void BackendMarble::setShowScaleBar(const bool state) { d->cacheShowScaleBar = state; updateActionAvailability(); if (d->marbleWidget) { Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("scalebar")); if (item) { item->setVisible(state); } } } void BackendMarble::setShowNavigation(const bool state) { d->cacheShowNavigation = state; updateActionAvailability(); if (d->marbleWidget) { Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("navigation")); if (item) { item->setVisible(state); } } } void BackendMarble::setShowOverviewMap(const bool state) { d->cacheShowOverviewMap = state; updateActionAvailability(); if (d->marbleWidget) { Marble::AbstractFloatItem* const item = d->marbleWidget->floatItem(QLatin1String("overviewmap")); if (item) { item->setVisible(state); } } } void BackendMarble::slotFloatSettingsTriggered(QAction* action) { const QString actionIdString = action->data().toString(); const bool actionState = action->isChecked(); - if (actionIdString == QLatin1String("showcompass")) + if (actionIdString == QLatin1String("showcompass")) { setShowCompass(actionState); } else if (actionIdString == QLatin1String("showscalebar")) { setShowScaleBar(actionState); } else if (actionIdString == QLatin1String("shownavigation")) { setShowNavigation(actionState); } else if (actionIdString == QLatin1String("showoverviewmap")) { setShowOverviewMap(actionState); } } void BackendMarble::slotClustersNeedUpdating() { if (!d->marbleWidget) { return; } // tell the widget to redraw: + d->marbleWidget->update(); } void BackendMarble::updateClusters() { // clusters are only needed during redraw } QSize BackendMarble::mapSize() const { return d->marbleWidget->size(); } void BackendMarble::slotMarbleZoomChanged() { // ignore the zoom change while changing the map theme + if (d->blockingZoomWhileChangingTheme) { return; } const QString newZoomString = getZoom(); s->worldMapWidget->markClustersAsDirty(); updateActionAvailability(); emit signalZoomChanged(newZoomString); } void BackendMarble::setZoom(const QString& newZoom) { const QString myZoomString = s->worldMapWidget->convertZoomToBackendZoom(newZoom, QLatin1String("marble")); GEOIFACE_ASSERT(myZoomString.startsWith(QLatin1String("marble:"))); const int myZoom = myZoomString.mid(QString::fromLatin1("marble:").length()).toInt(); d->cacheZoom = myZoom; d->marbleWidget->setZoom(myZoom); } QString BackendMarble::getZoom() const { if (d->marbleWidget) { d->cacheZoom = d->marbleWidget->zoom(); } return QString::fromLatin1("marble:%1").arg(d->cacheZoom); } int BackendMarble::getMarkerModelLevel() { -// return AbstractMarkerTiler::TileIndex::MaxLevel-1; +/* + return AbstractMarkerTiler::TileIndex::MaxLevel-1; +*/ GEOIFACE_ASSERT(isReady()); if (!isReady()) { return 0; } const int currentZoom = d->marbleWidget->zoom(); int tileLevel = 0; const Marble::Projection currentProjection = d->marbleWidget->projection(); switch (currentProjection) { case Marble::Equirectangular: - if (currentZoom < 1000) { tileLevel = 4; } + if (currentZoom < 1000) { tileLevel = 4; } else if (currentZoom < 1400) { tileLevel = 5; } else if (currentZoom < 1900) { tileLevel = 6; } else if (currentZoom < 2300) { tileLevel = 7; } else if (currentZoom < 2800) { tileLevel = 8; } else { tileLevel = 9; } + // note: level 9 is not enough starting at zoom level 3200 break; case Marble::Mercator: - if (currentZoom < 1000) { tileLevel = 4; } + if (currentZoom < 1000) { tileLevel = 4; } else if (currentZoom < 1500) { tileLevel = 5; } else if (currentZoom < 1900) { tileLevel = 6; } else if (currentZoom < 2300) { tileLevel = 7; } else if (currentZoom < 2800) { tileLevel = 8; } else { tileLevel = 9; } + // note: level 9 is not enough starting at zoom level 3200 break; default: case Marble::Spherical: - if (currentZoom < 1300) { tileLevel = 5; } + if (currentZoom < 1300) { tileLevel = 5; } else if (currentZoom < 1800) { tileLevel = 6; } else if (currentZoom < 2200) { tileLevel = 7; } else if (currentZoom < 2800) { tileLevel = 8; } else { tileLevel = 9; } + // note: level 9 is not enough starting at zoom level 3200 break; } // TODO: verify that this assertion was too strict -// GEOIFACE_ASSERT(tileLevel <= AbstractMarkerTiler::TileIndex::MaxLevel-1); - +/* + GEOIFACE_ASSERT(tileLevel <= AbstractMarkerTiler::TileIndex::MaxLevel-1); +*/ return tileLevel; } GeoCoordinates::PairList BackendMarble::getNormalizedBounds() { if (!d->marbleWidget) { return GeoCoordinates::PairList(); } const Marble::GeoDataLatLonAltBox marbleBounds = d->marbleWidget->viewport()->viewLatLonAltBox(); -// qCDebug(DIGIKAM_GEOIFACE_LOG) << marbleBounds.toString(GeoDataCoordinates::Degree); - +/* + qCDebug(DIGIKAM_GEOIFACE_LOG) << marbleBounds.toString(GeoDataCoordinates::Degree); +*/ const GeoCoordinates::Pair boundsPair = GeoCoordinates::makePair( marbleBounds.south(Marble::GeoDataCoordinates::Degree), marbleBounds.west(Marble::GeoDataCoordinates::Degree), marbleBounds.north(Marble::GeoDataCoordinates::Degree), marbleBounds.east(Marble::GeoDataCoordinates::Degree)); - -// qCDebug(DIGIKAM_GEOIFACE_LOG) << boundsPair.first<marbleWidget) { // event not filtered, because it is not for our object + return QObject::eventFilter(object, event); } // we only handle mouse events: + if ((event->type() != QEvent::MouseButtonPress) && (event->type() != QEvent::MouseMove) && (event->type() != QEvent::MouseButtonRelease)) { return QObject::eventFilter(object, event); } // no filtering in pan mode + if (s->currentMouseMode == MouseModePan) { return QObject::eventFilter(object, event); } QMouseEvent* const mouseEvent = static_cast(event); bool doFilterEvent = false; if (s->currentMouseMode == MouseModeRegionSelection) { - if ((event->type() == QEvent::MouseButtonPress) && - (mouseEvent->button() == Qt::LeftButton)) + if ((event->type() == QEvent::MouseButtonPress) && + (mouseEvent->button() == Qt::LeftButton)) { // we need to filter this event because otherwise Marble displays // a left click context menu + doFilterEvent = true; } else if (event->type() == QEvent::MouseMove) { if (d->firstSelectionPoint.hasCoordinates()) { d->intermediateSelectionPoint.clear(); geoCoordinates(mouseEvent->pos(), &d->intermediateSelectionPoint); d->intermediateSelectionScreenPoint = mouseEvent->pos(); qCDebug(DIGIKAM_GEOIFACE_LOG) << d->firstSelectionScreenPoint << " " << d->intermediateSelectionScreenPoint; qreal lonWest, latNorth, lonEast, latSouth; if (d->firstSelectionScreenPoint.x() < d->intermediateSelectionScreenPoint.x()) { lonWest = d->firstSelectionPoint.lon(); lonEast = d->intermediateSelectionPoint.lon(); } else { lonWest = d->intermediateSelectionPoint.lon(); lonEast = d->firstSelectionPoint.lon(); } if (d->firstSelectionScreenPoint.y() < d->intermediateSelectionScreenPoint.y()) { latNorth = d->firstSelectionPoint.lat(); latSouth = d->intermediateSelectionPoint.lat(); } else { latNorth = d->intermediateSelectionPoint.lat(); latSouth = d->firstSelectionPoint.lat(); } const GeoCoordinates::Pair selectionCoordinates( GeoCoordinates(latNorth, lonWest), GeoCoordinates(latSouth, lonEast)); //setSelectionRectangle(selectionCoordinates, SelectionRectangle); + d->displayedRectangle = selectionCoordinates; d->marbleWidget->update(); } doFilterEvent = true; } else if ((event->type() == QEvent::MouseButtonRelease) && (mouseEvent->button() == Qt::LeftButton)) { if (!d->firstSelectionPoint.hasCoordinates()) { geoCoordinates(mouseEvent->pos(), &d->firstSelectionPoint); d->firstSelectionScreenPoint = mouseEvent->pos(); } else { d->intermediateSelectionPoint.clear(); GeoCoordinates secondSelectionPoint; geoCoordinates(mouseEvent->pos(), &secondSelectionPoint); QPoint secondSelectionScreenPoint = mouseEvent->pos(); qreal lonWest, latNorth, lonEast, latSouth; if (d->firstSelectionScreenPoint.x() < secondSelectionScreenPoint.x()) { lonWest = d->firstSelectionPoint.lon(); lonEast = secondSelectionPoint.lon(); } else { lonWest = secondSelectionPoint.lon(); lonEast = d->firstSelectionPoint.lon(); } if (d->firstSelectionScreenPoint.y() < secondSelectionScreenPoint.y()) { latNorth = d->firstSelectionPoint.lat(); latSouth = secondSelectionPoint.lat(); } else { latNorth = secondSelectionPoint.lat(); latSouth = d->firstSelectionPoint.lat(); } const GeoCoordinates::Pair selectionCoordinates( GeoCoordinates(latNorth, lonWest), GeoCoordinates(latSouth, lonEast)); d->firstSelectionPoint.clear(); d->displayedRectangle.first.clear(); emit signalSelectionHasBeenMade(selectionCoordinates); } doFilterEvent = true; } } else { if ((event->type() == QEvent::MouseButtonPress) && (mouseEvent->button() == Qt::LeftButton)) { // check whether the user clicked on one of our items: // scan in reverse order, because the user would expect // the topmost marker to be picked up and not the // one below - // if (s->specialMarkersModel) - // { - // for (int row = s->specialMarkersModel->rowCount()-1; row >= 0; --row) - // { - // const QModelIndex currentIndex = s->specialMarkersModel->index(row, 0); - // const GeoCoordinates currentCoordinates = s->specialMarkersModel->data(currentIndex, s->specialMarkersCoordinatesRole).value(); - // - // QPoint markerPoint; - // if (!screenCoordinates(currentCoordinates, &markerPoint)) - // { - // continue; - // } - // - // const int markerPixmapHeight = s->markerPixmap.height(); - // const int markerPixmapWidth = s->markerPixmap.width(); - // const QRect markerRect(markerPoint.x()-markerPixmapWidth/2, markerPoint.y()-markerPixmapHeight, markerPixmapWidth, markerPixmapHeight); - // if (!markerRect.contains(mouseEvent->pos())) - // { - // continue; - // } - // - // // the user clicked on a marker: - // d->mouseMoveMarkerIndex = QPersistentModelIndex(currentIndex); - // d->mouseMoveCenterOffset = mouseEvent->pos() - markerPoint; - // d->mouseMoveObjectCoordinates = currentCoordinates; - // doFilterEvent = true; - // d->havePotentiallyMouseMovingObject = true; - // - // break; - // } - // } - - if (/*s->inEditMode &&*/ !doFilterEvent) +/* + if (s->specialMarkersModel) + { + for (int row = s->specialMarkersModel->rowCount()-1 ; row >= 0 ; --row) + { + const QModelIndex currentIndex = s->specialMarkersModel->index(row, 0); + const GeoCoordinates currentCoordinates = s->specialMarkersModel->data(currentIndex, s->specialMarkersCoordinatesRole).value(); + + QPoint markerPoint; + + if (!screenCoordinates(currentCoordinates, &markerPoint)) + { + continue; + } + + const int markerPixmapHeight = s->markerPixmap.height(); + const int markerPixmapWidth = s->markerPixmap.width(); + const QRect markerRect(markerPoint.x()-markerPixmapWidth/2, markerPoint.y()-markerPixmapHeight, markerPixmapWidth, markerPixmapHeight); + + if (!markerRect.contains(mouseEvent->pos())) + { + continue; + } + + // the user clicked on a marker: + + d->mouseMoveMarkerIndex = QPersistentModelIndex(currentIndex); + d->mouseMoveCenterOffset = mouseEvent->pos() - markerPoint; + d->mouseMoveObjectCoordinates = currentCoordinates; + doFilterEvent = true; + d->havePotentiallyMouseMovingObject = true; + + break; + } + } +*/ + if ( +/* + s->inEditMode && +*/ + !doFilterEvent + ) { // scan in reverse order of painting! + for (int clusterIndex = s->clusterList.count()-1 ; clusterIndex >= 0 ; --clusterIndex) { const GeoIfaceCluster& cluster = s->clusterList.at(clusterIndex); const GeoCoordinates currentCoordinates = cluster.coordinates; QPoint clusterPoint; if (!screenCoordinates(currentCoordinates, &clusterPoint)) { continue; } QRect markerRect; markerRect.setSize(cluster.pixmapSize); markerRect.moveTopLeft(clusterPoint); markerRect.translate(-cluster.pixmapOffset); if (!markerRect.contains(mouseEvent->pos())) { continue; } /// @todo For circles, make sure the mouse is really above the circle and not just in the rectangle! // the user clicked on a cluster: + d->mouseMoveClusterIndex = clusterIndex; d->mouseMoveCenterOffset = mouseEvent->pos() - clusterPoint; d->mouseMoveObjectCoordinates = currentCoordinates; doFilterEvent = true; d->havePotentiallyMouseMovingObject = true; s->haveMovingCluster = true; break; } } } else if ((event->type() == QEvent::MouseMove) && (d->havePotentiallyMouseMovingObject || d->haveMouseMovingObject)) { if ((!s->modificationsAllowed) || (!s->markerModel->tilerFlags().testFlag(AbstractMarkerTiler::FlagMovable)) || ((d->mouseMoveClusterIndex >= 0) && s->showThumbnails)) { // clusters only move in edit mode and when edit mode is enabled /// @todo This blocks moving of the map in non-edit mode + d->havePotentiallyMouseMovingObject = false; d->mouseMoveClusterIndex = -1; d->mouseMoveMarkerIndex = QPersistentModelIndex(); s->haveMovingCluster = false; } else { // mark the object as really moving: + d->havePotentiallyMouseMovingObject = false; d->haveMouseMovingObject = true; // a cluster or marker is being moved. update its position: + QPoint newMarkerPoint = mouseEvent->pos() - d->mouseMoveCenterOffset; QPoint snapPoint; if (findSnapPoint(newMarkerPoint, &snapPoint, nullptr, nullptr)) { newMarkerPoint = snapPoint; } GeoCoordinates newCoordinates; if (geoCoordinates(newMarkerPoint, &newCoordinates)) { d->mouseMoveObjectCoordinates = newCoordinates; d->marbleWidget->update(); } } } else if ((event->type() == QEvent::MouseButtonRelease) && (d->havePotentiallyMouseMovingObject)) { // the object was not moved, but just clicked once + if (d->mouseMoveClusterIndex >= 0) { const int mouseMoveClusterIndex = d->mouseMoveClusterIndex; // we are done with the clicked object // reset these before sending the signal + d->havePotentiallyMouseMovingObject = false; d->mouseMoveClusterIndex = -1; d->mouseMoveMarkerIndex = QPersistentModelIndex(); s->haveMovingCluster = false; doFilterEvent = true; emit signalClustersClicked(QIntList() << mouseMoveClusterIndex); } else { // we are done with the clicked object: + d->havePotentiallyMouseMovingObject = false; d->mouseMoveClusterIndex = -1; d->mouseMoveMarkerIndex = QPersistentModelIndex(); s->haveMovingCluster = false; } } else if ((event->type() == QEvent::MouseButtonRelease) && (d->haveMouseMovingObject)) { // the object was dropped, apply the coordinates if it is on screen: + const QPoint dropMarkerPoint = mouseEvent->pos() - d->mouseMoveCenterOffset; QPair snapTargetIndex(-1, QModelIndex()); GeoCoordinates newCoordinates; bool haveValidPoint = findSnapPoint(dropMarkerPoint, nullptr, &newCoordinates, &snapTargetIndex); if (!haveValidPoint) { haveValidPoint = geoCoordinates(dropMarkerPoint, &newCoordinates); } if (haveValidPoint) { if (d->mouseMoveMarkerIndex.isValid()) { /* // the marker was dropped to valid coordinates + s->specialMarkersModel->setData(d->mouseMoveMarkerIndex, QVariant::fromValue(newCoordinates), s->specialMarkersCoordinatesRole); QList markerIndices; markerIndices << d->mouseMoveMarkerIndex; // also emit a signal that the marker was moved: + emit signalSpecialMarkersMoved(markerIndices); */ } else { // a cluster is being moved + s->clusterList[d->mouseMoveClusterIndex].coordinates = newCoordinates; emit signalClustersMoved(QIntList() << d->mouseMoveClusterIndex, snapTargetIndex); } } d->haveMouseMovingObject = false; d->mouseMoveClusterIndex = -1; d->mouseMoveMarkerIndex = QPersistentModelIndex(); d->marbleWidget->update(); s->haveMovingCluster = false; } } if (doFilterEvent) { return true; } return QObject::eventFilter(object, event); } + /* void BackendMarble::updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData) { if (!dragData) { d->dragDropMarkerCount = 0; } else { d->dragDropMarkerPos = pos; d->dragDropMarkerCount = dragData->itemCount; } + d->marbleWidget->update(); // TODO: hide dragged markers on the map } void BackendMarble::updateDragDropMarkerPosition(const QPoint& pos) { d->dragDropMarkerPos = pos; d->marbleWidget->update(); } */ + void BackendMarble::updateActionAvailability() { if ((!d->activeState) || (!d->marbleWidget)) { return; } qCDebug(DIGIKAM_GEOIFACE_LOG) << d->cacheZoom << d->marbleWidget->maximumZoom() << d->marbleWidget->minimumZoom(); s->worldMapWidget->getControlAction(QLatin1String("zoomin"))->setEnabled(d->cacheZoommarbleWidget->maximumZoom()); s->worldMapWidget->getControlAction(QLatin1String("zoomout"))->setEnabled(d->cacheZoom>d->marbleWidget->minimumZoom()); const QList mapThemeActions = d->actionGroupMapTheme->actions(); for (int i = 0 ; isetChecked(mapThemeActions.at(i)->data().toString() == getMapTheme()); } const QList projectionActions = d->actionGroupProjection->actions(); for (int i = 0 ; isetChecked(projectionActions.at(i)->data().toString() == d->cacheProjection); } d->actionShowCompass->setChecked(d->cacheShowCompass); d->actionShowScaleBar->setChecked(d->cacheShowScaleBar); d->actionShowNavigation->setChecked(d->cacheShowNavigation); d->actionShowOverviewMap->setChecked(d->cacheShowOverviewMap); } void BackendMarble::slotThumbnailAvailableForIndex(const QVariant& index, const QPixmap& pixmap) { if (!d->marbleWidget) { return; } qCDebug(DIGIKAM_GEOIFACE_LOG) << index << pixmap.size(); if (pixmap.isNull() || !s->showThumbnails) { return; } // TODO: properly reject pixmaps with the wrong size + const int expectedThumbnailSize = s->worldMapWidget->getUndecoratedThumbnailSize(); if ((pixmap.size().height() > expectedThumbnailSize) || (pixmap.size().width() > expectedThumbnailSize)) { return; } // re-paint the map + d->marbleWidget->update(); } void BackendMarble::slotUngroupedModelChanged(const int index) { Q_UNUSED(index) if (!d->marbleWidget) { return; } d->marbleWidget->update(); } void BackendMarble::slotTrackManagerChanged() { d->trackCache.clear(); if (s->trackManager) { connect(s->trackManager, SIGNAL(signalTracksChanged(QList)), this, SLOT(slotTracksChanged(QList))); // when the visibility of the tracks is changed, we simple schedule a redraw + connect(s->trackManager, SIGNAL(signalVisibilityChanged(bool)), this, SLOT(slotScheduleUpdate())); } slotScheduleUpdate(); } bool BackendMarble::findSnapPoint(const QPoint& actualPoint, QPoint* const snapPoint, GeoCoordinates* const snapCoordinates, QPair* const snapTargetIndex) { QPoint bestSnapPoint; GeoCoordinates bestSnapCoordinates; int bestSnapDistanceSquared = -1; QModelIndex bestSnapIndex; int bestSnapUngroupedModel = -1; // now handle snapping: is there any object close by? + for (int im = 0 ; im < s->ungroupedModels.count() ; ++im) { GeoModelHelper* const modelHelper = s->ungroupedModels.at(im); // TODO: test for active snapping + if ((!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagVisible)) || (!modelHelper->modelFlags().testFlag(GeoModelHelper::FlagSnaps))) { continue; } // TODO: configurable snapping radius + const int snapRadiusSquared = 10*10; QAbstractItemModel* const itemModel = modelHelper->model(); for (int row = 0 ; row < itemModel->rowCount() ; ++row) { const QModelIndex currentIndex = itemModel->index(row, 0); GeoCoordinates currentCoordinates; if (!modelHelper->itemCoordinates(currentIndex, ¤tCoordinates)) { continue; } QPoint snapMarkerPoint; if (!screenCoordinates(currentCoordinates, &snapMarkerPoint)) { continue; } const QPoint distancePoint = snapMarkerPoint - actualPoint; const int snapDistanceSquared = (distancePoint.x()*distancePoint.x()+distancePoint.y()*distancePoint.y()); if ((snapDistanceSquared <= snapRadiusSquared) && ((bestSnapDistanceSquared == -1) || (bestSnapDistanceSquared > snapDistanceSquared))) { bestSnapDistanceSquared = snapDistanceSquared; bestSnapPoint = snapMarkerPoint; bestSnapCoordinates = currentCoordinates; bestSnapIndex = currentIndex; bestSnapUngroupedModel = im; } } } const bool foundSnapPoint = (bestSnapDistanceSquared >= 0); if (foundSnapPoint) { if (snapPoint) { *snapPoint = bestSnapPoint; } if (snapCoordinates) { *snapCoordinates = bestSnapCoordinates; } if (snapTargetIndex) { *snapTargetIndex = QPair(bestSnapUngroupedModel, bestSnapIndex); } } return foundSnapPoint; } void BackendMarble::regionSelectionChanged() { if (d->marbleWidget && d->activeState) { d->marbleWidget->update(); } } void BackendMarble::mouseModeChanged() { if (s->currentMouseMode != MouseModeRegionSelection) { d->firstSelectionPoint.clear(); d->intermediateSelectionPoint.clear(); if (d->marbleWidget && d->activeState) { d->marbleWidget->update(); } } } void BackendMarble::centerOn(const Marble::GeoDataLatLonBox& box, const bool useSaneZoomLevel) { if (!d->marbleWidget) { return; } /** * @todo Boxes with very small width or height (<1e-6 or so) cause a deadlock in Marble * in spherical projection. * So instead, we just center on the center of the box and go to maximum zoom. * This does not yet handle the case of only width or height being too small though. */ + const bool boxTooSmall = qMin(box.width(), box.height()) < 0.000001; if (boxTooSmall) { d->marbleWidget->centerOn(box.center()); d->marbleWidget->zoomView(useSaneZoomLevel ? qMin(3400, d->marbleWidget->maximumZoom()) : d->marbleWidget->maximumZoom()); } else { d->marbleWidget->centerOn(box, false); } // simple check to see whether the zoom level is now too high /// @todo for very small boxes, Marbles zoom becomes -2billion. Catch this case here. + int maxZoomLevel = d->marbleWidget->maximumZoom(); if (useSaneZoomLevel) { maxZoomLevel = qMin(maxZoomLevel, 3400); } if ((d->marbleWidget->zoom()>maxZoomLevel) || (d->marbleWidget->zoom()marbleWidget->minimumZoom())) { d->marbleWidget->zoomView(maxZoomLevel); } } void BackendMarble::setActive(const bool state) { const bool oldState = d->activeState; d->activeState = state; - if (oldState!=state) + if (oldState != state) { if ((!state) && d->marbleWidget) { // we should share our widget in the list of widgets in the global object + GeoIfaceInternalWidgetInfo info; info.deleteFunction = deleteInfoFunction; info.widget = d->marbleWidget; info.currentOwner = this; info.backendName = backendName(); info.state = d->widgetIsDocked ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked; BMInternalWidgetInfo intInfo; intInfo.bmLayer = d->bmLayer; info.backendData.setValue(intInfo); GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->addMyInternalWidgetToPool(info); } if (state && d->marbleWidget) { // we should remove our widget from the list of widgets in the global object + GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->removeMyInternalWidgetFromPool(this); } } } void BackendMarble::mapWidgetDocked(const bool state) { if (d->widgetIsDocked!=state) { GeoIfaceGlobalObject* const go = GeoIfaceGlobalObject::instance(); go->updatePooledWidgetState(d->marbleWidget, state ? GeoIfaceInternalWidgetInfo::InternalWidgetStillDocked : GeoIfaceInternalWidgetInfo::InternalWidgetUndocked); } d->widgetIsDocked = state; } void BackendMarble::drawSearchRectangle(Marble::GeoPainter* const painter, const GeoCoordinates::Pair& searchRectangle, const bool isOldRectangle) { const GeoCoordinates& topLeft = searchRectangle.first; const GeoCoordinates& bottomRight = searchRectangle.second; const qreal lonWest = topLeft.lon(); const qreal latNorth = topLeft.lat(); const qreal lonEast = bottomRight.lon(); const qreal latSouth = bottomRight.lat(); Marble::GeoDataCoordinates coordTopLeft(lonWest, latNorth, 0, Marble::GeoDataCoordinates::Degree); Marble::GeoDataCoordinates coordTopRight(lonEast, latNorth, 0, Marble::GeoDataCoordinates::Degree); Marble::GeoDataCoordinates coordBottomLeft(lonWest, latSouth, 0, Marble::GeoDataCoordinates::Degree); Marble::GeoDataCoordinates coordBottomRight(lonEast, latSouth, 0, Marble::GeoDataCoordinates::Degree); Marble::GeoDataLinearRing polyRing; polyRing << coordTopLeft << coordTopRight << coordBottomRight << coordBottomLeft; QPen selectionPen; if (isOldRectangle) { // there is a new selection in progress, // therefore display the current search rectangle in red + selectionPen.setColor(Qt::red); } else { selectionPen.setColor(Qt::blue); } selectionPen.setStyle(Qt::SolidLine); selectionPen.setWidth(1); painter->setPen(selectionPen); painter->setBrush(Qt::NoBrush); painter->drawPolygon(polyRing); } void BackendMarble::deleteInfoFunction(GeoIfaceInternalWidgetInfo* const info) { if (info->currentOwner) { qobject_cast(info->currentOwner.data())->releaseWidget(info); } BMInternalWidgetInfo intInfo = info->backendData.value(); if (intInfo.bmLayer) { delete intInfo.bmLayer; } delete info->widget.data(); } void BackendMarble::applyCacheToWidget() { /// @todo Do this only when the widget is active! + if (!d->marbleWidget) { return; } setMapTheme(d->cacheMapTheme); setProjection(d->cacheProjection); setShowCompass(d->cacheShowCompass); setShowScaleBar(d->cacheShowScaleBar); setShowNavigation(d->cacheShowNavigation); setShowOverviewMap(d->cacheShowOverviewMap); } void BackendMarble::slotTracksChanged(const QList trackChanges) { // invalidate the cache for all changed tracks + foreach (const TrackManager::TrackChanges& tc, trackChanges) { if (tc.second & (TrackManager::ChangeTrackPoints | TrackManager::ChangeRemoved)) { d->trackCache.remove(tc.first); } } slotScheduleUpdate(); } void BackendMarble::slotScheduleUpdate() { if (d->marbleWidget && d->activeState) { /// @TODO Put this onto the eventloop to collect update calls into one. + d->marbleWidget->update(); } } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/backends/backendmarble.h b/core/utilities/geolocation/geoiface/backends/backendmarble.h index 720dbe778e..8f90cee7a1 100644 --- a/core/utilities/geolocation/geoiface/backends/backendmarble.h +++ b/core/utilities/geolocation/geoiface/backends/backendmarble.h @@ -1,145 +1,145 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-08 * Description : Marble-backend for geolocation interface * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2009-2011 by Michael G. Hansen * Copyright (C) 2014 by Justus Schwartz * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_BACKEND_MARBLE_H #define DIGIKAM_BACKEND_MARBLE_H // Local includes #include "mapbackend.h" #include "trackmanager.h" #include "digikam_export.h" /// @cond false namespace Marble { class GeoPainter; } /// @endcond namespace Digikam { class DIGIKAM_EXPORT BackendMarble : public MapBackend { Q_OBJECT public: explicit BackendMarble(const QExplicitlySharedDataPointer& sharedData, QObject* const parent = nullptr); virtual ~BackendMarble(); virtual QString backendName() const override; virtual QString backendHumanName() const override; virtual QWidget* mapWidget() override; virtual void releaseWidget(GeoIfaceInternalWidgetInfo* const info) override; virtual void mapWidgetDocked(const bool state) override; virtual GeoCoordinates getCenter() const override; virtual void setCenter(const GeoCoordinates& coordinate) override; virtual bool isReady() const override; virtual void zoomIn() override; virtual void zoomOut() override; virtual void saveSettingsToGroup(KConfigGroup* const group) override; virtual void readSettingsFromGroup(const KConfigGroup* const group) override; virtual void addActionsToConfigurationMenu(QMenu* const configurationMenu) override; virtual void updateMarkers() override; virtual void updateClusters() override; QString getMapTheme() const; void setMapTheme(const QString& newMapTheme); QString getProjection() const; void setProjection(const QString& newProjection); virtual bool screenCoordinates(const GeoCoordinates& coordinates, QPoint* const point) override; virtual bool geoCoordinates(const QPoint& point, GeoCoordinates* const coordinates) const override; virtual QSize mapSize() const override; virtual void setZoom(const QString& newZoom) override; virtual QString getZoom() const override; virtual int getMarkerModelLevel() override; virtual GeoCoordinates::PairList getNormalizedBounds() override; - -// virtual void updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData); -// virtual void updateDragDropMarkerPosition(const QPoint& pos); - +/* + virtual void updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData); + virtual void updateDragDropMarkerPosition(const QPoint& pos); +*/ virtual void updateActionAvailability() override; void marbleCustomPaint(Marble::GeoPainter* painter); void setShowCompass(const bool state); void setShowScaleBar(const bool state); void setShowNavigation(const bool state); void setShowOverviewMap(const bool state); virtual void regionSelectionChanged() override; virtual void mouseModeChanged() override; virtual void centerOn(const Marble::GeoDataLatLonBox& box, const bool useSaneZoomLevel) override; virtual void setActive(const bool state) override; public Q_SLOTS: virtual void slotClustersNeedUpdating() override; virtual void slotThumbnailAvailableForIndex(const QVariant& index, const QPixmap& pixmap) override; void slotUngroupedModelChanged(const int index); void slotTrackManagerChanged() override; protected: bool eventFilter(QObject* object, QEvent* event) override; void createActions(); bool findSnapPoint(const QPoint& actualPoint, QPoint* const snapPoint, GeoCoordinates* const snapCoordinates, QPair* const snapTargetIndex); void GeoPainter_drawPixmapAtCoordinates(Marble::GeoPainter* const painter, const QPixmap& pixmap, const GeoCoordinates& coordinates, const QPoint& basePoint); void drawSearchRectangle(Marble::GeoPainter* const painter, const GeoCoordinates::Pair& searchRectangle, const bool isOldRectangle); void applyCacheToWidget(); static void deleteInfoFunction(GeoIfaceInternalWidgetInfo* const info); protected Q_SLOTS: void slotMapThemeActionTriggered(QAction* action); void slotProjectionActionTriggered(QAction* action); void slotFloatSettingsTriggered(QAction* action); void slotMarbleZoomChanged(); void slotTracksChanged(const QList trackChanges); void slotScheduleUpdate(); private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_BACKEND_MARBLE_H diff --git a/core/utilities/geolocation/geoiface/backends/backendmarblelayer.cpp b/core/utilities/geolocation/geoiface/backends/backendmarblelayer.cpp index 45f5a82d16..cc728f8507 100644 --- a/core/utilities/geolocation/geoiface/backends/backendmarblelayer.cpp +++ b/core/utilities/geolocation/geoiface/backends/backendmarblelayer.cpp @@ -1,73 +1,74 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-08 * Description : Internal part of the Marble-backend for geolocation interface * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2009-2010 by Michael G. Hansen * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "backendmarblelayer.h" // Marble includes #include // Local includes #include "backendmarble.h" namespace Digikam { BackendMarbleLayer::BackendMarbleLayer(BackendMarble* const pMarbleBackend) : marbleBackend(pMarbleBackend) { } BackendMarbleLayer::~BackendMarbleLayer() { } bool BackendMarbleLayer::render(Marble::GeoPainter* painter, Marble::ViewportParams* /*viewport*/, const QString& renderPos, Marble::GeoSceneLayer* /*layer*/) { if (marbleBackend && (renderPos == QLatin1String("HOVERS_ABOVE_SURFACE"))) { marbleBackend->marbleCustomPaint(painter); return true; } return false; } QStringList BackendMarbleLayer::renderPosition () const { QStringList layerNames; layerNames << QLatin1String("HOVERS_ABOVE_SURFACE" ); + return layerNames; } void BackendMarbleLayer::setBackend(BackendMarble* const pMarbleBackend) { marbleBackend = pMarbleBackend; } } // namespace Digikam diff --git a/core/utilities/geolocation/geoiface/backends/mapbackend.h b/core/utilities/geolocation/geoiface/backends/mapbackend.h index 1588712291..58ab448c29 100644 --- a/core/utilities/geolocation/geoiface/backends/mapbackend.h +++ b/core/utilities/geolocation/geoiface/backends/mapbackend.h @@ -1,124 +1,124 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-12-01 * Description : Base-class for backends for geolocation interface * * Copyright (C) 2010-2020 by Gilles Caulier * Copyright (C) 2009-2011 by Michael G. Hansen * Copyright (C) 2014 by Justus Schwartz * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_MAP_BACKEND_H #define DIGIKAM_MAP_BACKEND_H // Qt includes #include // Local includes #include "geoifacecommon.h" #include "digikam_export.h" class QMenu; class QWidget; class KConfigGroup; namespace Marble { class GeoDataLatLonBox; } namespace Digikam { class DIGIKAM_EXPORT MapBackend : public QObject { Q_OBJECT public: explicit MapBackend(const QExplicitlySharedDataPointer& sharedData, QObject* const parent); virtual ~MapBackend(); virtual QString backendName() const = 0; virtual QString backendHumanName() const = 0; virtual QWidget* mapWidget() = 0; virtual void releaseWidget(GeoIfaceInternalWidgetInfo* const info) = 0; virtual void mapWidgetDocked(const bool state) = 0; virtual GeoCoordinates getCenter() const = 0; virtual void setCenter(const GeoCoordinates& coordinate) = 0; virtual bool isReady() const = 0; virtual void zoomIn() = 0; virtual void zoomOut() = 0; virtual void saveSettingsToGroup(KConfigGroup* const group) = 0; virtual void readSettingsFromGroup(const KConfigGroup* const group) = 0; virtual void addActionsToConfigurationMenu(QMenu* const configurationMenu) = 0; virtual void updateMarkers() = 0; virtual void updateClusters() = 0; virtual bool screenCoordinates(const GeoCoordinates& coordinates, QPoint* const point) = 0; virtual bool geoCoordinates(const QPoint& point, GeoCoordinates* const coordinates) const = 0; virtual QSize mapSize() const = 0; virtual void setZoom(const QString& newZoom) = 0; virtual QString getZoom() const = 0; virtual int getMarkerModelLevel() = 0; virtual GeoCoordinates::PairList getNormalizedBounds() = 0; - -// virtual void updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData) = 0; -// virtual void updateDragDropMarkerPosition(const QPoint& pos) = 0; - +/* + virtual void updateDragDropMarker(const QPoint& pos, const GeoIfaceDragData* const dragData) = 0; + virtual void updateDragDropMarkerPosition(const QPoint& pos) = 0; +*/ virtual void updateActionAvailability() = 0; virtual void regionSelectionChanged() = 0; virtual void mouseModeChanged() = 0; const QExplicitlySharedDataPointer s; virtual void centerOn(const Marble::GeoDataLatLonBox& box, const bool useSaneZoomLevel = true) = 0; virtual void setActive(const bool state) = 0; public Q_SLOTS: virtual void slotClustersNeedUpdating() = 0; virtual void slotThumbnailAvailableForIndex(const QVariant& index, const QPixmap& pixmap); virtual void slotTrackManagerChanged(); Q_SIGNALS: void signalBackendReadyChanged(const QString& backendName); void signalClustersMoved(const QIntList& clusterIndices, const QPair& snapTarget); void signalClustersClicked(const QIntList& clusterIndices); void signalMarkersMoved(const QIntList& markerIndices); void signalZoomChanged(const QString& newZoom); void signalSelectionHasBeenMade(const Digikam::GeoCoordinates::Pair& coordinates); }; } // namespace Digikam #endif // DIGIKAM_MAP_BACKEND_H diff --git a/core/utilities/geolocation/geomapwrapper/gpsiteminfo.h b/core/utilities/geolocation/geomapwrapper/gpsiteminfo.h index 4ea3e00abd..2eb2088b8a 100644 --- a/core/utilities/geolocation/geomapwrapper/gpsiteminfo.h +++ b/core/utilities/geolocation/geomapwrapper/gpsiteminfo.h @@ -1,70 +1,70 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-01-06 * Description : Helper class for geomap interaction * * Copyright (C) 2011 by Michael G. Hansen * Copyright (C) 2011-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_GPS_ITEM_INFO_H #define DIGIKAM_GPS_ITEM_INFO_H // Qt includes #include #include // Local includes #include "geocoordinates.h" #include "geogroupstate.h" namespace Digikam { class GPSItemInfo { public: explicit GPSItemInfo(); ~GPSItemInfo(); public: static GPSItemInfo fromIdCoordinatesRatingDateTime(const qlonglong p_id, - const GeoCoordinates& p_coordinates, - const int p_rating, - const QDateTime& p_creationDate); + const GeoCoordinates& p_coordinates, + const int p_rating, + const QDateTime& p_creationDate); public: - qlonglong id; - GeoCoordinates coordinates; - int rating; - QDateTime dateTime; - QUrl url; + qlonglong id; + GeoCoordinates coordinates; + int rating; + QDateTime dateTime; + QUrl url; typedef QList List; }; } // namespace Digikam Q_DECLARE_METATYPE(Digikam::GPSItemInfo) #endif // DIGIKAM_GPS_ITEM_INFO_H diff --git a/core/utilities/geolocation/geomapwrapper/gpsiteminfosorter.cpp b/core/utilities/geolocation/geomapwrapper/gpsiteminfosorter.cpp index 69c74864fa..d9ef29eb6f 100644 --- a/core/utilities/geolocation/geomapwrapper/gpsiteminfosorter.cpp +++ b/core/utilities/geolocation/geomapwrapper/gpsiteminfosorter.cpp @@ -1,261 +1,272 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-01-06 * Description : Helper functions for geolocation interface interaction * * Copyright (C) 2011 by Michael G. Hansen * Copyright (C) 2011-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "gpsiteminfosorter.h" // Qt includes #include #include #include #include // KDE includes #include // Local includes #include "mapwidget.h" namespace Digikam { class Q_DECL_HIDDEN GPSItemInfoSorter::Private { public: explicit Private() : mapWidgets(), sortOrder(GPSItemInfoSorter::SortYoungestFirst), sortMenu(nullptr), sortActionOldestFirst(nullptr), sortActionYoungestFirst(nullptr), sortActionRating(nullptr) { } - QList > mapWidgets; - GPSItemInfoSorter::SortOptions sortOrder; - QPointer sortMenu; - QAction* sortActionOldestFirst; - QAction* sortActionYoungestFirst; - QAction* sortActionRating; + QList > mapWidgets; + GPSItemInfoSorter::SortOptions sortOrder; + QPointer sortMenu; + QAction* sortActionOldestFirst; + QAction* sortActionYoungestFirst; + QAction* sortActionRating; }; GPSItemInfoSorter::GPSItemInfoSorter(QObject* const parent) : QObject(parent), d(new Private()) { } GPSItemInfoSorter::~GPSItemInfoSorter() { if (d->sortMenu) { delete d->sortMenu; } delete d; } -bool GPSItemInfoSorter::fitsBetter(const GPSItemInfo& oldInfo, const GeoGroupState oldState, - const GPSItemInfo& newInfo, const GeoGroupState newState, - const GeoGroupState globalGroupState, const SortOptions sortOptions) +bool GPSItemInfoSorter::fitsBetter(const GPSItemInfo& oldInfo, + const GeoGroupState oldState, + const GPSItemInfo& newInfo, + const GeoGroupState newState, + const GeoGroupState globalGroupState, + const SortOptions sortOptions) { // the best index for a tile is determined like this: // region selected? -> prefer region selected markers // positive filtering on? - > prefer positively filtered markers // next -> depending on sortkey, prefer better rated ones // next -> depending on sortkey, prefer older or younger ones // next -> if the image has a URL, prefer the one with the 'lower' URL // next -> prefer the image with the higher image id - // region selection part: + // region selection part + if (globalGroupState & RegionSelectedMask) { - const bool oldIsRegionSelected = oldState & RegionSelectedMask; - const bool newIsRegionSelected = newState & RegionSelectedMask; + const bool oldIsRegionSelected = (oldState & RegionSelectedMask); + const bool newIsRegionSelected = (newState & RegionSelectedMask); if (oldIsRegionSelected != newIsRegionSelected) { return newIsRegionSelected; } } - // positive filtering part: + // positive filtering part + if (globalGroupState & FilteredPositiveMask) { - const bool oldIsFilteredPositive = oldState & FilteredPositiveMask; - const bool newIsFilteredPositive = newState & FilteredPositiveMask; + const bool oldIsFilteredPositive = (oldState & FilteredPositiveMask); + const bool newIsFilteredPositive = (newState & FilteredPositiveMask); if (oldIsFilteredPositive != newIsFilteredPositive) { return newIsFilteredPositive; } } // care about rating, if requested + if (sortOptions & SortRating) { - const bool oldHasRating = oldInfo.rating > 0; - const bool newHasRating = newInfo.rating > 0; + const bool oldHasRating = (oldInfo.rating > 0); + const bool newHasRating = (newInfo.rating > 0); if (oldHasRating != newHasRating) { return newHasRating; } - if ( (oldHasRating && newHasRating) && (oldInfo.rating != newInfo.rating) ) + if (oldHasRating && + newHasRating && + (oldInfo.rating != newInfo.rating)) { return oldInfo.rating < newInfo.rating; } // ratings are equal or both have no rating, therefore fall through to the next level } - // finally, decide by date: + // finally, decide by date + const bool oldHasDate = oldInfo.dateTime.isValid(); const bool newHasDate = newInfo.dateTime.isValid(); if (oldHasDate != newHasDate) { return newHasDate; } if (oldHasDate && newHasDate) { if (oldInfo.dateTime != newInfo.dateTime) { if (sortOptions & SortOldestFirst) { - return oldInfo.dateTime > newInfo.dateTime; + return (oldInfo.dateTime > newInfo.dateTime); } else { - return oldInfo.dateTime < newInfo.dateTime; + return (oldInfo.dateTime < newInfo.dateTime); } } } // compare the image URL + if (oldInfo.url.isValid() && newInfo.url.isValid()) { return oldInfo.url.url() > newInfo.url.url(); } // last resort: use the image id for reproducibility - return oldInfo.id > newInfo.id; + + return (oldInfo.id > newInfo.id); } void GPSItemInfoSorter::addToMapWidget(MapWidget* const mapWidget) { initializeSortMenu(); d->mapWidgets << QPointer(mapWidget); mapWidget->setSortOptionsMenu(d->sortMenu); } void GPSItemInfoSorter::initializeSortMenu() { if (d->sortMenu) { return; } d->sortMenu = new QMenu(); d->sortMenu->setTitle(i18n("Sorting")); QActionGroup* const sortOrderExclusive = new QActionGroup(d->sortMenu); sortOrderExclusive->setExclusive(true); connect(sortOrderExclusive, SIGNAL(triggered(QAction*)), this, SLOT(slotSortOptionTriggered())); d->sortActionOldestFirst = new QAction(i18n("Show oldest first"), sortOrderExclusive); d->sortActionOldestFirst->setCheckable(true); d->sortMenu->addAction(d->sortActionOldestFirst); d->sortActionYoungestFirst = new QAction(i18n("Show youngest first"), sortOrderExclusive); d->sortActionYoungestFirst->setCheckable(true); d->sortMenu->addAction(d->sortActionYoungestFirst); d->sortActionRating = new QAction(i18n("Sort by rating"), this); d->sortActionRating->setCheckable(true); d->sortMenu->addAction(d->sortActionRating); connect(d->sortActionRating, SIGNAL(triggered(bool)), this, SLOT(slotSortOptionTriggered())); /// @todo Should we initialize the checked state already or wait for a call to setSortOptions? } void GPSItemInfoSorter::setSortOptions(const SortOptions sortOptions) { d->sortOrder = sortOptions; - for (int i = 0; i < d->mapWidgets.count(); ++i) + for (int i = 0 ; i < d->mapWidgets.count() ; ++i) { if (d->mapWidgets.at(i)) { d->mapWidgets.at(i)->setSortKey(d->sortOrder); } } d->sortActionRating->setChecked(d->sortOrder & GPSItemInfoSorter::SortRating); d->sortActionOldestFirst->setChecked(d->sortOrder & GPSItemInfoSorter::SortOldestFirst); d->sortActionYoungestFirst->setChecked(!(d->sortOrder & GPSItemInfoSorter::SortOldestFirst)); } GPSItemInfoSorter::SortOptions GPSItemInfoSorter::getSortOptions() const { return d->sortOrder; } void GPSItemInfoSorter::slotSortOptionTriggered() { SortOptions newSortKey = SortYoungestFirst; if (d->sortActionOldestFirst->isChecked()) { newSortKey = SortOldestFirst; } if (d->sortActionRating->isChecked()) { - newSortKey|= SortRating; + newSortKey |= SortRating; } d->sortOrder = newSortKey; - for (int i = 0; i < d->mapWidgets.count(); ++i) + for (int i = 0 ; i < d->mapWidgets.count() ; ++i) { if (d->mapWidgets.at(i)) { d->mapWidgets.at(i)->setSortKey(d->sortOrder); } } } } // namespace Digikam diff --git a/core/utilities/geolocation/geomapwrapper/gpsiteminfosorter.h b/core/utilities/geolocation/geomapwrapper/gpsiteminfosorter.h index dbe53de361..bb291e4d10 100644 --- a/core/utilities/geolocation/geomapwrapper/gpsiteminfosorter.h +++ b/core/utilities/geolocation/geomapwrapper/gpsiteminfosorter.h @@ -1,94 +1,98 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2011-01-06 * Description : Helper class for geomap interaction * * Copyright (C) 2011 by Michael G. Hansen * Copyright (C) 2011-2020 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #ifndef DIGIKAM_GPS_ITEM_INFO_SORTER_H #define DIGIKAM_GPS_ITEM_INFO_SORTER_H // Qt includes #include #include #include #include // Local includes #include "geocoordinates.h" #include "geogroupstate.h" #include "gpsiteminfo.h" #include "mapwidget.h" namespace Digikam { class GPSItemInfoSorter : public QObject { Q_OBJECT public: enum SortOption { SortYoungestFirst = 0, SortOldestFirst = 1, SortRating = 2 }; Q_DECLARE_FLAGS(SortOptions, SortOption) public: explicit GPSItemInfoSorter(QObject* const parent); ~GPSItemInfoSorter(); void addToMapWidget(MapWidget* const mapWidget); + void setSortOptions(const SortOptions sortOptions); SortOptions getSortOptions() const; public: - static bool fitsBetter(const GPSItemInfo& oldInfo, const GeoGroupState oldState, - const GPSItemInfo& newInfo, const GeoGroupState newState, - const GeoGroupState globalGroupState, const SortOptions sortOptions); + static bool fitsBetter(const GPSItemInfo& oldInfo, + const GeoGroupState oldState, + const GPSItemInfo& newInfo, + const GeoGroupState newState, + const GeoGroupState globalGroupState, + const SortOptions sortOptions); private Q_SLOTS: void slotSortOptionTriggered(); private: void initializeSortMenu(); private: GPSItemInfoSorter(); // Disable class Private; Private* const d; }; } // namespace Digikam Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::GPSItemInfoSorter::SortOptions) #endif // DIGIKAM_GPS_ITEM_INFO_SORTER_H