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"