KSeExpr 4.0.4.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->delta() > 0)
99 zoomIn();
100 else if (event->delta() < 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 QRegExp completion(QString::fromLatin1("^(?:.*[^A-Za-z0-9_$])?((?:\\$[A-Za-z0-9_]*)|[A-Za-z]+[A-Za-z0-9_]*)$"));
153 int index = completion.indexIn(line);
154 QString completionPrefix;
155 if (index != -1 && !line.contains(QLatin1Char('#'))) {
156 completionPrefix = completion.cap(1);
157 // std::cout<<"we have completer prefix '"<<completionPrefix.toStdString()<<"'"<<std::endl;
158 }
159
160 // hide the completer if we have too few characters, we are at end of word
161 if (!isShortcut && (hasModifier || e->text().isEmpty() || completionPrefix.length() < 1 || index == -1)) {
162 completer->popup()->hide();
163 } else if (_popupEnabledAction->isChecked()) {
164 // copy the completion prefix in if we don't already have it in the completer
165 if (completionPrefix != completer->completionPrefix()) {
166 completer->setCompletionPrefix(completionPrefix);
167 completer->popup()->setCurrentIndex(completer->completionModel()->index(0, 0));
168 }
169
170 // display the completer
171 QRect cr = cursorRect();
172 cr.setWidth(completer->popup()->sizeHintForColumn(0) + completer->popup()->sizeHintForColumn(1) + completer->popup()->verticalScrollBar()->sizeHint().width());
173 cr.translate(0, 6);
174 completer->complete(cr);
175 hideTip();
176 return;
177 }
178
179 // documentation completion
180 static QRegExp inFunction(QString::fromLatin1("^(?:.*[^A-Za-z0-9_$])?([A-Za-z0-9_]+)\\([^()]*$"));
181 int index2 = inFunction.indexIn(line);
182 if (index2 != -1) {
183 QString functionName = inFunction.cap(1);
184 QStringList tips = completionModel->getDocString(functionName).split(QString::fromLatin1("\n"));
185 QString tip = QString(tr("<b>%1</b>")).arg(tips[0]);
186 for (int i = 1; i < tips.size(); i++) {
187 tip += QString(tr("<br>%1")).arg(tips[i]);
188 }
189 if (_popupEnabledAction->isChecked())
190 showTip(tip);
191 // QToolTip::showText(mapToGlobal(cr.bottomLeft()),tip,this,cr);
192 } else {
193 hideTip();
194 }
195}
196
197void ExprTextEdit::contextMenuEvent(QContextMenuEvent *event)
198{
199 QMenu *menu = createStandardContextMenu();
200
201 if (!menu->actions().empty()) {
202 QAction *f = menu->actions().first();
203 menu->insertAction(f, _popupEnabledAction);
204 menu->insertSeparator(f);
205 }
206
207 menu->exec(event->globalPos());
208 delete menu;
209}
210
211void ExprTextEdit::showTip(const QString &string)
212{
213 // skip empty strings
214 if (string.isEmpty())
215 return;
216 // skip already shown stuff
217 if (QToolTip::isVisible())
218 return;
219
220 QRect cr = cursorRect();
221 cr.setX(0);
222 cr.setWidth(cr.width() * 3);
223 QToolTip::showText(mapToGlobal(cr.bottomLeft()) + QPoint(0, 6), string);
224}
225
227{
228 QToolTip::hideText();
229}
230
231void ExprTextEdit::insertCompletion(const QString &completion)
232{
233 if (completer->widget() != this)
234 return;
235 QTextCursor tc = textCursor();
236 int extra = completion.length() - completer->completionPrefix().length();
237 tc.movePosition(QTextCursor::Left);
238 tc.movePosition(QTextCursor::EndOfWord);
239 tc.insertText(completion.right(extra));
240 setTextCursor(tc);
241}
242
244{
245 QTextCursor tc = textCursor();
246 tc.movePosition(QTextCursor::Left);
247 tc.movePosition(QTextCursor::EndOfWord);
248 tc.select(QTextCursor::WordUnderCursor);
249 tc.removeSelectedText();
250 setTextCursor(tc);
251}
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()