Skip to content

Commit 65a28dd

Browse files
committed
WIP: Complete rework
Move main functionality in new classes. Change pattern of dynamic queries, encapsulate comparision operations in dynamic query pattern.
1 parent 3535eaa commit 65a28dd

7 files changed

Lines changed: 570 additions & 2 deletions

File tree

code-check-wrapper.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ for I in \
5555
course_file_format.cpp \
5656
crs_ \
5757
duplicate_equals_t.cpp \
58+
dynamic_object_query.cpp \
5859
file_dialog.cpp \
5960
/file_format.cpp \
6061
file_format_t.cpp \

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ set(Mapper_Common_SRCS
104104
core/virtual_path.cpp
105105

106106
core/objects/boolean_tool.cpp
107+
core/objects/dynamic_object_query.cpp
107108
core/objects/object.cpp
108109
core/objects/object_mover.cpp
109110
core/objects/object_query.cpp
Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
/*
2+
* Copyright 2025 Matthias Kühlewein
3+
*
4+
* This file is part of OpenOrienteering.
5+
*
6+
* OpenOrienteering 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+
* OpenOrienteering 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 OpenOrienteering. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
#include "dynamic_object_query.h"
21+
22+
#include <algorithm>
23+
#include <functional>
24+
25+
#include <Qt>
26+
#include <QLatin1Char>
27+
28+
#include "core/map.h"
29+
#include "core/objects/object.h"
30+
#include "core/symbols/symbol.h"
31+
32+
namespace {
33+
34+
struct CompOpStruct {
35+
const QString op;
36+
const std::function<bool (double, double)> fn;
37+
};
38+
39+
static const CompOpStruct numerical_compare_operations[6] = {
40+
{ QLatin1String("<="), [](double op1, double op2) { return op1 <= op2; } },
41+
{ QLatin1String(">="), [](double op1, double op2) { return op1 >= op2; } },
42+
{ QLatin1String("=="), [](double op1, double op2) { return op1 == op2; } },
43+
{ QLatin1String("!="), [](double op1, double op2) { return op1 != op2; } },
44+
{ QLatin1String("<"), [](double op1, double op2) { return op1 < op2; } },
45+
{ QLatin1String(">"), [](double op1, double op2) { return op1 > op2; } },
46+
};
47+
48+
bool NumericalComparison(bool compare, QString element, double op1 = 0.0)
49+
{
50+
for (const auto& comp : numerical_compare_operations)
51+
{
52+
if (element.startsWith(comp.op))
53+
{
54+
const auto valuestr = element.remove(comp.op).trimmed();
55+
bool ok;
56+
const double op2 = valuestr.toDouble(&ok);
57+
if (!compare)
58+
return ok;
59+
return (comp.fn)(op1, op2);
60+
}
61+
}
62+
return false;
63+
}
64+
65+
bool EvaluateAndOrOperation(const QString& element, int& and_or_operation)
66+
{
67+
if (element == QLatin1String("AND"))
68+
and_or_operation = 0;
69+
else if (element == QLatin1String("OR"))
70+
and_or_operation = 1;
71+
else
72+
return false;
73+
return true;
74+
}
75+
76+
bool EvaluatePaperReal(const QString& element, int& type)
77+
{
78+
if (element == QLatin1String("PAPER"))
79+
type = 0;
80+
else if (element == QLatin1String("REAL"))
81+
type = 1;
82+
else
83+
return false;
84+
return true;
85+
}
86+
87+
bool EvaluateSymbolType(QString element, int symbol_type, bool& result)
88+
{
89+
QStringList symbol_types = {QLatin1String("Point"), QLatin1String("Line"), QLatin1String("Area"), QLatin1String("Text"), QLatin1String("Combined")};
90+
int operation;
91+
92+
if (element.startsWith(QLatin1String("==")))
93+
operation = 0;
94+
else if (element.startsWith(QLatin1String("!=")))
95+
operation = 1;
96+
else
97+
return false;
98+
99+
const auto valuestr = element.remove(0, 2).trimmed();
100+
//auto index = symbol_types.indexOf(valuestr, 0, Qt::CaseInsensitive); // Qt 6.7
101+
auto match = std::find_if(symbol_types.begin(), symbol_types.end(), [&valuestr](const auto& item) {
102+
return !item.compare(valuestr, Qt::CaseInsensitive);
103+
});
104+
if (match != symbol_types.end())
105+
{
106+
auto index = match - symbol_types.begin();
107+
result = operation ? (1<<index != symbol_type) : (1<<index == symbol_type);
108+
return true;
109+
}
110+
return false;
111+
}
112+
113+
} // namespace
114+
115+
116+
namespace OpenOrienteering {
117+
118+
DynamicObjectQueryManager::DynamicObjectQueryManager()
119+
{
120+
// nothing else
121+
}
122+
123+
DynamicObjectQuery::~DynamicObjectQuery()= default;
124+
125+
DynamicObjectQuery* DynamicObjectQueryManager::parse(QStringRef token_text, QStringRef token_attributes_text)
126+
{
127+
if (AreaObjectQuery::getKeyword() == token_text)
128+
{
129+
return new AreaObjectQuery(token_attributes_text);
130+
}
131+
else if (LineObjectQuery::getKeyword() == token_text)
132+
{
133+
return new LineObjectQuery(token_attributes_text);
134+
}
135+
else if (SymbolQuery::getKeyword() == token_text)
136+
{
137+
return new SymbolQuery(token_attributes_text);
138+
}
139+
/*
140+
else if (ObjectQuery::getKeyword() == token_text)
141+
{
142+
return new ObjectQuery(token_attributes_text);
143+
}
144+
*/
145+
return nullptr; // we should not get here
146+
}
147+
148+
bool DynamicObjectQueryManager::performDynamicQuery(const Object* object, const DynamicObjectQuery* dynamic_query)
149+
{
150+
switch (dynamic_query->getType())
151+
{
152+
case DynamicObjectQuery::AreaObjectQuery:
153+
if (object->getType() == Object::Path)
154+
{
155+
const auto& path_object = static_cast<const PathObject*>(object);
156+
const auto symbol = path_object->getSymbol();
157+
if (symbol && symbol->getContainedTypes() & Symbol::Area)
158+
return static_cast<const AreaObjectQuery*>(dynamic_query)->performQuery(path_object);
159+
}
160+
return false;
161+
case DynamicObjectQuery::LineObjectQuery:
162+
if (object->getType() == Object::Path)
163+
{
164+
const auto& path_object = static_cast<const PathObject*>(object);
165+
return static_cast<const LineObjectQuery*>(dynamic_query)->performQuery(path_object);
166+
}
167+
return false;
168+
case DynamicObjectQuery::SymbolQuery:
169+
{
170+
const auto symbol = object->getSymbol();
171+
const auto map = object->getMap();
172+
return symbol && map ? static_cast<const SymbolQuery*>(dynamic_query)->performQuery(map, symbol) : false;
173+
}
174+
/*
175+
case DynamicObjectQuery::ObjectQuery:
176+
return static_cast<const ObjectQuery*>(dynamic_query)->performQuery(object);
177+
*/
178+
default:
179+
return false; // we should not get here
180+
}
181+
182+
Q_UNREACHABLE();
183+
}
184+
185+
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+
* */
194+
AreaObjectQuery::AreaObjectQuery(QStringRef token_attributes_text)
195+
: DynamicObjectQuery(DynamicObjectQuery::AreaObjectQuery)
196+
{
197+
parseTokenAttributes(token_attributes_text);
198+
valid = performQuery(nullptr); // dry run
199+
}
200+
201+
bool AreaObjectQuery::performQuery(const PathObject* path_object) const
202+
{
203+
int area_type = 0; // 0 = PAPER, 1 = REAL
204+
int and_or_operation = 1; // 0 = AND, 1 = OR
205+
double object_value;
206+
int object_value_status = -1; // -1 = undefined, 0 = PAPER value, 1 = REAL value
207+
208+
bool result = false;
209+
for (auto& element : attributes)
210+
{
211+
if (EvaluateAndOrOperation(element, and_or_operation))
212+
continue;
213+
else if (EvaluatePaperReal(element, area_type))
214+
continue;
215+
else if (element == QLatin1String("ISTOOSMALL"))
216+
{
217+
const bool is_too_small = path_object ? path_object->isAreaTooSmall() : false;
218+
result = and_or_operation ? (result || is_too_small) : (result && is_too_small);
219+
and_or_operation = 0; // default after first operation is AND operation
220+
}
221+
else if (NumericalComparison(false, element))
222+
{
223+
if (object_value_status != area_type)
224+
{
225+
object_value = path_object ? (area_type ? path_object->calculateRealArea() : path_object->calculatePaperArea()) : 0.0;
226+
object_value_status = area_type;
227+
}
228+
const bool comparison_result = NumericalComparison(true, element, object_value);
229+
result = and_or_operation ? (result || comparison_result) : (result && comparison_result);
230+
and_or_operation = 0; // default after first operation is AND operation
231+
}
232+
else // unknown element or failure in NumericalComparison()
233+
{
234+
if (!path_object)
235+
return false; // dry run failed
236+
break;
237+
}
238+
}
239+
if (path_object)
240+
return result;
241+
return true; // dry run was successful
242+
}
243+
244+
245+
LineObjectQuery::LineObjectQuery(QStringRef token_attributes_text)
246+
: DynamicObjectQuery(DynamicObjectQuery::LineObjectQuery)
247+
{
248+
parseTokenAttributes(token_attributes_text);
249+
valid = performQuery(nullptr); // dry run
250+
}
251+
252+
bool LineObjectQuery::performQuery(const PathObject* path_object) const
253+
{
254+
int line_type = 0; // 0 = PAPER, 1 = REAL
255+
int and_or_operation = 1; // 0 = AND, 1 = OR
256+
double object_value;
257+
int object_value_status = -1; // -1 = undefined, 0 = PAPER value, 1 = REAL value
258+
259+
bool result = false;
260+
for (auto& element : attributes)
261+
{
262+
if (EvaluateAndOrOperation(element, and_or_operation))
263+
continue;
264+
else if (EvaluatePaperReal(element, line_type))
265+
continue;
266+
else if (element == QLatin1String("ISTOOSHORT"))
267+
{
268+
const bool is_too_short = path_object ? path_object->isLineTooShort() : false;
269+
result = and_or_operation ? (result || is_too_short) : (result && is_too_short);
270+
and_or_operation = 0; // default after first operation is AND operation
271+
}
272+
else if (NumericalComparison(false, element))
273+
{
274+
if (object_value_status != line_type)
275+
{
276+
object_value = path_object ? (line_type ? path_object->getRealLength() : path_object->getPaperLength()) : 0.0;
277+
object_value_status = line_type;
278+
}
279+
const bool comparison_result = NumericalComparison(true, element, object_value);
280+
result = and_or_operation ? (result || comparison_result) : (result && comparison_result);
281+
and_or_operation = 0; // default after first operation is AND operation
282+
}
283+
else // unknown element or failure in NumericalComparison()
284+
{
285+
if (!path_object)
286+
return false; // dry run failed
287+
break;
288+
}
289+
}
290+
if (path_object)
291+
return result;
292+
return true; // dry run was successful
293+
}
294+
295+
296+
SymbolQuery::SymbolQuery(QStringRef token_attributes_text)
297+
: DynamicObjectQuery(DynamicObjectQuery::SymbolQuery)
298+
{
299+
parseTokenAttributes(token_attributes_text);
300+
valid = performQuery(nullptr, nullptr); // dry run
301+
}
302+
303+
304+
bool SymbolQuery::performQuery(const Map* map, const Symbol* symbol) const
305+
{
306+
int and_or_operation = 1; // 0 = AND, 1 = OR
307+
int symbol_property = 0; // 0 = TYPE, 1 = ID
308+
309+
bool result = false;
310+
bool symbol_type_result;
311+
for (auto& element : attributes)
312+
{
313+
if (EvaluateAndOrOperation(element, and_or_operation))
314+
continue;
315+
else if (element == QLatin1String("TYPE"))
316+
symbol_property = 0;
317+
else if (element == QLatin1String("ID"))
318+
symbol_property = 1;
319+
else if (element == QLatin1String("ISUNDEFINED"))
320+
{
321+
const bool is_undefined = map && symbol ? (map->findSymbolIndex(symbol) < 0) : false;
322+
result = and_or_operation ? (result || is_undefined) : (result && is_undefined);
323+
and_or_operation = 0; // default after first operation is AND operation
324+
}
325+
else if (symbol_property == 0 && EvaluateSymbolType(element, (symbol ? symbol->getType() : 0), symbol_type_result))
326+
{
327+
result = and_or_operation ? (result || symbol_type_result) : (result && symbol_type_result);
328+
and_or_operation = 0; // default after first operation is AND operation
329+
}
330+
//else if (symbol_property == 1 && ....)
331+
else // unknown element or failure in EvaluateSymbolType()
332+
{
333+
if (!symbol)
334+
return false; // dry run failed
335+
break;
336+
}
337+
}
338+
if (symbol)
339+
return result;
340+
return true; // dry run was successful
341+
}
342+
343+
/*
344+
ObjectQuery::ObjectQuery(QStringRef token_attributes_text)
345+
: DynamicObjectQuery(DynamicObjectQuery::ObjectQuery)
346+
{
347+
parseTokenAttributes(token_attributes_text);
348+
valid = performQuery(nullptr); // dry run
349+
}
350+
*/
351+
352+
DynamicObjectQuery::DynamicObjectQuery(Type type) noexcept
353+
: type { type }
354+
{
355+
// nothing else
356+
}
357+
358+
void DynamicObjectQuery::parseTokenAttributes(QStringRef token_attributes_text)
359+
{
360+
attributes = token_attributes_text.toString().split(QLatin1Char(';'), Qt::SkipEmptyParts);
361+
for (auto& item : attributes)
362+
item = item.trimmed();
363+
}
364+
365+
366+
} // namespace OpenOrienteering

0 commit comments

Comments
 (0)