HAL
code_editor.cpp
Go to the documentation of this file.
2 
6 
8 
9 #include <QPainter>
10 #include <QPropertyAnimation>
11 #include <QTextBlock>
12 #include <QDebug>
13 
14 #if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
15  #include <QRegularExpression>
16 #else
17  #include <QRegExp>
18 #endif
19 
20 namespace hal
21 {
22 
24  mScrollbar(new CodeEditorScrollbar(this)),
25  mLineNumberArea(new LineNumberArea(this)),
26  mMinimap(new CodeEditorMinimap(this)),
27  mAnimation(new QPropertyAnimation(mScrollbar, "value", this)),
28  mLineNumbersEnabled(true),
29  mLineHighlightEnabled(true),
30  mMinimapEnabled(false),
31  mLineWrapEnabled(false)
32  {
33  connect(this, &CodeEditor::blockCountChanged, this, &CodeEditor::handleBlockCountChanged);
34  connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateLineNumberArea);
35  connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateMinimap);
36 
37  if (mLineHighlightEnabled)
38  {
39  connect(this, &CodeEditor::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine);
40  }
41 
42  setVerticalScrollBar(mScrollbar);
43  mScrollbar->setMinimapScrollbar(mMinimap->scrollbar());
44  mMinimap->scrollbar()->setScrollbar(mScrollbar);
45 
48 
49  mAnimation->setDuration(200);
50 
53 
55  updateLayout();
56 
57  installEventFilter(this);
58 
60  }
61 
62  bool CodeEditor::eventFilter(QObject* object, QEvent* event)
63  {
64 
65  Q_UNUSED(object)
66 
67  if (event->type() == QEvent::Wheel)
68  {
69  QWheelEvent* wheel = static_cast<QWheelEvent*>(event);
70  if (wheel->modifiers() == Qt::ControlModifier)
71  {
72  if (wheel->delta() > 0)
73  zoomIn(2);
74  else
75  zoomOut(2);
76 
77  return true;
78  }
79  }
80  return false;
81  }
82 
84  {
85  QPainter painter(mLineNumberArea);
86  painter.setFont(mLineNumberFont);
87  QPen pen;
88  pen.setColor(mLineNumberColor);
89  painter.setPen(pen);
90 
91  QTextBlock block = firstVisibleBlock();
92  int block_number = block.blockNumber();
93  qreal top = blockBoundingGeometry(block).translated(contentOffset()).top();
94  int bottom = event->rect().bottom();
95  int width = mLineNumberArea->width() - mLineNumberArea->rightOffset();
96 
97  while (block.isValid() && top <= bottom)
98  {
99  block_number++; // INCREMENT HERE SO FIRST BLOCK HAS INDEX 1 INSTEAD OF 0
100 
101  if (block.isVisible())
102  {
104  qreal height = blockBoundingGeometry(block).height();
105  painter.drawText(QRectF(0, top, width, height), Qt::AlignRight | Qt::AlignVCenter, QString::number(block_number));
106  }
107 
108  block = block.next();
109  }
110  }
111 
113  {
114  Q_UNUSED(event)
115 
116  // MIGHT BE NEEDED LATER
117  }
118 
120  {
121  // WARNING FUNCTION ONLY RETURNS CORRECT VALUES FOR MONOSPACE FONTS !
122  QFontMetrics fm(mLineNumberFont);
123  return mLineNumberArea->leftOffset() + fm.width(QString::number(blockCount())) + mLineNumberArea->rightOffset();
124  }
125 
127  {
128  return 160;
129  }
130 
132  {
133  updateLayout();
134 
135  qreal ratio = viewport()->height() / blockBoundingGeometry(document()->firstBlock()).height();
136  mMinimap->adjustSliderHeight(ratio);
137 
139  }
140 
141  void CodeEditor::clearLineHighlight()
142  {
143  QList<QTextEdit::ExtraSelection> no_selections;
144  setExtraSelections(no_selections);
145  }
146 
147  void CodeEditor::highlightCurrentLine()
148  {
149  /*
150  * Qt doesn't want to highlight blocks in line-wrap mode (even if the docs
151  * say so), so we hack our way around it by going to the end of a block and
152  * then go upwards selecting each line in that block separately.
153  */
154  QList<QTextEdit::ExtraSelection> extra_selections;
156  cursor.clearSelection();
157  cursor.movePosition(QTextCursor::EndOfBlock);
158  cursor.movePosition(QTextCursor::StartOfLine);
159  int block = cursor.blockNumber();
160  int oldPosition;
161 
162  do
163  {
164  QTextEdit::ExtraSelection selection;
165 
166  selection.format.setBackground(mCurrentLineBackground);
167  selection.format.setProperty(QTextFormat::FullWidthSelection, true);
168  selection.cursor = cursor;
169  extra_selections.append(selection);
170 
171  oldPosition = cursor.position();
172  cursor.movePosition(QTextCursor::Up);
173  // stop when we leave the block or hit the top of the document
174  } while(cursor.blockNumber() == block && cursor.position() != oldPosition);
175 
176  setExtraSelections(extra_selections);
177  }
178 
179  void CodeEditor::handleBlockCountChanged(int new_block_count)
180  {
181  Q_UNUSED(new_block_count);
182 
183  updateLayout();
184 
185  qreal ratio = viewport()->height() / blockBoundingGeometry(document()->firstBlock()).height();
186  mMinimap->adjustSliderHeight(ratio);
187  }
188 
189  void CodeEditor::updateLineNumberArea(const QRect& rect, int dy)
190  {
191  Q_UNUSED(rect)
192  Q_UNUSED(dy)
193 
194  mLineNumberArea->update();
195  }
196 
197  void CodeEditor::updateMinimap(const QRect& rect, int dy)
198  {
199  Q_UNUSED(rect)
200  Q_UNUSED(dy)
201 
202  mMinimap->update();
203  }
204 
205  void CodeEditor::search(const QString& string, SearchOptions searchOpts)
206  {
207  if (string.isEmpty())
208  {
209  setExtraSelections({});
210  return;
211  }
213 
215  QColor color = QColor(12, 15, 19);
216  QColor mBackgroundColor = QColor(255, 255, 0);
218 
219  options.setFlag(QTextDocument::FindCaseSensitively, searchOpts.isCaseSensitive());
220  options.setFlag(QTextDocument::FindWholeWords, searchOpts.isExactMatch());
221 
222  if(searchOpts.isRegularExpression())
223  {
224 #if QT_VERSION >= QT_VERSION_CHECK(5,13,0)
226 #else
227  QRegExp regExp(string, searchOpts.isCaseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive);
228 #endif
229  while (find(regExp, options))
230  {
231  QTextCursor cur = textCursor();
232  if (cur.anchor() == cur.position()) // zero-length match
233  {
235  break; // end of document reached
236  setTextCursor(cur);
237  continue;
238  }
240  extra.format.setForeground(QBrush(color));
241  extra.format.setBackground(mBackgroundColor);
242  extra.cursor = cur;
243  extraSelections.append(extra);
244  }
245  }
246  else
247  {
248 
249  while (find(string, options))
250  {
252  extra.format.setForeground(QBrush(color));
253  extra.format.setBackground(mBackgroundColor);
254  extra.cursor = textCursor();
255  extraSelections.append(extra);
256  }
257  }
258 
260  }
261 
263  {
264  mLineNumbersEnabled = !mLineNumbersEnabled;
265  updateLayout();
266  }
267 
269  {
270  mMinimapEnabled = !mMinimapEnabled;
271  updateLayout();
272  }
273 
275  {
276  return firstVisibleBlock().blockNumber();
277  }
278 
279  void CodeEditor::centerOnLine(const int number)
280  {
281  int total = document()->lineCount();
282 
283  if (number < 0 || number >= total)
284  return;
285 
286  if (mAnimation->state() == QPropertyAnimation::Running)
287  mAnimation->stop();
288 
289  mAnimation->setStartValue(verticalScrollBar()->value());
290  mAnimation->setEndValue(number);
291  mAnimation->start();
292  }
293 
295  {
297  font.setPointSize(pt);
299  }
300 
302  {
304  }
305 
307  {
308  return mMinimap;
309  }
310 
312  {
313  return mLineNumberFont;
314  }
315 
317  {
318  return mLineNumberColor;
319  }
320 
322  {
323  return mLineNumberBackground;
324  }
325 
327  {
328  return mLineNumberHighlightColor;
329  }
330 
332  {
333  return mLineNumberHighlightBackground;
334  }
335 
337  {
338  return mCurrentLineBackground;
339  }
340 
342  {
343  mLineNumberFont = font;
344  }
345 
347  {
348  mLineNumberColor = color;
349  }
350 
352  {
353  mLineNumberBackground = color;
354  }
355 
357  {
358  mLineNumberHighlightColor = color;
359  }
360 
362  {
363  mLineNumberHighlightBackground = color;
364  }
365 
367  {
368  mCurrentLineBackground = color;
369  }
370 
371  void CodeEditor::updateLayout()
372  {
373  int left_margin = 0;
374  int right_margin = 0;
375 
376  QRect cr = contentsRect();
377 
378  if (mLineNumbersEnabled)
379  {
380  left_margin = lineNumberAreaWidth();
381  mLineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), viewport()->height()));
382  mLineNumberArea->show();
383  }
384  else
385  mLineNumberArea->hide();
386 
387  if (mMinimapEnabled)
388  {
389  right_margin = minimapWidth();
390  mMinimap->setGeometry(QRect(cr.right() - minimapWidth(), cr.top(), minimapWidth(), viewport()->height()));
391  mMinimap->show();
392  }
393  else
394  mMinimap->hide();
395 
396  setViewportMargins(left_margin, 0, right_margin, 0);
397  }
398 
400  {
401  mLineNumbersEnabled = enabled;
402  updateLayout();
403  }
404 
406  {
407  mLineHighlightEnabled = enabled;
408 
409  if(enabled)
410  {
411  connect(this, &CodeEditor::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine);
412  highlightCurrentLine();
413  }
414  else
415  {
416  disconnect(this, &CodeEditor::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine);
417  clearLineHighlight();
418  }
419 
420  updateLayout();
421  }
422 
424  {
425  mLineWrapEnabled = enabled;
427  updateLayout();
428  }
429 
430  void CodeEditor::setMinimapEnabled(bool enabled)
431  {
432  mMinimapEnabled = enabled;
433  updateLayout();
434  }
435 }
virtual void resizeEvent(QResizeEvent *event) override
void setHighlightCurrentLineEnabled(bool enabled)
int lineNumberAreaWidth()
void setLineNumberColor(QColor &color)
void setFontSize(int pt)
QColor lineNumberHighlightColor
Definition: code_editor.h:57
void setLineWrapEnabled(bool enabled)
QColor lineNumberColor
Definition: code_editor.h:55
CodeEditor(QWidget *parent=nullptr)
Definition: code_editor.cpp:23
virtual bool eventFilter(QObject *object, QEvent *event) override
Definition: code_editor.cpp:62
void centerOnLine(const int number)
int first_visible_block()
void setLineNumberBackground(QColor &color)
QColor lineNumberHighlightBackground
Definition: code_editor.h:58
void setMinimapEnabled(bool enabled)
void lineNumberAreaPaintEvent(QPaintEvent *event)
Definition: code_editor.cpp:83
void handleWheelEvent(QWheelEvent *event)
void setLineNumberHighlightColor(QColor &color)
QColor lineNumberBackground
Definition: code_editor.h:56
void setLineNumberFont(const QFont &font)
CodeEditorMinimap * minimap()
void setLineNumberHighlightBackground(QColor &color)
void toggleLineNumbers()
QFont lineNumberFont
Definition: code_editor.h:54
void search(const QString &string, SearchOptions searchOpts=8)
void minimapPaintEvent(QPaintEvent *event)
QColor currentLineBackground
Definition: code_editor.h:59
void setLineNumberEnabled(bool enabled)
void setCurrentLineBackground(QColor &color)
A minimap that supports an easier navigation in larger files.
void adjustSliderHeight(int viewport_height)
MinimapScrollbar * scrollbar()
Represents the scrollbar of the CodeEditor.
void setMinimapScrollbar(MinimapScrollbar *scrollbar)
Shows line numbers next to a CodeEditor.
void setScrollbar(QScrollBar *scrollbar)
bool isExactMatch() const
bool isCaseSensitive() const
bool isRegularExpression() const
void start(QAbstractAnimation::DeletionPolicy policy)
virtual bool event(QEvent *event) override
QScrollBar * horizontalScrollBar() const const
void setVerticalScrollBar(QScrollBar *scrollBar)
void setViewportMargins(int left, int top, int right, int bottom)
QScrollBar * verticalScrollBar() const const
QWidget * viewport() const const
int width(const QString &text, int len) const const
void setFrameStyle(int style)
Qt::KeyboardModifiers modifiers() const const
void append(const T &value)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void installEventFilter(QObject *filterObj)
void drawText(const QPointF &position, const QString &text)
void setFont(const QFont &font)
void setPen(const QColor &color)
void setColor(const QColor &color)
QRectF blockBoundingGeometry(const QTextBlock &block) const const
void blockCountChanged(int newBlockCount)
QPointF contentOffset() const const
void cursorPositionChanged()
QTextDocument * document() const const
QList< QTextEdit::ExtraSelection > extraSelections() const const
bool find(const QString &exp, QTextDocument::FindFlags options)
QTextBlock firstVisibleBlock() const const
void setLineWrapMode(QPlainTextEdit::LineWrapMode mode)
void moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode)
virtual void resizeEvent(QResizeEvent *e) override
void setExtraSelections(const QList< QTextEdit::ExtraSelection > &selections)
void setTextCursor(const QTextCursor &cursor)
QTextCursor textCursor() const const
void updateRequest(const QRect &rect, int dy)
virtual void wheelEvent(QWheelEvent *e) override
void zoomIn(int range)
void zoomOut(int range)
int left() const const
int right() const const
int top() const const
qreal height() const const
qreal top() const const
QRectF translated(qreal dx, qreal dy) const const
QString number(int n, int base)
AlignRight
CaseSensitive
NoContextMenu
ControlModifier
int blockNumber() const const
bool isValid() const const
bool isVisible() const const
QTextBlock next() const const
int anchor() const const
bool movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n)
int position() const const
typedef FindFlags
void setDocumentMargin(qreal margin)
int lineCount() const const
void setDuration(int msecs)
void setEndValue(const QVariant &value)
void setStartValue(const QVariant &value)
int delta() const const
QRect contentsRect() const const
void setContextMenuPolicy(Qt::ContextMenuPolicy policy)
void ensurePolished() const const
void hide()
void setGeometry(int x, int y, int w, int h)
void show()
void update()