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 "CMainWindow.h"
22 #include "device/IDevice.h"
23 #include "gis/CGisDraw.h"
24 #include "gis/CGisListWks.h"
25 #include "gis/CGisWorkspace.h"
26 #include "gis/fit/CFitProject.h"
27 #include "gis/gpx/CGpxProject.h"
28 #include "gis/IGisItem.h"
29 #include "gis/ovl/CGisItemOvlArea.h"
30 #include "gis/prj/CDetailsPrj.h"
31 #include "gis/prj/IGisProject.h"
32 #include "gis/qlb/CQlbProject.h"
33 #include "gis/qms/CQmsProject.h"
34 #include "gis/rte/CGisItemRte.h"
35 #include "gis/rte/router/IRouter.h"
36 #include "gis/slf/CSlfProject.h"
37 #include "gis/suunto/CLogProject.h"
38 #include "gis/suunto/CSmlProject.h"
39 #include "gis/tcx/CTcxProject.h"
40 #include "gis/tcx/CTcxProject.h"
41 #include "gis/trk/CGisItemTrk.h"
42 #include "gis/wpt/CGisItemWpt.h"
43 #include "helpers/CProgressDialog.h"
44 #include "helpers/CSelectCopyAction.h"
45 #include "helpers/CSettings.h"
46 #include "misc.h"
47
48 #include <QtWidgets>
49
50
51 const QString IGisProject::filedialogAllSupported = "All Supported (*.gpx *.GPX *.tcx *.TCX *.sml *.log *.qms *.qlb *.slf *.fit)";
52 const QString IGisProject::filedialogFilterGPX = "GPS Exchange Format (*.gpx *.GPX)";
53 const QString IGisProject::filedialogFilterTCX = "TCX Garmin Proprietary (*.tcx *.TCX)";
54 const QString IGisProject::filedialogFilterSML = "Suunto XML format (*.sml)";
55 const QString IGisProject::filedialogFilterLOG = "Openambit XML format (*.log)";
56 const QString IGisProject::filedialogFilterQLB = "QLandkarte Binary (*.qlb)";
57 const QString IGisProject::filedialogFilterQMS = "QMapShack Binary (*.qms)";
58 const QString IGisProject::filedialogFilterSLF = "Sigma Log Format (*.slf)";
59 const QString IGisProject::filedialogFilterFIT = "Garmin FIT Format (*.fit)";
60 const QString IGisProject::filedialogSaveFilters = filedialogFilterGPX + ";; " + filedialogFilterQLB + ";; " + filedialogFilterQMS + ";; " + filedialogFilterTCX;
61 const QString IGisProject::filedialogLoadFilters = filedialogAllSupported + ";; " + filedialogFilterGPX + ";; " + filedialogFilterTCX + ";; " + filedialogFilterSML + ";; " + filedialogFilterLOG + ";; " + filedialogFilterQLB + ";; " + filedialogFilterQMS + ";; " + filedialogFilterSLF + ";; " + filedialogFilterFIT;
62
63 QString IGisProject::keyUserFocus;
64
IGisProject(type_e type,const QString & filename,CGisListWks * parent)65 IGisProject::IGisProject(type_e type, const QString& filename, CGisListWks* parent)
66 : QTreeWidgetItem(parent)
67 , type(type)
68 , filename(filename)
69 {
70 memset(cntItemsByType, 0, sizeof(cntItemsByType));
71 setCheckState(CGisListWks::eColumnCheckBox, Qt::Checked);
72
73 if(parent)
74 {
75 // move project up the list until there a re only projects, no devices
76 int newIdx = NOIDX;
77 const int myIdx = parent->topLevelItemCount() - 1;
78 for(int i = myIdx - 1; i >= 0; i--)
79 {
80 IDevice* device = dynamic_cast<IDevice*>(parent->topLevelItem(i));
81 if(device != nullptr)
82 {
83 newIdx = i;
84 continue;
85 }
86 break;
87 }
88
89 if(newIdx != NOIDX)
90 {
91 parent->takeTopLevelItem(myIdx);
92 parent->insertTopLevelItem(newIdx, this);
93 }
94 }
95 }
96
IGisProject(type_e type,const QString & filename,IDevice * parent)97 IGisProject::IGisProject(type_e type, const QString& filename, IDevice* parent)
98 : QTreeWidgetItem(parent)
99 , type(type)
100 , filename(filename)
101 {
102 memset(cntItemsByType, 0, sizeof(cntItemsByType));
103 setCheckState(CGisListWks::eColumnCheckBox, Qt::Checked);
104 nameSuffix = parent->getName();
105 }
106
~IGisProject()107 IGisProject::~IGisProject()
108 {
109 delete dlgDetails;
110 if(key == keyUserFocus)
111 {
112 keyUserFocus.clear();
113 }
114 }
115
create(const QString filename,CGisListWks * parent)116 IGisProject* IGisProject::create(const QString filename, CGisListWks* parent)
117 {
118 IGisProject* item = nullptr;
119 QString suffix = QFileInfo(filename).suffix().toLower();
120 if(suffix == "gpx")
121 {
122 item = new CGpxProject(filename, parent);
123 }
124 else if(suffix == "qms")
125 {
126 item = new CQmsProject(filename, parent);
127 }
128 else if(suffix == "slf")
129 {
130 item = new CSlfProject(filename);
131
132 // the CSlfProject does not - as the other C*Project - register itself in the list
133 // of currently opened projects. This is done manually here.
134 if(parent)
135 {
136 parent->addProject(item);
137 }
138 }
139 else if(suffix == "fit")
140 {
141 item = new CFitProject(filename, parent);
142 }
143 else if(suffix == "tcx")
144 {
145 item = new CTcxProject(filename, parent);
146 }
147 else if (suffix == "sml")
148 {
149 item = new CSmlProject(filename, parent);
150 }
151 else if (suffix == "log")
152 {
153 item = new CLogProject(filename, parent);
154 }
155 else if (suffix == "qlb")
156 {
157 item = new CQlbProject(filename, parent);
158 }
159
160 if(item && !item->isValid())
161 {
162 delete item;
163 item = nullptr;
164 }
165
166 return item;
167 }
168
html2Dev(const QString & str)169 QString IGisProject::html2Dev(const QString& str)
170 {
171 return isOnDevice() == IDevice::eTypeGarmin ? IGisItem::removeHtml(str) : str;
172 }
173
askBeforClose()174 bool IGisProject::askBeforClose()
175 {
176 int res = QMessageBox::Ok;
177 if(isChanged())
178 {
179 {
180 CCanvasCursorLock cursorLock(Qt::ArrowCursor, __func__);
181 res = QMessageBox::question(CMainWindow::getBestWidgetForParent(), tr("Save project?"), tr("<h3>%1</h3>The project was changed. Save before closing it?").arg(getName()), QMessageBox::Save | QMessageBox::No | QMessageBox::Abort, QMessageBox::No);
182 }
183
184 if(res == QMessageBox::Save)
185 {
186 // some project cannot be saved
187 if(canSave())
188 {
189 save();
190 }
191 else
192 {
193 saveAs();
194 }
195 }
196 }
197
198 return res == QMessageBox::Abort;
199 }
200
201
isVisible() const202 bool IGisProject::isVisible() const
203 {
204 return checkState(CGisListWks::eColumnCheckBox) == Qt::Checked;
205 }
206
207
genKey() const208 void IGisProject::genKey() const
209 {
210 if(key.isEmpty())
211 {
212 QByteArray buffer;
213 QDataStream stream(&buffer, QIODevice::WriteOnly);
214 stream.setByteOrder(QDataStream::LittleEndian);
215 stream.setVersion(QDataStream::Qt_5_2);
216
217 *this >> stream;
218
219 QCryptographicHash md5(QCryptographicHash::Md5);
220 md5.addData(buffer);
221 key = md5.result().toHex();
222 }
223 }
224
getDeviceKey() const225 QString IGisProject::getDeviceKey() const
226 {
227 IDevice* device = dynamic_cast<IDevice*>(parent());
228 if(device)
229 {
230 return device->getKey();
231 }
232
233 return "";
234 }
235
getIcon() const236 QPixmap IGisProject::getIcon() const
237 {
238 return icon(CGisListWks::eColumnIcon).pixmap(22, 22);
239 }
240
isOnDevice() const241 qint32 IGisProject::isOnDevice() const
242 {
243 IDevice* device = dynamic_cast<IDevice*>(parent());
244 return device != nullptr ? device->type() : IDevice::eTypeNone;
245 }
246
isChanged() const247 bool IGisProject::isChanged() const
248 {
249 return text(CGisListWks::eColumnDecoration).contains("*");
250 }
251
edit()252 void IGisProject::edit()
253 {
254 if(dlgDetails.isNull())
255 {
256 dlgDetails = new CDetailsPrj(*this, 0);
257 dlgDetails->setObjectName(getName());
258 }
259
260 CMainWindow::self().addWidgetToTab(dlgDetails);
261 }
262
setName(const QString & str)263 void IGisProject::setName(const QString& str)
264 {
265 metadata.name = str;
266 setText(CGisListWks::eColumnName, getNameEx());
267 setChanged();
268 }
269
setKeywords(const QString & str)270 void IGisProject::setKeywords(const QString& str)
271 {
272 metadata.keywords = str;
273 setChanged();
274 }
275
setDescription(const QString & str)276 void IGisProject::setDescription(const QString& str)
277 {
278 metadata.desc = str;
279 setChanged();
280 }
281
setLinks(const QList<IGisItem::link_t> & links)282 void IGisProject::setLinks(const QList<IGisItem::link_t>& links)
283 {
284 metadata.links = links;
285 setChanged();
286 }
287
setSortingRoadbook(sorting_roadbook_e s)288 void IGisProject::setSortingRoadbook(sorting_roadbook_e s)
289 {
290 changedRoadbookMode = (s != sortingRoadbook);
291 sortingRoadbook = s;
292 if(changedRoadbookMode)
293 {
294 setChanged();
295 }
296 }
297
setSortingFolder(sorting_folder_e s)298 void IGisProject::setSortingFolder(sorting_folder_e s)
299 {
300 bool changed = (s != sortingFolder);
301 sortingFolder = s;
302 sortItems();
303
304 if(changed)
305 {
306 setChanged();
307 if(dlgDetails != nullptr)
308 {
309 dlgDetails->updateData();
310 }
311 }
312 }
313
setChanged()314 void IGisProject::setChanged()
315 {
316 if(autoSave)
317 {
318 if(!autoSavePending)
319 {
320 autoSavePending = true;
321 CGisWorkspace::self().postEventForWks(new CEvtA2WSave(getKey()));
322 }
323 }
324
325 if(autoSyncToDev)
326 {
327 if(!autoSyncToDevPending)
328 {
329 autoSyncToDevPending = true;
330 CGisWorkspace::self().postEventForWks(new CEvtA2WSync(getKey()));
331 }
332 }
333 updateDecoration(false);
334 updateItems();
335 }
336
setAutoSave(bool on)337 void IGisProject::setAutoSave(bool on)
338 {
339 // make sure project is saved one more time to remove autoSave flag in storage
340 if(!on && autoSave)
341 {
342 CGisWorkspace::self().postEventForWks(new CEvtA2WSave(getKey()));
343 }
344
345 autoSave = on;
346 setChanged();
347 }
348
setAutoSyncToDevice(bool yes)349 void IGisProject::setAutoSyncToDevice(bool yes)
350 {
351 autoSyncToDev = yes;
352 updateDecoration();
353 }
354
switchOnCorrelation()355 void IGisProject::switchOnCorrelation()
356 {
357 noCorrelation = false;
358 hashTrkWpt[0].clear();
359 hashTrkWpt[1].clear();
360 updateItems();
361 }
362
updateItems()363 void IGisProject::updateItems()
364 {
365 if(noUpdate)
366 {
367 return;
368 }
369
370 sortItems();
371 updateItemCounters();
372
373 if(noCorrelation)
374 {
375 return;
376 }
377
378 if(!changedRoadbookMode)
379 {
380 if((hashTrkWpt[0] == hashTrkWpt[1]) || (getItemCountByType(IGisItem::eTypeTrk) == 0))
381 {
382 return;
383 }
384 }
385 changedRoadbookMode = false;
386
387
388 quint32 total = cntTrkPts * cntWpts;
389 quint32 current = 0;
390
391 PROGRESS_SETUP(tr("%1: Correlate tracks and waypoints.").arg(getName()), 0, total, CMainWindow::getBestWidgetForParent());
392
393 for(int i = 0; i < childCount(); i++)
394 {
395 CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(child(i));
396 if(trk)
397 {
398 trk->findWaypointsCloseBy(progress, current);
399 if(progress.wasCanceled())
400 {
401 QString msg = tr("<h3>%1</h3>Did that take too long for you? Do you want to skip correlation of tracks and waypoints for this project in the future?").arg(getNameEx());
402 int res = QMessageBox::question(&progress, tr("Canceled correlation..."), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
403 noCorrelation = res == QMessageBox::Yes;
404 break;
405 }
406 }
407 }
408
409 if(dlgDetails != nullptr)
410 {
411 dlgDetails->updateData();
412 }
413 }
414
save()415 bool IGisProject::save()
416 {
417 if(!canSave())
418 {
419 qWarning() << "This should never be called!";
420 return false;
421 }
422
423 return saveAs(filename, getFileDialogFilter());
424 }
425
saveAs(QString fn,QString filter)426 bool IGisProject::saveAs(QString fn, QString filter)
427 {
428 SETTINGS;
429
430 if(fn.isEmpty())
431 {
432 QString path = cfg.value("Paths/lastGisPath", QDir::homePath()).toString();
433
434 // guess the correct extension:
435 // by default use the extension provided by the current format,
436 // otherwise use gpx
437 QString ext = getFileExtension();
438 filter = getFileDialogFilter();
439 if(ext.isEmpty() || !canSave())
440 {
441 ext = "gpx";
442 filter = IGisProject::filedialogFilterGPX;
443 }
444 path += "/" + getName() + "." + ext;
445
446
447 fn = QFileDialog::getSaveFileName(CMainWindow::getBestWidgetForParent(), tr("Save \"%1\" to...").arg(getName()), path, filedialogSaveFilters, &filter);
448
449 if(fn.isEmpty())
450 {
451 return false;
452 }
453 }
454
455 bool res = false;
456 if(filter == getFileDialogFilter())
457 {
458 filename = fn;
459 setupName(QFileInfo(fn).completeBaseName());
460 }
461
462 if(filter == filedialogFilterGPX)
463 {
464 res = CGpxProject::saveAs(fn, *this, false);
465 }
466 else if(filter == filedialogFilterQMS)
467 {
468 res = CQmsProject::saveAs(fn, *this);
469 }
470 else if (filter == filedialogFilterTCX)
471 {
472 res = CTcxProject::saveAs(fn, *this);
473 }
474 else
475 {
476 return false;
477 }
478
479 if(res && filter == getFileDialogFilter())
480 {
481 markAsSaved();
482 }
483
484 QString path = QFileInfo(fn).absolutePath();
485 cfg.setValue("Paths/lastGisPath", path);
486
487 return res;
488 }
489
saveAsStrictGpx11()490 bool IGisProject::saveAsStrictGpx11()
491 {
492 SETTINGS;
493
494 QString fn;
495 QString path = cfg.value("Paths/lastGisPath", QDir::homePath()).toString();
496
497 // guess the correct extension:
498 // by default use the extension provided by the current format,
499 // otherwise use gpx
500 QString ext = "gpx";
501 QString filter = IGisProject::filedialogFilterGPX;
502 path += "/" + getName() + "." + ext;
503
504 fn = QFileDialog::getSaveFileName(CMainWindow::getBestWidgetForParent(), tr("Save \"%1\" to...").arg(getName()), path, "Strict GPX V 1.1 (*.gpx *.GPX)", &filter);
505
506 if(fn.isEmpty())
507 {
508 return false;
509 }
510
511 bool res = CGpxProject::saveAs(fn, *this, true);
512
513 path = QFileInfo(fn).absolutePath();
514 cfg.setValue("Paths/lastGisPath", path);
515
516 return res;
517 }
518
setupName(const QString & defaultName)519 void IGisProject::setupName(const QString& defaultName)
520 {
521 if(metadata.name.isEmpty())
522 {
523 metadata.name = defaultName;
524 }
525 setText(CGisListWks::eColumnName, getName());
526 }
527
markAsSaved()528 void IGisProject::markAsSaved()
529 {
530 updateDecoration(true);
531 for(int i = 0; i < childCount(); i++)
532 {
533 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
534 if(nullptr == item)
535 {
536 continue;
537 }
538 item->updateDecoration(IGisItem::eMarkNone, IGisItem::eMarkChanged);
539 }
540 }
541
getName() const542 QString IGisProject::getName() const
543 {
544 return metadata.name;
545 }
546
getNameEx() const547 QString IGisProject::getNameEx() const
548 {
549 if(nameSuffix.isEmpty())
550 {
551 return metadata.name;
552 }
553 else
554 {
555 return metadata.name + " @ " + nameSuffix;
556 }
557 }
558
559
getInfo() const560 QString IGisProject::getInfo() const
561 {
562 QString str = metadata.name.isEmpty() ? text(CGisListWks::eColumnName) : metadata.name;
563 str = "<div style='font-weight: bold;'>" + str + "</div>";
564
565 if(metadata.time.isValid())
566 {
567 str += "<br/>\n";
568 str += IUnit::datetime2string(metadata.time, false);
569 }
570
571
572 QString desc = IGisItem::removeHtml(metadata.desc).simplified();
573 if(!desc.isEmpty())
574 {
575 str += "<br/>\n";
576
577 if(desc.count() < 100)
578 {
579 str += desc;
580 }
581 else
582 {
583 str += desc.left(97) + "...";
584 }
585 }
586
587 if(!filename.isEmpty())
588 {
589 str += tr("<br/>\nFilename: %1").arg(filename);
590 }
591
592 if(cntItemsByType[IGisItem::eTypeWpt])
593 {
594 str += "<br/>\n" + tr("Waypoints: %1").arg(cntItemsByType[IGisItem::eTypeWpt]);
595 }
596 if(cntItemsByType[IGisItem::eTypeTrk])
597 {
598 str += "<br/>\n" + tr("Tracks: %1").arg(cntItemsByType[IGisItem::eTypeTrk]);
599 }
600 if(cntItemsByType[IGisItem::eTypeRte])
601 {
602 str += "<br/>\n" + tr("Routes: %1").arg(cntItemsByType[IGisItem::eTypeRte]);
603 }
604 if(cntItemsByType[IGisItem::eTypeOvl])
605 {
606 str += "<br/>\n" + tr("Areas: %1").arg(cntItemsByType[IGisItem::eTypeOvl]);
607 }
608
609 return str;
610 }
611
612
getItemByKey(const IGisItem::key_t & key)613 IGisItem* IGisProject::getItemByKey(const IGisItem::key_t& key)
614 {
615 for(int i = 0; i < childCount(); i++)
616 {
617 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
618 if(nullptr == item)
619 {
620 continue;
621 }
622
623 if(item->getKey() == key)
624 {
625 return item;
626 }
627 }
628 return nullptr;
629 }
630
getItemsByKeys(const QList<IGisItem::key_t> & keys,QList<IGisItem * > & items)631 void IGisProject::getItemsByKeys(const QList<IGisItem::key_t>& keys, QList<IGisItem*>& items)
632 {
633 for(int i = 0; i < childCount(); i++)
634 {
635 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
636 if(nullptr == item)
637 {
638 continue;
639 }
640
641 if(keys.contains(item->getKey()))
642 {
643 items << item;
644 }
645 }
646 }
647
getItemsByPos(const QPointF & pos,QList<IGisItem * > & items)648 void IGisProject::getItemsByPos(const QPointF& pos, QList<IGisItem*>& items)
649 {
650 if(!isVisible())
651 {
652 return;
653 }
654
655 for(int i = 0; i < childCount(); i++)
656 {
657 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
658 if(nullptr == item || item->isHidden())
659 {
660 continue;
661 }
662
663 if(item->isCloseTo(pos))
664 {
665 items << item;
666 }
667 }
668 }
669
getItemsByArea(const QRectF & area,IGisItem::selflags_t flags,QList<IGisItem * > & items)670 void IGisProject::getItemsByArea(const QRectF& area, IGisItem::selflags_t flags, QList<IGisItem*>& items)
671 {
672 if(!isVisible())
673 {
674 return;
675 }
676
677 for(int i = 0; i < childCount(); i++)
678 {
679 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
680 if(nullptr == item || item->isHidden())
681 {
682 continue;
683 }
684
685 if(item->isWithin(area, flags))
686 {
687 items << item;
688 }
689 }
690 }
691
getNogoAreas(QList<IGisItem * > & nogos) const692 void IGisProject::getNogoAreas(QList<IGisItem*>& nogos) const
693 {
694 if(!isVisible())
695 {
696 return;
697 }
698
699 for(int i = 0; i < childCount(); i++)
700 {
701 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
702 if (item != nullptr && !item->isHidden() && item->isNogo())
703 {
704 nogos << item;
705 }
706 }
707 }
708
709
mouseMove(const QPointF & pos)710 void IGisProject::mouseMove(const QPointF& pos)
711 {
712 if(!isVisible())
713 {
714 return;
715 }
716
717 for(int i = 0; i < childCount(); i++)
718 {
719 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
720 if(nullptr == item || item->isHidden())
721 {
722 continue;
723 }
724
725 item->mouseMove(pos);
726 }
727 }
728
729
delItemByKey(const IGisItem::key_t & key,QMessageBox::StandardButtons & last)730 bool IGisProject::delItemByKey(const IGisItem::key_t& key, QMessageBox::StandardButtons& last)
731 {
732 for(int i = childCount(); i > 0; i--)
733 {
734 IGisItem* item = dynamic_cast<IGisItem*>(child(i - 1));
735 if(nullptr == item )
736 {
737 continue;
738 }
739
740 if(item->getKey() == key)
741 {
742 if(last != QMessageBox::YesToAll)
743 {
744 QString msg = tr("Are you sure you want to delete '%1' from project '%2'?").arg(item->getName(), text(CGisListWks::eColumnName));
745 last = QMessageBox::question(CMainWindow::getBestWidgetForParent(), tr("Delete..."), msg, QMessageBox::YesToAll | QMessageBox::Cancel | QMessageBox::Ok | QMessageBox::No, QMessageBox::Ok);
746 if((last == QMessageBox::No) || (last == QMessageBox::Cancel))
747 {
748 // as each item in the project has to be unique, we can stop searching.
749 return false;
750 }
751 }
752 delete item;
753
754 /*
755 Database projects are a bit different. Deleting an item does not really
756 mean the project is changed as the item is still stored in the database.
757 */
758 if(type != eTypeDb)
759 {
760 setChanged();
761 }
762
763 // as each item in the project has to be unique, we can stop searching.
764 return true;
765 }
766 }
767 return false;
768 }
769
editItemByKey(const IGisItem::key_t & key)770 void IGisProject::editItemByKey(const IGisItem::key_t& key)
771 {
772 for(int i = childCount(); i > 0; i--)
773 {
774 IGisItem* item = dynamic_cast<IGisItem*>(child(i - 1));
775 if(nullptr == item)
776 {
777 continue;
778 }
779
780 if(item->getKey() == key)
781 {
782 item->edit();
783 }
784 }
785 }
786
787
insertCopyOfItem(IGisItem * item,int off,CSelectCopyAction::result_e & lastResult)788 void IGisProject::insertCopyOfItem(IGisItem* item, int off, CSelectCopyAction::result_e& lastResult)
789 {
790 bool clone = false;
791 IGisItem::key_t key = item->getKey();
792 key.project = getKey();
793 key.device = getDeviceKey();
794
795 IGisItem* item2 = getItemByKey(key);
796 if(item2 != nullptr)
797 {
798 CSelectCopyAction::result_e result = lastResult;
799 if(lastResult == CSelectCopyAction::eResultNone)
800 {
801 CSelectCopyAction dlg(item, item2, CMainWindow::getBestWidgetForParent());
802 dlg.exec();
803 result = dlg.getResult();
804 if(dlg.allOthersToo())
805 {
806 lastResult = result;
807 }
808 }
809
810 if(result == CSelectCopyAction::eResultSkip)
811 {
812 return;
813 }
814 if(result == CSelectCopyAction::eResultNone)
815 {
816 return;
817 }
818 if(result == CSelectCopyAction::eResultClone)
819 {
820 clone = true;
821 }
822 else
823 {
824 // replace item2 with item
825 if(item != item2)
826 {
827 delete item2;
828 }
829 else
830 {
831 // replacing an item with itself does not make sense
832 return;
833 }
834 }
835 }
836
837 switch(item->type())
838 {
839 case IGisItem::eTypeTrk:
840 {
841 CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(item);
842 if(trk != nullptr)
843 {
844 CGisItemTrk* newTrk = new CGisItemTrk(*trk, this, off, clone);
845 // if the track is on a device, remove hidden trackpoints
846 if(isOnDevice())
847 {
848 newTrk->filterDelete();
849 }
850 }
851 break;
852 }
853
854 case IGisItem::eTypeWpt:
855 {
856 CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(item);
857 if(wpt != nullptr)
858 {
859 new CGisItemWpt(*wpt, this, off, clone);
860 }
861 break;
862 }
863
864 case IGisItem::eTypeRte:
865 {
866 CGisItemRte* rte = dynamic_cast<CGisItemRte*>(item);
867 if(rte != nullptr)
868 {
869 new CGisItemRte(*rte, this, off, clone);
870 }
871 break;
872 }
873
874 case IGisItem::eTypeOvl:
875 {
876 CGisItemOvlArea* area = dynamic_cast<CGisItemOvlArea*>(item);
877 if(area != nullptr)
878 {
879 new CGisItemOvlArea(*area, this, off, clone);
880 }
881 break;
882 }
883 }
884 }
885
drawItem(QPainter & p,const QPolygonF & viewport,QList<QRectF> & blockedAreas,CGisDraw * gis)886 void IGisProject::drawItem(QPainter& p, const QPolygonF& viewport, QList<QRectF>& blockedAreas, CGisDraw* gis)
887 {
888 if(!isVisible())
889 {
890 return;
891 }
892
893 for(int i = 0; i < childCount(); i++)
894 {
895 if(gis->needsRedraw())
896 {
897 break;
898 }
899
900 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
901 if(nullptr == item || item->isHidden())
902 {
903 continue;
904 }
905
906 item->drawItem(p, viewport, blockedAreas, gis);
907 }
908 }
909
drawItem(QPainter & p,const QRectF & viewport,CGisDraw * gis)910 void IGisProject::drawItem(QPainter& p, const QRectF& viewport, CGisDraw* gis)
911 {
912 if(!isVisible())
913 {
914 return;
915 }
916
917 for(int i = 0; i < childCount(); i++)
918 {
919 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
920 if(nullptr == item || item->isHidden())
921 {
922 continue;
923 }
924
925
926 item->drawItem(p, viewport, gis);
927 }
928 }
929
drawLabel(QPainter & p,const QPolygonF & viewport,QList<QRectF> & blockedAreas,const QFontMetricsF & fm,CGisDraw * gis)930 void IGisProject::drawLabel(QPainter& p, const QPolygonF& viewport, QList<QRectF>& blockedAreas, const QFontMetricsF& fm, CGisDraw* gis)
931 {
932 if(!isVisible())
933 {
934 return;
935 }
936
937 for(int i = 0; i < childCount(); i++)
938 {
939 if(gis->needsRedraw())
940 {
941 break;
942 }
943
944 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
945 if(nullptr == item || item->isHidden())
946 {
947 continue;
948 }
949
950 item->drawLabel(p, viewport, blockedAreas, fm, gis);
951 }
952 }
953
mount()954 void IGisProject::mount()
955 {
956 if(!isOnDevice())
957 {
958 return;
959 }
960 IDevice* device = dynamic_cast<IDevice*>(parent());
961 if(device)
962 {
963 device->mount();
964 }
965 }
966
umount()967 void IGisProject::umount()
968 {
969 if(!isOnDevice())
970 {
971 return;
972 }
973 IDevice* device = dynamic_cast<IDevice*>(parent());
974 if(device)
975 {
976 device->umount();
977 }
978 }
979
remove()980 bool IGisProject::remove()
981 {
982 CProjectMountLock mountLock(*this);
983
984 /*
985 Check if parent is a device and give it a chance to take care of data.
986
987 e.g. Garmin devices remove images attached to the project.
988 */
989 IDevice* device = dynamic_cast<IDevice*>(parent());
990 if(device)
991 {
992 device->aboutToRemoveProject(this);
993 }
994
995 QFileInfo fi(filename);
996 if(fi.isFile())
997 {
998 QFile::remove(filename);
999 }
1000 else if(fi.isDir())
1001 {
1002 QDir(filename).removeRecursively();
1003 }
1004
1005 return true;
1006 }
1007
updateItemCounters()1008 void IGisProject::updateItemCounters()
1009 {
1010 // count number of items by type
1011 memset(cntItemsByType, 0, sizeof(cntItemsByType));
1012 cntTrkPts = 0;
1013 cntWpts = 0;
1014 totalDistance = 0;
1015 totalAscent = 0;
1016 totalDescent = 0;
1017 totalElapsedSeconds = 0;
1018 totalElapsedSecondsMoving = 0;
1019
1020 QByteArray buffer;
1021 QDataStream stream(&buffer, QIODevice::WriteOnly);
1022 stream.setByteOrder(QDataStream::LittleEndian);
1023 stream.setVersion(QDataStream::Qt_5_2);
1024
1025 for(int i = 0; i < childCount(); i++)
1026 {
1027 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
1028 if(nullptr == item)
1029 {
1030 continue;
1031 }
1032
1033 cntItemsByType[item->type()]++;
1034
1035 CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(item);
1036 if(trk)
1037 {
1038 cntTrkPts += trk->getNumberOfVisiblePoints();
1039 totalDistance += trk->getTotalDistance();
1040 totalAscent += trk->getTotalAscent();
1041 totalDescent += trk->getTotalDescent();
1042 totalElapsedSeconds += trk->getTotalElapsedSeconds();
1043 totalElapsedSecondsMoving += trk->getTotalElapsedSecondsMoving();
1044 stream << trk->getHash();
1045 }
1046
1047 CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(item);
1048 if(wpt)
1049 {
1050 cntWpts++;
1051 stream << wpt->getHash();
1052 }
1053 }
1054 QCryptographicHash md5(QCryptographicHash::Md5);
1055 md5.addData(buffer);
1056
1057 hashTrkWpt[1] = hashTrkWpt[0];
1058 hashTrkWpt[0] = md5.result().toHex();
1059 }
1060
blockUpdateItems(bool yes)1061 void IGisProject::blockUpdateItems(bool yes)
1062 {
1063 noUpdate = yes;
1064 if(noUpdate == false)
1065 {
1066 updateItems();
1067 }
1068 }
1069
updateDecoration()1070 void IGisProject::updateDecoration()
1071 {
1072 int N = childCount();
1073 bool saved = true;
1074
1075 for(int i = 0; i < N; i++)
1076 {
1077 IGisItem* item = dynamic_cast<IGisItem*>(child(i));
1078 if(nullptr == item)
1079 {
1080 continue;
1081 }
1082 if(item->isChanged())
1083 {
1084 saved = false;
1085 break;
1086 }
1087 }
1088 updateDecoration(saved);
1089 }
1090
updateDecoration(bool saved)1091 void IGisProject::updateDecoration(bool saved)
1092 {
1093 QString str = autoSave ? "A" : saved ? "" : "*";
1094 if(autoSyncToDev)
1095 {
1096 str += "S";
1097 }
1098 setText(CGisListWks::eColumnDecoration, str);
1099 }
1100
sortItems()1101 void IGisProject::sortItems()
1102 {
1103 QList<IGisItem*> trks;
1104 QList<IGisItem*> rtes;
1105 QList<IGisItem*> wpts;
1106 QList<IGisItem*> ovls;
1107
1108 QList<QTreeWidgetItem*> items = takeChildren();
1109 QList<QTreeWidgetItem*> others; //For example Search
1110 for(QTreeWidgetItem* item : qAsConst(items))
1111 {
1112 CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(item);
1113 if(trk != nullptr)
1114 {
1115 trks << trk;
1116 continue;
1117 }
1118
1119 CGisItemRte* rte = dynamic_cast<CGisItemRte*>(item);
1120 if(rte != nullptr)
1121 {
1122 rtes << rte;
1123 continue;
1124 }
1125
1126 CGisItemWpt* wpt = dynamic_cast<CGisItemWpt*>(item);
1127 if(wpt != nullptr)
1128 {
1129 wpts << wpt;
1130 continue;
1131 }
1132
1133 CGisItemOvlArea* ovl = dynamic_cast<CGisItemOvlArea*>(item);
1134 if(ovl != nullptr)
1135 {
1136 ovls << ovl;
1137 continue;
1138 }
1139
1140 others << item;
1141 }
1142
1143 sortItems(trks);
1144 sortItems(rtes);
1145 sortItems(wpts);
1146 sortItems(ovls);
1147
1148 items.clear();
1149 items << others;
1150 for(IGisItem* item : qAsConst(trks))
1151 {
1152 items << item;
1153 }
1154 for(IGisItem* item : qAsConst(rtes))
1155 {
1156 items << item;
1157 }
1158 for(IGisItem* item : qAsConst(wpts))
1159 {
1160 items << item;
1161 }
1162 for(IGisItem* item : qAsConst(ovls))
1163 {
1164 items << item;
1165 }
1166
1167 addChildren(items);
1168 if(projectFilter != nullptr)
1169 {
1170 projectFilter->showLineEdit(&projectSearch);
1171 }
1172 applyFilters();
1173 }
1174
1175
sortByTime(IGisItem * item1,IGisItem * item2)1176 static bool sortByTime(IGisItem* item1, IGisItem* item2)
1177 {
1178 const QDateTime& t1 = item1->getTimestamp();
1179 const QDateTime& t2 = item2->getTimestamp();
1180
1181 // avoid jumping items due to invalid timestamps
1182 if(!t1.isValid() || !t2.isValid())
1183 {
1184 return sortByName<IGisItem>(item1, item2);
1185 }
1186
1187 return t1 < t2;
1188 }
1189
sortItems(QList<IGisItem * > & items) const1190 void IGisProject::sortItems(QList<IGisItem*>& items) const
1191 {
1192 switch(sortingFolder)
1193 {
1194 case IGisProject::eSortFolderName:
1195 qSort(items.begin(), items.end(), &sortByName<IGisItem>);
1196 break;
1197
1198 case IGisProject::eSortFolderTime:
1199 qSort(items.begin(), items.end(), &sortByTime);
1200 break;
1201
1202 case IGisProject::eSortFolderRating:
1203 qSort(items.begin(), items.end(), [](IGisItem* a, IGisItem* b){return a->getRating() > b->getRating();});
1204 break;
1205 }
1206 }
1207
setProjectFilter(const CSearch & search)1208 void IGisProject::setProjectFilter(const CSearch& search)
1209 {
1210 projectSearch = search;
1211 applyFilters();
1212 }
1213
setWorkspaceFilter(const CSearch & search)1214 void IGisProject::setWorkspaceFilter(const CSearch& search)
1215 {
1216 workspaceSearch = search;
1217 applyFilters();
1218 }
1219
applyFilters()1220 void IGisProject::applyFilters()
1221 {
1222 const int N = childCount();
1223
1224 for(int n = 0; n < N; n++)
1225 {
1226 IGisItem* item = dynamic_cast<IGisItem*>(child(n));
1227 if(item == nullptr)
1228 {
1229 continue;
1230 }
1231
1232 bool projectFilterResult = projectSearch.getSearchResult(item);
1233 bool workspaceFilterResult = workspaceSearch.getSearchResult(item);
1234
1235 item->setHidden(!(projectFilterResult && workspaceFilterResult));//get search result returns wether the object matches
1236 }
1237 }
1238
findPolylineCloseBy(const QPointF & pt1,const QPointF & pt2,qint32 & threshold,QPolygonF & polyline)1239 bool IGisProject::findPolylineCloseBy(const QPointF& pt1, const QPointF& pt2, qint32& threshold, QPolygonF& polyline)
1240 {
1241 const int N = childCount();
1242 for(int n = 0; n < N; n++)
1243 {
1244 CGisItemTrk* trk = dynamic_cast<CGisItemTrk*>(child(n));
1245 if(trk != nullptr)
1246 {
1247 trk->findPolylineCloseBy(pt1, pt2, threshold, polyline);
1248 }
1249 }
1250 return !polyline.isEmpty();
1251 }
1252
gainUserFocus(bool yes)1253 void IGisProject::gainUserFocus(bool yes)
1254 {
1255 if(yes)
1256 {
1257 setIcon(CGisListWks::eColumnName, QIcon("://icons/32x32/Focus.png"));
1258 keyUserFocus = key;
1259 }
1260 else
1261 {
1262 setIcon(CGisListWks::eColumnName, QIcon());
1263 keyUserFocus.clear();
1264 }
1265 }
1266
filterProject(bool filter)1267 CProjectFilterItem* IGisProject::filterProject(bool filter)
1268 {
1269 if(filter)
1270 {
1271 if(projectFilter == nullptr)
1272 {
1273 projectFilter = new CProjectFilterItem(this);
1274 insertChild(0, projectFilter);
1275 }
1276 //Set expanded anyways to show that search exists
1277 projectFilter->showLineEdit();
1278 setExpanded(true);
1279 }
1280 else
1281 {
1282 removeChild(projectFilter);
1283 delete projectFilter;
1284 projectFilter = nullptr;
1285 projectSearch = CSearch("");
1286 }
1287 sortItems();
1288 return projectFilter;
1289 }
1290