QCodeEditor.cpp
Go to the documentation of this file.
1 // QCodeEditor
2 #include <QLineNumberArea>
3 #include <QSyntaxStyle>
4 #include <QCodeEditor>
5 #include <QStyleSyntaxHighlighter>
6 #include <QFramedTextAttribute>
7 #include <QCXXHighlighter>
8 
9 
10 // Qt
11 #include <QTextBlock>
12 #include <QPaintEvent>
13 #include <QFontDatabase>
14 #include <QScrollBar>
15 #include <QAbstractTextDocumentLayout>
16 #include <QTextCharFormat>
17 #include <QCursor>
18 #include <QCompleter>
19 #include <QAbstractItemView>
20 #include <QShortcut>
21 #include <QMimeData>
22 
24  {"(", ")"},
25  {"{", "}"},
26  {"[", "]"},
27  {"\"", "\""},
28  {"'", "'"}
29 };
30 
31 QCodeEditor::QCodeEditor(QWidget* widget) :
32  QTextEdit(widget),
33  m_highlighter(nullptr),
34  m_syntaxStyle(nullptr),
35  m_lineNumberArea(new QLineNumberArea(this)),
36  m_completer(nullptr),
37  m_framedAttribute(new QFramedTextAttribute(this)),
38  m_autoIndentation(true),
39  m_autoParentheses(true),
40  m_replaceTab(true),
41  m_tabReplace(QString(4, ' '))
42 {
44  initFont();
46 
48 }
49 
51 {
52  document()
53  ->documentLayout()
54  ->registerHandler(
57  );
58 }
59 
61 {
62  auto fnt = QFontDatabase::systemFont(QFontDatabase::FixedFont);
63  fnt.setFixedPitch(true);
64  fnt.setPointSize(10);
65 
66  setFont(fnt);
67 }
68 
70 {
71  connect(
72  document(),
73  &QTextDocument::blockCountChanged,
74  this,
76  );
77 
78  connect(
79  verticalScrollBar(),
80  &QScrollBar::valueChanged,
81  [this](int){ m_lineNumberArea->update(); }
82  );
83 
84  connect(
85  this,
86  &QTextEdit::cursorPositionChanged,
87  this,
89  );
90 
91  connect(
92  this,
93  &QTextEdit::selectionChanged,
94  this,
96  );
97 }
98 
100 {
101  if (m_highlighter)
102  {
103  m_highlighter->setDocument(nullptr);
104  }
105 
106  m_highlighter = highlighter;
107 
108  if (m_highlighter)
109  {
111  m_highlighter->setDocument(document());
112  }
113 }
114 
116 {
117  m_syntaxStyle = style;
118 
121 
122  if (m_highlighter)
123  {
125  }
126 
127  updateStyle();
128 }
129 
131 {
132  if (m_highlighter)
133  {
134  m_highlighter->rehighlight();
135  }
136 
137  if (m_syntaxStyle)
138  {
139  auto currentPalette = palette();
140 
141  // Setting text format/color
142  currentPalette.setColor(
143  QPalette::ColorRole::Text,
144  m_syntaxStyle->getFormat("Text").foreground().color()
145  );
146 
147  // Setting common background
148  currentPalette.setColor(
149  QPalette::Base,
150  m_syntaxStyle->getFormat("Text").background().color()
151  );
152 
153  // Setting selection color
154  currentPalette.setColor(
155  QPalette::Highlight,
156  m_syntaxStyle->getFormat("Selection").background().color()
157  );
158 
159  setPalette(currentPalette);
160  }
161 
163 }
164 
166 {
167  auto selected = textCursor().selectedText();
168 
169  auto cursor = textCursor();
170 
171  // Cursor is null if setPlainText was called.
172  if (cursor.isNull())
173  {
174  return;
175  }
176 
177  cursor.movePosition(QTextCursor::MoveOperation::Left);
178  cursor.select(QTextCursor::SelectionType::WordUnderCursor);
179 
180  QSignalBlocker blocker(this);
181  m_framedAttribute->clear(cursor);
182 
183  if (selected.size() > 1 &&
184  cursor.selectedText() == selected)
185  {
186  auto backup = textCursor();
187 
188  // Perform search selecting
189  handleSelectionQuery(cursor);
190 
191  setTextCursor(backup);
192  }
193 }
194 
195 void QCodeEditor::resizeEvent(QResizeEvent* e)
196 {
197  QTextEdit::resizeEvent(e);
198 
200 }
201 
203 {
204  QRect cr = contentsRect();
205  m_lineNumberArea->setGeometry(
206  QRect(cr.left(),
207  cr.top(),
208  m_lineNumberArea->sizeHint().width(),
209  cr.height()
210  )
211  );
212 }
213 
215 {
216  setViewportMargins(m_lineNumberArea->sizeHint().width(), 0, 0, 0);
217 }
218 
219 void QCodeEditor::updateLineNumberArea(const QRect& rect)
220 {
221  m_lineNumberArea->update(
222  0,
223  rect.y(),
224  m_lineNumberArea->sizeHint().width(),
225  rect.height()
226  );
228 
229  if (rect.contains(viewport()->rect()))
230  {
232  }
233 }
234 
235 void QCodeEditor::handleSelectionQuery(QTextCursor cursor)
236 {
237 
238  auto searchIterator = cursor;
239  searchIterator.movePosition(QTextCursor::Start);
240  searchIterator = document()->find(cursor.selectedText(), searchIterator);
241  while (searchIterator.hasSelection())
242  {
243  m_framedAttribute->frame(searchIterator);
244 
245  searchIterator = document()->find(cursor.selectedText(), searchIterator);
246  }
247 }
248 
250 {
252 
253  highlightCurrentLine(extra);
254  highlightParenthesis(extra);
255 
256  setExtraSelections(extra);
257 }
258 
260 {
261  auto currentSymbol = charUnderCursor();
262  auto prevSymbol = charUnderCursor(-1);
263 
264  for (auto& pair : parentheses)
265  {
266  int direction;
267 
268  QChar counterSymbol;
269  QChar activeSymbol;
270  auto position = textCursor().position();
271 
272  if (pair.first == currentSymbol)
273  {
274  direction = 1;
275  counterSymbol = pair.second[0];
276  activeSymbol = currentSymbol;
277  }
278  else if (pair.second == prevSymbol)
279  {
280  direction = -1;
281  counterSymbol = pair.first[0];
282  activeSymbol = prevSymbol;
283  position--;
284  }
285  else
286  {
287  continue;
288  }
289 
290  auto counter = 1;
291 
292  while (counter != 0 &&
293  position > 0 &&
294  position < (document()->characterCount() - 1))
295  {
296  // Moving position
297  position += direction;
298 
299  auto character = document()->characterAt(position);
300  // Checking symbol under position
301  if (character == activeSymbol)
302  {
303  ++counter;
304  }
305  else if (character == counterSymbol)
306  {
307  --counter;
308  }
309  }
310 
311  auto format = m_syntaxStyle->getFormat("Parentheses");
312 
313  // Found
314  if (counter == 0)
315  {
316  ExtraSelection selection{};
317 
318  auto directionEnum =
319  direction < 0 ?
321  :
323 
324  selection.format = format;
325  selection.cursor = textCursor();
326  selection.cursor.clearSelection();
327  selection.cursor.movePosition(
328  directionEnum,
329  QTextCursor::MoveMode::MoveAnchor,
330  std::abs(textCursor().position() - position)
331  );
332 
333  selection.cursor.movePosition(
335  QTextCursor::MoveMode::KeepAnchor,
336  1
337  );
338 
339  extraSelection.append(selection);
340 
341  selection.cursor = textCursor();
342  selection.cursor.clearSelection();
343  selection.cursor.movePosition(
344  directionEnum,
345  QTextCursor::MoveMode::KeepAnchor,
346  1
347  );
348 
349  extraSelection.append(selection);
350  }
351 
352  break;
353  }
354 }
355 
357 {
358  if (!isReadOnly())
359  {
360  QTextEdit::ExtraSelection selection{};
361 
362  selection.format = m_syntaxStyle->getFormat("CurrentLine");
363  selection.format.setForeground(QBrush());
364  selection.format.setProperty(QTextFormat::FullWidthSelection, true);
365  selection.cursor = textCursor();
366  selection.cursor.clearSelection();
367 
368  extraSelection.append(selection);
369  }
370 }
371 
372 void QCodeEditor::paintEvent(QPaintEvent* e)
373 {
374  updateLineNumberArea(e->rect());
375  QTextEdit::paintEvent(e);
376 }
377 
379 {
380  // Detect the first block for which bounding rect - once translated
381  // in absolute coordinated - is contained by the editor's text area
382 
383  // Costly way of doing but since "blockBoundingGeometry(...)" doesn't
384  // exists for "QTextEdit"...
385 
386  QTextCursor curs = QTextCursor(document());
387  curs.movePosition(QTextCursor::Start);
388  for(int i=0; i < document()->blockCount(); ++i)
389  {
390  QTextBlock block = curs.block();
391 
392  QRect r1 = viewport()->geometry();
393  QRect r2 = document()
394  ->documentLayout()
395  ->blockBoundingRect(block)
396  .translated(
397  viewport()->geometry().x(),
398  viewport()->geometry().y() - verticalScrollBar()->sliderPosition()
399  ).toRect();
400 
401  if (r1.intersects(r2))
402  {
403  return i;
404  }
405 
406  curs.movePosition(QTextCursor::NextBlock);
407  }
408 
409  return 0;
410 }
411 
413 {
414  if (m_completer &&
415  m_completer->popup()->isVisible())
416  {
417  switch (e->key())
418  {
419  case Qt::Key_Enter:
420  case Qt::Key_Return:
421  case Qt::Key_Escape:
422  case Qt::Key_Tab:
423  case Qt::Key_Backtab:
424  e->ignore();
425  return true; // let the completer do default behavior
426  default:
427  break;
428  }
429  }
430 
431  // todo: Replace with modifiable QShortcut
432  auto isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space);
433 
434  return !(!m_completer || !isShortcut);
435 
436 }
437 
439 {
440  auto ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
441 
442  if (!m_completer ||
443  (ctrlOrShift && e->text().isEmpty()) ||
444  e->key() == Qt::Key_Delete)
445  {
446  return;
447  }
448 
449  static QString eow(R"(~!@#$%^&*()_+{}|:"<>?,./;'[]\-=)");
450 
451  auto isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_Space);
452  auto completionPrefix = wordUnderCursor();
453 
454  if (!isShortcut &&
455  (e->text().isEmpty() ||
456  completionPrefix.length() < 2 ||
457  eow.contains(e->text().right(1))))
458  {
459  m_completer->popup()->hide();
460  return;
461  }
462 
463  if (completionPrefix != m_completer->completionPrefix())
464  {
465  m_completer->setCompletionPrefix(completionPrefix);
466  m_completer->popup()->setCurrentIndex(m_completer->completionModel()->index(0, 0));
467  }
468 
469  auto cursRect = cursorRect();
470  cursRect.setWidth(
471  m_completer->popup()->sizeHintForColumn(0) +
472  m_completer->popup()->verticalScrollBar()->sizeHint().width()
473  );
474 
475  m_completer->complete(cursRect);
476 }
477 
478 void QCodeEditor::keyPressEvent(QKeyEvent* e) {
479 #if QT_VERSION >= 0x050A00
480  const int defaultIndent = tabStopDistance() / fontMetrics().averageCharWidth();
481 #else
482  const int defaultIndent = tabStopWidth() / fontMetrics().averageCharWidth();
483 #endif
484 
485  auto completerSkip = proceedCompleterBegin(e);
486 
487  if (!completerSkip) {
488  if (m_replaceTab && e->key() == Qt::Key_Tab &&
489  e->modifiers() == Qt::NoModifier) {
490  insertPlainText(m_tabReplace);
491  return;
492  }
493 
494  // Auto indentation
495  int indentationLevel = getIndentationSpaces();
496 
497 #if QT_VERSION >= 0x050A00
498  int tabCounts =
499  indentationLevel * fontMetrics().averageCharWidth() / tabStopDistance();
500 #else
501  int tabCounts =
502  indentationLevel * fontMetrics().averageCharWidth() / tabStopWidth();
503 #endif
504 
505  // Have Qt Edior like behaviour, if {|} and enter is pressed indent the two
506  // parenthesis
507  if (m_autoIndentation &&
508  (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) &&
509  charUnderCursor() == '}' && charUnderCursor(-1) == '{')
510  {
511  int charsBack = 0;
512  insertPlainText("\n");
513 
514  if (m_replaceTab)
515  insertPlainText(QString(indentationLevel + defaultIndent, ' '));
516  else
517  insertPlainText(QString(tabCounts + 1, '\t'));
518 
519  insertPlainText("\n");
520  charsBack++;
521 
522  if (m_replaceTab)
523  {
524  insertPlainText(QString(indentationLevel, ' '));
525  charsBack += indentationLevel;
526  }
527  else
528  {
529  insertPlainText(QString(tabCounts, '\t'));
530  charsBack += tabCounts;
531  }
532 
533  while (charsBack--)
535  return;
536  }
537 
538  // Shortcut for moving line to left
539  if (m_replaceTab && e->key() == Qt::Key_Backtab) {
540  indentationLevel = std::min(indentationLevel, m_tabReplace.size());
541 
542  auto cursor = textCursor();
543 
544  cursor.movePosition(QTextCursor::MoveOperation::StartOfLine);
545  cursor.movePosition(QTextCursor::MoveOperation::Right,
546  QTextCursor::MoveMode::KeepAnchor, indentationLevel);
547 
548  cursor.removeSelectedText();
549  return;
550  }
551 
552  QTextEdit::keyPressEvent(e);
553 
554  if (m_autoIndentation && (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter)) {
555  if (m_replaceTab)
556  insertPlainText(QString(indentationLevel, ' '));
557  else
558  insertPlainText(QString(tabCounts, '\t'));
559  }
560 
561  if (m_autoParentheses)
562  {
563  for (auto&& el : parentheses)
564  {
565  // Inserting closed brace
566  if (el.first == e->text())
567  {
568  insertPlainText(el.second);
570  break;
571  }
572 
573  // If it's close brace - check parentheses
574  if (el.second == e->text())
575  {
576  auto symbol = charUnderCursor();
577 
578  if (symbol == el.second)
579  {
580  textCursor().deletePreviousChar();
582  }
583 
584  break;
585  }
586  }
587  }
588  }
589 
591 }
592 
593 void QCodeEditor::setAutoIndentation(bool enabled)
594 {
595  m_autoIndentation = enabled;
596 }
597 
598 bool QCodeEditor::autoIndentation() const
599 {
600  return m_autoIndentation;
601 }
602 
603 void QCodeEditor::setAutoParentheses(bool enabled)
604 {
605  m_autoParentheses = enabled;
606 }
607 
608 bool QCodeEditor::autoParentheses() const
609 {
610  return m_autoParentheses;
611 }
612 
613 void QCodeEditor::setTabReplace(bool enabled)
614 {
615  m_replaceTab = enabled;
616 }
617 
618 bool QCodeEditor::tabReplace() const
619 {
620  return m_replaceTab;
621 }
622 
623 void QCodeEditor::setTabReplaceSize(int val)
624 {
625  m_tabReplace.clear();
626 
627  m_tabReplace.fill(' ', val);
628 }
629 
630 int QCodeEditor::tabReplaceSize() const
631 {
632  return m_tabReplace.size();
633 }
634 
635 void QCodeEditor::setCompleter(QCompleter *completer)
636 {
637  if (m_completer)
638  {
639  disconnect(m_completer, nullptr, this, nullptr);
640  }
641 
643 
644  if (!m_completer)
645  {
646  return;
647  }
648 
649  m_completer->setWidget(this);
650  m_completer->setCompletionMode(QCompleter::CompletionMode::PopupCompletion);
651 
652  connect(
653  m_completer,
654  QOverload<const QString&>::of(&QCompleter::activated),
655  this,
657  );
658 }
659 
660 void QCodeEditor::focusInEvent(QFocusEvent *e)
661 {
662  if (m_completer)
663  {
664  m_completer->setWidget(this);
665  }
666 
667  QTextEdit::focusInEvent(e);
668 }
669 
670 void QCodeEditor::insertCompletion(QString s)
671 {
672  if (m_completer->widget() != this)
673  {
674  return;
675  }
676 
677  auto tc = textCursor();
678  tc.select(QTextCursor::SelectionType::WordUnderCursor);
679  tc.insertText(s);
680  setTextCursor(tc);
681 }
682 
683 QCompleter *QCodeEditor::completer() const
684 {
685  return m_completer;
686 }
687 
688 QChar QCodeEditor::charUnderCursor(int offset) const
689 {
690  auto block = textCursor().blockNumber();
691  auto index = textCursor().positionInBlock();
692  auto text = document()->findBlockByNumber(block).text();
693 
694  index += offset;
695 
696  if (index < 0 || index >= text.size())
697  {
698  return {};
699  }
700 
701  return text[index];
702 }
703 
704 QString QCodeEditor::wordUnderCursor() const
705 {
706  auto tc = textCursor();
707  tc.select(QTextCursor::WordUnderCursor);
708  return tc.selectedText();
709 }
710 
711 void QCodeEditor::insertFromMimeData(const QMimeData* source)
712 {
713  insertPlainText(source->text());
714 }
715 
717 {
718  auto blockText = textCursor().block().text();
719 
720  int indentationLevel = 0;
721 
722  for (auto i = 0;
723  i < blockText.size() && QString("\t ").contains(blockText[i]);
724  ++i)
725  {
726  if (blockText[i] == ' ')
727  {
728  indentationLevel++;
729  }
730  else
731  {
732 #if QT_VERSION >= 0x050A00
733  indentationLevel += tabStopDistance() / fontMetrics().averageCharWidth();
734 #else
735  indentationLevel += tabStopWidth() / fontMetrics().averageCharWidth();
736 #endif
737  }
738  }
739 
740  return indentationLevel;
741 }
QSize sizeHint() const override
Overridden method for getting line number area size.
void focusInEvent(QFocusEvent *e) override
Method, that&#39;s called on focus into widget. It&#39;s required for setting this widget to set completer...
void frame(QTextCursor cursor)
Method for creating frame in cursor selection.
#define nullptr
Definition: backward.hpp:386
int tabReplaceSize() const
Method for getting number of spaces, that will replace tab if tabReplace is true. Default: 4...
QStyleSyntaxHighlighter * m_highlighter
void initDocumentLayoutHandlers()
Method for initializing document layout handlers.
Definition: QCodeEditor.cpp:50
bool autoIndentation() const
Method for getting is auto indentation enabled. Default: true.
QCompleter * completer() const
Method for getting completer.
Class, that describes line number area widget.
XmlRpcServer s
void onSelectionChanged()
Slot, that will be called on selection change.
QString wordUnderCursor() const
Method for getting word under cursor.
void setCompleter(QCompleter *completer)
Method for setting completer.
bool tabReplace() const
Method for getting is tab replacing enabled. Default value: true.
bool autoParentheses() const
Method for getting is auto parentheses enabled. Default value: true.
void paintEvent(QPaintEvent *e) override
Method, that&#39;s called on editor painting. This method if overloaded for line number area redraw...
void proceedCompleterEnd(QKeyEvent *e)
QCodeEditor(QWidget *widget=nullptr)
Constructor.
Definition: QCodeEditor.cpp:31
bool m_autoIndentation
void highlightCurrentLine(QList< QTextEdit::ExtraSelection > &extraSelection)
Method, that adds highlighting of currently selected line to extra selection list.
void setTabReplace(bool enabled)
Method for setting tab replacing enabled.
void updateLineNumberAreaWidth(int)
Slot, that performs update of internal editor viewport based on line number area width.
QString m_tabReplace
void setTabReplaceSize(int val)
Method for setting amount of spaces, that will replace tab.
static int type()
Static method for getting framed text attribute type.
QCompleter * m_completer
static void block(LexState *ls)
Definition: lparser.c:1293
static QVector< QPair< QString, QString > > parentheses
Definition: QCodeEditor.cpp:23
Class, that describes Qt style parser for QCodeEditor.
void setHighlighter(QStyleSyntaxHighlighter *highlighter)
Method for setting highlighter.
Definition: QCodeEditor.cpp:99
QChar charUnderCursor(int offset=0) const
Method for getting character under cursor.
void keyPressEvent(QKeyEvent *e) override
Method, that&#39;s called on any key press, posted into code editor widget. This method is overloaded for...
Class, that descrubes highlighter with syntax style.
void setSyntaxStyle(QSyntaxStyle *style)
Method for setting syntax style object.
void insertFromMimeData(const QMimeData *source) override
Method, that&#39;s called on any text insertion of mimedata into editor. If it&#39;s text - it inserts text a...
const char * source
Definition: lz4.h:699
QFramedTextAttribute * m_framedAttribute
int getFirstVisibleBlock()
Method for getting first visible block index.
void updateStyle()
Slot, that will update editor style.
void updateLineGeometry()
Method for updating geometry of line number area.
QTextCharFormat getFormat(QString name) const
Method for getting format for property name.
void initFont()
Method for initializing default monospace font.
Definition: QCodeEditor.cpp:60
void highlightParenthesis(QList< QTextEdit::ExtraSelection > &extraSelection)
Method, that adds highlighting of parenthesis if available.
void setSyntaxStyle(QSyntaxStyle *style)
Method for setting syntax style.
void clear(QTextCursor cursor)
Method for clearing all frames with desired cursor.
void updateExtraSelection()
Slot, that will proceed extra selection for current cursor position.
void insertCompletion(QString s)
Slot, that performs insertion of completion info into code.
void setAutoParentheses(bool enabled)
Method setting auto parentheses enabled.
QSyntaxStyle * m_syntaxStyle
int getIndentationSpaces()
Method for getting number of indentation spaces in current line. Tabs will be treated as tabWidth / s...
QLineNumberArea * m_lineNumberArea
bool m_autoParentheses
static QSyntaxStyle * defaultStyle()
Static method for getting default style.
void updateLineNumberArea(const QRect &rect)
Slot, that performs update of some part of line number area.
void resizeEvent(QResizeEvent *e) override
Method, that&#39;s called on any widget resize. This method if overloaded for line number area resizing...
void setSyntaxStyle(QSyntaxStyle *style)
Method for setting syntax sty.e.
bool proceedCompleterBegin(QKeyEvent *e)
Method, that performs completer processing. Returns true if event has to be dropped.
void performConnections()
Method for performing connection of objects.
Definition: QCodeEditor.cpp:69
void handleSelectionQuery(QTextCursor cursor)
Method, that performs selection frame selection.
void setAutoIndentation(bool enabled)
Method for setting auto indentation enabled.
void setSyntaxStyle(QSyntaxStyle *style)
Method for setting syntax style for rendering.
Class, that describes attribute for making text frame.
std::basic_string< Char > format(const text_style &ts, const S &format_str, const Args &... args)
Definition: color.h:583


plotjuggler
Author(s): Davide Faconti
autogenerated on Mon Jun 19 2023 03:01:38