File indexing completed on 2025-01-19 03:57:44
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2009-12-01 0007 * Description : main-window of the demo application 0008 * 0009 * SPDX-FileCopyrightText: 2009-2010 by Michael G. Hansen <mike at mghansen dot de> 0010 * SPDX-FileCopyrightText: 2014 by Justus Schwartz <justus at gmx dot li> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "mainwindow.h" 0017 0018 // Qt includes 0019 0020 #include <QAction> 0021 #include <QStandardItemModel> 0022 #include <QCloseEvent> 0023 #include <QComboBox> 0024 #include <QFuture> 0025 #include <QFutureWatcher> 0026 #include <QHBoxLayout> 0027 #include <QLabel> 0028 #include <QPushButton> 0029 #include <QSplitter> 0030 #include <QTimer> 0031 #include <QTreeWidget> 0032 #include <QVBoxLayout> 0033 #include <QtConcurrentMap> 0034 #include <QCommandLineParser> 0035 #include <QMenuBar> 0036 #include <QStatusBar> 0037 #include <QPointer> 0038 #include <QScopedPointer> 0039 #include <QProgressBar> 0040 0041 // KDE includes 0042 0043 #include <kconfig.h> 0044 #include <kconfiggroup.h> 0045 0046 // geoiface includes 0047 0048 #include "digikam_debug.h" 0049 #include "dmetadata.h" 0050 #include "lookupaltitude.h" 0051 #include "lookupfactory.h" 0052 #include "mapwidget.h" 0053 #include "itemmarkertiler.h" 0054 #include "geoifacecommon.h" 0055 0056 // Local includes 0057 0058 #include "mydragdrophandler.h" 0059 #include "mytreewidget.h" 0060 #include "myimageitem.h" 0061 #include "dfiledialog.h" 0062 0063 using namespace Digikam; 0064 0065 MarkerModelHelper::MarkerModelHelper(QAbstractItemModel* const itemModel, 0066 QItemSelectionModel* const itemSelectionModel) 0067 : GeoModelHelper (itemModel), 0068 m_itemModel (itemModel), 0069 m_itemSelectionModel(itemSelectionModel) 0070 { 0071 connect(itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), 0072 this, SIGNAL(signalModelChangedDrastically())); 0073 } 0074 0075 MarkerModelHelper::~MarkerModelHelper() 0076 { 0077 } 0078 0079 QAbstractItemModel* MarkerModelHelper::model() const 0080 { 0081 return m_itemModel; 0082 } 0083 0084 QItemSelectionModel* MarkerModelHelper::selectionModel() const 0085 { 0086 return m_itemSelectionModel; 0087 } 0088 0089 bool MarkerModelHelper::itemCoordinates(const QModelIndex& index, 0090 GeoCoordinates* const coordinates) const 0091 { 0092 if (!index.data(RoleCoordinates).canConvert<GeoCoordinates>()) 0093 { 0094 return false; 0095 } 0096 0097 if (coordinates) 0098 { 0099 *coordinates = index.data(RoleCoordinates).value<GeoCoordinates>(); 0100 } 0101 0102 return true; 0103 } 0104 0105 void MarkerModelHelper::onIndicesMoved(const QList<QPersistentModelIndex>& movedIndices, 0106 const GeoCoordinates& targetCoordinates, 0107 const QPersistentModelIndex& targetSnapIndex) 0108 { 0109 Q_UNUSED(targetSnapIndex); 0110 0111 for (int i = 0; i < movedIndices.count(); ++i) 0112 { 0113 m_itemModel->setData(movedIndices.at(i), 0114 QVariant::fromValue(targetCoordinates), RoleCoordinates); 0115 } 0116 0117 Q_EMIT signalMarkersMoved(movedIndices); 0118 } 0119 0120 // ---------------------------------------------------------------------- 0121 0122 class Q_DECL_HIDDEN MyImageData 0123 { 0124 public: 0125 0126 GeoCoordinates coordinates; 0127 QUrl url; 0128 }; 0129 0130 // ---------------------------------------------------------------------- 0131 0132 MyTrackModelHelper::MyTrackModelHelper(QAbstractItemModel* const imageItemsModel) 0133 : QObject (imageItemsModel), 0134 m_itemModel(imageItemsModel) 0135 { 0136 connect(imageItemsModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), 0137 this, SLOT(slotTrackModelChanged())); 0138 0139 connect(imageItemsModel, SIGNAL(rowsInserted(QModelIndex,int,int)), 0140 this, SLOT(slotTrackModelChanged())); 0141 0142 connect(imageItemsModel, SIGNAL(modelReset()), 0143 this, SLOT(slotTrackModelChanged())); 0144 } 0145 0146 void MyTrackModelHelper::slotTrackModelChanged() 0147 { 0148 m_tracks.clear(); 0149 0150 TrackManager::Track track; 0151 0152 for (int row = 0; row < m_itemModel->rowCount(); ++row) 0153 { 0154 const QModelIndex currentIndex = m_itemModel->index(row, 0); 0155 0156 if (!currentIndex.data(RoleCoordinates).canConvert<GeoCoordinates>()) 0157 { 0158 continue; 0159 } 0160 0161 const GeoCoordinates markerCoordinates = currentIndex.data(RoleCoordinates).value<GeoCoordinates>(); 0162 TrackManager::TrackPoint trackPoint; 0163 trackPoint.coordinates = markerCoordinates; 0164 track.points << trackPoint; 0165 } 0166 0167 m_tracks << track; 0168 0169 Q_EMIT signalModelChanged(); 0170 } 0171 0172 TrackManager::Track::List MyTrackModelHelper::getTracks() const 0173 { 0174 return m_tracks; 0175 } 0176 0177 // ---------------------------------------------------------------------- 0178 0179 class Q_DECL_HIDDEN MainWindow::Private 0180 { 0181 public: 0182 0183 explicit Private() 0184 : splitter (nullptr), 0185 mapWidget (nullptr), 0186 lookupAltitudeList (), 0187 treeWidget (nullptr), 0188 progressBar (nullptr), 0189 imageLoadingRunningFutures (), 0190 imageLoadingFutureWatchers (), 0191 imageLoadingTotalCount (0), 0192 imageLoadingCurrentCount (0), 0193 imageLoadingBuncher (), 0194 imageLoadingBunchTimer (nullptr), 0195 cmdLineArgs (nullptr), 0196 lastImageOpenDir (), 0197 displayMarkersModel (nullptr), 0198 selectionModel (nullptr), 0199 markerModelHelper (nullptr), 0200 trackModelHelper (nullptr) 0201 { 0202 } 0203 0204 QSplitter* splitter; 0205 MapWidget* mapWidget; 0206 QList<LookupAltitude*> lookupAltitudeList; 0207 MyTreeWidget* treeWidget; 0208 QPointer<QProgressBar> progressBar; 0209 QList<QFuture<MyImageData> > imageLoadingRunningFutures; 0210 QList<QFutureWatcher<MyImageData>*> imageLoadingFutureWatchers; 0211 int imageLoadingTotalCount; 0212 int imageLoadingCurrentCount; 0213 QList<MyImageData> imageLoadingBuncher; 0214 QTimer* imageLoadingBunchTimer; 0215 QCommandLineParser* cmdLineArgs; 0216 QUrl lastImageOpenDir; 0217 0218 QAbstractItemModel* displayMarkersModel; 0219 QItemSelectionModel* selectionModel; 0220 MarkerModelHelper* markerModelHelper; 0221 MyTrackModelHelper* trackModelHelper; 0222 }; 0223 0224 MainWindow::MainWindow(QCommandLineParser* const cmdLineArgs, QWidget* const parent) 0225 : QMainWindow(parent), 0226 d (new Private()) 0227 { 0228 // initialize Exiv2 before doing any multitasking 0229 0230 MetaEngine::initializeExiv2(); 0231 0232 d->treeWidget = new MyTreeWidget(this); 0233 d->treeWidget->setColumnCount(2); 0234 d->treeWidget->setHeaderLabels(QStringList() << QLatin1String("Filename") << QLatin1String("Coordinates")); 0235 d->treeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); 0236 0237 d->displayMarkersModel = d->treeWidget->model(); 0238 d->selectionModel = d->treeWidget->selectionModel(); 0239 d->markerModelHelper = new MarkerModelHelper(d->displayMarkersModel, d->selectionModel); 0240 d->trackModelHelper = new MyTrackModelHelper(d->treeWidget->model()); 0241 ItemMarkerTiler* const mm = new ItemMarkerTiler(d->markerModelHelper, this); 0242 0243 resize(512, 512); 0244 setWindowTitle(QLatin1String("Geolocation Interface Demo")); 0245 setWindowIcon(QIcon::fromTheme(QLatin1String("globe"))); 0246 setObjectName(QLatin1String("DemoGeoLocationInterface" )); 0247 0248 d->cmdLineArgs = cmdLineArgs; 0249 0250 d->imageLoadingBunchTimer = new QTimer(this); 0251 d->imageLoadingBunchTimer->setSingleShot(false); 0252 0253 connect(d->imageLoadingBunchTimer, SIGNAL(timeout()), 0254 this, SLOT(slotImageLoadingBunchReady())); 0255 0256 // create a status bar: 0257 0258 statusBar(); 0259 createMenus(); 0260 0261 d->splitter = new QSplitter(Qt::Vertical, this); 0262 setCentralWidget(d->splitter); 0263 0264 d->mapWidget = new MapWidget(d->splitter); 0265 d->mapWidget->setGroupedModel(mm); 0266 d->mapWidget->setActive(true); 0267 d->mapWidget->setDragDropHandler(new MyDragDropHandler(d->displayMarkersModel, d->mapWidget)); 0268 d->mapWidget->setVisibleMouseModes(MouseModePan|MouseModeZoomIntoGroup|MouseModeSelectThumbnail); 0269 d->mapWidget->setAvailableMouseModes(MouseModePan|MouseModeZoomIntoGroup|MouseModeSelectThumbnail); 0270 // d->mapWidget->setTrackModel(d->trackModelHelper); 0271 0272 connect(d->markerModelHelper, SIGNAL(signalMarkersMoved(QList<QPersistentModelIndex>)), 0273 this, SLOT(slotMarkersMoved(QList<QPersistentModelIndex>))); 0274 0275 // d->mapWidget->resize(d->mapWidget->width(), 200); 0276 d->splitter->addWidget(d->mapWidget); 0277 d->splitter->setCollapsible(0, false); 0278 d->splitter->setSizes(QList<int>()<<200); 0279 d->splitter->setStretchFactor(0, 10); 0280 0281 QWidget* const dummyWidget = new QWidget(this); 0282 QVBoxLayout* const vbox = new QVBoxLayout(dummyWidget); 0283 0284 vbox->addWidget(d->mapWidget->getControlWidget()); 0285 vbox->addWidget(d->treeWidget); 0286 0287 d->progressBar = new QProgressBar(); 0288 d->progressBar->setFormat(QLatin1String("Loading images -")); 0289 0290 d->splitter->addWidget(dummyWidget); 0291 0292 readSettings(); 0293 0294 GeoCoordinates::List markerList; 0295 0296 // ice cafe 0297 markerList<<GeoCoordinates::fromGeoUrl(QLatin1String("geo:51.0913031421,6.88878178596,44" )); 0298 0299 // bar 0300 markerList<<GeoCoordinates::fromGeoUrl(QLatin1String("geo:51.06711205,6.90020261667,43" )); 0301 0302 // Marienburg castle 0303 markerList<<GeoCoordinates::fromGeoUrl(QLatin1String("geo:51.087647318,6.88282728201,44" )); 0304 0305 // head of monster 0306 markerList<<GeoCoordinates::fromGeoUrl(QLatin1String("geo:51.0889433167,6.88000331667,39.6" )); 0307 0308 // Langenfeld 0309 markerList<<GeoCoordinates::fromGeoUrl(QLatin1String("geo:51.1100157609,6.94911003113,51" )); 0310 0311 // Sagrada Familia in Spain 0312 markerList<<GeoCoordinates::fromGeoUrl(QLatin1String("geo:41.4036480511,2.1743756533,46" )); 0313 0314 if (cmdLineArgs->isSet(QLatin1String("demopoints_single")) || 0315 cmdLineArgs->isSet(QLatin1String("demopoints_group"))) 0316 { 0317 for (int i = 0 ; i < markerList.count() ; ++i) 0318 { 0319 QTreeWidgetItem* const treeItem = new QTreeWidgetItem(); 0320 treeItem->setText(0, QString::fromLatin1("item %1").arg(i)); 0321 treeItem->setText(1, markerList.at(i).geoUrl()); 0322 0323 d->treeWidget->addTopLevelItem(treeItem); 0324 } 0325 } 0326 } 0327 0328 MainWindow::~MainWindow() 0329 { 0330 if (d->progressBar) 0331 { 0332 delete d->progressBar; 0333 } 0334 0335 delete d; 0336 } 0337 0338 void MainWindow::readSettings() 0339 { 0340 KConfig config( QLatin1String("wmw-demo-1" )); 0341 0342 const KConfigGroup groupWidgetConfig = config.group(QLatin1String("WidgetConfig")); 0343 d->mapWidget->readSettingsFromGroup(&groupWidgetConfig); 0344 0345 KConfigGroup groupMainWindowConfig = config.group(QLatin1String("MainWindowConfig")); 0346 d->lastImageOpenDir = groupMainWindowConfig.readEntry("Last Image Open Directory", QUrl()); 0347 0348 if (groupMainWindowConfig.hasKey("SplitterState")) 0349 { 0350 const QByteArray splitterState = QByteArray::fromBase64(groupMainWindowConfig.readEntry(QLatin1String("SplitterState"), QByteArray())); 0351 0352 if (!splitterState.isEmpty()) 0353 { 0354 d->splitter->restoreState(splitterState); 0355 } 0356 } 0357 } 0358 0359 void MainWindow::saveSettings() 0360 { 0361 KConfig config( QLatin1String("wmw-demo-1" )); 0362 0363 KConfigGroup groupWidgetConfig = config.group(QLatin1String("WidgetConfig")); 0364 d->mapWidget->saveSettingsToGroup(&groupWidgetConfig); 0365 0366 KConfigGroup groupMainWindowConfig = config.group(QLatin1String("MainWindowConfig")); 0367 groupMainWindowConfig.writeEntry("Last Image Open Directory", d->lastImageOpenDir.toLocalFile()); 0368 groupMainWindowConfig.writeEntry(QLatin1String("SplitterState"), d->splitter->saveState().toBase64()); 0369 } 0370 0371 void MainWindow::closeEvent(QCloseEvent* e) 0372 { 0373 if (!e) 0374 { 0375 return; 0376 } 0377 0378 saveSettings(); 0379 e->accept(); 0380 } 0381 0382 MyImageData LoadImageData(const QUrl& urlToLoad) 0383 { 0384 MyImageData imageData; 0385 imageData.url = urlToLoad; 0386 0387 // TODO: error handling! 0388 QScopedPointer<DMetadata> meta(new DMetadata); 0389 meta->load(urlToLoad.toLocalFile()); 0390 double lat, lon, alt; 0391 0392 if (meta->getGPSInfo(alt, lat, lon)) 0393 { 0394 imageData.coordinates.setLatLon(lat, lon); 0395 imageData.coordinates.setAlt(alt); 0396 } 0397 0398 return imageData; 0399 } 0400 0401 void MainWindow::slotFutureResultsReadyAt(int startIndex, int endIndex) 0402 { 0403 // //qCDebug(DIGIKAM_TESTS_LOG)<<"future"<<startIndex<<endIndex; 0404 0405 // determine the sender: 0406 QFutureWatcher<MyImageData>* const futureSender = reinterpret_cast<QFutureWatcher<MyImageData>*>(sender()); 0407 0408 GEOIFACE_ASSERT(futureSender != nullptr); 0409 0410 if (futureSender == nullptr) 0411 { 0412 return; 0413 } 0414 0415 int futureIndex = -1; 0416 0417 for (int i = 0; i < d->imageLoadingFutureWatchers.size(); ++i) 0418 { 0419 if (d->imageLoadingFutureWatchers.at(i) == futureSender) 0420 { 0421 futureIndex = i; 0422 break; 0423 } 0424 } 0425 0426 GEOIFACE_ASSERT(futureIndex >= 0); 0427 0428 if (futureIndex < 0) 0429 { 0430 // TODO: error! 0431 return; 0432 } 0433 0434 for (int index = startIndex; index < endIndex; ++index) 0435 { 0436 MyImageData newData = d->imageLoadingRunningFutures.at(futureIndex).resultAt(index); 0437 // //qCDebug(DIGIKAM_TESTS_LOG)<<"future"<<newData.url<<newData.coordinates.geoUrl(); 0438 0439 d->imageLoadingBuncher << newData; 0440 } 0441 0442 d->imageLoadingCurrentCount+= endIndex-startIndex; 0443 0444 if (d->imageLoadingCurrentCount < d->imageLoadingTotalCount) 0445 { 0446 d->progressBar->setValue(d->imageLoadingCurrentCount); 0447 } 0448 else 0449 { 0450 statusBar()->removeWidget(d->progressBar); 0451 statusBar()->showMessage(QLatin1String("%1 image(s) have been loaded.", d->imageLoadingTotalCount), 3000); 0452 d->imageLoadingCurrentCount = 0; 0453 d->imageLoadingTotalCount = 0; 0454 0455 // remove the QFutures: 0456 qDeleteAll(d->imageLoadingFutureWatchers); 0457 d->imageLoadingFutureWatchers.clear(); 0458 d->imageLoadingRunningFutures.clear(); 0459 0460 d->imageLoadingBunchTimer->stop(); 0461 0462 // force display of all images: 0463 QTimer::singleShot(0, this, SLOT(slotImageLoadingBunchReady()));; 0464 } 0465 } 0466 0467 void MainWindow::slotScheduleImagesForLoading(const QList<QUrl>& imagesToSchedule) 0468 { 0469 if (imagesToSchedule.isEmpty()) 0470 { 0471 return; 0472 } 0473 0474 if (d->imageLoadingTotalCount == 0) 0475 { 0476 statusBar()->addWidget(d->progressBar); 0477 d->imageLoadingBunchTimer->start(100); 0478 } 0479 0480 d->imageLoadingTotalCount+=imagesToSchedule.count(); 0481 d->progressBar->setRange(0, d->imageLoadingTotalCount); 0482 d->progressBar->setValue(d->imageLoadingCurrentCount); 0483 QFutureWatcher<MyImageData>* const watcher = new QFutureWatcher<MyImageData>(this); 0484 0485 connect(watcher, SIGNAL(resultsReadyAt(int,int)), 0486 this, SLOT(slotFutureResultsReadyAt(int,int))); 0487 0488 QFuture<MyImageData> future = QtConcurrent::mapped(imagesToSchedule, LoadImageData); 0489 watcher->setFuture(future); 0490 0491 d->imageLoadingRunningFutures << future; 0492 d->imageLoadingFutureWatchers << watcher; 0493 } 0494 0495 void MainWindow::slotImageLoadingBunchReady() 0496 { 0497 qCDebug(DIGIKAM_TESTS_LOG) << "slotImageLoadingBunchReady"; 0498 0499 for (int i = 0; i < d->imageLoadingBuncher.count(); ++i) 0500 { 0501 const MyImageData& currentInfo = d->imageLoadingBuncher.at(i); 0502 0503 // add the item to the tree widget: 0504 QTreeWidgetItem* const treeItem = new MyImageItem(currentInfo.url, currentInfo.coordinates); 0505 d->treeWidget->addTopLevelItem(treeItem); 0506 } 0507 0508 d->imageLoadingBuncher.clear(); 0509 0510 if (d->imageLoadingTotalCount == 0) 0511 { 0512 // remove the QFutures: 0513 qDeleteAll(d->imageLoadingFutureWatchers); 0514 d->imageLoadingFutureWatchers.clear(); 0515 d->imageLoadingRunningFutures.clear(); 0516 } 0517 } 0518 0519 void MainWindow::slotMarkersMoved(const QList<QPersistentModelIndex>& markerIndices) 0520 { 0521 // prepare altitude lookups 0522 LookupAltitude::Request::List altitudeQueries; 0523 0524 for (int i = 0; i < markerIndices.count(); ++i) 0525 { 0526 const QPersistentModelIndex currentIndex = markerIndices.at(i); 0527 const GeoCoordinates newCoordinates = currentIndex.data(RoleCoordinates).value<GeoCoordinates>(); 0528 0529 LookupAltitude::Request myLookup; 0530 myLookup.coordinates = newCoordinates; 0531 myLookup.data = QVariant::fromValue(QPersistentModelIndex(currentIndex)); 0532 altitudeQueries << myLookup; 0533 } 0534 0535 if (!altitudeQueries.isEmpty()) 0536 { 0537 LookupAltitude* const myAltitudeLookup = LookupFactory::getAltitudeLookup(QLatin1String("geonames"), this); 0538 0539 connect(myAltitudeLookup, SIGNAL(signalRequestsReady(QList<int>)), 0540 this, SLOT(slotAltitudeRequestsReady(QList<int>))); 0541 0542 connect(myAltitudeLookup, SIGNAL(signalDone()), 0543 this, SLOT(slotAltitudeLookupDone())); 0544 0545 myAltitudeLookup->addRequests(altitudeQueries); 0546 0547 d->lookupAltitudeList << myAltitudeLookup; 0548 0549 /// @todo Check the return value? 0550 myAltitudeLookup->startLookup(); 0551 qCDebug(DIGIKAM_TESTS_LOG) << "Starting lookup for " << altitudeQueries.count() << " items!"; 0552 } 0553 } 0554 0555 void MainWindow::slotAltitudeRequestsReady(const QList<int>& readyRequests) 0556 { 0557 qCDebug(DIGIKAM_TESTS_LOG) << readyRequests.count() << " items ready!"; 0558 LookupAltitude* const myAltitudeLookup = qobject_cast<LookupAltitude*>(sender()); 0559 0560 if (!myAltitudeLookup) 0561 { 0562 return; 0563 } 0564 0565 for (int i = 0; i < readyRequests.count(); ++i) 0566 { 0567 const LookupAltitude::Request& myLookup = myAltitudeLookup->getRequest(readyRequests.at(i)); 0568 const QPersistentModelIndex markerIndex = myLookup.data.value<QPersistentModelIndex>(); 0569 0570 if (!markerIndex.isValid()) 0571 { 0572 continue; 0573 } 0574 0575 /// @todo Why does the index return a const model??? 0576 const QAbstractItemModel* const itemModel = markerIndex.model(); 0577 const_cast<QAbstractItemModel*>(itemModel)->setData(markerIndex, QVariant::fromValue(myLookup.coordinates), RoleCoordinates); 0578 } 0579 } 0580 0581 void MainWindow::slotAltitudeLookupDone() 0582 { 0583 LookupAltitude* const myAltitudeLookup = qobject_cast<LookupAltitude*>(sender()); 0584 0585 if (!myAltitudeLookup) 0586 { 0587 return; 0588 } 0589 0590 d->lookupAltitudeList.removeOne(myAltitudeLookup); 0591 myAltitudeLookup->deleteLater(); 0592 } 0593 0594 void MainWindow::slotAddImages() 0595 { 0596 const QList<QUrl> fileNames = DFileDialog::getOpenFileUrls(this, QLatin1String("Add image files"), d->lastImageOpenDir, QLatin1String("Images (*.jpg *.jpeg *.png *.tif *.tiff)")); 0597 0598 if (fileNames.isEmpty()) 0599 { 0600 return; 0601 } 0602 0603 d->lastImageOpenDir = fileNames.first().resolved(QUrl(QLatin1String("../"))); 0604 0605 slotScheduleImagesForLoading(fileNames); 0606 } 0607 0608 void MainWindow::createMenus() 0609 { 0610 QMenu* const fileMenu = menuBar()->addMenu(QLatin1String("File")); 0611 QAction* const addFilesAction = new QAction(QLatin1String("Add images..."), fileMenu); 0612 fileMenu->addAction(addFilesAction); 0613 0614 connect(addFilesAction, SIGNAL(triggered()), 0615 this, SLOT(slotAddImages())); 0616 } 0617 0618 GeoModelHelper::PropertyFlags MarkerModelHelper::modelFlags() const 0619 { 0620 return FlagMovable; 0621 } 0622 0623 #include "moc_mainwindow.cpp"