KSeExpr  4.0.4.0
ExprEditor.cpp
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: 2011-2019 Disney Enterprises, Inc.
2 // SPDX-License-Identifier: LicenseRef-Apache-2.0
3 // SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 /*
6  * @file ExprEditor.cpp
7  * @brief This provides an expression editor for SeExpr syntax with auto ui features
8  * @author aselle
9  */
10 
11 #include <QVBoxLayout>
12 
13 #include <KSeExpr/ExprBuiltins.h>
14 #include <KSeExpr/ExprFunc.h>
15 #include <KSeExpr/ExprNode.h>
16 #include <KSeExpr/Expression.h>
17 
18 
19 #include "ExprColorCurve.h"
20 #include "ExprCompletionModel.h"
21 #include "ExprControl.h"
22 #include "ExprControlCollection.h"
23 #include "ExprCurve.h"
24 #include "ExprEditor.h"
25 #include "ExprHighlighter.h"
26 #include "ExprPopupDoc.h"
27 
28 
29 ExprLineEdit::ExprLineEdit(int id, QWidget *parent)
30  : QLineEdit(parent)
31  , _id(id)
32 {
33  connect(this, SIGNAL(textChanged(const QString &)), SLOT(textChangedCB(const QString &)));
34 }
35 
36 void ExprLineEdit::textChangedCB(const QString &text)
37 {
38  _signaling = true;
39  emit textChanged(_id, text);
40  _signaling = false;
41 }
42 
44 {
45  QString newText = exprTe->toPlainText();
46  controls->updateText(id, newText);
47  _updatingText = true;
48  exprTe->selectAll();
49  exprTe->insertPlainText(newText);
50  // exprTe->setPlainText(newText);
51  _updatingText = false;
52 
53  // schedule preview update
54  previewTimer->setSingleShot(true);
55  previewTimer->start(0);
56 }
57 
59 {
60  delete controlRebuildTimer;
61  delete previewTimer;
62 }
63 
64 ExprEditor::ExprEditor(QWidget *parent)
65  : QWidget(parent)
66  , errorHeight(0)
67 {
68  // timers
69  controlRebuildTimer = new QTimer();
70  previewTimer = new QTimer();
71 
72  // title and minimum size
73  setWindowTitle(tr("Expression Editor"));
74  setMinimumHeight(100);
75 
76  // make layout
77  auto *exprAndErrors = new QVBoxLayout;
78  exprAndErrors->setMargin(0);
79  setLayout(exprAndErrors);
80 
81  // create text editor widget
82  exprTe = new ExprTextEdit(this);
83  exprTe->setObjectName(QString::fromUtf8("exprTe"));
84  exprTe->setMinimumHeight(50);
85 
86  // calibrate the font size
87  // This should be done inside the target application. --amyspark
88  // int fontsize = 12
89  // QFont font("Liberation Sans", fontsize);
90  // QFont font = exprTe->font();
91  // font.setPointSize(fontsize);
92  // while (QFontMetrics(font).boundingRect("yabcdef").width() < 38 && fontsize < 20) {
93  // fontsize++;
94  // font.setPointSize(fontsize);
95  // } ;
96  // while (QFontMetrics(font).boundingRect("abcdef").width() > 44 && fontsize > 3) {
97  // fontsize--;
98  // font.setPointSize(fontsize);
99  // };
100  // exprTe->setFont(font);
101 
102  exprAndErrors->addWidget(exprTe, 4);
103 
104  // create error widget
105  errorWidget = new QListWidget();
106  errorWidget->setObjectName(QString::fromUtf8("errorWidget"));
107  errorWidget->setSelectionMode(QAbstractItemView::SingleSelection);
108  errorWidget->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding));
109  errorWidget->setMinimumHeight(30);
110  connect(errorWidget, SIGNAL(itemSelectionChanged()), SLOT(selectError()));
111  clearErrors();
112  exprAndErrors->addWidget(errorWidget, 1);
113 
114  // wire up signals
115  connect(exprTe, SIGNAL(applyShortcut()), SLOT(sendApply()));
116  connect(exprTe, SIGNAL(nextError()), SLOT(nextError()));
117  connect(exprTe, SIGNAL(textChanged()), SLOT(exprChanged()));
118  connect(controlRebuildTimer, SIGNAL(timeout()), SLOT(sendPreview()));
119  connect(previewTimer, SIGNAL(timeout()), SLOT(sendPreview()));
120 }
121 
123 {
124  return this->controls;
125 }
126 
127 // expression controls, we need for signal connections
129 {
130  if (this->controls) {
131  disconnect(controlRebuildTimer, SIGNAL(timeout())), disconnect(controls, SIGNAL(controlChanged(int)));
132  disconnect(controls, SIGNAL(insertString(const QString &)));
133  }
134 
135  this->controls = widget;
136 
137  if (this->controls) {
138  connect(controlRebuildTimer, SIGNAL(timeout()), SLOT(rebuildControls()));
139  connect(controls, SIGNAL(controlChanged(int)), SLOT(controlChanged(int)));
140  connect(controls, SIGNAL(insertString(const QString &)), SLOT(insertStr(const QString &)));
141  }
142 }
143 
145 {
146  int selected = errorWidget->currentRow();
147  QListWidgetItem *item = errorWidget->item(selected);
148  int start = item->data(Qt::UserRole).toInt();
149  int end = item->data(Qt::UserRole + 1).toInt();
150  QTextCursor cursor = exprTe->textCursor();
151  cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
152  cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, start);
153  cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, end - start + 1);
154  exprTe->setTextCursor(cursor);
155 }
156 
158 {
159  emit apply();
160 }
161 
163 {
164  emit preview();
165 }
166 
168 {
169  if (_updatingText)
170  return;
171 
172  // schedule control rebuild
173  controlRebuildTimer->setSingleShot(true);
174  controlRebuildTimer->start(0);
175 }
176 
178 {
179  bool wasShown = !exprTe->completer->popup()->isHidden();
180  bool newVariables = controls->rebuildControls(exprTe->toPlainText(), exprTe->completionModel->local_variables);
181  if (newVariables)
182  exprTe->completer->setModel(exprTe->completionModel);
183  if (wasShown)
184  exprTe->completer->popup()->show();
185 }
186 
188 {
189  return exprTe->toPlainText();
190 }
191 
192 void ExprEditor::setExpr(const QString &expression, const bool doApply)
193 {
194  // exprTe->clear();
195  exprTe->selectAll();
196  exprTe->insertPlainText(expression);
197  clearErrors();
198  exprTe->moveCursor(QTextCursor::Start);
199  if (doApply)
200  emit apply();
201 }
202 
203 void ExprEditor::insertStr(const QString &str)
204 {
205  exprTe->moveCursor(QTextCursor::StartOfLine);
206  exprTe->insertPlainText(str);
207 }
208 
209 void ExprEditor::appendStr(const QString &str)
210 {
211  exprTe->append(str);
212 }
213 
214 void ExprEditor::addError(const int startPos, const int endPos, const QString &error)
215 {
216  int startLine, startCol;
217  errorWidget->setHidden(false);
218 
219  // Underline error
220  QTextCursor cursor = exprTe->textCursor();
221  cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
222  cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, startPos);
223  startLine = cursor.blockNumber();
224  startCol = cursor.columnNumber();
225  cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, endPos - startPos + 1);
226  QList<QTextEdit::ExtraSelection> extraSelections = exprTe->extraSelections();
227  QTextEdit::ExtraSelection selection;
228  QColor lineColor = QColor(Qt::yellow).lighter(130);
229  selection.format.setUnderlineColor(lineColor);
230  selection.format.setUnderlineStyle(QTextCharFormat::UnderlineStyle::WaveUnderline);
231  selection.cursor = cursor;
232  extraSelections.append(selection);
233  exprTe->setExtraSelections(extraSelections);
234 
235  QString message = tr("(%1, %2): %3").arg(startLine).arg(startCol).arg(error);
236  auto *item = new QListWidgetItem(message, errorWidget);
237  item->setData(Qt::UserRole, startPos);
238  item->setData(Qt::UserRole + 1, endPos);
239 
240  // errorWidget has its height fixed -- amyspark
241  // TODO: fix to not use count lines and compute heuristic of 25 pixels per line!
242  // const char* c = error.c_str();
243  // int lines = 1;
244  // while (*c != '\0') {
245  // if (*c == '\n') lines++;
246  // c++;
247  // }
248  // errorHeight += 25 * lines;
249  // // widget should not need to be bigger than this
250  // errorWidget->setMaximumHeight(errorHeight);
251  // ensure cursor stays visible if it was hidden by the error widget -- amyspark
252  exprTe->ensureCursorVisible();
253 }
254 
256 {
257  int newRow = errorWidget->currentRow() + 1;
258  if (newRow >= errorWidget->count())
259  newRow = 0;
260  errorWidget->setCurrentRow(newRow);
261 }
262 
264 {
265  QList<QTextEdit::ExtraSelection> extraSelections;
266  exprTe->setExtraSelections(extraSelections);
267  errorWidget->clear();
268  errorWidget->setHidden(true);
269  errorHeight = 0;
270 }
271 
273 {
276 }
277 
278 void ExprEditor::registerExtraFunction(const QString &name, const QString &docString)
279 {
280  exprTe->completionModel->addFunction(name, docString);
281 }
282 
283 void ExprEditor::registerExtraVariable(const QString &name, const QString &docString)
284 {
285  exprTe->completionModel->addVariable(name, docString);
286 }
287 
289 {
290  exprTe->completionModel->syncExtras(completer);
291 }
292 
294 {
295  exprTe->completer->setModel(exprTe->completionModel);
296 }
297 
299 {
300  exprTe->updateStyle();
301 }
std::vector< QString > local_variables
void syncExtras(const ExprCompletionModel &otherModel)
void addVariable(const QString &str, const QString &comment)
void addFunction(const QString &, const QString &)
void updateText(int id, QString &text)
Request new text, given taking into account control id's new values.
bool rebuildControls(const QString &expressionText, std::vector< QString > &variables)
Rebuild the controls given the new expressionText. Return any local variables found.
void rebuildControls()
Definition: ExprEditor.cpp:177
ExprEditor(QWidget *parent)
Definition: ExprEditor.cpp:64
ExprControlCollection * controlCollectionWidget() const
Definition: ExprEditor.cpp:122
void registerExtraVariable(const QString &name, const QString &docString)
Definition: ExprEditor.cpp:283
void sendPreview()
Definition: ExprEditor.cpp:162
~ExprEditor() override
Definition: ExprEditor.cpp:58
void nextError()
Definition: ExprEditor.cpp:255
void clearErrors()
Definition: ExprEditor.cpp:263
void apply()
void insertStr(const QString &str)
Definition: ExprEditor.cpp:203
void updateStyle()
Definition: ExprEditor.cpp:298
void preview()
QTimer * previewTimer
Definition: ExprEditor.h:80
void sendApply()
Definition: ExprEditor.cpp:157
void updateCompleter()
Definition: ExprEditor.cpp:293
void appendStr(const QString &str)
Definition: ExprEditor.cpp:209
QListWidget * errorWidget
Definition: ExprEditor.h:77
virtual void setControlCollectionWidget(ExprControlCollection *widget)
Definition: ExprEditor.cpp:128
std::atomic< bool > _updatingText
Definition: ExprEditor.h:82
void clearExtraCompleters()
Definition: ExprEditor.cpp:272
void exprChanged()
Definition: ExprEditor.cpp:167
ExprTextEdit * exprTe
Definition: ExprEditor.h:73
ExprControlCollection * controls
Definition: ExprEditor.h:76
void addError(int startPos, int endPos, const QString &error)
Definition: ExprEditor.cpp:214
void selectError()
Definition: ExprEditor.cpp:144
void setExpr(const QString &expression, bool apply=false)
Definition: ExprEditor.cpp:192
QString getExpr()
Definition: ExprEditor.cpp:187
void replaceExtras(const ExprCompletionModel &completer)
Definition: ExprEditor.cpp:288
int errorHeight
Definition: ExprEditor.h:83
QTimer * controlRebuildTimer
Definition: ExprEditor.h:79
void controlChanged(int id)
Definition: ExprEditor.cpp:43
void registerExtraFunction(const QString &name, const QString &docString)
Definition: ExprEditor.cpp:278
std::atomic< bool > _signaling
Definition: ExprControl.h:102
void textChanged(int id, const QString &text)
void textChangedCB(const QString &text)
Definition: ExprEditor.cpp:36
ExprLineEdit(int id, QWidget *parent)
Definition: ExprEditor.cpp:29
void updateStyle()
ExprCompletionModel * completionModel
Definition: ExprTextEdit.h:35
QCompleter * completer
Definition: ExprTextEdit.h:34