1 /**********************************************************************************************
2     Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>
3     Copyright (C) 2017 Norbert Truchsess <norbert.truchsess@t-online.de>
4     Copyright (C) 2019 Henri Hornburg <hrnbg@t-online.de>
5 
6     This program is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 
19 **********************************************************************************************/
20 
21 #include "canvas/CCanvas.h"
22 #include "CMainWindow.h"
23 #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
24 #include "device/CDeviceWatcherLinux.h"
25 #endif
26 #ifdef Q_OS_WIN
27 #include "device/CDeviceWatcherWindows.h"
28 #endif
29 #ifdef Q_OS_MAC
30 #include "device/CDeviceWatcherMac.h"
31 #endif
32 #include "device/IDevice.h"
33 #include "gis/CGisDatabase.h"
34 #include "gis/CGisListWks.h"
35 #include "gis/CGisWorkspace.h"
36 #include "gis/CSelDevices.h"
37 #include "gis/db/CDBProject.h"
38 #include "gis/db/CLostFoundProject.h"
39 #include "gis/db/CSelectDBFolder.h"
40 #include "gis/db/CSetupFolder.h"
41 #include "gis/db/macros.h"
42 #include "gis/fit/CFitProject.h"
43 #include "gis/gpx/CGpxProject.h"
44 #include "gis/IGisItem.h"
45 #include "gis/ovl/CGisItemOvlArea.h"
46 #include "gis/prj/IGisProject.h"
47 #include "gis/qlb/CQlbProject.h"
48 #include "gis/qms/CQmsProject.h"
49 #include "gis/rte/CGisItemRte.h"
50 #include "gis/search/CGeoSearch.h"
51 #include "gis/search/CGeoSearchConfig.h"
52 #include "gis/search/CGeoSearchWeb.h"
53 #include "gis/slf/CSlfProject.h"
54 #include "gis/suunto/CLogProject.h"
55 #include "gis/suunto/CSmlProject.h"
56 #include "gis/tcx/CTcxProject.h"
57 #include "gis/trk/CGisItemTrk.h"
58 #include "gis/wpt/CGisItemWpt.h"
59 #include "helpers/CProgressDialog.h"
60 #include "helpers/CSelectCopyAction.h"
61 #include "helpers/CSelectProjectDialog.h"
62 #include "helpers/CSettings.h"
63 #include "helpers/CWptIconManager.h"
64 #include "setup/IAppSetup.h"
65 
66 #include <QApplication>
67 #include <QtSql>
68 #include <QtWidgets>
69 
70 
71 #undef  DB_VERSION
72 #define DB_VERSION 4
73 
74 class CGisListWksEditLock
75 {
76 public:
CGisListWksEditLock(bool waitCursor,QMutex & mutex)77     CGisListWksEditLock(bool waitCursor, QMutex& mutex) : mutex(mutex), waitCursor(waitCursor)
78     {
79         if(waitCursor)
80         {
81             CCanvas::setOverrideCursor(Qt::WaitCursor, "CGisListWksEditLock");
82         }
83         mutex.lock();
84     }
~CGisListWksEditLock()85     ~CGisListWksEditLock()
86     {
87         if(waitCursor)
88         {
89             CCanvas::restoreOverrideCursor("~CGisListWksEditLock");
90         }
91         mutex.unlock();
92     }
93 private:
94     QMutex& mutex;
95     bool waitCursor;
96 };
97 
CGisListWks(QWidget * parent)98 CGisListWks::CGisListWks(QWidget* parent)
99     : QTreeWidget(parent)
100 {
101     db = QSqlDatabase::addDatabase("QSQLITE", "Workspace1");
102     QString config = QDir(IAppSetup::getPlatformInstance()->userDataPath()).filePath("workspace.db");
103     db.setDatabaseName(config);
104     db.open();
105     configDB();
106 
107     // workspace project related actions
108     actionEditPrj = addAction(QIcon("://icons/32x32/EditDetails.png"), tr("Edit.."), this, &CGisListWks::slotEditPrj);
109     actionCopyPrj = addAction(QIcon("://icons/32x32/Copy.png"), tr("Copy to..."), this, &CGisListWks::slotCopyProject);
110     actionShowOnMap = addAction(QIcon("://icons/32x32/ShowAll.png"), tr("Show on Map"), this, &CGisListWks::slotShowOnMap);
111     actionHideFrMap = addAction(QIcon("://icons/32x32/ShowNone.png"), tr("Hide from Map"), this, &CGisListWks::slotHideFrMap);
112     actionGroupSort = new QActionGroup(this);
113     actionGroupSort->setExclusive(true);
114     actionSortByTime = addSortAction(this, actionGroupSort, "://icons/32x32/Time.png", tr("Sort by Time"), IGisProject::eSortFolderTime);
115     actionSortByName = addSortAction(this, actionGroupSort, "://icons/32x32/SortName.png", tr("Sort by Name"), IGisProject::eSortFolderName);
116     actionSortByRating = addSortAction(this, actionGroupSort, "://icons/32x32/Tag.png", tr("Sort by Rating"), IGisProject::eSortFolderRating);
117     actionFilterProject = addAction(QIcon("://icons/32x32/Filter.png"), tr("Filter Project"), this, &CGisListWks::slotAddProjectFilter);
118     actionFilterProject->setCheckable(true);
119     actionAutoSave = addAction(QIcon("://icons/32x32/AutoSave.png"), tr("Autom. Save"), this, &CGisListWks::slotAutoSaveProject);
120     actionAutoSave->setCheckable(true);
121     actionUserFocusPrj = addAction(QIcon("://icons/32x32/Focus.png"), tr("Active Project"), this, &CGisListWks::slotUserFocusPrj);
122     actionUserFocusPrj->setCheckable(true);
123     actionAutoSyncToDev = addAction(QIcon("://icons/32x32/Device.png"), tr("Autom. Sync. w. Device"), this, &CGisListWks::slotAutoSyncProject);
124     actionAutoSyncToDev->setCheckable(true);
125 
126     actionSave = addAction(QIcon("://icons/32x32/SaveGIS.png"), tr("Save"), this, &CGisListWks::slotSaveProject);
127     actionSaveAs = addAction(QIcon("://icons/32x32/SaveGISAs.png"), tr("Save as..."), this, &CGisListWks::slotSaveAsProject);
128     actionSaveAsStrict = addAction(QIcon("://icons/32x32/SaveGISAsGpx11.png"), tr("Save as GPX 1.1 w/o ext..."), this, &CGisListWks::slotSaveAsStrictGpx11Project);
129     actionSyncWksDev = addAction(QIcon("://icons/32x32/Device.png"), tr("Send to Devices"), this, &CGisListWks::slotSyncWksDev);
130     actionSyncDB = addAction(QIcon("://icons/32x32/DatabaseSync.png"), tr("Sync. with Database"), this, &CGisListWks::slotSyncDB);
131     actionCloseProj = addAction(QIcon("://icons/32x32/Close.png"), tr("Close"), this, &CGisListWks::slotCloseProject);
132 
133     // device project related actions
134     actionSyncDevWks = addAction(QIcon("://icons/32x32/Device.png"), tr("Update Project on Device"), this, &CGisListWks::slotSyncDevWks);
135     actionDelProj = addAction(QIcon("://icons/32x32/DeleteOne.png"), tr("Delete"), this, &CGisListWks::slotDeleteProject);
136 
137     // common to all items actions
138     actionEditDetails = addAction(QIcon("://icons/32x32/EditDetails.png"), tr("Edit..."), this, &CGisListWks::slotEditItem);
139     actionTagItem = addAction(QIcon("://icons/32x32/Tag.png"), tr("Set Tags"), this, &CGisListWks::slotTagItem);
140     actionCopyItem = addAction(QIcon("://icons/32x32/Copy.png"), tr("Copy to..."), this, &CGisListWks::slotCopyItem);
141     actionDelete = addAction(QIcon("://icons/32x32/DeleteOne.png"), tr("Delete"), this, &CGisListWks::slotDeleteItem);
142 
143     // track related actions
144     actionFocusTrk = addAction(QIcon("://icons/32x32/TrkProfile.png"), tr("Track Information"), this, &CGisListWks::slotFocusTrk);
145     actionFocusTrk->setCheckable(true);
146     actionRangeTrk = addAction(QIcon("://icons/32x32/SelectRange.png"), tr("Select Range"), this, &CGisListWks::slotRangeTrk);
147     actionEditTrk = addAction(QIcon("://icons/32x32/LineMove.png"), tr("Edit Track Points"), this, &CGisListWks::slotEditTrk);
148     actionReverseTrk = addAction(QIcon("://icons/32x32/Reverse.png"), tr("Reverse Track"), this, &CGisListWks::slotReverseTrk);
149     actionCombineTrk = addAction(QIcon("://icons/32x32/Combine.png"), tr("Combine Tracks"), this, &CGisListWks::slotCombineTrk);
150     actionEleWptTrk = addAction(QIcon("://icons/32x32/SetEle.png"), tr("Replace Elevation by DEM"), this, &CGisListWks::slotEleWptTrk);
151     actionCopyTrkWithWpt = addAction(QIcon("://icons/32x32/CopyTrkWithWpt.png"), tr("Copy Track with Waypoints"), this, &CGisListWks::slotCopyTrkWithWpt);
152     actionToRoute = addAction(QIcon("://icons/32x32/Route.png"), tr("Convert to Route"), this, &CGisListWks::slotToRoute);
153     actionNogoTrk = addAction(QIcon("://icons/32x32/NoGo.png"), tr("Toggle Nogo-Line"), this, &CGisListWks::slotNogoItem);
154     actionNogoTrk->setCheckable(true);
155 
156     // waypoint related actions
157     actionBubbleWpt = addAction(QIcon("://icons/32x32/Bubble.png"), tr("Show Bubble"), this, &CGisListWks::slotBubbleWpt);
158     actionBubbleWpt->setCheckable(true);
159     actionMoveWpt = addAction(QIcon("://icons/32x32/WptMove.png"), tr("Move Waypoint"), this, &CGisListWks::slotMoveWpt);
160     actionProjWpt = addAction(QIcon("://icons/32x32/WptProj.png"), tr("Proj. Waypoint..."), this, &CGisListWks::slotProjWpt);
161     actionEditRadiusWpt = addAction(QIcon("://icons/32x32/WptEditProx.png"), tr("Change Radius"), this, &CGisListWks::slotEditRadiusWpt);
162     actionDelRadiusWpt = addAction(QIcon("://icons/32x32/WptDelProx.png"), tr("Delete Radius"), this, &CGisListWks::slotDelRadiusWpt);
163     actionNogoWpt = addAction(QIcon("://icons/32x32/NoGo.png"), tr("Toggle Nogo-Area"), this, &CGisListWks::slotNogoItem);
164     actionNogoWpt->setCheckable(true);
165     actionCopyCoordWpt = addAction(QIcon("://icons/32x32/CopyCoord.png"), tr("Copy position"), this, &CGisListWks::slotCopyCoordWpt);
166 
167     // route related actions
168     actionFocusRte = addAction(QIcon("://icons/32x32/RteInstr.png"), tr("Route Instructions"), this, &CGisListWks::slotFocusRte);
169     actionFocusRte->setCheckable(true);
170     actionCalcRte = addAction(QIcon("://icons/32x32/Apply.png"), tr("Calculate Route"), this, &CGisListWks::slotCalcRte);
171     actionResetRte = addAction(QIcon("://icons/32x32/Reset.png"), tr("Reset Route"), this, &CGisListWks::slotResetRte);
172     actionEditRte = addAction(QIcon("://icons/32x32/LineMove.png"), tr("Edit Route"), this, &CGisListWks::slotEditRte);
173     actionReverseRte = addAction(QIcon("://icons/32x32/Reverse.png"), tr("Reverse Route"), this, &CGisListWks::slotReverseRte);
174     actionRte2Trk = addAction(QIcon("://icons/32x32/Track.png"), tr("Convert to Track"), this, &CGisListWks::slotRte2Trk);
175     actionNogoRte = addAction(QIcon("://icons/32x32/NoGo.png"), tr("Toggle Nogo-Line"), this, &CGisListWks::slotNogoItem);
176     actionNogoRte->setCheckable(true);
177 
178     // area related actions
179     actionEditArea = addAction(QIcon("://icons/32x32/AreaMove.png"), tr("Edit Area Points"), this, &CGisListWks::slotEditArea);
180     actionNogoArea = addAction(QIcon("://icons/32x32/NoGo.png"), tr("Toggle Nogo-Area"), this, &CGisListWks::slotNogoItem);
181     actionNogoArea->setCheckable(true);
182 
183     // several GIS items related actions
184     actionRteFromWpt = addAction(QIcon("://icons/32x32/Route.png"), tr("Create Route..."), this, &CGisListWks::slotRteFromWpt);
185     actionEditPrxWpt = addAction(QIcon("://icons/32x32/WptEditProx.png"), tr("Change Proximity..."), this, &CGisListWks::slotEditPrxWpt);
186 
187     connect(qApp, &QApplication::aboutToQuit, this, &CGisListWks::slotSaveWorkspace);
188     connect(this, &CGisListWks::customContextMenuRequested, this, &CGisListWks::slotContextMenu);
189     connect(this, &CGisListWks::itemDoubleClicked, this, &CGisListWks::slotItemDoubleClicked);
190     connect(this, &CGisListWks::itemChanged, this, &CGisListWks::slotItemChanged);
191 
192     SETTINGS;
193     saveOnExit = cfg.value("Database/saveOnExit", saveOnExit).toBool();
194     saveEvery = cfg.value("Database/saveEvery", saveEvery).toInt();
195 
196     if(saveOnExit && (saveEvery > 0))
197     {
198         QTimer::singleShot(saveEvery * 60000, this, &CGisListWks::slotSaveWorkspace);
199     }
200 
201     if(cfg.value("Database/device support", true).toBool())
202     {
203         qDebug() << "Device support enabled";
204 #ifdef Q_OS_MAC
205         deviceWatcher = new CDeviceWatcherMac(this);
206 #else
207     #ifdef Q_OS_WIN
208         deviceWatcher = new CDeviceWatcherWindows(this);
209     #else
210         #ifdef HAVE_DBUS
211         deviceWatcher = new CDeviceWatcherLinux(this);
212         #endif // HAVE_DBUS
213     #endif // Q_OS_WIN
214 #endif // Q_OS_MAC
215 
216         if(deviceWatcher)
217         {
218             connect(deviceWatcher, &IDeviceWatcher::sigChanged, this, &CGisListWks::sigChanged);
219             connect(deviceWatcher, &IDeviceWatcher::sigChanged, this, &CGisListWks::slotNewDevice);
220         }
221     }
222 }
223 
~CGisListWks()224 CGisListWks::~CGisListWks()
225 {
226 }
227 
configDB()228 void CGisListWks::configDB()
229 {
230     QSqlQuery query(db);
231 
232     QUERY_RUN("PRAGMA locking_mode=EXCLUSIVE", return )
233     QUERY_RUN("PRAGMA synchronous=OFF", return )
234     QUERY_RUN("PRAGMA temp_store=MEMORY", return )
235     QUERY_RUN("PRAGMA default_cache_size=50", return )
236     QUERY_RUN("PRAGMA page_size=8192", return )
237 
238     // When migrating the database these tables are used.
239     // Due to caching they can't be dropped right after the
240     // migration. That is why we look for them on startup.
241     // And delete them as a second chance.
242     if(query.exec("select * from tmp_workspace"))
243     {
244         QUERY_RUN("DROP TABLE tmp_workspace;", return );
245     }
246 
247 
248     if(!query.exec("SELECT version FROM versioninfo"))
249     {
250         initDB();
251     }
252     else if(query.next())
253     {
254         int version = query.value(0).toInt();
255         if(version != DB_VERSION)
256         {
257             migrateDB(version);
258         }
259     }
260     else
261     {
262         initDB();
263     }
264 }
265 
initDB()266 void CGisListWks::initDB()
267 {
268     QSqlQuery query(db);
269 
270     if(query.exec( "CREATE TABLE versioninfo ( version TEXT )"))
271     {
272         query.prepare( "INSERT INTO versioninfo (version) VALUES(:version)");
273         query.bindValue(":version", DB_VERSION);
274         QUERY_EXEC();
275     }
276 
277     QUERY_RUN("CREATE TABLE workspace ("
278               "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
279               "type           INTEGER NOT NULL,"
280               "name           TEXT NOT NULL,"
281               "keyqms         TEXT NOT NULL,"
282               "changed        BOOLEAN DEFAULT FALSE,"
283               "visible        BOOLEAN DEFAULT TRUE,"
284               "data           BLOB NOT NULL"
285               ")", NO_CMD)
286 
287     if(query.exec( "CREATE TABLE userfocus ( focus TEXT )"))
288     {
289         query.prepare( "INSERT INTO userfocus (focus) VALUES(:focus)");
290         query.bindValue(":focus", "");
291         QUERY_EXEC();
292     }
293 }
294 
migrateDB(int version)295 void CGisListWks::migrateDB(int version)
296 {
297     qDebug() << "workspace.db has version " << version << ", migration to version " << DB_VERSION << " required";
298 
299     // try to migrate between the database versions step by step (as soon as applicable)
300     if(version < 2)
301     {
302         migrateDB1to2();
303     }
304     if(version < 3)
305     {
306         migrateDB2to3();
307     }
308     if(version < 4)
309     {
310         migrateDB3to4();
311     }
312 
313     // save the new version to the database
314     QSqlQuery query(db);
315     query.prepare( "UPDATE versioninfo set version=:version");
316     query.bindValue(":version", DB_VERSION);
317     QUERY_EXEC();
318 }
319 
migrateDB1to2()320 void CGisListWks::migrateDB1to2()
321 {
322     qDebug() << "migrating workspace.db from version 1 to version 2";
323     // add a new column `visible` to the database
324     // the default value is `true`, as - by default in older versions of QMS - all saved projects
325     // have been loaded and shown on the map directly after starting
326     QSqlQuery query(db);
327     QUERY_RUN("ALTER TABLE workspace ADD COLUMN visible BOOLEAN DEFAULT TRUE;", NO_CMD)
328 }
329 
migrateDB2to3()330 void CGisListWks::migrateDB2to3()
331 {
332     QSqlQuery query(db);
333 
334     QUERY_RUN("BEGIN TRANSACTION;", return )
335     QUERY_RUN("ALTER TABLE workspace RENAME TO tmp_workspace;", return )
336     QUERY_RUN("CREATE TABLE workspace ("
337               "id             INTEGER PRIMARY KEY AUTOINCREMENT,"
338               "type           INTEGER NOT NULL,"
339               "name           TEXT NOT NULL,"
340               "keyqms         TEXT NOT NULL,"
341               "changed        BOOLEAN DEFAULT FALSE,"
342               "visible        BOOLEAN DEFAULT TRUE,"
343               "data           BLOB NOT NULL"
344               ")", return );
345     QUERY_RUN("INSERT INTO workspace(id,type,name,keyqms,changed,visible,data) SELECT * FROM tmp_workspace;", return )
346     QUERY_RUN("COMMIT;", return )
347     QUERY_RUN("DROP TABLE tmp_workspace;", return )
348 }
349 
migrateDB3to4()350 void CGisListWks::migrateDB3to4()
351 {
352     QSqlQuery query(db);
353 
354     if(query.exec( "CREATE TABLE userfocus ( focus TEXT )"))
355     {
356         query.prepare( "INSERT INTO userfocus (focus) VALUES(:focus)");
357         query.bindValue(":focus", "");
358         QUERY_EXEC();
359     }
360 }
361 
setExternalMenu(QMenu * project)362 void CGisListWks::setExternalMenu(QMenu* project)
363 {
364     menuNone = project;
365     connect(CMainWindow::self().findChild<QAction*>("actionAddEmptyProject"), &QAction::triggered, this, &CGisListWks::slotAddEmptyProject);
366     connect(CMainWindow::self().findChild<QAction*>("actionCloseAllProjects"), &QAction::triggered, this, &CGisListWks::slotCloseAllProjects);
367     connect(CMainWindow::self().findChild<QAction*>("actionGeoSearch"), &QAction::triggered, this, &CGisListWks::slotGeoSearch);
368 }
369 
370 
addSortAction(QObject * parent,QActionGroup * actionGroup,const QString & icon,const QString & text,IGisProject::sorting_folder_e mode)371 QAction* CGisListWks::addSortAction(QObject* parent, QActionGroup* actionGroup, const QString& icon, const QString& text, IGisProject::sorting_folder_e mode)
372 {
373     QAction* action = new QAction(QIcon(icon), text, parent);
374     action->setCheckable(true);
375 
376     connect(action, &QAction::toggled, this, [this, mode](bool checked){slotSetSortMode(mode, checked);});
377 
378     actionGroup->addAction(action);
379 
380     return action;
381 }
382 
dragMoveEvent(QDragMoveEvent * e)383 void CGisListWks::dragMoveEvent(QDragMoveEvent* e )
384 {
385     CGisListWksEditLock lock(true, IGisItem::mutexItems);
386 
387     QTreeWidgetItem* item1 = currentItem();
388     QTreeWidgetItem* item2 = itemAt(e->pos());
389 
390     // changing the item order is only valid for single selected items
391     if(selectedItems().count() == 1)
392     {
393         /*
394             What's happening here?
395 
396             1) Cast current item and item under cursor to GIS item type
397             2) If type matches for both test for common parent
398             2.1) common parent->  move
399             2.1) different parent -> copy
400             3) go on with dragMoveEvent();
401 
402          */
403         CGisItemTrk* trk1 = dynamic_cast<CGisItemTrk*>(item1);
404         CGisItemTrk* trk2 = dynamic_cast<CGisItemTrk*>(item2);
405 
406         if(trk1 && trk2)
407         {
408             e->setDropAction( trk1->parent() == trk2->parent() ? Qt::MoveAction : Qt::CopyAction);
409             QTreeWidget::dragMoveEvent(e);
410             return;
411         }
412 
413         CGisItemWpt* wpt1 = dynamic_cast<CGisItemWpt*>(item1);
414         CGisItemWpt* wpt2 = dynamic_cast<CGisItemWpt*>(item2);
415 
416         if(wpt1 && wpt2)
417         {
418             e->setDropAction( wpt1->parent() == wpt2->parent() ? Qt::MoveAction : Qt::CopyAction);
419             QTreeWidget::dragMoveEvent(e);
420             return;
421         }
422 
423         CGisItemRte* rte1 = dynamic_cast<CGisItemRte*>(item1);
424         CGisItemRte* rte2 = dynamic_cast<CGisItemRte*>(item2);
425 
426         if(rte1 && rte2)
427         {
428             e->setDropAction( rte1->parent() == rte2->parent() ? Qt::MoveAction : Qt::CopyAction);
429             QTreeWidget::dragMoveEvent(e);
430             return;
431         }
432 
433         CGisItemOvlArea* area1 = dynamic_cast<CGisItemOvlArea*>(item1);
434         CGisItemOvlArea* area2 = dynamic_cast<CGisItemOvlArea*>(item2);
435 
436         if(area1 && area2)
437         {
438             e->setDropAction( area1->parent() == area2->parent() ? Qt::MoveAction : Qt::CopyAction);
439             QTreeWidget::dragMoveEvent(e);
440             return;
441         }
442 
443         /*
444             Never move/copy projects on devices. Data has to be removed or changed
445             to store a project and it's items on a device. Moving it back to the
446             workspace would conflict with the original project. To much hassle to
447             resolve this properly.
448          */
449         IGisProject* proj1 = dynamic_cast<IGisProject*>(item1);
450         if(proj1 && proj1->isOnDevice())
451         {
452             e->setDropAction(Qt::IgnoreAction);
453             QTreeWidget::dragMoveEvent(e);
454             return;
455         }
456     }
457 
458     /*
459         Test for other project, to change project order. But if other project
460         is on a device block the request. A project has to be copied to the
461         device via it's device item.
462      */
463     IGisProject* proj2 = dynamic_cast<IGisProject*>(item2);
464     if(proj2)
465     {
466         IGisProject* proj1 = dynamic_cast<IGisProject*>(item1);
467         if(proj1)
468         {
469             e->setDropAction(proj2->isOnDevice() ? Qt::IgnoreAction : Qt::MoveAction);
470             QTreeWidget::dragMoveEvent(e);
471             return;
472         }
473 
474         IGisItem* gisItem1 = dynamic_cast<IGisItem*>(item1);
475         if(gisItem1)
476         {
477             e->setDropAction(Qt::CopyAction);
478             QTreeWidget::dragMoveEvent(e);
479             return;
480         }
481     }
482 
483     /*
484         Test for device as drop target. A device will copy the project into
485         it's own supported format.
486      */
487     IDevice* device = dynamic_cast<IDevice*>(item2);
488     if(device)
489     {
490         IGisProject* proj1 = dynamic_cast<IGisProject*>(item1);
491         if(proj1 && !proj1->isOnDevice())
492         {
493             e->setDropAction(Qt::CopyAction);
494             QTreeWidget::dragMoveEvent(e);
495             return;
496         }
497     }
498 
499     e->setDropAction(Qt::IgnoreAction);
500     QTreeWidget::dragMoveEvent(e);
501 }
502 
dropEvent(QDropEvent * e)503 void CGisListWks::dropEvent( QDropEvent* e )
504 {
505     CGisListWksEditLock lock(true, IGisItem::mutexItems);
506 
507     const QList<QTreeWidgetItem*>& items = selectedItems();
508     if(items.isEmpty())
509     {
510         return;
511     }
512 
513     CSelectCopyAction::result_e lastResult = CSelectCopyAction::eResultNone;
514 
515     // go on with item insertion
516     /*
517         What's happening here?
518 
519         for single selected items do:
520         1) Test if item will be inserted above ore below item under cursor.
521         2) Cast current item and item under cursor to GIS item type
522         3) If type matches for both test for common parent
523         3.1) common parent-> go on with default drop event
524         3.1) different parent -> create a copy and insert it index
525         4) signal change of project
526 
527         for single and multiple selected items, do:
528         5) Test if item under cursor is a project
529         6) If project and project is not item's project create a copy
530 
531      */
532     if(items.size() == 1)
533     {
534         // calc. index offset (below/above item)
535         QRect r = visualItemRect(itemAt(e->pos()));
536         int y1 = r.top() + r.height() / 2;
537         int y2 = e->pos().y();
538         int off = y2 > y1 ? 1 : 0;
539 
540         IGisProject* prj1 = dynamic_cast<IGisProject*>(currentItem());
541         IGisProject* prj2 = dynamic_cast<IGisProject*>(itemAt(e->pos()));
542         if(prj1 && prj2)
543         {
544             prj2->setFlags(prj2->flags() & ~Qt::ItemIsDropEnabled);
545             QTreeWidget::dropEvent(e);
546             prj2->setFlags(prj2->flags() | Qt::ItemIsDropEnabled);
547             emit sigChanged();
548             return;
549         }
550 
551         CGisItemWpt* wpt1 = dynamic_cast<CGisItemWpt*>(currentItem());
552         CGisItemWpt* wpt2 = dynamic_cast<CGisItemWpt*>(itemAt(e->pos()));
553 
554         if(wpt1 && wpt2)
555         {
556             if(wpt1->parent() == wpt2->parent())
557             {
558                 QTreeWidget::dropEvent(e);
559             }
560             else
561             {
562                 IGisProject* project = dynamic_cast<IGisProject*>(wpt2->parent());
563                 if(project)
564                 {
565                     project->insertCopyOfItem(wpt1, off, lastResult);
566                 }
567             }
568             emit sigChanged();
569             return;
570         }
571 
572         CGisItemTrk* trk1 = dynamic_cast<CGisItemTrk*>(currentItem());
573         CGisItemTrk* trk2 = dynamic_cast<CGisItemTrk*>(itemAt(e->pos()));
574 
575         if(trk1 && trk2)
576         {
577             if(trk1->parent() == trk2->parent())
578             {
579                 QTreeWidget::dropEvent(e);
580             }
581             else
582             {
583                 IGisProject* project = dynamic_cast<IGisProject*>(trk2->parent());
584                 if(project)
585                 {
586                     project->insertCopyOfItem(trk1, off, lastResult);
587                 }
588             }
589             emit sigChanged();
590             return;
591         }
592 
593         CGisItemRte* rte1 = dynamic_cast<CGisItemRte*>(currentItem());
594         CGisItemRte* rte2 = dynamic_cast<CGisItemRte*>(itemAt(e->pos()));
595 
596         if(rte1 && rte2)
597         {
598             if(rte1->parent() == rte2->parent())
599             {
600                 QTreeWidget::dropEvent(e);
601             }
602             else
603             {
604                 IGisProject* project = dynamic_cast<IGisProject*>(rte2->parent());
605                 if(project)
606                 {
607                     project->insertCopyOfItem(rte1, off, lastResult);
608                 }
609             }
610             emit sigChanged();
611             return;
612         }
613 
614         CGisItemOvlArea* area1 = dynamic_cast<CGisItemOvlArea*>(currentItem());
615         CGisItemOvlArea* area2 = dynamic_cast<CGisItemOvlArea*>(itemAt(e->pos()));
616 
617         if(area1 && area2)
618         {
619             if(area1->parent() == area2->parent())
620             {
621                 QTreeWidget::dropEvent(e);
622             }
623             else
624             {
625                 IGisProject* project = dynamic_cast<IGisProject*>(area2->parent());
626                 if(project)
627                 {
628                     project->insertCopyOfItem(area1, off, lastResult);
629                 }
630             }
631             emit sigChanged();
632             return;
633         }
634     }
635 
636     // check if item at position is a project and insert a copy of all selected items
637     IGisProject* project = dynamic_cast<IGisProject*>(itemAt(e->pos()));
638     if(project)
639     {
640         project->blockUpdateItems(true);
641 
642         int cnt = 1;
643         int N = items.size();
644         PROGRESS_SETUP(tr("Drop items..."), 0, N, this);
645 
646         for(QTreeWidgetItem* item : items)
647         {
648             PROGRESS(cnt++, break);
649 
650             IGisItem* gisItem = dynamic_cast<IGisItem*>(item);
651             if(gisItem)
652             {
653                 project->insertCopyOfItem(gisItem, NOIDX, lastResult);
654             }
655         }
656 
657         project->blockUpdateItems(false);
658     }
659 
660     IDevice* device = dynamic_cast<IDevice*>(itemAt(e->pos()));
661     if(device)
662     {
663         IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
664         if(project)
665         {
666             CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
667             if(canvas)
668             {
669                 canvas->reportStatus("device", tr("<b>Update devices</b><p>Update %1<br/>Please wait...</p>").arg(device->text(CGisListWks::eColumnName)));
670                 canvas->update();
671                 qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
672             }
673 
674             int lastResult = CSelectCopyAction::eResultNone;
675             device->insertCopyOfProject(project, lastResult);
676 
677             if(canvas)
678             {
679                 canvas->reportStatus("device", "");
680             }
681         }
682     }
683 
684     emit sigChanged();
685 }
686 
addProject(IGisProject * proj)687 void CGisListWks::addProject(IGisProject* proj)
688 {
689     if(!proj->isValid())
690     {
691         return;
692     }
693 
694     addTopLevelItem(proj);
695 
696     // move project up the list until there a re only projects, no devices
697     int newIdx = NOIDX;
698     const int myIdx = topLevelItemCount() - 1;
699     for(int i = myIdx - 1; i >= 0; i--)
700     {
701         IDevice* device = dynamic_cast<IDevice*>(topLevelItem(i));
702         if(nullptr == device)
703         {
704             break;
705         }
706 
707         newIdx = i;
708     }
709 
710     if(newIdx != NOIDX)
711     {
712         takeTopLevelItem(myIdx);
713         insertTopLevelItem(newIdx, proj);
714     }
715 }
716 
removeDevice(const QString & key)717 void CGisListWks::removeDevice(const QString& key)
718 {
719     CGisListWksEditLock lock(true, IGisItem::mutexItems);
720 
721     for(int i = 0; i < topLevelItemCount(); i++)
722     {
723         IDevice* device = dynamic_cast<IDevice*>(topLevelItem(i));
724         if(device && device->getKey() == key)
725         {
726             delete device;
727             emit sigChanged();
728             return;
729         }
730     }
731 }
732 
hasProject(IGisProject * project)733 bool CGisListWks::hasProject(IGisProject* project)
734 {
735     CGisListWksEditLock lock(true, IGisItem::mutexItems);
736 
737     QString key = project->getKey();
738 
739     for(int i = 0; i < topLevelItemCount(); i++)
740     {
741         IGisProject* item = dynamic_cast<IGisProject*>(topLevelItem(i));
742         if(item && item->getKey() == key && item != project)
743         {
744             return true;
745         }
746     }
747     return false;
748 }
749 
getProjectByKey(const QString & key)750 IGisProject* CGisListWks::getProjectByKey(const QString& key)
751 {
752     CGisListWksEditLock lock(true, IGisItem::mutexItems);
753 
754     for(int i = 0; i < topLevelItemCount(); i++)
755     {
756         IGisProject* item = dynamic_cast<IGisProject*>(topLevelItem(i));
757         if(item && item->getKey() == key)
758         {
759             return item;
760         }
761     }
762     return nullptr;
763 }
764 
getProjectById(quint64 id,const QString & db)765 CDBProject* CGisListWks::getProjectById(quint64 id, const QString& db)
766 {
767     CGisListWksEditLock lock(true, IGisItem::mutexItems);
768 
769     for(int i = 0; i < topLevelItemCount(); i++)
770     {
771         CDBProject* item = dynamic_cast<CDBProject*>(topLevelItem(i));
772         if(item && item->getId() == id && item->getDBName() == db)
773         {
774             return item;
775         }
776     }
777     return nullptr;
778 }
779 
slotSaveWorkspace()780 void CGisListWks::slotSaveWorkspace()
781 {
782     CGisListWksEditLock lock(true, IGisItem::mutexItems);
783 
784     if(!saveOnExit)
785     {
786         return;
787     }
788 
789     QSqlQuery query(db);
790     QUERY_RUN("DELETE FROM workspace", return )
791 
792     qDebug() << "slotSaveWorkspace()";
793 
794     const int total = topLevelItemCount();
795     PROGRESS_SETUP(tr("Saving workspace. Please wait."), 0, total, this);
796 
797     for(int i = 0; i < total; i++)
798     {
799         PROGRESS(i, return );
800 
801         IGisProject* project = dynamic_cast<IGisProject*>(topLevelItem(i));
802         if(nullptr == project)
803         {
804             continue;
805         }
806 
807         QByteArray data;
808         QDataStream stream(&data, QIODevice::WriteOnly);
809         stream.setVersion(QDataStream::Qt_5_2);
810         stream.setByteOrder(QDataStream::LittleEndian);
811 
812         project->IGisProject::operator>>(stream);
813 
814         query.prepare("INSERT INTO workspace (type, keyqms, name, changed, visible, data) VALUES (:type, :keyqms, :name, :changed, :visible, :data)");
815         query.bindValue(":type", project->getType());
816         query.bindValue(":keyqms", project->getKey());
817         query.bindValue(":name", project->getName());
818         query.bindValue(":changed", project->isChanged());
819 
820         bool visible = (project->checkState(CGisListDB::eColumnCheckbox) == Qt::Checked);
821         query.bindValue(":visible", visible);
822         query.bindValue(":data", data);
823         QUERY_EXEC(continue);
824     }
825 
826     query.prepare( "UPDATE userfocus set focus=:focus");
827     query.bindValue(":focus", IGisProject::getUserFocus());
828     QUERY_EXEC();
829 
830     if(saveEvery)
831     {
832         QTimer::singleShot(saveEvery * 60000, this, &CGisListWks::slotSaveWorkspace);
833     }
834 }
835 
slotLoadWorkspace()836 void CGisListWks::slotLoadWorkspace()
837 {
838     CGisListWksEditLock lock(true, IGisItem::mutexItems);
839 
840     QSqlQuery query(db);
841 
842     QUERY_RUN("SELECT type, keyqms, name, changed, visible, data FROM workspace", return )
843 
844     { // open context for progress dialog
845         const int total = query.size();
846         PROGRESS_SETUP(tr("Loading workspace. Please wait."), 0, total, this);
847         quint32 progCnt = 0;
848 
849         while(query.next())
850         {
851             PROGRESS(progCnt++, return );
852 
853             int type = query.value(0).toInt();
854             QString name = query.value(2).toString();
855             bool changed = query.value(3).toBool();
856             Qt::CheckState visible = query.value(4).toBool() ? Qt::Checked : Qt::Unchecked;
857             QByteArray data = query.value(5).toByteArray();
858 
859             QDataStream stream(&data, QIODevice::ReadOnly);
860             stream.setVersion(QDataStream::Qt_5_2);
861             stream.setByteOrder(QDataStream::LittleEndian);
862 
863             IGisProject* project = nullptr;
864             switch(type)
865             {
866             case IGisProject::eTypeQms:
867             {
868                 project = new CQmsProject(name, this);
869                 project->setCheckState(CGisListDB::eColumnCheckbox, visible); // (1a)
870                 *project << stream;
871                 break;
872             }
873 
874             case IGisProject::eTypeQlb:
875             {
876                 project = new CQlbProject(name, this);
877                 project->setCheckState(CGisListDB::eColumnCheckbox, visible); // (1a)
878                 *project << stream;
879                 break;
880             }
881 
882             case IGisProject::eTypeGpx:
883             {
884                 project = new CGpxProject(name, this);
885                 project->setCheckState(CGisListDB::eColumnCheckbox, visible); // (1b)
886                 *project << stream;
887                 break;
888             }
889 
890             case IGisProject::eTypeDb:
891             {
892                 CDBProject* dbProject;
893                 project = dbProject = new CDBProject(this);
894                 project->setCheckState(CGisListDB::eColumnCheckbox, visible); // (1c)
895 
896                 project->IGisProject::operator<<(stream);
897                 dbProject->restoreDBLink();
898 
899                 if(!project->isValid())
900                 {
901                     delete project;
902                     project = nullptr;
903                 }
904                 else
905                 {
906                     dbProject->postStatus(false);
907                 }
908                 break;
909             }
910 
911             case IGisProject::eTypeSlf:
912             {
913                 project = new CSlfProject(name, false);
914                 project->setCheckState(CGisListDB::eColumnCheckbox, visible); // (1d)
915                 *project << stream;
916 
917                 // the CSlfProject does not - as the other C*Project - register itself in the list
918                 // of currently opened projects. This is done manually here.
919                 addProject(project);
920                 break;
921             }
922 
923             case IGisProject::eTypeFit:
924             {
925                 project = new CFitProject(name, this);
926                 project->setCheckState(CGisListDB::eColumnCheckbox, visible);
927                 *project << stream;
928                 break;
929             }
930 
931             case IGisProject::eTypeTcx:
932             {
933                 project = new CTcxProject(name, this);
934                 project->setCheckState(CGisListDB::eColumnCheckbox, visible);
935                 *project << stream;
936                 break;
937             }
938 
939             case IGisProject::eTypeSml:
940             {
941                 project = new CSmlProject(name, this);
942                 project->setCheckState(CGisListDB::eColumnCheckbox, visible);
943                 *project << stream;
944                 break;
945             }
946 
947             case IGisProject::eTypeLog:
948             {
949                 project = new CSmlProject(name, this);
950                 project->setCheckState(CGisListDB::eColumnCheckbox, visible);
951                 *project << stream;
952                 break;
953             }
954             }
955 
956             if(nullptr != project)
957             {
958                 // Hiding the individual projects from the map (1a, 1b, 1c) could be done here within a single statement,
959                 // but this results in a visible `the checkbox is being unchecked`, especially in case the project
960                 // is large and takes some time to load.
961                 // When done directly after construction there is no `blinking` of the check mark
962 
963                 project->setToolTip(eColumnName, project->getInfo());
964                 if(changed)
965                 {
966                     project->setChanged();
967                 }
968             }
969         }
970     } // close context for progress dialog
971 
972     slotGeoSearch(static_cast<QAction*>(CMainWindow::self().findChild<QAction*>("actionGeoSearch"))->isChecked());
973 
974     for(const QString& filename : qlOpts->arguments)
975     {
976         CGisWorkspace::self().loadGisProject(filename);
977     }
978 
979     QUERY_RUN("SELECT focus FROM userfocus", );
980     if(query.next())
981     {
982         QString key = query.value(0).toString();
983         IGisProject* project = getProjectByKey(key);
984         if(project != nullptr)
985         {
986             project->gainUserFocus(true);
987         }
988     }
989 
990     emit sigChanged();
991 }
992 
showMenuProjectWks(const QPoint & p)993 void CGisListWks::showMenuProjectWks(const QPoint& p)
994 {
995     QMenu menu(this);
996     menu.addAction(actionEditPrj);
997     menu.addAction(actionCopyPrj);
998     menu.addAction(actionShowOnMap);
999     menu.addAction(actionHideFrMap);
1000     menu.addSeparator();
1001     menu.addAction(actionSortByTime);
1002     menu.addAction(actionSortByName);
1003     menu.addAction(actionSortByRating);
1004     menu.addAction(actionFilterProject);
1005     menu.addSeparator();
1006     menu.addAction(actionAutoSave);
1007     menu.addAction(actionUserFocusPrj);
1008     menu.addSeparator();
1009     menu.addAction(actionSave);
1010     menu.addAction(actionSaveAs);
1011     menu.addAction(actionSaveAsStrict);
1012     menu.addSeparator();
1013     menu.addAction(actionSyncWksDev);
1014     menu.addAction(actionAutoSyncToDev);
1015     menu.addAction(actionSyncDB);
1016     menu.addSeparator();
1017     menu.addAction(actionCloseProj);
1018     menu.exec(p);
1019 }
1020 
showMenuProjectDev(const QPoint & p)1021 void CGisListWks::showMenuProjectDev(const QPoint& p)
1022 {
1023     QMenu menu(this);
1024     menu.addAction(actionEditPrj);
1025     menu.addAction(actionCopyPrj);
1026     menu.addAction(actionShowOnMap);
1027     menu.addAction(actionHideFrMap);
1028     menu.addSeparator();
1029     menu.addAction(actionSyncDevWks);
1030     menu.addSeparator();
1031     menu.addAction(actionDelProj);
1032     menu.exec(p);
1033 }
1034 
showMenuProjectTrash(const QPoint & p)1035 void CGisListWks::showMenuProjectTrash(const QPoint& p)
1036 {
1037     QMenu menu(this);
1038     menu.addAction(actionSaveAs);
1039     menu.addAction(actionSaveAsStrict);
1040     menu.addAction(actionCloseProj);
1041     menu.exec(p);
1042 }
1043 
showMenuItemTrk(const QPoint & p,const IGisItem::key_t & key)1044 void CGisListWks::showMenuItemTrk(const QPoint& p, const IGisItem::key_t& key)
1045 {
1046     CGisWorkspace::self().slotWksItemSelectionReset();
1047 
1048     QMenu menu(this);
1049     menu.addAction(actionEditDetails);
1050     menu.addAction(actionTagItem);
1051     menu.addAction(actionCopyItem);
1052     menu.addSeparator();
1053     menu.addAction(actionFocusTrk);
1054     menu.addAction(actionRangeTrk);
1055     menu.addAction(actionEditTrk);
1056     menu.addAction(actionReverseTrk);
1057     menu.addAction(actionCombineTrk);
1058     menu.addMenu(CActivityTrk::getMenu(key, &menu));
1059     menu.addMenu(IGisItem::getColorMenu(tr("Set Track Color"), this, SLOT(slotColorTrk()), &menu));
1060     menu.addAction(actionEleWptTrk);
1061     menu.addAction(actionCopyTrkWithWpt);
1062     menu.addAction(actionToRoute);
1063     menu.addAction(actionNogoTrk);
1064     menu.addSeparator();
1065     menu.addAction(actionDelete);
1066     menu.exec(p);
1067 }
1068 
showMenuItemWpt(const QPoint & p,CGisItemWpt * wpt)1069 void CGisListWks::showMenuItemWpt(const QPoint& p, CGisItemWpt* wpt)
1070 {
1071     CGisWorkspace::self().slotWksItemSelectionReset();
1072 
1073     QMenu menu(this);
1074     menu.addAction(actionEditDetails);
1075     menu.addAction(actionTagItem);
1076     menu.addAction(actionCopyItem);
1077     menu.addSeparator();
1078     menu.addAction(actionBubbleWpt);
1079     menu.addAction(actionMoveWpt);
1080     menu.addAction(actionProjWpt);
1081     menu.addAction(actionEleWptTrk);
1082     menu.addSeparator();
1083     menu.addAction(actionEditRadiusWpt);
1084     menu.addAction(actionDelRadiusWpt);
1085     menu.addAction(actionNogoWpt);
1086     menu.addSeparator();
1087     menu.addMenu(CGeoSearchWeb::self().getMenu(wpt->getPosition(), &menu));
1088     menu.addAction(actionCopyCoordWpt);
1089     menu.addSeparator();
1090     menu.addAction(actionDelete);
1091     menu.exec(p);
1092 }
1093 
showMenuItemRte(const QPoint & p)1094 void CGisListWks::showMenuItemRte(const QPoint& p)
1095 {
1096     CGisWorkspace::self().slotWksItemSelectionReset();
1097 
1098     QMenu menu(this);
1099     menu.addAction(actionEditDetails);
1100     menu.addAction(actionTagItem);
1101     menu.addAction(actionCopyItem);
1102     menu.addSeparator();
1103     menu.addAction(actionFocusRte);
1104     menu.addAction(actionCalcRte);
1105     menu.addAction(actionResetRte);
1106     menu.addAction(actionEditRte);
1107     menu.addAction(actionReverseRte);
1108     menu.addAction(actionRte2Trk);
1109     menu.addAction(actionNogoRte);
1110     menu.addSeparator();
1111     menu.addAction(actionDelete);
1112     menu.exec(p);
1113 }
1114 
showMenuItemOvl(const QPoint & p)1115 void CGisListWks::showMenuItemOvl(const QPoint& p)
1116 {
1117     CGisWorkspace::self().slotWksItemSelectionReset();
1118 
1119     QMenu menu(this);
1120     menu.addAction(actionEditDetails);
1121     menu.addAction(actionTagItem);
1122     menu.addAction(actionCopyItem);
1123     menu.addSeparator();
1124     menu.addAction(actionEditArea);
1125     menu.addAction(actionNogoArea);
1126     menu.addSeparator();
1127     menu.addAction(actionDelete);
1128     menu.exec(p);
1129 }
1130 
showMenuItem(const QPoint & p,const QList<IGisItem::key_t> & keysTrks,const QList<IGisItem::key_t> & keysWpts)1131 void CGisListWks::showMenuItem(const QPoint& p, const QList<IGisItem::key_t>& keysTrks, const QList<IGisItem::key_t>& keysWpts)
1132 {
1133     CGisWorkspace::self().slotWksItemSelectionReset();
1134     QAction* action;
1135 
1136     QMenu menu(this);
1137     menu.addAction(actionTagItem);
1138     menu.addAction(actionCopyItem);
1139     menu.addSection(tr("Waypoints"));
1140     menu.addAction(actionRteFromWpt);
1141     menu.addAction(actionEditPrxWpt);
1142     action = menu.addMenu(CWptIconManager::self().getWptIconMenu(tr("Change Icon"), this, SLOT(slotSymWpt()), &menu));
1143     action->setEnabled(!keysWpts.isEmpty());
1144     menu.addSection(tr("Wayp. & Tracks"));
1145     menu.addAction(actionEleWptTrk);
1146     menu.addSection(tr("Tracks"));
1147     menu.addAction(actionCombineTrk);
1148     action = menu.addMenu(CActivityTrk::getMenu(keysTrks, &menu));
1149     action->setEnabled(!keysTrks.isEmpty());
1150     action = menu.addMenu(IGisItem::getColorMenu(tr("Set Track Color"), this, SLOT(slotColorTrk()), &menu));
1151     action->setEnabled(!keysTrks.isEmpty());
1152     menu.addSeparator();
1153     menu.addAction(actionDelete);
1154     menu.exec(p);
1155 }
1156 
slotContextMenu(const QPoint & point)1157 void CGisListWks::slotContextMenu(const QPoint& point)
1158 {
1159     QPoint p = mapToGlobal(point);
1160     if(selectedItems().isEmpty() && menuNone)
1161     {
1162         menuNone->exec(p);
1163         return;
1164     }
1165 
1166     // check whether all projects are checked or unchecked...
1167     bool allChecked = true;
1168     bool allUnchecked = true;
1169     bool allCantSave = true;
1170 
1171     const QList<QTreeWidgetItem*>& items = selectedItems();
1172     for(QTreeWidgetItem* item : items)
1173     {
1174         IGisProject* project = dynamic_cast<IGisProject*>(item);
1175         if(nullptr != project)
1176         {
1177             // as soon as we find an unchecked element, not all elements are checked (and vice versa)
1178             if(project->checkState(CGisListDB::eColumnCheckbox) == Qt::Unchecked)
1179             {
1180                 allChecked = false;
1181             }
1182             else
1183             {
1184                 allUnchecked = false;
1185             }
1186 
1187             if(project->canSave())
1188             {
1189                 allCantSave = false;
1190             }
1191         }
1192     }
1193 
1194     // ...and disable entries without any effect
1195     actionShowOnMap->setEnabled(!allChecked);
1196     actionHideFrMap->setEnabled(!allUnchecked);
1197     actionSave->setEnabled(!allCantSave);
1198 
1199     if(selectedItems().count() > 1)
1200     {
1201         IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
1202         if(nullptr != project)
1203         {
1204             if(project->isOnDevice())
1205             {
1206                 showMenuProjectDev(p);
1207             }
1208             else
1209             {
1210                 actionGroupSort->setEnabled(false);
1211                 actionFilterProject->setEnabled(false);
1212                 actionSyncWksDev->setEnabled(IDevice::count());
1213                 actionAutoSyncToDev->setVisible(false);
1214                 actionSyncDB->setEnabled(project->getType() == IGisProject::eTypeDb);
1215                 actionAutoSave->setVisible(false);
1216                 actionUserFocusPrj->setVisible(false);
1217                 showMenuProjectWks(p);
1218             }
1219             return;
1220         }
1221 
1222         IGisItem* gisItem = dynamic_cast<IGisItem*>(currentItem());
1223         if(nullptr != gisItem)
1224         {
1225             QList<IGisItem::key_t> keysTrk;
1226             QList<IGisItem::key_t> keysWpt;
1227 
1228             const QList<QTreeWidgetItem*>& items = selectedItems();
1229             for(QTreeWidgetItem* item : items)
1230             {
1231                 CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(item);
1232                 if(trk != nullptr)
1233                 {
1234                     keysTrk << trk->getKey();
1235                 }
1236 
1237                 CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(item);
1238                 if(wpt != nullptr)
1239                 {
1240                     keysWpt << wpt->getKey();
1241                 }
1242             }
1243 
1244             bool hasWpts = !keysWpt.isEmpty();
1245             bool hasTrks = !keysTrk.isEmpty();
1246 
1247             actionRteFromWpt->setEnabled(keysWpt.count() > 1);
1248             actionEditPrxWpt->setEnabled(hasWpts);
1249             actionCombineTrk->setEnabled(keysTrk.count() > 1);
1250             actionEleWptTrk->setEnabled(hasWpts | hasTrks);
1251             showMenuItem(p, keysTrk, keysWpt);
1252             return;
1253         }
1254         return;
1255     }
1256 
1257     if(selectedItems().count() == 1)
1258     {
1259         IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
1260         if(nullptr != project)
1261         {
1262             if(project->getType() == IGisProject::eTypeLostFound)
1263             {
1264                 showMenuProjectTrash(p);
1265             }
1266             else
1267             {
1268                 if(project->isOnDevice())
1269                 {
1270                     showMenuProjectDev(p);
1271                 }
1272                 else
1273                 {
1274                     actionGroupSort->setEnabled(true);
1275 
1276                     bool autoSyncToDev = project->doAutoSyncToDevice();
1277                     actionAutoSyncToDev->setVisible(true);
1278                     actionAutoSyncToDev->setChecked(autoSyncToDev);
1279 
1280                     actionSyncWksDev->setEnabled(IDevice::count() && !autoSyncToDev);
1281                     actionSyncDB->setEnabled(project->getType() == IGisProject::eTypeDb);
1282 
1283                     blockSorting = true;
1284                     switch(project->getSortingFolder())
1285                     {
1286                     case IGisProject::eSortFolderName:
1287                         actionSortByName->setChecked(true);
1288                         break;
1289 
1290                     case IGisProject::eSortFolderTime:
1291                         actionSortByTime->setChecked(true);
1292                         break;
1293 
1294                     case IGisProject::eSortFolderRating:
1295                         actionSortByRating->setChecked(true);
1296                         break;
1297                     }
1298 
1299                     blockSorting = false;
1300 
1301                     actionFilterProject->setEnabled(true);
1302                     actionFilterProject->setChecked(project->getProjectFilterItem() != nullptr);
1303 
1304                     bool hasUserFocus = project->hasUserFocus();
1305 
1306                     actionAutoSave->setVisible(true);
1307                     actionAutoSave->setEnabled(project->canSave());
1308                     actionAutoSave->setChecked(project->isAutoSave());
1309                     actionUserFocusPrj->setVisible(true);
1310                     actionUserFocusPrj->setChecked(hasUserFocus);
1311                     const QIcon& icon = hasUserFocus ? QIcon("://icons/32x32/Focus.png") : QIcon("://icons/32x32/UnFocus.png");
1312                     actionUserFocusPrj->setIcon(icon);
1313                     showMenuProjectWks(p);
1314                 }
1315             }
1316             return;
1317         }
1318 
1319         IGisItem* gisItem = dynamic_cast<IGisItem*>(currentItem());
1320         if(nullptr != gisItem)
1321         {
1322             bool isOnDevice = gisItem->isOnDevice();
1323             IGisProject* project = gisItem->getParentProject();
1324             bool isProjectVisible = project == nullptr ? false : project->isVisible();
1325 
1326             switch(gisItem->type())
1327             {
1328             case IGisItem::eTypeTrk:
1329             {
1330                 CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(gisItem);
1331                 if(trk == nullptr)
1332                 {
1333                     break;
1334                 }
1335 
1336                 if(project != nullptr)
1337                 {
1338                     actionCombineTrk->setEnabled(project->getItemCountByType(IGisItem::eTypeTrk) > 1);
1339                 }
1340                 else
1341                 {
1342                     actionCombineTrk->setEnabled(false);
1343                 }
1344                 actionRangeTrk->setEnabled(isProjectVisible && !isOnDevice);
1345                 actionReverseTrk->setDisabled(isOnDevice);
1346                 actionEditTrk->setEnabled(isProjectVisible && !isOnDevice);
1347                 actionNogoTrk->setEnabled(isProjectVisible);
1348                 actionNogoTrk->setChecked(gisItem->isNogo());
1349                 actionCopyTrkWithWpt->setEnabled(trk->getNumberOfAttachedWpt() != 0);
1350                 actionFocusTrk->setChecked(gisItem->hasUserFocus());
1351                 actionFocusTrk->setEnabled(isProjectVisible);
1352                 showMenuItemTrk(p, trk->getKey());
1353                 break;
1354             }
1355 
1356             case IGisItem::eTypeWpt:
1357             {
1358                 CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(gisItem);
1359                 if(wpt == nullptr)
1360                 {
1361                     break;
1362                 }
1363 
1364                 actionBubbleWpt->setChecked(wpt->hasBubble());
1365                 actionBubbleWpt->setEnabled(isProjectVisible);
1366                 actionEditRadiusWpt->setEnabled(isProjectVisible);
1367                 bool radius = wpt->hasRadius();
1368                 actionDelRadiusWpt->setEnabled(isProjectVisible && radius);
1369                 actionNogoWpt->setEnabled(isProjectVisible && radius);
1370                 actionNogoWpt->setChecked(radius && wpt->isNogo());
1371                 actionMoveWpt->setEnabled(isProjectVisible && !isOnDevice);
1372                 actionProjWpt->setDisabled(isOnDevice);
1373                 showMenuItemWpt(p, wpt);
1374                 break;
1375             }
1376 
1377             case IGisItem::eTypeRte:
1378             {
1379                 CGisItemRte* rte = dynamic_cast<CGisItemRte*>(gisItem);
1380                 if(rte == nullptr)
1381                 {
1382                     break;
1383                 }
1384 
1385                 actionFocusRte->setChecked(rte->hasUserFocus());
1386                 actionFocusRte->setEnabled(isProjectVisible && rte->isCalculated());
1387                 actionCalcRte->setEnabled(isProjectVisible);
1388                 actionEditRte->setEnabled(isProjectVisible);
1389                 actionNogoRte->setEnabled(isProjectVisible);
1390                 actionNogoRte->setChecked(gisItem->isNogo());
1391                 actionResetRte->setEnabled(isProjectVisible);
1392                 showMenuItemRte(p);
1393                 break;
1394             }
1395 
1396             case IGisItem::eTypeOvl:
1397                 actionEditArea->setEnabled(isProjectVisible && !isOnDevice);
1398                 actionNogoArea->setEnabled(isProjectVisible);
1399                 actionNogoArea->setChecked(gisItem->isNogo());
1400                 showMenuItemOvl(p);
1401                 break;
1402             }
1403 
1404             return;
1405         }
1406     }
1407 }
1408 
setVisibilityOnMap(bool visible)1409 void CGisListWks::setVisibilityOnMap(bool visible)
1410 {
1411     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1412     const QList<QTreeWidgetItem*>& items = selectedItems();
1413     for(QTreeWidgetItem* item : items)
1414     {
1415         IGisProject* project = dynamic_cast<IGisProject*>(item);
1416         if(nullptr != project)
1417         {
1418             project->setCheckState(CGisListDB::eColumnCheckbox, visible ? Qt::Checked : Qt::Unchecked);
1419         }
1420     }
1421     emit sigChanged();
1422 }
1423 
slotShowOnMap()1424 void CGisListWks::slotShowOnMap()
1425 {
1426     setVisibilityOnMap(true);
1427 }
1428 
slotHideFrMap()1429 void CGisListWks::slotHideFrMap()
1430 {
1431     setVisibilityOnMap(false);
1432 }
1433 
closeProjects(const QList<QTreeWidgetItem * > & items)1434 static void closeProjects(const QList<QTreeWidgetItem*>& items)
1435 {
1436     for(QTreeWidgetItem* item : items)
1437     {
1438         IGisProject* project = dynamic_cast<IGisProject*>(item);
1439         if(nullptr != project)
1440         {
1441             if(project->doAutoSyncToDevice())
1442             {
1443                 continue;
1444             }
1445 
1446             if(project->askBeforClose())
1447             {
1448                 break;
1449             }
1450 
1451             if(IGisProject::eTypeGeoSearch == project->getType())
1452             {
1453                 CMainWindow::self().findChild<QAction*>("actionGeoSearch")->setChecked(false);
1454             }
1455             delete project;
1456         }
1457     }
1458 }
1459 
slotCloseProject()1460 void CGisListWks::slotCloseProject()
1461 {
1462     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1463 
1464     closeProjects(selectedItems());
1465     emit sigChanged();
1466 }
1467 
slotCloseAllProjects()1468 void CGisListWks::slotCloseAllProjects()
1469 {
1470     int res = QMessageBox::question(this, tr("Close all projects..."), tr("This will remove all projects from the workspace."), QMessageBox::Ok | QMessageBox::Abort, QMessageBox::Ok);
1471     if(res != QMessageBox::Ok)
1472     {
1473         return;
1474     }
1475 
1476     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1477     closeProjects(findItems("*", Qt::MatchWildcard));
1478 
1479     CGisWorkspace::self().slotWksItemSelectionReset();
1480 
1481     emit sigChanged();
1482 }
1483 
1484 
slotDeleteProject()1485 void CGisListWks::slotDeleteProject()
1486 {
1487     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1488 
1489     const QList<QTreeWidgetItem*>& items = selectedItems();
1490     for(QTreeWidgetItem* item : items)
1491     {
1492         IGisProject* project = dynamic_cast<IGisProject*>(item);
1493         if(nullptr != project)
1494         {
1495             {
1496                 CCanvasCursorLock cursorLock(Qt::ArrowCursor, __func__);
1497                 int res = QMessageBox::question(CMainWindow::getBestWidgetForParent(), tr("Delete project..."), tr("Do you really want to delete %1?").arg(project->getFilename()), QMessageBox::Ok | QMessageBox::No, QMessageBox::Ok);
1498 
1499                 if(res != QMessageBox::Ok)
1500                 {
1501                     continue;
1502                 }
1503             }
1504 
1505 
1506             if(project->remove())
1507             {
1508                 delete project;
1509             }
1510         }
1511     }
1512     emit sigChanged();
1513 }
1514 
slotSaveProject()1515 void CGisListWks::slotSaveProject()
1516 {
1517     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1518 
1519     const QList<QTreeWidgetItem*>& items = selectedItems();
1520     for(QTreeWidgetItem* item : items)
1521     {
1522         IGisProject* project = dynamic_cast<IGisProject*>(item);
1523         if(nullptr != project)
1524         {
1525             if(project->canSave())
1526             {
1527                 project->save();
1528             }
1529             else
1530             {
1531                 project->saveAs();
1532             }
1533         }
1534     }
1535 }
1536 
slotSaveAsProject()1537 void CGisListWks::slotSaveAsProject()
1538 {
1539     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1540 
1541     const QList<QTreeWidgetItem*>& items = selectedItems();
1542     for(QTreeWidgetItem* item : items)
1543     {
1544         IGisProject* project = dynamic_cast<IGisProject*>(item);
1545         if(nullptr != project)
1546         {
1547             project->saveAs();
1548         }
1549     }
1550 }
1551 
slotSaveAsStrictGpx11Project()1552 void CGisListWks::slotSaveAsStrictGpx11Project()
1553 {
1554     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1555 
1556     const QList<QTreeWidgetItem*>& items = selectedItems();
1557     for(QTreeWidgetItem* item : items)
1558     {
1559         IGisProject* project = dynamic_cast<IGisProject*>(item);
1560         if(nullptr != project)
1561         {
1562             project->saveAsStrictGpx11();
1563         }
1564     }
1565 }
1566 
slotAutoSaveProject(bool on)1567 void CGisListWks::slotAutoSaveProject(bool on)
1568 {
1569     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1570 
1571     IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
1572     if(project != nullptr)
1573     {
1574         project->setAutoSave(on);
1575     }
1576 }
1577 
slotUserFocusPrj(bool yes)1578 void CGisListWks::slotUserFocusPrj(bool yes)
1579 {
1580     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1581 
1582     const int N = topLevelItemCount();
1583     for(int n = 0; n < N; n++)
1584     {
1585         IGisProject* project = dynamic_cast<IGisProject*>(topLevelItem(n));
1586         if(project != nullptr)
1587         {
1588             project->gainUserFocus(false);
1589         }
1590     }
1591 
1592     IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
1593     if(project != nullptr)
1594     {
1595         project->gainUserFocus(yes);
1596     }
1597 }
1598 
slotAutoSyncProject(bool yes)1599 void CGisListWks::slotAutoSyncProject(bool yes)
1600 {
1601     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1602 
1603     IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
1604     if(project != nullptr)
1605     {
1606         project->setAutoSyncToDevice(yes);
1607         if(yes)
1608         {
1609             syncPrjToDevices(project, getAllDeviceKeys());
1610         }
1611     }
1612 }
1613 
slotEditPrj()1614 void CGisListWks::slotEditPrj()
1615 {
1616     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1617 
1618     IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
1619     if(project != nullptr)
1620     {
1621         project->edit();
1622     }
1623 }
1624 
slotItemDoubleClicked(QTreeWidgetItem * item,int)1625 void CGisListWks::slotItemDoubleClicked(QTreeWidgetItem* item, int )
1626 {
1627     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1628 
1629     IGisItem* gisItem = dynamic_cast<IGisItem*>(item);
1630     if(gisItem != nullptr)
1631     {
1632         CGisWorkspace::self().slotWksItemSelectionReset();
1633         IGisProject* project = gisItem->getParentProject();
1634         if (project != nullptr && project->isVisible())
1635         {
1636             CMainWindow::self().resetMouse();
1637             CMainWindow::self().zoomCanvasTo(gisItem->getBoundingRect());
1638             CGisWorkspace::self().focusTrkByKey(true, gisItem->getKey());
1639         }
1640     }
1641 }
1642 
slotItemChanged(QTreeWidgetItem *,int column)1643 void CGisListWks::slotItemChanged(QTreeWidgetItem* /*item*/, int column)
1644 {
1645     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1646 
1647     if(column == eColumnCheckBox)
1648     {
1649         CGisWorkspace::self().slotWksItemSelectionReset();
1650         emit sigChanged();
1651     }
1652 }
1653 
slotEditItem()1654 void CGisListWks::slotEditItem()
1655 {
1656     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1657 
1658     IGisItem* gisItem = dynamic_cast<IGisItem*>(currentItem());
1659     if(gisItem != nullptr)
1660     {
1661         CGisWorkspace::self().editItemByKey(gisItem->getKey());
1662     }
1663 }
1664 
slotTagItem()1665 void CGisListWks::slotTagItem()
1666 {
1667     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1668     CGisWorkspace::self().tagItemsByKey(selectedItems2Keys<IGisItem>());
1669 }
1670 
slotDeleteItem()1671 void CGisListWks::slotDeleteItem()
1672 {
1673     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1674     CGisWorkspace::self().delItemsByKey(selectedItems2Keys<IGisItem>());
1675 }
1676 
slotCopyItem()1677 void CGisListWks::slotCopyItem()
1678 {
1679     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1680 
1681     /*
1682      * Item selection is reset when the target project is a new database
1683      * project. Additionally the list of selected items pointers seems
1684      * to get invalid, causing a segfault when used.
1685      *
1686      * As a fix the keys of the selected items are stored temporarily and
1687      * later used to retrieve the item on the workspace via CGisWorkspace::getItemByKey()
1688      * again. This is always safe.
1689      */
1690     CGisWorkspace::self().copyItemsByKey(selectedItems2Keys<IGisItem>());
1691 }
1692 
slotProjWpt()1693 void CGisListWks::slotProjWpt()
1694 {
1695     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1696 
1697     CGisItemWpt* gisItem = dynamic_cast<CGisItemWpt*>(currentItem());
1698     if(gisItem != nullptr)
1699     {
1700         CGisWorkspace::self().projWptByKey(gisItem->getKey());
1701     }
1702 }
1703 
slotBubbleWpt()1704 void CGisListWks::slotBubbleWpt()
1705 {
1706     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1707 
1708     CGisItemWpt* gisItem = dynamic_cast<CGisItemWpt*>(currentItem());
1709     if(gisItem != nullptr)
1710     {
1711         CGisWorkspace::self().toggleWptBubble(gisItem->getKey());
1712     }
1713 }
1714 
slotNogoItem()1715 void CGisListWks::slotNogoItem()
1716 {
1717     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1718 
1719     IGisItem* gisItem = dynamic_cast<IGisItem*>(currentItem());
1720     if(gisItem != nullptr)
1721     {
1722         CGisWorkspace::self().toggleNogoItem(gisItem->getKey());
1723     }
1724 }
1725 
slotDelRadiusWpt()1726 void CGisListWks::slotDelRadiusWpt()
1727 {
1728     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1729 
1730     CGisItemWpt* gisItem = dynamic_cast<CGisItemWpt*>(currentItem());
1731     if(gisItem != nullptr)
1732     {
1733         CGisWorkspace::self().deleteWptRadius(gisItem->getKey());
1734     }
1735 }
1736 
slotEditRadiusWpt()1737 void CGisListWks::slotEditRadiusWpt()
1738 {
1739     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1740 
1741     CGisItemWpt* gisItem = dynamic_cast<CGisItemWpt*>(currentItem());
1742     if(gisItem != nullptr)
1743     {
1744         CGisWorkspace::self().editWptRadius(gisItem->getKey());
1745     }
1746 }
1747 
slotMoveWpt()1748 void CGisListWks::slotMoveWpt()
1749 {
1750     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1751 
1752     CGisItemWpt* gisItem = dynamic_cast<CGisItemWpt*>(currentItem());
1753     if(gisItem != nullptr)
1754     {
1755         CGisWorkspace::self().moveWptByKey(gisItem->getKey());
1756     }
1757 }
1758 
slotCopyCoordWpt()1759 void CGisListWks::slotCopyCoordWpt()
1760 {
1761     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1762     CGisItemWpt* gisItem = dynamic_cast<CGisItemWpt*>(currentItem());
1763     if(gisItem != nullptr)
1764     {
1765         CGisWorkspace::self().copyWptCoordByKey(gisItem->getKey());
1766     }
1767 }
1768 
slotFocusTrk(bool on)1769 void CGisListWks::slotFocusTrk(bool on)
1770 {
1771     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1772 
1773     CGisItemTrk* gisItem = dynamic_cast<CGisItemTrk*>(currentItem());
1774     if(gisItem != nullptr)
1775     {
1776         CGisWorkspace::self().focusTrkByKey(on, gisItem->getKey());
1777     }
1778 }
1779 
slotEditTrk()1780 void CGisListWks::slotEditTrk()
1781 {
1782     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1783 
1784     CGisItemTrk* gisItem = dynamic_cast<CGisItemTrk*>(currentItem());
1785     if(gisItem != nullptr)
1786     {
1787         CGisWorkspace::self().editTrkByKey(gisItem->getKey());
1788     }
1789 }
1790 
slotReverseTrk()1791 void CGisListWks::slotReverseTrk()
1792 {
1793     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1794 
1795     CGisItemTrk* gisItem = dynamic_cast<CGisItemTrk*>(currentItem());
1796     if(gisItem != nullptr)
1797     {
1798         CGisWorkspace::self().reverseTrkByKey(gisItem->getKey());
1799     }
1800 }
1801 
slotCombineTrk()1802 void CGisListWks::slotCombineTrk()
1803 {
1804     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1805 
1806     const QList<IGisItem::key_t>& keys = selectedItems2Keys<CGisItemTrk>();
1807 
1808     if(!keys.isEmpty())
1809     {
1810         if(keys.size() == 1)
1811         {
1812             CGisWorkspace::self().combineTrkByKey(keys.first());
1813         }
1814         else
1815         {
1816             CGisWorkspace::self().combineTrkByKey(keys, keys);
1817         }
1818     }
1819 }
1820 
slotActivityTrk(trkact_t act)1821 void CGisListWks::slotActivityTrk(trkact_t act)
1822 {
1823     if(CTrackData::trkpt_t::eAct20Bad != act)
1824     {
1825         CGisListWksEditLock lock(true, IGisItem::mutexItems);
1826         const QList<QTreeWidgetItem*>& items = selectedItems();
1827         for(QTreeWidgetItem* item : items)
1828         {
1829             CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(item);
1830             if(trk)
1831             {
1832                 trk->setActivity(act);
1833             }
1834         }
1835     }
1836 }
1837 
slotColorTrk()1838 void CGisListWks::slotColorTrk()
1839 {
1840     QObject* obj = sender();
1841     bool ok = false;
1842     qint32 colorIdx = obj->property("colorIdx").toInt(&ok);
1843     if(!ok || (colorIdx == NOIDX))
1844     {
1845         return;
1846     }
1847 
1848     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1849     const QList<QTreeWidgetItem*>& items = selectedItems();
1850     for(QTreeWidgetItem* item : items)
1851     {
1852         CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(item);
1853         if(trk)
1854         {
1855             trk->setColor(colorIdx);
1856         }
1857     }
1858 }
1859 
1860 
slotRangeTrk()1861 void CGisListWks::slotRangeTrk()
1862 {
1863     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1864 
1865     CGisItemTrk* gisItem = dynamic_cast<CGisItemTrk*>(currentItem());
1866     if(gisItem != nullptr)
1867     {
1868         CGisWorkspace::self().rangeTrkByKey(gisItem->getKey());
1869     }
1870 }
1871 
slotCopyTrkWithWpt()1872 void CGisListWks::slotCopyTrkWithWpt()
1873 {
1874     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1875 
1876     CGisItemTrk* gisItem = dynamic_cast<CGisItemTrk*>(currentItem());
1877     if(gisItem != nullptr)
1878     {
1879         CGisWorkspace::self().copyTrkWithWptByKey(gisItem->getKey());
1880     }
1881 }
1882 
slotFocusRte(bool on)1883 void CGisListWks::slotFocusRte(bool on)
1884 {
1885     CGisListWksEditLock lock(true, IGisItem::mutexItems);
1886 
1887     CGisItemRte* gisItem = dynamic_cast<CGisItemRte*>(currentItem());
1888     if(gisItem != nullptr)
1889     {
1890         CGisWorkspace::self().focusRteByKey(on, gisItem->getKey());
1891     }
1892 }
1893 
slotCalcRte()1894 void CGisListWks::slotCalcRte()
1895 {
1896     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1897 
1898     CGisItemRte* gisItem = dynamic_cast<CGisItemRte*>(currentItem());
1899     if(gisItem != nullptr)
1900     {
1901         CGisWorkspace::self().calcRteByKey(gisItem->getKey());
1902     }
1903 }
1904 
slotResetRte()1905 void CGisListWks::slotResetRte()
1906 {
1907     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1908 
1909     CGisItemRte* gisItem = dynamic_cast<CGisItemRte*>(currentItem());
1910     if(gisItem != nullptr)
1911     {
1912         CGisWorkspace::self().resetRteByKey(gisItem->getKey());
1913     }
1914 }
1915 
1916 
slotEditRte()1917 void CGisListWks::slotEditRte()
1918 {
1919     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1920 
1921     CGisItemRte* gisItem = dynamic_cast<CGisItemRte*>(currentItem());
1922     if(gisItem != nullptr)
1923     {
1924         CGisWorkspace::self().editRteByKey(gisItem->getKey());
1925     }
1926 }
1927 
slotReverseRte()1928 void CGisListWks::slotReverseRte()
1929 {
1930     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1931 
1932     CGisItemRte* gisItem = dynamic_cast<CGisItemRte*>(currentItem());
1933     if(gisItem != nullptr)
1934     {
1935         CGisWorkspace::self().reverseRteByKey(gisItem->getKey());
1936     }
1937 }
1938 
slotRte2Trk()1939 void CGisListWks::slotRte2Trk()
1940 {
1941     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1942 
1943     CGisItemRte* gisItem = dynamic_cast<CGisItemRte*>(currentItem());
1944     if(gisItem != nullptr)
1945     {
1946         CGisWorkspace::self().convertRouteToTrack(gisItem->getKey());
1947     }
1948 }
1949 
slotEditArea()1950 void CGisListWks::slotEditArea()
1951 {
1952     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1953 
1954     CGisItemOvlArea* gisItem = dynamic_cast<CGisItemOvlArea*>(currentItem());
1955     if(gisItem != nullptr)
1956     {
1957         CGisWorkspace::self().editAreaByKey(gisItem->getKey());
1958     }
1959 }
1960 
slotAddEmptyProject()1961 void CGisListWks::slotAddEmptyProject()
1962 {
1963     CGisListWksEditLock lock(false, IGisItem::mutexItems);
1964 
1965     QString key, name;
1966     IGisProject::type_e type;
1967     CSelectProjectDialog dlg(key, name, type, nullptr);
1968     if(dlg.exec() == QDialog::Rejected)
1969     {
1970         return;
1971     }
1972     if(name.isEmpty() && (type != IGisProject::eTypeDb))
1973     {
1974         return;
1975     }
1976 
1977     if(type == IGisProject::eTypeGpx)
1978     {
1979         new CGpxProject(name, this);
1980     }
1981     else if(type == IGisProject::eTypeQms)
1982     {
1983         new CQmsProject(name, this);
1984     }
1985     else if(type == IGisProject::eTypeDb)
1986     {
1987         QList<quint64> ids;
1988         QString db;
1989         QString host;
1990         IDBFolder::type_e type;
1991 
1992 
1993         CSelectDBFolder dlg1(ids, db, host, this);
1994         if((dlg1.exec() == QDialog::Rejected) || ids.isEmpty())
1995         {
1996             return;
1997         }
1998 
1999         CSetupFolder dlg2(type, name, false, this);
2000         if(dlg2.exec() == QDialog::Rejected)
2001         {
2002             return;
2003         }
2004 
2005         CEvtW2DCreate* evt = new CEvtW2DCreate(name, type, ids[0], db, host);
2006         CGisDatabase::self().postEventForDb(evt);
2007     }
2008 }
2009 
slotGeoSearch(bool on)2010 void CGisListWks::slotGeoSearch(bool on)
2011 {
2012     CGisListWksEditLock lock(true, IGisItem::mutexItems);
2013 
2014     delete geoSearch;
2015     if(on)
2016     {
2017         geoSearch = new CGeoSearch(this);
2018     }
2019 
2020     CCanvas::triggerCompleteUpdate(CCanvas::eRedrawGis);
2021 }
2022 
slotSyncWksDev()2023 void CGisListWks::slotSyncWksDev()
2024 {
2025     CGisListWksEditLock lock(true, IGisItem::mutexItems);
2026 
2027     if(IDevice::count() == 0)
2028     {
2029         return;
2030     }
2031 
2032     IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
2033     if(nullptr == project)
2034     {
2035         return;
2036     }
2037 
2038     const int N = topLevelItemCount();
2039     QSet<QString> keys;
2040     if(IDevice::count() > 1)
2041     {
2042         CSelDevices dlg(project, this);
2043         if(dlg.exec() != QDialog::Accepted)
2044         {
2045             return;
2046         }
2047         dlg.getSlectedDevices(keys);
2048     }
2049     else
2050     {
2051         for(int n = 0; n < N; n++)
2052         {
2053             IDevice* device = dynamic_cast<IDevice*>(topLevelItem(n));
2054             if(nullptr != device)
2055             {
2056                 keys << device->getKey();
2057                 break;
2058             }
2059         }
2060     }
2061     syncPrjToDevices(project, keys);
2062 }
2063 
slotSyncDevWks()2064 void CGisListWks::slotSyncDevWks()
2065 {
2066     CGisListWksEditLock lock(true, IGisItem::mutexItems);
2067 
2068     IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
2069     if(nullptr == project)
2070     {
2071         return;
2072     }
2073 
2074     IDevice* device = dynamic_cast<IDevice*>(project->parent());
2075     if(nullptr == device)
2076     {
2077         return;
2078     }
2079 
2080     QString key = project->getKey();
2081 
2082     project = getProjectByKey(key);
2083     if(project)
2084     {
2085         CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
2086         if(canvas)
2087         {
2088             canvas->reportStatus("device", tr("<b>Update devices</b><p>Update %1<br/>Please wait...</p>").arg(device->text(CGisListWks::eColumnName)));
2089             canvas->update();
2090             qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
2091         }
2092 
2093         device->updateProject(project);
2094 
2095         if(canvas)
2096         {
2097             canvas->reportStatus("device", "");
2098         }
2099         emit sigChanged();
2100     }
2101 }
2102 
slotAddProjectFilter()2103 void CGisListWks::slotAddProjectFilter()
2104 {
2105     CGisListWksEditLock lock(true, IGisItem::mutexItems);
2106 
2107     //Since we only allow one Item to be selected at a time
2108     IGisProject* project = dynamic_cast<IGisProject*>(selectedItems()[0]);
2109     if(project != nullptr)
2110     {
2111         project->filterProject(actionFilterProject->isChecked());
2112     }
2113 }
2114 
slotNewDevice()2115 void CGisListWks::slotNewDevice()
2116 {
2117     QTimer::singleShot(200, this, &CGisListWks::slotSyncPrjToDevices);
2118 }
2119 
slotSyncPrjToDevices()2120 void CGisListWks::slotSyncPrjToDevices()
2121 {
2122     CGisListWksEditLock lock(true, IGisItem::mutexItems);
2123 
2124     const QSet<QString>& keys = getAllDeviceKeys();
2125     const int N = topLevelItemCount();
2126     for(int n = 0; n < N; n++)
2127     {
2128         IGisProject* project = dynamic_cast<IGisProject*>(topLevelItem(n));
2129         if(project && project->doAutoSyncToDevice())
2130         {
2131             syncPrjToDevices(project, keys);
2132         }
2133     }
2134 }
2135 
getAllDeviceKeys() const2136 QSet<QString> CGisListWks::getAllDeviceKeys() const
2137 {
2138     const int N = topLevelItemCount();
2139     QSet<QString> keys;
2140     for(int n = 0; n < N; n++)
2141     {
2142         IDevice* device = dynamic_cast<IDevice*>(topLevelItem(n));
2143         if(nullptr != device)
2144         {
2145             keys << device->getKey();
2146         }
2147     }
2148     return keys;
2149 }
2150 
syncPrjToDevices(IGisProject * project,const QSet<QString> & keys)2151 void CGisListWks::syncPrjToDevices(IGisProject* project, const QSet<QString>& keys)
2152 {
2153     const int N = topLevelItemCount();
2154     CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
2155     for(int n = 0; n < N; n++)
2156     {
2157         IDevice* device = dynamic_cast<IDevice*>(topLevelItem(n));
2158         if(nullptr == device || keys.isEmpty() || !keys.contains(device->getKey()))
2159         {
2160             continue;
2161         }
2162         if(canvas)
2163         {
2164             canvas->reportStatus("device", tr("<b>Update devices</b><p>Update %1<br/>Please wait...</p>").arg(device->text(CGisListWks::eColumnName)));
2165             canvas->update();
2166             qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
2167         }
2168 
2169         device->updateProject(project);
2170     }
2171     if(canvas)
2172     {
2173         canvas->reportStatus("device", "");
2174     }
2175     emit sigChanged();
2176 }
2177 
event(QEvent * e)2178 bool CGisListWks::event(QEvent* e)
2179 {
2180     if(e->type() > QEvent::User)
2181     {
2182         const bool doWaitCursor = (eEvtA2WCutTrk != event_types_e(e->type()));
2183         CGisListWksEditLock lock(doWaitCursor, IGisItem::mutexItems);
2184 
2185         switch(e->type())
2186         {
2187         case eEvtD2WReqInfo:
2188         {
2189             CEvtD2WReqInfo* evt = (CEvtD2WReqInfo*)e;
2190             CDBProject* project = getProjectById(evt->id, evt->db);
2191             if(nullptr != project)
2192             {
2193                 project->postStatus(false);
2194             }
2195             e->accept();
2196             emit sigChanged();
2197             return true;
2198         }
2199 
2200         case eEvtD2WShowFolder:
2201         {
2202             CEvtD2WShowFolder* evt = (CEvtD2WShowFolder*)e;
2203             CDBProject* project = getProjectById(evt->id, evt->db);
2204             if(nullptr == project)
2205             {
2206                 if(evt->id == 0)
2207                 {
2208                     project = new CLostFoundProject(evt->db, this);
2209                 }
2210                 else
2211                 {
2212                     project = new CDBProject(evt->db, evt->id, this);
2213                 }
2214                 if(!project->isValid())
2215                 {
2216                     delete project;
2217                     break;
2218                 }
2219                 project->setWorkspaceFilter(CGisWorkspace::self().getCurrentSearch());
2220             }
2221             e->accept();
2222             emit sigChanged();
2223             return true;
2224         }
2225 
2226         case eEvtD2WHideFolder:
2227         {
2228             CEvtD2WHideFolder* evt = (CEvtD2WHideFolder*)e;
2229             CDBProject* project = getProjectById(evt->id, evt->db);
2230             if(project && project->askBeforClose())
2231             {
2232                 /*
2233                     Tell the DB view that we aborted to hide the folder by posting it's
2234                     current status.
2235                  */
2236                 project->postStatus(false);
2237                 return false;
2238             }
2239             delete project;
2240 
2241             e->accept();
2242             emit sigChanged();
2243             emit sigItemDeleted();
2244             return true;
2245         }
2246 
2247         case eEvtD2WShowItems:
2248         {
2249             CEvtD2WShowItems* evt = (CEvtD2WShowItems*)e;
2250             CDBProject* project = getProjectById(evt->id, evt->db);
2251             if(project)
2252             {
2253                 project->showItems(evt);
2254             }
2255             e->accept();
2256             emit sigChanged();
2257             return true;
2258         }
2259 
2260         case eEvtD2WHideItems:
2261         {
2262             CEvtD2WHideItems* evt = (CEvtD2WHideItems*)e;
2263             CDBProject* project = getProjectById(evt->id, evt->db);
2264             if(project)
2265             {
2266                 project->hideItems(evt);
2267             }
2268             e->accept();
2269             emit sigChanged();
2270             emit sigItemDeleted();
2271             return true;
2272         }
2273 
2274         case eEvtD2WUpdateLnF:
2275         {
2276             CEvtD2WUpdateLnF* evt = (CEvtD2WUpdateLnF*)e;
2277             CLostFoundProject* project = dynamic_cast<CLostFoundProject*>(getProjectById(evt->id, evt->db));
2278             if(project)
2279             {
2280                 project->updateFromDb();
2281             }
2282             e->accept();
2283             emit sigChanged();
2284             return true;
2285         }
2286 
2287         case eEvtD2WUpdateItems:
2288         {
2289             CEvtD2WUpdateItems* evt = (CEvtD2WUpdateItems*)e;
2290             IGisProject* project = dynamic_cast<IGisProject*>(getProjectById(evt->id, evt->db));
2291             if(project)
2292             {
2293                 project->blockUpdateItems(false);
2294             }
2295             e->accept();
2296             emit sigChanged();
2297             return true;
2298         }
2299 
2300         case eEvtD2WReload:
2301         {
2302             CEvtD2WReload* evt = (CEvtD2WReload*)e;
2303             QList<CDBProject*> projects;
2304 
2305             const int N = topLevelItemCount();
2306             for(int i = 0; i < N; i++)
2307             {
2308                 CDBProject* project = dynamic_cast<CDBProject*>(topLevelItem(i));
2309 
2310                 if(project && (project->getDBName() == evt->db))
2311                 {
2312                     project->update();
2313                     projects << project;
2314                 }
2315             }
2316 
2317             for(CDBProject* project : qAsConst(projects))
2318             {
2319                 project->blockUpdateItems(false);
2320             }
2321             e->accept();
2322             return true;
2323         }
2324 
2325         case eEvtA2WCutTrk:
2326         {
2327             CEvtA2WCutTrk* evt = (CEvtA2WCutTrk*)e;
2328             CGisWorkspace::self().cutTrkByKey(evt->key);
2329             e->accept();
2330             return true;
2331         }
2332 
2333         case eEvtA2WSave:
2334         {
2335             CEvtA2WSave* evt = (CEvtA2WSave*)e;
2336 
2337             IGisProject* project = getProjectByKey(evt->key);
2338             if(project)
2339             {
2340                 project->save();
2341                 project->confirmPendingAutoSave();
2342             }
2343             e->accept();
2344             return true;
2345         }
2346 
2347         case eEvtA2WSync:
2348         {
2349             CEvtA2WSave* evt = (CEvtA2WSave*)e;
2350 
2351             IGisProject* project = getProjectByKey(evt->key);
2352             if(project)
2353             {
2354                 syncPrjToDevices(project, getAllDeviceKeys());
2355                 project->confirmPendingAutoSyncToDev();
2356             }
2357             e->accept();
2358             return true;
2359         }
2360         }
2361     }
2362     return QTreeWidget::event(e);
2363 }
2364 
2365 
2366 
slotRteFromWpt()2367 void CGisListWks::slotRteFromWpt()
2368 {
2369     CGisListWksEditLock lock(false, IGisItem::mutexItems);
2370 
2371     const QList<IGisItem::key_t>& keys = selectedItems2Keys<CGisItemWpt>();
2372 
2373     if(!keys.isEmpty())
2374     {
2375         CGisWorkspace::self().makeRteFromWpt(keys);
2376     }
2377 }
2378 
slotEditPrxWpt()2379 void CGisListWks::slotEditPrxWpt()
2380 {
2381     CGisListWksEditLock lock(false, IGisItem::mutexItems);
2382 
2383     const QList<IGisItem::key_t>& keys = selectedItems2Keys<CGisItemWpt>();
2384 
2385     if(!keys.isEmpty())
2386     {
2387         CGisWorkspace::self().editPrxWpt(keys);
2388     }
2389 }
2390 
slotSyncDB()2391 void CGisListWks::slotSyncDB()
2392 {
2393     CGisListWksEditLock lock(true, IGisItem::mutexItems);
2394 
2395     const QList<QTreeWidgetItem*>& items = selectedItems();
2396     for(QTreeWidgetItem* item : items)
2397     {
2398         CDBProject* project = dynamic_cast<CDBProject*>(item);
2399         if(project == nullptr)
2400         {
2401             continue;
2402         }
2403 
2404         project->update();
2405     }
2406 }
2407 
slotSetSortMode(IGisProject::sorting_folder_e mode,bool checked)2408 void CGisListWks::slotSetSortMode(IGisProject::sorting_folder_e mode, bool checked)
2409 {
2410     if(!checked || blockSorting)
2411     {
2412         return;
2413     }
2414 
2415     IGisProject* project = dynamic_cast<IGisProject*>(currentItem());
2416     if(project != nullptr)
2417     {
2418         project->setSortingFolder(mode);
2419     }
2420 }
2421 
2422 
slotCopyProject()2423 void CGisListWks::slotCopyProject()
2424 {
2425     CGisListWksEditLock lock(true, IGisItem::mutexItems);
2426 
2427     QList<IGisItem::key_t>  keys;
2428 
2429     const QList<QTreeWidgetItem*>& items = selectedItems();
2430     for(QTreeWidgetItem* item : items)
2431     {
2432         IGisProject* project = dynamic_cast<IGisProject*>(item);
2433         if(project == nullptr)
2434         {
2435             continue;
2436         }
2437 
2438         const int N = project->childCount();
2439         for(int i = 0; i < N; i++)
2440         {
2441             IGisItem* item = dynamic_cast<IGisItem*>(project->child(i));
2442             if(item != nullptr)
2443             {
2444                 keys << item->getKey();
2445             }
2446         }
2447     }
2448 
2449     CGisWorkspace::self().copyItemsByKey(keys);
2450 }
2451 
2452 
slotSymWpt()2453 void CGisListWks::slotSymWpt()
2454 {
2455     CGisListWksEditLock lock(false, IGisItem::mutexItems);
2456 
2457     QObject* obj = sender();
2458     QString iconName = obj->property("iconName").toString();
2459     if(iconName.isEmpty())
2460     {
2461         return;
2462     }
2463     CGisWorkspace::self().changeWptSymByKey(selectedItems2Keys<CGisItemWpt>(), iconName);
2464 }
2465 
slotEleWptTrk()2466 void CGisListWks::slotEleWptTrk()
2467 {
2468     CGisWorkspace::self().addEleToWptTrkByKey(selectedItems2Keys<IGisItem>());
2469 }
2470 
slotToRoute()2471 void CGisListWks::slotToRoute()
2472 {
2473     CGisListWksEditLock lock(false, IGisItem::mutexItems);
2474 
2475     CGisItemTrk *gisItem = dynamic_cast<CGisItemTrk*>(currentItem());
2476     if(gisItem != nullptr)
2477     {
2478         CGisWorkspace::self().convertTrackToRoute(gisItem->getKey());
2479     }
2480 }
2481