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