SeExpr
ExprShortEdit.cpp
Go to the documentation of this file.
1/*
2* Copyright Disney Enterprises, Inc. All rights reserved.
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License
6* and the following modification to it: Section 6 Trademarks.
7* deleted and replaced with:
8*
9* 6. Trademarks. This License does not grant permission to use the
10* trade names, trademarks, service marks, or product names of the
11* Licensor and its affiliates, except as required for reproducing
12* the content of the NOTICE file.
13*
14* You may obtain a copy of the License at
15* http://www.apache.org/licenses/LICENSE-2.0
16*
17* @file ExprShortEdit.cpp
18* @brief This provides an expression editor for SeExpr syntax with auto ui features
19* @author aselle
20*/
21#include <QLineEdit>
22#include <QPushButton>
23#include <QToolButton>
24#include <QHBoxLayout>
25#include <QIcon>
26#include <QCompleter>
27#include <QTreeView>
28#include <QScrollBar>
29#include <QToolTip>
30#include <QLabel>
31
32#include "ExprShortEdit.h"
33#include "ExprDialog.h"
34#include "ExprBrowser.h"
35#include "ExprHighlighter.h"
36#include "ExprCompletionModel.h"
38#include "ExprPopupDoc.h"
39#include "BasicExpression.h"
40
41/* XPM */
42static const char* sum_xpm[] = {"16 16 6 1", "# c None", ". c None", "b c #808080",
43 "d c #010000", "c c #aaaaaa", "a c #303030", "................",
44 ".#aaaaaaaaaa##..", ".abbbbbbcbbba...", ".#abbaaaaabba...", "..#aabba..aba...",
45 "....#abba..a#...", ".....#abba......", ".....#abba......", "...##abba...#...",
46 "...#abba...aa...", "..#abba...aca...", ".#abbaaaaabba...", ".abbbbbbbbbba...",
47 ".aaaaaaaaaaa#...", "................", "................"};
48
49/* XPM */
50static const char* stop_xpm[] = {"16 16 4 1", " c None", ". c #FF0000", "+ c #FF8080",
51 "@ c #FFFFFF", " ", " ", " ...... ",
52 " ...+++.. ", " ....@@@... ", " .....@@@.... ", " .....@@@.... ",
53 " .....@@@.... ", " .....@@@.... ", " ............ ", " ....@@@.... ",
54 " ...@@@... ", " ....... ", " ..... ", " ",
55 " "};
56
57ExprShortEdit::ExprShortEdit(QWidget* parent, bool expanded, bool applyOnSelect)
58 : QWidget(parent), _dialog(0), _context(""), _searchPath(""), _applyOnSelect(applyOnSelect) {
59 controlRebuildTimer = new QTimer(this);
60
61 vboxlayout = new QVBoxLayout();
62 vboxlayout->setSpacing(2);
63 vboxlayout->setContentsMargins(0, 0, 0, 0);
64 hboxlayout = new QHBoxLayout();
65 hboxlayout->setSpacing(2);
66 hboxlayout->setContentsMargins(0, 0, 0, 0);
67 edit = new ExprShortTextEdit(parent);
68
69 error = new QLabel();
70 error->setPixmap(QPixmap(stop_xpm));
71 error->setHidden(true);
72
73 expandButton = new QToolButton();
74 expandButton->setFixedSize(20, 20);
75 expandButton->setFocusPolicy(Qt::NoFocus);
76 if (expanded)
77 expandButton->setArrowType(Qt::DownArrow);
78 else
79 expandButton->setArrowType(Qt::RightArrow);
80 connect(expandButton, SIGNAL(clicked()), SLOT(expandPressed()));
81
82 QToolButton* button = new QToolButton();
83 editDetail = button;
84 button->setIcon(QIcon(QPixmap(sum_xpm)));
85 hboxlayout->addWidget(expandButton);
86 hboxlayout->addWidget(edit);
87 hboxlayout->addWidget(error);
88 hboxlayout->addWidget(editDetail);
89
90 editDetail->setFixedSize(20, 20);
91 connect(editDetail, SIGNAL(clicked()), SLOT(detailPressed()));
92 connect(edit, SIGNAL(editingFinished()), SLOT(textFinished()));
93
94 vboxlayout->addLayout(hboxlayout);
95
96 controls = 0;
97 if (expanded) expandPressed();
98
99 setLayout(vboxlayout);
100 connect(controlRebuildTimer, SIGNAL(timeout()), SLOT(rebuildControls()));
101}
102
104
105void ExprShortEdit::setSearchPath(const QString& context, const QString& path) {
106 _context = context.toStdString();
107 _searchPath = path.toStdString();
108}
109
111
113 _dialog = new ExprDialog(0);
115
120 if (idx >= 0) {
121 _dialog->showEditor(idx);
122 }
123
124 connect(_dialog, SIGNAL(expressionApplied()), SLOT(expressionApplied()));
125 connect(_dialog, SIGNAL(dialogClosed()), SLOT(dialogClosed()));
126 _dialog->show();
127 setEnabled(false);
128}
129
131
132void ExprShortEdit::dialogClosed() { setEnabled(true); }
133
135 if (controls) {
136 bool wasShown = !edit->completer->popup()->isHidden();
138 if (controls->numControls() == 0) {
139 controls->deleteLater();
140 controls = 0;
141 expandButton->setArrowType(Qt::RightArrow);
142 } else
143 vboxlayout->addWidget(controls);
144
145 if (newVariables) edit->completer->setModel(edit->completionModel);
146 if (wasShown) edit->completer->popup()->show();
147 }
148}
149
151 if (controls) {
152 // vboxlayout->removeWidget(controls);
153 controls->deleteLater();
154 controls = 0;
155 expandButton->setArrowType(Qt::RightArrow);
156 } else {
157 controls = new ExprControlCollection(0, false);
158 // vboxlayout->addWidget(controls);
159 connect(controls, SIGNAL(controlChanged(int)), SLOT(controlChanged(int)));
160 controlRebuildTimer->setSingleShot(true);
161 controlRebuildTimer->start(0);
162 expandButton->setArrowType(Qt::DownArrow);
163 }
164}
165
167
169 controlRebuildTimer->setSingleShot(true);
170 controlRebuildTimer->start(0);
171 checkErrors();
172 emit exprChanged();
173}
174
175void ExprShortEdit::setExpressionString(const std::string& expression) {
176 edit->setText(QString(expression.c_str()));
177 controlRebuildTimer->setSingleShot(true);
178 controlRebuildTimer->start(0);
179 checkErrors();
180 emit exprChanged();
181}
182
183QString ExprShortEdit::getExpression() const { return edit->toPlainText(); }
184
185std::string ExprShortEdit::getExpressionString() const { return getExpression().toStdString(); }
186
188 if (controls) {
189 QString newText = getExpression();
190 controls->updateText(id, newText);
191 edit->setText(newText);
192 checkErrors();
193 emit exprChanged();
194 }
195}
196
201
202void ExprShortEdit::registerExtraFunction(const std::string& name, const std::string& docString) {
203 edit->completionModel->addFunction(name.c_str(), docString.c_str());
204}
205
206void ExprShortEdit::registerExtraVariable(const std::string& name, const std::string& docString) {
207 edit->completionModel->addVariable(name.c_str(), docString.c_str());
208}
209
211
214 bool valid = expr.isValid();
215 std::string err;
216 if (!valid) err = expr.parseError();
217
218 hideErrors(valid, err);
219}
220
221void ExprShortEdit::hideErrors(bool hidden, const std::string& err) {
222 error->setHidden(hidden);
223 if (!hidden) {
224 error->setToolTip(QString::fromStdString(err));
225 }
226}
227
228void ExprShortEdit::setSimple(bool enabled) {
229 edit->setHidden(enabled);
230 editDetail->setHidden(enabled);
231 expandButton->setHidden(enabled);
232}
233
234void ExprShortEdit::setDetailsMenu(QMenu* menu) { editDetail->setMenu(menu); }
235
236void ExprShortEdit::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy) { edit->setVerticalScrollBarPolicy(policy); }
237
238void ExprShortEdit::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy) {
239 edit->setHorizontalScrollBarPolicy(policy);
240}
241
242void ExprShortEdit::setLineWrapMode(QTextEdit::LineWrapMode mode) { edit->setLineWrapMode(mode); }
243
244ExprShortTextEdit::ExprShortTextEdit(QWidget* parent) : QTextEdit(parent), editing(false), _tip(0) {
246 setMaximumHeight(25);
247 highlighter = new ExprHighlighter(document());
248 highlighter->fixStyle(palette());
249 highlighter->rehighlight();
250 repaint();
251
252 // setup auto completion
253 completer = new QCompleter();
255 completer->setModel(completionModel);
256 QTreeView* treePopup = new QTreeView;
257 completer->setPopup(treePopup);
258 treePopup->setRootIsDecorated(false);
259 treePopup->setMinimumWidth(300);
260 treePopup->setMinimumHeight(50);
261 treePopup->setItemsExpandable(true);
262
263 completer->setWidget(this);
264 completer->setCompletionMode(QCompleter::PopupCompletion);
265 completer->setCaseSensitivity(Qt::CaseInsensitive);
266 QObject::connect(completer, SIGNAL(activated(const QString&)), this, SLOT(insertCompletion(const QString&)));
267}
268
270 // setTextCursor(QTextCursor(document()));
271 if (completer) completer->setWidget(this);
272 QTextEdit::focusInEvent(e);
273}
274
276 // setTextCursor(QTextCursor());
277 finishEdit();
278 QTextCursor newCursor = textCursor();
279 newCursor.clearSelection();
280 setTextCursor(newCursor);
281 setColor(false);
282 hideTip();
283 QTextEdit::focusOutEvent(e);
284}
285
286void ExprShortTextEdit::mousePressEvent(QMouseEvent* event) {
287 hideTip();
288 QTextEdit::mousePressEvent(event);
289}
290
292 hideTip();
293 QTextEdit::mouseDoubleClickEvent(event);
294}
295
296void ExprShortTextEdit::paintEvent(QPaintEvent* e) {
299 highlighter->fixStyle(palette());
300 highlighter->rehighlight();
301 }
302 QTextEdit::paintEvent(e);
303}
304
306
307 // If the completer is active pass keys it needs down
308 if (completer && completer->popup()->isVisible()) {
309 switch (e->key()) {
310 case Qt::Key_Enter:
311 case Qt::Key_Return:
312 case Qt::Key_Escape:
313 case Qt::Key_Tab:
314 case Qt::Key_Backtab:
315 e->ignore();
316 return;
317 default:
318 break;
319 }
320 }
321
322 // Accept expression
323 if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
324 selectAll();
325 finishEdit();
326 return;
327 } else if (e->key() == Qt::Key_Escape) {
328 setText(savedText);
329 selectAll();
330 finishEdit();
331 return;
332 } else if (e->key() == Qt::Key_Tab) {
333 QWidget::keyPressEvent(e);
334 return;
335 } else if (!editing) {
336 editing = true;
337 setColor(true);
338 savedText = toPlainText();
339 }
340
341 // use the values here as long as we are not using the shortcut to bring up the editor
342 bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
343 if (!isShortcut) // dont process the shortcut when we have a completer
344 QTextEdit::keyPressEvent(e);
345
346 const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
347 if (!completer || (ctrlOrShift && e->text().isEmpty())) return;
348
349 bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
350
351 // grab the line we're on
352 QTextCursor tc = textCursor();
353 tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
354 QString line = tc.selectedText();
355
356 // matches the last prefix of a completable variable or function and extract as completionPrefix
357 static QRegExp completion("^(?:.*[^A-Za-z0-9_$])?((?:\\$[A-Za-z0-9_]*)|[A-Za-z]+[A-Za-z0-9_]*)$");
358 int index = completion.indexIn(line);
359 QString completionPrefix;
360 if (index != -1 && !line.contains('#')) {
361 completionPrefix = completion.cap(1);
362 // std::cout<<"we have completer prefix '"<<completionPrefix.toStdString()<<"'"<<std::endl;
363 }
364
365 // hide the completer if we have too few characters, we are at end of word
366 if (!isShortcut && (hasModifier || e->text().isEmpty() || completionPrefix.length() < 1 || index == -1)) {
367 completer->popup()->hide();
368 } else {
369
370 // copy the completion prefix in if we don't already have it in the completer
371 if (completionPrefix != completer->completionPrefix()) {
372 completer->setCompletionPrefix(completionPrefix);
373 completer->popup()->setCurrentIndex(completer->completionModel()->index(0, 0));
374 }
375
376 // display the completer
377 QRect cr = cursorRect();
378 cr.setWidth(2 * (completer->popup()->sizeHintForColumn(0) + completer->popup()->sizeHintForColumn(1) +
379 completer->popup()->verticalScrollBar()->sizeHint().width()));
380 completer->complete(cr);
381 hideTip();
382 return;
383 }
384
385 // documentation completion
386 static QRegExp inFunction("^(?:.*[^A-Za-z0-9_$])?([A-Za-z0-9_]+)\\([^()]*$");
387 int index2 = inFunction.indexIn(line);
388 if (index2 != -1) {
389 QString functionName = inFunction.cap(1);
390 QStringList tips = completionModel->getDocString(functionName).split("\n");
391 QString tip = "<b>" + tips[0] + "</b>";
392 for (int i = 1; i < tips.size(); i++) {
393 tip += "<br>" + tips[i];
394 }
395 showTip(tip);
396 } else {
397 hideTip();
398 }
399}
400
401void ExprShortTextEdit::showTip(const QString& string) {
402 // skip empty strings
403 if (string == "") return;
404 // skip already shwon stuff
405 // if(_tip && !_tip->isHidden() && _tip->label->text() == string) return;
406
407 QRect cr = cursorRect();
408 cr.setX(0);
409 cr.setWidth(cr.width() * 3);
410 if (_tip) {
411 delete _tip;
412 _tip = 0;
413 }
414 _tip = new ExprPopupDoc(this, mapToGlobal(cr.bottomLeft()), string);
415}
416
418 if (_tip) _tip->hide();
419}
420
421void ExprShortTextEdit::insertCompletion(const QString& completion) {
422 if (completer->widget() != this) return;
423 QTextCursor tc = textCursor();
424 int extra = completion.length() - completer->completionPrefix().length();
425 tc.movePosition(QTextCursor::Left);
426 tc.movePosition(QTextCursor::EndOfWord);
427 tc.insertText(completion.right(extra));
428 if (completion[0] != '$') tc.insertText("(");
429 setTextCursor(tc);
430}
431
433 editing = false;
434 setColor(false);
435 emit editingFinished();
436}
437
438void ExprShortTextEdit::setColor(bool editing) {
439 Q_UNUSED(editing);
440 // todo: decorate when editing
441}
static const char * stop_xpm[]
static const char * sum_xpm[]
void setSearchPath(const QString &context, const QString &path)
void setApplyOnSelect(bool on)
Definition ExprBrowser.h:71
std::vector< QString > local_variables
QString getDocString(const QString &s)
void addVariable(const QString &str, const QString &comment)
void addFunction(const QString &function, const QString &docString)
bool rebuildControls(const QString &expressionText, std::vector< QString > &variables)
Rebuild the controls given the new expressionText. Return any local variables found.
int numControls()
Number of controls.
void updateText(const int id, QString &text)
Request new text, given taking into account control id's new values.
std::string getExpressionString()
Definition ExprDialog.h:70
ExprEditor * editor
Definition ExprDialog.h:48
void setExpressionString(const std::string &str)
Definition ExprDialog.h:72
void showEditor(int idx)
ExprBrowser * browser
Definition ExprDialog.h:49
void replaceExtras(const ExprCompletionModel &completer)
void fixStyle(const QPalette &palette)
void clearExtraCompleters()
virtual ~ExprShortEdit()
void setDetailsMenu(QMenu *menu)
QToolButton * editDetail
virtual void rebuildControls()
ExprDialog * _dialog
QHBoxLayout * hboxlayout
std::string _context
void setExpressionString(const std::string &expression)
std::string getExpressionString() const
virtual void dialogClosed()
void setSimple(bool enabled)
virtual void expressionApplied()
void exprChanged()
void registerExtraFunction(const std::string &name, const std::string &docString)
void registerExtraVariable(const std::string &name, const std::string &docString)
QTimer * controlRebuildTimer
std::string _searchPath
void showDetails(int idx)
ExprShortEdit(QWidget *parent, bool expanded=true, bool applyOnSelect=true)
void setLineWrapMode(QTextEdit::LineWrapMode mode)
virtual void hideErrors(bool hidden, const std::string &err)
QToolButton * expandButton
virtual void expandPressed()
virtual void handleTextEdited()
QVBoxLayout * vboxlayout
ExprControlCollection * controls
ExprShortTextEdit * edit
void setSearchPath(const QString &context, const QString &path)
virtual void detailPressed()
void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy)
virtual void textFinished()
QString getExpression() const
virtual void controlChanged(int id)
void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy)
virtual void mousePressEvent(QMouseEvent *event)
void showTip(const QString &string)
virtual void mouseDoubleClickEvent(QMouseEvent *event)
void paintEvent(QPaintEvent *e)
ExprShortTextEdit(QWidget *parent)
ExprCompletionModel * completionModel
virtual void keyPressEvent(QKeyEvent *e)
void setColor(bool editing)
virtual void focusOutEvent(QFocusEvent *e)
ExprHighlighter * highlighter
ExprPopupDoc * _tip
QCompleter * completer
void insertCompletion(const QString &completion)
virtual void focusInEvent(QFocusEvent *e)
QStyle * lastStyleForHighlighter
</pre >< h3 > Binding our variable reference</h3 > If we now tried to use the variable would still not be found by our expressions To make it bindable we need to override the resolveVar() function as follows</pre >< h3 > Variable setting</h3 > Next we need to make a way of setting the variable As the controlling code will use the expression it will repeatedly alternate between setting the independent variables that are used and calling evaluate(). What it has to do depends very much on the application. In this case we only need to set the independent variable x as</pre >< h2 > Evaluating expressions</h2 > Evaluating an expression is pretty easy But before we can do that we need to make an instance< pre > GrapherExpr expr("x+x^2")
For a multi line expression
Definition userdoc.txt:551
"margin-left: 40px style
Definition userdoc.txt:70
If a scalar is used in a vector context
Definition userdoc.txt:436
The result is computed int int< br >< div style="margin-left: 40px;"> Picks values randomly between loRange and hiRange based on supplied index(which is automatically hashed). &nbsp