1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2009-12-01
7 * Description : world map widget library
8 *
9 * Copyright (C) 2010-2021 by Gilles Caulier <caulier dot gilles at gmail dot com>
10 * Copyright (C) 2009-2011 by Michael G. Hansen <mike at mghansen dot de>
11 * Copyright (C) 2014 by Justus Schwartz <justus at gmx dot li>
12 *
13 * This program is free software; you can redistribute it
14 * and/or modify it under the terms of the GNU General
15 * Public License as published by the Free Software Foundation;
16 * either version 2, or (at your option)
17 * any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * ============================================================ */
25
26 #include "mapwidget.h"
27
28 // C++ includes
29
30 #include <cmath>
31
32 // Qt includes
33
34 #include <QDragEnterEvent>
35 #include <QDropEvent>
36 #include <QItemSelectionModel>
37 #include <QMenu>
38 #include <QPainter>
39 #include <QPointer>
40 #include <QStackedLayout>
41 #include <QTimer>
42 #include <QPushButton>
43 #include <QToolButton>
44 #include <QHBoxLayout>
45 #include <QAction>
46 #include <QFrame>
47
48 // KDE includes
49
50 #include <klocalizedstring.h>
51 #include <kconfiggroup.h>
52
53 // Marbel includes
54
55 #include <marble/GeoDataLineString.h>
56 #include <marble/GeoDataLatLonBox.h>
57 #include <marble/MarbleGlobal.h>
58
59 // local includes
60
61 #include "geoifacecommon.h"
62 #include "geodragdrophandler.h"
63 #include "geomodelhelper.h"
64 #include "trackmanager.h"
65 #include "placeholderwidget.h"
66 #include "tilegrouper.h"
67 #include "digikam_debug.h"
68 #include "abstractmarkertiler.h"
69 #include "backendgooglemaps.h"
70 #include "backendmarble.h"
71
72 namespace Digikam
73 {
74
75 /**
76 * @class MapWidget
77 * @brief The central map view class of geolocation interface
78 *
79 * The MapWidget class is the central widget of geolocation interface. It provides a widget which can display maps using
80 * either the Marble or Google Maps backend. Using a model, items can be displayed on the map. For
81 * models containing only a small number of items, the items can be shown directly, but for models with
82 * a larger number of items, the items can also be grouped. Currently, any number of ungrouped models
83 * can be shown, but only one grouped model. Item selection models can also be used along with the models,
84 * to interact with the selection states of the items on the map. In order to use a model with geolocation interface, however,
85 * a model helper has to be implemented, which extracts data from the model that is not provided by the Qt part
86 * of a model's API.
87 *
88 * Now, a brief introduction on how to get geolocation interface working is provided:
89 * @li First, an instance of @c MapWidget has to be created.
90 * @li Next, @c GeoModelHelper has to be subclassed and at least the pure virtual functions have to be implemented.
91 * @li To show the model's data ungrouped, the model helper has to be added to @c MapWidget instance using addUngroupedModel.
92 * @li To show the model's data grouped, an instance of @c AbstractMarkerTiler has to be created and the model helper has to be
93 * set to it using setMarkerGeoModelHelper. The @c AbstractMarkerTiler has then to be given to MapWidget using setGroupedModel. If
94 * the items to be displayed do not reside in a model, a subclass of @c AbstractMarkerTiler can be created which returns
95 * just the number of items in a particular area, and picks representative items for thumbnails.
96 * @li To handle dropping of items from the host applications UI onto the map, @c DragDropHandler has to be subclassed
97 * as well and added to the model using setDragDropHandler.
98 * @li Finally, setActive() has to be called to tell the widget that it should start displaying things.
99 */
100
101 class Q_DECL_HIDDEN MapWidget::Private
102 {
103 public:
104
Private()105 explicit Private()
106 : loadedBackends(),
107 currentBackend(nullptr),
108 currentBackendName(),
109 stackedLayout(nullptr),
110 cacheCenterCoordinate(52.0,6.0),
111 cacheZoom(QLatin1String("marble:900")),
112 configurationMenu(nullptr),
113 actionGroupBackendSelection(nullptr),
114 actionZoomIn(nullptr),
115 actionZoomOut(nullptr),
116 actionShowThumbnails(nullptr),
117 mouseModesHolder(nullptr),
118 controlWidget(nullptr),
119 actionPreviewSingleItems(nullptr),
120 actionPreviewGroupedItems(nullptr),
121 actionShowNumbersOnItems(nullptr),
122 lazyReclusteringRequested(false),
123 dragDropHandler(nullptr),
124 sortMenu(nullptr),
125 actionIncreaseThumbnailSize(nullptr),
126 actionDecreaseThumbnailSize(nullptr),
127 hBoxForAdditionalControlWidgetItems(nullptr),
128 mouseModeActionGroup(nullptr),
129 actionRemoveCurrentRegionSelection(nullptr),
130 actionSetRegionSelectionMode(nullptr),
131 actionSetPanMode(nullptr),
132 actionSetZoomIntoGroupMode(nullptr),
133 actionSetRegionSelectionFromIconMode(nullptr),
134 actionSetFilterMode(nullptr),
135 actionRemoveFilter(nullptr),
136 actionSetSelectThumbnailMode(nullptr),
137 setPanModeButton(nullptr),
138 setSelectionModeButton(nullptr),
139 removeCurrentSelectionButton(nullptr),
140 setZoomModeButton(nullptr),
141 setRegionSelectionFromIconModeButton(nullptr),
142 setFilterModeButton(nullptr),
143 removeFilterModeButton(nullptr),
144 setSelectThumbnailMode(nullptr),
145 thumbnailTimer(nullptr),
146 thumbnailTimerCount(0),
147 thumbnailsHaveBeenLoaded(false),
148 availableExtraActions(),
149 visibleExtraActions(),
150 actionStickyMode(nullptr),
151 buttonStickyMode(nullptr),
152 placeholderWidget(nullptr)
153 {
154 }
155
156 QList<MapBackend*> loadedBackends;
157 MapBackend* currentBackend;
158 QString currentBackendName;
159 QStackedLayout* stackedLayout;
160
161 /// NOTE: these values are cached in case the backend is not ready:
162 GeoCoordinates cacheCenterCoordinate;
163 QString cacheZoom;
164
165 /// actions for controlling the widget
166 QMenu* configurationMenu;
167 QActionGroup* actionGroupBackendSelection;
168 QAction* actionZoomIn;
169 QAction* actionZoomOut;
170 QAction* actionShowThumbnails;
171 QWidget* mouseModesHolder;
172 QPointer<QWidget> controlWidget;
173 QAction* actionPreviewSingleItems;
174 QAction* actionPreviewGroupedItems;
175 QAction* actionShowNumbersOnItems;
176
177 bool lazyReclusteringRequested;
178
179 GeoDragDropHandler* dragDropHandler;
180
181 QMenu* sortMenu;
182 QAction* actionIncreaseThumbnailSize;
183 QAction* actionDecreaseThumbnailSize;
184 QWidget* hBoxForAdditionalControlWidgetItems;
185
186 QActionGroup* mouseModeActionGroup;
187 QAction* actionRemoveCurrentRegionSelection;
188 QAction* actionSetRegionSelectionMode;
189 QAction* actionSetPanMode;
190 QAction* actionSetZoomIntoGroupMode;
191 QAction* actionSetRegionSelectionFromIconMode;
192 QAction* actionSetFilterMode;
193 QAction* actionRemoveFilter;
194 QAction* actionSetSelectThumbnailMode;
195 QToolButton* setPanModeButton;
196 QToolButton* setSelectionModeButton;
197 QToolButton* removeCurrentSelectionButton;
198 QToolButton* setZoomModeButton;
199 QToolButton* setRegionSelectionFromIconModeButton;
200 QToolButton* setFilterModeButton;
201 QToolButton* removeFilterModeButton;
202 QToolButton* setSelectThumbnailMode;
203
204 QTimer* thumbnailTimer;
205 int thumbnailTimerCount;
206 bool thumbnailsHaveBeenLoaded;
207
208 GeoExtraActions availableExtraActions;
209 GeoExtraActions visibleExtraActions;
210 QAction* actionStickyMode;
211 QToolButton* buttonStickyMode;
212
213 /// NOTE: to be sorted later
214 PlaceholderWidget* placeholderWidget;
215 };
216
MapWidget(QWidget * const parent)217 MapWidget::MapWidget(QWidget* const parent)
218 : QWidget(parent),
219 s(new GeoIfaceSharedData),
220 d(new Private)
221 {
222 createActions();
223
224 s->worldMapWidget = this;
225 s->tileGrouper = new TileGrouper(s, this);
226
227 d->stackedLayout = new QStackedLayout(this);
228 setLayout(d->stackedLayout);
229
230 d->placeholderWidget = new PlaceholderWidget();
231 d->stackedLayout->addWidget(d->placeholderWidget);
232
233 d->loadedBackends.append(new BackendGoogleMaps(s, this));
234 d->loadedBackends.append(new BackendMarble(s, this));
235 /*
236 d->loadedBackends.append(new BackendOSM(s, this));
237 */
238 createActionsForBackendSelection();
239
240 setAcceptDrops(true);
241 }
242
createActions()243 void MapWidget::createActions()
244 {
245 d->actionZoomIn = new QAction(this);
246 d->actionZoomIn->setIcon(QIcon::fromTheme( QLatin1String("zoom-in") ));
247 d->actionZoomIn->setToolTip(i18n("Zoom in"));
248
249 connect(d->actionZoomIn, &QAction::triggered,
250 this, &MapWidget::slotZoomIn);
251
252 d->actionZoomOut = new QAction(this);
253 d->actionZoomOut->setIcon(QIcon::fromTheme( QLatin1String("zoom-out") ));
254 d->actionZoomOut->setToolTip(i18n("Zoom out"));
255
256 connect(d->actionZoomOut, &QAction::triggered,
257 this, &MapWidget::slotZoomOut);
258
259 d->actionShowThumbnails = new QAction(this);
260 d->actionShowThumbnails->setToolTip(i18n("Switch between markers and thumbnails."));
261 d->actionShowThumbnails->setCheckable(true);
262 d->actionShowThumbnails->setChecked(true);
263
264 connect(d->actionShowThumbnails, &QAction::triggered,
265 this, &MapWidget::slotShowThumbnailsChanged);
266
267 // create backend selection entries:
268
269 d->actionGroupBackendSelection = new QActionGroup(this);
270 d->actionGroupBackendSelection->setExclusive(true);
271
272 connect(d->actionGroupBackendSelection, &QActionGroup::triggered,
273 this, &MapWidget::slotChangeBackend);
274
275 createActionsForBackendSelection();
276
277 d->configurationMenu = new QMenu(this);
278 d->actionPreviewSingleItems = new QAction(i18n("Preview single items"), this);
279 d->actionPreviewSingleItems->setCheckable(true);
280 d->actionPreviewSingleItems->setChecked(true);
281 d->actionPreviewGroupedItems = new QAction(i18n("Preview grouped items"), this);
282 d->actionPreviewGroupedItems->setCheckable(true);
283 d->actionPreviewGroupedItems->setChecked(true);
284 d->actionShowNumbersOnItems = new QAction(i18n("Show numbers"), this);
285 d->actionShowNumbersOnItems->setCheckable(true);
286 d->actionShowNumbersOnItems->setChecked(true);
287
288 d->actionIncreaseThumbnailSize = new QAction(i18n("T+"), this);
289 d->actionIncreaseThumbnailSize->setToolTip(i18n("Increase the thumbnail size on the map"));
290 d->actionDecreaseThumbnailSize = new QAction(i18n("T-"), this);
291 d->actionDecreaseThumbnailSize->setToolTip(i18n("Decrease the thumbnail size on the map"));
292
293 d->actionRemoveCurrentRegionSelection = new QAction(this);
294 /*
295 d->actionRemoveCurrentRegionSelection->setEnabled(false);
296 */
297 d->actionRemoveCurrentRegionSelection->setIcon(QIcon::fromTheme( QLatin1String("edit-clear") ));
298 d->actionRemoveCurrentRegionSelection->setToolTip(i18n("Remove the current region selection"));
299
300 d->mouseModeActionGroup = new QActionGroup(this);
301 d->mouseModeActionGroup->setExclusive(true);
302
303 d->actionSetRegionSelectionMode = new QAction(d->mouseModeActionGroup);
304 d->actionSetRegionSelectionMode->setCheckable(true);
305 d->actionSetRegionSelectionMode->setIcon(QIcon::fromTheme( QLatin1String("select-rectangular") ));
306 d->actionSetRegionSelectionMode->setToolTip(i18n("Select images by drawing a rectangle"));
307 d->actionSetRegionSelectionMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeRegionSelection));
308
309 d->actionSetPanMode = new QAction(d->mouseModeActionGroup);
310 d->actionSetPanMode->setCheckable(true);
311 d->actionSetPanMode->setToolTip(i18n("Pan mode"));
312 d->actionSetPanMode->setIcon(QIcon::fromTheme( QLatin1String("transform-move") ));
313 d->actionSetPanMode->setChecked(true);
314 d->actionSetPanMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModePan));
315
316 d->actionSetZoomIntoGroupMode = new QAction(d->mouseModeActionGroup);
317 d->actionSetZoomIntoGroupMode->setCheckable(true);
318 d->actionSetZoomIntoGroupMode->setToolTip(i18n("Zoom into a group"));
319 d->actionSetZoomIntoGroupMode->setIcon(QIcon::fromTheme( QLatin1String("zoom-fit-best") ));
320 d->actionSetZoomIntoGroupMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeZoomIntoGroup));
321
322 d->actionSetRegionSelectionFromIconMode = new QAction(d->mouseModeActionGroup);
323 d->actionSetRegionSelectionFromIconMode->setCheckable(true);
324 d->actionSetRegionSelectionFromIconMode->setToolTip(i18n("Create a region selection from a thumbnail"));
325 d->actionSetRegionSelectionFromIconMode->setIcon(QIcon::fromTheme( QLatin1String("edit-node") ));
326 d->actionSetRegionSelectionFromIconMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeRegionSelectionFromIcon));
327
328 d->actionSetFilterMode = new QAction(d->mouseModeActionGroup);
329 d->actionSetFilterMode->setCheckable(true);
330 d->actionSetFilterMode->setToolTip(i18n("Filter images"));
331 d->actionSetFilterMode->setIcon(QIcon::fromTheme( QLatin1String("view-filter") ));
332 d->actionSetFilterMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeFilter));
333
334 d->actionRemoveFilter = new QAction(this);
335 d->actionRemoveFilter->setToolTip(i18n("Remove the current filter"));
336 d->actionRemoveFilter->setIcon(QIcon::fromTheme( QLatin1String("window-close") ));
337
338 d->actionSetSelectThumbnailMode = new QAction(d->mouseModeActionGroup);
339 d->actionSetSelectThumbnailMode->setCheckable(true);
340 d->actionSetSelectThumbnailMode->setToolTip(i18n("Select images"));
341 d->actionSetSelectThumbnailMode->setIcon(QIcon::fromTheme( QLatin1String("edit-select") ));
342 d->actionSetSelectThumbnailMode->setData(QVariant::fromValue<Digikam::GeoMouseModes>(MouseModeSelectThumbnail));
343
344 d->actionStickyMode = new QAction(this);
345 d->actionStickyMode->setCheckable(true);
346 d->actionStickyMode->setToolTip(i18n("Lock the map position"));
347
348 connect(d->actionStickyMode, &QAction::triggered,
349 this, &MapWidget::slotStickyModeChanged);
350
351 connect(d->actionIncreaseThumbnailSize, &QAction::triggered,
352 this, &MapWidget::slotIncreaseThumbnailSize);
353
354 connect(d->actionDecreaseThumbnailSize, &QAction::triggered,
355 this, &MapWidget::slotDecreaseThumbnailSize);
356
357 connect(d->actionPreviewSingleItems, &QAction::changed,
358 this, &MapWidget::slotItemDisplaySettingsChanged);
359
360 connect(d->actionPreviewGroupedItems, &QAction::changed,
361 this, &MapWidget::slotItemDisplaySettingsChanged);
362
363 connect(d->actionShowNumbersOnItems, &QAction::changed,
364 this, &MapWidget::slotItemDisplaySettingsChanged);
365
366 connect(d->mouseModeActionGroup, &QActionGroup::triggered,
367 this, &MapWidget::slotMouseModeChanged);
368
369 connect(d->actionRemoveFilter, &QAction::triggered,
370 this, &MapWidget::signalRemoveCurrentFilter);
371
372 connect(d->actionRemoveCurrentRegionSelection, &QAction::triggered,
373 this, &MapWidget::slotRemoveCurrentRegionSelection);
374 }
375
createActionsForBackendSelection()376 void MapWidget::createActionsForBackendSelection()
377 {
378 // delete the existing actions:
379
380 qDeleteAll(d->actionGroupBackendSelection->actions());
381
382 // create actions for all backends:
383
384 for (int i = 0 ; i < d->loadedBackends.size() ; ++i)
385 {
386 const QString backendName = d->loadedBackends.at(i)->backendName();
387 QAction* const backendAction = new QAction(d->actionGroupBackendSelection);
388 backendAction->setData(backendName);
389 backendAction->setText(d->loadedBackends.at(i)->backendHumanName());
390 backendAction->setCheckable(true);
391 }
392 }
393
~MapWidget()394 MapWidget::~MapWidget()
395 {
396 // release all widgets:
397
398 for (int i = 0 ; i < d->stackedLayout->count() ; ++i)
399 {
400 d->stackedLayout->removeWidget(d->stackedLayout->widget(i));
401 }
402
403 qDeleteAll(d->loadedBackends);
404 d->currentBackend = nullptr;
405 d->loadedBackends.clear();
406 delete d;
407
408 /// @todo delete s, but make sure it is not accessed by any other objects any more!
409 }
410
availableBackends() const411 QStringList MapWidget::availableBackends() const
412 {
413 QStringList result;
414
415 foreach (MapBackend* const backend, d->loadedBackends)
416 {
417 result.append(backend->backendName());
418 }
419
420 return result;
421 }
422
setBackend(const QString & backendName)423 bool MapWidget::setBackend(const QString& backendName)
424 {
425 if (backendName == d->currentBackendName)
426 {
427 return true;
428 }
429
430 saveBackendToCache();
431
432 // switch to the placeholder widget:
433
434 setShowPlaceholderWidget(true);
435 removeMapWidgetFromFrame();
436
437 // disconnect signals from old backend:
438
439 if (d->currentBackend)
440 {
441 d->currentBackend->setActive(false);
442
443 disconnect(d->currentBackend, SIGNAL(signalBackendReadyChanged(QString)),
444 this, SLOT(slotBackendReadyChanged(QString)));
445
446 disconnect(d->currentBackend, SIGNAL(signalZoomChanged(QString)),
447 this, SLOT(slotBackendZoomChanged(QString)));
448
449 disconnect(d->currentBackend, SIGNAL(signalClustersMoved(QIntList,QPair<int,QModelIndex>)),
450 this, SLOT(slotClustersMoved(QIntList,QPair<int,QModelIndex>)));
451
452 disconnect(d->currentBackend, SIGNAL(signalClustersClicked(QIntList)),
453 this, SLOT(slotClustersClicked(QIntList)));
454
455 disconnect(this, SIGNAL(signalUngroupedModelChanged(int)),
456 d->currentBackend, SLOT(slotUngroupedModelChanged(int)));
457
458 if (s->markerModel)
459 {
460 disconnect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)),
461 d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap)));
462 }
463
464 disconnect(d->currentBackend, SIGNAL(signalSelectionHasBeenMade(Digikam::GeoCoordinates::Pair)),
465 this, SLOT(slotNewSelectionFromMap(Digikam::GeoCoordinates::Pair)));
466
467 }
468
469 foreach (MapBackend* const backend, d->loadedBackends)
470 {
471 if (backend->backendName() == backendName)
472 {
473 qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("setting backend %1").arg(backendName);
474 d->currentBackend = backend;
475 d->currentBackendName = backendName;
476
477 connect(d->currentBackend, &MapBackend::signalBackendReadyChanged,
478 this, &MapWidget::slotBackendReadyChanged);
479
480 connect(d->currentBackend, &MapBackend::signalZoomChanged,
481 this, &MapWidget::slotBackendZoomChanged);
482
483 connect(d->currentBackend, &MapBackend::signalClustersMoved,
484 this, &MapWidget::slotClustersMoved);
485
486 connect(d->currentBackend, &MapBackend::signalClustersClicked,
487 this, &MapWidget::slotClustersClicked);
488
489 /**
490 * @todo This connection is queued because otherwise QAbstractItemModel::itemSelected does not
491 * reflect the true state. Maybe monitor another signal instead?
492 */
493 connect(this, SIGNAL(signalUngroupedModelChanged(int)),
494 d->currentBackend, SLOT(slotUngroupedModelChanged(int)), Qt::QueuedConnection);
495
496 if (s->markerModel)
497 {
498 connect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)),
499 d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap)));
500 }
501
502 connect(d->currentBackend, &MapBackend::signalSelectionHasBeenMade,
503 this, &MapWidget::slotNewSelectionFromMap);
504
505 if (s->activeState)
506 {
507 setMapWidgetInFrame(d->currentBackend->mapWidget());
508
509 // call this slot manually in case the backend was ready right away:
510
511 if (d->currentBackend->isReady())
512 {
513 slotBackendReadyChanged(d->currentBackendName);
514 }
515 else
516 {
517 rebuildConfigurationMenu();
518 }
519 }
520
521 d->currentBackend->setActive(s->activeState);
522
523 return true;
524 }
525 }
526
527 return false;
528 }
529
applyCacheToBackend()530 void MapWidget::applyCacheToBackend()
531 {
532 if ((!currentBackendReady()) || (!s->activeState))
533 {
534 return;
535 }
536
537 /// @todo Only do this if the zoom was changed!
538
539 qCDebug(DIGIKAM_GEOIFACE_LOG) << d->cacheZoom;
540
541 setZoom(d->cacheZoom);
542 setCenter(d->cacheCenterCoordinate);
543 d->currentBackend->mouseModeChanged();
544 d->currentBackend->regionSelectionChanged();
545 }
546
saveBackendToCache()547 void MapWidget::saveBackendToCache()
548 {
549 if (!currentBackendReady())
550 {
551 return;
552 }
553
554 d->cacheCenterCoordinate = getCenter();
555 d->cacheZoom = getZoom();
556 }
557
getCenter() const558 GeoCoordinates MapWidget::getCenter() const
559 {
560 if (!currentBackendReady())
561 {
562 return d->cacheCenterCoordinate;
563 }
564
565 return d->currentBackend->getCenter();
566 }
567
setCenter(const GeoCoordinates & coordinate)568 void MapWidget::setCenter(const GeoCoordinates& coordinate)
569 {
570 d->cacheCenterCoordinate = coordinate;
571
572 if (!currentBackendReady())
573 {
574 return;
575 }
576
577 d->currentBackend->setCenter(coordinate);
578 }
579
slotBackendReadyChanged(const QString & backendName)580 void MapWidget::slotBackendReadyChanged(const QString& backendName)
581 {
582 qCDebug(DIGIKAM_GEOIFACE_LOG) << QString::fromLatin1("backend %1 is ready!").arg(backendName);
583
584 if (backendName != d->currentBackendName)
585 {
586 return;
587 }
588
589 if (!currentBackendReady())
590 {
591 return;
592 }
593
594 applyCacheToBackend();
595
596 setShowPlaceholderWidget(false);
597
598 if (!d->thumbnailsHaveBeenLoaded)
599 {
600 d->thumbnailTimer = new QTimer(this);
601 d->thumbnailTimerCount = 0;
602
603 connect(d->thumbnailTimer, &QTimer::timeout,
604 this, &MapWidget::stopThumbnailTimer);
605
606 d->thumbnailTimer->start(2000);
607 }
608
609 updateMarkers();
610 markClustersAsDirty();
611 rebuildConfigurationMenu();
612 }
613
stopThumbnailTimer()614 void MapWidget::stopThumbnailTimer()
615 {
616 d->currentBackend->updateMarkers();
617 d->thumbnailTimerCount++;
618
619 if (d->thumbnailTimerCount == 10)
620 {
621 d->thumbnailTimer->stop();
622 d->thumbnailsHaveBeenLoaded = true;
623 }
624 }
625
saveSettingsToGroup(KConfigGroup * const group)626 void MapWidget::saveSettingsToGroup(KConfigGroup* const group)
627 {
628 GEOIFACE_ASSERT(group != nullptr);
629
630 if (!group)
631 {
632 return;
633 }
634
635 if (!d->currentBackendName.isEmpty())
636 {
637 group->writeEntry("Backend", d->currentBackendName);
638 }
639
640 group->writeEntry("Center", getCenter().geoUrl());
641 group->writeEntry("Zoom", getZoom());
642 group->writeEntry("Preview Single Items", s->previewSingleItems);
643 group->writeEntry("Preview Grouped Items", s->previewGroupedItems);
644 group->writeEntry("Show numbers on items", s->showNumbersOnItems);
645 group->writeEntry("Thumbnail Size", s->thumbnailSize);
646 group->writeEntry("Thumbnail Grouping Radius", s->thumbnailGroupingRadius);
647 group->writeEntry("Marker Grouping Radius", s->markerGroupingRadius);
648 group->writeEntry("Show Thumbnails", s->showThumbnails);
649 group->writeEntry("Mouse Mode", int(s->currentMouseMode));
650
651 if (d->visibleExtraActions.testFlag(ExtraActionSticky))
652 {
653 group->writeEntry("Sticky Mode State", d->actionStickyMode->isChecked());
654 }
655
656 for (int i = 0 ; i < d->loadedBackends.size() ; ++i)
657 {
658 d->loadedBackends.at(i)->saveSettingsToGroup(group);
659 }
660 }
661
readSettingsFromGroup(const KConfigGroup * const group)662 void MapWidget::readSettingsFromGroup(const KConfigGroup* const group)
663 {
664 GEOIFACE_ASSERT(group != nullptr);
665
666 if (!group)
667 {
668 return;
669 }
670
671 if (d->currentBackendName.isEmpty())
672 {
673 setBackend(group->readEntry("Backend", "marble"));
674 }
675
676 // Options concerning the display of markers
677
678 d->actionPreviewSingleItems->setChecked(group->readEntry("Preview Single Items", true));
679 d->actionPreviewGroupedItems->setChecked(group->readEntry("Preview Grouped Items", true));
680 d->actionShowNumbersOnItems->setChecked(group->readEntry("Show numbers on items", true));
681
682 setThumnailSize(group->readEntry("Thumbnail Size", 2*GeoIfaceMinThumbnailSize));
683 setThumbnailGroupingRadius(group->readEntry("Thumbnail Grouping Radius", 2*GeoIfaceMinThumbnailGroupingRadius));
684 setMarkerGroupingRadius(group->readEntry("Edit Grouping Radius", GeoIfaceMinMarkerGroupingRadius));
685 s->showThumbnails = group->readEntry("Show Thumbnails", s->showThumbnails);
686 d->actionShowThumbnails->setChecked(s->showThumbnails);
687 d->actionStickyMode->setChecked(group->readEntry("Sticky Mode State", d->actionStickyMode->isChecked()));
688
689 // let the backends load their settings
690
691 for (int i = 0 ; i < d->loadedBackends.size() ; ++i)
692 {
693 d->loadedBackends.at(i)->readSettingsFromGroup(group);
694 }
695
696 // current map state
697
698 const GeoCoordinates centerDefault = GeoCoordinates(52.0, 6.0);
699 const QString centerGeoUrl = group->readEntry("Center", centerDefault.geoUrl());
700 bool centerGeoUrlValid = false;
701 const GeoCoordinates centerCoordinate = GeoCoordinates::fromGeoUrl(centerGeoUrl, ¢erGeoUrlValid);
702 d->cacheCenterCoordinate = centerGeoUrlValid ? centerCoordinate : centerDefault;
703 d->cacheZoom = group->readEntry("Zoom", d->cacheZoom);
704 s->currentMouseMode = GeoMouseModes(group->readEntry("Mouse Mode", int(s->currentMouseMode)));
705
706 // propagate the loaded values to the map, if appropriate
707
708 applyCacheToBackend();
709 slotUpdateActionsEnabled();
710 }
711
rebuildConfigurationMenu()712 void MapWidget::rebuildConfigurationMenu()
713 {
714 d->configurationMenu->clear();
715 const QList<QAction*> backendSelectionActions = d->actionGroupBackendSelection->actions();
716
717 for (int i = 0 ; i < backendSelectionActions.count() ; ++i)
718 {
719 QAction* const backendAction = backendSelectionActions.at(i);
720
721 if (backendAction->data().toString() == d->currentBackendName)
722 {
723 backendAction->setChecked(true);
724 }
725
726 d->configurationMenu->addAction(backendAction);
727 }
728
729 if (currentBackendReady())
730 {
731 d->currentBackend->addActionsToConfigurationMenu(d->configurationMenu);
732 }
733
734 if (s->showThumbnails)
735 {
736 d->configurationMenu->addSeparator();
737
738 if (d->sortMenu)
739 {
740 d->configurationMenu->addMenu(d->sortMenu);
741 }
742
743 d->configurationMenu->addAction(d->actionPreviewSingleItems);
744 d->configurationMenu->addAction(d->actionPreviewGroupedItems);
745 d->configurationMenu->addAction(d->actionShowNumbersOnItems);
746 }
747
748 slotUpdateActionsEnabled();
749 }
750
getControlAction(const QString & actionName)751 QAction* MapWidget::getControlAction(const QString& actionName)
752 {
753 if (actionName == QLatin1String("zoomin"))
754 {
755 return d->actionZoomIn;
756 }
757 else if (actionName == QLatin1String("zoomout"))
758 {
759 return d->actionZoomOut;
760 }
761 else if (actionName == QLatin1String("mousemode-regionselectionmode"))
762 {
763 return d->actionSetRegionSelectionMode;
764 }
765 else if (actionName == QLatin1String("mousemode-removecurrentregionselection"))
766 {
767 return d->actionRemoveCurrentRegionSelection;
768 }
769 else if (actionName == QLatin1String("mousemode-regionselectionfromiconmode"))
770 {
771 return d->actionSetRegionSelectionFromIconMode;
772 }
773 else if (actionName == QLatin1String("mousemode-removefilter"))
774 {
775 return d->actionRemoveFilter;
776 }
777
778 return nullptr;
779 }
780
781 /**
782 * @brief Returns the control widget.
783 */
getControlWidget()784 QWidget* MapWidget::getControlWidget()
785 {
786 if (!d->controlWidget)
787 {
788 d->controlWidget = new QWidget(this);
789 QHBoxLayout* const controlWidgetHBoxLayout = new QHBoxLayout(d->controlWidget);
790 controlWidgetHBoxLayout->setContentsMargins(QMargins());
791
792 QPushButton* const configurationButton = new QPushButton(d->controlWidget);
793 configurationButton->setIcon(QIcon::fromTheme(QLatin1String("globe")));
794 controlWidgetHBoxLayout->addWidget(configurationButton);
795 configurationButton->setToolTip(i18n("Map settings"));
796 configurationButton->setMenu(d->configurationMenu);
797
798 QToolButton* const zoomInButton = new QToolButton(d->controlWidget);
799 controlWidgetHBoxLayout->addWidget(zoomInButton);
800 zoomInButton->setDefaultAction(d->actionZoomIn);
801
802 QToolButton* const zoomOutButton = new QToolButton(d->controlWidget);
803 controlWidgetHBoxLayout->addWidget(zoomOutButton);
804 zoomOutButton->setDefaultAction(d->actionZoomOut);
805
806 QToolButton* const showThumbnailsButton = new QToolButton(d->controlWidget);
807 controlWidgetHBoxLayout->addWidget(showThumbnailsButton);
808 showThumbnailsButton->setDefaultAction(d->actionShowThumbnails);
809
810 QFrame* const vline1 = new QFrame(d->controlWidget);
811 vline1->setLineWidth(1);
812 vline1->setMidLineWidth(0);
813 vline1->setFrameShape(QFrame::VLine);
814 vline1->setFrameShadow(QFrame::Sunken);
815 vline1->setMinimumSize(2, 0);
816 vline1->updateGeometry();
817 controlWidgetHBoxLayout->addWidget(vline1);
818
819 QToolButton* const increaseThumbnailSizeButton = new QToolButton(d->controlWidget);
820 controlWidgetHBoxLayout->addWidget(increaseThumbnailSizeButton);
821 increaseThumbnailSizeButton->setDefaultAction(d->actionIncreaseThumbnailSize);
822
823 QToolButton* const decreaseThumbnailSizeButton = new QToolButton(d->controlWidget);
824 controlWidgetHBoxLayout->addWidget(decreaseThumbnailSizeButton);
825 decreaseThumbnailSizeButton->setDefaultAction(d->actionDecreaseThumbnailSize);
826
827 /* --- --- --- */
828
829 d->mouseModesHolder = new QWidget(d->controlWidget);
830 QHBoxLayout* const mouseModesHolderHBoxLayout = new QHBoxLayout(d->mouseModesHolder);
831 mouseModesHolderHBoxLayout->setContentsMargins(QMargins());
832 controlWidgetHBoxLayout->addWidget(d->mouseModesHolder);
833
834 QFrame* const vline2 = new QFrame(d->mouseModesHolder);
835 vline2->setLineWidth(1);
836 vline2->setMidLineWidth(0);
837 vline2->setFrameShape(QFrame::VLine);
838 vline2->setFrameShadow(QFrame::Sunken);
839 vline2->setMinimumSize(2, 0);
840 vline2->updateGeometry();
841 mouseModesHolderHBoxLayout->addWidget(vline2);
842
843 d->setPanModeButton = new QToolButton(d->mouseModesHolder);
844 mouseModesHolderHBoxLayout->addWidget(d->setPanModeButton);
845 d->setPanModeButton->setDefaultAction(d->actionSetPanMode);
846
847 d->setSelectionModeButton = new QToolButton(d->mouseModesHolder);
848 mouseModesHolderHBoxLayout->addWidget(d->setSelectionModeButton);
849 d->setSelectionModeButton->setDefaultAction(d->actionSetRegionSelectionMode);
850
851 d->setRegionSelectionFromIconModeButton = new QToolButton(d->mouseModesHolder);
852 mouseModesHolderHBoxLayout->addWidget(d->setRegionSelectionFromIconModeButton);
853 d->setRegionSelectionFromIconModeButton->setDefaultAction(d->actionSetRegionSelectionFromIconMode);
854
855 d->removeCurrentSelectionButton = new QToolButton(d->mouseModesHolder);
856 mouseModesHolderHBoxLayout->addWidget(d->removeCurrentSelectionButton);
857 d->removeCurrentSelectionButton->setDefaultAction(d->actionRemoveCurrentRegionSelection);
858
859 d->setZoomModeButton = new QToolButton(d->mouseModesHolder);
860 mouseModesHolderHBoxLayout->addWidget(d->setZoomModeButton);
861 d->setZoomModeButton->setDefaultAction(d->actionSetZoomIntoGroupMode);
862
863 d->setFilterModeButton = new QToolButton(d->mouseModesHolder);
864 mouseModesHolderHBoxLayout->addWidget(d->setFilterModeButton);
865 d->setFilterModeButton->setDefaultAction(d->actionSetFilterMode);
866
867 d->removeFilterModeButton = new QToolButton(d->mouseModesHolder);
868 mouseModesHolderHBoxLayout->addWidget(d->removeFilterModeButton);
869 d->removeFilterModeButton->setDefaultAction(d->actionRemoveFilter);
870
871 d->setSelectThumbnailMode = new QToolButton(d->mouseModesHolder);
872 mouseModesHolderHBoxLayout->addWidget(d->setSelectThumbnailMode);
873 d->setSelectThumbnailMode->setDefaultAction(d->actionSetSelectThumbnailMode);
874
875 d->buttonStickyMode = new QToolButton(d->controlWidget);
876 controlWidgetHBoxLayout->addWidget(d->buttonStickyMode);
877 d->buttonStickyMode->setDefaultAction(d->actionStickyMode);
878
879 d->hBoxForAdditionalControlWidgetItems = new QWidget(d->controlWidget);
880 QHBoxLayout *hBoxForAdditionalControlWidgetItemsHBoxLayout = new QHBoxLayout(d->hBoxForAdditionalControlWidgetItems);
881 hBoxForAdditionalControlWidgetItemsHBoxLayout->setContentsMargins(QMargins());
882 controlWidgetHBoxLayout->addWidget(d->hBoxForAdditionalControlWidgetItems);
883
884 setVisibleMouseModes(s->visibleMouseModes);
885 setVisibleExtraActions(d->visibleExtraActions);
886
887 // add stretch after the controls:
888
889 QHBoxLayout* const hBoxLayout = reinterpret_cast<QHBoxLayout*>(d->controlWidget->layout());
890
891 if (hBoxLayout)
892 {
893 hBoxLayout->addStretch();
894 }
895 }
896
897 // make sure the menu exists, even if no backend has been set:
898
899 rebuildConfigurationMenu();
900
901 return d->controlWidget;
902 }
903
slotZoomIn()904 void MapWidget::slotZoomIn()
905 {
906 if (!currentBackendReady())
907 {
908 return;
909 }
910
911 d->currentBackend->zoomIn();
912 }
913
slotZoomOut()914 void MapWidget::slotZoomOut()
915 {
916 if (!currentBackendReady())
917 {
918 return;
919 }
920
921 d->currentBackend->zoomOut();
922 }
923
slotUpdateActionsEnabled()924 void MapWidget::slotUpdateActionsEnabled()
925 {
926 if (!s->activeState)
927 {
928 // this widget is not active, no need to update the action availability
929 return;
930 }
931
932 d->actionDecreaseThumbnailSize->setEnabled((s->showThumbnails)&&(s->thumbnailSize>GeoIfaceMinThumbnailSize));
933
934 /// @todo Define an upper limit for the thumbnail size!
935
936 d->actionIncreaseThumbnailSize->setEnabled(s->showThumbnails);
937
938 d->actionSetRegionSelectionMode->setEnabled(s->availableMouseModes.testFlag(MouseModeRegionSelection));
939
940 d->actionSetPanMode->setEnabled(s->availableMouseModes.testFlag(MouseModePan));
941 d->actionSetZoomIntoGroupMode->setEnabled(s->availableMouseModes.testFlag(MouseModeZoomIntoGroup));
942 d->actionSetRegionSelectionFromIconMode->setEnabled(s->availableMouseModes.testFlag(MouseModeRegionSelectionFromIcon));
943 d->actionSetFilterMode->setEnabled(s->availableMouseModes.testFlag(MouseModeFilter));
944 d->actionSetSelectThumbnailMode->setEnabled(s->availableMouseModes.testFlag(MouseModeSelectThumbnail));
945
946 // the 'Remove X' actions are only available if the corresponding X is actually there:
947
948 bool clearRegionSelectionAvailable = s->availableMouseModes.testFlag(MouseModeRegionSelection);
949
950 if (clearRegionSelectionAvailable && s->markerModel)
951 {
952 clearRegionSelectionAvailable = s->markerModel->getGlobalGroupState() & RegionSelectedMask;
953 }
954
955 d->actionRemoveCurrentRegionSelection->setEnabled(clearRegionSelectionAvailable);
956 bool clearFilterAvailable = s->availableMouseModes.testFlag(MouseModeRegionSelectionFromIcon);
957
958 if (clearFilterAvailable && s->markerModel)
959 {
960 clearFilterAvailable = s->markerModel->getGlobalGroupState() & FilteredPositiveMask;
961 }
962
963 d->actionRemoveFilter->setEnabled(clearFilterAvailable);
964
965 d->actionStickyMode->setEnabled(d->availableExtraActions.testFlag(ExtraActionSticky));
966
967 /// @todo Only set the icons if they have to be changed!
968
969 d->actionStickyMode->setIcon(QIcon::fromTheme(QLatin1String(d->actionStickyMode->isChecked()
970 ? "document-encrypted"
971 : "document-decrypt")));
972 d->actionShowThumbnails->setIcon(d->actionShowThumbnails->isChecked()
973 ? QIcon::fromTheme(QLatin1String("folder-pictures"))
974 : GeoIfaceGlobalObject::instance()->getMarkerPixmap(QLatin1String("marker-icon-16x16")));
975
976 // make sure the action for the current mouse mode is checked
977
978 const QList<QAction*> mouseModeActions = d->mouseModeActionGroup->actions();
979
980 foreach (QAction* const action, mouseModeActions)
981 {
982 if (action->data().value<GeoMouseModes>() == s->currentMouseMode)
983 {
984 action->setChecked(true);
985 break;
986 }
987 }
988 }
989
slotChangeBackend(QAction * action)990 void MapWidget::slotChangeBackend(QAction* action)
991 {
992 GEOIFACE_ASSERT(action != nullptr);
993
994 if (!action)
995 {
996 return;
997 }
998
999 const QString newBackendName = action->data().toString();
1000 setBackend(newBackendName);
1001 }
1002
updateMarkers()1003 void MapWidget::updateMarkers()
1004 {
1005 if (!currentBackendReady())
1006 {
1007 return;
1008 }
1009
1010 // tell the backend to update the markers
1011
1012 d->currentBackend->updateMarkers();
1013 }
1014
updateClusters()1015 void MapWidget::updateClusters()
1016 {
1017 /// @todo Find a better way to tell the TileGrouper about the backend
1018
1019 s->tileGrouper->setCurrentBackend(d->currentBackend);
1020 s->tileGrouper->updateClusters();
1021 }
1022
slotClustersNeedUpdating()1023 void MapWidget::slotClustersNeedUpdating()
1024 {
1025 if (currentBackendReady())
1026 {
1027 d->currentBackend->slotClustersNeedUpdating();
1028 }
1029 }
1030
1031 /**
1032 * @brief Return color and style information for rendering the cluster
1033 * @param clusterIndex Index of the cluster
1034 * @param fillColor Color used to fill the circle
1035 * @param strokeColor Color used for the stroke around the circle
1036 * @param strokeStyle Style used to draw the stroke around the circle
1037 * @param labelText Text for the label
1038 * @param labelColor Color for the label text
1039 * @param overrideSelection Get the colors for a different selection state
1040 * @param overrideCount Get the colors for a different amount of markers
1041 */
getColorInfos(const int clusterIndex,QColor * fillColor,QColor * strokeColor,Qt::PenStyle * strokeStyle,QString * labelText,QColor * labelColor,const GeoGroupState * const overrideSelection,const int * const overrideCount) const1042 void MapWidget::getColorInfos(const int clusterIndex,
1043 QColor* fillColor,
1044 QColor* strokeColor,
1045 Qt::PenStyle* strokeStyle,
1046 QString* labelText,
1047 QColor* labelColor,
1048 const GeoGroupState* const overrideSelection,
1049 const int* const overrideCount) const
1050 {
1051 /// @todo Call the new getColorInfos function!
1052
1053 const GeoIfaceCluster& cluster = s->clusterList.at(clusterIndex);
1054
1055 /// @todo Check that this number is already valid!
1056
1057 const int nMarkers = overrideCount ? *overrideCount : cluster.markerCount;
1058
1059 getColorInfos(overrideSelection ? *overrideSelection : cluster.groupState,
1060 nMarkers,
1061 fillColor, strokeColor, strokeStyle, labelText, labelColor);
1062 }
1063
getColorInfos(const GeoGroupState groupState,const int nMarkers,QColor * fillColor,QColor * strokeColor,Qt::PenStyle * strokeStyle,QString * labelText,QColor * labelColor) const1064 void MapWidget::getColorInfos(const GeoGroupState groupState,
1065 const int nMarkers,
1066 QColor* fillColor, QColor* strokeColor,
1067 Qt::PenStyle* strokeStyle, QString* labelText,
1068 QColor* labelColor) const
1069 {
1070 if (nMarkers < 1000)
1071 {
1072 *labelText = QString::number(nMarkers);
1073 }
1074 else if ((nMarkers >= 1000) && (nMarkers <= 1950)) // cppcheck-suppress knownConditionTrueFalse
1075 {
1076 *labelText = QString::fromLatin1("%L1k").arg(qreal(nMarkers)/1000.0, 0, 'f', 1);
1077 }
1078 else if ((nMarkers >= 1951) && (nMarkers < 19500)) // cppcheck-suppress knownConditionTrueFalse
1079 {
1080 *labelText = QString::fromLatin1("%L1k").arg(qreal(nMarkers)/1000.0, 0, 'f', 0);
1081 }
1082 else
1083 {
1084 // convert to "1E5" notation for numbers >=20k:
1085
1086 qreal exponent = floor(log((qreal)nMarkers)/log((qreal)10));
1087 qreal nMarkersFirstDigit = round(qreal(nMarkers)/pow(10,exponent));
1088
1089 if (nMarkersFirstDigit >= 10)
1090 {
1091 nMarkersFirstDigit=round(nMarkersFirstDigit/10.0);
1092 exponent++;
1093 }
1094
1095 *labelText = QString::fromLatin1("%1E%2").arg(int(nMarkersFirstDigit)).arg(int(exponent));
1096 }
1097
1098 *labelColor = QColor(Qt::black);
1099 *strokeStyle = Qt::NoPen;
1100
1101 /// @todo On my system, digikam uses QColor(67, 172, 232) as the selection color. Or should we just use blue?
1102
1103 switch (groupState & SelectedMask)
1104 {
1105 case SelectedNone:
1106 *strokeStyle = Qt::SolidLine;
1107 *strokeColor = QColor(Qt::black);
1108 break;
1109
1110 case SelectedSome:
1111 *strokeStyle = Qt::DotLine;
1112 *strokeColor = QColor(Qt::blue);//67, 172, 232);
1113 break;
1114
1115 case SelectedAll:
1116 *strokeStyle = Qt::SolidLine;
1117 *strokeColor = QColor(Qt::blue);//67, 172, 232);
1118 break;
1119 }
1120
1121 /**
1122 * @todo These are the fill colors for the circles, for cases in which only some or all of the images
1123 * are positively filtered. Filtering is implemented in GeoIface, but the code here has not been adapted yet.
1124 */
1125 QColor fillAll, fillSome, fillNone;
1126
1127 if (nMarkers >= 100)
1128 {
1129 fillAll = QColor(255, 0, 0);
1130 fillSome = QColor(255, 188, 125);
1131 fillNone = QColor(255, 185, 185);
1132 }
1133 else if (nMarkers >= 50)
1134 {
1135 fillAll = QColor(255, 127, 0);
1136 fillSome = QColor(255, 190, 125);
1137 fillNone = QColor(255, 220, 185);
1138 }
1139 else if (nMarkers >= 10)
1140 {
1141 fillAll = QColor(255, 255, 0);
1142 fillSome = QColor(255, 255, 105);
1143 fillNone = QColor(255, 255, 185);
1144 }
1145 else if (nMarkers >= 2)
1146 {
1147 fillAll = QColor(0, 255, 0);
1148 fillSome = QColor(125, 255, 125);
1149 fillNone = QColor(185, 255, 255);
1150 }
1151 else
1152 {
1153 fillAll = QColor(0, 255, 255);
1154 fillSome = QColor(125, 255, 255);
1155 fillNone = QColor(185, 255, 255);
1156 }
1157
1158 *fillColor = fillAll;
1159 /*
1160 switch (groupState)
1161 {
1162 case PartialAll:
1163 *fillColor = fillAll;
1164 break;
1165
1166 case PartialSome:
1167 *fillColor = fillSome;
1168 break;
1169
1170 case PartialNone:
1171
1172 if (haveAnySolo)
1173 {
1174 *fillColor = fillNone;
1175 }
1176 else
1177 {
1178 *fillColor = fillAll;
1179 }
1180
1181 break;
1182 }
1183 */
1184 }
1185
convertZoomToBackendZoom(const QString & someZoom,const QString & targetBackend) const1186 QString MapWidget::convertZoomToBackendZoom(const QString& someZoom,
1187 const QString& targetBackend) const
1188 {
1189 const QStringList zoomParts = someZoom.split(QLatin1Char( ':' ));
1190 GEOIFACE_ASSERT(zoomParts.count() == 2);
1191 const QString sourceBackend = zoomParts.first();
1192
1193 if (sourceBackend == targetBackend)
1194 {
1195 return someZoom;
1196 }
1197
1198 const int sourceZoom = zoomParts.last().toInt();
1199 int targetZoom = -1;
1200
1201 // all of these values were found experimentally!
1202
1203 if (targetBackend == QLatin1String("marble" ))
1204 {
1205 if (sourceZoom == 0) { targetZoom = 900; }
1206 else if (sourceZoom == 1) { targetZoom = 970; }
1207 else if (sourceZoom == 2) { targetZoom = 1108; }
1208 else if (sourceZoom == 3) { targetZoom = 1250; }
1209 else if (sourceZoom == 4) { targetZoom = 1384; }
1210 else if (sourceZoom == 5) { targetZoom = 1520; }
1211 else if (sourceZoom == 6) { targetZoom = 1665; }
1212 else if (sourceZoom == 7) { targetZoom = 1800; }
1213 else if (sourceZoom == 8) { targetZoom = 1940; }
1214 else if (sourceZoom == 9) { targetZoom = 2070; }
1215 else if (sourceZoom ==10) { targetZoom = 2220; }
1216 else if (sourceZoom ==11) { targetZoom = 2357; }
1217 else if (sourceZoom ==12) { targetZoom = 2510; }
1218 else if (sourceZoom ==13) { targetZoom = 2635; }
1219 else if (sourceZoom ==14) { targetZoom = 2775; }
1220 else if (sourceZoom ==15) { targetZoom = 2900; }
1221 else if (sourceZoom ==16) { targetZoom = 3051; }
1222 else if (sourceZoom ==17) { targetZoom = 3180; }
1223 else if (sourceZoom ==18) { targetZoom = 3295; }
1224 else if (sourceZoom ==19) { targetZoom = 3450; }
1225 else { targetZoom = 3500; } /// @todo Find values for level 20 and up
1226 }
1227
1228 if (targetBackend == QLatin1String("googlemaps" ))
1229 {
1230 if (sourceZoom <= 900) { targetZoom = 0; }
1231 else if (sourceZoom <= 970) { targetZoom = 1; }
1232 else if (sourceZoom <=1108) { targetZoom = 2; }
1233 else if (sourceZoom <=1250) { targetZoom = 3; }
1234 else if (sourceZoom <=1384) { targetZoom = 4; }
1235 else if (sourceZoom <=1520) { targetZoom = 5; }
1236 else if (sourceZoom <=1665) { targetZoom = 6; }
1237 else if (sourceZoom <=1800) { targetZoom = 7; }
1238 else if (sourceZoom <=1940) { targetZoom = 8; }
1239 else if (sourceZoom <=2070) { targetZoom = 9; }
1240 else if (sourceZoom <=2220) { targetZoom = 10; }
1241 else if (sourceZoom <=2357) { targetZoom = 11; }
1242 else if (sourceZoom <=2510) { targetZoom = 12; }
1243 else if (sourceZoom <=2635) { targetZoom = 13; }
1244 else if (sourceZoom <=2775) { targetZoom = 14; }
1245 else if (sourceZoom <=2900) { targetZoom = 15; }
1246 else if (sourceZoom <=3051) { targetZoom = 16; }
1247 else if (sourceZoom <=3180) { targetZoom = 17; }
1248 else if (sourceZoom <=3295) { targetZoom = 18; }
1249 else if (sourceZoom <=3450) { targetZoom = 19; }
1250 else { targetZoom = 20; } /// @todo Find values for level 20 and up
1251 }
1252
1253 GEOIFACE_ASSERT(targetZoom >= 0);
1254
1255 return QString::fromLatin1("%1:%2").arg(targetBackend).arg(targetZoom);
1256 }
1257
slotBackendZoomChanged(const QString & newZoom)1258 void MapWidget::slotBackendZoomChanged(const QString& newZoom)
1259 {
1260 d->cacheZoom = newZoom;
1261 }
1262
setZoom(const QString & newZoom)1263 void MapWidget::setZoom(const QString& newZoom)
1264 {
1265 d->cacheZoom = newZoom;
1266
1267 if (currentBackendReady())
1268 {
1269 d->currentBackend->setZoom(d->cacheZoom);
1270 }
1271 }
1272
getZoom()1273 QString MapWidget::getZoom()
1274 {
1275 if (currentBackendReady())
1276 {
1277 d->cacheZoom = d->currentBackend->getZoom();
1278 }
1279
1280 return d->cacheZoom;
1281 }
1282
getRegionSelection()1283 GeoCoordinates::Pair MapWidget::getRegionSelection()
1284 {
1285 return s->selectionRectangle;
1286 }
1287
slotClustersMoved(const QIntList & clusterIndices,const QPair<int,QModelIndex> & snapTarget)1288 void MapWidget::slotClustersMoved(const QIntList& clusterIndices,
1289 const QPair<int, QModelIndex>& snapTarget)
1290 {
1291 qCDebug(DIGIKAM_GEOIFACE_LOG) << clusterIndices;
1292
1293 /// @todo We actually expect only one clusterindex
1294
1295 int clusterIndex = clusterIndices.first();
1296 GeoCoordinates targetCoordinates = s->clusterList.at(clusterIndex).coordinates;
1297 TileIndex::List movedTileIndices;
1298
1299 if (s->clusterList.at(clusterIndex).groupState == SelectedNone)
1300 {
1301 // a not-selected marker was moved. update all of its items:
1302
1303 const GeoIfaceCluster& cluster = s->clusterList.at(clusterIndex);
1304
1305 for (int i = 0 ; i < cluster.tileIndicesList.count() ; ++i)
1306 {
1307 const TileIndex tileIndex = cluster.tileIndicesList.at(i);
1308 movedTileIndices << tileIndex;
1309 }
1310 }
1311 else
1312 {
1313 // selected items were moved. The model helper should know which tiles are selected,
1314 // therefore we give him an empty list
1315 }
1316
1317 s->markerModel->onIndicesMoved(movedTileIndices, targetCoordinates, snapTarget.second);
1318
1319 /**
1320 * @todo Clusters are marked as dirty by slotClustersNeedUpdating
1321 * which is called while we update the model
1322 */
1323 }
1324
addUngroupedModel(GeoModelHelper * const modelHelper)1325 void MapWidget::addUngroupedModel(GeoModelHelper* const modelHelper)
1326 {
1327 s->ungroupedModels << modelHelper;
1328
1329 /// @todo monitor all model signals!
1330
1331 connect(modelHelper->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
1332 this, SLOT(slotUngroupedModelChanged()));
1333
1334 connect(modelHelper->model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
1335 this, SLOT(slotUngroupedModelChanged()));
1336
1337 connect(modelHelper->model(), SIGNAL(modelReset()),
1338 this, SLOT(slotUngroupedModelChanged()));
1339
1340 connect(modelHelper, SIGNAL(signalVisibilityChanged()),
1341 this, SLOT(slotUngroupedModelChanged()));
1342
1343 if (modelHelper->selectionModel())
1344 {
1345 connect(modelHelper->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
1346 this, SLOT(slotUngroupedModelChanged()));
1347 }
1348
1349 emit signalUngroupedModelChanged(s->ungroupedModels.count() - 1);
1350 }
1351
removeUngroupedModel(GeoModelHelper * const modelHelper)1352 void MapWidget::removeUngroupedModel(GeoModelHelper* const modelHelper)
1353 {
1354 if (!modelHelper)
1355 {
1356 return;
1357 }
1358
1359 const int modelIndex = s->ungroupedModels.indexOf(modelHelper);
1360
1361 if (modelIndex < 0)
1362 {
1363 return;
1364 }
1365
1366 /// @todo monitor all model signals!
1367
1368 disconnect(modelHelper->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
1369 this, SLOT(slotUngroupedModelChanged()));
1370
1371 disconnect(modelHelper->model(), SIGNAL(rowsInserted(QModelIndex,int,int)),
1372 this, SLOT(slotUngroupedModelChanged()));
1373
1374 disconnect(modelHelper->model(), SIGNAL(modelReset()),
1375 this, SLOT(slotUngroupedModelChanged()));
1376
1377 disconnect(modelHelper, SIGNAL(signalVisibilityChanged()),
1378 this, SLOT(slotUngroupedModelChanged()));
1379
1380 if (modelHelper->selectionModel())
1381 {
1382 disconnect(modelHelper->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
1383 this, SLOT(slotUngroupedModelChanged()));
1384 }
1385
1386 s->ungroupedModels.removeAt(modelIndex);
1387
1388 // the indices changed, therefore send out notifications
1389 // sending out a signal with i=s->ungroupedModel.count()
1390 // will cause the backends to see that the last model is missing
1391
1392 for (int i = modelIndex ; i <= s->ungroupedModels.count() ; ++i)
1393 {
1394 emit signalUngroupedModelChanged(i);
1395 }
1396 }
1397
setGroupedModel(AbstractMarkerTiler * const markerModel)1398 void MapWidget::setGroupedModel(AbstractMarkerTiler* const markerModel)
1399 {
1400 s->markerModel = markerModel;
1401
1402 if (s->markerModel)
1403 {
1404 s->markerModel->setActive(s->activeState);
1405
1406 /// @todo this needs some buffering for the google maps backend
1407
1408 connect(s->markerModel, SIGNAL(signalTilesOrSelectionChanged()),
1409 this, SLOT(slotRequestLazyReclustering()));
1410
1411 if (d->currentBackend)
1412 {
1413 connect(s->markerModel, SIGNAL(signalThumbnailAvailableForIndex(QVariant,QPixmap)),
1414 d->currentBackend, SLOT(slotThumbnailAvailableForIndex(QVariant,QPixmap)));
1415 }
1416 }
1417
1418 slotRequestLazyReclustering();
1419 }
1420
setShowThumbnails(const bool state)1421 void MapWidget::setShowThumbnails(const bool state)
1422 {
1423 s->showThumbnails = state;
1424
1425 rebuildConfigurationMenu();
1426 slotUpdateActionsEnabled();
1427 slotRequestLazyReclustering();
1428 }
1429
slotShowThumbnailsChanged()1430 void MapWidget::slotShowThumbnailsChanged()
1431 {
1432 setShowThumbnails(d->actionShowThumbnails->isChecked());
1433 }
1434
1435 /**
1436 * @brief Request reclustering, repeated calls should generate only one actual update of the clusters
1437 */
slotRequestLazyReclustering()1438 void MapWidget::slotRequestLazyReclustering()
1439 {
1440 if (d->lazyReclusteringRequested)
1441 {
1442 return;
1443 }
1444
1445 s->tileGrouper->setClustersDirty();
1446
1447 if (s->activeState)
1448 {
1449 d->lazyReclusteringRequested = true;
1450 QTimer::singleShot(0, this, SLOT(slotLazyReclusteringRequestCallBack()));
1451 }
1452 }
1453
1454 /**
1455 * @brief Helper function to buffer reclustering
1456 */
slotLazyReclusteringRequestCallBack()1457 void MapWidget::slotLazyReclusteringRequestCallBack()
1458 {
1459 if (!d->lazyReclusteringRequested)
1460 {
1461 return;
1462 }
1463
1464 d->lazyReclusteringRequested = false;
1465 slotClustersNeedUpdating();
1466 }
1467
1468 /**
1469 * @todo Clicking on several clusters at once is not actually possible
1470 */
slotClustersClicked(const QIntList & clusterIndices)1471 void MapWidget::slotClustersClicked(const QIntList& clusterIndices)
1472 {
1473 qCDebug(DIGIKAM_GEOIFACE_LOG) << clusterIndices;
1474
1475 if ((s->currentMouseMode == MouseModeZoomIntoGroup) ||
1476 (s->currentMouseMode == MouseModeRegionSelectionFromIcon) )
1477 {
1478 int maxTileLevel = 0;
1479
1480 Marble::GeoDataLineString tileString;
1481
1482 for (int i = 0 ; i < clusterIndices.count() ; ++i)
1483 {
1484 const int clusterIndex = clusterIndices.at(i);
1485 const GeoIfaceCluster currentCluster = s->clusterList.at(clusterIndex);
1486
1487 for (int j = 0 ; j < currentCluster.tileIndicesList.count() ; ++j)
1488 {
1489 const TileIndex& currentTileIndex = currentCluster.tileIndicesList.at(j);
1490
1491 for (int corner = 1 ; corner <= 4 ; ++corner)
1492 {
1493 GeoCoordinates currentTileCoordinate;
1494
1495 if (corner == 1)
1496 {
1497 currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerNW);
1498 }
1499 else if (corner == 2)
1500 {
1501 currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerSW);
1502 }
1503 else if (corner == 3)
1504 {
1505 currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerNE);
1506 }
1507 else if (corner == 4)
1508 {
1509 currentTileCoordinate = currentTileIndex.toCoordinates(TileIndex::CornerSE);
1510 }
1511
1512 const Marble::GeoDataCoordinates tileCoordinate(currentTileCoordinate.lon(),
1513 currentTileCoordinate.lat(),
1514 0,
1515 Marble::GeoDataCoordinates::Degree);
1516
1517 if (maxTileLevel < currentTileIndex.level())
1518 {
1519 maxTileLevel = currentTileIndex.level();
1520 }
1521
1522 tileString.append(tileCoordinate);
1523 }
1524 }
1525 }
1526
1527 Marble::GeoDataLatLonBox latLonBox = Marble::GeoDataLatLonBox::fromLineString(tileString);
1528
1529 /// @todo Review this section
1530 /*
1531 if (maxTileLevel != 0)
1532 {
1533 //increase the selection boundaries with 0.1 degrees because some thumbnails aren't caught by selection
1534 latLonBox.setWest((latLonBox.west(Marble::GeoDataCoordinates::Degree)-(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree);
1535 latLonBox.setNorth((latLonBox.north(Marble::GeoDataCoordinates::Degree)+(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree);
1536 latLonBox.setEast((latLonBox.east(Marble::GeoDataCoordinates::Degree)+(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree);
1537 latLonBox.setSouth((latLonBox.south(Marble::GeoDataCoordinates::Degree)-(0.1/maxTileLevel)), Marble::GeoDataCoordinates::Degree);
1538 }
1539 else
1540 {
1541 */
1542 latLonBox.setWest((latLonBox.west(Marble::GeoDataCoordinates::Degree)-0.0001), Marble::GeoDataCoordinates::Degree);
1543 latLonBox.setNorth((latLonBox.north(Marble::GeoDataCoordinates::Degree)+0.0001), Marble::GeoDataCoordinates::Degree);
1544 latLonBox.setEast((latLonBox.east(Marble::GeoDataCoordinates::Degree)+0.0001), Marble::GeoDataCoordinates::Degree);
1545 latLonBox.setSouth((latLonBox.south(Marble::GeoDataCoordinates::Degree)-0.0001), Marble::GeoDataCoordinates::Degree);
1546 /*
1547 }
1548 */
1549 if (s->currentMouseMode == MouseModeZoomIntoGroup)
1550 {
1551 /// @todo Very small latLonBoxes can crash Marble
1552
1553 d->currentBackend->centerOn(latLonBox);
1554 }
1555 else
1556 {
1557 const GeoCoordinates::Pair newSelection(
1558 GeoCoordinates(latLonBox.north(Marble::GeoDataCoordinates::Degree),
1559 latLonBox.west(Marble::GeoDataCoordinates::Degree)),
1560 GeoCoordinates(latLonBox.south(Marble::GeoDataCoordinates::Degree),
1561 latLonBox.east(Marble::GeoDataCoordinates::Degree))
1562 );
1563
1564 s->selectionRectangle = newSelection;
1565 d->currentBackend->regionSelectionChanged();
1566 emit signalRegionSelectionChanged();
1567 }
1568 }
1569 else if ((s->currentMouseMode == MouseModeFilter && s->selectionRectangle.first.hasCoordinates()) ||
1570 (s->currentMouseMode == MouseModeSelectThumbnail) )
1571 {
1572 // update the selection and filtering state of the clusters
1573
1574 for (int i = 0 ; i < clusterIndices.count() ; ++i)
1575 {
1576 const int clusterIndex = clusterIndices.at(i);
1577 const GeoIfaceCluster currentCluster = s->clusterList.at(clusterIndex);
1578 const TileIndex::List tileIndices = currentCluster.tileIndicesList;
1579
1580 /// @todo Isn't this cached in the cluster?
1581
1582 const QVariant representativeIndex = getClusterRepresentativeMarker(clusterIndex, s->sortKey);
1583
1584 AbstractMarkerTiler::ClickInfo clickInfo;
1585 clickInfo.tileIndicesList = tileIndices;
1586 clickInfo.representativeIndex = representativeIndex;
1587 clickInfo.groupSelectionState = currentCluster.groupState;
1588 clickInfo.currentMouseMode = s->currentMouseMode;
1589 s->markerModel->onIndicesClicked(clickInfo);
1590 }
1591 }
1592 }
1593
dragEnterEvent(QDragEnterEvent * event)1594 void MapWidget::dragEnterEvent(QDragEnterEvent* event)
1595 {
1596 /// @todo ignore drops if no marker tiler or model can accept them
1597
1598 if (!d->dragDropHandler)
1599 {
1600 event->ignore();
1601 return;
1602 }
1603
1604 if (d->dragDropHandler->accepts(event) == Qt::IgnoreAction)
1605 {
1606 event->ignore();
1607 return;
1608 }
1609
1610 /// @todo need data about the dragged object: #markers, selected, icon, ...
1611
1612 event->accept();
1613 }
1614
dragMoveEvent(QDragMoveEvent * event)1615 void MapWidget::dragMoveEvent(QDragMoveEvent* event)
1616 {
1617 if (!d->dragDropHandler)
1618 {
1619 event->ignore();
1620 return;
1621 }
1622
1623 event->accept();
1624 }
1625
dropEvent(QDropEvent * event)1626 void MapWidget::dropEvent(QDropEvent* event)
1627 {
1628 // remove the drag marker:
1629 /*
1630 d->currentBackend->updateDragDropMarker(QPoint(), 0);
1631 */
1632 if (!d->dragDropHandler)
1633 {
1634 event->ignore();
1635 return;
1636 }
1637
1638 GeoCoordinates dropCoordinates;
1639
1640 if (!d->currentBackend->geoCoordinates(event->pos(), &dropCoordinates))
1641 {
1642 return;
1643 }
1644
1645 // the drag and drop handler handled the drop if it returned true here
1646
1647 if (d->dragDropHandler->dropEvent(event, dropCoordinates))
1648 {
1649 event->acceptProposedAction();
1650 }
1651 }
1652
dragLeaveEvent(QDragLeaveEvent * event)1653 void MapWidget::dragLeaveEvent(QDragLeaveEvent* event)
1654 {
1655 Q_UNUSED(event);
1656
1657 // remove the marker:
1658 /*
1659 d->currentBackend->updateDragDropMarker(QPoint(), 0);
1660 */
1661 }
1662
markClustersAsDirty()1663 void MapWidget::markClustersAsDirty()
1664 {
1665 s->tileGrouper->setClustersDirty();
1666 }
1667
setDragDropHandler(GeoDragDropHandler * const dragDropHandler)1668 void MapWidget::setDragDropHandler(GeoDragDropHandler* const dragDropHandler)
1669 {
1670 d->dragDropHandler = dragDropHandler;
1671 }
1672
getClusterRepresentativeMarker(const int clusterIndex,const int sortKey)1673 QVariant MapWidget::getClusterRepresentativeMarker(const int clusterIndex, const int sortKey)
1674 {
1675 if (!s->markerModel)
1676 {
1677 return QVariant();
1678 }
1679
1680 const GeoIfaceCluster cluster = s->clusterList.at(clusterIndex);
1681 QMap<int, QVariant>::const_iterator it = cluster.representativeMarkers.find(sortKey);
1682
1683 if (it != cluster.representativeMarkers.end())
1684 {
1685 return *it;
1686 }
1687
1688 QList<QVariant> repIndices;
1689
1690 for (int i = 0 ; i < cluster.tileIndicesList.count() ; ++i)
1691 {
1692 repIndices << s->markerModel->getTileRepresentativeMarker(cluster.tileIndicesList.at(i), sortKey);
1693 }
1694
1695 const QVariant clusterRepresentative = s->markerModel->bestRepresentativeIndexFromList(repIndices, sortKey);
1696 s->clusterList[clusterIndex].representativeMarkers[sortKey] = clusterRepresentative;
1697
1698 return clusterRepresentative;
1699 }
1700
slotItemDisplaySettingsChanged()1701 void MapWidget::slotItemDisplaySettingsChanged()
1702 {
1703 s->previewSingleItems = d->actionPreviewSingleItems->isChecked();
1704 s->previewGroupedItems = d->actionPreviewGroupedItems->isChecked();
1705 s->showNumbersOnItems = d->actionShowNumbersOnItems->isChecked();
1706
1707 /// @todo Update action availability?
1708
1709 /// @todo We just need to update the display, no need to recluster?
1710
1711 slotRequestLazyReclustering();
1712 }
1713
setSortOptionsMenu(QMenu * const sortMenu)1714 void MapWidget::setSortOptionsMenu(QMenu* const sortMenu)
1715 {
1716 d->sortMenu = sortMenu;
1717
1718 rebuildConfigurationMenu();
1719 }
1720
setSortKey(const int sortKey)1721 void MapWidget::setSortKey(const int sortKey)
1722 {
1723 s->sortKey = sortKey;
1724
1725 // this is probably faster than writing a function that changes all the clusters icons...
1726 /// @todo We just need to update the display, no need to recluster?
1727
1728 slotRequestLazyReclustering();
1729 }
1730
getDecoratedPixmapForCluster(const int clusterId,const GeoGroupState * const selectedStateOverride,const int * const countOverride,QPoint * const centerPoint)1731 QPixmap MapWidget::getDecoratedPixmapForCluster(const int clusterId,
1732 const GeoGroupState* const selectedStateOverride,
1733 const int* const countOverride,
1734 QPoint* const centerPoint)
1735 {
1736 GeoIfaceCluster& cluster = s->clusterList[clusterId];
1737 int markerCount = cluster.markerCount;
1738 GeoGroupState groupState = cluster.groupState;
1739
1740 if (selectedStateOverride)
1741 {
1742 groupState = *selectedStateOverride;
1743 markerCount = *countOverride;
1744 }
1745
1746 const GeoGroupState selectedState = groupState & SelectedMask;
1747
1748 // first determine all the color and style values
1749
1750 QColor fillColor;
1751 QColor strokeColor;
1752 Qt::PenStyle strokeStyle;
1753 QColor labelColor;
1754 QString labelText;
1755 getColorInfos(clusterId, &fillColor, &strokeColor,
1756 &strokeStyle, &labelText, &labelColor,
1757 &selectedState,
1758 &markerCount);
1759
1760 // determine whether we should use a pixmap or a placeholder
1761
1762 if (!s->showThumbnails)
1763 {
1764 /// @todo Handle positive filtering and region selection!
1765
1766 QString pixmapName = fillColor.name().mid(1);
1767
1768 if (selectedState == SelectedAll)
1769 {
1770 pixmapName += QLatin1String("-selected");
1771 }
1772
1773 if (selectedState == SelectedSome)
1774 {
1775 pixmapName += QLatin1String("-someselected");
1776 }
1777
1778 const QPixmap& markerPixmap = GeoIfaceGlobalObject::instance()->getMarkerPixmap(pixmapName);
1779
1780 // update the display information stored in the cluster:
1781
1782 cluster.pixmapType = GeoIfaceCluster::PixmapMarker;
1783 cluster.pixmapOffset = QPoint(markerPixmap.width()/2, markerPixmap.height()-1);
1784 cluster.pixmapSize = markerPixmap.size();
1785
1786 if (centerPoint)
1787 {
1788 *centerPoint = cluster.pixmapOffset;
1789 }
1790
1791 return markerPixmap;
1792 }
1793
1794 /// @todo This check is strange, there can be no clusters without a markerModel?
1795
1796 bool displayThumbnail = (s->markerModel != nullptr);
1797
1798 if (displayThumbnail)
1799 {
1800 if (markerCount==1)
1801 {
1802 displayThumbnail = s->previewSingleItems;
1803 }
1804 else
1805 {
1806 displayThumbnail = s->previewGroupedItems;
1807 }
1808 }
1809
1810 if (displayThumbnail)
1811 {
1812 const QVariant representativeMarker = getClusterRepresentativeMarker(clusterId, s->sortKey);
1813 const int undecoratedThumbnailSize = getUndecoratedThumbnailSize();
1814 QPixmap clusterPixmap = s->markerModel->pixmapFromRepresentativeIndex(representativeMarker,
1815 QSize(undecoratedThumbnailSize,
1816 undecoratedThumbnailSize));
1817
1818 if (!clusterPixmap.isNull())
1819 {
1820 QPixmap resultPixmap(clusterPixmap.size() + QSize(2, 2));
1821
1822 // we may draw with partially transparent pixmaps later, so make sure we have a defined
1823 // background color
1824
1825 resultPixmap.fill(QColor::fromRgb(0xff, 0xff, 0xff));
1826 QPainter painter(&resultPixmap);
1827 /*
1828 painter.setRenderHint(QPainter::Antialiasing);
1829 */
1830 const int borderWidth = (groupState&SelectedSome) ? 2 : 1;
1831 QPen borderPen;
1832 borderPen.setWidth(borderWidth);
1833 borderPen.setJoinStyle(Qt::MiterJoin);
1834
1835 GeoGroupState globalState = s->markerModel->getGlobalGroupState();
1836
1837 /// @todo What about partially in the region or positively filtered?
1838
1839 const bool clusterIsNotInRegionSelection = (globalState & RegionSelectedMask) &&
1840 ((groupState & RegionSelectedMask) == RegionSelectedNone);
1841 const bool clusterIsNotPositivelyFiltered = (globalState & FilteredPositiveMask) &&
1842 ((groupState & FilteredPositiveMask) == FilteredPositiveNone);
1843
1844 const bool shouldGrayOut = clusterIsNotInRegionSelection || clusterIsNotPositivelyFiltered;
1845 const bool shouldCrossOut = clusterIsNotInRegionSelection;
1846
1847 if (shouldGrayOut)
1848 {
1849 /// @todo Cache the alphaPixmap!
1850
1851 QPixmap alphaPixmap(clusterPixmap.size());
1852 alphaPixmap.fill(QColor::fromRgb(0x80, 0x80, 0x80));
1853 /*
1854 NOTE : old Qt4 code ported to Qt5 due to deprecated QPixmap::setAlphaChannel()
1855 clusterPixmap.setAlphaChannel(alphaPixmap);
1856 */
1857 QPainter p(&clusterPixmap);
1858 p.setOpacity(0.2);
1859 p.drawPixmap(0, 0, alphaPixmap);
1860 p.end();
1861 }
1862
1863 painter.drawPixmap(QPoint(1, 1), clusterPixmap);
1864
1865 if (shouldGrayOut || shouldCrossOut)
1866 {
1867 // draw a red cross above the pixmap
1868
1869 QPen crossPen(Qt::red);
1870
1871 if (!shouldCrossOut)
1872 {
1873 /// @todo Maybe we should also do a cross for not positively filtered images?
1874
1875 crossPen.setColor(Qt::blue);
1876 }
1877
1878 crossPen.setWidth(2);
1879 painter.setPen(crossPen);
1880
1881 const int width = resultPixmap.size().width();
1882 const int height = resultPixmap.size().height();
1883 painter.drawLine(0, 0, width-1, height-1);
1884 painter.drawLine(width-1, 0, 0, height-1);
1885 }
1886
1887 if (strokeStyle != Qt::SolidLine)
1888 {
1889 // paint a white border around the image
1890
1891 borderPen.setColor(Qt::white);
1892 painter.setPen(borderPen);
1893 painter.drawRect(borderWidth-1, borderWidth-1,
1894 resultPixmap.size().width()-borderWidth,
1895 resultPixmap.size().height()-borderWidth);
1896 }
1897
1898 // now draw the selection border
1899
1900 borderPen.setColor(strokeColor);
1901 borderPen.setStyle(strokeStyle);
1902 painter.setPen(borderPen);
1903 painter.drawRect(borderWidth-1, borderWidth-1,
1904 resultPixmap.size().width()-borderWidth,
1905 resultPixmap.size().height()-borderWidth);
1906
1907 if (s->showNumbersOnItems)
1908 {
1909 QPen labelPen(labelColor);
1910
1911 // note: the pen has to be set, otherwise the bounding rect is 0 x 0!!!
1912
1913 painter.setPen(labelPen);
1914 const QRect textRect(0, 0, resultPixmap.width(), resultPixmap.height());
1915 QRect textBoundingRect = painter.boundingRect(textRect,
1916 Qt::AlignHCenter | Qt::AlignVCenter,
1917 labelText);
1918 textBoundingRect.adjust(-1, -1, 1, 1);
1919
1920 // fill the bounding rect:
1921
1922 painter.setPen(Qt::NoPen);
1923 painter.setBrush(QColor::fromRgb(0xff, 0xff, 0xff, 0x80));
1924 painter.drawRect(textBoundingRect);
1925
1926 // draw the text:
1927
1928 painter.setPen(labelPen);
1929 painter.setBrush(Qt::NoBrush);
1930 painter.drawText(textRect, Qt::AlignHCenter|Qt::AlignVCenter, labelText);
1931 }
1932
1933 // update the display information stored in the cluster:
1934
1935 cluster.pixmapType = GeoIfaceCluster::PixmapImage;
1936 cluster.pixmapOffset = QPoint(resultPixmap.width()/2, resultPixmap.height()/2);
1937 cluster.pixmapSize = resultPixmap.size();
1938
1939 if (centerPoint)
1940 {
1941 *centerPoint = cluster.pixmapOffset;
1942 }
1943
1944 return resultPixmap;
1945 }
1946 }
1947
1948 // we do not have a thumbnail, draw the circle instead:
1949
1950 const int circleRadius = s->thumbnailSize/2;
1951 QPen circlePen;
1952 circlePen.setColor(strokeColor);
1953 circlePen.setStyle(strokeStyle);
1954 circlePen.setWidth(2);
1955 QBrush circleBrush(fillColor);
1956 QPen labelPen;
1957 labelPen.setColor(labelColor);
1958 const QRect circleRect(0, 0, 2*circleRadius, 2*circleRadius);
1959
1960 const int pixmapDiameter = 2*(circleRadius+1);
1961 QPixmap circlePixmap(pixmapDiameter, pixmapDiameter);
1962
1963 /// @todo cache this somehow
1964
1965 circlePixmap.fill(QColor(0, 0, 0, 0));
1966
1967 QPainter circlePainter(&circlePixmap);
1968 circlePainter.setPen(circlePen);
1969 circlePainter.setBrush(circleBrush);
1970 circlePainter.drawEllipse(circleRect);
1971
1972 circlePainter.setPen(labelPen);
1973 circlePainter.setBrush(Qt::NoBrush);
1974 circlePainter.drawText(circleRect, Qt::AlignHCenter|Qt::AlignVCenter, labelText);
1975
1976 // update the display information stored in the cluster:
1977
1978 cluster.pixmapType = GeoIfaceCluster::PixmapCircle;
1979 cluster.pixmapOffset = QPoint(circlePixmap.width()/2, circlePixmap.height()/2);
1980 cluster.pixmapSize = circlePixmap.size();
1981
1982 if (centerPoint)
1983 {
1984 *centerPoint = QPoint(circlePixmap.width()/2, circlePixmap.height()/2);
1985 }
1986
1987 return circlePixmap;
1988 }
1989
setThumnailSize(const int newThumbnailSize)1990 void MapWidget::setThumnailSize(const int newThumbnailSize)
1991 {
1992 s->thumbnailSize = qMax(GeoIfaceMinThumbnailSize, newThumbnailSize);
1993
1994 // make sure the grouping radius is larger than the thumbnail size
1995
1996 if (2*s->thumbnailGroupingRadius < newThumbnailSize)
1997 {
1998 /// @todo more straightforward way for this?
1999
2000 s->thumbnailGroupingRadius = newThumbnailSize/2 + newThumbnailSize%2;
2001 }
2002
2003 if (s->showThumbnails)
2004 {
2005 slotRequestLazyReclustering();
2006 }
2007
2008 slotUpdateActionsEnabled();
2009 }
2010
setThumbnailGroupingRadius(const int newGroupingRadius)2011 void MapWidget::setThumbnailGroupingRadius(const int newGroupingRadius)
2012 {
2013 s->thumbnailGroupingRadius = qMax(GeoIfaceMinThumbnailGroupingRadius, newGroupingRadius);
2014
2015 // make sure the thumbnails are smaller than the grouping radius
2016
2017 if (2*s->thumbnailGroupingRadius < s->thumbnailSize)
2018 {
2019 s->thumbnailSize = 2*newGroupingRadius;
2020 }
2021
2022 if (s->showThumbnails)
2023 {
2024 slotRequestLazyReclustering();
2025 }
2026
2027 slotUpdateActionsEnabled();
2028 }
2029
setMarkerGroupingRadius(const int newGroupingRadius)2030 void MapWidget::setMarkerGroupingRadius(const int newGroupingRadius)
2031 {
2032 s->markerGroupingRadius = qMax(GeoIfaceMinMarkerGroupingRadius, newGroupingRadius);
2033
2034 if (!s->showThumbnails)
2035 {
2036 slotRequestLazyReclustering();
2037 }
2038
2039 slotUpdateActionsEnabled();
2040 }
2041
slotDecreaseThumbnailSize()2042 void MapWidget::slotDecreaseThumbnailSize()
2043 {
2044 if (!s->showThumbnails)
2045 {
2046 return;
2047 }
2048
2049 if (s->thumbnailSize>GeoIfaceMinThumbnailSize)
2050 {
2051 const int newThumbnailSize = qMax(GeoIfaceMinThumbnailSize, s->thumbnailSize-5);
2052
2053 // make sure the grouping radius is also decreased
2054 // this will automatically decrease the thumbnail size as well
2055
2056 setThumbnailGroupingRadius(newThumbnailSize/2);
2057 }
2058 }
2059
slotIncreaseThumbnailSize()2060 void MapWidget::slotIncreaseThumbnailSize()
2061 {
2062 if (!s->showThumbnails)
2063 {
2064 return;
2065 }
2066
2067 setThumnailSize(s->thumbnailSize+5);
2068 }
2069
getThumbnailSize() const2070 int MapWidget::getThumbnailSize() const
2071 {
2072 return s->thumbnailSize;
2073 }
2074
getUndecoratedThumbnailSize() const2075 int MapWidget::getUndecoratedThumbnailSize() const
2076 {
2077 return s->thumbnailSize-2;
2078 }
2079
setRegionSelection(const GeoCoordinates::Pair & region)2080 void MapWidget::setRegionSelection(const GeoCoordinates::Pair& region)
2081 {
2082 s->selectionRectangle = region;
2083
2084 d->currentBackend->regionSelectionChanged();
2085
2086 slotUpdateActionsEnabled();
2087 }
2088
clearRegionSelection()2089 void MapWidget::clearRegionSelection()
2090 {
2091 s->selectionRectangle.first.clear();
2092
2093 d->currentBackend->regionSelectionChanged();
2094
2095 slotUpdateActionsEnabled();
2096 }
2097
slotNewSelectionFromMap(const Digikam::GeoCoordinates::Pair & sel)2098 void MapWidget::slotNewSelectionFromMap(const Digikam::GeoCoordinates::Pair& sel)
2099 {
2100 /// @todo Should the backend update s on its own?
2101
2102 s->selectionRectangle = sel;
2103
2104 slotUpdateActionsEnabled();
2105
2106 emit signalRegionSelectionChanged();
2107 }
2108
slotRemoveCurrentRegionSelection()2109 void MapWidget::slotRemoveCurrentRegionSelection()
2110 {
2111 clearRegionSelection();
2112 d->currentBackend->regionSelectionChanged();
2113
2114 slotUpdateActionsEnabled();
2115
2116 emit signalRegionSelectionChanged();
2117 }
2118
slotUngroupedModelChanged()2119 void MapWidget::slotUngroupedModelChanged()
2120 {
2121 // determine the index under which we handle this model
2122
2123 QObject* const senderObject = sender();
2124 QAbstractItemModel* const senderModel = qobject_cast<QAbstractItemModel*>(senderObject);
2125
2126 if (senderModel)
2127 {
2128 for (int i = 0 ; i < s->ungroupedModels.count() ; ++i)
2129 {
2130 if (s->ungroupedModels.at(i)->model() == senderModel)
2131 {
2132 emit signalUngroupedModelChanged(i);
2133
2134 break;
2135 }
2136 }
2137 return;
2138 }
2139
2140 GeoModelHelper* const senderHelper = qobject_cast<GeoModelHelper*>(senderObject);
2141
2142 if (senderHelper)
2143 {
2144 for (int i = 0 ; i < s->ungroupedModels.count() ; ++i)
2145 {
2146 if (s->ungroupedModels.at(i) == senderHelper)
2147 {
2148 emit signalUngroupedModelChanged(i);
2149
2150 break;
2151 }
2152 }
2153 }
2154
2155 QItemSelectionModel* const senderSelectionModel = qobject_cast<QItemSelectionModel*>(senderObject);
2156
2157 if (senderSelectionModel)
2158 {
2159 for (int i = 0 ; i < s->ungroupedModels.count() ; ++i)
2160 {
2161 if (s->ungroupedModels.at(i)->selectionModel()==senderSelectionModel)
2162 {
2163 emit signalUngroupedModelChanged(i);
2164
2165 break;
2166 }
2167 }
2168
2169 return;
2170 }
2171 }
2172
addWidgetToControlWidget(QWidget * const newWidget)2173 void MapWidget::addWidgetToControlWidget(QWidget* const newWidget)
2174 {
2175 // make sure the control widget exists
2176
2177 if (!d->controlWidget)
2178 {
2179 getControlWidget();
2180 }
2181
2182 QHBoxLayout* const hBoxLayout = reinterpret_cast<QHBoxLayout*>(d->hBoxForAdditionalControlWidgetItems->layout());
2183
2184 if (hBoxLayout)
2185 {
2186 hBoxLayout->addWidget(newWidget);
2187 }
2188 }
2189
2190 // Static methods ---------------------------------------------------------
2191
MarbleWidgetVersion()2192 QString MapWidget::MarbleWidgetVersion()
2193 {
2194 return QString(Marble::MARBLE_VERSION_STRING).section(QLatin1Char(' '), 0, 0);
2195 }
2196
setActive(const bool state)2197 void MapWidget::setActive(const bool state)
2198 {
2199 const bool oldState = s->activeState;
2200 s->activeState = state;
2201
2202 if (d->currentBackend)
2203 {
2204 d->currentBackend->setActive(state);
2205 }
2206
2207 if (s->markerModel)
2208 {
2209 s->markerModel->setActive(state);
2210 }
2211
2212 if (state)
2213 {
2214 // do we have a map widget shown?
2215
2216 if ((d->stackedLayout->count() == 1) && d->currentBackend)
2217 {
2218 setMapWidgetInFrame(d->currentBackend->mapWidget());
2219
2220 // call this slot manually in case the backend was ready right away:
2221
2222 if (d->currentBackend->isReady())
2223 {
2224 slotBackendReadyChanged(d->currentBackendName);
2225 }
2226 else
2227 {
2228 rebuildConfigurationMenu();
2229 }
2230 }
2231 }
2232
2233 if (state && !oldState && s->tileGrouper->getClustersDirty())
2234 {
2235 slotRequestLazyReclustering();
2236 }
2237 }
2238
getActiveState()2239 bool MapWidget::getActiveState()
2240 {
2241 return s->activeState;
2242 }
2243
setVisibleMouseModes(const GeoMouseModes mouseModes)2244 void MapWidget::setVisibleMouseModes(const GeoMouseModes mouseModes)
2245 {
2246 s->visibleMouseModes = mouseModes;
2247
2248 if (d->mouseModesHolder)
2249 {
2250 d->mouseModesHolder->setVisible(s->visibleMouseModes);
2251 d->setSelectionModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeRegionSelection));
2252 d->removeCurrentSelectionButton->setVisible(s->visibleMouseModes.testFlag(MouseModeRegionSelection));
2253 d->setPanModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModePan));
2254 d->setZoomModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeZoomIntoGroup));
2255 d->setRegionSelectionFromIconModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeRegionSelectionFromIcon));
2256 d->setFilterModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeFilter));
2257 d->removeFilterModeButton->setVisible(s->visibleMouseModes.testFlag(MouseModeFilter));
2258 d->setSelectThumbnailMode->setVisible(s->visibleMouseModes.testFlag(MouseModeSelectThumbnail));
2259 }
2260 }
2261
setAvailableMouseModes(const GeoMouseModes mouseModes)2262 void MapWidget::setAvailableMouseModes(const GeoMouseModes mouseModes)
2263 {
2264 s->availableMouseModes = mouseModes;
2265 }
2266
getStickyModeState() const2267 bool MapWidget::getStickyModeState() const
2268 {
2269 return d->actionStickyMode->isChecked();
2270 }
2271
setStickyModeState(const bool state)2272 void MapWidget::setStickyModeState(const bool state)
2273 {
2274 d->actionStickyMode->setChecked(state);
2275
2276 slotUpdateActionsEnabled();
2277 }
2278
setVisibleExtraActions(const GeoExtraActions actions)2279 void MapWidget::setVisibleExtraActions(const GeoExtraActions actions)
2280 {
2281 d->visibleExtraActions = actions;
2282
2283 if (d->buttonStickyMode)
2284 {
2285 d->buttonStickyMode->setVisible(actions.testFlag(ExtraActionSticky));
2286 }
2287
2288 slotUpdateActionsEnabled();
2289 }
2290
setEnabledExtraActions(const GeoExtraActions actions)2291 void MapWidget::setEnabledExtraActions(const GeoExtraActions actions)
2292 {
2293 d->availableExtraActions = actions;
2294
2295 slotUpdateActionsEnabled();
2296 }
2297
slotStickyModeChanged()2298 void MapWidget::slotStickyModeChanged()
2299 {
2300 slotUpdateActionsEnabled();
2301
2302 emit signalStickyModeChanged();
2303 }
2304
setAllowModifications(const bool state)2305 void MapWidget::setAllowModifications(const bool state)
2306 {
2307 s->modificationsAllowed = state;
2308
2309 slotUpdateActionsEnabled();
2310 slotRequestLazyReclustering();
2311 }
2312
2313 /**
2314 * @brief Adjusts the visible map area such that all grouped markers are visible.
2315 *
2316 * Note that a call to this function currently has no effect if the widget has been
2317 * set inactive via setActive() or the backend is not yet ready.
2318 *
2319 * @param useSaneZoomLevel Stop zooming at a sane level, if markers are too close together.
2320 */
adjustBoundariesToGroupedMarkers(const bool useSaneZoomLevel)2321 void MapWidget::adjustBoundariesToGroupedMarkers(const bool useSaneZoomLevel)
2322 {
2323 if ((!s->activeState) || (!s->markerModel) || (!currentBackendReady()))
2324 {
2325 return;
2326 }
2327
2328 Marble::GeoDataLineString tileString;
2329
2330 /// @todo not sure that this is the best way to find the bounding box of all items
2331
2332 for (AbstractMarkerTiler::NonEmptyIterator tileIterator(s->markerModel, TileIndex::MaxLevel);
2333 !tileIterator.atEnd();
2334 tileIterator.nextIndex())
2335 {
2336 const TileIndex tileIndex = tileIterator.currentIndex();
2337
2338 for (int corner = 1 ; corner <= 4 ; ++corner)
2339 {
2340 const GeoCoordinates currentTileCoordinate = tileIndex.toCoordinates(TileIndex::CornerPosition(corner));
2341
2342 const Marble::GeoDataCoordinates tileCoordinate(currentTileCoordinate.lon(),
2343 currentTileCoordinate.lat(),
2344 0,
2345 Marble::GeoDataCoordinates::Degree);
2346
2347 tileString.append(tileCoordinate);
2348 }
2349 }
2350
2351 const Marble::GeoDataLatLonBox latLonBox = Marble::GeoDataLatLonBox::fromLineString(tileString);
2352
2353 /// @todo use a sane zoom level
2354
2355 d->currentBackend->centerOn(latLonBox, useSaneZoomLevel);
2356 }
2357
refreshMap()2358 void MapWidget::refreshMap()
2359 {
2360 slotRequestLazyReclustering();
2361 }
2362
setShowPlaceholderWidget(const bool state)2363 void MapWidget::setShowPlaceholderWidget(const bool state)
2364 {
2365 if (state)
2366 {
2367 d->stackedLayout->setCurrentIndex(0);
2368 }
2369 else
2370 {
2371 if (d->stackedLayout->count() > 1)
2372 {
2373 d->stackedLayout->setCurrentIndex(1);
2374 }
2375 }
2376 }
2377
2378 /**
2379 * @brief Set @p widgetForFrame as the widget in the frame, but does not show it.
2380 */
setMapWidgetInFrame(QWidget * const widgetForFrame)2381 void MapWidget::setMapWidgetInFrame(QWidget* const widgetForFrame)
2382 {
2383 if (d->stackedLayout->count() > 1)
2384 {
2385 // widget 0 is the status widget, widget 1 is the map widget
2386
2387 if (d->stackedLayout->widget(1) == widgetForFrame)
2388 {
2389 return;
2390 }
2391
2392 // there is some other widget at the target position.
2393 // remove it and add our widget instead
2394
2395 d->stackedLayout->removeWidget(d->stackedLayout->widget(1));
2396 }
2397
2398 d->stackedLayout->addWidget(widgetForFrame);
2399 }
2400
removeMapWidgetFromFrame()2401 void MapWidget::removeMapWidgetFromFrame()
2402 {
2403 if (d->stackedLayout->count() > 1)
2404 {
2405 d->stackedLayout->removeWidget(d->stackedLayout->widget(1));
2406 }
2407
2408 d->stackedLayout->setCurrentIndex(0);
2409 }
2410
slotMouseModeChanged(QAction * triggeredAction)2411 void MapWidget::slotMouseModeChanged(QAction* triggeredAction)
2412 {
2413 // determine the new mouse mode:
2414
2415 const QVariant triggeredActionData = triggeredAction->data();
2416 const GeoMouseModes newMouseMode = triggeredActionData.value<Digikam::GeoMouseModes>();
2417
2418 if (newMouseMode == s->currentMouseMode)
2419 {
2420 return;
2421 }
2422
2423 // store the new mouse mode:
2424
2425 s->currentMouseMode = newMouseMode;
2426
2427 if (d->currentBackend)
2428 {
2429 d->currentBackend->mouseModeChanged();
2430 }
2431
2432 emit signalMouseModeChanged(s->currentMouseMode);
2433
2434 /// @todo Update action availability?
2435 }
2436
currentBackendReady() const2437 bool MapWidget::currentBackendReady() const
2438 {
2439 if (!d->currentBackend)
2440 {
2441 return false;
2442 }
2443
2444 return d->currentBackend->isReady();
2445 }
2446
setMouseMode(const GeoMouseModes mouseMode)2447 void MapWidget::setMouseMode(const GeoMouseModes mouseMode)
2448 {
2449 s->currentMouseMode = mouseMode;
2450
2451 if (currentBackendReady())
2452 {
2453 d->currentBackend->mouseModeChanged();
2454 }
2455
2456 slotUpdateActionsEnabled();
2457 }
2458
setTrackManager(TrackManager * const trackManager)2459 void MapWidget::setTrackManager(TrackManager* const trackManager)
2460 {
2461 s->trackManager = trackManager;
2462
2463 // Some backends track the track manager activity even when not active
2464 // therefore they have to be notified.
2465
2466 foreach (MapBackend* const backend, d->loadedBackends)
2467 {
2468 backend->slotTrackManagerChanged();
2469 }
2470 }
2471
2472 } // namespace Digikam
2473