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