KSeExpr 6.0.0.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
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"
23#include "ExprCurve.h"
24#include "ExprEditor.h"
25#include "ExprHighlighter.h"
26#include "ExprPopupDoc.h"
27
28
29ExprLineEdit::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
36void 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
63
64ExprEditor::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->setContentsMargins({});
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
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)
183 if (wasShown)
184 exprTe->completer->popup()->show();
185}
186
188{
189 return exprTe->toPlainText();
190}
191
192void 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
203void ExprEditor::insertStr(const QString &str)
204{
205 exprTe->moveCursor(QTextCursor::StartOfLine);
206 exprTe->insertPlainText(str);
207}
208
209void ExprEditor::appendStr(const QString &str)
210{
211 exprTe->append(str);
212}
213
214void 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
277
278void ExprEditor::registerExtraFunction(const QString &name, const QString &docString)
279{
280 exprTe->completionModel->addFunction(name, docString);
281}
282
283void 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
297
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()
ExprEditor(QWidget *parent)
ExprControlCollection * controlCollectionWidget() const
void registerExtraVariable(const QString &name, const QString &docString)
void sendPreview()
~ExprEditor() override
void nextError()
void clearErrors()
void apply()
void insertStr(const QString &str)
void updateStyle()
void preview()
QTimer * previewTimer
Definition ExprEditor.h:80
void sendApply()
void updateCompleter()
void appendStr(const QString &str)
QListWidget * errorWidget
Definition ExprEditor.h:77
virtual void setControlCollectionWidget(ExprControlCollection *widget)
std::atomic< bool > _updatingText
Definition ExprEditor.h:82
void clearExtraCompleters()
void exprChanged()
ExprTextEdit * exprTe
Definition ExprEditor.h:73
ExprControlCollection * controls
Definition ExprEditor.h:76
void addError(int startPos, int endPos, const QString &error)
void selectError()
void setExpr(const QString &expression, bool apply=false)
QString getExpr()
void replaceExtras(const ExprCompletionModel &completer)
int errorHeight
Definition ExprEditor.h:83
QTimer * controlRebuildTimer
Definition ExprEditor.h:79
void controlChanged(int id)
void registerExtraFunction(const QString &name, const QString &docString)
std::atomic< bool > _signaling
void textChanged(int id, const QString &text)
void textChangedCB(const QString &text)
ExprLineEdit(int id, QWidget *parent)
ExprCompletionModel * completionModel
QCompleter * completer