Skip to content

Commit 6df1260

Browse files
committed
Finish rework
Add more functionality. Enhance keyword pull-down menu to be context sensitive.
1 parent baa9b67 commit 6df1260

4 files changed

Lines changed: 216 additions & 60 deletions

File tree

src/core/objects/dynamic_object_query.cpp

Lines changed: 184 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2025 Matthias Kühlewein
2+
* Copyright 2026 Matthias Kühlewein
33
*
44
* This file is part of OpenOrienteering.
55
*
@@ -24,8 +24,10 @@
2424

2525
#include <Qt>
2626
#include <QLatin1Char>
27+
#include <QLatin1String>
2728

2829
#include "core/map.h"
30+
#include "core/map_part.h"
2931
#include "core/objects/object.h"
3032
#include "core/symbols/symbol.h"
3133

@@ -86,13 +88,13 @@ bool EvaluatePaperReal(const QString& element, int& type)
8688

8789
bool EvaluateSymbolType(QString element, int symbol_type, bool& result)
8890
{
89-
QStringList symbol_types = {QLatin1String("Point"), QLatin1String("Line"), QLatin1String("Area"), QLatin1String("Text"), QLatin1String("Combined")};
91+
const QStringList symbol_types = {QLatin1String("Point"), QLatin1String("Line"), QLatin1String("Area"), QLatin1String("Text"), QLatin1String("Combined")};
9092
int operation;
9193

9294
if (element.startsWith(QLatin1String("==")))
93-
operation = 0;
94-
else if (element.startsWith(QLatin1String("!=")))
9595
operation = 1;
96+
else if (element.startsWith(QLatin1String("!=")))
97+
operation = 0;
9698
else
9799
return false;
98100

@@ -104,17 +106,60 @@ bool EvaluateSymbolType(QString element, int symbol_type, bool& result)
104106
if (match != symbol_types.end())
105107
{
106108
auto index = match - symbol_types.begin();
107-
result = operation ? (1<<index != symbol_type) : (1<<index == symbol_type);
109+
result = operation ? (1<<index == symbol_type) : (1<<index != symbol_type);
108110
return true;
109111
}
110112
return false;
111113
}
112114

115+
bool EvaluateSymbolId(QString element, QString symbol_id, bool& result)
116+
{
117+
int operation;
118+
119+
if (element.startsWith(QLatin1String("==")))
120+
operation = 1;
121+
else if (element.startsWith(QLatin1String("!=")))
122+
operation = 0;
123+
else
124+
return false;
125+
126+
const auto symbol_id_elements = symbol_id.split(QLatin1Char('.'));
127+
const auto valuestr = element.remove(0, 2).trimmed();
128+
if (valuestr.isEmpty())
129+
return false;
130+
const auto value_elements = valuestr.split(QLatin1Char('.'));
131+
if (value_elements.size() > 3)
132+
return false;
133+
134+
for (auto i = 0; i < value_elements.size(); ++i)
135+
{
136+
if (i >= symbol_id_elements.size())
137+
{
138+
result = !(bool)operation;
139+
return true;
140+
}
141+
if (value_elements.at(i) == QLatin1Char('*'))
142+
{
143+
result = (bool)operation;
144+
return true;
145+
}
146+
if (value_elements.at(i) != symbol_id_elements.at(i))
147+
{
148+
result = !(bool)operation;
149+
return true;
150+
}
151+
}
152+
result = (bool)operation;
153+
return true;
154+
}
155+
113156
} // namespace
114157

115158

116159
namespace OpenOrienteering {
117160

161+
const QStringList DynamicObjectQueryManager::keywords = {QLatin1String{"LINE"}, QLatin1String{"AREA"}, QLatin1String{"SYMBOL"}, QLatin1String{"OBJECT"}};
162+
118163
DynamicObjectQueryManager::DynamicObjectQueryManager()
119164
{
120165
// nothing else
@@ -124,25 +169,23 @@ DynamicObjectQuery::~DynamicObjectQuery()= default;
124169

125170
DynamicObjectQuery* DynamicObjectQueryManager::parse(QStringRef token_text, QStringRef token_attributes_text)
126171
{
127-
if (AreaObjectQuery::getKeyword() == token_text)
172+
if (token_text == keywords[DynamicObjectQuery::AreaObjectQuery])
128173
{
129174
return new AreaObjectQuery(token_attributes_text);
130175
}
131-
else if (LineObjectQuery::getKeyword() == token_text)
176+
else if (token_text == keywords[DynamicObjectQuery::LineObjectQuery])
132177
{
133178
return new LineObjectQuery(token_attributes_text);
134179
}
135-
else if (SymbolQuery::getKeyword() == token_text)
180+
else if (token_text == keywords[DynamicObjectQuery::SymbolQuery])
136181
{
137182
return new SymbolQuery(token_attributes_text);
138183
}
139-
/*
140-
else if (ObjectQuery::getKeyword() == token_text)
184+
else if (token_text == keywords[DynamicObjectQuery::GeneralObjectQuery])
141185
{
142-
return new ObjectQuery(token_attributes_text);
186+
return new GeneralObjectQuery(token_attributes_text);
143187
}
144-
*/
145-
return nullptr; // we should not get here
188+
return nullptr;
146189
}
147190

148191
bool DynamicObjectQueryManager::performDynamicQuery(const Object* object, const DynamicObjectQuery* dynamic_query)
@@ -171,26 +214,94 @@ bool DynamicObjectQueryManager::performDynamicQuery(const Object* object, const
171214
const auto map = object->getMap();
172215
return symbol && map ? static_cast<const SymbolQuery*>(dynamic_query)->performQuery(map, symbol) : false;
173216
}
174-
/*
175-
case DynamicObjectQuery::ObjectQuery:
176-
return static_cast<const ObjectQuery*>(dynamic_query)->performQuery(object);
177-
*/
217+
case DynamicObjectQuery::GeneralObjectQuery:
218+
{
219+
const auto map = object->getMap();
220+
return map ? static_cast<const GeneralObjectQuery*>(dynamic_query)->performQuery(map, object) : false;
221+
}
178222
default:
179223
return false; // we should not get here
180224
}
181225

182226
Q_UNREACHABLE();
183227
}
184228

229+
const QStringList DynamicObjectQueryManager::getContextKeywords(const QString& text, int position, bool& append)
230+
{
231+
int keyword_found = -1;
232+
int i = position - 1;
233+
for ( ; i > 3; --i) // consider shortest keyword
234+
{
235+
if (text.at(i) == QLatin1Char(')'))
236+
{
237+
break;
238+
}
239+
else if (text.at(i) == QLatin1Char('('))
240+
{
241+
for (auto j = 0; j < keywords.size(); ++j)
242+
{
243+
auto match = text.lastIndexOf(keywords[j], i-1);
244+
if (match != -1 && match == i - keywords[j].length())
245+
{
246+
keyword_found = j;
247+
break;
248+
}
249+
}
250+
}
251+
}
252+
253+
if (keyword_found == -1)
254+
{
255+
QStringList context_keywords;
256+
for (auto keyword : keywords)
257+
context_keywords.append(keyword.append(QLatin1String("()")));
258+
append = true;
259+
return context_keywords;
260+
}
261+
262+
auto parameters = text.mid(i, position - i).trimmed();
263+
264+
switch (keyword_found)
265+
{
266+
case DynamicObjectQuery::LineObjectQuery:
267+
case DynamicObjectQuery::AreaObjectQuery:
268+
for (const auto& comp : numerical_compare_operations)
269+
{
270+
if (parameters.endsWith(comp.op))
271+
return QStringList();
272+
}
273+
{
274+
QStringList keywords = {keyword_found == DynamicObjectQuery::LineObjectQuery ? QLatin1String("ISTOOSHORT;") : QLatin1String("ISTOOSMALL;")};
275+
keywords += QString(QLatin1String("PAPER; REAL; AND; OR;")).split(QLatin1Char(' '));
276+
for (const auto& comp : numerical_compare_operations)
277+
keywords += comp.op;
278+
return keywords;
279+
}
280+
281+
case DynamicObjectQuery::SymbolQuery:
282+
if (parameters.endsWith(QLatin1String("==")) || parameters.endsWith(QLatin1String("!=")))
283+
{
284+
const auto find_type_keyword = parameters.lastIndexOf(QLatin1String("TYPE"));
285+
const auto find_id_keyword = parameters.lastIndexOf(QLatin1String("ID"));
286+
if (find_type_keyword >= find_id_keyword) // if both keywords are not found the '=' part is needed for the -1 values
287+
{
288+
return QString(QLatin1String("Point; Line; Area; Text; Combined;")).split(QLatin1Char(' '));
289+
}
290+
return QStringList();
291+
}
292+
return QString(QLatin1String("ISUNDEFINED; TYPE; ID; == != AND; OR;")).split(QLatin1Char(' '));
293+
294+
case DynamicObjectQuery::GeneralObjectQuery:
295+
return QStringList{QLatin1String("IGNORESYMBOL;"), QLatin1String("ISDUPLICATE;")};
296+
297+
default:
298+
return QStringList(); // we should not get here
299+
}
300+
301+
Q_UNREACHABLE();
302+
}
303+
185304

186-
/* Bsp. und Ideen:
187-
* AREA(ISTOOSMALL)
188-
* AREA(REAL;<= 20.5;OR;>= 30.2)
189-
* SYMBOL(ISUNDEFINED) bzw. SYMBOL(TYPE;== UNDEFINED)
190-
* SYMBOL(TYPE;== AREA)
191-
* SYMBOL(TYPE;== ISHELPER)
192-
* SYMBOL(ID;== 102.1)
193-
* */
194305
AreaObjectQuery::AreaObjectQuery(QStringRef token_attributes_text)
195306
: DynamicObjectQuery(DynamicObjectQuery::AreaObjectQuery)
196307
{
@@ -307,7 +418,7 @@ bool SymbolQuery::performQuery(const Map* map, const Symbol* symbol) const
307418
int symbol_property = 0; // 0 = TYPE, 1 = ID
308419

309420
bool result = false;
310-
bool symbol_type_result;
421+
bool eval_symbol_result;
311422
for (auto& element : attributes)
312423
{
313424
if (EvaluateAndOrOperation(element, and_or_operation))
@@ -322,13 +433,17 @@ bool SymbolQuery::performQuery(const Map* map, const Symbol* symbol) const
322433
result = and_or_operation ? (result || is_undefined) : (result && is_undefined);
323434
and_or_operation = 0; // default after first operation is AND operation
324435
}
325-
else if (symbol_property == 0 && EvaluateSymbolType(element, (symbol ? symbol->getType() : 0), symbol_type_result))
436+
else if (symbol_property == 0 && EvaluateSymbolType(element, (symbol ? symbol->getType() : 0), eval_symbol_result))
437+
{
438+
result = and_or_operation ? (result || eval_symbol_result) : (result && eval_symbol_result);
439+
and_or_operation = 0; // default after first operation is AND operation
440+
}
441+
else if (symbol_property == 1 && EvaluateSymbolId(element, (symbol ? symbol->getNumberAsString() : QLatin1String("1")), eval_symbol_result))
326442
{
327-
result = and_or_operation ? (result || symbol_type_result) : (result && symbol_type_result);
443+
result = and_or_operation ? (result || eval_symbol_result) : (result && eval_symbol_result);
328444
and_or_operation = 0; // default after first operation is AND operation
329445
}
330-
//else if (symbol_property == 1 && ....)
331-
else // unknown element or failure in EvaluateSymbolType()
446+
else // unknown element or failure in EvaluateSymbolType() or EvaluateSymbolId()
332447
{
333448
if (!symbol)
334449
return false; // dry run failed
@@ -340,14 +455,48 @@ bool SymbolQuery::performQuery(const Map* map, const Symbol* symbol) const
340455
return true; // dry run was successful
341456
}
342457

343-
/*
344-
ObjectQuery::ObjectQuery(QStringRef token_attributes_text)
345-
: DynamicObjectQuery(DynamicObjectQuery::ObjectQuery)
458+
459+
GeneralObjectQuery::GeneralObjectQuery(QStringRef token_attributes_text)
460+
: DynamicObjectQuery(DynamicObjectQuery::GeneralObjectQuery)
346461
{
347462
parseTokenAttributes(token_attributes_text);
348-
valid = performQuery(nullptr); // dry run
463+
valid = performQuery(nullptr, nullptr); // dry run
349464
}
350-
*/
465+
466+
bool GeneralObjectQuery::performQuery(const Map* map, const Object* object) const
467+
{
468+
int and_or_operation = 1; // 0 = AND, 1 = OR
469+
bool ignore_symbols = false;
470+
471+
bool result = false;
472+
for (auto& element : attributes)
473+
{
474+
if (EvaluateAndOrOperation(element, and_or_operation))
475+
continue;
476+
else if (element == QLatin1String("IGNORESYMBOL"))
477+
ignore_symbols = true;
478+
else if (element == QLatin1String("ISDUPLICATE"))
479+
{
480+
if (!map || !object)
481+
return true;
482+
const bool isduplicate_result = map->getCurrentPart()->existsObject([object, ignore_symbols](auto const* o)
483+
{ return object != o && object->equals(o, !ignore_symbols); }
484+
);
485+
result = and_or_operation ? (result || isduplicate_result) : (result && isduplicate_result);
486+
and_or_operation = 0; // default after first operation is AND operation
487+
}
488+
else // unknown element
489+
{
490+
if (!object)
491+
return false; // dry run failed
492+
break;
493+
}
494+
}
495+
if (object)
496+
return result;
497+
return true; // dry run was successful
498+
}
499+
351500

352501
DynamicObjectQuery::DynamicObjectQuery(Type type) noexcept
353502
: type { type }

0 commit comments

Comments
 (0)