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 #include "gis/CGisDraw.h"
24 #include "gis/CGisListWks.h"
25 #include "gis/GeoMath.h"
26 #include "gis/prj/IGisProject.h"
27 #include "gis/Poi.h"
28 #include "gis/wpt/CDetailsGeoCache.h"
29 #include "gis/wpt/CDetailsWpt.h"
30 #include "gis/wpt/CGisItemWpt.h"
31 #include "gis/wpt/CScrOptWpt.h"
32 #include "gis/wpt/CScrOptWptRadius.h"
33 #include "gis/wpt/CSetupIconAndName.h"
34 #include "helpers/CDraw.h"
35 #include "helpers/CSettings.h"
36 #include "helpers/CWptIconManager.h"
37 #include "mouse/IMouse.h"
38 #include "units/IUnit.h"
39 
40 #include <QPainterPath>
41 #include <QtWidgets>
42 #include <QtXml>
43 #include <QPainterPath>
44 
45 IGisItem::key_t CGisItemWpt::keyUserFocus;
46 QMap<searchProperty_e, CGisItemWpt::fSearch> CGisItemWpt::keywordLambdaMap;
47 QList<QString> CGisItemWpt::geocache_t::attributeMeaningsTranslated;
48 
49 
CGisItemWpt(const QPointF & pos,qreal ele,const QDateTime & time,const QString & name,const QString & icon,IGisProject * project)50 CGisItemWpt::CGisItemWpt(const QPointF& pos, qreal ele, const QDateTime& time, const QString& name, const QString& icon, IGisProject* project)
51     : IGisItem(project, eTypeWpt, NOIDX)
52 {
53     wpt.name = name;
54     wpt.sym = icon;
55     wpt.lon = pos.x();
56     wpt.lat = pos.y();
57     wpt.ele = (ele == NOFLOAT) ? NOINT : qRound(ele);
58     wpt.time = time;
59 
60     detBoundingRect();
61 
62     setupHistory();
63     updateDecoration(eMarkNone, eMarkNone);
64 }
65 
66 /// used to add a new waypoint
CGisItemWpt(const QPointF & pos,const QString & name,const QString & icon,IGisProject * project)67 CGisItemWpt::CGisItemWpt(const QPointF& pos, const QString& name, const QString& icon, IGisProject* project)
68     : CGisItemWpt(pos, NOFLOAT, QDateTime::currentDateTimeUtc(), name, icon, project)
69 {
70     flags = eFlagCreatedInQms | eFlagWriteAllowed;
71     qreal ele = CMainWindow::self().getElevationAt(pos * DEG_TO_RAD);
72     wpt.ele = (ele == NOFLOAT) ? NOINT : qRound(ele);
73 
74     detBoundingRect();
75 
76     setupHistory();
77     updateDecoration(eMarkChanged, eMarkNone);
78 }
79 
80 /// used to move a copy of waypoint
CGisItemWpt(const QPointF & pos,const CGisItemWpt & parentWpt,IGisProject * project)81 CGisItemWpt::CGisItemWpt(const QPointF& pos, const CGisItemWpt& parentWpt, IGisProject* project)
82     : IGisItem(project, eTypeWpt, NOIDX)
83 {
84     *this = parentWpt;
85     wpt.lon = pos.x();
86     wpt.lat = pos.y();
87     wpt.time = QDateTime::currentDateTimeUtc();
88 
89     key.clear();
90     history.events.clear();
91     flags = eFlagCreatedInQms | eFlagWriteAllowed;
92 
93     qreal ele = CMainWindow::self().getElevationAt(pos * DEG_TO_RAD);
94     wpt.ele = (ele == NOFLOAT) ? NOINT : qRound(ele);
95 
96     setNogoFlag(parentWpt.isNogo());
97 
98     detBoundingRect();
99 
100     setupHistory();
101     updateDecoration(eMarkChanged, eMarkNone);
102 }
103 
104 /// used to create a copy of waypoint with new parent
CGisItemWpt(const CGisItemWpt & parentWpt,IGisProject * project,int idx,bool clone)105 CGisItemWpt::CGisItemWpt(const CGisItemWpt& parentWpt, IGisProject* project, int idx, bool clone)
106     : IGisItem(project, eTypeWpt, idx)
107 {
108     history = parentWpt.history;
109     loadHistory(history.histIdxCurrent);
110 
111     if(clone)
112     {
113         wpt.name += tr("_Clone");
114         key.clear();
115         history.events.clear();
116         setupHistory();
117     }
118 
119     if(parentWpt.isOnDevice() || !parentWpt.isReadOnly())
120     {
121         flags |= eFlagWriteAllowed;
122     }
123     else
124     {
125         flags &= ~eFlagWriteAllowed;
126     }
127 
128     setNogoFlag(parentWpt.isNogo());
129 
130     detBoundingRect();
131     updateDecoration(eMarkChanged, eMarkNone);
132 }
133 
134 /// used to create waypoint from GPX file
CGisItemWpt(const QDomNode & xml,IGisProject * project)135 CGisItemWpt::CGisItemWpt(const QDomNode& xml, IGisProject* project)
136     : IGisItem(project, eTypeWpt, project->childCount())
137 {
138     readGpx(xml);
139     detBoundingRect();
140 
141     CGisItemWpt::genKey();
142     setupHistory();
143     updateDecoration(eMarkNone, eMarkNone);
144 }
145 
CGisItemWpt(const history_t & hist,const QString & dbHash,IGisProject * project)146 CGisItemWpt::CGisItemWpt(const history_t& hist, const QString& dbHash, IGisProject* project)
147     : IGisItem(project, eTypeWpt, project->childCount())
148 {
149     history = hist;
150     loadHistory(hist.histIdxCurrent);
151     detBoundingRect();
152     if(!dbHash.isEmpty())
153     {
154         lastDatabaseHash = dbHash;
155     }
156 }
157 
CGisItemWpt(quint64 id,QSqlDatabase & db,IGisProject * project)158 CGisItemWpt::CGisItemWpt(quint64 id, QSqlDatabase& db, IGisProject* project)
159     : IGisItem(project, eTypeWpt, NOIDX)
160 {
161     loadFromDb(id, db);
162     detBoundingRect();
163 }
164 
CGisItemWpt(const CTwoNavProject::wpt_t & tnvWpt,IGisProject * project)165 CGisItemWpt::CGisItemWpt(const CTwoNavProject::wpt_t& tnvWpt, IGisProject* project)
166     : IGisItem(project, eTypeWpt, NOIDX)
167 {
168     readTwoNav(tnvWpt);
169     detBoundingRect();
170 
171     CGisItemWpt::genKey();
172     setupHistory();
173     updateDecoration(eMarkNone, eMarkNone);
174 }
175 
CGisItemWpt(CFitStream & stream,IGisProject * project)176 CGisItemWpt::CGisItemWpt(CFitStream& stream, IGisProject* project)
177     : IGisItem(project, eTypeWpt, NOIDX)
178     , proximity(NOFLOAT)
179     , posScreen(NOPOINTF)
180 {
181     readWptFromFit(stream);
182     detBoundingRect();
183 
184     CGisItemWpt::genKey();
185     setupHistory();
186     updateDecoration(eMarkNone, eMarkNone);
187 }
188 
~CGisItemWpt()189 CGisItemWpt::~CGisItemWpt()
190 {
191 }
192 
createClone()193 IGisItem* CGisItemWpt::createClone()
194 {
195     int idx = -1;
196     IGisProject* project = getParentProject();
197     if(project)
198     {
199         idx = project->indexOfChild(this);
200     }
201     return new CGisItemWpt(*this, project, idx, true);
202 }
203 
204 
setSymbol()205 void CGisItemWpt::setSymbol()
206 {
207     setIcon();
208 }
209 
genKey() const210 void CGisItemWpt::genKey() const
211 {
212     if(geocache.hasData)
213     {
214         key.item = QString::number(geocache.id);
215     }
216     IGisItem::genKey();
217 }
218 
getLastName(const QString & name)219 QString CGisItemWpt::getLastName(const QString& name)
220 {
221     SETTINGS;
222     QString lastName = name;
223 
224     if(lastName.isEmpty())
225     {
226         lastName = cfg.value("Waypoint/lastName", "wpt").toString();
227     }
228 
229     const int s = lastName.size();
230     if(s != 0)
231     {
232         int idx;
233         for(idx = s; idx > 0; idx--)
234         {
235             if(!lastName[idx - 1].isDigit())
236             {
237                 break;
238             }
239         }
240 
241         if(idx == 0)
242         {
243             lastName = QString::number(lastName.toInt() + 1);
244         }
245         else if(idx < s)
246         {
247             lastName = lastName.left(idx) + QString::number(lastName.midRef(idx).toInt() + 1);
248         }
249     }
250 
251     cfg.setValue("Waypoint/lastName", lastName);
252     return lastName;
253 }
254 
getIconAndName(QString & icon,QString & name)255 bool CGisItemWpt::getIconAndName(QString& icon, QString& name)
256 {
257     SETTINGS;
258     QString lastIcon = cfg.value("Waypoint/lastIcon", "Waypoint").toString();
259 
260     if(name.isEmpty())
261     {
262         name = getLastName("");
263     }
264     icon = lastIcon;
265 
266     CSetupIconAndName dlg(icon, name, CMainWindow::getBestWidgetForParent());
267     if(dlg.exec() != QDialog::Accepted)
268     {
269         return false;
270     }
271 
272     cfg.setValue("Waypoint/lastName", name);
273     cfg.setValue("Waypoint/lastIcon", icon);
274 
275     return true;
276 }
277 
newWpt(const QPointF & pt,const QString & name,const QString & desc,IGisProject * project)278 void CGisItemWpt::newWpt(const QPointF& pt, const QString& name, const QString& desc, IGisProject* project)
279 {
280     SETTINGS;
281     const QString& _icon = cfg.value("Waypoint/lastIcon", "Waypoint").toString();
282     const QString& _name = name.isEmpty() ? getLastName("") : name;
283 
284     CGisItemWpt* wpt = new CGisItemWpt(pt, _name, _icon, project);
285     if(!desc.isEmpty())
286     {
287         wpt->setDescription(desc);
288     }
289     wpt->editInitial();
290 
291     cfg.setValue("Waypoint/lastName", wpt->getName());
292     cfg.setValue("Waypoint/lastIcon", wpt->getIconName());
293 }
294 
newWpt(const poi_t & poi,IGisProject * project,bool openEditWIndow)295 void CGisItemWpt::newWpt(const poi_t& poi, IGisProject* project, bool openEditWIndow)
296 {
297     SETTINGS;
298     const QString& _icon = poi.icon.isEmpty() ? cfg.value("Waypoint/lastIcon", "Waypoint").toString() : poi.icon;
299     const QString& _name = poi.name.isEmpty() ? getLastName("") : poi.name;
300 
301     CGisItemWpt* wpt = new CGisItemWpt(poi.pos * RAD_TO_DEG, _name, _icon, project);
302     if(!poi.desc.isEmpty())
303     {
304         wpt->setDescription(poi.desc);
305     }
306     if(!poi.links.isEmpty())
307     {
308         wpt->setLinks(poi.links);
309     }
310     if(poi.ele != NOINT)
311     {
312         wpt->setElevation(poi.ele);
313     }
314     if(openEditWIndow)
315     {
316         wpt->editInitial();
317     }
318 
319     wpt->setReadOnlyMode(true);
320     cfg.setValue("Waypoint/lastName", wpt->getName());
321     cfg.setValue("Waypoint/lastIcon", wpt->getIconName());
322 }
323 
getInfo(quint32 feature) const324 QString CGisItemWpt::getInfo(quint32 feature) const
325 {
326     QString str = "<div>";
327     qint32 initialSize = str.size();
328 
329     if(feature & eFeatureShowName)
330     {
331         str = "<b>" + getName() + "</b>";
332     }
333 
334     if(wpt.ele != NOINT)
335     {
336         if(str.size() > initialSize)
337         {
338             str += "<br/>\n";
339         }
340         QString val, unit;
341         IUnit::self().meter2elevation(wpt.ele, val, unit);
342         str += tr("Elevation: %1%2").arg(val, unit);
343     }
344 
345     if(proximity != NOFLOAT)
346     {
347         if(str.size() > initialSize)
348         {
349             str += "<br/>\n";
350         }
351         QString val, unit;
352         IUnit::self().meter2distance(proximity, val, unit);
353         str += tr("Proximity: %1%2").arg(val, unit);
354     }
355 
356     QString desc = removeHtml(wpt.desc).simplified();
357     if(geocache.hasData)
358     {
359         if(str.size() > initialSize)
360         {
361             str += "<br/>\n";
362         }
363 
364         str += QString(" %4 (%1, D %2, T %3)")
365                .arg(geocache.container)
366                .arg(geocache.difficulty, 0, 'f', 1)
367                .arg(geocache.terrain, 0, 'f', 1)
368                .arg(geocache.name);
369 
370         const QDateTime& lastFound = geocache.getLastFound();
371         if(lastFound.isValid())
372         {
373             str += "<br/>" + tr("Last found: %1")
374                    .arg(IUnit::datetime2string(lastFound, false, wpt));
375         }
376 
377         const IGisProject* project = getParentProject();
378         if(project != nullptr)
379         {
380             const QDateTime& projectDate = getParentProject()->getTime();
381             if(projectDate.isValid())
382             {
383                 str += "<br/>" + tr("Project created: %1")
384                        .arg(IUnit::datetime2string(projectDate, false, wpt));
385             }
386         }
387     }
388     else
389     {
390         if(desc.count())
391         {
392             if(str.size() > initialSize)
393             {
394                 str += "<br/>\n";
395             }
396 
397             if((feature & eFeatureShowFullText) || (desc.count() < 300))
398             {
399                 str += desc;
400             }
401             else
402             {
403                 str += desc.left(297) + "...";
404             }
405         }
406     }
407 
408     QString cmt = removeHtml(wpt.cmt).simplified();
409     if((cmt != desc) && cmt.count())
410     {
411         if(str.size() > initialSize)
412         {
413             str += "<br/>\n";
414         }
415 
416         if((feature & eFeatureShowFullText) || (cmt.count() < 300))
417         {
418             str += cmt;
419         }
420         else
421         {
422             str += cmt.left(297) + "...";
423         }
424     }
425     if(feature & eFeatureShowDateTime)
426     {
427         if(wpt.time.isValid())
428         {
429             if(str.size() > initialSize)
430             {
431                 str += "<br/>\n";
432             }
433             str += tr("Created: %1").arg(IUnit::datetime2string(wpt.time, false, QPointF(wpt.lon * DEG_TO_RAD, wpt.lat * DEG_TO_RAD)));
434         }
435     }
436 
437     if((feature & eFeatureShowLinks) && !wpt.links.isEmpty())
438     {
439         for(const link_t& link : wpt.links)
440         {
441             if(link.type.isEmpty() || (link.type == "text/html"))
442             {
443                 str += "<br/>\n";
444                 str += QString("<a href='%1'>%2</a>").arg(link.uri.toString(), link.text);
445             }
446         }
447         //Add logging link separately, since the link to the geocache site is extracted from the gpx file.
448         if(geocache.hasData && geocache.service == eGcCom)
449         {
450             str += " <a href='https://www.geocaching.com/play/geocache/" + wpt.name + "/log'>Log Geocache</a>";
451         }
452     }
453 
454 
455     str += getRatingKeywordInfo();
456 
457     return str + "</div>";
458 }
459 
getScreenOptions(const QPoint & origin,IMouse * mouse)460 IScrOpt* CGisItemWpt::getScreenOptions(const QPoint& origin, IMouse* mouse)
461 {
462     if (closeToRadius)
463     {
464         if(scrOptRadius.isNull())
465         {
466             scrOptRadius = new CScrOptWptRadius(this, origin, mouse);
467         }
468         return scrOptRadius;
469     }
470     else
471     {
472         if(scrOptWpt.isNull())
473         {
474             scrOptWpt = new CScrOptWpt(this, origin, mouse);
475         }
476         return scrOptWpt;
477     }
478 }
479 
getPointCloseBy(const QPoint & point)480 QPointF CGisItemWpt::getPointCloseBy(const QPoint& point)
481 {
482     if (closeToRadius)
483     {
484         QPointF l = (QPointF(point) - posScreen);
485         return posScreen + l * (radius / sqrt(QPointF::dotProduct(l, l)));
486     }
487     else
488     {
489         return posScreen;
490     }
491 }
492 
setIcon()493 void CGisItemWpt::setIcon()
494 {
495     if(geocache.hasData)
496     {
497         if(geocache.available)
498         {
499             IGisItem::setIcon(CWptIconManager::self().getWptIconByName(geocache.type, focus));
500         }
501         else
502         {
503             IGisItem::setIcon(CWptIconManager::self().getWptIconByName("gray_" + geocache.type, focus));
504         }
505     }
506     else
507     {
508         IGisItem::setIcon(CWptIconManager::self().getWptIconByName(wpt.sym, focus));
509     }
510 }
511 
setName(const QString & str)512 void CGisItemWpt::setName(const QString& str)
513 {
514     SETTINGS;
515     cfg.setValue("Waypoint/lastName", str);
516 
517     setText(CGisListWks::eColumnName, str);
518 
519     wpt.name = str;
520     changed(tr("Changed name"), "://icons/48x48/EditText.png");
521 }
522 
setPosition(const QPointF & pos)523 void CGisItemWpt::setPosition(const QPointF& pos)
524 {
525     wpt.lon = pos.x();
526     wpt.lat = pos.y();
527 
528     detBoundingRect();
529 
530     changed(tr("Changed position"), "://icons/48x48/WptMove.png");
531 }
532 
setElevation(qint32 val)533 void CGisItemWpt::setElevation(qint32 val)
534 {
535     wpt.ele = val;
536     changed(tr("Changed elevation"), "://icons/48x48/SetEle.png");
537 }
538 
setProximity(qreal val)539 void CGisItemWpt::setProximity(qreal val)
540 {
541     if (val == NOFLOAT)
542     {
543         proximity = NOFLOAT;
544         setNogoFlag(false);
545         changed(tr("Removed proximity"), "://icons/48x48/WptDelProx.png");
546     }
547     else
548     {
549         proximity = qRound(val);
550         changed(tr("Changed proximity"), "://icons/48x48/WptEditProx.png");
551     }
552 
553     detBoundingRect();
554 
555     radius = NOFLOAT; //radius is proximity in set on redraw
556 }
557 
setIcon(const QString & name)558 void CGisItemWpt::setIcon(const QString& name)
559 {
560     SETTINGS;
561     cfg.setValue("Waypoint/lastIcon", name);
562 
563     wpt.sym = name;
564 
565     QPointF focus;
566     QString path;
567     CWptIconManager::self().getWptIconByName(name, focus, &path);
568 
569     changed(tr("Changed icon"), path);
570 }
571 
setComment(const QString & str)572 void CGisItemWpt::setComment(const QString& str)
573 {
574     wpt.cmt = str;
575     changed(tr("Changed comment"), "://icons/48x48/EditText.png");
576 }
577 
setDescription(const QString & str)578 void CGisItemWpt::setDescription(const QString& str)
579 {
580     wpt.desc = str;
581     changed(tr("Changed description"), "://icons/48x48/EditText.png");
582 }
583 
setLinks(const QList<link_t> & links)584 void CGisItemWpt::setLinks(const QList<link_t>& links)
585 {
586     wpt.links = links;
587     changed(tr("Changed links"), "://icons/48x48/Link.png");
588 }
589 
setImages(const QList<image_t> & imgs)590 void CGisItemWpt::setImages(const QList<image_t>& imgs)
591 {
592     images = imgs;
593     changed(tr("Changed images"), "://icons/48x48/Image.png");
594 }
595 
addImage(const image_t & img)596 void CGisItemWpt::addImage(const image_t& img)
597 {
598     images.append(img);
599     changed(tr("Add image"), "://icons/48x48/Image.png");
600 }
601 
602 
isCloseTo(const QPointF & pos)603 bool CGisItemWpt::isCloseTo(const QPointF& pos)
604 {
605     closeToRadius = false;
606 
607     if(posScreen == NOPOINTF)
608     {
609         return false;
610     }
611 
612     QPointF dist = (pos - posScreen);
613     if(dist.manhattanLength() < 22)
614     {
615         return true;
616     }
617     if (radius == NOFLOAT)
618     {
619         return false;
620     }
621 
622     closeToRadius = abs(QPointF::dotProduct(dist, dist) / radius - radius) < 22;
623     return closeToRadius;
624 }
625 
isWithin(const QRectF & area,selflags_t flags)626 bool CGisItemWpt::isWithin(const QRectF& area, selflags_t flags)
627 {
628     return (flags & eSelectionWpt) ? area.contains(QPointF(wpt.lon, wpt.lat)) : false;
629 }
630 
631 
gainUserFocus(bool yes)632 void CGisItemWpt::gainUserFocus(bool yes)
633 {
634     keyUserFocus = yes ? key : key_t();
635 }
636 
edit()637 void CGisItemWpt::edit()
638 {
639     if(geocache.hasData)
640     {
641         CDetailsGeoCache dlg(*this, CMainWindow::getBestWidgetForParent());
642         dlg.exec();
643     }
644     else
645     {
646         CDetailsWpt dlg(*this, CMainWindow::getBestWidgetForParent());
647         dlg.exec();
648     }
649 }
650 
editInitial()651 void CGisItemWpt::editInitial()
652 {
653     CDetailsWpt dlg(*this, CMainWindow::getBestWidgetForParent());
654     dlg.disableHistory();
655     dlg.exec();
656     squashHistory();
657 }
658 
drawItem(QPainter & p,const QPolygonF & viewport,QList<QRectF> & blockedAreas,CGisDraw * gis)659 void CGisItemWpt::drawItem(QPainter& p, const QPolygonF& viewport, QList<QRectF>& blockedAreas, CGisDraw* gis)
660 {
661     posScreen = QPointF(wpt.lon * DEG_TO_RAD, wpt.lat * DEG_TO_RAD);
662 
663     if (proximity == NOFLOAT || proximity == 0. ? !isVisible(posScreen, viewport, gis) : !isVisible(boundingRect, viewport, gis))
664     {
665         rectBubble = QRect();
666         posScreen = NOPOINTF;
667         return;
668     }
669 
670     gis->convertRad2Px(posScreen);
671 
672     if(proximity == NOFLOAT)
673     {
674         radius = NOFLOAT;
675     }
676     else
677     {
678         //remember radius for isCloseTo-method
679         radius = calcRadius(QPointF(wpt.lon * DEG_TO_RAD, wpt.lat * DEG_TO_RAD), posScreen, proximity, gis);
680 
681         drawCircle(p, posScreen, radius, !hideArea && isNogo(), false);
682     }
683 
684     drawBubble(p);
685 
686     p.drawPixmap(posScreen - focus, icon);
687 
688     blockedAreas << QRectF(posScreen - focus, icon.size());
689 }
690 
drawItem(QPainter & p,const QRectF &,CGisDraw * gis)691 void CGisItemWpt::drawItem(QPainter& p, const QRectF& /*viewport*/, CGisDraw* gis)
692 {
693     if(mouseIsOverBubble && !doBubbleMove && !doBubbleSize && rectBubble.isValid() && !isReadOnly())
694     {
695         QPainterPath clip;
696         clip.addRoundedRect(rectBubble, RECT_RADIUS, RECT_RADIUS);
697         p.setClipPath(clip);
698 
699         QRect barTop(rectBubble.topLeft(), QSize(rectBubble.width(), 26));
700         QRect barBottom(barTop);
701         barBottom.moveBottomLeft(rectBubble.bottomLeft());
702         barBottom.adjust(1, 0, -1, -1);
703         barTop.adjust(1, 1, -1, 0);
704 
705         p.setPen(Qt::NoPen);
706         p.setBrush(QColor(200, 200, 255, 150));
707         p.drawRect(barTop);
708         p.drawRect(barBottom);
709 
710         p.setBrush(Qt::white);
711         p.drawRoundedRect(rectBubbleMove.adjusted(-2, -2, 2, 2), RECT_RADIUS, RECT_RADIUS);
712         p.drawRoundedRect(rectBubbleEdit.adjusted(-2, -2, 2, 2), RECT_RADIUS, RECT_RADIUS);
713         p.drawRoundedRect(rectBubbleSize.adjusted(-2, -2, 2, 2), RECT_RADIUS, RECT_RADIUS);
714 
715         p.drawPixmap(rectBubbleMove, QPixmap("://icons/32x32/MoveArrow.png"));
716         p.drawPixmap(rectBubbleEdit, QPixmap("://icons/32x32/EditDetails.png"));
717         p.drawPixmap(rectBubbleSize, QPixmap("://icons/32x32/SizeArrow.png"));
718     }
719 }
720 
721 
drawLabel(QPainter & p,const QPolygonF &,QList<QRectF> & blockedAreas,const QFontMetricsF & fm,CGisDraw *)722 void CGisItemWpt::drawLabel(QPainter& p, const QPolygonF& /*viewport*/, QList<QRectF>& blockedAreas, const QFontMetricsF& fm, CGisDraw*/*gis*/)
723 {
724     if(flags & eFlagWptBubble)
725     {
726         return;
727     }
728 
729     if(posScreen == NOPOINTF)
730     {
731         return;
732     }
733 
734     QPointF pt = posScreen - focus;
735 
736     QRectF rect = fm.boundingRect(wpt.name);
737     rect.adjust(-2, -2, 2, 2);
738 
739     // place label on top
740     rect.moveCenter(pt + QPointF(icon.width() / 2, -fm.height()));
741     if(CDraw::doesOverlap(blockedAreas, rect))
742     {
743         // place label on bottom
744         rect.moveCenter(pt + QPointF( icon.width() / 2, +fm.height() + icon.height()));
745         if(CDraw::doesOverlap(blockedAreas, rect))
746         {
747             // place label on right
748             rect.moveCenter(pt + QPointF( icon.width() + rect.width() / 2, +fm.height()));
749             if(CDraw::doesOverlap(blockedAreas, rect))
750             {
751                 // place label on left
752                 rect.moveCenter(pt + QPointF( -rect.width() / 2, +fm.height()));
753                 if(CDraw::doesOverlap(blockedAreas, rect))
754                 {
755                     // failed to place label anywhere
756                     return;
757                 }
758             }
759         }
760     }
761 
762     CDraw::text(wpt.name, p, rect.toRect(), Qt::darkBlue);
763     blockedAreas << rect;
764 }
765 
drawHighlight(QPainter & p)766 void CGisItemWpt::drawHighlight(QPainter& p)
767 {
768     if(posScreen == NOPOINTF)
769     {
770         return;
771     }
772 
773     if (closeToRadius)
774     {
775         drawCircle(p, posScreen, radius, false, true);
776     }
777     else
778     {
779         p.drawImage(posScreen - QPointF(31, 31), QImage("://cursors/wptHighlightRed.png"));
780     }
781 }
782 
drawBubble(QPainter & p)783 void CGisItemWpt::drawBubble(QPainter& p)
784 {
785     if(!(flags & eFlagWptBubble))
786     {
787         return;
788     }
789 
790     QString str = QString("<b>%1</b>").arg(getName());
791 
792     if(!removeHtml(wpt.desc).simplified().isEmpty())
793     {
794         str += QString("<p>%1</p>").arg(wpt.desc);
795     }
796 
797     if(!removeHtml(wpt.cmt).simplified().isEmpty())
798     {
799         str += QString("<p>%1</p>").arg(wpt.cmt);
800     }
801 
802     QTextDocument doc;
803     doc.setHtml(str);
804     doc.setTextWidth(widthBubble);
805 
806     rectBubble.setWidth(widthBubble);
807     rectBubble.setHeight(doc.size().height());
808 
809     QPoint posBubble = posScreen.toPoint() + offsetBubble;
810     rectBubble.moveTopLeft(posBubble);
811 
812     rectBubbleMove.moveTopLeft(rectBubble.topLeft() + QPoint(5, 5));
813     rectBubbleEdit.moveTopLeft(rectBubbleMove.topRight() + QPoint(7, 0));
814     rectBubbleSize.moveBottomRight(rectBubble.bottomRight() - QPoint(5, 5));
815 
816     QPolygonF frame = makePolyline(posScreen, rectBubble);
817     p.setPen(CDraw::penBorderGray);
818     p.setBrush(CDraw::brushBackWhite);
819     p.drawPolygon(frame);
820 
821     p.save();
822     p.translate(posBubble);
823     p.setPen(Qt::black);
824     doc.drawContents(&p);
825     p.restore();
826 }
827 
drawCircle(QPainter & p,const QPointF & pos,const qreal & r,const bool & nogo,const bool & selected)828 void CGisItemWpt::drawCircle(QPainter& p, const QPointF& pos, const qreal& r, const bool& nogo, const bool& selected)
829 {
830     QRect circle(pos.x() - r - 1, pos.y() - r - 1, 2 * r + 1, 2 * r + 1);
831     p.save();
832     p.setBrush(Qt::NoBrush);
833     if (selected)
834     {
835         p.setPen(QPen(Qt::red, 3));
836     }
837     else
838     {
839         p.setPen(QPen(Qt::white, 3));
840         p.drawEllipse(circle);
841         p.setPen(QPen(Qt::red, 1));
842     }
843     p.drawEllipse(circle);
844     if (nogo)
845     {
846         p.setBrush(getNogoTextureBrush());
847         p.setPen(Qt::NoPen);
848         p.drawEllipse(circle);
849     }
850     p.restore();
851 }
852 
calcRadius(const QPointF & posRad,const QPointF & posPx,const qreal & radiusRad,CGisDraw * gis)853 qreal CGisItemWpt::calcRadius(const QPointF& posRad, const QPointF& posPx, const qreal& radiusRad, CGisDraw* gis)
854 {
855     QPointF pt1 = posRad;
856     pt1 = GPS_Math_Wpt_Projection(pt1, radiusRad, 90 * DEG_TO_RAD);
857     gis->convertRad2Px(pt1);
858 
859     return pt1.x() - posPx.x();
860 }
861 
makePolyline(const QPointF & anchor,const QRectF & r)862 QPolygonF CGisItemWpt::makePolyline(const QPointF& anchor, const QRectF& r)
863 {
864     QPolygonF poly1, poly2;
865     poly1 << r.topLeft() << r.topRight() << r.bottomRight() << r.bottomLeft();
866 
867     if(!r.contains(anchor))
868     {
869         qreal w = rectBubble.width() >> 1;
870         qreal h = rectBubble.height() >> 1;
871 
872         if(w > 30)
873         {
874             w = 30;
875         }
876         if(h > 30)
877         {
878             h = 30;
879         }
880 
881         w = h = qMin(w, h);
882 
883         if(anchor.x() < r.left())
884         {
885             poly2 << anchor << (r.center() + QPoint(0, -h)) << (r.center() + QPoint(0, h)) << anchor;
886         }
887         else if(r.right() < anchor.x())
888         {
889             poly2 << anchor << (r.center() + QPoint(0, -h)) << (r.center() + QPoint(0, h)) << anchor;
890         }
891         else if(anchor.y() < r.top())
892         {
893             poly2 << anchor << (r.center() + QPoint(-w, 0)) << (r.center() + QPoint(w, 0)) << anchor;
894         }
895         else if(r.bottom() < anchor.y())
896         {
897             poly2 << anchor << (r.center() + QPoint(-w, 0)) << (r.center() + QPoint(w, 0)) << anchor;
898         }
899 
900         QPainterPath path1;
901         path1.addRoundedRect(r, RECT_RADIUS, RECT_RADIUS);
902         QPainterPath path2;
903         path2.addPolygon(poly2);
904 
905         path1 = path1.united(path2);
906 
907         poly1 = path1.toFillPolygon();
908     }
909 
910     return poly1;
911 }
912 
913 
removeLinksByType(const QString & type)914 void CGisItemWpt::removeLinksByType(const QString& type)
915 {
916     QList<IGisItem::link_t>::iterator link = wpt.links.begin();
917 
918     while(link != wpt.links.end())
919     {
920         if(link->type == type)
921         {
922             link = wpt.links.erase(link);
923             continue;
924         }
925 
926         ++link;
927     }
928 }
929 
mouseMove(const QPointF & pos)930 void CGisItemWpt::mouseMove(const QPointF& pos)
931 {
932     if(!hasBubble() || isReadOnly())
933     {
934         return;
935     }
936     CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
937     if(!canvas)
938     {
939         return;
940     }
941 
942     if(mouseIsOverBubble)
943     {
944         processMouseOverBubble(pos.toPoint());
945         if(!rectBubble.contains(pos.toPoint()))
946         {
947             doBubbleMove = doBubbleSize = false;
948             canvas->resetMouse();
949             mouseIsOverBubble = false;
950         }
951     }
952     else
953     {
954         if(rectBubble.contains(pos.toPoint()))
955         {
956             doBubbleMove = doBubbleSize = false;
957             canvas->setMouseWptBubble(getKey());
958             mouseIsOverBubble = true;
959         }
960     }
961 }
962 
mouseDragged(const QPoint &,const QPoint &,const QPoint & pos)963 void CGisItemWpt::mouseDragged(const QPoint& /*start*/, const QPoint& /*last*/, const QPoint& pos)
964 {
965     CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
966     if(!canvas)
967     {
968         return;
969     }
970     if (!doBubbleMove && !doBubbleSize)
971     {
972         if(rectBubbleMove.contains(pos))
973         {
974             offsetMouse = pos - rectBubble.topLeft();
975             doBubbleMove = true;
976         }
977         else if(rectBubbleSize.contains(pos))
978         {
979             offsetMouse = pos - rectBubble.bottomRight();
980             doBubbleSize = true;
981         }
982         else
983         {
984             return;
985         }
986     }
987     if(doBubbleMove)
988     {
989         offsetBubble = pos - posScreen.toPoint();
990         offsetBubble -= offsetMouse;
991     }
992     else if(doBubbleSize)
993     {
994         qDebug() << offsetMouse;
995         int width = pos.x() - rectBubble.left() - offsetMouse.x();
996         if(width > 50)
997         {
998             widthBubble = width;
999         }
1000     }
1001     canvas->slotTriggerCompleteUpdate(CCanvas::eRedrawGis);
1002 }
1003 
dragFinished(const QPoint &)1004 void CGisItemWpt::dragFinished(const QPoint& /*pos*/)
1005 {
1006     updateHistory();
1007     doBubbleMove = doBubbleSize = false;
1008 }
1009 
leftClicked(const QPoint & pos)1010 void CGisItemWpt::leftClicked(const QPoint& pos)
1011 {
1012     if(rectBubbleEdit.contains(pos))
1013     {
1014         CCanvas* canvas = CMainWindow::self().getVisibleCanvas();
1015         if(canvas)
1016         {
1017             doBubbleMove = doBubbleSize = false;
1018             canvas->resetMouse();
1019         }
1020         mouseIsOverBubble = false;
1021         edit();
1022     }
1023 }
1024 
toggleBubble()1025 void CGisItemWpt::toggleBubble()
1026 {
1027     if(flags & eFlagWptBubble)
1028     {
1029         flags &= ~eFlagWptBubble;
1030     }
1031     else
1032     {
1033         flags |= eFlagWptBubble;
1034     }
1035     updateHistory();
1036 }
1037 
getValueByKeyword(searchProperty_e keyword)1038 const searchValue_t CGisItemWpt::getValueByKeyword(searchProperty_e keyword)
1039 {
1040     if(keywordLambdaMap.contains(keyword))
1041     {
1042         return keywordLambdaMap.value(keyword)(this);
1043     }
1044     return searchValue_t();
1045 }
1046 
processMouseOverBubble(const QPoint & pos)1047 void CGisItemWpt::processMouseOverBubble(const QPoint& pos)
1048 {
1049     if(rectBubbleMove.contains(pos) || rectBubbleEdit.contains(pos) || rectBubbleSize.contains(pos))
1050     {
1051         if(!doSpecialCursor)
1052         {
1053             CCanvas::setOverrideCursor(Qt::PointingHandCursor, "processMouseOverBubble");
1054             doSpecialCursor = true;
1055         }
1056     }
1057     else
1058     {
1059         if(doSpecialCursor)
1060         {
1061             CCanvas::restoreOverrideCursor("processMouseOverBubble");
1062             doSpecialCursor = false;
1063         }
1064     }
1065 }
1066 
detBoundingRect()1067 void CGisItemWpt::detBoundingRect()
1068 {
1069     if(proximity == NOFLOAT)
1070     {
1071         boundingRect = QRectF(QPointF(wpt.lon, wpt.lat) * DEG_TO_RAD, QPointF(wpt.lon, wpt.lat) * DEG_TO_RAD);
1072     }
1073     else
1074     {
1075         qreal diag = proximity * 1.414213562;
1076         QPointF cent(wpt.lon* DEG_TO_RAD, wpt.lat* DEG_TO_RAD);
1077 
1078         QPointF pt1 = GPS_Math_Wpt_Projection(cent, diag, 225 * DEG_TO_RAD);
1079         QPointF pt2 = GPS_Math_Wpt_Projection(cent, diag, 45 * DEG_TO_RAD);
1080 
1081         boundingRect = QRectF(pt1, pt2);
1082     }
1083 }
1084 
1085 const QList<QString> CGisItemWpt::geocache_t::attributeMeanings = {
1086     "QMS Attribute Flag",         //Not to be serialized in GPX files
1087     "Dogs",
1088     "Access or parking fee",
1089     "Climbing gear",
1090     "Boat",
1091     "Scuba gear",
1092     "Recommended for kids",
1093     "Takes less than an hour",
1094     "Scenic view",
1095     "Significant hike",
1096     "Difficult climbing",
1097     "May require wading",
1098     "May require swimming",
1099     "Available at all times",
1100     "Recommended at night",
1101     "Available during winter",
1102     "",
1103     "Poison plants",
1104     "Dangerous Animals",
1105     "Ticks",
1106     "Abandoned mines",
1107     "Cliff / falling rocks",
1108     "Hunting",
1109     "Dangerous area",
1110     "Wheelchair accessible",
1111     "Parking available",
1112     "Public transportation",
1113     "Drinking water nearby",
1114     "Public restrooms nearby",
1115     "Telephone nearby",
1116     "Picnic tables nearby",
1117     "Camping available",
1118     "Bicycles",
1119     "Motorcycles",
1120     "Quads",
1121     "Off-road vehicles",
1122     "Snowmobiles",
1123     "Horses",
1124     "Campfires",
1125     "Thorns",
1126     "Stealth required",
1127     "Stroller accessible",
1128     "Needs maintenance",
1129     "Watch for livestock",
1130     "Flashlight required",
1131     "",
1132     "Truck Driver/RV",
1133     "Field Puzzle",
1134     "UV Light Required",
1135     "Snowshoes",
1136     "Cross Country Skis",
1137     "Special Tool Required",
1138     "Night Cache",
1139     "Park and Grab",
1140     "Abandoned Structure",
1141     "Short hike (less than 1km)",
1142     "Medium hike (1km-10km)",
1143     "Long Hike (+10km)",
1144     "Fuel Nearby",
1145     "Food Nearby",
1146     "Wireless Beacon",
1147     "Partnership cache",
1148     "Seasonal Access",
1149     "Tourist Friendly",
1150     "Tree Climbing",
1151     "Front Yard (Private Residence)",
1152     "Teamwork Required",
1153     "GeoTour"
1154 };
1155 
initAttributeMeaningsTranslated()1156 QList<QString> CGisItemWpt::geocache_t::initAttributeMeaningsTranslated()
1157 {
1158     QList<QString> translated = {
1159         tr("QMS Attribute Flag"),         //Not to be serialized in GPX files
1160         tr("Dogs"),
1161         tr("Access or parking fee"),
1162         tr("Climbing gear"),
1163         tr("Boat"),
1164         tr("Scuba gear"),
1165         tr("Recommended for kids"),
1166         tr("Takes less than an hour"),
1167         tr("Scenic view"),
1168         tr("Significant hike"),
1169         tr("Difficult climbing"),
1170         tr("May require wading"),
1171         tr("May require swimming"),
1172         tr("Available at all times"),
1173         tr("Recommended at night"),
1174         tr("Available during winter"),
1175         "",
1176         tr("Poison plants"),
1177         tr("Dangerous Animals"),
1178         tr("Ticks"),
1179         tr("Abandoned mines"),
1180         tr("Cliff / falling rocks"),
1181         tr("Hunting"),
1182         tr("Dangerous area"),
1183         tr("Wheelchair accessible"),
1184         tr("Parking available"),
1185         tr("Public transportation"),
1186         tr("Drinking water nearby"),
1187         tr("Public restrooms nearby"),
1188         tr("Telephone nearby"),
1189         tr("Picnic tables nearby"),
1190         tr("Camping available"),
1191         tr("Bicycles"),
1192         tr("Motorcycles"),
1193         tr("Quads"),
1194         tr("Off-road vehicles"),
1195         tr("Snowmobiles"),
1196         tr("Horses"),
1197         tr("Campfires"),
1198         tr("Thorns"),
1199         tr("Stealth required"),
1200         tr("Stroller accessible"),
1201         tr("Needs maintenance"),
1202         tr("Watch for livestock"),
1203         tr("Flashlight required"),
1204         "",
1205         tr("Truck Driver/RV"),
1206         tr("Field Puzzle"),
1207         tr("UV Light Required"),
1208         tr("Snowshoes"),
1209         tr("Cross Country Skis"),
1210         tr("Special Tool Required"),
1211         tr("Night Cache"),
1212         tr("Park and Grab"),
1213         tr("Abandoned Structure"),
1214         tr("Short hike (less than 1km)"),
1215         tr("Medium hike (1km-10km)"),
1216         tr("Long Hike (+10km)"),
1217         tr("Fuel Nearby"),
1218         tr("Food Nearby"),
1219         tr("Wireless Beacon"),
1220         tr("Partnership cache"),
1221         tr("Seasonal Access"),
1222         tr("Tourist Friendly"),
1223         tr("Tree Climbing"),
1224         tr("Front Yard (Private Residence)"),
1225         tr("Teamwork Required"),
1226         tr("GeoTour")
1227     };
1228     return translated;
1229 }
1230 
getLastFound() const1231 QDateTime CGisItemWpt::geocache_t::getLastFound() const
1232 {
1233     QDateTime lastFound;
1234     for(const geocachelog_t& log : logs)
1235     {
1236         if(lastFound.isValid() == false || (log.type == "Found It" && log.date > lastFound))
1237         {
1238             lastFound = log.date;
1239         }
1240     }
1241     return lastFound;
1242 }
1243 
getLogs() const1244 QString CGisItemWpt::geocache_t::getLogs() const
1245 {
1246     QString strLogs;
1247     for(const geocachelog_t& log : logs)
1248     {
1249         QString thislog = log.text;
1250         strLogs += "<p><b>"
1251                    + log.date.date().toString(Qt::SystemLocaleShortDate)
1252                    + ": "
1253                    + log.type
1254                    + tr(" by ")
1255                    + log.finder
1256                    + "</b></p><p>"
1257                    + thislog.replace("\n", "<br/>")
1258                    + "</p><hr>";
1259     }
1260     return strLogs;
1261 }
1262 
initKeywordLambdaMap()1263 QMap<searchProperty_e, CGisItemWpt::fSearch> CGisItemWpt::initKeywordLambdaMap()
1264 {
1265     QMap<searchProperty_e, CGisItemWpt::fSearch> map;
1266     map.insert(eSearchPropertyGeneralName, [](CGisItemWpt* item){
1267         searchValue_t searchValue;
1268         if(item->geocache.hasData)
1269         {
1270             searchValue.str1 = item->geocache.name + " - " + item->getName();
1271         }
1272         else
1273         {
1274             searchValue.str1 = item->getName();
1275         }
1276         return searchValue;
1277     });
1278     map.insert(eSearchPropertyGeneralFullText, [](CGisItemWpt* item){
1279         searchValue_t searchValue;
1280         searchValue.str1 = item->getInfo(eFeatureShowFullText | eFeatureShowName);
1281         return searchValue;
1282     });
1283     map.insert(eSearchPropertyGeneralElevation, [](CGisItemWpt* item){
1284         searchValue_t searchValue;
1285         IUnit::self().meter2elevation(item->wpt.ele, searchValue.value1, searchValue.str1);
1286         return searchValue;
1287     });
1288     map.insert(eSearchPropertyGeneralDate, [](CGisItemWpt* item){
1289         searchValue_t searchValue;
1290         if(item->wpt.time.isValid())
1291         {
1292             searchValue.value1 = item->wpt.time.toSecsSinceEpoch();
1293             searchValue.str1 = "SsE"; //To differentiate Dates and Durations
1294         }
1295         return searchValue;
1296     });
1297     map.insert(eSearchPropertyGeneralComment, [](CGisItemWpt* item){
1298         searchValue_t searchValue;
1299         searchValue.str1 = item->getComment();
1300         return searchValue;
1301     });
1302     map.insert(eSearchPropertyGeneralDescription, [](CGisItemWpt* item){
1303         searchValue_t searchValue;
1304         searchValue.str1 = item->getDescription();
1305         return searchValue;
1306     });
1307     map.insert(eSearchPropertyGeneralRating, [](CGisItemWpt* item){
1308         searchValue_t searchValue;
1309         searchValue.value1 = item->getRating();
1310         return searchValue;
1311     });
1312     map.insert(eSearchPropertyGeneralKeywords, [](CGisItemWpt* item){
1313         searchValue_t searchValue;
1314         searchValue.str1 = QStringList(item->getKeywords().toList()).join(", ");
1315         return searchValue;
1316     });
1317     map.insert(eSearchPropertyGeneralType, [](CGisItemWpt* /*item*/){
1318         searchValue_t searchValue;
1319         searchValue.str1 = tr("waypoint");
1320         return searchValue;
1321     });
1322     //Geocache keywords
1323     map.insert(eSearchPropertyGeocacheDifficulty, [](CGisItemWpt* item){
1324         searchValue_t searchValue;
1325         searchValue.value1 = item->geocache.difficulty;
1326         return searchValue;
1327     });
1328     map.insert(eSearchPropertyGeocacheTerrain, [](CGisItemWpt* item){
1329         searchValue_t searchValue;
1330         searchValue.value1 = item->geocache.terrain;
1331         return searchValue;
1332     });
1333     map.insert(eSearchPropertyGeocachePositiveAttributes, [](CGisItemWpt* item){
1334         searchValue_t searchValue;
1335         const QList<quint8>& keys = item->geocache.attributes.keys();
1336         for(quint8 attr : keys)
1337         {
1338             if(attr >= item->geocache.attributeMeaningsTranslated.length())
1339             {
1340                 continue;
1341             }
1342             if(!item->geocache.attributes[attr])// It is negated
1343             {
1344                 continue;
1345             }
1346             searchValue.str1 += item->geocache.attributeMeaningsTranslated[attr] + ", ";
1347         }
1348         return searchValue;
1349     });
1350     map.insert(eSearchPropertyGeocacheNegatedAttributes, [](CGisItemWpt* item){
1351         searchValue_t searchValue;
1352         const QList<quint8>& keys = item->geocache.attributes.keys();
1353         for(quint8 attr : keys)
1354         {
1355             if(attr >= item->geocache.attributeMeaningsTranslated.length())
1356             {
1357                 continue;
1358             }
1359             if(item->geocache.attributes[attr])// It is not negated
1360             {
1361                 continue;
1362             }
1363             searchValue.str1 += item->geocache.attributeMeaningsTranslated[attr] + ", ";
1364         }
1365         return searchValue;
1366     });
1367     map.insert(eSearchPropertyGeocacheSize, [](CGisItemWpt* item){
1368         searchValue_t searchValue;
1369         searchValue.str1 = item->geocache.container;
1370         return searchValue;
1371     });
1372     map.insert(eSearchPropertyGeocacheGCCode, [](CGisItemWpt* item){
1373         searchValue_t searchValue;
1374         searchValue.str1 = item->getName();
1375         return searchValue;
1376     });
1377     map.insert(eSearchPropertyGeocacheGCName, [](CGisItemWpt* item){
1378         searchValue_t searchValue;
1379         searchValue.str1 = item->geocache.name;
1380         return searchValue;
1381     });
1382     map.insert(eSearchPropertyGeocacheStatus, [](CGisItemWpt* item){
1383         searchValue_t searchValue;
1384         if(!item->geocache.hasData)
1385         {
1386             return searchValue;
1387         }
1388 
1389         if(item->geocache.archived)
1390         {
1391             searchValue.str1 = tr("archived");
1392         }
1393         else if(item->geocache.available)
1394         {
1395             searchValue.str1 = tr("available");
1396         }
1397         else
1398         {
1399             searchValue.str1 = tr("not available");
1400         }
1401 
1402         return searchValue;
1403     });
1404     map.insert(eSearchPropertyGeocacheGCType, [](CGisItemWpt* item){
1405         searchValue_t searchValue;
1406         searchValue.str1 = item->geocache.type;
1407         return searchValue;
1408     });
1409     map.insert(eSearchPropertyGeocacheLoggedBy, [](CGisItemWpt* item){
1410         searchValue_t searchValue;
1411         for(const geocachelog_t& log : qAsConst(item->geocache.logs))
1412         {
1413             searchValue.str1 += log.finder + ", ";
1414         }
1415         return searchValue;
1416     });
1417     map.insert(eSearchPropertyGeocacheLastLogDate, [](CGisItemWpt* item){
1418         searchValue_t searchValue;
1419         if(item->geocache.logs.size() > 0)
1420         {
1421             searchValue.value1 = item->geocache.logs[0].date.toSecsSinceEpoch();
1422             searchValue.str1 = "SsE"; //To differentiate Dates and Durations
1423         }
1424         return searchValue;
1425     });
1426     map.insert(eSearchPropertyGeocacheLastLogType, [](CGisItemWpt* item){
1427         searchValue_t searchValue;
1428         if(item->geocache.logs.size() > 0)
1429         {
1430             searchValue.str1 = item->geocache.logs[0].type;
1431         }
1432         return searchValue;
1433     });
1434     map.insert(eSearchPropertyGeocacheLastLogBy, [](CGisItemWpt* item){
1435         searchValue_t searchValue;
1436         if(item->geocache.logs.size() > 0)
1437         {
1438             searchValue.str1 = item->geocache.logs[0].finder;
1439         }
1440         return searchValue;
1441     });
1442     map.insert(eSearchPropertyGeocacheGCOwner, [](CGisItemWpt* item){
1443         searchValue_t searchValue;
1444         searchValue.str1 = item->geocache.owner;
1445         return searchValue;
1446     });
1447     return map;
1448 }
1449 
1450