KSeExpr 6.0.0.0
ExprTextEdit.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 ExprTextEdit.cpp
7 * @brief This provides an expression editor for SeExpr syntax with auto ui features
8 * @author aselle
9 */
10
11#include <QAction>
12#include <QLabel>
13#include <QMenu>
14#include <QScrollBar>
15#include <QTreeView>
16
17#include "ExprTextEdit.h"
18
20 : QTextEdit(parent)
21{
22 highlighter = new ExprHighlighter(document());
23
24 // Block all external RTF input - amyspark
25 this->setAcceptRichText(false);
26
27 // setup auto completion
28 completer = new QCompleter();
30 completer->setModel(completionModel);
31 auto *treePopup = new QTreeView;
32 completer->setPopup(treePopup);
33 treePopup->setRootIsDecorated(false);
34 treePopup->setMinimumWidth(300);
35 treePopup->setMinimumHeight(50);
36 treePopup->setItemsExpandable(true);
37 treePopup->setWordWrap(true);
38
39 completer->setWidget(this);
40 completer->setCompletionMode(QCompleter::PopupCompletion);
41 completer->setCaseSensitivity(Qt::CaseInsensitive);
42 QObject::connect(completer, SIGNAL(activated(const QString &)), this, SLOT(insertCompletion(const QString &)));
43
44 _popupEnabledAction = new QAction(tr("Pop-up Help"), this);
45 _popupEnabledAction->setCheckable(true);
46 _popupEnabledAction->setChecked(true);
47
48 this->horizontalScrollBar()->setObjectName("exprTextEdit_horizontalBar");
49 this->verticalScrollBar()->setObjectName("exprTextEdit_verticalBar");
50}
51
53{
55 highlighter->fixStyle(palette());
56 highlighter->rehighlight();
57 repaint();
58}
59
60void ExprTextEdit::focusInEvent(QFocusEvent *e)
61{
62 if (completer)
63 completer->setWidget(this);
64 QTextEdit::focusInEvent(e);
65}
66
67void ExprTextEdit::focusOutEvent(QFocusEvent *e)
68{
69 hideTip();
70 QTextEdit::focusInEvent(e);
71}
72
73void ExprTextEdit::mousePressEvent(QMouseEvent *event)
74{
75 hideTip();
76 QTextEdit::mousePressEvent(event);
77}
78
80{
81 hideTip();
82 QTextEdit::mouseDoubleClickEvent(event);
83}
84
85void ExprTextEdit::paintEvent(QPaintEvent *event)
86{
87 if (lastStyleForHighlighter != style()) {
89 highlighter->fixStyle(palette());
90 highlighter->rehighlight();
91 }
92 QTextEdit::paintEvent(event);
93}
94
95void ExprTextEdit::wheelEvent(QWheelEvent *event)
96{
97 if (event->modifiers() == Qt::ControlModifier) {
98 if (event->angleDelta().y() > 0)
99 zoomIn();
100 else if (event->angleDelta().y() < 0)
101 zoomOut();
102 }
103 return QTextEdit::wheelEvent(event);
104}
105
107{
108 // Accept expression
109 if (e->key() == Qt::Key_Return && e->modifiers() == Qt::ControlModifier) {
110 emit applyShortcut();
111 return;
112 } else if (e->key() == Qt::Key_F4) {
113 emit nextError();
114 return;
115 } else if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::ControlModifier) {
116 removeWord();
117 return;
118 }
119
120 // If the completer is active pass keys it needs down
121 if (completer && completer->popup()->isVisible()) {
122 switch (e->key()) {
123 case Qt::Key_Enter:
124 case Qt::Key_Return:
125 case Qt::Key_Escape:
126 case Qt::Key_Tab:
127 case Qt::Key_Backtab:
128 e->ignore();
129 return;
130 default:
131 break;
132 }
133 }
134
135 // use the values here as long as we are not using the shortcut to bring up the editor
136 bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
137 if (!isShortcut) // dont process the shortcut when we have a completer
138 QTextEdit::keyPressEvent(e);
139
140 const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
141 if (!completer || (ctrlOrShift && e->text().isEmpty()))
142 return;
143
144 bool hasModifier = (e->modifiers() != Qt::NoModifier) && ~(e->modifiers() & Qt::KeypadModifier) && !ctrlOrShift;
145
146 // grab the line we're on
147 QTextCursor tc = textCursor();
148 tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
149 QString line = tc.selectedText();
150
151 // matches the last prefix of a completable variable or function and extract as completionPrefix
152 static QRegularExpression completion(QStringLiteral("^(?:.*[^A-Za-z0-9_$])?((?:\\$[A-Za-z0-9_]*)|[A-Za-z]+[A-Za-z0-9_]*)$"));
153 QRegularExpressionMatch match;
154 int index = line.indexOf(completion, 0, &match);
155 QString completionPrefix;
156 if (index != -1 && !line.contains(QLatin1Char('#'))) {
157 completionPrefix = match.captured(1);
158 // std::cout<<"we have completer prefix '"<<completionPrefix.toStdString()<<"'"<<std::endl;
159 }
160
161 // hide the completer if we have too few characters, we are at end of word
162 if (!isShortcut && (hasModifier || e->text().isEmpty() || completionPrefix.length() < 1 || index == -1)) {
163 completer->popup()->hide();
164 } else if (_popupEnabledAction->isChecked()) {
165 // copy the completion prefix in if we don't already have it in the completer
166 if (completionPrefix != completer->completionPrefix()) {
167 completer->setCompletionPrefix(completionPrefix);
168 completer->popup()->setCurrentIndex(completer->completionModel()->index(0, 0));
169 }
170
171 // display the completer
172 QRect cr = cursorRect();
173 cr.setWidth(completer->popup()->sizeHintForColumn(0) + completer->popup()->sizeHintForColumn(1) + completer->popup()->verticalScrollBar()->sizeHint().width());
174 cr.translate(0, 6);
175 completer->complete(cr);
176 hideTip();
177 return;
178 }
179
180 // documentation completion
181 static QRegularExpression inFunction(QStringLiteral("^(?:.*[^A-Za-z0-9_$])?([A-Za-z0-9_]+)\\([^()]*$"));
182 int index2 = line.indexOf(inFunction, 0, &match);
183 if (index2 != -1) {
184 QString functionName = match.captured(1);
185 QStringList tips = completionModel->getDocString(functionName).split(QString::fromLatin1("\n"));
186 QString tip = QString(tr("<b>%1</b>")).arg(tips[0]);
187 for (int i = 1; i < tips.size(); i++) {
188 tip += QString(tr("<br>%1")).arg(tips[i]);
189 }
190 if (_popupEnabledAction->isChecked())
191 showTip(tip);
192 // QToolTip::showText(mapToGlobal(cr.bottomLeft()),tip,this,cr);
193 } else {
194 hideTip();
195 }
196}
197
198void ExprTextEdit::contextMenuEvent(QContextMenuEvent *event)
199{
200 QMenu *menu = createStandardContextMenu();
201
202 if (!menu->actions().empty()) {
203 QAction *f = menu->actions().first();
204 menu->insertAction(f, _popupEnabledAction);
205 menu->insertSeparator(f);
206 }
207
208 menu->exec(event->globalPos());
209 delete menu;
210}
211
212void ExprTextEdit::showTip(const QString &string)
213{
214 // skip empty strings
215 if (string.isEmpty())
216 return;
217 // skip already shown stuff
218 if (QToolTip::isVisible())
219 return;
220
221 QRect cr = cursorRect();
222 cr.setX(0);
223 cr.setWidth(cr.width() * 3);
224 QToolTip::showText(mapToGlobal(cr.bottomLeft()) + QPoint(0, 6), string);
225}
226
228{
229 QToolTip::hideText();
230}
231
232void ExprTextEdit::insertCompletion(const QString &completion)
233{
234 if (completer->widget() != this)
235 return;
236 QTextCursor tc = textCursor();
237 int extra = completion.length() - completer->completionPrefix().length();
238 tc.movePosition(QTextCursor::Left);
239 tc.movePosition(QTextCursor::EndOfWord);
240 tc.insertText(completion.right(extra));
241 setTextCursor(tc);
242}
243
245{
246 QTextCursor tc = textCursor();
247 tc.movePosition(QTextCursor::Left);
248 tc.movePosition(QTextCursor::EndOfWord);
249 tc.select(QTextCursor::WordUnderCursor);
250 tc.removeSelectedText();
251 setTextCursor(tc);
252}
QString getDocString(const QString &s)
void fixStyle(const QPalette &palette)
void paintEvent(QPaintEvent *e) override
void applyShortcut()
void showTip(const QString &string)
void contextMenuEvent(QContextMenuEvent *event) override
void keyPressEvent(QKeyEvent *e) override
void insertCompletion(const QString &completion)
ExprCompletionModel * completionModel
void focusInEvent(QFocusEvent *e) override
QStyle * lastStyleForHighlighter
static void hideTip()
void wheelEvent(QWheelEvent *e) override
void focusOutEvent(QFocusEvent *e) override
void mouseDoubleClickEvent(QMouseEvent *event) override
QAction * _popupEnabledAction
ExprTextEdit(QWidget *parent=nullptr)
QCompleter * completer
ExprHighlighter * highlighter
void mousePressEvent(QMouseEvent *event) override
void nextError()