mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 16:18:42 +00:00
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.
This commit is contained in:
@@ -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:"<<DataFiltererrors;
|
||||
emit parseBad(DataFiltererrors);
|
||||
clearFilter();
|
||||
|
||||
} else { // yep! .. we have a winner!
|
||||
|
||||
// successfuly parsed, lets check semantics
|
||||
//treeRoot->print(treeRoot);
|
||||
emit parseGood();
|
||||
|
||||
// get all fields...
|
||||
QList<SummaryMetrics> allRides = main->metricDB->getAllMetricsFor(QDateTime(), QDateTime());
|
||||
|
||||
filenames.clear();
|
||||
|
||||
for (int i=0; i<allRides.count(); i++) {
|
||||
|
||||
// evaluate each ride...
|
||||
bool result = treeRoot->eval(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;
|
||||
|
||||
@@ -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<QString,QString> lookupMap;
|
||||
QMap<QString,bool> 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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -332,6 +332,8 @@ class MainWindow : public QMainWindow
|
||||
#endif
|
||||
|
||||
#ifdef GC_HAVE_LUCENE
|
||||
void filterSubmitted(QString);
|
||||
void filterCleared();
|
||||
void searchSubmitted(QString);
|
||||
void searchCleared();
|
||||
#endif
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<QString, double> &values() { return value; }
|
||||
QMap<QString, QString> &texts() { return text; }
|
||||
|
||||
private:
|
||||
QString fileName;
|
||||
QString id;
|
||||
QDateTime rideDate;
|
||||
QMap<QString, double> value;
|
||||
QMap<QString, QString> texts;
|
||||
QMap<QString, QString> text;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user