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:
Mark Liversedge
2012-10-29 15:33:08 +00:00
parent 91794f0eba
commit 66668018b0
8 changed files with 239 additions and 23 deletions

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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

View File

@@ -332,6 +332,8 @@ class MainWindow : public QMainWindow
#endif
#ifdef GC_HAVE_LUCENE
void filterSubmitted(QString);
void filterCleared();
void searchSubmitted(QString);
void searchCleared();
#endif

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;
};