1 /**********************************************************************************************
2     Copyright (C) 2014 Oliver Eichler <oliver.eichler@gmx.de>
3     Copyright (C) 2017 Norbert Truchsess <norbert.truchsess@t-online.de>
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 **********************************************************************************************/
19 
20 #include "canvas/CCanvas.h"
21 #include "canvas/CCanvasSelect.h"
22 #include "CMainWindow.h"
23 #include "device/IDevice.h"
24 #include "gis/CGisDatabase.h"
25 #include "gis/CGisDraw.h"
26 #include "gis/CGisItemRate.h"
27 #include "gis/CGisWorkspace.h"
28 #include "gis/db/CDBProject.h"
29 #include "gis/db/CSelectDBFolder.h"
30 #include "gis/db/CSetupFolder.h"
31 #include "gis/gpx/CGpxProject.h"
32 #include "gis/IGisItem.h"
33 #include "gis/ovl/CGisItemOvlArea.h"
34 #include "gis/prj/IGisProject.h"
35 #include "gis/Poi.h"
36 #include "gis/qms/CQmsProject.h"
37 #include "gis/rte/CCreateRouteFromWpt.h"
38 #include "gis/rte/CGisItemRte.h"
39 #include "gis/rte/router/IRouter.h"
40 #include "gis/search/CGeoSearchWeb.h"
41 #include "gis/search/CSearch.h"
42 #include "gis/search/CSearchExplanationDialog.h"
43 #include "gis/trk/CCombineTrk.h"
44 #include "gis/trk/CGisItemTrk.h"
45 #include "gis/wpt/CGisItemWpt.h"
46 #include "gis/wpt/CProjWpt.h"
47 #include "helpers/CInputDialog.h"
48 #include "helpers/CProgressDialog.h"
49 #include "helpers/CSelectCopyAction.h"
50 #include "helpers/CSelectProjectDialog.h"
51 #include "helpers/CSettings.h"
52 
53 #include <QtWidgets>
54 #include <QtXml>
55 
56 CGisWorkspace* CGisWorkspace::pSelf = nullptr;
57 
CGisWorkspace(QMenu * menuProject,QWidget * parent)58 CGisWorkspace::CGisWorkspace(QMenu* menuProject, QWidget* parent)
59     : QWidget(parent), currentSearch("")
60 {
61     pSelf = this;
62     setupUi(this);
63 
64     treeWks->setExternalMenu(menuProject);
65 
66     SETTINGS;
67     treeWks->header()->restoreState(cfg.value("Workspace/treeWks/state", treeWks->header()->saveState()).toByteArray());
68 
69     tags_hidden_e tagsHidden = (tags_hidden_e)cfg.value("Workspace/treeWks/tagsHidden", eTagsHiddenUnknown).toInt();
70     // Limit the column width to half the table width to avoid the name column to be pushed out the window.
71     if(tagsHidden == eTagsHiddenUnknown || treeWks->columnWidth(CGisListWks::eColumnRating) > 0.4 * this->width())
72     {
73         treeWks->setColumnWidth(CGisListWks::eColumnRating, 0.2 * this->width());
74     }
75 
76     //Only show tags if they are explcitely not hidden
77     setTagsHidden(tagsHidden != eTagsHiddenFalse);
78 
79 
80     CSearch::setSearchMode(CSearch::search_mode_e(cfg.value("Workspace/projects/filterMode", CSearch::getSearchMode()).toInt()));
81     CSearch::setCaseSensitivity(Qt::CaseSensitivity(cfg.value("Workspace/projects/CaseSensitivity", CSearch::getCaseSensitivity()).toInt()));
82 
83     connect(treeWks, &CGisListWks::sigChanged, this, &CGisWorkspace::sigChanged);
84     connect(sliderOpacity, &QSlider::valueChanged, this, &CGisWorkspace::slotSetGisLayerOpacity);
85     connect(lineFilter, &CSearchLineEdit::sigWorkspaceSearchChanged, this, &CGisWorkspace::slotSearch);
86     connect(treeWks, &CGisListWks::itemPressed, this, &CGisWorkspace::slotWksItemPressed);
87     connect(treeWks, &CGisListWks::itemSelectionChanged, this, &CGisWorkspace::slotWksItemSelectionChanged);
88     connect(treeWks, &CGisListWks::sigItemDeleted, this, &CGisWorkspace::slotWksItemSelectionChanged);
89 }
90 
~CGisWorkspace()91 CGisWorkspace::~CGisWorkspace()
92 {
93     SETTINGS;
94     //To ensure backwards compatibility first it is saved wether the tags are hidden,
95     //then the column is made visible and then the header is saved. This way the name column is in no case hidden.
96     cfg.setValue("Workspace/treeWks/tagsHidden", areTagsHidden() ? eTagsHiddenTrue : eTagsHiddenFalse);
97     setTagsHidden(false);
98     cfg.setValue("Workspace/treeWks/state", treeWks->header()->saveState());
99 
100     cfg.setValue("Workspace/projects/filterMode", CSearch::getSearchMode());
101     cfg.setValue("Workspace/projects/CaseSensitivity", CSearch::getCaseSensitivity());
102     /*
103         Explicitly delete workspace here, as database projects use
104         CGisWorkspace upon destruction to signal the database their destruction.
105 
106      */
107     delete treeWks;
108 }
109 
slotLateInit()110 void CGisWorkspace::slotLateInit()
111 {
112     // [Issue #265] Delay the loading of the workspace to make sure the complete IUnit system
113     //              is up and running.
114     QTimer::singleShot(1000, treeWks, &CGisListWks::slotLoadWorkspace);
115 }
116 
setOpacity(qreal val)117 void CGisWorkspace::setOpacity(qreal val)
118 {
119     sliderOpacity->setValue(val * 100);
120 }
121 
postEventForWks(QEvent * event)122 void CGisWorkspace::postEventForWks(QEvent* event)
123 {
124     QCoreApplication::postEvent(treeWks, event);
125 }
126 
loadGisProject(const QString & filename)127 void CGisWorkspace::loadGisProject(const QString& filename)
128 {
129     // add project to workspace
130     {
131         CCanvasCursorLock cursorLock(Qt::WaitCursor, __func__);
132         treeWks->blockSignals(true);
133 
134         QMutexLocker lock(&IGisItem::mutexItems);
135 
136         IGisProject* item = IGisProject::create(filename, treeWks);
137         // skip if project is already loaded
138         if(item && treeWks->hasProject(item))
139         {
140             QMessageBox::information(this, tr("Load project..."), tr("The project \"%1\" is already in the workspace.").arg(item->getName()), QMessageBox::Abort);
141 
142             delete item;
143             item = nullptr;
144         }
145 
146         treeWks->blockSignals(false);
147 
148         if(item != nullptr)
149         {
150             item->setWorkspaceFilter(currentSearch);
151         }
152     }
153 
154     emit sigChanged();
155 }
156 
157 
slotSetGisLayerOpacity(int val)158 void CGisWorkspace::slotSetGisLayerOpacity(int val)
159 {
160     CCanvas::gisLayerOpacity = qreal(val) / 100;
161     CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
162     if(canvas != nullptr)
163     {
164         canvas->update();
165     }
166 }
167 
slotSearch(const CSearch & currentSearch)168 void CGisWorkspace::slotSearch(const CSearch& currentSearch)
169 {
170     this->currentSearch = currentSearch;
171     {
172         CCanvasCursorLock cursorLock(Qt::WaitCursor, __func__);
173         QMutexLocker lock(&IGisItem::mutexItems);
174 
175         const int N = treeWks->topLevelItemCount();
176         for(int n = 0; n < N; n++)
177         {
178             IGisProject* item = dynamic_cast<IGisProject*>(treeWks->topLevelItem(n));
179             if(item == nullptr)
180             {
181                 continue;
182             }
183 
184             item->setWorkspaceFilter(currentSearch);
185             item->setExpanded(!lineFilter->text().isEmpty());
186         }
187     }
188     CCanvas::triggerCompleteUpdate(CCanvas::eRedrawGis);
189 }
190 
slotSaveAll()191 void CGisWorkspace::slotSaveAll()
192 {
193     CCanvasCursorLock cursorLock(Qt::WaitCursor, __func__);
194     QMutexLocker lock(&IGisItem::mutexItems);
195     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
196     {
197         IGisProject* item = dynamic_cast<IGisProject*>(treeWks->topLevelItem(i));
198         if(nullptr == item)
199         {
200             continue;
201         }
202 
203         if(item->skipSave())
204         {
205             continue;
206         }
207 
208         if(item->canSave())
209         {
210             item->save();
211         }
212         else
213         {
214             item->saveAs();
215         }
216     }
217 }
218 
219 
slotWksItemSelectionChanged()220 void CGisWorkspace::slotWksItemSelectionChanged()
221 {
222     slotWksItemPressed(treeWks->currentItem());
223 }
224 
slotWksItemPressed(QTreeWidgetItem * i)225 void CGisWorkspace::slotWksItemPressed(QTreeWidgetItem* i)
226 {
227     IGisItem* item = dynamic_cast<IGisItem*>(i);
228     if(item != nullptr)
229     {
230         IGisProject* project = item->getParentProject();
231         if (project != nullptr && project->isVisible())
232         {
233             keyWksSelection = item->getKey();
234             const QList<CCanvas*>& allCanvas = CMainWindow::self().getCanvas();
235             for(CCanvas* canvas : allCanvas)
236             {
237                 canvas->reportStatus("WksSelection", tr("<b>Item Selection: </b>Item selected from workspace list. Click on the map to switch back to normal mouse selection behavior."));
238                 canvas->abortMouse();
239             }
240         }
241     }
242     else
243     {
244         slotWksItemSelectionReset();
245     }
246 }
247 
slotWksItemSelectionReset()248 void CGisWorkspace::slotWksItemSelectionReset()
249 {
250     keyWksSelection.clear();
251     const QList<CCanvas*>& allCanvas = CMainWindow::self().getCanvas();
252     for(CCanvas* canvas : allCanvas)
253     {
254         canvas->reportStatus("WksSelection", "");
255         canvas->abortMouse();
256     }
257 }
258 
slotActivityTrkByKey(const QList<IGisItem::key_t> & keys,trkact_t act)259 void CGisWorkspace::slotActivityTrkByKey(const QList<IGisItem::key_t>& keys, trkact_t act)
260 {
261     if(keys.isEmpty())
262     {
263         return;
264     }
265 
266     if(CTrackData::trkpt_t::eAct20Bad != act)
267     {
268         QMutexLocker lock(&IGisItem::mutexItems);
269 
270         QSet<IGisProject*> projects;
271         for(const IGisItem::key_t& key : keys)
272         {
273             CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(getItemByKey(key));
274             if(trk == nullptr)
275             {
276                 continue;
277             }
278 
279             IGisProject* project = trk->getParentProject();
280             if(!projects.contains(project))
281             {
282                 project->blockUpdateItems(true);
283                 projects << project;
284             }
285 
286             if(trk->isRangeSelected())
287             {
288                 trk->setActivityRange(act);
289             }
290             else
291             {
292                 trk->setActivity(act);
293             }
294         }
295 
296         for(IGisProject* project : qAsConst(projects))
297         {
298             project->blockUpdateItems(false);
299         }
300     }
301 }
302 
selectProject(bool forceSelect)303 IGisProject* CGisWorkspace::selectProject(bool forceSelect)
304 {
305     QString key = IGisProject::getUserFocus();
306     QString name;
307     IGisProject::type_e type = IGisProject::eTypeQms;
308 
309     if(key.isEmpty() || forceSelect)
310     {
311         CSelectProjectDialog dlg(key, name, type, treeWks);
312         if(dlg.exec() == QDialog::Rejected)
313         {
314             return nullptr;
315         }
316     }
317 
318     IGisProject* project = nullptr;
319     if(!key.isEmpty())
320     {
321         QMutexLocker lock(&IGisItem::mutexItems);
322         for(int i = 0; i < treeWks->topLevelItemCount(); i++)
323         {
324             project = dynamic_cast<IGisProject*>(treeWks->topLevelItem(i));
325             if(nullptr == project)
326             {
327                 continue;
328             }
329             if(key == project->getKey())
330             {
331                 break;
332             }
333         }
334     }
335     else if(type == IGisProject::eTypeDb)
336     {
337         QList<quint64> ids;
338         QString db;
339         QString host;
340         IDBFolder::type_e type;
341 
342         CSelectDBFolder dlg1(ids, db, host, this);
343         if((dlg1.exec() == QDialog::Rejected) || ids.isEmpty())
344         {
345             return nullptr;
346         }
347 
348         CSetupFolder dlg2(type, name, false, this);
349         if(dlg2.exec() == QDialog::Rejected)
350         {
351             return nullptr;
352         }
353 
354         QMutexLocker lock(&IGisItem::mutexItems);
355         CEvtW2DCreate evt(name, type, ids[0], db, host);
356         CGisDatabase::self().sendEventForDb(&evt);
357 
358         if(evt.idChild)
359         {
360             CDBProject* p = nullptr;
361             while(nullptr == p)
362             {
363                 QApplication::processEvents(QEventLoop::WaitForMoreEvents | QEventLoop::ExcludeUserInputEvents, 100);
364                 p = dynamic_cast<CDBProject*>(treeWks->getProjectById(evt.idChild, db));
365             }
366             /*
367                Creating a project usually does initiate an info request. However as the project isn't in the workspace
368                the moment we create it, the request will fail. That is why we send the info now.
369              */
370             p->postStatus(false);
371             project = p;
372         }
373     }
374     else if(!name.isEmpty())
375     {
376         QMutexLocker lock(&IGisItem::mutexItems);
377         if(type == IGisProject::eTypeGpx)
378         {
379             project = new CGpxProject(name, treeWks);
380         }
381         else if (type == IGisProject::eTypeQms)
382         {
383             project = new CQmsProject(name, treeWks);
384         }
385     }
386 
387     return project;
388 }
389 
getItemsByPos(const QPointF & pos,QList<IGisItem * > & items)390 void CGisWorkspace::getItemsByPos(const QPointF& pos, QList<IGisItem*>& items)
391 {
392     QMutexLocker lock(&IGisItem::mutexItems);
393 
394     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
395     {
396         QTreeWidgetItem* item = treeWks->topLevelItem(i);
397         IGisProject* project = dynamic_cast<IGisProject*>(item);
398         if(project)
399         {
400             project->getItemsByPos(pos, items);
401             continue;
402         }
403         IDevice* device = dynamic_cast<IDevice*>(item);
404         if(device)
405         {
406             device->getItemsByPos(pos, items);
407             continue;
408         }
409     }
410 
411     /*
412         If there is an item selected by the workspace limit
413         the list of items to this item. But only if the item
414         is part of the items close to position.
415      */
416     if(!keyWksSelection.item.isEmpty() && !items.isEmpty())
417     {
418         IGisItem* item = getItemByKey(keyWksSelection);
419         if(item && items.contains(item))
420         {
421             items.clear();
422             items << item;
423         }
424         else
425         {
426             items.clear();
427         }
428     }
429 }
430 
getItemsByKeys(const QList<IGisItem::key_t> & keys,QList<IGisItem * > & items)431 void CGisWorkspace::getItemsByKeys(const QList<IGisItem::key_t>& keys, QList<IGisItem*>& items)
432 {
433     QMutexLocker lock(&IGisItem::mutexItems);
434     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
435     {
436         QTreeWidgetItem* item = treeWks->topLevelItem(i);
437         IGisProject* project = dynamic_cast<IGisProject*>(item);
438         if(project)
439         {
440             project->getItemsByKeys(keys, items);
441             continue;
442         }
443         IDevice* device = dynamic_cast<IDevice*>(item);
444         if(device)
445         {
446             device->getItemsByKeys(keys, items);
447             continue;
448         }
449     }
450 }
451 
getItemsByArea(const QRectF & area,IGisItem::selflags_t flags,QList<IGisItem * > & items)452 void CGisWorkspace::getItemsByArea(const QRectF& area, IGisItem::selflags_t flags, QList<IGisItem*>& items)
453 {
454     QMutexLocker lock(&IGisItem::mutexItems);
455     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
456     {
457         QTreeWidgetItem* item = treeWks->topLevelItem(i);
458         IGisProject* project = dynamic_cast<IGisProject*>(item);
459         if(project)
460         {
461             project->getItemsByArea(area, flags, items);
462             continue;
463         }
464         IDevice* device = dynamic_cast<IDevice*>(item);
465         if(device)
466         {
467             device->getItemsByArea(area, flags, items);
468             continue;
469         }
470     }
471 }
472 
getNogoAreas(QList<IGisItem * > & nogos)473 void CGisWorkspace::getNogoAreas(QList<IGisItem*>& nogos)
474 {
475     QMutexLocker lock(&IGisItem::mutexItems);
476     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
477     {
478         QTreeWidgetItem* item = treeWks->topLevelItem(i);
479         IGisProject* project = dynamic_cast<IGisProject*>(item);
480         if(project)
481         {
482             project->getNogoAreas(nogos);
483             continue;
484         }
485         IDevice* device = dynamic_cast<IDevice*>(item);
486         if(device)
487         {
488             device->getNogoAreas(nogos);
489             continue;
490         }
491     }
492 }
493 
mouseMove(const QPointF & pos)494 void CGisWorkspace::mouseMove(const QPointF& pos)
495 {
496     QMutexLocker lock(&IGisItem::mutexItems);
497     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
498     {
499         QTreeWidgetItem* item = treeWks->topLevelItem(i);
500         IGisProject* project = dynamic_cast<IGisProject*>(item);
501         if(project)
502         {
503             project->mouseMove(pos);
504             continue;
505         }
506     }
507 }
508 
getItemByKey(const IGisItem::key_t & key)509 IGisItem* CGisWorkspace::getItemByKey(const IGisItem::key_t& key)
510 {
511     IGisItem* item = nullptr;
512     QMutexLocker lock(&IGisItem::mutexItems);
513     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
514     {
515         QTreeWidgetItem* item1 = treeWks->topLevelItem(i);
516         IGisProject* project = dynamic_cast<IGisProject*>(item1);
517         if(project)
518         {
519             if(project->getKey() != key.project)
520             {
521                 continue;
522             }
523 
524             item = project->getItemByKey(key);
525             if(nullptr != item)
526             {
527                 break;
528             }
529 
530             continue;
531         }
532 
533         IDevice* device = dynamic_cast<IDevice*>(item1);
534         if(device)
535         {
536             if(device->getKey() != key.device)
537             {
538                 continue;
539             }
540 
541             item = device->getItemByKey(key);
542             if(nullptr != item)
543             {
544                 break;
545             }
546         }
547     }
548 
549     return item;
550 }
551 
delItemByKey(const IGisItem::key_t & key)552 void CGisWorkspace::delItemByKey(const IGisItem::key_t& key)
553 {
554     QMutexLocker lock(&IGisItem::mutexItems);
555     QMessageBox::StandardButtons last = QMessageBox::NoButton;
556     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
557     {
558         IGisProject* project = dynamic_cast<IGisProject*>(treeWks->topLevelItem(i));
559         if(nullptr == project)
560         {
561             continue;
562         }
563 
564         if(project->delItemByKey(key, last))
565         {
566             // update database tree if that is a database project
567             CDBProject* dbp = dynamic_cast<CDBProject*>(project);
568             if(dbp)
569             {
570                 dbp->postStatus(true);
571             }
572         }
573 
574         if(last == QMessageBox::Cancel)
575         {
576             break;
577         }
578     }
579 
580 
581     emit sigChanged();
582 }
583 
delItemsByKey(const QList<IGisItem::key_t> & keys)584 void CGisWorkspace::delItemsByKey(const QList<IGisItem::key_t>& keys)
585 {
586     QMessageBox::StandardButtons last = QMessageBox::NoButton;
587 
588     QSet<CDBProject*>   projects;
589     QSet<IGisProject*>  projectsAll;
590 
591     for(const IGisItem::key_t& key : keys)
592     {
593         IGisItem* gisItem = getItemByKey(key);
594         if(nullptr != gisItem)
595         {
596             bool yes = false;
597             IGisProject* project = dynamic_cast<IGisProject*>(gisItem->parent());
598             if(nullptr != project)
599             {
600                 project->blockUpdateItems(true);
601                 yes = project->delItemByKey(gisItem->getKey(), last);
602 
603 
604                 /*
605                     collect database projects to update their counterpart in
606                     the database view, after all operations are done.
607                  */
608                 if(yes && project->getType() == IGisProject::eTypeDb)
609                 {
610                     projects << dynamic_cast<CDBProject*>(project);
611                 }
612 
613                 /*
614                     Collect all projects to unblock update later on.
615                  */
616                 projectsAll << project;
617             }
618 
619             if(last == QMessageBox::Cancel)
620             {
621                 break;
622             }
623         }
624     }
625 
626     // make all database projects that are changed to post their new status
627     // this will update the database view.
628     for(CDBProject* project : qAsConst(projects))
629     {
630         project->postStatus(true);
631     }
632     // unblock update for all projects seen
633     for(IGisProject* project : qAsConst(projectsAll))
634     {
635         project->blockUpdateItems(false);
636     }
637 
638     CCanvas::triggerCompleteUpdate(CCanvas::eRedrawGis);
639 }
640 
editItemByKey(const IGisItem::key_t & key)641 void CGisWorkspace::editItemByKey(const IGisItem::key_t& key)
642 {
643     QMutexLocker lock(&IGisItem::mutexItems);
644     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
645     {
646         QTreeWidgetItem* item = treeWks->topLevelItem(i);
647         IGisProject* project = dynamic_cast<IGisProject*>(item);
648         if(nullptr != project)
649         {
650             project->editItemByKey(key);
651             continue;
652         }
653         IDevice* device = dynamic_cast<IDevice*>(item);
654         if(nullptr != device)
655         {
656             device->editItemByKey(key);
657             continue;
658         }
659     }
660 
661     emit sigChanged();
662 }
663 
copyItemByKey(const IGisItem::key_t & key)664 void CGisWorkspace::copyItemByKey(const IGisItem::key_t& key)
665 {
666     QMutexLocker lock(&IGisItem::mutexItems);
667 
668     IGisItem* item = getItemByKey(key);
669     if(nullptr == item)
670     {
671         return;
672     }
673 
674     IGisProject* project = selectProject(true);
675     if(nullptr == project)
676     {
677         return;
678     }
679     CSelectCopyAction::result_e actionForAll = CSelectCopyAction::eResultNone;
680     project->insertCopyOfItem(item, NOIDX, actionForAll);
681 
682 
683     emit sigChanged();
684 }
685 
copyItemsByKey(const QList<IGisItem::key_t> & keys)686 IGisProject* CGisWorkspace::copyItemsByKey(const QList<IGisItem::key_t>& keys)
687 {
688     QMutexLocker lock(&IGisItem::mutexItems);
689 
690     IGisProject* project = selectProject(true);
691     if(nullptr == project)
692     {
693         return nullptr;
694     }
695 
696     CSelectCopyAction::result_e lastResult = CSelectCopyAction::eResultNone;
697 
698     project->blockUpdateItems(true);
699     int cnt = 1;
700     PROGRESS_SETUP(tr("Copy items..."), 0, keys.count(), this);
701     for(const IGisItem::key_t& key : keys)
702     {
703         PROGRESS(cnt++, break);
704         IGisItem* gisItem = getItemByKey(key);
705         if(nullptr != gisItem)
706         {
707             project->insertCopyOfItem(gisItem, NOIDX, lastResult);
708         }
709     }
710     project->blockUpdateItems(false);
711 
712     CCanvas::triggerCompleteUpdate(CCanvas::eRedrawGis);
713     return project;
714 }
715 
searchWebByKey(const IGisItem::key_t & key)716 void CGisWorkspace::searchWebByKey(const IGisItem::key_t& key)
717 {
718     QMutexLocker lock(&IGisItem::mutexItems);
719 
720     CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(getItemByKey(key));
721     if(wpt != nullptr)
722     {
723         CGeoSearchWeb::self().getMenu(wpt->getPosition(), this, true);
724     }
725 }
726 
changeWptSymByKey(const QList<IGisItem::key_t> & keys,const QString & sym)727 void CGisWorkspace::changeWptSymByKey(const QList<IGisItem::key_t>& keys, const QString& sym)
728 {
729     QMutexLocker lock(&IGisItem::mutexItems);
730 
731     PROGRESS_SETUP(tr("Change waypoint symbols."), 0, keys.count(), this);
732     int cnt = 0;
733 
734     QSet<IGisProject*> projects;
735     for(const IGisItem::key_t& key : keys)
736     {
737         PROGRESS(cnt++, break);
738         CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(getItemByKey(key));
739         if(nullptr != wpt)
740         {
741             IGisProject* project = wpt->getParentProject();
742             if(!projects.contains(project))
743             {
744                 project->blockUpdateItems(true);
745                 projects << project;
746             }
747             wpt->setIcon(sym);
748         }
749     }
750 
751     for(IGisProject* project : qAsConst(projects))
752     {
753         project->blockUpdateItems(false);
754     }
755 
756     emit sigChanged();
757 }
758 
759 
addEleToWptTrkByKey(const QList<IGisItem::key_t> & keys)760 void CGisWorkspace::addEleToWptTrkByKey(const QList<IGisItem::key_t>& keys)
761 {
762     CCanvas* canvas = nullptr;
763     CCanvasSelect dlg(canvas, this);
764     dlg.exec();
765 
766     if(canvas == nullptr)
767     {
768         return;
769     }
770 
771     QMutexLocker lock(&IGisItem::mutexItems);
772 
773     QSet<IGisProject*> projects;
774     for(const IGisItem::key_t& key : keys)
775     {
776         IGisItem* item = dynamic_cast<IGisItem*>(getItemByKey(key));
777 
778         IGisProject* project = item->getParentProject();
779         if(!projects.contains(project))
780         {
781             project->blockUpdateItems(true);
782             projects << project;
783         }
784 
785         switch(item->type())
786         {
787         case IGisItem::eTypeTrk:
788         {
789             CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(item);
790             if(trk != nullptr)
791             {
792                 trk->filterReplaceElevation(canvas);
793             }
794             break;
795         }
796 
797         case IGisItem::eTypeWpt:
798         {
799             CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(item);
800             if(wpt != nullptr)
801             {
802                 qreal ele = canvas->getElevationAt(wpt->getPosition() * DEG_TO_RAD);
803                 wpt->setElevation(ele == NOFLOAT ? NOINT : ele);
804             }
805             break;
806         }
807         }
808     }
809 
810     for(IGisProject* project : qAsConst(projects))
811     {
812         project->blockUpdateItems(false);
813     }
814 
815     emit sigChanged();
816 }
817 
projWptByKey(const IGisItem::key_t & key)818 void CGisWorkspace::projWptByKey(const IGisItem::key_t& key)
819 {
820     QMutexLocker lock(&IGisItem::mutexItems);
821 
822     CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(getItemByKey(key));
823     if(nullptr != wpt)
824     {
825         CProjWpt dlg(*wpt, 0);
826         dlg.exec();
827     }
828 
829 
830     emit sigChanged();
831 }
832 
moveWptByKey(const IGisItem::key_t & key)833 void CGisWorkspace::moveWptByKey(const IGisItem::key_t& key)
834 {
835     QMutexLocker lock(&IGisItem::mutexItems);
836     CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(getItemByKey(key));
837     if(nullptr != wpt)
838     {
839         if(!wpt->setReadOnlyMode(false))
840         {
841             return;
842         }
843 
844         CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
845         if(nullptr != canvas)
846         {
847             canvas->setMouseMoveWpt(*wpt);
848         }
849     }
850 }
851 
toggleWptBubble(const IGisItem::key_t & key)852 void CGisWorkspace::toggleWptBubble(const IGisItem::key_t& key)
853 {
854     QMutexLocker lock(&IGisItem::mutexItems);
855     CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(getItemByKey(key));
856     if(nullptr != wpt)
857     {
858         wpt->toggleBubble();
859     }
860 }
861 
deleteWptRadius(const IGisItem::key_t & key)862 void CGisWorkspace::deleteWptRadius(const IGisItem::key_t& key)
863 {
864     IGisItem* item = getItemByKey(key);
865     if(nullptr != item)
866     {
867         CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(item);
868         wpt->setProximity(NOFLOAT);
869     }
870 }
871 
toggleNogoItem(const IGisItem::key_t & key)872 void CGisWorkspace::toggleNogoItem(const IGisItem::key_t& key)
873 {
874     QMutexLocker lock(&IGisItem::mutexItems);
875     IGisItem* item = getItemByKey(key);
876     if(nullptr != item)
877     {
878         item->setNogo(!item->isNogo());
879     }
880 }
881 
editWptRadius(const IGisItem::key_t & key)882 void CGisWorkspace::editWptRadius(const IGisItem::key_t& key)
883 {
884     QMutexLocker lock(&IGisItem::mutexItems);
885     CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(getItemByKey(key));
886     if(nullptr != wpt)
887     {
888         if(!wpt->setReadOnlyMode(false))
889         {
890             return;
891         }
892 
893         CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
894         if(nullptr != canvas)
895         {
896             canvas->setMouseRadiusWpt(*wpt);
897         }
898     }
899 }
900 
copyWptCoordByKey(const IGisItem::key_t & key)901 void CGisWorkspace::copyWptCoordByKey(const IGisItem::key_t& key)
902 {
903     QMutexLocker lock(&IGisItem::mutexItems);
904     CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(getItemByKey(key));
905     if(nullptr != wpt)
906     {
907         QString strPos;
908         QPointF pos = wpt->getPosition();
909         IUnit::degToStr(pos.x(), pos.y(), strPos);
910         QClipboard* clipboard = QApplication::clipboard();
911         clipboard->setText(strPos);
912     }
913 }
914 
addWptByPos(const QPointF & pt,const QString & name,const QString & desc) const915 void CGisWorkspace::addWptByPos(const QPointF& pt, const QString& name, const QString& desc) const
916 {
917     QMutexLocker lock(&IGisItem::mutexItems);
918 
919     IGisProject* project = CGisWorkspace::self().selectProject(false);
920     if(nullptr == project)
921     {
922         return;
923     }
924 
925     CGisItemWpt::newWpt(pt, name, desc, project);
926 }
927 
addPoisAsWpt(const QSet<poi_t> & pois,IGisProject * project) const928 void CGisWorkspace::addPoisAsWpt(const QSet<poi_t>& pois, IGisProject* project) const
929 {
930     if(nullptr == project)
931     {
932         project = CGisWorkspace::self().selectProject(false);
933     }
934     if(nullptr == project)
935     {
936         return;
937     }
938     tristate_e openEditWindow = eTristateUndefined;
939     for(const poi_t& poi : pois)
940     {
941         addPoiAsWpt(poi, openEditWindow, project);
942     }
943 }
944 
addPoiAsWpt(const poi_t & poi,IGisProject * project) const945 void CGisWorkspace::addPoiAsWpt(const poi_t& poi, IGisProject* project) const
946 {
947     tristate_e tmp = eTristateUndefined;
948     addPoiAsWpt(poi, tmp, project);
949 }
950 
addPoiAsWpt(const poi_t & poi,tristate_e & openEditWindow,IGisProject * project) const951 void CGisWorkspace::addPoiAsWpt(const poi_t& poi, tristate_e& openEditWindow, IGisProject* project) const
952 {
953     QMutexLocker lock(&IGisItem::mutexItems);
954 
955     if(nullptr == project)
956     {
957         project = CGisWorkspace::self().selectProject(false);
958     }
959     if(nullptr == project)
960     {
961         return;
962     }
963     if(openEditWindow == eTristateUndefined && poi.icon.isEmpty())
964     {
965         int answer = QMessageBox(QMessageBox::Icon::Question,
966                                  tr("Undefined Waypoint Symbol"),
967                                  tr("QMapShack couldn't automatically assign a waypoint icon to one of the POIs you want to convert to a waypoint.\n\n"
968                                     "Do you want to choose an icon for each new waypoint for which no icon could be found?\n"
969                                     "If you choose 'No' the respective last used waypoint icon is applied."),
970                                  QMessageBox::Yes | QMessageBox::No).exec();
971 
972         if(answer == QMessageBox::Yes)
973         {
974             openEditWindow = eTristateTrue;
975         }
976         else
977         {
978             openEditWindow = eTristateFalse;
979         }
980     }
981     CGisItemWpt::newWpt(poi, project, openEditWindow == eTristateTrue && poi.icon.isEmpty());
982 }
983 
focusTrkByKey(bool yes,const IGisItem::key_t & key)984 void CGisWorkspace::focusTrkByKey(bool yes, const IGisItem::key_t& key)
985 {
986     QMutexLocker lock(&IGisItem::mutexItems);
987 
988     CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(getItemByKey(key));
989     if(nullptr != trk)
990     {
991         trk->gainUserFocus(yes);
992     }
993 
994     emit sigChanged();
995 }
996 
focusRteByKey(bool yes,const IGisItem::key_t & key)997 void CGisWorkspace::focusRteByKey(bool yes, const IGisItem::key_t& key)
998 {
999     QMutexLocker lock(&IGisItem::mutexItems);
1000 
1001     CGisItemRte* rte = dynamic_cast<CGisItemRte*>(getItemByKey(key));
1002     if(nullptr != rte)
1003     {
1004         rte->gainUserFocus(yes);
1005     }
1006 
1007     emit sigChanged();
1008 }
1009 
convertRouteToTrack(const IGisItem::key_t & key)1010 void CGisWorkspace::convertRouteToTrack(const IGisItem::key_t& key)
1011 {
1012     QMutexLocker lock(&IGisItem::mutexItems);
1013     CGisItemRte* rte = dynamic_cast<CGisItemRte*>(getItemByKey(key));
1014     if(nullptr != rte)
1015     {
1016         rte->toTrack();
1017     }
1018 
1019     emit sigChanged();
1020 }
1021 
convertTrackToRoute(const IGisItem::key_t & key)1022 void CGisWorkspace::convertTrackToRoute(const IGisItem::key_t& key)
1023 {
1024     QMutexLocker lock(&IGisItem::mutexItems);
1025     CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(getItemByKey(key));
1026     if(nullptr != trk)
1027     {
1028         trk->toRoute();
1029     }
1030 
1031     emit sigChanged();
1032 }
1033 
cutTrkByKey(const IGisItem::key_t & key)1034 void CGisWorkspace::cutTrkByKey(const IGisItem::key_t& key)
1035 {
1036     QMutexLocker lock(&IGisItem::mutexItems);
1037 
1038     CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(getItemByKey(key));
1039     if(nullptr != trk && trk->cut())
1040     {
1041         int res = QMessageBox::question(this, tr("Cut Track..."), tr("Do you want to delete the original track?"), QMessageBox::Ok | QMessageBox::No, QMessageBox::Ok);
1042         if(res == QMessageBox::Ok)
1043         {
1044             delete trk;
1045         }
1046     }
1047 
1048     emit sigChanged();
1049 }
1050 
addTrkInfoByKey(const IGisItem::key_t & key)1051 void CGisWorkspace::addTrkInfoByKey(const IGisItem::key_t& key)
1052 {
1053     QMutexLocker lock(&IGisItem::mutexItems);
1054 
1055     CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(getItemByKey(key));
1056     if(nullptr != trk)
1057     {
1058         trk->addTrkPtDesc();
1059     }
1060 
1061     emit sigChanged();
1062 }
1063 
reverseTrkByKey(const IGisItem::key_t & key)1064 void CGisWorkspace::reverseTrkByKey(const IGisItem::key_t& key)
1065 {
1066     QMutexLocker lock(&IGisItem::mutexItems);
1067 
1068     CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(getItemByKey(key));
1069     if(nullptr != trk)
1070     {
1071         trk->reverse();
1072     }
1073 
1074     emit sigChanged();
1075 }
1076 
combineTrkByKey(const IGisItem::key_t & keyTrk)1077 void CGisWorkspace::combineTrkByKey(const IGisItem::key_t& keyTrk)
1078 {
1079     QMutexLocker lock(&IGisItem::mutexItems);
1080 
1081     QList<IGisItem::key_t> keys;
1082     IGisItem* item = dynamic_cast<IGisItem*>(getItemByKey(keyTrk));
1083     if(item == nullptr)
1084     {
1085         return;
1086     }
1087 
1088     keys << keyTrk;
1089 
1090     IGisProject* project = dynamic_cast<IGisProject*>(item->parent());
1091     if(project == nullptr)
1092     {
1093         return;
1094     }
1095 
1096     const int N = project->childCount();
1097     for(int i = 0; i < N; i++)
1098     {
1099         CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(project->child(i));
1100         if(trk != nullptr)
1101         {
1102             const IGisItem::key_t& key = trk->getKey();
1103             if(key != keyTrk)
1104             {
1105                 keys << key;
1106             }
1107         }
1108     }
1109 
1110     combineTrkByKey(keys, {keyTrk});
1111 }
1112 
combineTrkByKey(const QList<IGisItem::key_t> & keys,const QList<IGisItem::key_t> & keysPreSel)1113 void CGisWorkspace::combineTrkByKey(const QList<IGisItem::key_t>& keys, const QList<IGisItem::key_t>& keysPreSel)
1114 {
1115     if(keys.isEmpty())
1116     {
1117         return;
1118     }
1119 
1120     QMutexLocker lock(&IGisItem::mutexItems);
1121 
1122     CCombineTrk dlg(keys, keysPreSel, this);
1123     dlg.exec();
1124 
1125     emit sigChanged();
1126 }
1127 
colorTrkByKey(const QList<IGisItem::key_t> & keys)1128 void CGisWorkspace::colorTrkByKey(const QList<IGisItem::key_t>& keys)
1129 {
1130     if(keys.isEmpty())
1131     {
1132         return;
1133     }
1134 
1135     qint32 colorIdx = IGisItem::selectColor(this);
1136     if(colorIdx != NOIDX)
1137     {
1138         QMutexLocker lock(&IGisItem::mutexItems);
1139 
1140         QSet<IGisProject*> projects;
1141         for(const IGisItem::key_t& key : keys)
1142         {
1143             CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(getItemByKey(key));
1144             if(trk != nullptr)
1145             {
1146                 IGisProject* project = trk->getParentProject();
1147                 if(!projects.contains(project))
1148                 {
1149                     project->blockUpdateItems(true);
1150                     projects << project;
1151                 }
1152 
1153                 trk->setColor(colorIdx);
1154             }
1155         }
1156 
1157         for(IGisProject* project : qAsConst(projects))
1158         {
1159             project->blockUpdateItems(false);
1160         }
1161     }
1162 }
1163 
editTrkByKey(const IGisItem::key_t & key)1164 void CGisWorkspace::editTrkByKey(const IGisItem::key_t& key)
1165 {
1166     QMutexLocker lock(&IGisItem::mutexItems);
1167 
1168     CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(getItemByKey(key));
1169     if(nullptr != trk)
1170     {
1171         if(!trk->setReadOnlyMode(false))
1172         {
1173             return;
1174         }
1175 
1176         CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
1177         if(nullptr != canvas)
1178         {
1179             canvas->setMouseEditTrk(*trk);
1180         }
1181     }
1182 }
1183 
rangeTrkByKey(const IGisItem::key_t & key)1184 void CGisWorkspace::rangeTrkByKey(const IGisItem::key_t& key)
1185 {
1186     QMutexLocker lock(&IGisItem::mutexItems);
1187 
1188     CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(getItemByKey(key));
1189     if(nullptr != trk)
1190     {
1191         CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
1192         if(nullptr != canvas)
1193         {
1194             canvas->setMouseRangeTrk(*trk);
1195         }
1196     }
1197 }
1198 
copyTrkWithWptByKey(const IGisItem::key_t & key)1199 void CGisWorkspace::copyTrkWithWptByKey(const IGisItem::key_t& key)
1200 {
1201     QList<IGisItem::key_t> keys;
1202 
1203     CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(CGisWorkspace::self().getItemByKey(key));
1204     if(nullptr != trk)
1205     {
1206         keys << key;
1207 
1208         const CTrackData& t = trk->getTrackData();
1209         for(const CTrackData::trkpt_t& trkpt : t)
1210         {
1211             if(trkpt.isHidden() || trkpt.keyWpt.item.isEmpty())
1212             {
1213                 continue;
1214             }
1215 
1216             keys << trkpt.keyWpt;
1217         }
1218 
1219         copyItemsByKey(keys);
1220     }
1221 }
1222 
editRteByKey(const IGisItem::key_t & key)1223 void CGisWorkspace::editRteByKey(const IGisItem::key_t& key)
1224 {
1225     QMutexLocker lock(&IGisItem::mutexItems);
1226 
1227     CGisItemRte* rte = dynamic_cast<CGisItemRte*>(getItemByKey(key));
1228     if(nullptr != rte)
1229     {
1230         if(!rte->setReadOnlyMode(false))
1231         {
1232             return;
1233         }
1234 
1235         CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
1236         if(nullptr != canvas)
1237         {
1238             canvas->setMouseEditRte(*rte);
1239         }
1240     }
1241 }
1242 
reverseRteByKey(const IGisItem::key_t & key)1243 void CGisWorkspace::reverseRteByKey(const IGisItem::key_t& key)
1244 {
1245     QMutexLocker lock(&IGisItem::mutexItems);
1246 
1247     CGisItemRte* rte = dynamic_cast<CGisItemRte*>(getItemByKey(key));
1248     if(nullptr != rte)
1249     {
1250         rte->reverse();
1251     }
1252 }
1253 
calcRteByKey(const IGisItem::key_t & key)1254 void CGisWorkspace::calcRteByKey(const IGisItem::key_t& key)
1255 {
1256     QMutexLocker lock(&IGisItem::mutexItems);
1257 
1258     CGisItemRte* rte = dynamic_cast<CGisItemRte*>(getItemByKey(key));
1259     if(nullptr != rte)
1260     {
1261         rte->calc();
1262     }
1263 }
1264 
resetRteByKey(const IGisItem::key_t & key)1265 void CGisWorkspace::resetRteByKey(const IGisItem::key_t& key)
1266 {
1267     QMutexLocker lock(&IGisItem::mutexItems);
1268 
1269     CGisItemRte* rte = dynamic_cast<CGisItemRte*>(getItemByKey(key));
1270     if(rte != nullptr)
1271     {
1272         rte->reset();
1273     }
1274 }
1275 
1276 
editAreaByKey(const IGisItem::key_t & key)1277 void CGisWorkspace::editAreaByKey(const IGisItem::key_t& key)
1278 {
1279     QMutexLocker lock(&IGisItem::mutexItems);
1280 
1281     CGisItemOvlArea* area = dynamic_cast<CGisItemOvlArea*>(getItemByKey(key));
1282     if(area != nullptr)
1283     {
1284         if(!area->setReadOnlyMode(false))
1285         {
1286             return;
1287         }
1288 
1289         CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
1290         if(canvas != nullptr)
1291         {
1292             canvas->setMouseEditArea(*area);
1293         }
1294     }
1295 }
1296 
makeRteFromWpt(const QList<IGisItem::key_t> & keys)1297 void CGisWorkspace::makeRteFromWpt(const QList<IGisItem::key_t>& keys)
1298 {
1299     QMutexLocker lock(&IGisItem::mutexItems);
1300 
1301     CCreateRouteFromWpt dlg(keys, this);
1302     dlg.exec();
1303 }
1304 
editPrxWpt(const QList<IGisItem::key_t> & keys)1305 void CGisWorkspace::editPrxWpt(const QList<IGisItem::key_t>& keys)
1306 {
1307     QMutexLocker lock(&IGisItem::mutexItems);
1308 
1309     QVariant var;
1310     CInputDialog dlg(this, tr("Enter new proximity range."), var, QVariant(NOFLOAT), IUnit::self().baseUnit);
1311     dlg.setOption(tr("Is no-go area"), false);
1312     if(dlg.exec() != QDialog::Accepted)
1313     {
1314         return;
1315     }
1316 
1317     qreal proximity = var.toDouble() / IUnit::self().baseFactor;
1318     bool isNoGo = dlg.optionIsChecked();
1319     for(const IGisItem::key_t& key : keys)
1320     {
1321         CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(getItemByKey(key));
1322         if(wpt != nullptr)
1323         {
1324             wpt->setProximity(proximity);
1325             if(wpt->isNogo() != isNoGo)
1326             {
1327                 wpt->setNogo(isNoGo);
1328             }
1329         }
1330     }
1331 }
1332 
1333 
draw(QPainter & p,const QPolygonF & viewport,CGisDraw * gis)1334 void CGisWorkspace::draw(QPainter& p, const QPolygonF& viewport, CGisDraw* gis)
1335 {
1336     QFontMetricsF fm(CMainWindow::self().getMapFont());
1337     QList<QRectF> blockedAreas;
1338 
1339     QMutexLocker lock(&IGisItem::mutexItems);
1340     // draw mandatory stuff first
1341     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
1342     {
1343         if(gis->needsRedraw())
1344         {
1345             break;
1346         }
1347 
1348         QTreeWidgetItem* item = treeWks->topLevelItem(i);
1349 
1350         IGisProject* project = dynamic_cast<IGisProject*>(item);
1351         if(nullptr != project)
1352         {
1353             project->drawItem(p, viewport, blockedAreas, gis);
1354             continue;
1355         }
1356         IDevice* device = dynamic_cast<IDevice*>(item);
1357         if(nullptr != device)
1358         {
1359             device->drawItem(p, viewport, blockedAreas, gis);
1360             continue;
1361         }
1362     }
1363 
1364     // draw optional labels second
1365     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
1366     {
1367         if(gis->needsRedraw())
1368         {
1369             break;
1370         }
1371 
1372         QTreeWidgetItem* item = treeWks->topLevelItem(i);
1373 
1374         IGisProject* project = dynamic_cast<IGisProject*>(item);
1375         if(nullptr != project)
1376         {
1377             project->drawLabel(p, viewport, blockedAreas, fm, gis);
1378             continue;
1379         }
1380         IDevice* device = dynamic_cast<IDevice*>(item);
1381         if(nullptr != device)
1382         {
1383             device->drawLabel(p, viewport, blockedAreas, fm, gis);
1384             continue;
1385         }
1386     }
1387 }
1388 
fastDraw(QPainter & p,const QRectF & viewport,CGisDraw * gis)1389 void CGisWorkspace::fastDraw(QPainter& p, const QRectF& viewport, CGisDraw* gis)
1390 {
1391     /*
1392         Mutex locking will make map moving very slow if there are many GIS items
1393         visible. Remove it for now. But I am not sure if that is a good idea.
1394      */
1395     //QMutexLocker lock(&IGisItem::mutexItems);
1396     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
1397     {
1398         QTreeWidgetItem* item = treeWks->topLevelItem(i);
1399 
1400         IGisProject* project = dynamic_cast<IGisProject*>(item);
1401         if(nullptr != project)
1402         {
1403             project->drawItem(p, viewport, gis);
1404             continue;
1405         }
1406         IDevice* device = dynamic_cast<IDevice*>(item);
1407         if(nullptr != device)
1408         {
1409             device->drawItem(p, viewport, gis);
1410             continue;
1411         }
1412     }
1413 
1414 
1415     IGisItem* item = getItemByKey(keyWksSelection);
1416     if(item != nullptr)
1417     {
1418         IGisProject* project = item->getParentProject();
1419         if (project != nullptr && project->isVisible())
1420         {
1421             item->drawHighlight(p);
1422         }
1423     }
1424 }
1425 
1426 
findPolylineCloseBy(const QPointF & pt1,const QPointF & pt2,qint32 threshold,QPolygonF & polyline)1427 bool CGisWorkspace::findPolylineCloseBy(const QPointF& pt1, const QPointF& pt2, qint32 threshold, QPolygonF& polyline)
1428 {
1429     QMutexLocker lock(&IGisItem::mutexItems);
1430     for(int i = 0; i < treeWks->topLevelItemCount(); i++)
1431     {
1432         QTreeWidgetItem* item1 = treeWks->topLevelItem(i);
1433         IGisProject* project = dynamic_cast<IGisProject*>(item1);
1434         if(project)
1435         {
1436             project->findPolylineCloseBy(pt1, pt2, threshold, polyline);
1437         }
1438     }
1439 
1440     return !polyline.isEmpty();
1441 }
1442 
areTagsHidden() const1443 bool CGisWorkspace::areTagsHidden() const
1444 {
1445     return treeWks->isColumnHidden(CGisListWks::eColumnRating);
1446 }
1447 
setTagsHidden(bool hidden)1448 void CGisWorkspace::setTagsHidden(bool hidden)
1449 {
1450     treeWks->setColumnHidden(CGisListWks::eColumnRating, hidden);
1451 }
1452 
tagItemsByKey(const QList<IGisItem::key_t> & keys)1453 void CGisWorkspace::tagItemsByKey(const QList<IGisItem::key_t>& keys)
1454 {
1455     QSet<QString> commonKeywords;
1456 
1457     QList<IGisItem*> items;
1458     bool firstItem = true;
1459     qreal rating = 0;
1460     for(const IGisItem::key_t& key : keys)
1461     {
1462         IGisItem* gisItem = getItemByKey(key);
1463         if(gisItem != nullptr)
1464         {
1465             if(firstItem)
1466             {
1467                 commonKeywords = gisItem->getKeywords();
1468                 rating = gisItem->getRating();
1469                 firstItem = false;
1470             }
1471             else
1472             {
1473                 commonKeywords = commonKeywords.intersect(gisItem->getKeywords());
1474             }
1475 
1476             items << gisItem;
1477         }
1478     }
1479 
1480     CGisItemRate dlg (this, commonKeywords, items.size() > 1 ? 0 : rating);
1481     dlg.exec();
1482 
1483     if(dlg.result() == QDialog::Accepted)
1484     {
1485         for(IGisItem* gisItem : qAsConst(items))
1486         {
1487             if(dlg.getRatingChanged())
1488             {
1489                 gisItem->setRating(dlg.getRating());
1490             }
1491             gisItem->removeKeywords(dlg.getRemovedKeywords());
1492             gisItem->addKeywords(dlg.getAddedKeywords());
1493         }
1494     }
1495 }
1496