From 66668018b0be9241eef779fbc2219eee6f2cf969 Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Mon, 29 Oct 2012 15:33:08 +0000 Subject: [PATCH] Data Filter (Part 2 of 3) Added evaluation of filters and integrated with the ride list, this means the user can filter the rides listed. Additionally the search box will highlight the filter in red if it doesn't parse correctly, and a tooltip describes the errors. --- src/DataFilter.cpp | 187 ++++++++++++++++++++++++++++++++++++++++--- src/DataFilter.h | 11 ++- src/DataFilter.y | 3 +- src/MainWindow.cpp | 17 +++- src/MainWindow.h | 2 + src/SearchBox.cpp | 23 ++++++ src/SearchBox.h | 4 + src/SummaryMetrics.h | 15 ++-- 8 files changed, 239 insertions(+), 23 deletions(-) diff --git a/src/DataFilter.cpp b/src/DataFilter.cpp index 28ff6bc49..490a3efa8 100644 --- a/src/DataFilter.cpp +++ b/src/DataFilter.cpp @@ -102,7 +102,7 @@ void Leaf::validateFilter(DataFilter *df, Leaf *leaf) QString lookup = df->lookupMap.value(*(leaf->lvalue.n), ""); if (lookup == "") { - DataFiltererrors << QString("Unknown: %1").arg(*(leaf->lvalue.n)); + DataFiltererrors << QString("%1 is unknown").arg(*(leaf->lvalue.n)); } } break; @@ -113,13 +113,13 @@ void Leaf::validateFilter(DataFilter *df, Leaf *leaf) bool lhsType = Leaf::isNumber(df, leaf->lvalue.l); bool rhsType = Leaf::isNumber(df, leaf->rvalue.l); if (lhsType != rhsType) { - DataFiltererrors << QString("Type mismatch"); + DataFiltererrors << QString("comparing strings with numbers"); } // what about using string operations on a lhs/rhs that // are numeric? if ((lhsType || rhsType) && leaf->op >= MATCHES && leaf->op <= CONTAINS) { - DataFiltererrors << "Mixing string operations with numbers"; + DataFiltererrors << "using a string operations with a number"; } validateFilter(df, leaf->lvalue.l); @@ -165,13 +165,28 @@ QStringList DataFilter::parseFilter(QString query) if (DataFiltererrors.count() > 0) { // nope // Bzzzt, malformed - qDebug()<<"parse filter errors:"<print(treeRoot); + emit parseGood(); + + // get all fields... + QList allRides = main->metricDB->getAllMetricsFor(QDateTime(), QDateTime()); + + filenames.clear(); + + for (int i=0; ieval(this, treeRoot, allRides.at(i)); + if (result) { + filenames << allRides.at(i).getFileName(); + } + } } errors = DataFiltererrors; @@ -204,8 +219,10 @@ void DataFilter::configUpdate() // now add the ride metadata fields -- should be the same generally foreach(FieldDefinition field, main->rideMetadata()->getFields()) { QString underscored = field.name; - lookupMap.insert(field.name.replace(" ","_"), field.name); - lookupType.insert(field.name.replace(" ","_"), (field.type > 2)); // true if is number + if (!main->specialFields.isMetric(underscored)) { + lookupMap.insert(underscored.replace(" ","_"), field.name); + lookupType.insert(underscored.replace(" ","_"), (field.type > 2)); // true if is number + } } #if 0 @@ -218,39 +235,187 @@ void DataFilter::configUpdate() #endif } -bool Leaf::eval(Leaf *leaf, SummaryMetrics m) +bool Leaf::eval(DataFilter *df, Leaf *leaf, SummaryMetrics m) { switch(leaf->type) { case Leaf::Logical : + { switch (leaf->op) { case AND : - return (eval(leaf->lvalue.l, m) && eval(leaf->rvalue.l, m)); + return (eval(df, leaf->lvalue.l, m) && eval(df, leaf->rvalue.l, m)); case OR : - return (eval(leaf->lvalue.l, m) || eval(leaf->rvalue.l, m)); + return (eval(df, leaf->lvalue.l, m) || eval(df, leaf->rvalue.l, m)); } + } + break; case Leaf::Operation : { + double lhsdouble, rhsdouble; + QString lhsstring, rhsstring; + bool lhsisNumber, rhsisNumber; + + // GET LHS VALUE + switch (leaf->lvalue.l->type) { + + case Leaf::Symbol : + { + QString rename; + // get symbol value + if ((lhsisNumber = df->lookupType.value(*(leaf->lvalue.l->lvalue.n))) == true) { + // numeric + lhsdouble = m.getForSymbol(rename=df->lookupMap.value(*(leaf->lvalue.l->lvalue.n),"")); + //qDebug()<<"symbol" << *(leaf->lvalue.l->lvalue.n) << "is" << lhsdouble << "via" << rename; + } else { + // string + lhsstring = m.getText(rename=df->lookupMap.value(*(leaf->lvalue.l->lvalue.n),""), ""); + //qDebug()<<"symbol" << *(leaf->lvalue.l->lvalue.n) << "is" << lhsstring << "via" << rename; + } + } + break; + + case Leaf::Float : + lhsisNumber = true; + lhsdouble = leaf->lvalue.l->lvalue.f; + break; + + case Leaf::Integer : + lhsisNumber = true; + lhsdouble = leaf->lvalue.l->lvalue.i; + break; + + case Leaf::String : + lhsisNumber = false; + lhsstring = *(leaf->lvalue.l->lvalue.s); + break; + + default: + break; + } + + // GET RHS VALUE + switch (leaf->rvalue.l->type) { + + case Leaf::Symbol : + { + QString rename; + // get symbol value + if ((rhsisNumber=df->lookupType.value(*(leaf->rvalue.l->lvalue.n))) == true) { + // numeric + rhsdouble = m.getForSymbol(rename=df->lookupMap.value(*(leaf->rvalue.l->lvalue.n),"")); + //qDebug()<<"symbol" << *(leaf->rvalue.l->lvalue.n) << "is" << rhsdouble << "via" << rename; + } else { + // string + rhsstring = m.getText(rename=df->lookupMap.value(*(leaf->rvalue.l->lvalue.n),""), "notfound"); + //qDebug()<<"symbol" << *(leaf->rvalue.l->lvalue.n) << "is" << rhsstring << "via" << rename; + } + } + break; + + case Leaf::Float : + rhsisNumber = true; + rhsdouble = leaf->rvalue.l->lvalue.f; + break; + + case Leaf::Integer : + rhsisNumber = true; + rhsdouble = leaf->rvalue.l->lvalue.i; + break; + + case Leaf::String : + rhsisNumber = false; + rhsstring = *(leaf->rvalue.l->lvalue.s); + break; + + default: + break; + } + + // NOW PERFORM OPERATION switch (leaf->op) { case EQ: + { + if (lhsisNumber) { + return lhsdouble == rhsdouble; + } else { + return lhsstring == rhsstring; + } + } + break; + case NEQ: + { + if (lhsisNumber) { + return lhsdouble != rhsdouble; + } else { + return lhsstring != rhsstring; + } + } + break; + case LT: + { + if (lhsisNumber) { + return lhsdouble < rhsdouble; + } else { + return lhsstring < rhsstring; + } + } + break; case LTE: + { + if (lhsisNumber) { + return lhsdouble <= rhsdouble; + } else { + return lhsstring <= rhsstring; + } + } + break; case GT: + { + if (lhsisNumber) { + return lhsdouble > rhsdouble; + } else { + return lhsstring > rhsstring; + } + } + break; case GTE: + { + if (lhsisNumber) { + return lhsdouble >= rhsdouble; + } else { + return lhsstring >= rhsstring; + } + } + break; + case MATCHES: + return QRegExp(rhsstring).exactMatch(lhsstring); + break; + case ENDSWITH: + return lhsstring.endsWith(rhsstring); + break; + case BEGINSWITH: + return lhsstring.endsWith(rhsstring); + break; + case CONTAINS: + return lhsstring.endsWith(rhsstring); + break; + default: break; } } - break; - default: + break; + + default: // we don't need to evaluate any lower - they are leaf nodes handled above break; } return false; diff --git a/src/DataFilter.h b/src/DataFilter.h index 6e15b1384..c6218ac26 100644 --- a/src/DataFilter.h +++ b/src/DataFilter.h @@ -48,7 +48,7 @@ class Leaf { Leaf() : type(none) { } // evaluate against a SummaryMetric - bool eval(Leaf *, SummaryMetrics); + bool eval(DataFilter *df, Leaf *, SummaryMetrics); // tree traversal etc void print(Leaf *); // print leaf and all children @@ -77,6 +77,9 @@ class DataFilter : public QObject public: DataFilter(QObject *parent, MainWindow *main); + QStringList &files() { return filenames; } + + // used by Leaf QMap lookupMap; QMap lookupType; // true if a number, false if a string @@ -87,10 +90,14 @@ class DataFilter : public QObject //void setData(); // set the file list from the current filter + signals: + void parseGood(); + void parseBad(QStringList erorrs); + private: MainWindow *main; Leaf *treeRoot; QStringList errors; - QStringList files; + QStringList filenames; }; diff --git a/src/DataFilter.y b/src/DataFilter.y index 0a8b480cd..ef747d73e 100644 --- a/src/DataFilter.y +++ b/src/DataFilter.y @@ -114,7 +114,8 @@ lop : AND value : SYMBOL { $$ = new Leaf(); $$->type = Leaf::Symbol; $$->lvalue.n = new QString(DataFiltertext); } | STRING { $$ = new Leaf(); $$->type = Leaf::String; - $$->lvalue.s = new QString(DataFiltertext); } + QString s2(DataFiltertext); + $$->lvalue.s = new QString(s2.mid(1,s2.length()-2)); } | FLOAT { $$ = new Leaf(); $$->type = Leaf::Float; $$->lvalue.f = QString(DataFiltertext).toFloat(); } | INTEGER { $$ = new Leaf(); $$->type = Leaf::Integer; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 1cc8dccc3..df118e0ea 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -462,9 +462,11 @@ MainWindow::MainWindow(const QDir &home) : toolbuttons->addWidget(searchBox); //toolbuttons->addStretch(); connect(searchBox, SIGNAL(submitQuery(QString)), this, SLOT(searchSubmitted(QString))); - connect(searchBox, SIGNAL(submitFilter(QString)), datafilter, SLOT(parseFilter(QString))); + connect(searchBox, SIGNAL(submitFilter(QString)), this, SLOT(filterSubmitted(QString))); connect(searchBox, SIGNAL(clearQuery()), this, SLOT(searchCleared())); - connect(searchBox, SIGNAL(clearFilter()), datafilter, SLOT(clearFilter())); + connect(searchBox, SIGNAL(clearFilter()), this, SLOT(filterCleared())); + connect(datafilter, SIGNAL(parseGood()), searchBox, SLOT(setGood())); + connect(datafilter, SIGNAL(parseBad(QStringList)), searchBox, SLOT(setBad(QStringList))); #endif @@ -2240,4 +2242,15 @@ void MainWindow::searchCleared() { emit searchClear(); } +void MainWindow::filterSubmitted(QString query) +{ + QStringList errors = datafilter->parseFilter(query); + if (errors.count() == 0) + emit searchResults(datafilter->files()); +} + +void MainWindow::filterCleared() +{ + emit searchClear(); +} #endif diff --git a/src/MainWindow.h b/src/MainWindow.h index 49a3442c4..5d90d57f8 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -332,6 +332,8 @@ class MainWindow : public QMainWindow #endif #ifdef GC_HAVE_LUCENE + void filterSubmitted(QString); + void filterCleared(); void searchSubmitted(QString); void searchCleared(); #endif diff --git a/src/SearchBox.cpp b/src/SearchBox.cpp index c35554d05..aedb1f8f0 100644 --- a/src/SearchBox.cpp +++ b/src/SearchBox.cpp @@ -129,6 +129,8 @@ void SearchBox::updateCloseButton(const QString& text) clearButton->setVisible(!text.isEmpty()); if (mode == Search) searchSubmit(); // only do search as you type in search mode + + setGood(); // if user changing then don't stay red - wait till resubmitted } void SearchBox::searchSubmit() @@ -142,8 +144,29 @@ void SearchBox::searchSubmit() void SearchBox::clearClicked() { mode == Search ? clearQuery() : clearFilter(); + setGood(); } +void SearchBox::setBad(QStringList errors) +{ + QPalette pal; + pal.setColor(QPalette::Text, Qt::red); + setPalette(pal); + + setToolTip(errors.join(" and ")); +} + +void SearchBox::setGood() +{ + QPalette pal; + pal.setColor(QPalette::Text, Qt::black); + setPalette(pal); + + setToolTip(""); +} + + + // Drag and drop columns from the chooser... void SearchBox::dragEnterEvent(QDragEnterEvent *event) diff --git a/src/SearchBox.h b/src/SearchBox.h index 864e38c28..1f91cb58f 100644 --- a/src/SearchBox.h +++ b/src/SearchBox.h @@ -51,6 +51,10 @@ private slots: void dragEnterEvent(QDragEnterEvent *event); void dropEvent(QDropEvent *event); + // highlight errors etc + void setBad(QStringList errors); + void setGood(); + signals: // text search mode void submitQuery(QString); diff --git a/src/SummaryMetrics.h b/src/SummaryMetrics.h index 7c556a023..f81e3d12f 100644 --- a/src/SummaryMetrics.h +++ b/src/SummaryMetrics.h @@ -28,27 +28,27 @@ class SummaryMetrics { public: // filename - QString getFileName() { return fileName; } + QString getFileName() const { return fileName; } void setFileName(QString fileName) { this->fileName = fileName; } // Identifier - QString getId() { return id; } + QString getId() const { return id; } void setId(QString id) { this->id = id; } // ride date - QDateTime getRideDate() { return rideDate; } + QDateTime getRideDate() const { return rideDate; } void setRideDate(QDateTime rideDate) { this->rideDate = rideDate; } // for non-rides, ie. measures use same field but overload - QDateTime getDateTime() { return rideDate; } + QDateTime getDateTime() const { return rideDate; } void setDateTime(QDateTime dateTime) { this->rideDate = dateTime; } // metric values void setForSymbol(QString symbol, double v) { value.insert(symbol, v); } double getForSymbol(QString symbol) const { return value.value(symbol, 0.0); } - void setText(QString name, QString v) { texts.insert(name, v); } - QString getText(QString name, QString fallback) { return texts.value(name, fallback); } + void setText(QString name, QString v) { text.insert(name, v); } + QString getText(QString name, QString fallback) { return text.value(name, fallback); } // convert to string, using format supplied // replaces ${...:units} or ${...} with unit string @@ -62,13 +62,14 @@ class SummaryMetrics QString getUnitsForSymbol(QString symbol, bool UseMetric) const; QMap &values() { return value; } + QMap &texts() { return text; } private: QString fileName; QString id; QDateTime rideDate; QMap value; - QMap texts; + QMap text; };