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