17 : QWidget(parent), m_type(NO_VALUE) {
29 QMap<QString, QString>
result;
30 for (
auto it = m_lineEdits.constBegin(); it != m_lineEdits.constEnd();
32 result[it.key()] = it.value()->text();
38 for (
auto it =
values.constBegin(); it !=
values.constEnd(); ++it) {
39 if (m_lineEdits.contains(it.key())) {
40 m_lineEdits[it.key()]->setText(it.value());
46 for (
auto* edit : m_lineEdits) {
51 void cvQueryValueWidget::rebuildUI() {
53 qDeleteAll(findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly));
64 auto* vbox =
new QVBoxLayout(
this);
65 vbox->setContentsMargins(0, 0, 0, 0);
68 auto* edit =
new QLineEdit(
this);
69 edit->setObjectName(
"value");
71 edit->setPlaceholderText(tr(
"value"));
73 edit->setPlaceholderText(tr(
"comma separated values"));
75 vbox->addWidget(edit);
76 m_lineEdits[
"value"] = edit;
78 connect(edit, &QLineEdit::textChanged,
this,
84 auto* hbox =
new QHBoxLayout(
this);
85 hbox->setContentsMargins(0, 0, 0, 0);
88 auto* editMin =
new QLineEdit(
this);
89 editMin->setObjectName(
"value_min");
90 editMin->setPlaceholderText(tr(
"minimum"));
91 m_lineEdits[
"value_min"] = editMin;
93 auto* label =
new QLabel(tr(
"and"),
this);
94 label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
96 auto* editMax =
new QLineEdit(
this);
97 editMax->setObjectName(
"value_max");
98 editMax->setPlaceholderText(tr(
"maximum"));
99 m_lineEdits[
"value_max"] = editMax;
101 hbox->addWidget(editMin, 1);
102 hbox->addWidget(label, 0);
103 hbox->addWidget(editMax, 1);
105 connect(editMin, &QLineEdit::textChanged,
this,
107 connect(editMax, &QLineEdit::textChanged,
this,
114 auto* grid =
new QGridLayout(
this);
115 grid->setContentsMargins(0, 0, 0, 0);
116 grid->setVerticalSpacing(3);
117 grid->setHorizontalSpacing(3);
119 auto* editX =
new QLineEdit(
this);
120 editX->setObjectName(
"value_x");
121 editX->setPlaceholderText(tr(
"X"));
122 m_lineEdits[
"value_x"] = editX;
124 auto* editY =
new QLineEdit(
this);
125 editY->setObjectName(
"value_y");
126 editY->setPlaceholderText(tr(
"Y"));
127 m_lineEdits[
"value_y"] = editY;
129 auto* editZ =
new QLineEdit(
this);
130 editZ->setObjectName(
"value_z");
131 editZ->setPlaceholderText(tr(
"Z"));
132 m_lineEdits[
"value_z"] = editZ;
134 grid->addWidget(editX, 0, 0);
135 grid->addWidget(editY, 0, 1);
136 grid->addWidget(editZ, 0, 2);
138 connect(editX, &QLineEdit::textChanged,
this,
140 connect(editY, &QLineEdit::textChanged,
this,
142 connect(editZ, &QLineEdit::textChanged,
this,
146 auto* editTolerance =
new QLineEdit(
this);
147 editTolerance->setObjectName(
"value_tolerance");
148 editTolerance->setPlaceholderText(tr(
"within epsilon"));
149 m_lineEdits[
"value_tolerance"] = editTolerance;
150 grid->addWidget(editTolerance, 1, 0, 1, 3);
152 connect(editTolerance, &QLineEdit::textChanged,
this,
166 m_termCombo(new QComboBox(this)),
167 m_operatorCombo(new QComboBox(this)),
169 m_termCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
170 m_operatorCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
172 auto* hbox =
new QHBoxLayout(
this);
173 hbox->setContentsMargins(0, 0, 0, 0);
175 hbox->addWidget(m_termCombo, 0, Qt::AlignTop);
176 hbox->addWidget(m_operatorCombo, 0, Qt::AlignTop);
177 hbox->addWidget(m_valueWidget, 1, Qt::AlignTop);
179 connect(m_termCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
180 this, &cvQueryConditionWidget::onTermChanged);
181 connect(m_operatorCombo,
182 QOverload<int>::of(&QComboBox::currentIndexChanged),
this,
183 &cvQueryConditionWidget::onOperatorChanged);
189 const QStringList& arrayNames,
190 const QMap<QString, int>& arrayComponents,
192 const QSignalBlocker blocker(m_termCombo);
193 m_termCombo->clear();
196 addTerm(tr(
"ID"),
ARRAY,
"id");
199 for (
const QString& arrayName : arrayNames) {
200 int numComponents = arrayComponents.value(arrayName, 1);
202 if (numComponents == 1) {
204 addTerm(arrayName,
ARRAY, arrayName);
205 }
else if (numComponents > 1) {
208 addTerm(QString(
"%1 (magnitude)").arg(arrayName),
ARRAY,
209 QString(
"mag(%1)").arg(arrayName));
212 QStringList compNames;
213 if (numComponents == 3) {
214 compNames <<
"X" <<
"Y" <<
"Z";
215 }
else if (numComponents == 4) {
216 compNames <<
"X" <<
"Y" <<
"Z" <<
"W";
217 }
else if (numComponents == 2) {
218 compNames <<
"X" <<
"Y";
220 for (
int i = 0; i < numComponents; ++i) {
221 compNames << QString::number(i);
225 for (
int i = 0; i < numComponents && i < compNames.size(); ++i) {
226 addTerm(QString(
"%1 (%2)").arg(arrayName, compNames[i]),
ARRAY,
227 QString(
"%1[:,%2]").arg(arrayName).arg(i));
240 if (m_termCombo->count() > 0) {
241 m_termCombo->setCurrentIndex(0);
247 QString expr = m_operatorCombo->currentData(ExprTemplateRole).toString();
248 if (expr.isEmpty()) {
252 QMap<QString, QString> values = m_valueWidget->
values();
253 values[
"term"] = currentTerm();
261 populateOperators(
static_cast<TermType>(termType));
263 for (
int i = 0; i < m_operatorCombo->count(); ++i) {
264 QRegularExpression regex =
265 m_operatorCombo->itemData(i, ExprRegExRole)
266 .toRegularExpression();
267 QRegularExpressionMatch match = regex.match(expr);
269 if (match.hasMatch()) {
271 setCurrentTerm(match.captured(
"term"));
272 m_operatorCombo->setCurrentIndex(i);
275 QMap<QString, QString> values;
276 for (
const QString& key :
277 {
"value",
"value_min",
"value_max",
"value_x",
"value_y",
278 "value_z",
"value_tolerance"}) {
279 QString capturedValue = match.captured(key);
280 if (!capturedValue.isNull() && !capturedValue.isEmpty()) {
281 values[key] = capturedValue;
291 if (m_termCombo->count() > 0) {
292 m_termCombo->setCurrentIndex(0);
294 if (m_operatorCombo->count() > 0) {
295 m_operatorCombo->setCurrentIndex(0);
297 m_valueWidget->
clear();
301 if (m_termCombo->count() > 0) {
302 m_termCombo->setCurrentIndex(0);
304 if (m_operatorCombo->count() > 0) {
305 m_operatorCombo->setCurrentIndex(0);
307 m_valueWidget->
clear();
310 void cvQueryConditionWidget::onTermChanged(
int index) {
312 populateOperators(currentTermType());
317 void cvQueryConditionWidget::onOperatorChanged(
int index) {
323 void cvQueryConditionWidget::populateOperators(TermType termType) {
324 const QSignalBlocker blocker(m_operatorCombo);
325 m_operatorCombo->clear();
331 addOperator(
"is", VT::SINGLE_VALUE,
"{term} == {value}");
332 addOperator(
"is in range", VT::RANGE_PAIR,
333 "({term} > {value_min}) & ({term} < {value_max})");
334 addOperator(
"is one of", VT::COMMA_SEPARATED_VALUES,
335 "isin({term}, [{value}])");
336 addOperator(
"is >=", VT::SINGLE_VALUE,
"{term} >= {value}");
337 addOperator(
"is <=", VT::SINGLE_VALUE,
"{term} <= {value}");
338 addOperator(
"is min", VT::NO_VALUE,
"{term} == min({term})");
339 addOperator(
"is max", VT::NO_VALUE,
"{term} == max({term})");
340 addOperator(
"is min per block", VT::NO_VALUE,
341 "{term} == min_per_block({term})");
342 addOperator(
"is max per block", VT::NO_VALUE,
343 "{term} == max_per_block({term})");
344 addOperator(
"is NaN", VT::NO_VALUE,
"isnan({term})");
345 addOperator(
"is <= mean", VT::NO_VALUE,
"{term} <= mean({term})");
346 addOperator(
"is >= mean", VT::NO_VALUE,
"{term} >= mean({term})");
347 addOperator(
"is mean", VT::SINGLE_VALUE,
348 "abs({term} - mean({term})) <= {value}");
352 addOperator(
"nearest to", VT::LOCATION_WITH_TOLERANCE,
353 "pointIsNear([({value_x}, {value_y}, {value_z}),], "
354 "{value_tolerance}, {term})");
358 addOperator(
"containing", VT::LOCATION,
359 "cellContainsPoint({term}, [({value_x}, {value_y}, "
365 void cvQueryConditionWidget::updateValueWidget() {
367 m_operatorCombo->currentData(ValueTypeRole).toInt());
368 m_valueWidget->
setType(valueType);
371 QString cvQueryConditionWidget::currentTerm()
const {
372 return m_termCombo->currentData(NameRole).toString();
378 m_termCombo->currentData(TermTypeRole).toInt());
381 void cvQueryConditionWidget::setCurrentTerm(
const QString& term) {
382 for (
int i = 0; i < m_termCombo->count(); ++i) {
383 if (m_termCombo->itemData(i, NameRole).toString() == term) {
384 m_termCombo->setCurrentIndex(i);
390 m_termCombo->insertItem(0, term +
"(?)");
391 m_termCombo->setItemData(0,
ARRAY, TermTypeRole);
392 m_termCombo->setItemData(0, term, NameRole);
393 m_termCombo->setCurrentIndex(0);
396 void cvQueryConditionWidget::addOperator(
399 const QString& expressionTemplate) {
400 int index = m_operatorCombo->count();
401 m_operatorCombo->addItem(text);
402 m_operatorCombo->setItemData(index, valueType, ValueTypeRole);
403 m_operatorCombo->setItemData(index, expressionTemplate, ExprTemplateRole);
406 QRegularExpression regex =
408 m_operatorCombo->setItemData(index, regex, ExprRegExRole);
411 void cvQueryConditionWidget::addTerm(
const QString& text,
413 const QString& internalName) {
414 int index = m_termCombo->count();
415 m_termCombo->addItem(text);
416 m_termCombo->setItemData(index,
type, TermTypeRole);
417 m_termCombo->setItemData(index, internalName, NameRole);
427 const QMap<QString, QString>& values) {
428 QString
result = templateStr;
431 for (
auto it = values.constBegin(); it != values.constEnd(); ++it) {
432 if (it.key() !=
"term" && it.value().isEmpty()) {
438 for (
auto it = values.constBegin(); it != values.constEnd(); ++it) {
439 QString placeholder = QString(
"{%1}").arg(it.key());
440 result.replace(placeholder, it.value());
447 QString pattern = templateStr;
450 pattern.replace(
"(",
"\\(").replace(
")",
"\\)");
451 pattern.replace(
"[",
"\\[").replace(
"]",
"\\]");
452 pattern.replace(
"+",
"\\+").replace(
"*",
"\\*");
453 pattern.replace(
".",
"\\.");
456 QMap<QString, QString> capturePatterns;
459 capturePatterns[
"term"] = R
"==(\w+|\w+\(\w+\)|\w+\[:,\d+\])==";
461 capturePatterns[
"value"] = R
"([a-zA-Z0-9\._,\s\-]+)";
463 capturePatterns[
"value_min"] = R
"([a-zA-Z0-9_.\-]+)";
464 capturePatterns["value_max"] = R
"([a-zA-Z0-9_.\-]+)";
465 capturePatterns["value_x"] = R
"([a-zA-Z0-9_.\-]+)";
466 capturePatterns["value_y"] = R
"([a-zA-Z0-9_.\-]+)";
467 capturePatterns["value_z"] = R
"([a-zA-Z0-9_.\-]+)";
468 capturePatterns["value_tolerance"] = R
"([a-zA-Z0-9_.\-]+)";
473 QMap<QString, bool> usedKeys;
474 for (
auto it = capturePatterns.constBegin();
475 it != capturePatterns.constEnd(); ++it) {
476 QString placeholder = QString(
"{%1}").arg(it.key());
477 int firstOccurrence = pattern.indexOf(placeholder);
479 if (firstOccurrence != -1) {
481 pattern.replace(firstOccurrence, placeholder.length(),
482 QString(
"(?<%1>%2)").arg(it.key(), it.value()));
483 usedKeys[it.key()] =
true;
486 pattern.replace(placeholder, QString(
"\\g{%1}").arg(it.key()));
490 QRegularExpression regex(
"^" + pattern +
"$");
491 if (!regex.isValid()) {
492 CVLog::Warning(QString(
"[QueryExpressionUtils] Invalid regex: %1")
493 .arg(regex.errorString()));
504 for (
int pos = 0; pos < expression.size(); ++pos) {
505 if (expression[pos] ==
'(') {
507 }
else if (expression[pos] ==
')') {
509 }
else if (parentCount == 0 && expression[pos] ==
'&') {
510 QString term = expression.mid(start, pos - start).trimmed();
511 if (!term.isEmpty()) {
513 if (term.startsWith(
'(') && term.endsWith(
')')) {
514 term = term.mid(1, term.length() - 2).trimmed();
523 if (start < expression.size()) {
524 QString term = expression.mid(start).trimmed();
525 if (!term.isEmpty()) {
526 if (term.startsWith(
'(') && term.endsWith(
')')) {
527 term = term.mid(1, term.length() - 2).trimmed();
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Helper functions for expression formatting and parsing.
QStringList splitByAnd(const QString &expression)
QString formatExpression(const QString &templateStr, const QMap< QString, QString > &values)
QRegularExpression createRegex(const QString &templateStr)