SeExpr
ExprEditor.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 ExprEditor.cpp
18* @brief This provides an expression editor for SeExpr syntax with auto ui features
19* @author aselle
20*/
21#include <QRegExp>
22#include <QLineEdit>
23#include <QPushButton>
24#include <QSplitter>
25#include <QLabel>
26#include <QMouseEvent>
27#include <QKeyEvent>
28#include <QHBoxLayout>
29#include <QVBoxLayout>
30#include <QPaintEvent>
31#include <QPainter>
32#include <QScrollArea>
33#include <QSpacerItem>
34#include <QSizePolicy>
35#include <QTextCharFormat>
36#include <QCompleter>
37#include <QAbstractItemView>
38#include <QStandardItemModel>
39#include <QStringListModel>
40#include <QScrollBar>
41#include <QToolTip>
42#include <QListWidget>
43#include <QTreeView>
44#include <QAction>
45#include <QMenu>
46
47#include "../Expression.h"
48#include "../ExprNode.h"
49#include "../ExprFunc.h"
50#include "../ExprBuiltins.h"
51
52#include "ExprEditor.h"
53#include "ExprHighlighter.h"
54#include "ExprCompletionModel.h"
56#include "ExprCurve.h"
57#include "ExprColorCurve.h"
58#include "ExprControl.h"
59#include "ExprPopupDoc.h"
60
61ExprLineEdit::ExprLineEdit(int id, QWidget* parent) : QLineEdit(parent), _id(id), _signaling(0) {
62 connect(this, SIGNAL(textChanged(const QString&)), SLOT(textChangedCB(const QString&)));
63}
64
65void ExprLineEdit::textChangedCB(const QString& text) {
66 _signaling = 1;
67 emit textChanged(_id, text);
68 _signaling = 0;
69}
70
72 QString newText = exprTe->toPlainText();
73 controls->updateText(id, newText);
74 _updatingText = 1;
75 exprTe->selectAll();
76 exprTe->insertPlainText(newText);
77 // exprTe->setPlainText(newText);
78 _updatingText = 0;
79
80 // schedule preview update
81 previewTimer->setSingleShot(true);
82 previewTimer->start(0);
83}
84
87 delete previewTimer;
88}
89
91
93 : QWidget(parent), _updatingText(0), errorHeight(0) {
94 // timers
95 controlRebuildTimer = new QTimer();
96 previewTimer = new QTimer();
97
98 // title and minimum size
99 setWindowTitle("Expression Editor");
100 setMinimumHeight(100);
101
102 // expression controls, we need for signal connections
103 this->controls = controls;
104
105 // make layout
106 QVBoxLayout* exprAndErrors = new QVBoxLayout;
107 exprAndErrors->setMargin(0);
108 setLayout(exprAndErrors);
109
110 // create text editor widget
111 exprTe = new ExprTextEdit(this);
112 exprTe->setMinimumHeight(50);
113
114 // calibrate the font size
115 int fontsize = 12;
116 while (QFontMetrics(QFont("Liberation Sans", fontsize)).width("abcdef") < 38 && fontsize < 20) fontsize++;
117 while (QFontMetrics(QFont("Liberation Sans", fontsize)).width("abcdef") > 44 && fontsize > 3) fontsize--;
118
119 exprTe->setFont(QFont("Liberation Sans", fontsize));
120
121 exprAndErrors->addWidget(exprTe);
122
123 // create error widget
124 errorWidget = new QListWidget();
125 errorWidget->setSelectionMode(QAbstractItemView::SingleSelection);
126 errorWidget->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum));
127 connect(errorWidget, SIGNAL(itemSelectionChanged()), SLOT(selectError()));
128 clearErrors();
129 exprAndErrors->addWidget(errorWidget);
130
131 // wire up signals
132 connect(exprTe, SIGNAL(applyShortcut()), SLOT(sendApply()));
133 connect(exprTe, SIGNAL(nextError()), SLOT(nextError()));
134 connect(exprTe, SIGNAL(textChanged()), SLOT(exprChanged()));
135 connect(controls, SIGNAL(controlChanged(int)), SLOT(controlChanged(int)));
136 connect(controls, SIGNAL(insertString(const std::string&)), SLOT(insertStr(const std::string&)));
137 connect(controlRebuildTimer, SIGNAL(timeout()), SLOT(rebuildControls()));
138 connect(previewTimer, SIGNAL(timeout()), SLOT(sendPreview()));
139}
140
142 int selected = errorWidget->currentRow();
143 QListWidgetItem* item = errorWidget->item(selected);
144 int start = item->data(Qt::UserRole).toInt();
145 int end = item->data(Qt::UserRole + 1).toInt();
146 QTextCursor cursor = exprTe->textCursor();
147 cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
148 cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, start);
149 cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, end - start + 1);
150 exprTe->setTextCursor(cursor);
151}
152
153void ExprEditor::sendApply() { emit apply(); }
154
156
158 if (_updatingText) return;
159
160 // schedule control rebuild
161 controlRebuildTimer->setSingleShot(true);
162 controlRebuildTimer->start(0);
163}
164
166 bool wasShown = !exprTe->completer->popup()->isHidden();
167 bool newVariables = controls->rebuildControls(exprTe->toPlainText(), exprTe->completionModel->local_variables);
168 if (newVariables) exprTe->completer->setModel(exprTe->completionModel);
169 if (wasShown) exprTe->completer->popup()->show();
170}
171
174 highlighter->fixStyle(palette());
175 highlighter->rehighlight();
176 repaint();
177}
178
179ExprTextEdit::ExprTextEdit(QWidget* parent) : QTextEdit(parent), lastStyleForHighlighter(0), _tip(0) {
180 highlighter = new ExprHighlighter(document());
181
182 // setup auto completion
183 completer = new QCompleter();
185 completer->setModel(completionModel);
186 QTreeView* treePopup = new QTreeView;
187 completer->setPopup(treePopup);
188 treePopup->setRootIsDecorated(false);
189 treePopup->setMinimumWidth(300);
190 treePopup->setMinimumHeight(50);
191 treePopup->setItemsExpandable(true);
192
193 completer->setWidget(this);
194 completer->setCompletionMode(QCompleter::PopupCompletion);
195 completer->setCaseSensitivity(Qt::CaseInsensitive);
196 QObject::connect(completer, SIGNAL(activated(const QString&)), this, SLOT(insertCompletion(const QString&)));
197
198 _popupEnabledAction = new QAction("Pop-up Help", this);
199 _popupEnabledAction->setCheckable(true);
200 _popupEnabledAction->setChecked(true);
201}
202
203void ExprTextEdit::focusInEvent(QFocusEvent* e) {
204 if (completer) completer->setWidget(this);
205 QTextEdit::focusInEvent(e);
206}
207
208void ExprTextEdit::focusOutEvent(QFocusEvent* e) {
209 hideTip();
210 QTextEdit::focusInEvent(e);
211}
212
213void ExprTextEdit::mousePressEvent(QMouseEvent* event) {
214 hideTip();
215 QTextEdit::mousePressEvent(event);
216}
217
218void ExprTextEdit::mouseDoubleClickEvent(QMouseEvent* event) {
219 hideTip();
220 QTextEdit::mouseDoubleClickEvent(event);
221}
222
223void ExprTextEdit::paintEvent(QPaintEvent* event) {
226 highlighter->fixStyle(palette());
227 highlighter->rehighlight();
228 }
229 QTextEdit::paintEvent(event);
230}
231
232void ExprTextEdit::wheelEvent(QWheelEvent* event) {
233 if (event->modifiers() == Qt::ControlModifier) {
234 if (event->delta() > 0)
235 zoomIn();
236 else if (event->delta() < 0)
237 zoomOut();
238 }
239 return QTextEdit::wheelEvent(event);
240}
241
242void ExprTextEdit::keyPressEvent(QKeyEvent* e) {
243 // Accept expression
244 if (e->key() == Qt::Key_Return && e->modifiers() == Qt::ControlModifier) {
245 emit applyShortcut();
246 return;
247 } else if (e->key() == Qt::Key_F4) {
248 emit nextError();
249 return;
250 }
251
252 // If the completer is active pass keys it needs down
253 if (completer && completer->popup()->isVisible()) {
254 switch (e->key()) {
255 case Qt::Key_Enter:
256 case Qt::Key_Return:
257 case Qt::Key_Escape:
258 case Qt::Key_Tab:
259 case Qt::Key_Backtab:
260 e->ignore();
261 return;
262 default:
263 break;
264 }
265 }
266
267 // use the values here as long as we are not using the shortcut to bring up the editor
268 bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
269 if (!isShortcut) // dont process the shortcut when we have a completer
270 QTextEdit::keyPressEvent(e);
271
272 const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
273 if (!completer || (ctrlOrShift && e->text().isEmpty())) return;
274
275 bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
276
277 // grab the line we're on
278 QTextCursor tc = textCursor();
279 tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
280 QString line = tc.selectedText();
281
282 // matches the last prefix of a completable variable or function and extract as completionPrefix
283 static QRegExp completion("^(?:.*[^A-Za-z0-9_$])?((?:\\$[A-Za-z0-9_]*)|[A-Za-z]+[A-Za-z0-9_]*)$");
284 int index = completion.indexIn(line);
285 QString completionPrefix;
286 if (index != -1 && !line.contains('#')) {
287 completionPrefix = completion.cap(1);
288 // std::cout<<"we have completer prefix '"<<completionPrefix.toStdString()<<"'"<<std::endl;
289 }
290
291 // hide the completer if we have too few characters, we are at end of word
292 if (!isShortcut && (hasModifier || e->text().isEmpty() || completionPrefix.length() < 1 || index == -1)) {
293 completer->popup()->hide();
294 } else if (_popupEnabledAction->isChecked()) {
295
296 // copy the completion prefix in if we don't already have it in the completer
297 if (completionPrefix != completer->completionPrefix()) {
298 completer->setCompletionPrefix(completionPrefix);
299 completer->popup()->setCurrentIndex(completer->completionModel()->index(0, 0));
300 }
301
302 // display the completer
303 QRect cr = cursorRect();
304 cr.setWidth(completer->popup()->sizeHintForColumn(0) + completer->popup()->sizeHintForColumn(1) +
305 completer->popup()->verticalScrollBar()->sizeHint().width());
306 cr.translate(0, 6);
307 completer->complete(cr);
308 hideTip();
309 return;
310 }
311
312 // documentation completion
313 static QRegExp inFunction("^(?:.*[^A-Za-z0-9_$])?([A-Za-z0-9_]+)\\([^()]*$");
314 int index2 = inFunction.indexIn(line);
315 if (index2 != -1) {
316 QString functionName = inFunction.cap(1);
317 QStringList tips = completionModel->getDocString(functionName).split("\n");
318 QString tip = "<b>" + tips[0] + "</b>";
319 for (int i = 1; i < tips.size(); i++) {
320 tip += "<br>" + tips[i];
321 }
322 if (_popupEnabledAction->isChecked()) showTip(tip);
323 // QToolTip::showText(mapToGlobal(cr.bottomLeft()),tip,this,cr);
324 } else {
325 hideTip();
326 }
327}
328
329void ExprTextEdit::contextMenuEvent(QContextMenuEvent* event) {
330 QMenu* menu = createStandardContextMenu();
331
332 if (!menu->actions().empty()) {
333 QAction* f = menu->actions().first();
334 menu->insertAction(f, _popupEnabledAction);
335 menu->insertSeparator(f);
336 }
337
338 menu->exec(event->globalPos());
339 delete menu;
340}
341
342void ExprTextEdit::showTip(const QString& string) {
343 // skip empty strings
344 if (string == "") return;
345 // skip already shown stuff
346 if (_tip && !_tip->isHidden() && _tip->label->text() == string) return;
347
348 QRect cr = cursorRect();
349 cr.setX(0);
350 cr.setWidth(cr.width() * 3);
351 if (_tip) {
352 delete _tip;
353 _tip = 0;
354 }
355 _tip = new ExprPopupDoc(this, mapToGlobal(cr.bottomLeft()) + QPoint(0, 6), string);
356}
357
359 if (_tip) _tip->hide();
360}
361
362void ExprTextEdit::insertCompletion(const QString& completion) {
363 if (completer->widget() != this) return;
364 QTextCursor tc = textCursor();
365 int extra = completion.length() - completer->completionPrefix().length();
366 tc.movePosition(QTextCursor::Left);
367 tc.movePosition(QTextCursor::EndOfWord);
368 tc.insertText(completion.right(extra));
369 if (completion[0] != '$') tc.insertText("(");
370 setTextCursor(tc);
371}
372
373std::string ExprEditor::getExpr() { return exprTe->toPlainText().toStdString(); }
374
375void ExprEditor::setExpr(const std::string& expression, const bool doApply) {
376 // exprTe->clear();
377 exprTe->selectAll();
378 exprTe->insertPlainText(QString::fromStdString(expression));
379 clearErrors();
380 exprTe->moveCursor(QTextCursor::Start);
381 if (doApply) emit apply();
382}
383
384void ExprEditor::insertStr(const std::string& str) { exprTe->insertPlainText(QString::fromStdString(str)); }
385
386void ExprEditor::appendStr(const std::string& str) { exprTe->append(QString::fromStdString(str)); }
387
388void ExprEditor::addError(const int startPos, const int endPos, const std::string& error) {
389 QListWidgetItem* item = new QListWidgetItem(("Error: " + error).c_str(), errorWidget);
390 item->setData(Qt::UserRole, startPos);
391 item->setData(Qt::UserRole + 1, endPos);
392 errorWidget->setHidden(false);
393 // TODO: fix to not use count lines and compute heuristic of 25 pixels per line!
394 const char* c = error.c_str();
395 int lines = 1;
396 while (*c != '\0') {
397 if (*c == '\n') lines++;
398 c++;
399 }
400 errorHeight += 25 * lines;
401 // widget should not need to be bigger than this
402 errorWidget->setMaximumHeight(errorHeight);
403}
404
406 int newRow = errorWidget->currentRow() + 1;
407 if (newRow >= errorWidget->count()) newRow = 0;
408 errorWidget->setCurrentRow(newRow);
409}
410
412 errorWidget->clear();
413 errorWidget->setHidden(true);
414 errorHeight = 0;
415}
416
420}
421
422void ExprEditor::registerExtraFunction(const std::string& name, const std::string& docString) {
423 exprTe->completionModel->addFunction(name.c_str(), docString.c_str());
424}
425
426void ExprEditor::registerExtraVariable(const std::string& name, const std::string& docString) {
427 exprTe->completionModel->addVariable(name.c_str(), docString.c_str());
428}
429
431
433
std::vector< QString > local_variables
QString getDocString(const QString &s)
void syncExtras(const ExprCompletionModel &otherModel)
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.
void updateText(const int id, QString &text)
Request new text, given taking into account control id's new values.
void rebuildControls()
Definition: ExprEditor.cpp:165
void sendPreview()
Definition: ExprEditor.cpp:155
virtual ~ExprEditor()
Definition: ExprEditor.cpp:85
void nextError()
Definition: ExprEditor.cpp:405
void clearErrors()
Definition: ExprEditor.cpp:411
void apply()
void insertStr(const std::string &str)
Definition: ExprEditor.cpp:384
void updateStyle()
Definition: ExprEditor.cpp:434
void preview()
std::string getExpr()
Definition: ExprEditor.cpp:373
QTimer * previewTimer
Definition: ExprEditor.h:151
void sendApply()
Definition: ExprEditor.cpp:153
void appendStr(const std::string &str)
Definition: ExprEditor.cpp:386
void updateCompleter()
Definition: ExprEditor.cpp:432
QListWidget * errorWidget
Definition: ExprEditor.h:148
void registerExtraVariable(const std::string &name, const std::string &docString)
Definition: ExprEditor.cpp:426
void clearExtraCompleters()
Definition: ExprEditor.cpp:417
void exprChanged()
Definition: ExprEditor.cpp:157
ExprTextEdit * exprTe
Definition: ExprEditor.h:146
ExprControlCollection * controls
Definition: ExprEditor.h:147
bool _updatingText
Definition: ExprEditor.h:153
void selectError()
Definition: ExprEditor.cpp:141
ExprEditor(QWidget *parent, ExprControlCollection *controls)
Definition: ExprEditor.cpp:92
void replaceExtras(const ExprCompletionModel &completer)
Definition: ExprEditor.cpp:430
int errorHeight
Definition: ExprEditor.h:154
void registerExtraFunction(const std::string &name, const std::string &docString)
Definition: ExprEditor.cpp:422
QTimer * controlRebuildTimer
Definition: ExprEditor.h:150
void controlChanged(int id)
Definition: ExprEditor.cpp:71
void setExpr(const std::string &expression, const bool apply=false)
Definition: ExprEditor.cpp:375
void addError(const int startPos, const int endPos, const std::string &error)
Definition: ExprEditor.cpp:388
void fixStyle(const QPalette &palette)
void textChanged(int id, const QString &text)
void textChangedCB(const QString &text)
Definition: ExprEditor.cpp:65
ExprLineEdit(int id, QWidget *parent)
Definition: ExprEditor.cpp:61
QLabel * label
Definition: ExprPopupDoc.h:27
void applyShortcut()
void showTip(const QString &string)
Definition: ExprEditor.cpp:342
void contextMenuEvent(QContextMenuEvent *event)
Definition: ExprEditor.cpp:329
ExprTextEdit(QWidget *parent=0)
Definition: ExprEditor.cpp:179
void insertCompletion(const QString &completion)
Definition: ExprEditor.cpp:362
void updateStyle()
Definition: ExprEditor.cpp:172
void focusInEvent(QFocusEvent *e)
Definition: ExprEditor.cpp:203
void wheelEvent(QWheelEvent *e)
Definition: ExprEditor.cpp:232
ExprCompletionModel * completionModel
Definition: ExprEditor.h:66
QStyle * lastStyleForHighlighter
Definition: ExprEditor.h:60
void hideTip()
Definition: ExprEditor.cpp:358
virtual void keyPressEvent(QKeyEvent *e)
Definition: ExprEditor.cpp:242
void mousePressEvent(QMouseEvent *event)
Definition: ExprEditor.cpp:213
QAction * _popupEnabledAction
Definition: ExprEditor.h:62
QCompleter * completer
Definition: ExprEditor.h:65
ExprHighlighter * highlighter
Definition: ExprEditor.h:59
void focusOutEvent(QFocusEvent *e)
Definition: ExprEditor.cpp:208
void paintEvent(QPaintEvent *e)
Definition: ExprEditor.cpp:223
void mouseDoubleClickEvent(QMouseEvent *event)
Definition: ExprEditor.cpp:218
void nextError()
ExprPopupDoc * _tip
Definition: ExprEditor.h:61
For a multi line expression
Definition: userdoc.txt:551
"margin-left: 40px style
Definition: userdoc.txt:70
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
with numParticles numAttributes A variable block contains variable names and types but doesn t care what the values are< pre > void f(const std::string &s, MyParticleData *p, int outputDim=3)
Definition: varblocks.txt:35