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"
55 #include "ExprControlCollection.h"
56 #include "ExprCurve.h"
57 #include "ExprColorCurve.h"
58 #include "ExprControl.h"
59 #include "ExprPopupDoc.h"
60 
61 ExprLineEdit::ExprLineEdit(int id, QWidget* parent) : QLineEdit(parent), _id(id), _signaling(0) {
62  connect(this, SIGNAL(textChanged(const QString&)), SLOT(textChangedCB(const QString&)));
63 }
64 
65 void 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 
86  delete controlRebuildTimer;
87  delete previewTimer;
88 }
89 
91 
92 ExprEditor::ExprEditor(QWidget* parent, ExprControlCollection* controls)
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 
153 void 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 
179 ExprTextEdit::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 
203 void ExprTextEdit::focusInEvent(QFocusEvent* e) {
204  if (completer) completer->setWidget(this);
205  QTextEdit::focusInEvent(e);
206 }
207 
208 void ExprTextEdit::focusOutEvent(QFocusEvent* e) {
209  hideTip();
210  QTextEdit::focusInEvent(e);
211 }
212 
213 void ExprTextEdit::mousePressEvent(QMouseEvent* event) {
214  hideTip();
215  QTextEdit::mousePressEvent(event);
216 }
217 
218 void ExprTextEdit::mouseDoubleClickEvent(QMouseEvent* event) {
219  hideTip();
220  QTextEdit::mouseDoubleClickEvent(event);
221 }
222 
223 void ExprTextEdit::paintEvent(QPaintEvent* event) {
224  if (lastStyleForHighlighter != style()) {
226  highlighter->fixStyle(palette());
227  highlighter->rehighlight();
228  }
229  QTextEdit::paintEvent(event);
230 }
231 
232 void 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 
242 void 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 
329 void 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 
342 void 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 
362 void 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 
373 std::string ExprEditor::getExpr() { return exprTe->toPlainText().toStdString(); }
374 
375 void 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 
384 void ExprEditor::insertStr(const std::string& str) { exprTe->insertPlainText(QString::fromStdString(str)); }
385 
386 void ExprEditor::appendStr(const std::string& str) { exprTe->append(QString::fromStdString(str)); }
387 
388 void 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 
422 void ExprEditor::registerExtraFunction(const std::string& name, const std::string& docString) {
423  exprTe->completionModel->addFunction(name.c_str(), docString.c_str());
424 }
425 
426 void ExprEditor::registerExtraVariable(const std::string& name, const std::string& docString) {
427  exprTe->completionModel->addVariable(name.c_str(), docString.c_str());
428 }
429 
431 
433 
ExprEditor::clearErrors
void clearErrors()
Definition: ExprEditor.cpp:411
ExprCompletionModel::getDocString
QString getDocString(const QString &s)
Definition: ExprCompletionModel.cpp:125
ExprCompletionModel::clearVariables
void clearVariables()
Definition: ExprCompletionModel.cpp:36
index
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
ExprTextEdit::~ExprTextEdit
~ExprTextEdit()
Definition: ExprEditor.cpp:90
f
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
ExprCurve.h
ExprHighlighter::fixStyle
void fixStyle(const QPalette &palette)
Definition: ExprHighlighter.h:45
ExprLineEdit::ExprLineEdit
ExprLineEdit(int id, QWidget *parent)
Definition: ExprEditor.cpp:61
ExprLineEdit::textChanged
void textChanged(int id, const QString &text)
ExprEditor::selectError
void selectError()
Definition: ExprEditor.cpp:141
ExprEditor::replaceExtras
void replaceExtras(const ExprCompletionModel &completer)
Definition: ExprEditor.cpp:430
ExprTextEdit::hideTip
void hideTip()
Definition: ExprEditor.cpp:358
ExprEditor::controlRebuildTimer
QTimer * controlRebuildTimer
Definition: ExprEditor.h:150
ExprEditor::~ExprEditor
virtual ~ExprEditor()
Definition: ExprEditor.cpp:85
ExprEditor::registerExtraVariable
void registerExtraVariable(const std::string &name, const std::string &docString)
Definition: ExprEditor.cpp:426
ExprEditor::errorHeight
int errorHeight
Definition: ExprEditor.h:154
ExprEditor::appendStr
void appendStr(const std::string &str)
Definition: ExprEditor.cpp:386
ExprEditor::exprChanged
void exprChanged()
Definition: ExprEditor.cpp:157
ExprEditor::nextError
void nextError()
Definition: ExprEditor.cpp:405
ExprTextEdit::highlighter
ExprHighlighter * highlighter
Definition: ExprEditor.h:59
ExprLineEdit::_signaling
bool _signaling
Definition: ExprControl.h:122
ExprTextEdit::lastStyleForHighlighter
QStyle * lastStyleForHighlighter
Definition: ExprEditor.h:60
ExprEditor::sendApply
void sendApply()
Definition: ExprEditor.cpp:153
ExprEditor::sendPreview
void sendPreview()
Definition: ExprEditor.cpp:155
ExprControlCollection
Definition: ExprControlCollection.h:81
ExprTextEdit::_popupEnabledAction
QAction * _popupEnabledAction
Definition: ExprEditor.h:62
ExprLineEdit::textChangedCB
void textChangedCB(const QString &text)
Definition: ExprEditor.cpp:65
ExprTextEdit::contextMenuEvent
void contextMenuEvent(QContextMenuEvent *event)
Definition: ExprEditor.cpp:329
ExprEditor::apply
void apply()
ExprEditor::ExprEditor
ExprEditor(QWidget *parent, ExprControlCollection *controls)
Definition: ExprEditor.cpp:92
ExprTextEdit::updateStyle
void updateStyle()
Definition: ExprEditor.cpp:172
ExprTextEdit
Definition: ExprEditor.h:54
ExprEditor::previewTimer
QTimer * previewTimer
Definition: ExprEditor.h:151
ExprHighlighter.h
ExprEditor::exprTe
ExprTextEdit * exprTe
Definition: ExprEditor.h:146
ExprEditor::clearExtraCompleters
void clearExtraCompleters()
Definition: ExprEditor.cpp:417
ExprTextEdit::completionModel
ExprCompletionModel * completionModel
Definition: ExprEditor.h:66
ExprPopupDoc::label
QLabel * label
Definition: ExprPopupDoc.h:24
ExprEditor::preview
void preview()
ExprEditor::rebuildControls
void rebuildControls()
Definition: ExprEditor.cpp:165
ExprTextEdit::wheelEvent
void wheelEvent(QWheelEvent *e)
Definition: ExprEditor.cpp:232
ExprCompletionModel::local_variables
std::vector< QString > local_variables
Definition: ExprCompletionModel.h:83
ExprEditor::addError
void addError(const int startPos, const int endPos, const std::string &error)
Definition: ExprEditor.cpp:388
ExprEditor::insertStr
void insertStr(const std::string &str)
Definition: ExprEditor.cpp:384
ExprEditor::getExpr
std::string getExpr()
Definition: ExprEditor.cpp:373
ExprEditor::registerExtraFunction
void registerExtraFunction(const std::string &name, const std::string &docString)
Definition: ExprEditor.cpp:422
ExprControlCollection::updateText
void updateText(const int id, QString &text)
Request new text, given taking into account control id's new values.
Definition: ExprControlCollection.cpp:441
ExprTextEdit::insertCompletion
void insertCompletion(const QString &completion)
Definition: ExprEditor.cpp:362
ExprCompletionModel::addVariable
void addVariable(const QString &str, const QString &comment)
Definition: ExprCompletionModel.cpp:41
ExprPopupDoc.h
ExprPopupDoc
Definition: ExprPopupDoc.h:23
ExprColorCurve.h
ExprCompletionModel::clearFunctions
void clearFunctions()
Definition: ExprCompletionModel.cpp:46
ExprControlCollection::rebuildControls
bool rebuildControls(const QString &expressionText, std::vector< QString > &variables)
Rebuild the controls given the new expressionText. Return any local variables found.
Definition: ExprControlCollection.cpp:334
ExprTextEdit::ExprTextEdit
ExprTextEdit(QWidget *parent=0)
Definition: ExprEditor.cpp:179
ExprTextEdit::keyPressEvent
virtual void keyPressEvent(QKeyEvent *e)
Definition: ExprEditor.cpp:242
style
"margin-left: 40px style
Definition: userdoc.txt:70
ExprEditor::controls
ExprControlCollection * controls
Definition: ExprEditor.h:147
ExprEditor::setExpr
void setExpr(const std::string &expression, const bool apply=false)
Definition: ExprEditor.cpp:375
ExprTextEdit::paintEvent
void paintEvent(QPaintEvent *e)
Definition: ExprEditor.cpp:223
ExprEditor::controlChanged
void controlChanged(int id)
Definition: ExprEditor.cpp:71
ExprCompletionModel
Definition: ExprCompletionModel.h:31
ExprEditor::updateStyle
void updateStyle()
Definition: ExprEditor.cpp:434
ExprEditor::errorWidget
QListWidget * errorWidget
Definition: ExprEditor.h:148
ExprEditor::updateCompleter
void updateCompleter()
Definition: ExprEditor.cpp:432
ExprHighlighter
Definition: ExprHighlighter.h:27
ExprTextEdit::focusInEvent
void focusInEvent(QFocusEvent *e)
Definition: ExprEditor.cpp:203
ExprControl.h
ExprTextEdit::showTip
void showTip(const QString &string)
Definition: ExprEditor.cpp:342
ExprTextEdit::_tip
ExprPopupDoc * _tip
Definition: ExprEditor.h:61
ExprTextEdit::focusOutEvent
void focusOutEvent(QFocusEvent *e)
Definition: ExprEditor.cpp:208
ExprCompletionModel.h
ExprLineEdit::_id
int _id
Definition: ExprControl.h:121
ExprTextEdit::completer
QCompleter * completer
Definition: ExprEditor.h:65
ExprTextEdit::mousePressEvent
void mousePressEvent(QMouseEvent *event)
Definition: ExprEditor.cpp:213
ExprEditor.h
ExprTextEdit::nextError
void nextError()
ExprCompletionModel::addFunction
void addFunction(const QString &function, const QString &docString)
Definition: ExprCompletionModel.cpp:52
ExprControlCollection.h
ExprTextEdit::applyShortcut
void applyShortcut()
ExprEditor::_updatingText
bool _updatingText
Definition: ExprEditor.h:153
ExprCompletionModel::syncExtras
void syncExtras(const ExprCompletionModel &otherModel)
Definition: ExprCompletionModel.cpp:58
ExprTextEdit::mouseDoubleClickEvent
void mouseDoubleClickEvent(QMouseEvent *event)
Definition: ExprEditor.cpp:218
expression
For a multi line expression
Definition: userdoc.txt:551