Formula Editor Improvements 1 of 2

.. update the editor to do some basic syntax
   highlighting; literals in red, comments in
   blue and so on.

.. next commit will focus on highlighting errors
   with a wavy line and some form of error list.
This commit is contained in:
Mark Liversedge
2015-08-15 23:47:04 +01:00
parent 3431f87adc
commit 22bc70cd1a
6 changed files with 246 additions and 33 deletions

View File

@@ -155,6 +155,144 @@ Leaf::isDynamic(Leaf *leaf)
return false;
}
void
DataFilter::colorSyntax(QTextDocument *document)
{
// for looking for comments
QString string = document->toPlainText();
// clear formatting and color comments
QTextCharFormat normal;
normal.setUnderlineStyle(QTextCharFormat::NoUnderline);
normal.setForeground(Qt::black);
QTextCharFormat comment;
comment.setUnderlineStyle(QTextCharFormat::NoUnderline);
comment.setForeground(Qt::blue);
QTextCursor cursor(document);
// set all black
cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
cursor.selectionStart();
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.selectionEnd();
cursor.setCharFormat(normal);
// color comments
bool instring=false;
bool incomment=false;
int commentstart=0;
for(int i=0; i<string.length(); i++) {
// watch for being in a string
if (string[i] == '"' && !incomment) instring=!instring;
// watch for being in a comment
if (string[i] == '#' && !instring && !incomment) {
incomment = true;
commentstart=i;
}
if ((string[i] == '\r' || string[i] == '\n')) {
// mark if we have a comment
if (incomment) {
// we have a line of comments to here from commentstart
incomment = false;
// comments are blue
cursor.setPosition(commentstart, QTextCursor::MoveAnchor);
cursor.selectionStart();
cursor.setPosition(i, QTextCursor::KeepAnchor);
cursor.selectionEnd();
cursor.setCharFormat(comment);
} else {
instring = false; // always quit on end of line
}
}
}
// apply selective coloring to the symbols and expressions
if(treeRoot) treeRoot->color(treeRoot, document);
}
void Leaf::color(Leaf *leaf, QTextDocument *document)
{
// nope
if (leaf == NULL) return;
QTextCharFormat apply;
apply.setForeground(Qt::red); // default to yucky
switch(leaf->type) {
case Leaf::Float :
case Leaf::Integer :
case Leaf::String :
apply.setForeground(Qt::red);
break;
case Leaf::Symbol :
apply.setForeground(Qt::green);
break;
case Leaf::Logical :
leaf->color(leaf->lvalue.l, document);
if (leaf->op) leaf->color(leaf->rvalue.l, document);
return;
break;
case Leaf::Operation :
leaf->color(leaf->lvalue.l, document);
leaf->color(leaf->rvalue.l, document);
return;
break;
case Leaf::BinaryOperation :
leaf->color(leaf->lvalue.l, document);
leaf->color(leaf->rvalue.l, document);
return;
break;
case Leaf::Function :
if (leaf->series) {
if (leaf->lvalue.l) leaf->color(leaf->lvalue.l, document);
return;
} else {
foreach(Leaf*l, leaf->fparms) leaf->color(l, document);
apply.setForeground(Qt::green);
}
break;
case Leaf::Conditional :
{
leaf->color(leaf->cond.l, document);
leaf->color(leaf->lvalue.l, document);
leaf->color(leaf->rvalue.l, document);
return;
}
break;
default:
return;
break;
}
// lets apply that then
QTextCursor cursor(document);
// highlight this token and apply
cursor.setPosition(leaf->loc, QTextCursor::MoveAnchor);
cursor.selectionStart();
cursor.setPosition(leaf->leng+1, QTextCursor::KeepAnchor);
cursor.selectionEnd();
cursor.setCharFormat(apply);
}
void Leaf::print(Leaf *leaf, int level)
{
qDebug()<<"LEVEL"<<level;
@@ -340,7 +478,7 @@ void Leaf::validateFilter(DataFilter *df, Leaf *leaf)
DataFiltererrors << QString(QObject::tr("invalid data series for tiz(): %1")).arg(symbol);
if (leaf->function == "config" && !configValidSymbols.exactMatch(symbol))
DataFiltererrors << QString(QObject::tr("invalid data series for config(): %1")).arg(symbol);
DataFiltererrors << QString(QObject::tr("invalid literal for config(): %1")).arg(symbol);
if (leaf->function == "const") {
if (!constValidSymbols.exactMatch(symbol))
@@ -473,6 +611,35 @@ Result DataFilter::evaluate(RideItem *item)
return res;
}
QStringList DataFilter::check(QString query)
{
// remember where we apply
isdynamic=false;
// Parse from string
DataFiltererrors.clear(); // clear out old errors
DataFilter_setString(query);
DataFilterparse();
DataFilter_clearString();
// save away the results
treeRoot = root;
// if it passed syntax lets check semantics
if (treeRoot && DataFiltererrors.count() == 0) treeRoot->validateFilter(this, treeRoot);
// ok, did it pass all tests?
if (!treeRoot || DataFiltererrors.count() > 0) { // nope
// no errors just failed to finish
if (!treeRoot) DataFiltererrors << tr("malformed expression.");
}
errors = DataFiltererrors;
return errors;
}
QStringList DataFilter::parseFilter(QString query, QStringList *list)
{
// remember where we apply

View File

@@ -24,6 +24,7 @@
#include <QDebug>
#include <QList>
#include <QStringList>
#include <QTextDocument>
#include "RideCache.h"
#include "RideFile.h" //for SeriesType
@@ -50,13 +51,14 @@ class Leaf {
public:
Leaf() : type(none),op(0),series(NULL),dynamic(false) { }
Leaf(int loc, int leng) : type(none),op(0),series(NULL),dynamic(false),loc(loc),leng(leng) { }
// evaluate against a RideItem
Result eval(Context *context, DataFilter *df, Leaf *, RideItem *m);
// tree traversal etc
void print(Leaf *, int level); // print leaf and all children
void color(Leaf *, QTextDocument *); // update the document to match
bool isDynamic(Leaf *);
void validateFilter(DataFilter *, Leaf*); // validate
bool isNumber(DataFilter *df, Leaf *leaf);
@@ -78,6 +80,7 @@ class Leaf {
Leaf *series; // is a symbol
bool dynamic;
RideFile::SeriesType seriesType; // for ridefilecache
int loc, leng;
};
class DataFilter : public QObject
@@ -100,11 +103,14 @@ class DataFilter : public QObject
// when used for formulas
Result evaluate(RideItem *rideItem);
QStringList getErrors() { return errors; };
void colorSyntax(QTextDocument *content);
static QStringList functions(); // return list of functions supported
public slots:
QStringList parseFilter(QString query, QStringList *list=0);
QStringList check(QString query);
void clearFilter();
void configChanged(qint32);
void dynamicParse();

View File

@@ -25,11 +25,18 @@
// tokens
#include "DataFilter_yacc.h"/* generated by the scanner */
int DataFiltercolumn = 1;
#define YY_USER_ACTION DataFilterlloc.first_line = DataFilterlloc.last_line = DataFilterlineno; \
DataFilterlloc.first_column = DataFiltercolumn; DataFilterlloc.last_column = DataFiltercolumn + DataFilterleng - 1; \
DataFiltercolumn += DataFilterleng;
%}
%option noyywrap
%option nounput
%option noinput
%option 8bit
%option yylineno
%%
"=" DataFilterlval.op = EQ; return EQ;
@@ -104,6 +111,7 @@ int DataFilterlex_destroy(void) { return 0; }
void DataFilter_setString(QString p)
{
BEGIN(0);
DataFiltercolumn = 0;
DataFilter_scan_string(p.toLatin1().data());
}

View File

@@ -71,6 +71,8 @@ extern Leaf *root; // root node for parsed statement
char function[32];
}
%locations
%type <leaf> symbol value lexpr expr parms;
%type <op> lop cop bop;
@@ -84,7 +86,7 @@ extern Leaf *root; // root node for parsed statement
filter: lexpr { root = $1; }
;
parms: lexpr { $$ = new Leaf();
parms: lexpr { $$ = new Leaf(@1.first_column, @1.last_column);
$$->type = Leaf::Parameters;
$$->fparms << $1;
}
@@ -92,42 +94,42 @@ parms: lexpr { $$ = new Leaf();
| parms ',' lexpr { $1->fparms << $3; }
;
lexpr : expr lop expr { $$ = new Leaf();
lexpr : expr lop expr { $$ = new Leaf(@1.first_column, @3.last_column);
$$->type = Leaf::Logical;
$$->lvalue.l = $1;
$$->op = $2;
$$->rvalue.l = $3; }
| lexpr lop lexpr { $$ = new Leaf();
| lexpr lop lexpr { $$ = new Leaf(@1.first_column, @3.last_column);
$$->type = Leaf::Logical;
$$->lvalue.l = $1;
$$->op = $2;
$$->rvalue.l = $3; }
| '(' expr ')' { $$ = new Leaf();
| '(' expr ')' { $$ = new Leaf(@2.first_column, @2.last_column);
$$->type = Leaf::Logical;
$$->lvalue.l = $2;
$$->op = 0; }
| expr
| expr { $$ = $1; }
;
expr : '(' expr ')' { $$ = new Leaf();
expr : '(' expr ')' { $$ = new Leaf(@2.first_column, @2.last_column);
$$->type = Leaf::Logical;
$$->lvalue.l = $2;
$$->op = 0; }
| expr cop expr { $$ = new Leaf();
| expr cop expr { $$ = new Leaf(@1.first_column, @3.last_column);
$$->type = Leaf::Operation;
$$->lvalue.l = $1;
$$->op = $2;
$$->rvalue.l = $3; }
| expr bop expr { $$ = new Leaf();
| expr bop expr { $$ = new Leaf(@1.first_column, @3.last_column);
$$->type = Leaf::BinaryOperation;
$$->lvalue.l = $1;
$$->op = $2;
$$->rvalue.l = $3; }
| expr Q expr COL expr { $$ = new Leaf();
| expr Q expr COL expr { $$ = new Leaf(@1.first_column, @5.last_column);
$$->type = Leaf::Conditional;
$$->op = 0; // unused
$$->lvalue.l = $3;
@@ -179,63 +181,64 @@ bop : ADD
| POW
;
symbol : SYMBOL { $$ = new Leaf(); $$->type = Leaf::Symbol;
if (QString(DataFiltertext) == "BikeScore")
$$->lvalue.n = new QString("BikeScore&#8482;");
else
$$->lvalue.n = new QString(DataFiltertext);
}
symbol : SYMBOL { $$ = new Leaf(@1.first_column, @1.last_column);
$$->type = Leaf::Symbol;
if (QString(DataFiltertext) == "BikeScore")
$$->lvalue.n = new QString("BikeScore&#8482;");
else
$$->lvalue.n = new QString(DataFiltertext);
}
;
value : symbol { $$ = $1; }
| DF_STRING { $$ = new Leaf(); $$->type = Leaf::String;
| DF_STRING { $$ = new Leaf(@1.first_column, @1.last_column); $$->type = Leaf::String;
QString s2(DataFiltertext);
$$->lvalue.s = new QString(s2.mid(1,s2.length()-2)); }
| DF_FLOAT { $$ = new Leaf(); $$->type = Leaf::Float;
| DF_FLOAT { $$ = new Leaf(@1.first_column, @1.last_column); $$->type = Leaf::Float;
$$->lvalue.f = QString(DataFiltertext).toFloat(); }
| DF_INTEGER { $$ = new Leaf(); $$->type = Leaf::Integer;
| DF_INTEGER { $$ = new Leaf(@1.first_column, @1.last_column); $$->type = Leaf::Integer;
$$->lvalue.i = QString(DataFiltertext).toInt(); }
| BEST '(' symbol ',' lexpr ')' { $$ = new Leaf(); $$->type = Leaf::Function;
| BEST '(' symbol ',' lexpr ')' { $$ = new Leaf(@1.first_column, @6.last_column); $$->type = Leaf::Function;
$$->function = QString($1);
$$->series = $3;
$$->lvalue.l = $5;
}
| TIZ '(' symbol ',' lexpr ')' { $$ = new Leaf(); $$->type = Leaf::Function;
| TIZ '(' symbol ',' lexpr ')' { $$ = new Leaf(@1.first_column, @6.last_column); $$->type = Leaf::Function;
$$->function = QString($1);
$$->series = $3;
$$->lvalue.l = $5;
}
| LTS '(' symbol ')' { $$ = new Leaf(); $$->type = Leaf::Function;
| LTS '(' symbol ')' { $$ = new Leaf(@1.first_column, @4.last_column); $$->type = Leaf::Function;
$$->function = QString($1);
$$->series = $3;
$$->lvalue.l = NULL;
}
| STS '(' symbol ')' { $$ = new Leaf(); $$->type = Leaf::Function;
| STS '(' symbol ')' { $$ = new Leaf(@1.first_column, @4.last_column); $$->type = Leaf::Function;
$$->function = QString($1);
$$->series = $3;
$$->lvalue.l = NULL;
}
| RR '(' symbol ')' { $$ = new Leaf(); $$->type = Leaf::Function;
| RR '(' symbol ')' { $$ = new Leaf(@1.first_column, @4.last_column); $$->type = Leaf::Function;
$$->function = QString($1);
$$->series = $3;
$$->lvalue.l = NULL;
}
| SB '(' symbol ')' { $$ = new Leaf(); $$->type = Leaf::Function;
| SB '(' symbol ')' { $$ = new Leaf(@1.first_column, @4.last_column); $$->type = Leaf::Function;
$$->function = QString($1);
$$->series = $3;
$$->lvalue.l = NULL;
}
| CONFIG '(' symbol ')' { $$ = new Leaf(); $$->type = Leaf::Function;
| CONFIG '(' symbol ')' { $$ = new Leaf(@1.first_column, @4.last_column); $$->type = Leaf::Function;
$$->function = QString($1);
$$->series = $3;
$$->lvalue.l = NULL;
}
| CONST_ '(' symbol ')' { $$ = new Leaf(); $$->type = Leaf::Function;
| CONST_ '(' symbol ')' { $$ = new Leaf(@1.first_column, @4.last_column); $$->type = Leaf::Function;
$$->function = QString($1);
$$->series = $3;
$$->lvalue.l = NULL;

View File

@@ -1703,7 +1703,7 @@ EditMetricDetailDialog::EditMetricDetailDialog(Context *context, LTMTool *ltmToo
//formulaWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QVBoxLayout *formulaLayout = new QVBoxLayout(formulaWidget);
formulaLayout->addStretch();
formulaEdit = new DataFilterEdit(this);
formulaEdit = new DataFilterEdit(this, context);
//formulaEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
formulaType = new QComboBox(this);
formulaType->addItem(tr("Total"), static_cast<int>(RideMetric::Total));
@@ -2370,8 +2370,8 @@ LTMTool::setFilter(QStringList files)
emit filterChanged();
}
DataFilterEdit::DataFilterEdit(QWidget *parent)
: QTextEdit(parent), c(0)
DataFilterEdit::DataFilterEdit(QWidget *parent, Context *context)
: QTextEdit(parent), context(context), c(0)
{
}
@@ -2379,6 +2379,18 @@ DataFilterEdit::~DataFilterEdit()
{
}
void
DataFilterEdit::checkErrors()
{
// parse and present errors to user
DataFilter checker(this, context);
QStringList errors = checker.check(toPlainText());
checker.colorSyntax(document());
// need to fixup for errors!
// XXX next commit
}
void DataFilterEdit::setCompleter(QCompleter *completer)
{
if (c)
@@ -2411,6 +2423,16 @@ void DataFilterEdit::insertCompletion(const QString& completion)
tc.movePosition(QTextCursor::EndOfWord);
tc.insertText(completion.right(extra));
setTextCursor(tc);
checkErrors();
}
void
DataFilterEdit::setText(const QString &text)
{
// set text..
QTextEdit::setText(text);
checkErrors();
}
QString DataFilterEdit::textUnderCursor() const
@@ -2428,6 +2450,7 @@ void DataFilterEdit::focusInEvent(QFocusEvent *e)
void DataFilterEdit::keyPressEvent(QKeyEvent *e)
{
// wait a couple of seconds before checking the changes....
if (c && c->popup()->isVisible()) {
// The following keys are forwarded by the completer to the widget
switch (e->key()) {
@@ -2447,6 +2470,9 @@ void DataFilterEdit::keyPressEvent(QKeyEvent *e)
if (!c || !isShortcut) // do not process the shortcut when we have a completer
QTextEdit::keyPressEvent(e);
// check
checkErrors();
const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
if (!c || (ctrlOrShift && e->text().isEmpty()))
return;

View File

@@ -237,7 +237,7 @@ class DataFilterEdit : public QTextEdit
Q_OBJECT
public:
DataFilterEdit(QWidget *parent = 0);
DataFilterEdit(QWidget *parent, Context *context);
~DataFilterEdit();
void setCompleter(QCompleter *c);
@@ -247,14 +247,17 @@ protected:
void keyPressEvent(QKeyEvent *e);
void focusInEvent(QFocusEvent *e);
private slots:
public slots:
void setText(const QString&);
void insertCompletion(const QString &completion);
void checkErrors();
private:
QString textUnderCursor() const;
private:
QCompleter *c;
Context *context;
};
#endif // _GC_LTMTool_h