File indexing completed on 2025-01-05 03:56:42

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2006-02-22
0007  * Description : a tab widget to display GPS info
0008  *
0009  * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2011      by Michael G. Hansen <mike at mghansen dot de>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 /**
0017  * NOTE: Good explanations about GPS (in French) can be found at this url :
0018  * www.gpspassion.com/forumsen/topic.asp?TOPIC_ID=16593
0019  */
0020 
0021 #include "itempropertiesgpstab.h"
0022 
0023 // Qt includes
0024 
0025 #include <QFile>
0026 #include <QFrame>
0027 #include <QGridLayout>
0028 #include <QGroupBox>
0029 #include <QLabel>
0030 #include <QMap>
0031 #include <QPushButton>
0032 #include <QToolButton>
0033 #include <QStandardItemModel>
0034 #include <QStandardItem>
0035 #include <QComboBox>
0036 #include <QDesktopServices>
0037 #include <QIcon>
0038 #include <QLocale>
0039 #include <QScopedPointer>
0040 #include <QStackedWidget>
0041 
0042 // KDE includes
0043 
0044 #include <kconfiggroup.h>
0045 #include <klocalizedstring.h>
0046 
0047 // Local includes
0048 
0049 #include "mapwidget.h"
0050 #include "itemmarkertiler.h"
0051 #include "dlayoutbox.h"
0052 #include "digikam_debug.h"
0053 #include "itemgpsmodelhelper.h"
0054 #include "dexpanderbox.h"
0055 #include "digikam_config.h"
0056 
0057 #ifdef HAVE_QWEBENGINE
0058 #   include "webbrowserdlg.h"
0059 #endif
0060 
0061 namespace Digikam
0062 {
0063 
0064 class Q_DECL_HIDDEN ItemPropertiesGPSTab::Private
0065 {
0066 
0067 public:
0068 
0069     explicit Private()
0070       : altLabel                    (nullptr),
0071         latLabel                    (nullptr),
0072         lonLabel                    (nullptr),
0073         dateLabel                   (nullptr),
0074         mapView                     (nullptr),
0075         detailsBtn                  (nullptr),
0076         detailsCombo                (nullptr),
0077         altitude                    (nullptr),
0078         latitude                    (nullptr),
0079         longitude                   (nullptr),
0080         date                        (nullptr),
0081         map                         (nullptr),
0082         itemMarkerTiler             (nullptr),
0083         itemModel                   (nullptr),
0084         gpsModelHelper              (nullptr),
0085         gpsItemInfoSorter           (nullptr),
0086         boundariesShouldBeAdjusted  (false)
0087     {
0088     }
0089 
0090     QLabel*                    altLabel;
0091     QLabel*                    latLabel;
0092     QLabel*                    lonLabel;
0093     QLabel*                    dateLabel;
0094 
0095     QStackedWidget*            mapView;
0096     QToolButton*               detailsBtn;
0097     QComboBox*                 detailsCombo;
0098 
0099     DAdjustableLabel*          altitude;
0100     DAdjustableLabel*          latitude;
0101     DAdjustableLabel*          longitude;
0102     DAdjustableLabel*          date;
0103 
0104     MapWidget*                 map;
0105     ItemMarkerTiler*           itemMarkerTiler;
0106     GPSItemInfo::List          gpsInfoList;
0107 
0108     QStandardItemModel*        itemModel;
0109     ItemGPSModelHelper*        gpsModelHelper;
0110     GPSItemInfoSorter*         gpsItemInfoSorter;
0111     bool                       boundariesShouldBeAdjusted;
0112 };
0113 
0114 ItemPropertiesGPSTab::ItemPropertiesGPSTab(QWidget* const parent)
0115     : QWidget(parent),
0116       d      (new Private)
0117 {
0118     QGridLayout* const layout = new QGridLayout(this);
0119 
0120     // --------------------------------------------------------
0121 
0122     d->mapView                = new QStackedWidget(this);
0123     d->mapView->setMinimumWidth(100);
0124     d->mapView->setMinimumHeight(100);
0125     d->mapView->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
0126     d->mapView->setLineWidth(style()->pixelMetric(QStyle::PM_DefaultFrameWidth));
0127 
0128     QWidget* const mapPanel   = new QWidget(this);
0129     QVBoxLayout* const vlay2  = new QVBoxLayout(mapPanel);
0130     d->map                    = new MapWidget(mapPanel);
0131     d->map->setAvailableMouseModes(MouseModePan | MouseModeZoomIntoGroup);
0132     d->map->setVisibleMouseModes(MouseModePan | MouseModeZoomIntoGroup);
0133     d->map->setEnabledExtraActions(ExtraActionSticky);
0134     d->map->setVisibleExtraActions(ExtraActionSticky);
0135     d->map->setBackend(QLatin1String("marble"));
0136     d->gpsItemInfoSorter      = new GPSItemInfoSorter(this);
0137     d->gpsItemInfoSorter->addToMapWidget(d->map);
0138     vlay2->addWidget(d->map);
0139     vlay2->setContentsMargins(QMargins());
0140     vlay2->setSpacing(0);
0141 
0142     d->mapView->insertWidget(0, mapPanel);
0143 
0144     QLabel* const noGPSInfo   = new QLabel(i18n("No Geolocation\nInformation\nAvailable"));
0145     noGPSInfo->setAlignment(Qt::AlignCenter);
0146     noGPSInfo->setWordWrap(true);
0147     d->mapView->insertWidget(1, noGPSInfo);
0148 
0149     // --------------------------------------------------------
0150 
0151     d->itemModel        = new QStandardItemModel(this);
0152     d->gpsModelHelper   = new ItemGPSModelHelper(d->itemModel, this);
0153     d->itemMarkerTiler  = new ItemMarkerTiler(d->gpsModelHelper, this);
0154     d->map->setGroupedModel(d->itemMarkerTiler);
0155 
0156     d->altLabel         = new QLabel(i18n("<b>Altitude</b>:"),  this);
0157     d->latLabel         = new QLabel(i18n("<b>Latitude</b>:"),  this);
0158     d->lonLabel         = new QLabel(i18n("<b>Longitude</b>:"), this);
0159     d->dateLabel        = new QLabel(i18n("<b>Date</b>:"),      this);
0160     d->altitude         = new DAdjustableLabel(this);
0161     d->latitude         = new DAdjustableLabel(this);
0162     d->longitude        = new DAdjustableLabel(this);
0163     d->date             = new DAdjustableLabel(this);
0164     d->altitude->setAlignment(Qt::AlignRight);
0165     d->latitude->setAlignment(Qt::AlignRight);
0166     d->longitude->setAlignment(Qt::AlignRight);
0167     d->date->setAlignment(Qt::AlignRight);
0168 
0169     // --------------------------------------------------------
0170 
0171     QWidget* const box            = new DHBox(this);
0172     QHBoxLayout* const hBoxLayout = reinterpret_cast<QHBoxLayout*>(box->layout());
0173 
0174     if (hBoxLayout)
0175     {
0176         hBoxLayout->addStretch();
0177     }
0178 
0179     d->detailsCombo = new QComboBox(box);
0180     d->detailsBtn   = new QToolButton(box);
0181     d->detailsBtn->setIcon(QIcon::fromTheme(QLatin1String("globe")));
0182     d->detailsBtn->setToolTip(i18n("See more information on the Internet"));
0183     d->detailsCombo->insertItem(MapQuest,      QLatin1String("MapQuest"));
0184     d->detailsCombo->insertItem(GoogleMaps,    QLatin1String("Google Maps"));
0185     d->detailsCombo->insertItem(BingMaps,      QLatin1String("Bing Maps"));
0186     d->detailsCombo->insertItem(OpenStreetMap, QLatin1String("OpenStreetMap"));
0187     d->detailsCombo->insertItem(LocAlizeMaps,  QLatin1String("loc.alize.us Maps"));
0188 
0189     // --------------------------------------------------------
0190 
0191     layout->addWidget(d->mapView,                 0, 0, 1, 2);
0192     layout->addWidget(d->altLabel,                1, 0, 1, 1);
0193     layout->addWidget(d->altitude,                1, 1, 1, 1);
0194     layout->addWidget(d->latLabel,                2, 0, 1, 1);
0195     layout->addWidget(d->latitude,                2, 1, 1, 1);
0196     layout->addWidget(d->lonLabel,                3, 0, 1, 1);
0197     layout->addWidget(d->longitude,               3, 1, 1, 1);
0198     layout->addWidget(d->dateLabel,               4, 0, 1, 1);
0199     layout->addWidget(d->date,                    4, 1, 1, 1);
0200     layout->addWidget(d->map->getControlWidget(), 5, 0, 1, 2);
0201     layout->addWidget(box,                        6, 0, 1, 2);
0202     layout->setRowStretch(0, 10);
0203     layout->setColumnStretch(1, 10);
0204     layout->setContentsMargins(QMargins());
0205     layout->setSpacing(0);
0206 
0207     // --------------------------------------------------------
0208 
0209     connect(d->detailsBtn, SIGNAL(clicked()),
0210             this, SLOT(slotGPSDetails()));
0211 }
0212 
0213 ItemPropertiesGPSTab::~ItemPropertiesGPSTab()
0214 {
0215     delete d;
0216 }
0217 
0218 void ItemPropertiesGPSTab::readSettings(const KConfigGroup& group)
0219 {
0220     d->gpsItemInfoSorter->setSortOptions(GPSItemInfoSorter::SortOptions(group.readEntry(QLatin1String("Sort Order"),
0221                                           int(d->gpsItemInfoSorter->getSortOptions()))));
0222     setWebGPSLocator(group.readEntry(QLatin1String("Web GPS Locator"), getWebGPSLocator()));
0223 
0224     KConfigGroup groupMapWidget = KConfigGroup(&group, QLatin1String("Map Widget"));
0225     d->map->readSettingsFromGroup(&groupMapWidget);
0226 
0227 }
0228 void ItemPropertiesGPSTab::writeSettings(KConfigGroup& group)
0229 {
0230     group.writeEntry(QLatin1String("Sort Order"),      int(d->gpsItemInfoSorter->getSortOptions()));
0231     group.writeEntry(QLatin1String("Web GPS Locator"), getWebGPSLocator());
0232 
0233     KConfigGroup groupMapWidget = KConfigGroup(&group, QLatin1String("Map Widget"));
0234     d->map->saveSettingsToGroup(&groupMapWidget);
0235 }
0236 
0237 int ItemPropertiesGPSTab::getWebGPSLocator() const
0238 {
0239     return d->detailsCombo->currentIndex();
0240 }
0241 
0242 void ItemPropertiesGPSTab::setWebGPSLocator(int locator)
0243 {
0244     d->detailsCombo->setCurrentIndex(locator);
0245 }
0246 
0247 void ItemPropertiesGPSTab::slotGPSDetails()
0248 {
0249     QString val, url;
0250 
0251     if (d->gpsInfoList.isEmpty())
0252     {
0253         return;
0254     }
0255 
0256     const GPSItemInfo info = d->gpsInfoList.first();
0257 
0258     switch ( getWebGPSLocator() )
0259     {
0260         case MapQuest:
0261         {
0262             url.append(QLatin1String("https://www.mapquest.com/maps/map.adp?searchtype=address"
0263                                      "&formtype=address&latlongtype=decimal"));
0264             url.append(QLatin1String("&latitude="));
0265             url.append(val.setNum(info.coordinates.lat(), 'g', 12));
0266             url.append(QLatin1String("&longitude="));
0267             url.append(val.setNum(info.coordinates.lon(), 'g', 12));
0268             break;
0269         }
0270 
0271         case GoogleMaps:
0272         {
0273             url.append(QLatin1String("https://maps.google.com/?q="));
0274             url.append(val.setNum(info.coordinates.lat(), 'g', 12));
0275             url.append(QLatin1String(","));
0276             url.append(val.setNum(info.coordinates.lon(), 'g', 12));
0277             url.append(QLatin1String("&spn=0.05,0.05&t=h&om=1"));
0278             break;
0279         }
0280 
0281         case LocAlizeMaps:
0282         {
0283             url.append(QLatin1String("https://loc.alize.us/#/geo:"));
0284             url.append(val.setNum(info.coordinates.lat(), 'g', 12));
0285             url.append(QLatin1String(","));
0286             url.append(val.setNum(info.coordinates.lon(), 'g', 12));
0287             url.append(QLatin1String(",15,/"));
0288             break;
0289         }
0290 
0291         case BingMaps:
0292         {
0293             url.append(QLatin1String("https://www.bing.com/maps/?v=2&where1="));
0294             url.append(val.setNum(info.coordinates.lat(), 'g', 12));
0295             url.append(QLatin1String(","));
0296             url.append(val.setNum(info.coordinates.lon(), 'g', 12));
0297             break;
0298         }
0299 
0300         case OpenStreetMap:
0301         {
0302             // lat and lon would also work, but wouldn't show a marker
0303             url.append(QLatin1String("https://www.openstreetmap.org/?"));
0304             url.append(QLatin1String("mlat="));
0305             url.append(val.setNum(info.coordinates.lat(), 'g', 12));
0306             url.append(QLatin1String("&mlon="));
0307             url.append(val.setNum(info.coordinates.lon(), 'g', 12));
0308             url.append(QLatin1String("&zoom=15"));
0309             break;
0310         }
0311     }
0312 
0313     qCDebug(DIGIKAM_GENERAL_LOG) << url;
0314 
0315 #ifdef HAVE_QWEBENGINE
0316 
0317     WebBrowserDlg* const browser = new WebBrowserDlg(QUrl(url), this);
0318     browser->show();
0319 
0320 #else
0321 
0322     QDesktopServices::openUrl(QUrl(url));
0323 
0324 #endif
0325 
0326 }
0327 
0328 void ItemPropertiesGPSTab::setCurrentURL(const QUrl& url)
0329 {
0330     if (url.isEmpty())
0331     {
0332         clearGPSInfo();
0333         return;
0334     }
0335 
0336     QScopedPointer<DMetadata> meta(new DMetadata(url.toLocalFile()));
0337 
0338     setMetadata(meta.data(), url);
0339 }
0340 
0341 void ItemPropertiesGPSTab::setMetadata(DMetadata* const meta, const QUrl& url)
0342 {
0343     double lat, lng;
0344     const bool haveCoordinates = meta->getGPSLatitudeNumber(&lat) && meta->getGPSLongitudeNumber(&lng);
0345 
0346     if (haveCoordinates)
0347     {
0348         double alt;
0349         const bool haveAlt = meta->getGPSAltitude(&alt);
0350 
0351         GeoCoordinates coordinates(lat, lng);
0352 
0353         if (haveAlt)
0354         {
0355             coordinates.setAlt(alt);
0356         }
0357 
0358         GPSItemInfo gpsInfo;
0359         gpsInfo.coordinates = coordinates;
0360         gpsInfo.dateTime    = meta->getItemDateTime();
0361         gpsInfo.rating      = meta->getItemRating();
0362         gpsInfo.url         = url;
0363         setGPSInfoList(GPSItemInfo::List() << gpsInfo);
0364     }
0365     else
0366     {
0367         clearGPSInfo();
0368     }
0369 }
0370 
0371 void ItemPropertiesGPSTab::clearGPSInfo()
0372 {
0373     d->altitude->setAdjustedText();
0374     d->latitude->setAdjustedText();
0375     d->longitude->setAdjustedText();
0376     d->date->setAdjustedText();
0377     d->itemModel->clear();
0378     d->mapView->setCurrentIndex(1);
0379     setEnabled(false);
0380 }
0381 
0382 void ItemPropertiesGPSTab::setGPSInfoList(const GPSItemInfo::List& list)
0383 {
0384     // Clear info label
0385 
0386     d->altitude->setAdjustedText();
0387     d->latitude->setAdjustedText();
0388     d->longitude->setAdjustedText();
0389     d->date->setAdjustedText();
0390     d->gpsInfoList.clear();
0391     d->itemModel->clear();
0392     d->gpsInfoList = list;
0393 
0394     if (list.isEmpty())
0395     {
0396         clearGPSInfo();
0397         return;
0398     }
0399 
0400     d->mapView->setCurrentIndex(0);
0401     setEnabled(true);
0402 
0403     if (list.count() == 1)
0404     {
0405         const GPSItemInfo info            = list.first();
0406         const GeoCoordinates& coordinates = info.coordinates;
0407 
0408         if (!coordinates.hasAltitude())
0409         {
0410             d->altitude->setAdjustedText(i18nc("@label: no GPS coordinates", "Undefined"));
0411         }
0412         else
0413         {
0414             d->altitude->setAdjustedText(QString::fromLatin1("%1 m").arg(QLocale().toString(coordinates.alt(), 'g', 7)));
0415         }
0416 
0417         d->latitude->setAdjustedText(QLocale().toString(coordinates.lat(), 'g', 7));
0418         d->longitude->setAdjustedText(QLocale().toString(coordinates.lon(), 'g', 7));
0419         d->date->setAdjustedText(QLocale().toString(info.dateTime, QLocale::ShortFormat));
0420     }
0421 
0422     for (int i = 0 ; i < d->gpsInfoList.count() ; ++i)
0423     {
0424         QStandardItem* const currentItemGPSItem = new QStandardItem();
0425         currentItemGPSItem->setData(QVariant::fromValue(d->gpsInfoList.at(i)), RoleGPSItemInfo);
0426         d->itemModel->appendRow(currentItemGPSItem);
0427     }
0428 
0429     if (!d->map->getStickyModeState())
0430     {
0431         if (!d->map->getActiveState())
0432         {
0433             d->boundariesShouldBeAdjusted = true;
0434         }
0435         else
0436         {
0437             d->boundariesShouldBeAdjusted = false;
0438             d->map->adjustBoundariesToGroupedMarkers();
0439         }
0440     }
0441 }
0442 
0443 void ItemPropertiesGPSTab::setActive(const bool state)
0444 {
0445     d->map->setActive(state);
0446 
0447     if (state && d->boundariesShouldBeAdjusted)
0448     {
0449         d->boundariesShouldBeAdjusted = false;
0450         d->map->adjustBoundariesToGroupedMarkers();
0451     }
0452 }
0453 
0454 } // namespace Digikam
0455 
0456 #include "moc_itempropertiesgpstab.cpp"