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 {
54  lastStyleForHighlighter = nullptr;
55  highlighter->fixStyle(palette());
56  highlighter->rehighlight();
57  repaint();
58 }
59 
60 void ExprTextEdit::focusInEvent(QFocusEvent *e)
61 {
62  if (completer)
63  completer->setWidget(this);
64  QTextEdit::focusInEvent(e);
65 }
66 
67 void ExprTextEdit::focusOutEvent(QFocusEvent *e)
68 {
69  hideTip();
70  QTextEdit::focusInEvent(e);
71 }
72 
73 void ExprTextEdit::mousePressEvent(QMouseEvent *event)
74 {
75  hideTip();
76  QTextEdit::mousePressEvent(event);
77 }
78 
79 void ExprTextEdit::mouseDoubleClickEvent(QMouseEvent *event)
80 {
81  hideTip();
82  QTextEdit::mouseDoubleClickEvent(event);
83 }
84 
85 void ExprTextEdit::paintEvent(QPaintEvent *event)
86 {
87  if (lastStyleForHighlighter != style()) {
88  lastStyleForHighlighter = style();
89  highlighter->fixStyle(palette());
90  highlighter->rehighlight();
91  }
92  QTextEdit::paintEvent(event);
93 }
94 
95 void 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 
106 void ExprTextEdit::keyPressEvent(QKeyEvent *e)
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 
197 void 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 
211 void 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 
231 void 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 }
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)
void updateStyle()
ExprCompletionModel * completionModel
Definition: ExprTextEdit.h:35
void focusInEvent(QFocusEvent *e) override
QStyle * lastStyleForHighlighter
Definition: ExprTextEdit.h:30
static void hideTip()
void wheelEvent(QWheelEvent *e) override
void focusOutEvent(QFocusEvent *e) override
void mouseDoubleClickEvent(QMouseEvent *event) override
QAction * _popupEnabledAction
Definition: ExprTextEdit.h:31
ExprTextEdit(QWidget *parent=nullptr)
QCompleter * completer
Definition: ExprTextEdit.h:34
ExprHighlighter * highlighter
Definition: ExprTextEdit.h:29
void mousePressEvent(QMouseEvent *event) override
void nextError()