1 /**********************************************************************************************
2     Copyright (C) 2019 Henri Hornburg <hrnbg@t-online.de>
3 
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 3 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 
17 **********************************************************************************************/
18 
19 #include "CSearch.h"
20 
21 Qt::CaseSensitivity CSearch::caseSensitivity = Qt::CaseInsensitive;
22 CSearch::search_mode_e CSearch::searchMode = CSearch::eSearchModeText;
23 
24 QMap<QString, CSearch::search_type_e> CSearch::keywordSearchTypeMap;
25 QMap<QString, QString> CSearch::keywordSearchExampleMap;
26 QMap<QString, searchProperty_e> CSearch::searchPropertyEnumMap;
27 QMap<searchProperty_e, QString> CSearch::searchPropertyMeaningMap;
28 
CSearch(QString searchstring)29 CSearch::CSearch(QString searchstring)
30     : searchText(searchstring)
31 {
32     //Initialisation
33     searchTypeLambdaMap = initSearchTypeLambdaMap();
34 
35     if(searchstring.simplified().isEmpty())
36     {
37         return;
38     }
39 
40     //Since in the shortened search the searchType may be at the beginning and for reasons stated below
41     //there must be a space before such a keyword, we prepend it here just to make sure.
42     searchstring.prepend(" ");
43 
44     //Detect which comparison keyword in order to later spilt the string at this word.
45     QString searchTypeKeyword;
46     const QStringList& keys = keywordSearchTypeMap.keys();
47     for(const QString& key : keys)
48     {
49         //Make spaces around so for example with is not detected inside without
50         if(searchstring.contains(" " + key + " ", Qt::CaseInsensitive))
51         {
52             searchTypeKeyword = key;
53             break;
54         }
55     }
56 
57     if(searchTypeKeyword.isEmpty())
58     {
59         //Default to search for what the user typed in the name or the full text
60         search.searchType = eSearchTypeWith;
61         if(searchMode == eSearchModeText)
62         {
63             search.property = searchProperty_e::eSearchPropertyGeneralFullText;
64         }
65         else
66         {
67             search.property = searchProperty_e::eSearchPropertyGeneralName;
68         }
69         search.searchValue.str1 = searchstring.simplified();
70         syntaxError = true;
71     }
72     else
73     {
74         search.searchType = keywordSearchTypeMap.value(searchTypeKeyword);
75         //Everything before the Search Type keyword is the property, i.e. "date after 2019" would result in "date"
76         search.property = eSearchPropertyNoMatch;
77         const QString& propertyString = searchstring.section(searchTypeKeyword, 0, 0, QString::SectionCaseInsensitiveSeps).simplified();
78         const QStringList& keys = searchPropertyEnumMap.keys();
79         for(const QString& key : keys)
80         {
81             if(propertyString.compare(key, Qt::CaseInsensitive) == 0)
82             {
83                 search.property = searchPropertyEnumMap[key];
84                 break;
85             }
86         }
87         //Don't raise a syntax error yet, since the improve query might find the correct property
88 
89         //Everything after the Search Type keyword is the value, i.e. "date after 2019" would result in "2019"
90         const QString& filterValueString = searchstring.section(searchTypeKeyword, 1, -1, QString::SectionCaseInsensitiveSeps).simplified();
91         const QString& filterValueStringFirstPart = filterValueString.section(tr("and"), 0, 0, QString::SectionCaseInsensitiveSeps).simplified();
92         const QString& filterValueStringSecondPart = filterValueString.section(tr("and"), 1, -1, QString::SectionCaseInsensitiveSeps).simplified();
93         searchValue_t filterValue;
94 
95         //Try if it is a time. Do so first, since this is the most exclusive
96         const static QList<QString> timeFormats = {
97             QLocale::system().timeFormat(QLocale::LongFormat),
98             QLocale::system().timeFormat(QLocale::ShortFormat),
99             QLocale::c().timeFormat(QLocale::LongFormat),
100             QLocale::c().timeFormat(QLocale::ShortFormat)
101         };
102 
103         for(const QString& tf : timeFormats)
104         {
105             const QTime& time1a = QLocale::system().toTime(filterValueStringFirstPart, tf);
106             if(time1a.isValid())
107             {
108                 filterValue.value1 = time1a.msecsSinceStartOfDay() / 1000;
109                 filterValue.str1 = "SsE";
110             }
111 
112             const QTime& time1b = QLocale::c().toTime(filterValueStringFirstPart, tf);
113             if(time1b.isValid())
114             {
115                 filterValue.value1 = time1b.msecsSinceStartOfDay() / 1000;
116                 filterValue.str1 = "SsE";
117             }
118 
119             if(time1a.isValid() || time1b.isValid())
120             {
121                 const QTime& time2a = QLocale::system().toTime(filterValueStringSecondPart, tf);
122                 if(time2a.isValid())
123                 {
124                     filterValue.value2 = time2a.msecsSinceStartOfDay() / 1000;
125                     filterValue.str2 = "SsE";
126                 }
127 
128                 const QTime& time2b = QLocale::c().toTime(filterValueStringSecondPart, tf);
129                 if(time2b.isValid())
130                 {
131                     filterValue.value2 = time2b.msecsSinceStartOfDay() / 1000;
132                     filterValue.str2 = "SsE";
133                 }
134                 break;
135             }
136         }
137 
138         if(filterValue.toString().isEmpty())
139         {
140             //Try if it is a date
141             const static QList<QString> dateFormats = {
142                 QLocale::system().dateTimeFormat(QLocale::LongFormat),
143                 QLocale::system().dateTimeFormat(QLocale::ShortFormat),
144                 QLocale::c().dateTimeFormat(QLocale::LongFormat),
145                 QLocale::c().dateTimeFormat(QLocale::ShortFormat),
146                 QLocale::system().dateFormat(QLocale::LongFormat),
147                 QLocale::system().dateFormat(QLocale::ShortFormat),
148                 QLocale::c().dateFormat(QLocale::LongFormat),
149                 QLocale::c().dateFormat(QLocale::ShortFormat)
150             };
151 
152             for(const QString& df : dateFormats)
153             {
154                 const QDateTime& time1a = QLocale::system().toDateTime(filterValueStringFirstPart, df);
155                 if(time1a.isValid())
156                 {
157                     filterValue.value1 = time1a.toSecsSinceEpoch();
158                     filterValue.str1 = "SsE";
159                 }
160 
161                 const QDateTime& time1b = QLocale::c().toDateTime(filterValueStringFirstPart, df);
162                 if(time1b.isValid())
163                 {
164                     filterValue.value1 = time1b.toSecsSinceEpoch();
165                     filterValue.str1 = "SsE";
166                 }
167 
168                 if(time1a.isValid() || time1b.isValid())
169                 {
170                     const QDateTime& time2a = QLocale::system().toDateTime(filterValueStringSecondPart, df);
171                     if(time2a.isValid())
172                     {
173                         filterValue.value2 = time2a.toSecsSinceEpoch();
174                         filterValue.str2 = "SsE";
175                     }
176 
177                     const QDateTime& time2b = QLocale::c().toDateTime(filterValueStringSecondPart, df);
178                     if(time2b.isValid())
179                     {
180                         filterValue.value2 = time2b.toSecsSinceEpoch();
181                         filterValue.str2 = "SsE";
182                     }
183                     break;
184                 }
185             }
186         }
187 
188         if(filterValue.toString().isEmpty())
189         {
190             //Match speeds and distances after dates to have less problems with avoid sorting them out
191             const static QString capNum = "(\\d+\\.?\\d*)(?![\\.\\d\\/\\:])";    //Match all numbers making sure no numbers are omitted directly at the end
192             const static QString capNumOpt = "(\\d+\\.?\\d*)?(?![\\.\\d\\/\\:])";
193             const static QString capIgnWS = "(?:\\s*)";     //Ignore Whitespaces
194             //Capture distances, speeds and simple Times that don't get caught by QDateTime.
195             const static QString capUnit = "(m|km|mi|ft|mi|m\\/h|km\\/h|mi\\/h|ft\\/h|mi\\/h|h|min|s|м|км|м\\/ч|км\\/ч|ч|мин|с)?";
196             const static QString capIgnAnd = "(?:" + tr("and") + ")?";
197             //The second number, the units and the "and" are optional
198             //The String has to be matched completely in order to avoid false positives thus the ^ and the $
199             QRegExp numericArguments("^" + capNum + capIgnWS + capUnit + capIgnWS + capIgnAnd + capIgnWS + capNumOpt + capIgnWS + capUnit + "$", Qt::CaseInsensitive);
200             numericArguments.indexIn(filterValueString);
201             if(numericArguments.cap(0).simplified() != "")
202             {
203                 if(numericArguments.cap(1) != "")     //to avoid removal of NOFLOAT
204                 {
205                     filterValue.value1 = numericArguments.cap(1).toFloat();
206                 }
207 
208                 filterValue.str1 = numericArguments.cap(2);
209 
210                 if(numericArguments.cap(3) != "")     //to avoid removal of NOFLOAT
211                 {
212                     filterValue.value2 = numericArguments.cap(3).toFloat();
213                 }
214 
215                 filterValue.str2 = numericArguments.cap(4);
216             }
217         }
218         if(filterValue.toString().isEmpty())
219         {
220             filterValue.str1 = filterValueString;
221         }
222         search.searchValue = filterValue;
223     }
224     improveQuery();
225     if(search.property == eSearchPropertyNoMatch)
226     {
227         syntaxError = true;
228     }
229 }
230 
getSearchResult(IGisItem * item)231 bool CSearch::getSearchResult(IGisItem* item)
232 {
233     bool passed = true;
234     if(searchTypeLambdaMap.contains(search.searchType))
235     {
236         const searchValue_t& itemFilterValue = item->getValueByKeyword(search.property);
237         passed = searchTypeLambdaMap.value(search.searchType)(itemFilterValue, search.searchValue);
238         return passed;
239     }
240     return true; //Empty search shouldn't hide anything
241 }
242 
243 //itemValue is the value returned by a GisItem and thus always has the same unit.
244 //searchValue is what the user queried
adjustUnits(const searchValue_t & itemValue,searchValue_t & searchValue)245 bool CSearch::adjustUnits(const searchValue_t& itemValue, searchValue_t& searchValue)
246 {
247     bool success = true;
248     if(searchValue.str1 != "" && searchValue.str1 != itemValue.str1)
249     {
250         success = IUnit::convert(searchValue.value1, searchValue.str1, itemValue.str1);
251         syntaxError |= !success;
252         if(!success)
253         {
254             return false;
255         }
256     }
257     else
258     {
259         searchValue.str1 = itemValue.str1;
260     }
261 
262     //Try to adjust unit of search to second value returned by item.
263     //If this fails (for example if the search is "max speed between a and b",
264     //adjust the unit of the search to the first returned unit
265     if(itemValue.str2 != "")
266     {
267         if(searchValue.str2 != "" && searchValue.str2 != itemValue.str2)
268         {
269             success = IUnit::convert(searchValue.value2, searchValue.str2, itemValue.str2);
270             syntaxError |= !success;
271         }
272         else
273         {
274             searchValue.str2 = itemValue.str2;
275         }
276     }
277     else
278     {
279         if(searchValue.str2 != "" && searchValue.str2 != itemValue.str1)
280         {
281             success = IUnit::convert(searchValue.value2, searchValue.str2, itemValue.str1);
282             syntaxError |= !success;
283         }
284         //Don't add a unit if there is no valid value
285         else if(searchValue.value2 != NOFLOAT)
286         {
287             searchValue.str2 = itemValue.str1;
288         }
289     }
290     return success;
291 }
292 
293 //Make life easier for the user. The method tries to make assumption on what the user meant
improveQuery()294 void CSearch::improveQuery()
295 {
296     //If the user entered a number with a unit and another number, assume they have the same unit
297     if(search.searchValue.str1 != "" && search.searchValue.str2 == "" && search.searchValue.value1 != NOFLOAT && search.searchValue.value2 != NOFLOAT)
298     {
299         search.searchValue.str2 = search.searchValue.str1;
300     }
301     if(search.searchValue.str1 == "" && search.searchValue.str2 != "" && search.searchValue.value1 != NOFLOAT && search.searchValue.value2 != NOFLOAT)
302     {
303         search.searchValue.str1 = search.searchValue.str2;
304     }
305 
306     //Try to guess what property the user meant when there is no match. I.e. make "shorter than 5km" work
307     if(search.property == eSearchPropertyNoMatch)
308     {
309         if(search.searchValue.str1.contains("/h", Qt::CaseInsensitive) ||
310            search.searchValue.str1.contains("/s", Qt::CaseInsensitive))
311         {
312             search.property = eSearchPropertyRteTrkAvgSpeed;
313             autoDetectedProperty = true;
314         }
315         else if(search.searchValue.str1.compare("km", Qt::CaseInsensitive) == 0 ||
316                 search.searchValue.str1.compare("mi", Qt::CaseInsensitive) == 0)
317         {
318             search.property = eSearchPropertyRteTrkDistance;
319             autoDetectedProperty = true;
320         }
321         else if(search.searchValue.str1.compare("m", Qt::CaseInsensitive) == 0 ||
322                 search.searchValue.str1.compare("ft", Qt::CaseInsensitive) == 0)
323         {
324             search.property = eSearchPropertyGeneralElevation;
325             autoDetectedProperty = true;
326         }
327         else if(search.searchValue.str1.compare("s", Qt::CaseInsensitive) == 0 ||
328                 search.searchValue.str1.compare("min", Qt::CaseInsensitive) == 0 ||
329                 search.searchValue.str1.compare("h", Qt::CaseInsensitive) == 0)
330         {
331             search.property = eSearchPropertyRteTrkTimeMoving;
332             autoDetectedProperty = true;
333         }
334         else if(search.searchValue.str1.compare("SsE", Qt::CaseInsensitive) == 0)
335         {
336             search.property = eSearchPropertyGeneralDate;
337             autoDetectedProperty = true;
338         }
339     }
340 
341     //Searching for dates is error prone, thus some checks to make sure one searche sfor a probable value.
342     if(search.property == eSearchPropertyGeneralDate)
343     {
344         if(search.searchValue.value1 != NOFLOAT)
345         {
346             //Try to catch if user only entered a year. Not done in regular detecting as it could be a speed or so.
347             if(search.searchValue.str1.isEmpty())
348             {
349                 search.searchValue.value1 = QDateTime(QDate(search.searchValue.value1, 1, 1)).toSecsSinceEpoch();
350                 search.searchValue.str1 = "SsE";
351             }
352             //Assume you want 2012 and not 1912 (qt defaults to 19xx)
353             else if(QDateTime::fromSecsSinceEpoch(search.searchValue.value1).addYears(100) <= QDateTime::currentDateTime())
354             {
355                 search.searchValue.value1 = QDateTime::fromSecsSinceEpoch(search.searchValue.value1).addYears(100).toSecsSinceEpoch();
356                 search.searchValue.str1 = "SsE";
357             }
358         }
359         if(search.searchValue.value2 != NOFLOAT)
360         {
361             //Try to catch if user only entered a year. Not done in regular detecting as it could be a speed or so.
362             if(search.searchValue.str2.isEmpty())
363             {
364                 search.searchValue.value2 = QDateTime(QDate(search.searchValue.value2, 1, 1)).toSecsSinceEpoch();
365                 search.searchValue.str2 = "SsE";
366             }
367             //Assume you want 2012 and not 1912 (qt defaults to 19xx)
368             else if(QDateTime::fromSecsSinceEpoch(search.searchValue.value2).addYears(100) <= QDateTime::currentDateTime())
369             {
370                 search.searchValue.value2 = QDateTime::fromSecsSinceEpoch(search.searchValue.value2).addYears(100).toSecsSinceEpoch();
371                 search.searchValue.str2 = "SsE";
372             }
373         }
374 
375         //If the user searched for 'date equals' change it to 'between 0h and 24h of the day queried'
376         if(search.searchType == eSearchTypeEquals)
377         {
378             search.searchType = eSearchTypeBetween;
379             search.searchValue.str2 = "SsE";
380             search.searchValue.value2 = search.searchValue.value1 + 24 * 60 * 60;
381         }
382     }
383 }
384 
initKeywordSearchTypeMap()385 QMap<QString, CSearch::search_type_e> CSearch::initKeywordSearchTypeMap()
386 {
387     QMap<QString, search_type_e> map;
388     map.insert(tr("with"), eSearchTypeWith);
389     map.insert(tr("contains"), eSearchTypeWith);
390     map.insert(tr("contain"), eSearchTypeWith);
391     map.insert(tr("containing"), eSearchTypeWith);
392     map.insert(tr("without"), eSearchTypeWithout);
393     map.insert(tr("shorter than"), eSearchTypeSmaller);
394     map.insert(tr("smaller than"), eSearchTypeSmaller);
395     map.insert(tr("under"), eSearchTypeSmaller);
396     map.insert(tr("lower than"), eSearchTypeSmaller);
397     map.insert(tr("earlier than"), eSearchTypeSmaller);
398     map.insert(tr("before"), eSearchTypeSmaller);
399     map.insert(tr("less than"), eSearchTypeSmaller);
400     map.insert("<", eSearchTypeSmaller);
401     map.insert(tr("longer than"), eSearchTypeBigger);
402     map.insert(tr("higher than"), eSearchTypeBigger);
403     map.insert(tr("bigger than"), eSearchTypeBigger);
404     map.insert(tr("greater than"), eSearchTypeBigger);
405     map.insert(tr("above"), eSearchTypeBigger);
406     map.insert(tr("over"), eSearchTypeBigger);
407     map.insert(tr("after"), eSearchTypeBigger);
408     map.insert(tr("later than"), eSearchTypeBigger);
409     map.insert(">", eSearchTypeBigger);
410     map.insert(tr("regex"), eSearchTypeRegEx);
411     map.insert("=", eSearchTypeEquals);
412     map.insert(tr("equals"), eSearchTypeEquals);
413     map.insert(tr("is"), eSearchTypeEquals);
414     map.insert(tr("between"), eSearchTypeBetween);
415     return map;
416 }
417 
initKeywordSearchExampleMap()418 QMap<QString, QString> CSearch::initKeywordSearchExampleMap()
419 {
420     QMap<QString, QString> map;
421     map.insert(tr("with"), tr("example: attributes with dog"));
422     map.insert(tr("contains"), tr("example: name contains bike"));
423     map.insert(tr("contain"), tr("example: keywords contain bike"));
424     map.insert(tr("containing"), tr("example: name containing bike"));
425     map.insert(tr("without"), tr("example: name without water"));
426     map.insert(tr("shorter than"), tr("example: shorter than 5km"));
427     map.insert(tr("smaller than"), tr("example: area smaller than 5m²"));
428     map.insert(tr("under"), tr("example: elevation under 1000ft"));
429     map.insert(tr("lower than"), tr("example: lower than 500m"));
430     map.insert(tr("earlier than"), tr("example: date earlier than 2015"));
431     map.insert(tr("before"), tr("example: date before 10.05.2017"));//Localisation of date in example!
432     map.insert(tr("less than"), tr("example: ascent less than 500m"));
433     map.insert("<", tr("example: D < 3"));
434     map.insert(tr("longer than"), tr("example: distance longer than 20mi"));
435     map.insert(tr("higher than"), tr("example: terrain higher than 2"));
436     map.insert(tr("bigger than"), tr("example: area bigger than 50m²"));
437     map.insert(tr("greater than"), tr("example: descent greater than 3000ft"));
438     map.insert(tr("above"), tr("example: above 50m"));
439     map.insert(tr("over"), tr("example: elevation over 400m"));
440     map.insert(tr("after"), tr("example: date after 2013"));
441     map.insert(tr("later than"), tr("example: date later than 2015"));
442     map.insert(">", tr("example: T > 4"));
443     map.insert(tr("regex"), tr("example: size regex (regular|large)"));
444     map.insert("=", tr("example: size = micro"));
445     map.insert(tr("equals"), tr("example: activity equals bike"));
446     map.insert(tr("is"), tr("example: status is available"));
447     map.insert(tr("between"), tr("example: length between 20km and 20mi"));
448     return map;
449 }
450 
initSearchPropertyEnumMap()451 QMap<QString, searchProperty_e> CSearch::initSearchPropertyEnumMap()
452 {
453     QMap<QString, searchProperty_e> map;
454     //General keywords
455     map.insert(tr("name"), eSearchPropertyGeneralName);
456     map.insert(tr("full text"), eSearchPropertyGeneralFullText);
457     map.insert(tr("elevation"), eSearchPropertyGeneralElevation);
458     map.insert(tr("date"), eSearchPropertyGeneralDate);
459     map.insert(tr("comment"), eSearchPropertyGeneralComment);
460     map.insert(tr("description"), eSearchPropertyGeneralDescription);
461     map.insert(tr("rating"), eSearchPropertyGeneralRating);
462     map.insert(tr("keywords"), eSearchPropertyGeneralKeywords);
463     map.insert(tr("type"), eSearchPropertyGeneralType);
464 
465     //Area keywords
466     map.insert(tr("area"), eSearchPropertyAreaArea);
467 
468     //Geocache keywords
469     map.insert(tr("difficulty"), eSearchPropertyGeocacheDifficulty);
470     map.insert("D", eSearchPropertyGeocacheDifficulty);
471     map.insert(tr("terrain"), eSearchPropertyGeocacheTerrain);
472     map.insert(tr("T"), eSearchPropertyGeocacheTerrain);
473     map.insert(tr("positive attributes"), eSearchPropertyGeocachePositiveAttributes);
474     map.insert(tr("non-negated attributes"), eSearchPropertyGeocachePositiveAttributes);
475     map.insert(tr("negated attributes"), eSearchPropertyGeocacheNegatedAttributes);
476     map.insert(tr("size"), eSearchPropertyGeocacheSize);
477     map.insert(tr("GCCode"), eSearchPropertyGeocacheGCCode);
478     map.insert(tr("GCName"), eSearchPropertyGeocacheGCName);
479     map.insert(tr("status"), eSearchPropertyGeocacheStatus);
480     map.insert(tr("GCType"), eSearchPropertyGeocacheGCType);
481     map.insert(tr("logged by"), eSearchPropertyGeocacheLoggedBy);
482     map.insert(tr("latest log date"), eSearchPropertyGeocacheLastLogDate);
483     map.insert(tr("latest log type"), eSearchPropertyGeocacheLastLogType);
484     map.insert(tr("latest log by"), eSearchPropertyGeocacheLastLogBy);
485     map.insert(tr("GCOwner"), eSearchPropertyGeocacheGCOwner);
486 
487     //Waypoint keywords
488 
489     //Route / track keywords
490     map.insert(tr("distance"), eSearchPropertyRteTrkDistance);
491     map.insert(tr("length"), eSearchPropertyRteTrkDistance);
492     map.insert(tr("ascent"), eSearchPropertyRteTrkAscent);
493     map.insert(tr("elevation gain"), eSearchPropertyRteTrkAscent);
494     map.insert(tr("descent"), eSearchPropertyRteTrkDescent);
495     map.insert(tr("min elevation"), eSearchPropertyRteTrkMinElevation);
496     map.insert(tr("minimal elevation"), eSearchPropertyRteTrkMinElevation);
497     map.insert(tr("max elevation"), eSearchPropertyRteTrkMaxElevation);
498     map.insert(tr("maximal elevation"), eSearchPropertyRteTrkMaxElevation);
499     map.insert(tr("max speed"), eSearchPropertyRteTrkMaxSpeed);
500     map.insert(tr("maximal speed"), eSearchPropertyRteTrkMaxSpeed);
501     map.insert(tr("min speed"), eSearchPropertyRteTrkMinSpeed);
502     map.insert(tr("minimal speed"), eSearchPropertyRteTrkMinSpeed);
503     map.insert(tr("average speed"), eSearchPropertyRteTrkAvgSpeed);
504     map.insert(tr("activity"), eSearchPropertyRteTrkActivity);
505     map.insert(tr("total time"), eSearchPropertyRteTrkTotalTime);
506     map.insert(tr("duration"), eSearchPropertyRteTrkTotalTime);
507     map.insert(tr("time moving"), eSearchPropertyRteTrkTimeMoving);
508 
509     return map;
510 }
511 
initSearchPropertyMeaningMap()512 QMap<searchProperty_e, QString> CSearch::initSearchPropertyMeaningMap()
513 {
514     QMap<searchProperty_e, QString> map;
515     //General keywords
516     map.insert(eSearchPropertyGeneralName, tr("searches the name of the item. For Geocaches this is \"Name - GCCode\"."));
517     map.insert(eSearchPropertyGeneralFullText, tr("searches the full text"));
518     map.insert(eSearchPropertyGeneralElevation, tr("searches the elevation. For items consisting of multiple points the minimum and the maximum is used"));
519     map.insert(eSearchPropertyGeneralDate, tr("searches the Date"));
520     map.insert(eSearchPropertyGeneralComment, tr("searches the Comment"));
521     map.insert(eSearchPropertyGeneralDescription, tr("searches the Description"));
522     map.insert(eSearchPropertyGeneralKeywords, tr("searches the Keywords"));
523     map.insert(eSearchPropertyGeneralRating, tr("compares the Rating"));
524     map.insert(eSearchPropertyGeneralType, tr("searches the type of the GisItem (Waypoint, Track, Route, Area)"));
525 
526     //Area keywords
527     map.insert(eSearchPropertyAreaArea, tr("searches the area"));
528 
529     //Geocache keywords
530     map.insert(eSearchPropertyGeocacheDifficulty, tr("searches the difficulty rating of a geocache"));
531     map.insert(eSearchPropertyGeocacheTerrain, tr("searches the terrain rating of a geocache"));
532     map.insert(eSearchPropertyGeocachePositiveAttributes, tr("searches the translated meanings of the non-negated attributes (Those not crossed out)"));
533     map.insert(eSearchPropertyGeocacheNegatedAttributes, tr("searches the translated meanings of the negated attributes (Those crossed out)"));
534     map.insert(eSearchPropertyGeocacheSize, tr("searches the size of a geocache. (micro, small, regular, large)"));
535     map.insert(eSearchPropertyGeocacheGCCode, tr("searches the GCCode of a geocache."));
536     map.insert(eSearchPropertyGeocacheGCName, tr("searches the Name of a geocache."));
537     map.insert(eSearchPropertyGeocacheStatus, tr("searches the status of a geocache. (available, not available, archived)"));
538     map.insert(eSearchPropertyGeocacheGCType, tr("searches the type of a geocache. (traditional, unknown, virtual...)"));
539     map.insert(eSearchPropertyGeocacheLoggedBy, tr("searches the available logs for a username"));
540     map.insert(eSearchPropertyGeocacheLastLogDate, tr("searches the date of the latest log"));
541     map.insert(eSearchPropertyGeocacheLastLogType, tr("searches the type of the latest log (Found It, Didn't find it, Owner Maintenance, Write Note...)"));
542     map.insert(eSearchPropertyGeocacheLastLogBy, tr("searches the username of the latest log"));
543     map.insert(eSearchPropertyGeocacheGCOwner, tr("searches the username of the geocache owner"));
544 
545     //Waypoint keywords
546 
547     //Route / track keywords
548     map.insert(eSearchPropertyRteTrkDistance, tr("searches the distance covered by a route or track"));
549     map.insert(eSearchPropertyRteTrkAscent, tr("searches the total ascent in a route or track"));
550     map.insert(eSearchPropertyRteTrkDescent, tr("searches the total descent in a route or track"));
551     map.insert(eSearchPropertyRteTrkMinElevation, tr("searches the minimal elevation in a route or track"));
552     map.insert(eSearchPropertyRteTrkMaxElevation, tr("searches the maximal elevation in a route or track"));
553     map.insert(eSearchPropertyRteTrkMaxSpeed, tr("searches the maximal speed in a track"));
554     map.insert(eSearchPropertyRteTrkMinSpeed, tr("searches the minimal speed in a track"));
555     map.insert(eSearchPropertyRteTrkAvgSpeed, tr("searches the average speed in a track"));
556     map.insert(eSearchPropertyRteTrkActivity, tr("searches the activity of a route or track"));
557     map.insert(eSearchPropertyRteTrkTotalTime, tr("searches the total time spent on a route or track"));
558     map.insert(eSearchPropertyRteTrkTimeMoving, tr("searches the time spent moving on a track"));
559 
560     return map;
561 }
562 
initSearchTypeLambdaMap()563 QMap<CSearch::search_type_e, CSearch::fSearch> CSearch::initSearchTypeLambdaMap()
564 {
565     QMap<CSearch::search_type_e, CSearch::fSearch> map;
566     map.insert(eSearchTypeEquals, [](const searchValue_t& itemValue, searchValue_t& searchValue){
567         return itemValue.toString().compare(searchValue.toString(), CSearch::caseSensitivity) == 0;
568     });
569     map.insert(eSearchTypeSmaller, [this](const searchValue_t& itemValue, searchValue_t& searchValue){
570         if(itemValue.value1 != NOFLOAT)
571         {
572             if(searchValue.value1 != NOFLOAT)
573             {
574                 bool adjustSuccess = adjustUnits(itemValue, searchValue);
575                 if(adjustSuccess == false)
576                 {
577                     return false;
578                 }
579                 if(itemValue.value2 == NOFLOAT)
580                 {
581                     return itemValue.value1 < searchValue.value1;
582                 }
583                 else
584                 {
585                     return qMax(itemValue.value1, itemValue.value2) < searchValue.value1;
586                 }
587             }
588             else
589             {
590                 syntaxError = true;
591             }
592         }
593         return false;
594     });
595     map.insert(eSearchTypeBigger, [this](const searchValue_t& itemValue, searchValue_t& searchValue){
596         if(itemValue.value1 != NOFLOAT)
597         {
598             if(searchValue.value1 != NOFLOAT)
599             {
600                 bool adjustSuccess = adjustUnits(itemValue, searchValue);
601                 if(adjustSuccess == false)
602                 {
603                     return false;
604                 }
605                 if(itemValue.value2 == NOFLOAT)
606                 {
607                     return itemValue.value1 > searchValue.value1;
608                 }
609                 else
610                 {
611                     return qMin(itemValue.value1, itemValue.value2) > searchValue.value1;
612                 }
613             }
614             else
615             {
616                 syntaxError = true;
617             }
618         }
619         return false;
620     });
621 
622     map.insert(eSearchTypeBetween, [this](const searchValue_t& itemValue, searchValue_t& searchValue){
623         if(itemValue.value1 != NOFLOAT)
624         {
625             if(searchValue.value1 != NOFLOAT && searchValue.value2 != NOFLOAT)
626             {
627                 bool adjustSuccess = adjustUnits(itemValue, searchValue);
628                 if(adjustSuccess == false)
629                 {
630                     return false;
631                 }
632                 if(itemValue.value2 == NOFLOAT)
633                 {
634                     return itemValue.value1 < qMax(searchValue.value1, searchValue.value2) && itemValue.value1 > qMin(searchValue.value1, searchValue.value2);
635                 }
636                 else
637                 {
638                     return qMax(itemValue.value1, itemValue.value2) < qMax(searchValue.value1, searchValue.value2) && qMin(itemValue.value1, itemValue.value2) > qMin(searchValue.value1, searchValue.value2);
639                 }
640             }
641             else
642             {
643                 syntaxError = true;
644             }
645         }
646         return false;
647     });
648     map.insert(eSearchTypeWith, [](const searchValue_t& itemValue, searchValue_t& searchValue){
649         return itemValue.toString().contains(searchValue.toString(), CSearch::caseSensitivity);
650     });
651     map.insert(eSearchTypeWithout, [](const searchValue_t& itemValue, searchValue_t& searchValue){
652         if(itemValue.toString().isEmpty())
653         {
654             return false;
655         }
656         else
657         {
658             return !itemValue.toString().contains(searchValue.toString(), CSearch::caseSensitivity);
659         }
660     });
661     map.insert(eSearchTypeRegEx, [](const searchValue_t& itemValue, searchValue_t& searchValue){
662         if(CSearch::caseSensitivity == Qt::CaseInsensitive)//There is no option to make regex caseinsensitive
663         {
664             return itemValue.toString().toLower().contains(QRegExp(searchValue.toString().toLower()));
665         }
666         else
667         {
668             return itemValue.toString().contains(QRegExp(searchValue.toString()));
669         }
670     });
671     return map;
672 }
673