KTextEditor

kateviewhelpers.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2008, 2009 Matthew Woehlke <mw_triad@users.sourceforge.net>
4 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
5 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
6 SPDX-FileCopyrightText: 2001 Anders Lund <anders@alweb.dk>
7 SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
8 SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
9 SPDX-FileCopyrightText: 2012 Kåre Särs <kare.sars@iki.fi> (Minimap)
10 SPDX-FileCopyrightText: 2017-2018 Friedrich W. H. Kossebau <kossebau@kde.org>
11
12 SPDX-License-Identifier: LGPL-2.0-only
13*/
14
15#include "kateviewhelpers.h"
16
17#include "kateabstractinputmode.h"
18#include "kateannotationitemdelegate.h"
19#include "katecmd.h"
20#include "katecommandrangeexpressionparser.h"
21#include "kateconfig.h"
22#include "katedocument.h"
23#include "kateglobal.h"
24#include "katelayoutcache.h"
25#include "katepartdebug.h"
26#include "katerenderer.h"
27#include "katesyntaxmanager.h"
28#include "katetextlayout.h"
29#include "katetextpreview.h"
30#include "kateview.h"
31#include "kateviewinternal.h"
32#include <katebuffer.h>
33#include <ktexteditor/annotationinterface.h>
34#include <ktexteditor/attribute.h>
35#include <ktexteditor/command.h>
36#include <ktexteditor/movingrange.h>
37
38#include <KActionCollection>
39#include <KCharsets>
40#include <KColorUtils>
41#include <KConfigGroup>
42#include <KHelpClient>
43#include <KLocalizedString>
44
45#include <QAction>
46#include <QActionGroup>
47#include <QBoxLayout>
48#include <QCursor>
49#include <QGuiApplication>
50#include <QKeyEvent>
51#include <QLinearGradient>
52#include <QMenu>
53#include <QPainter>
54#include <QPainterPath>
55#include <QPalette>
56#include <QPen>
57#include <QRegularExpression>
58#include <QStackedWidget>
59#include <QStyle>
60#include <QStyleOption>
61#include <QToolButton>
62#include <QToolTip>
63#include <QVariant>
64#include <QWhatsThis>
65#include <QtAlgorithms>
66
67#include <math.h>
68
69// BEGIN KateMessageLayout
70KateMessageLayout::KateMessageLayout(QWidget *parent)
71 : QLayout(parent)
72{
73 qCDebug(LOG_KTE);
74}
75
76KateMessageLayout::~KateMessageLayout()
77{
78 while (QLayoutItem *item = takeAt(0)) {
79 delete item;
80 }
81}
82
83void KateMessageLayout::addItem(QLayoutItem *item)
84{
85 Q_ASSERT(false);
87}
88
89void KateMessageLayout::addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos)
90{
91 add(new QWidgetItem(widget), pos);
92}
93
94int KateMessageLayout::count() const
95{
96 return m_items.size();
97}
98
99QLayoutItem *KateMessageLayout::itemAt(int index) const
100{
101 return m_items.value(index).item;
102}
103
104void KateMessageLayout::setGeometry(const QRect &rect)
105{
107 const int s = spacing();
108 const QRect adjustedRect = rect.adjusted(s, s, -s, -s);
109
110 for (const auto &wrapper : std::as_const(m_items)) {
111 QLayoutItem *item = wrapper.item;
112 auto position = wrapper.position;
113
114 if (position == KTextEditor::Message::TopInView) {
115 const QRect r(adjustedRect.width() - item->sizeHint().width(), s, item->sizeHint().width(), item->sizeHint().height());
116 item->setGeometry(r);
117 } else if (position == KTextEditor::Message::BottomInView) {
118 const QRect r(adjustedRect.width() - item->sizeHint().width(),
119 adjustedRect.height() - item->sizeHint().height(),
120 item->sizeHint().width(),
121 item->sizeHint().height());
122 item->setGeometry(r);
123 } else if (position == KTextEditor::Message::CenterInView) {
124 QRect r(0, 0, item->sizeHint().width(), item->sizeHint().height());
125 r.moveCenter(adjustedRect.center());
126 item->setGeometry(r);
127 } else {
128 Q_ASSERT_X(false, "setGeometry", "Only TopInView, CenterInView, and BottomInView are supported.");
129 }
130 }
131}
132
133QSize KateMessageLayout::sizeHint() const
134{
135 return QSize();
136}
137
138QLayoutItem *KateMessageLayout::takeAt(int index)
139{
140 if (index >= 0 && index < m_items.size()) {
141 return m_items.takeAt(index).item;
142 }
143 return nullptr;
144}
145
146void KateMessageLayout::add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos)
147{
148 m_items.push_back({item, pos});
149}
150// END KateMessageLayout
151
152// BEGIN KateScrollBar
153static const int s_lineWidth = 100;
154static const int s_pixelMargin = 8;
155static const int s_linePixelIncLimit = 6;
156
157KateScrollBar::KateScrollBar(Qt::Orientation orientation, KateViewInternal *parent)
158 : QScrollBar(orientation, parent->m_view)
159 , m_middleMouseDown(false)
160 , m_leftMouseDown(false)
161 , m_view(parent->m_view)
162 , m_doc(parent->doc())
163 , m_viewInternal(parent)
164 , m_textPreview(nullptr)
165 , m_showMarks(false)
166 , m_showMiniMap(false)
167 , m_miniMapAll(true)
168 , m_needsUpdateOnShow(false)
169 , m_miniMapWidth(40)
170 , m_grooveHeight(height())
171{
172 connect(this, &KateScrollBar::valueChanged, this, &KateScrollBar::sliderMaybeMoved);
173 connect(m_doc, &KTextEditor::DocumentPrivate::marksChanged, this, &KateScrollBar::marksChanged);
174
175 m_updateTimer.setInterval(300);
176 m_updateTimer.setSingleShot(true);
177
178 // track mouse for text preview widget
179 setMouseTracking(orientation == Qt::Vertical);
180
181 // setup text preview timer
182 m_delayTextPreviewTimer.setSingleShot(true);
183 m_delayTextPreviewTimer.setInterval(250);
184 connect(&m_delayTextPreviewTimer, &QTimer::timeout, this, &KateScrollBar::showTextPreview);
185}
186
187void KateScrollBar::showEvent(QShowEvent *event)
188{
190
191 if (m_needsUpdateOnShow) {
192 m_needsUpdateOnShow = false;
193 updatePixmap();
194 }
195}
196
197KateScrollBar::~KateScrollBar()
198{
199 delete m_textPreview;
200}
201
202void KateScrollBar::setShowMiniMap(bool b)
203{
204 if (b && !m_showMiniMap) {
205 auto timerSlot = qOverload<>(&QTimer::start);
206 connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, &m_updateTimer, timerSlot, Qt::UniqueConnection);
208 connect(m_view, &KTextEditor::ViewPrivate::delayedUpdateOfView, &m_updateTimer, timerSlot, Qt::UniqueConnection);
209 connect(&m_updateTimer, &QTimer::timeout, this, &KateScrollBar::updatePixmap, Qt::UniqueConnection);
210 connect(&(m_view->textFolding()), &Kate::TextFolding::foldingRangesChanged, &m_updateTimer, timerSlot, Qt::UniqueConnection);
211 } else if (!b) {
212 disconnect(&m_updateTimer);
213 }
214
215 m_showMiniMap = b;
216
218 update();
219}
220
221QSize KateScrollBar::sizeHint() const
222{
223 if (m_showMiniMap) {
224 return QSize(m_miniMapWidth, QScrollBar::sizeHint().height());
225 }
226 return QScrollBar::sizeHint();
227}
228
229int KateScrollBar::minimapYToStdY(int y)
230{
231 // Check if the minimap fills the whole scrollbar
232 if (m_stdGroveRect.height() == m_mapGroveRect.height()) {
233 return y;
234 }
235
236 // check if y is on the step up/down
237 if ((y < m_stdGroveRect.top()) || (y > m_stdGroveRect.bottom())) {
238 return y;
239 }
240
241 if (y < m_mapGroveRect.top()) {
242 return m_stdGroveRect.top() + 1;
243 }
244
245 if (y > m_mapGroveRect.bottom()) {
246 return m_stdGroveRect.bottom() - 1;
247 }
248
249 // check for div/0
250 if (m_mapGroveRect.height() == 0) {
251 return y;
252 }
253
254 int newY = (y - m_mapGroveRect.top()) * m_stdGroveRect.height() / m_mapGroveRect.height();
255 newY += m_stdGroveRect.top();
256 return newY;
257}
258
259void KateScrollBar::mousePressEvent(QMouseEvent *e)
260{
261 // delete text preview
262 hideTextPreview();
263
264 if (e->button() == Qt::MiddleButton) {
265 m_middleMouseDown = true;
266 } else if (e->button() == Qt::LeftButton) {
267 m_leftMouseDown = true;
268 }
269
270 if (m_showMiniMap) {
271 if (!m_sliderRect.contains(e->pos()) && m_leftMouseDown && e->pos().y() > m_mapGroveRect.top() && e->pos().y() < m_mapGroveRect.bottom()) {
272 // if we show the minimap left-click jumps directly to the selected position
273 int newVal = (e->pos().y() - m_mapGroveRect.top()) / (double)m_mapGroveRect.height() * (double)(maximum() + pageStep()) - pageStep() / 2;
274 newVal = qBound(0, newVal, maximum());
275 setSliderPosition(newVal);
276 }
277 const QPoint pos(6, minimapYToStdY(e->pos().y()));
280 } else {
282 }
283
284 m_toolTipPos = e->globalPosition().toPoint() - QPoint(e->pos().x(), 0);
285 const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
286 const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
287 QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
288
289 redrawMarks();
290}
291
292void KateScrollBar::mouseReleaseEvent(QMouseEvent *e)
293{
294 if (e->button() == Qt::MiddleButton) {
295 m_middleMouseDown = false;
296 } else if (e->button() == Qt::LeftButton) {
297 m_leftMouseDown = false;
298 }
299
300 redrawMarks();
301
302 if (m_leftMouseDown || m_middleMouseDown) {
304 }
305
306 if (m_showMiniMap) {
307 const QPoint pos(e->pos().x(), minimapYToStdY(e->pos().y()));
310 } else {
312 }
313}
314
315void KateScrollBar::mouseMoveEvent(QMouseEvent *e)
316{
317 if (m_showMiniMap) {
318 const QPoint pos(e->pos().x(), minimapYToStdY(e->pos().y()));
321 } else {
323 }
324
325 if (e->buttons() & (Qt::LeftButton | Qt::MiddleButton)) {
326 redrawMarks();
327
328 // current line tool tip
329 m_toolTipPos = e->globalPosition().toPoint() - QPoint(e->pos().x(), 0);
330 const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
331 const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
332 QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
333 }
334
335 showTextPreviewDelayed();
336}
337
338void KateScrollBar::leaveEvent(QEvent *event)
339{
340 hideTextPreview();
341
343}
344
345bool KateScrollBar::eventFilter(QObject *object, QEvent *event)
346{
347 Q_UNUSED(object)
348
349 if (m_textPreview && event->type() == QEvent::WindowDeactivate) {
350 // We need hide the scrollbar TextPreview widget
351 hideTextPreview();
352 }
353
354 return false;
355}
356
357void KateScrollBar::paintEvent(QPaintEvent *e)
358{
359 if (m_doc->marks().size() != m_lines.size()) {
360 recomputeMarksPositions();
361 }
362 if (m_showMiniMap) {
363 miniMapPaintEvent(e);
364 } else {
365 normalPaintEvent(e);
366 }
367}
368
369void KateScrollBar::showTextPreviewDelayed()
370{
371 if (!m_textPreview) {
372 if (!m_delayTextPreviewTimer.isActive()) {
373 m_delayTextPreviewTimer.start();
374 }
375 } else {
376 showTextPreview();
377 }
378}
379
380void KateScrollBar::showTextPreview()
381{
382 if (orientation() != Qt::Vertical || isSliderDown() || (minimum() == maximum()) || !m_view->config()->scrollBarPreview()) {
383 return;
384 }
385
386 // only show when main window is active (#392396)
387 if (window() && !window()->isActiveWindow()) {
388 return;
389 }
390
391 QRect grooveRect;
392 if (m_showMiniMap) {
393 // If mini-map is shown, the height of the map might not be the whole height
394 grooveRect = m_mapGroveRect;
395 } else {
397 opt.initFrom(this);
398 opt.subControls = QStyle::SC_None;
399 opt.activeSubControls = QStyle::SC_None;
400 opt.orientation = orientation();
401 opt.minimum = minimum();
402 opt.maximum = maximum();
403 opt.sliderPosition = sliderPosition();
404 opt.sliderValue = value();
405 opt.singleStep = singleStep();
406 opt.pageStep = pageStep();
407
409 }
410
411 if (m_view->config()->scrollPastEnd()) {
412 // Adjust the grove size to accommodate the added pageStep at the bottom
413 int adjust = pageStep() * grooveRect.height() / (maximum() + pageStep() - minimum());
414 grooveRect.adjust(0, 0, 0, -adjust);
415 }
416
417 const QPoint cursorPos = mapFromGlobal(QCursor::pos());
418 if (grooveRect.contains(cursorPos)) {
419 if (!m_textPreview) {
420 m_textPreview = new KateTextPreview(m_view, this);
421 m_textPreview->setAttribute(Qt::WA_ShowWithoutActivating);
422 m_textPreview->setFrameStyle(QFrame::StyledPanel);
423
424 // event filter to catch application WindowDeactivate event, to hide the preview window
425 qApp->installEventFilter(this);
426 }
427
428 const qreal posInPercent = static_cast<double>(cursorPos.y() - grooveRect.top()) / grooveRect.height();
429 const qreal startLine = posInPercent * m_view->textFolding().visibleLines();
430
431 m_textPreview->resize(m_view->width() / 2, m_view->height() / 5);
432 const int xGlobal = mapToGlobal(QPoint(0, 0)).x();
433 const int yGlobal = qMin(mapToGlobal(QPoint(0, height())).y() - m_textPreview->height(),
434 qMax(mapToGlobal(QPoint(0, 0)).y(), mapToGlobal(cursorPos).y() - m_textPreview->height() / 2));
435 m_textPreview->move(xGlobal - m_textPreview->width(), yGlobal);
436 m_textPreview->setLine(startLine);
437 m_textPreview->setCenterView(true);
438 m_textPreview->setScaleFactor(0.75);
439 m_textPreview->raise();
440 m_textPreview->show();
441 } else {
442 hideTextPreview();
443 }
444}
445
446void KateScrollBar::hideTextPreview()
447{
448 if (m_delayTextPreviewTimer.isActive()) {
449 m_delayTextPreviewTimer.stop();
450 }
451
452 qApp->removeEventFilter(this);
453 delete m_textPreview;
454}
455
456// This function is optimized for bing called in sequence.
457void KateScrollBar::getCharColorRanges(const QList<Kate::TextLine::Attribute> &attributes,
458 const QList<Kate::TextRange *> &decorations,
459 const QString &text,
461 QVarLengthArray<std::pair<QRgb, QPen>, 20> &penCache)
462{
463 ranges.clear();
464
465 auto getPen = [&](const QBrush &color) -> int {
466 uint rgb = color.color().rgb();
467 auto it = std::find_if(penCache.begin(), penCache.end(), [rgb](const std::pair<QRgb, QPen> &rgbToPen) {
468 return rgb == rgbToPen.first;
469 });
470 if (it != penCache.end()) {
471 return it - penCache.begin();
472 }
473 penCache.push_back({rgb, QPen(color, 1)});
474 return (int)penCache.size() - 1;
475 };
476
477 constexpr QChar space = QLatin1Char(' ');
478 constexpr QChar tab = QLatin1Char('\t');
479
480 for (int i = 0; i < text.size() && i < s_lineWidth; ++i) {
481 if (text[i] == space || text[i] == tab) {
482 continue;
483 }
484
485 bool styleFound = false;
486 for (auto range : decorations) {
487 if (range->containsColumn(i)) {
488 QBrush color = range->attribute()->foreground();
489 styleFound = true;
490 int startCol = range->start().column();
491 int endCol = range->end().column();
492 ranges << ColumnRangeWithColor{.penIndex = getPen(color), .startColumn = startCol, .endColumn = endCol};
493 i = endCol;
494 break;
495 }
496 }
497
498 if (styleFound) {
499 continue;
500 }
501
502 // If there's no decoration set for the current character (this will mostly be the case for
503 // plain Kate), query the styles, that is, the default kate syntax highlighting.
504 // go to the block containing x
505 qsizetype attributeIndex = 0;
506 while ((attributeIndex < attributes.size()) && ((attributes[attributeIndex].offset + attributes[attributeIndex].length) < i)) {
507 ++attributeIndex;
508 }
509 if (attributeIndex < attributes.size()) {
510 const auto attr = attributes[attributeIndex];
511 if ((i < attr.offset + attr.length)) {
512 QBrush color = m_view->renderer()->attribute(attr.attributeValue)->foreground();
513 int startCol = attr.offset;
514 int endCol = attr.offset + attr.length;
515 ranges << ColumnRangeWithColor{.penIndex = getPen(color), .startColumn = startCol, .endColumn = endCol};
516 i = endCol;
517 }
518 }
519 }
520}
521
522void KateScrollBar::updatePixmap()
523{
524 // QElapsedTimer time;
525 // time.start();
526
527 if (!m_showMiniMap) {
528 // make sure no time is wasted if the option is disabled
529 return;
530 }
531
532 if (!isVisible()) {
533 // don't update now if the document is not visible; do it when
534 // the document is shown again instead
535 m_needsUpdateOnShow = true;
536 return;
537 }
538
539 // For performance reason, only every n-th line will be drawn if the widget is
540 // sufficiently small compared to the amount of lines in the document.
541 int docLineCount = m_view->textFolding().visibleLines();
542 int pixmapLineCount = docLineCount;
543 if (m_view->config()->scrollPastEnd()) {
544 pixmapLineCount += pageStep();
545 }
546 int pixmapLinesUnscaled = pixmapLineCount;
547 if (m_grooveHeight < 5) {
548 m_grooveHeight = 5;
549 }
550 int charIncrement = 1;
551 int lineIncrement = 1;
552 if ((m_grooveHeight > 10) && (pixmapLineCount >= m_grooveHeight * 2)) {
553 charIncrement = pixmapLineCount / m_grooveHeight;
554 while (charIncrement > s_linePixelIncLimit) {
555 lineIncrement++;
556 pixmapLineCount = pixmapLinesUnscaled / lineIncrement;
557 charIncrement = pixmapLineCount / m_grooveHeight;
558 }
559 pixmapLineCount /= charIncrement;
560 }
561
562 int pixmapLineWidth = s_pixelMargin + s_lineWidth / charIncrement;
563
564 // qCDebug(LOG_KTE) << "l" << lineIncrement << "c" << charIncrement << "d";
565 // qCDebug(LOG_KTE) << "pixmap" << pixmapLineCount << pixmapLineWidth << "docLines" << m_view->textFolding().visibleLines() << "height" << m_grooveHeight;
566
567 const QBrush backgroundColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->background();
568 const QBrush defaultTextColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground();
569 const QBrush selectionBgColor = m_view->rendererConfig()->selectionColor();
570
571 QColor modifiedLineColor = m_view->rendererConfig()->modifiedLineColor();
572 QColor savedLineColor = m_view->rendererConfig()->savedLineColor();
573 // move the modified line color away from the background color
574 modifiedLineColor.setHsv(modifiedLineColor.hue(), 255, 255 - backgroundColor.color().value() / 3);
575 savedLineColor.setHsv(savedLineColor.hue(), 100, 255 - backgroundColor.color().value() / 3);
576
577 const QBrush modifiedLineBrush = modifiedLineColor;
578 const QBrush savedLineBrush = savedLineColor;
579
580 // increase dimensions by ratio
581 m_pixmap = QPixmap(pixmapLineWidth * m_view->devicePixelRatioF(), pixmapLineCount * m_view->devicePixelRatioF());
582 m_pixmap.fill(QColor("transparent"));
583
584 // The text currently selected in the document, to be drawn later.
585 const KTextEditor::Range selection = m_view->selectionRange();
586 const bool hasSelection = !selection.isEmpty();
587 // reusable buffer for color->range
589 const QPen defaultTextPen = QPen(defaultTextColor, 1);
590 // resusable buffer for line ranges;
591 QList<Kate::TextRange *> decorations;
592
593 QPainter painter;
594 if (painter.begin(&m_pixmap)) {
595 // init pen once, afterwards, only change it if color changes to avoid a lot of allocation for setPen
596 painter.setPen(QPen(selectionBgColor, 1));
597
598 // Do not force updates of the highlighting if the document is very large
599 const bool simpleMode = m_doc->lines() > 7500;
600
601 int pixelY = 0;
602 int drawnLines = 0;
603
604 // pen cache to avoid a lot of allocations from pen creation
606
607 // Iterate over all visible lines, drawing them.
608 for (int virtualLine = 0; virtualLine < docLineCount; virtualLine += lineIncrement) {
609 int realLineNumber = m_view->textFolding().visibleLineToLine(virtualLine);
610 const Kate::TextLine kateline = m_doc->plainKateTextLine(realLineNumber);
611 const QString lineText = kateline.text();
612
613 if (!simpleMode) {
614 m_doc->buffer().ensureHighlighted(realLineNumber);
615 }
616
617 // get normal highlighting stuff
618 const auto &attributes = kateline.attributesList();
619
620 // get moving ranges with attribs (semantic highlighting and co.)
621 m_view->doc()->buffer().rangesForLine(realLineNumber, m_view, true, decorations);
622
623 // Draw selection if it is on an empty line
624
625 int pixelX = s_pixelMargin; // use this to control the offset of the text from the left
626
627 if (hasSelection) {
628 if (selection.contains(KTextEditor::Cursor(realLineNumber, 0)) && lineText.size() == 0) {
629 if (selectionBgColor != painter.pen().brush()) {
630 painter.setPen(QPen(selectionBgColor, 1));
631 }
632 painter.drawLine(s_pixelMargin, pixelY, s_pixelMargin + s_lineWidth - 1, pixelY);
633 }
634 // Iterate over the line to draw the background
635 int selStartX = -1;
636 int selEndX = -1;
637 for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) {
638 if (pixelX >= s_lineWidth + s_pixelMargin) {
639 break;
640 }
641 // Query the selection and draw it behind the character
642 if (selection.contains(KTextEditor::Cursor(realLineNumber, x))) {
643 if (selStartX == -1) {
644 selStartX = pixelX;
645 }
646 selEndX = pixelX;
647 if (lineText.size() - 1 == x) {
648 selEndX = s_lineWidth + s_pixelMargin - 1;
649 }
650 }
651
652 if (lineText[x] == QLatin1Char('\t')) {
653 pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width...
654 } else {
655 pixelX++;
656 }
657 }
658
659 if (selStartX != -1) {
660 if (selectionBgColor != painter.pen().brush()) {
661 painter.setPen(QPen(selectionBgColor, 1));
662 }
663 painter.drawLine(selStartX, pixelY, selEndX, pixelY);
664 }
665 }
666
667 // Iterate over all the characters in the current line
668 getCharColorRanges(attributes, decorations, lineText, colorRangesForLine, penCache);
669 pixelX = s_pixelMargin;
670 for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) {
671 if (pixelX >= s_lineWidth + s_pixelMargin) {
672 break;
673 }
674
675 // draw the pixels
676 if (lineText[x] == QLatin1Char(' ')) {
677 pixelX++;
678 } else if (lineText[x] == QLatin1Char('\t')) {
679 pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width...
680 } else {
681 const QPen *pen = nullptr;
682 int rangeEnd = x + 1;
683 for (const auto &cr : colorRangesForLine) {
684 if (cr.startColumn <= x && x <= cr.endColumn) {
685 rangeEnd = cr.endColumn;
686 if (cr.penIndex != -1) {
687 pen = &penCache[cr.penIndex].second;
688 }
689 }
690 }
691
692 if (!pen) {
693 pen = &defaultTextPen;
694 }
695 // get the column range and color in which this 'x' lies
696 painter.setPen(*pen);
697
698 // Actually draw the pixels with the color queried from the renderer.
700 for (; x < rangeEnd; x += charIncrement) {
701 if (pixelX >= s_lineWidth + s_pixelMargin) {
702 break;
703 }
704 points.append({pixelX++, pixelY});
705 }
706 painter.drawPoints(points.data(), points.size());
707 }
708 }
709 drawnLines++;
710 if (((drawnLines) % charIncrement) == 0) {
711 pixelY++;
712 }
713 }
714 // qCDebug(LOG_KTE) << drawnLines;
715 // Draw line modification marker map.
716 // Disable this if the document is really huge,
717 // since it requires querying every line.
718 if (m_doc->lines() < 50000) {
719 for (int lineno = 0; lineno < docLineCount; lineno++) {
720 int realLineNo = m_view->textFolding().visibleLineToLine(lineno);
721 const auto line = m_doc->plainKateTextLine(realLineNo);
722 const QBrush &col = line.markedAsModified() ? modifiedLineBrush : savedLineBrush;
723 if (line.markedAsModified() || line.markedAsSavedOnDisk()) {
724 int pos = (lineno * pixmapLineCount) / pixmapLinesUnscaled;
725 painter.fillRect(2, pos, 3, 1, col);
726 }
727 }
728 }
729
730 // end painting
731 painter.end();
732 }
733
734 // set right ratio
735 m_pixmap.setDevicePixelRatio(m_view->devicePixelRatioF());
736
737 // qCDebug(LOG_KTE) << time.elapsed();
738 // Redraw the scrollbar widget with the updated pixmap.
739 update();
740}
741
742void KateScrollBar::miniMapPaintEvent(QPaintEvent *e)
743{
745
746 QPainter painter(this);
747
749 opt.initFrom(this);
750 opt.subControls = QStyle::SC_None;
751 opt.activeSubControls = QStyle::SC_None;
752 opt.orientation = orientation();
753 opt.minimum = minimum();
754 opt.maximum = maximum();
755 opt.sliderPosition = sliderPosition();
756 opt.sliderValue = value();
757 opt.singleStep = singleStep();
758 opt.pageStep = pageStep();
759
761 m_stdGroveRect = grooveRect;
762 if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this).height() == 0) {
763 int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this);
764 grooveRect.moveTop(alignMargin);
765 grooveRect.setHeight(grooveRect.height() - alignMargin);
766 }
767 if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this).height() == 0) {
768 int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this);
769 grooveRect.setHeight(grooveRect.height() - alignMargin);
770 }
771 m_grooveHeight = grooveRect.height();
772
773 const int docXMargin = 1;
774 // style()->drawControl(QStyle::CE_ScrollBarAddLine, &opt, &painter, this);
775 // style()->drawControl(QStyle::CE_ScrollBarSubLine, &opt, &painter, this);
776
777 // calculate the document size and position
778 const int docHeight = qMin(grooveRect.height(), int(m_pixmap.height() / m_pixmap.devicePixelRatio() * 2)) - 2 * docXMargin;
779 const int yoffset = 1; // top-aligned in stead of center-aligned (grooveRect.height() - docHeight) / 2;
780 const QRect docRect(QPoint(grooveRect.left() + docXMargin, yoffset + grooveRect.top()), QSize(grooveRect.width() - docXMargin, docHeight));
781 m_mapGroveRect = docRect;
782
783 // calculate the visible area
784 int max = qMax(maximum() + 1, 1);
785 int visibleStart = value() * docHeight / (max + pageStep()) + docRect.top() + 0.5;
786 int visibleEnd = (value() + pageStep()) * docHeight / (max + pageStep()) + docRect.top();
787 QRect visibleRect = docRect;
788 visibleRect.moveTop(visibleStart);
789 visibleRect.setHeight(visibleEnd - visibleStart);
790
791 // calculate colors
792 const QColor backgroundColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->background().color();
793 const QColor foregroundColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground().color();
794 const QColor highlightColor = palette().highlight().color();
795
796 const int backgroundLightness = backgroundColor.lightness();
797 const int foregroundLightness = foregroundColor.lightness();
798 const int lighnessDiff = (foregroundLightness - backgroundLightness);
799
800 // get a color suited for the color theme
801 QColor darkShieldColor = palette().color(QPalette::Mid);
802 int hue;
803 int sat;
804 int light;
805 darkShieldColor.getHsl(&hue, &sat, &light);
806 // apply suitable lightness
807 darkShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.35);
808 // gradient for nicer results
809 QLinearGradient gradient(0, 0, width(), 0);
810 gradient.setColorAt(0, darkShieldColor);
811 gradient.setColorAt(0.3, darkShieldColor.lighter(115));
812 gradient.setColorAt(1, darkShieldColor);
813
814 QColor lightShieldColor;
815 lightShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.15);
816
817 QColor outlineColor;
818 outlineColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.5);
819
820 // draw the grove background in case the document is small
821 painter.setPen(Qt::NoPen);
822 painter.setBrush(backgroundColor);
823 painter.drawRect(grooveRect);
824
825 // adjust the rectangles
827 sliderRect.setX(docXMargin);
828 sliderRect.setWidth(width() - docXMargin * 2);
829
830 if ((docHeight + 2 * docXMargin >= grooveRect.height()) && (sliderRect.height() > visibleRect.height() + 2)) {
831 visibleRect.adjust(2, 0, -3, 0);
832 } else {
833 visibleRect.adjust(1, 0, -1, 2);
834 sliderRect.setTop(visibleRect.top() - 1);
835 sliderRect.setBottom(visibleRect.bottom() + 1);
836 }
837
838 // Smooth transform only when squeezing
839 if (grooveRect.height() < m_pixmap.height() / m_pixmap.devicePixelRatio()) {
841 }
842
843 // draw the modified lines margin
844 QRect pixmapMarginRect(QPoint(0, 0), QSize(s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio()));
845 QRect docPixmapMarginRect(QPoint(0, docRect.top()), QSize(s_pixelMargin, docRect.height()));
846 painter.drawPixmap(docPixmapMarginRect, m_pixmap, pixmapMarginRect);
847
848 // calculate the stretch and draw the stretched lines (scrollbar marks)
849 QRect pixmapRect(QPoint(s_pixelMargin, 0),
850 QSize(m_pixmap.width() / m_pixmap.devicePixelRatio() - s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio()));
851 QRect docPixmapRect(QPoint(s_pixelMargin, docRect.top()), QSize(docRect.width() - s_pixelMargin, docRect.height()));
852 painter.drawPixmap(docPixmapRect, m_pixmap, pixmapRect);
853
854 // delimit the end of the document
855 const int y = docPixmapRect.height() + grooveRect.y();
856 if (y + 2 < grooveRect.y() + grooveRect.height()) {
857 QColor fg(foregroundColor);
858 fg.setAlpha(30);
859 painter.setBrush(Qt::NoBrush);
860 painter.setPen(QPen(fg, 1));
861 painter.drawLine(grooveRect.x() + 1, y + 2, width() - 1, y + 2);
862 }
863
864 // fade the invisible sections
865 const QRect top(grooveRect.x(),
866 grooveRect.y(),
867 grooveRect.width(),
868 visibleRect.y() - grooveRect.y() // Pen width
869 );
870 const QRect bottom(grooveRect.x(),
871 grooveRect.y() + visibleRect.y() + visibleRect.height() - grooveRect.y(), // Pen width
872 grooveRect.width(),
873 grooveRect.height() - (visibleRect.y() - grooveRect.y()) - visibleRect.height());
874
875 QColor faded(backgroundColor);
876 faded.setAlpha(110);
877 painter.fillRect(top, faded);
878 painter.fillRect(bottom, faded);
879
880 // add a thin line to limit the scrollbar
881 QColor c(foregroundColor);
882 c.setAlpha(10);
883 painter.setPen(QPen(c, 1));
884 painter.drawLine(0, 0, 0, height());
885
886 if (m_showMarks) {
887 QHashIterator<int, QColor> it = m_lines;
888 QPen penBg;
889 penBg.setWidth(4);
890 lightShieldColor.setAlpha(180);
891 penBg.setColor(lightShieldColor);
892 painter.setPen(penBg);
893 while (it.hasNext()) {
894 it.next();
895 int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();
896 painter.drawLine(6, y, width() - 6, y);
897 }
898
899 it = m_lines;
900 QPen pen;
901 pen.setWidth(2);
902 while (it.hasNext()) {
903 it.next();
904 pen.setColor(it.value());
905 painter.setPen(pen);
906 int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();
907 painter.drawLine(6, y, width() - 6, y);
908 }
909 }
910
911 // slider outline
912 QColor sliderColor(highlightColor);
913 sliderColor.setAlpha(50);
914 painter.fillRect(sliderRect, sliderColor);
915 painter.setPen(QPen(highlightColor, 0));
916 m_sliderRect = sliderRect;
917 // rounded rect looks ugly for some reason, so we draw 4 lines.
918 painter.drawLine(sliderRect.left(), sliderRect.top() + 1, sliderRect.left(), sliderRect.bottom() - 1);
919 painter.drawLine(sliderRect.right(), sliderRect.top() + 1, sliderRect.right(), sliderRect.bottom() - 1);
920 painter.drawLine(sliderRect.left() + 1, sliderRect.top(), sliderRect.right() - 1, sliderRect.top());
921 painter.drawLine(sliderRect.left() + 1, sliderRect.bottom(), sliderRect.right() - 1, sliderRect.bottom());
922}
923
924void KateScrollBar::normalPaintEvent(QPaintEvent *e)
925{
927
928 if (!m_showMarks) {
929 return;
930 }
931
932 QPainter painter(this);
933
935 opt.initFrom(this);
936 opt.subControls = QStyle::SC_None;
937 opt.activeSubControls = QStyle::SC_None;
938 opt.orientation = orientation();
939 opt.minimum = minimum();
940 opt.maximum = maximum();
941 opt.sliderPosition = sliderPosition();
942 opt.sliderValue = value();
943 opt.singleStep = singleStep();
944 opt.pageStep = pageStep();
945
947 int sideMargin = width() - rect.width();
948 if (sideMargin < 4) {
949 sideMargin = 4;
950 }
951 sideMargin /= 2;
952
953 QHashIterator<int, QColor> it = m_lines;
954 while (it.hasNext()) {
955 it.next();
956 painter.setPen(it.value());
957 if (it.key() < rect.top() || it.key() > rect.bottom()) {
958 painter.drawLine(0, it.key(), width(), it.key());
959 } else {
960 painter.drawLine(0, it.key(), sideMargin, it.key());
961 painter.drawLine(width() - sideMargin, it.key(), width(), it.key());
962 }
963 }
964}
965
966void KateScrollBar::resizeEvent(QResizeEvent *e)
967{
969 m_updateTimer.start();
970 m_lines.clear();
971 update();
972}
973
974void KateScrollBar::sliderChange(SliderChange change)
975{
976 // call parents implementation
978
980 redrawMarks();
981 } else if (change == QAbstractSlider::SliderRangeChange) {
982 marksChanged();
983 }
984
985 if (m_leftMouseDown || m_middleMouseDown) {
986 const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
987 const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
988 QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
989 }
990}
991
992void KateScrollBar::marksChanged()
993{
994 m_lines.clear();
995 update();
996}
997
998void KateScrollBar::redrawMarks()
999{
1000 if (!m_showMarks) {
1001 return;
1002 }
1003 update();
1004}
1005
1006void KateScrollBar::recomputeMarksPositions()
1007{
1008 // get the style options to compute the scrollbar pixels
1010 initStyleOption(&opt);
1012
1013 // cache top margin and groove height
1014 const int top = grooveRect.top();
1015 const int h = grooveRect.height() - 1;
1016
1017 // make sure we have a sane height
1018 if (h <= 0) {
1019 return;
1020 }
1021
1022 // get total visible (=without folded) lines in the document
1023 int visibleLines = m_view->textFolding().visibleLines() - 1;
1024 if (m_view->config()->scrollPastEnd()) {
1025 visibleLines += m_viewInternal->linesDisplayed() - 1;
1026 visibleLines -= m_view->config()->autoCenterLines();
1027 }
1028
1029 const QColor searchMatchColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground().color();
1030
1031 // now repopulate the scrollbar lines list
1032 m_lines.clear();
1033 const QHash<int, KTextEditor::Mark *> &marks = m_doc->marks();
1034 for (QHash<int, KTextEditor::Mark *>::const_iterator i = marks.constBegin(); i != marks.constEnd(); ++i) {
1035 KTextEditor::Mark *mark = i.value();
1036 const int line = m_view->textFolding().lineToVisibleLine(mark->line);
1037 const double ratio = static_cast<double>(line) / visibleLines;
1038 const QColor markColor = mark->type == KTextEditor::Document::SearchMatch
1039 ? searchMatchColor
1040 : m_view->rendererConfig()->lineMarkerColor((KTextEditor::Document::MarkTypes)mark->type);
1041 m_lines.insert(top + (int)(h * ratio), markColor);
1042 }
1043}
1044
1045void KateScrollBar::sliderMaybeMoved(int value)
1046{
1047 if (m_middleMouseDown) {
1048 // we only need to emit this signal once, as for the following slider
1049 // movements the signal sliderMoved() is already emitted.
1050 // Thus, set m_middleMouseDown to false right away.
1051 m_middleMouseDown = false;
1052 Q_EMIT sliderMMBMoved(value);
1053 }
1054}
1055// END
1056
1057// BEGIN KateCmdLineEditFlagCompletion
1058/**
1059 * This class provide completion of flags. It shows a short description of
1060 * each flag, and flags are appended.
1061 */
1062class KateCmdLineEditFlagCompletion : public KCompletion
1063{
1064public:
1065 KateCmdLineEditFlagCompletion()
1066 {
1067 ;
1068 }
1069
1070 QString makeCompletion(const QString & /*s*/) override
1071 {
1072 return QString();
1073 }
1074};
1075// END KateCmdLineEditFlagCompletion
1076
1077// BEGIN KateCmdLineEdit
1078KateCommandLineBar::KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent)
1079 : KateViewBarWidget(true, parent)
1080{
1081 QHBoxLayout *topLayout = new QHBoxLayout(centralWidget());
1082 topLayout->setContentsMargins(0, 0, 0, 0);
1083 m_lineEdit = new KateCmdLineEdit(this, view);
1084 connect(m_lineEdit, &KateCmdLineEdit::hideRequested, this, &KateCommandLineBar::hideMe);
1085 topLayout->addWidget(m_lineEdit);
1086
1087 QToolButton *helpButton = new QToolButton(this);
1088 helpButton->setAutoRaise(true);
1089 helpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
1090 topLayout->addWidget(helpButton);
1091 connect(helpButton, &QToolButton::clicked, this, &KateCommandLineBar::showHelpPage);
1092
1093 setFocusProxy(m_lineEdit);
1094}
1095
1096void KateCommandLineBar::showHelpPage()
1097{
1098 KHelpClient::invokeHelp(QStringLiteral("advanced-editing-tools-commandline"), QStringLiteral("katepart"));
1099}
1100
1101KateCommandLineBar::~KateCommandLineBar() = default;
1102
1103// inserts the given string in the command line edit and (if selected = true) selects it so the user
1104// can type over it if they want to
1105void KateCommandLineBar::setText(const QString &text, bool selected)
1106{
1107 m_lineEdit->setText(text);
1108 if (selected) {
1109 m_lineEdit->selectAll();
1110 }
1111}
1112
1113void KateCommandLineBar::execute(const QString &text)
1114{
1115 m_lineEdit->slotReturnPressed(text);
1116}
1117
1118KateCmdLineEdit::KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view)
1119 : KLineEdit()
1120 , m_view(view)
1121 , m_bar(bar)
1122 , m_msgMode(false)
1123 , m_histpos(0)
1124 , m_cmdend(0)
1125 , m_command(nullptr)
1126{
1127 connect(this, &KateCmdLineEdit::returnKeyPressed, this, &KateCmdLineEdit::slotReturnPressed);
1128
1129 setCompletionObject(KateCmd::self()->commandCompletionObject());
1130 setAutoDeleteCompletionObject(false);
1131
1132 m_hideTimer = new QTimer(this);
1133 m_hideTimer->setSingleShot(true);
1134 connect(m_hideTimer, &QTimer::timeout, this, &KateCmdLineEdit::hideLineEdit);
1135
1136 // make sure the timer is stopped when the user switches views. if not, focus will be given to the
1137 // wrong view when KateViewBar::hideCurrentBarWidget() is called after 4 seconds. (the timer is
1138 // used for showing things like "Success" for four seconds after the user has used the kate
1139 // command line)
1141}
1142
1143void KateCmdLineEdit::hideEvent(QHideEvent *e)
1144{
1145 Q_UNUSED(e);
1146}
1147
1148QString KateCmdLineEdit::helptext(const QPoint &) const
1149{
1150 const QString beg = QStringLiteral("<qt background=\"white\"><div><table width=\"100%\"><tr><td bgcolor=\"brown\"><font color=\"white\"><b>Help: <big>");
1151 const QString mid = QStringLiteral("</big></b></font></td></tr><tr><td>");
1152 const QString end = QStringLiteral("</td></tr></table></div><qt>");
1153
1154 const QString t = text();
1155 static const QRegularExpression re(QStringLiteral("\\s*help\\s+(.*)"));
1156 auto match = re.match(t);
1157 if (match.hasMatch()) {
1158 QString s;
1159 // get help for command
1160 const QString name = match.captured(1);
1161 if (name == QLatin1String("list")) {
1162 return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(QLatin1Char(' '))
1163 + i18n("<p>For help on individual commands, do <code>'help &lt;command&gt;'</code></p>") + end;
1164 } else if (!name.isEmpty()) {
1165 KTextEditor::Command *cmd = KateCmd::self()->queryCommand(name);
1166 if (cmd) {
1167 if (cmd->help(m_view, name, s)) {
1168 return beg + name + mid + s + end;
1169 } else {
1170 return beg + name + mid + i18n("No help for '%1'", name) + end;
1171 }
1172 } else {
1173 return beg + mid + i18n("No such command <b>%1</b>", name) + end;
1174 }
1175 }
1176 }
1177
1178 return beg + mid
1179 + i18n("<p>This is the Katepart <b>command line</b>.<br />"
1180 "Syntax: <code><b>command [ arguments ]</b></code><br />"
1181 "For a list of available commands, enter <code><b>help list</b></code><br />"
1182 "For help for individual commands, enter <code><b>help &lt;command&gt;</b></code></p>")
1183 + end;
1184}
1185
1186bool KateCmdLineEdit::event(QEvent *e)
1187{
1188 if (e->type() == QEvent::QueryWhatsThis) {
1189 setWhatsThis(helptext(QPoint()));
1190 e->accept();
1191 return true;
1192 }
1193 return KLineEdit::event(e);
1194}
1195
1196/**
1197 * Parse the text as a command.
1198 *
1199 * The following is a simple PEG grammar for the syntax of the command.
1200 * (A PEG grammar is like a BNF grammar, except that "/" stands for
1201 * ordered choice: only the first matching rule is used. In other words,
1202 * the parsing is short-circuited in the manner of the "or" operator in
1203 * programming languages, and so the grammar is unambiguous.)
1204 *
1205 * Text <- Range? Command
1206 * / Position
1207 * Range <- Position ("," Position)?
1208 * / "%"
1209 * Position <- Base Offset?
1210 * Base <- Line
1211 * / LastLine
1212 * / ThisLine
1213 * / Mark
1214 * Offset <- [+-] Base
1215 * Line <- [0-9]+
1216 * LastLine <- "$"
1217 * ThisLine <- "."
1218 * Mark <- "'" [a-z]
1219 */
1220
1221void KateCmdLineEdit::slotReturnPressed(const QString &text)
1222{
1223 static const QRegularExpression focusChangingCommands(QStringLiteral("^(?:buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e)$"));
1224
1225 if (text.isEmpty()) {
1226 return;
1227 }
1228 // silently ignore leading space characters
1229 uint n = 0;
1230 const uint textlen = text.length();
1231 while ((n < textlen) && (text[n].isSpace())) {
1232 n++;
1233 }
1234
1235 if (n >= textlen) {
1236 return;
1237 }
1238
1239 QString cmd = text.mid(n);
1240
1241 // Parse any leading range expression, and strip it (and maybe do some other transforms on the command).
1242 QString leadingRangeExpression;
1243 KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd);
1244
1245 // Built in help: if the command starts with "help", [try to] show some help
1246 if (cmd.startsWith(QLatin1String("help"))) {
1247 QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint()));
1248 clear();
1249 KateCmd::self()->appendHistory(cmd);
1250 m_histpos = KateCmd::self()->historyLength();
1251 m_oldText.clear();
1252 return;
1253 }
1254
1255 if (cmd.length() > 0) {
1256 KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd);
1257
1258 m_oldText = leadingRangeExpression + cmd;
1259 m_msgMode = true;
1260
1261 // if the command changes the focus itself, the bar should be hidden before execution.
1262 if (focusChangingCommands.matchView(QStringView(cmd).left(cmd.indexOf(QLatin1Char(' ')))).hasMatch()) {
1263 Q_EMIT hideRequested();
1264 }
1265
1266 if (!p) {
1267 setText(i18n("No such command: \"%1\"", cmd));
1268 } else if (range.isValid() && !p->supportsRange(cmd)) {
1269 // Raise message, when the command does not support ranges.
1270 setText(i18n("Error: No range allowed for command \"%1\".", cmd));
1271 } else {
1272 QString msg;
1273 if (p->exec(m_view, cmd, msg, range)) {
1274 // append command along with range (will be empty if none given) to history
1275 KateCmd::self()->appendHistory(leadingRangeExpression + cmd);
1276 m_histpos = KateCmd::self()->historyLength();
1277 m_oldText.clear();
1278
1279 if (msg.length() > 0) {
1280 setText(i18n("Success: ") + msg);
1281 } else if (isVisible()) {
1282 // always hide on success without message
1283 Q_EMIT hideRequested();
1284 }
1285 } else {
1286 if (msg.length() > 0) {
1287 if (msg.contains(QLatin1Char('\n'))) {
1288 // multiline error, use widget with more space
1290 } else {
1291 setText(msg);
1292 }
1293 } else {
1294 setText(i18n("Command \"%1\" failed.", cmd));
1295 }
1296 }
1297 }
1298 }
1299
1300 // clean up
1301 if (completionObject() != KateCmd::self()->commandCompletionObject()) {
1303 setCompletionObject(KateCmd::self()->commandCompletionObject());
1304 delete c;
1305 }
1306 m_command = nullptr;
1307 m_cmdend = 0;
1308
1309 if (!focusChangingCommands.matchView(QStringView(cmd).left(cmd.indexOf(QLatin1Char(' ')))).hasMatch()) {
1310 m_view->setFocus();
1311 }
1312
1313 if (isVisible()) {
1314 m_hideTimer->start(4000);
1315 }
1316}
1317
1318void KateCmdLineEdit::hideLineEdit() // unless i have focus ;)
1319{
1320 if (!hasFocus()) {
1321 Q_EMIT hideRequested();
1322 }
1323}
1324
1325void KateCmdLineEdit::focusInEvent(QFocusEvent *ev)
1326{
1327 if (m_msgMode) {
1328 m_msgMode = false;
1329 setText(m_oldText);
1330 selectAll();
1331 }
1332
1334}
1335
1336void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev)
1337{
1338 if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) {
1339 m_view->setFocus();
1340 hideLineEdit();
1341 clear();
1342 } else if (ev->key() == Qt::Key_Up) {
1343 fromHistory(true);
1344 } else if (ev->key() == Qt::Key_Down) {
1345 fromHistory(false);
1346 }
1347
1348 uint cursorpos = cursorPosition();
1350
1351 // during typing, let us see if we have a valid command
1352 if (!m_cmdend || cursorpos <= m_cmdend) {
1353 QChar c;
1354 if (!ev->text().isEmpty()) {
1355 c = ev->text().at(0);
1356 }
1357
1358 if (!m_cmdend && !c.isNull()) { // we have no command, so lets see if we got one
1359 if (!c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) {
1360 m_command = KateCmd::self()->queryCommand(text().trimmed());
1361 if (m_command) {
1362 // qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<<m_command<<". text is '"<<text()<<"'";
1363 // if the typed character is ":",
1364 // we try if the command has flag completions
1365 m_cmdend = cursorpos;
1366 // qCDebug(LOG_KTE)<<"keypress in commandline: Set m_cmdend to "<<m_cmdend;
1367 } else {
1368 m_cmdend = 0;
1369 }
1370 }
1371 } else { // since cursor is inside the command name, we reconsider it
1372 // qCDebug(LOG_KTE) << "keypress in commandline: \\W -- text is " << text();
1373 m_command = KateCmd::self()->queryCommand(text().trimmed());
1374 if (m_command) {
1375 // qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<<m_command;
1376 QString t = text();
1377 m_cmdend = 0;
1378 bool b = false;
1379 for (; (int)m_cmdend < t.length(); m_cmdend++) {
1380 if (t[m_cmdend].isLetter()) {
1381 b = true;
1382 }
1383 if (b && (!t[m_cmdend].isLetterOrNumber() && t[m_cmdend] != QLatin1Char('-') && t[m_cmdend] != QLatin1Char('_'))) {
1384 break;
1385 }
1386 }
1387
1388 if (c == QLatin1Char(':') && cursorpos == m_cmdend) {
1389 // check if this command wants to complete flags
1390 // qCDebug(LOG_KTE)<<"keypress in commandline: Checking if flag completion is desired!";
1391 }
1392 } else {
1393 // clean up if needed
1394 if (completionObject() != KateCmd::self()->commandCompletionObject()) {
1396 setCompletionObject(KateCmd::self()->commandCompletionObject());
1397 delete c;
1398 }
1399
1400 m_cmdend = 0;
1401 }
1402 }
1403
1404 // if we got a command, check if it wants to do something.
1405 if (m_command) {
1406 KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed());
1407 if (cmpl) {
1408 // We need to prepend the current command name + flag string
1409 // when completion is done
1410 // qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!";
1411
1412 setCompletionObject(cmpl);
1413 }
1414 }
1415 } else if (m_command && !ev->text().isEmpty()) {
1416 // check if we should call the commands processText()
1417 if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) {
1418 m_command->processText(m_view, text());
1419 }
1420 }
1421}
1422
1423void KateCmdLineEdit::fromHistory(bool up)
1424{
1425 if (!KateCmd::self()->historyLength()) {
1426 return;
1427 }
1428
1429 QString s;
1430
1431 if (up) {
1432 if (m_histpos > 0) {
1433 m_histpos--;
1434 s = KateCmd::self()->fromHistory(m_histpos);
1435 }
1436 } else {
1437 if (m_histpos < (KateCmd::self()->historyLength() - 1)) {
1438 m_histpos++;
1439 s = KateCmd::self()->fromHistory(m_histpos);
1440 } else {
1441 m_histpos = KateCmd::self()->historyLength();
1442 setText(m_oldText);
1443 }
1444 }
1445 if (!s.isEmpty()) {
1446 // Select the argument part of the command, so that it is easy to overwrite
1447 setText(s);
1448 static const QRegularExpression reCmd(QStringLiteral("^[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)"), QRegularExpression::UseUnicodePropertiesOption);
1449 const auto match = reCmd.match(text());
1450 if (match.hasMatch()) {
1451 setSelection(text().length() - match.capturedLength(1), match.capturedLength(1));
1452 }
1453 }
1454}
1455// END KateCmdLineEdit
1456
1457// BEGIN KateIconBorder
1458using namespace KTextEditor;
1459
1460KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent)
1461 : QWidget(parent)
1462 , m_view(internalView->m_view)
1463 , m_doc(internalView->doc())
1464 , m_viewInternal(internalView)
1465 , m_iconBorderOn(false)
1466 , m_lineNumbersOn(false)
1467 , m_relLineNumbersOn(false)
1468 , m_updateRelLineNumbers(false)
1469 , m_foldingMarkersOn(false)
1470 , m_dynWrapIndicatorsOn(false)
1471 , m_annotationBorderOn(false)
1472 , m_updatePositionToArea(true)
1473 , m_annotationItemDelegate(new KateAnnotationItemDelegate(this))
1474{
1475 setAcceptDrops(true);
1476 setAttribute(Qt::WA_StaticContents);
1477
1478 // See: https://doc.qt.io/qt-5/qwidget.html#update. As this widget does not
1479 // have a background, there's no need for Qt to erase the widget's area
1480 // before repainting. Enabling this prevents flickering when the widget is
1481 // repainted.
1482 setAttribute(Qt::WA_OpaquePaintEvent);
1483
1485 setMouseTracking(true);
1486 m_doc->setMarkDescription(Document::markType01, i18n("Bookmark"));
1487 m_doc->setMarkIcon(Document::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")));
1488
1489 connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
1490
1491 updateFont();
1492
1493 m_antiFlickerTimer.setSingleShot(true);
1494 m_antiFlickerTimer.setInterval(300);
1495 connect(&m_antiFlickerTimer, &QTimer::timeout, this, &KateIconBorder::highlightFolding);
1496
1497 // user interaction (scrolling) hides e.g. preview
1498 connect(m_view, &KTextEditor::ViewPrivate::displayRangeChanged, this, &KateIconBorder::displayRangeChanged);
1499}
1500
1501KateIconBorder::~KateIconBorder()
1502{
1503 delete m_foldingPreview;
1504 delete m_foldingRange;
1505}
1506
1507void KateIconBorder::setIconBorderOn(bool enable)
1508{
1509 if (enable == m_iconBorderOn) {
1510 return;
1511 }
1512
1513 m_iconBorderOn = enable;
1514
1515 m_updatePositionToArea = true;
1516
1517 QTimer::singleShot(0, this, SLOT(update()));
1518}
1519
1520void KateIconBorder::setAnnotationBorderOn(bool enable)
1521{
1522 if (enable == m_annotationBorderOn) {
1523 return;
1524 }
1525
1526 m_annotationBorderOn = enable;
1527
1528 // make sure the tooltip is hidden
1529 if (!m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
1530 m_hoveredAnnotationGroupIdentifier.clear();
1531 hideAnnotationTooltip();
1532 }
1533
1534 Q_EMIT m_view->annotationBorderVisibilityChanged(m_view, enable);
1535
1536 m_updatePositionToArea = true;
1537
1538 QTimer::singleShot(0, this, SLOT(update()));
1539}
1540
1541void KateIconBorder::removeAnnotationHovering()
1542{
1543 // remove hovering if it's still there
1544 if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
1545 m_hoveredAnnotationGroupIdentifier.clear();
1546 QTimer::singleShot(0, this, SLOT(update()));
1547 }
1548}
1549
1550void KateIconBorder::setLineNumbersOn(bool enable)
1551{
1552 if (enable == m_lineNumbersOn) {
1553 return;
1554 }
1555
1556 m_lineNumbersOn = enable;
1557 m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators;
1558
1559 m_updatePositionToArea = true;
1560
1561 QTimer::singleShot(0, this, SLOT(update()));
1562}
1563
1564void KateIconBorder::setRelLineNumbersOn(bool enable)
1565{
1566 if (enable == m_relLineNumbersOn) {
1567 return;
1568 }
1569
1570 m_relLineNumbersOn = enable;
1571 /*
1572 * We don't have to touch the m_dynWrapIndicatorsOn because
1573 * we already got it right from the m_lineNumbersOn
1574 */
1575 m_updatePositionToArea = true;
1576
1577 QTimer::singleShot(0, this, SLOT(update()));
1578}
1579
1580void KateIconBorder::updateForCursorLineChange()
1581{
1582 if (m_relLineNumbersOn) {
1583 m_updateRelLineNumbers = true;
1584 }
1585
1586 // always do normal update, e.g. for different current line color!
1587 update();
1588}
1589
1590void KateIconBorder::setDynWrapIndicators(int state)
1591{
1592 if (state == m_dynWrapIndicators) {
1593 return;
1594 }
1595
1596 m_dynWrapIndicators = state;
1597 m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state;
1598
1599 m_updatePositionToArea = true;
1600
1601 QTimer::singleShot(0, this, SLOT(update()));
1602}
1603
1604void KateIconBorder::setFoldingMarkersOn(bool enable)
1605{
1606 if (enable == m_foldingMarkersOn) {
1607 return;
1608 }
1609
1610 m_foldingMarkersOn = enable;
1611
1612 m_updatePositionToArea = true;
1613
1614 QTimer::singleShot(0, this, SLOT(update()));
1615}
1616
1617QSize KateIconBorder::sizeHint() const
1618{
1619 int w = 1; // Must be any value != 0 or we will never painted!
1620
1621 const int i = m_positionToArea.size();
1622 if (i > 0) {
1623 w = m_positionToArea.at(i - 1).first;
1624 }
1625
1626 return QSize(w, 0);
1627}
1628
1629// This function (re)calculates the maximum width of any of the digit characters (0 -> 9)
1630// for graceful handling of variable-width fonts as the linenumber font.
1631void KateIconBorder::updateFont()
1632{
1633 // Loop to determine the widest numeric character in the current font.
1634 const QFontMetricsF &fm = m_view->renderer()->currentFontMetrics();
1635 m_maxCharWidth = 0.0;
1636 for (char c = '0'; c <= '9'; ++c) {
1637 const qreal charWidth = ceil(fm.horizontalAdvance(QLatin1Char(c)));
1638 m_maxCharWidth = qMax(m_maxCharWidth, charWidth);
1639 }
1640
1641 // NOTE/TODO(or not) Take size of m_dynWrapIndicatorChar into account.
1642 // It's a multi-char and it's reported size is, even with a mono-space font,
1643 // bigger than each digit, e.g. 10 vs 12. Currently it seems to work even with
1644 // "Line Numbers Off" but all these width calculating looks slightly hacky
1645
1646 // the icon pane scales with the font...
1647 m_iconAreaWidth = fm.height();
1648
1649 // Only for now, later may that become an own value
1650 m_foldingAreaWidth = m_iconAreaWidth;
1651
1652 calcAnnotationBorderWidth();
1653
1654 m_updatePositionToArea = true;
1655
1657 this,
1658 [this] {
1659 update();
1660 },
1662}
1663
1664int KateIconBorder::lineNumberWidth() const
1665{
1666 int width = 0;
1667 // Avoid unneeded expensive calculations ;-)
1668 if (m_lineNumbersOn) {
1669 // width = (number of digits + 1) * char width
1670 const int digits = (int)ceil(log10((double)(m_view->doc()->lines() + 1)));
1671 width = (int)ceil((digits + 1) * m_maxCharWidth);
1672 }
1673
1674 if ((width < 1) && m_dynWrapIndicatorsOn && m_view->config()->dynWordWrap()) {
1675 // FIXME Why 2x? because of above (number of digits + 1)
1676 // -> looks to me like a hint for bad calculation elsewhere
1677 width = 2 * m_maxCharWidth;
1678 }
1679
1680 return width;
1681}
1682
1683void KateIconBorder::dragEnterEvent(QDragEnterEvent *event)
1684{
1685 m_view->m_viewInternal->dragEnterEvent(event);
1686}
1687
1688void KateIconBorder::dragMoveEvent(QDragMoveEvent *event)
1689{
1690 // FIXME Just calling m_view->m_viewInternal->dragMoveEvent(e) don't work
1691 // as intended, we need to set the cursor at column 1
1692 // Is there a way to change the pos of the event?
1693 QPoint pos(0, event->position().y());
1694 // Code copy of KateViewInternal::dragMoveEvent
1695 m_view->m_viewInternal->placeCursor(pos, true, false);
1696 m_view->m_viewInternal->fixDropEvent(event);
1697}
1698
1699void KateIconBorder::dropEvent(QDropEvent *event)
1700{
1701 m_view->m_viewInternal->dropEvent(event);
1702}
1703
1704void KateIconBorder::paintEvent(QPaintEvent *e)
1705{
1706 paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height());
1707}
1708
1709static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open)
1710{
1712
1713 qreal size = qMin(width, height);
1714
1715 if (open) {
1716 // Paint unfolded icon less pushy
1717 if (KColorUtils::luma(c) < 0.25) {
1718 c = KColorUtils::darken(c);
1719 } else {
1720 c = KColorUtils::shade(c, 0.1);
1721 }
1722
1723 } else {
1724 // Paint folded icon in contrast to popup highlighting
1725 if (KColorUtils::luma(c) > 0.25) {
1726 c = KColorUtils::darken(c);
1727 } else {
1728 c = KColorUtils::shade(c, 0.1);
1729 }
1730 }
1731
1732 QPen pen;
1734 pen.setColor(c);
1735 pen.setWidthF(1.5);
1736 painter.setPen(pen);
1737 painter.setBrush(c);
1738
1739 // let some border, if possible
1740 size *= 0.6;
1741
1742 qreal halfSize = size / 2;
1743 qreal halfSizeP = halfSize * 0.6;
1744 QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2);
1745
1746 if (open) {
1747 QPointF points[3] = {middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP)};
1748 painter.drawConvexPolygon(points, 3);
1749 } else {
1750 QPointF points[3] = {middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0)};
1751 painter.drawConvexPolygon(points, 3);
1752 }
1753
1754 painter.setRenderHint(QPainter::Antialiasing, false);
1755}
1756
1757/**
1758 * Helper class for an identifier which can be an empty or non-empty string or invalid.
1759 * Avoids complicated explicit statements in code dealing with the identifier
1760 * received as QVariant from a model.
1761 */
1762class KateAnnotationGroupIdentifier
1763{
1764public:
1765 KateAnnotationGroupIdentifier(const QVariant &identifier)
1766 : m_isValid(identifier.isValid() && identifier.canConvert<QString>())
1767 , m_id(m_isValid ? identifier.toString() : QString())
1768 {
1769 }
1770 KateAnnotationGroupIdentifier() = default;
1771 KateAnnotationGroupIdentifier(const KateAnnotationGroupIdentifier &rhs) = default;
1772
1773 KateAnnotationGroupIdentifier &operator=(const KateAnnotationGroupIdentifier &rhs)
1774 {
1775 m_isValid = rhs.m_isValid;
1776 m_id = rhs.m_id;
1777 return *this;
1778 }
1779 KateAnnotationGroupIdentifier &operator=(const QVariant &identifier)
1780 {
1781 m_isValid = (identifier.isValid() && identifier.canConvert<QString>());
1782 if (m_isValid) {
1783 m_id = identifier.toString();
1784 } else {
1785 m_id.clear();
1786 }
1787 return *this;
1788 }
1789
1790 bool operator==(const KateAnnotationGroupIdentifier &rhs) const
1791 {
1792 return (m_isValid == rhs.m_isValid) && (!m_isValid || (m_id == rhs.m_id));
1793 }
1794 bool operator!=(const KateAnnotationGroupIdentifier &rhs) const
1795 {
1796 return (m_isValid != rhs.m_isValid) || (m_isValid && (m_id != rhs.m_id));
1797 }
1798
1799 void clear()
1800 {
1801 m_isValid = false;
1802 m_id.clear();
1803 }
1804 bool isValid() const
1805 {
1806 return m_isValid;
1807 }
1808 const QString &id() const
1809 {
1810 return m_id;
1811 }
1812
1813private:
1814 bool m_isValid = false;
1815 QString m_id;
1816};
1817
1818/**
1819 * Helper class for iterative calculation of data regarding the position
1820 * of a line with regard to annotation item grouping.
1821 */
1822class KateAnnotationGroupPositionState
1823{
1824public:
1825 /**
1826 * @param startz first rendered displayed line
1827 * @param isUsed flag whether the KateAnnotationGroupPositionState object will
1828 * be used or is just created due to being on the stack
1829 */
1830 KateAnnotationGroupPositionState(KateViewInternal *viewInternal,
1831 const KTextEditor::AnnotationModel *model,
1832 const QString &hoveredAnnotationGroupIdentifier,
1833 uint startz,
1834 bool isUsed);
1835 /**
1836 * @param styleOption option to fill with data for the given line
1837 * @param z rendered displayed line
1838 * @param realLine real line which is rendered here (passed to avoid another look-up)
1839 */
1840 void nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine);
1841
1842private:
1843 KateViewInternal *m_viewInternal;
1844 const KTextEditor::AnnotationModel *const m_model;
1845 const QString m_hoveredAnnotationGroupIdentifier;
1846
1847 int m_visibleWrappedLineInAnnotationGroup = -1;
1848 KateAnnotationGroupIdentifier m_lastAnnotationGroupIdentifier;
1849 KateAnnotationGroupIdentifier m_nextAnnotationGroupIdentifier;
1850 bool m_isSameAnnotationGroupsSinceLast = false;
1851};
1852
1853KateAnnotationGroupPositionState::KateAnnotationGroupPositionState(KateViewInternal *viewInternal,
1854 const KTextEditor::AnnotationModel *model,
1855 const QString &hoveredAnnotationGroupIdentifier,
1856 uint startz,
1857 bool isUsed)
1858 : m_viewInternal(viewInternal)
1859 , m_model(model)
1860 , m_hoveredAnnotationGroupIdentifier(hoveredAnnotationGroupIdentifier)
1861{
1862 if (!isUsed) {
1863 return;
1864 }
1865
1866 if (!m_model || (static_cast<int>(startz) >= m_viewInternal->cache()->viewCacheLineCount())) {
1867 return;
1868 }
1869
1870 const auto realLineAtStart = m_viewInternal->cache()->viewLine(startz).line();
1871 m_nextAnnotationGroupIdentifier = m_model->data(realLineAtStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1872 if (m_nextAnnotationGroupIdentifier.isValid()) {
1873 // estimate state of annotation group before first rendered line
1874 if (startz == 0) {
1875 if (realLineAtStart > 0) {
1876 // TODO: here we would want to scan until the next line that would be displayed,
1877 // to see if there are any group changes until then
1878 // for now simply taking neighbour line into account, not a grave bug on the first displayed line
1879 m_lastAnnotationGroupIdentifier = m_model->data(realLineAtStart - 1, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1880 m_isSameAnnotationGroupsSinceLast = (m_lastAnnotationGroupIdentifier == m_nextAnnotationGroupIdentifier);
1881 }
1882 } else {
1883 const auto realLineBeforeStart = m_viewInternal->cache()->viewLine(startz - 1).line();
1884 m_lastAnnotationGroupIdentifier = m_model->data(realLineBeforeStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1885 if (m_lastAnnotationGroupIdentifier.isValid()) {
1886 if (m_lastAnnotationGroupIdentifier.id() == m_nextAnnotationGroupIdentifier.id()) {
1887 m_isSameAnnotationGroupsSinceLast = true;
1888 // estimate m_visibleWrappedLineInAnnotationGroup from lines before startz
1889 for (uint z = startz; z > 0; --z) {
1890 const auto realLine = m_viewInternal->cache()->viewLine(z - 1).line();
1891 const KateAnnotationGroupIdentifier identifier =
1892 m_model->data(realLine, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1893 if (identifier != m_lastAnnotationGroupIdentifier) {
1894 break;
1895 }
1896 ++m_visibleWrappedLineInAnnotationGroup;
1897 }
1898 }
1899 }
1900 }
1901 }
1902}
1903
1904void KateAnnotationGroupPositionState::nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine)
1905{
1906 styleOption.wrappedLine = m_viewInternal->cache()->viewLine(z).viewLine();
1907 styleOption.wrappedLineCount = m_viewInternal->cache()->viewLineCount(realLine);
1908
1909 // Estimate position in group
1910 const KateAnnotationGroupIdentifier annotationGroupIdentifier = m_nextAnnotationGroupIdentifier;
1911 bool isSameAnnotationGroupsSinceThis = false;
1912 // Calculate next line's group identifier
1913 // shortcut: assuming wrapped lines are always displayed together, test is simple
1914 if (styleOption.wrappedLine + 1 < styleOption.wrappedLineCount) {
1915 m_nextAnnotationGroupIdentifier = annotationGroupIdentifier;
1916 isSameAnnotationGroupsSinceThis = true;
1917 } else {
1918 if (static_cast<int>(z + 1) < m_viewInternal->cache()->viewCacheLineCount()) {
1919 const int realLineAfter = m_viewInternal->cache()->viewLine(z + 1).line();
1920 // search for any realLine with a different group id, also the non-displayed
1921 int rl = realLine + 1;
1922 for (; rl <= realLineAfter; ++rl) {
1923 m_nextAnnotationGroupIdentifier = m_model->data(rl, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1924 if (!m_nextAnnotationGroupIdentifier.isValid() || (m_nextAnnotationGroupIdentifier.id() != annotationGroupIdentifier.id())) {
1925 break;
1926 }
1927 }
1928 isSameAnnotationGroupsSinceThis = (rl > realLineAfter);
1929 if (rl < realLineAfter) {
1930 m_nextAnnotationGroupIdentifier = m_model->data(realLineAfter, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1931 }
1932 } else {
1933 // TODO: check next line after display end
1934 m_nextAnnotationGroupIdentifier.clear();
1935 isSameAnnotationGroupsSinceThis = false;
1936 }
1937 }
1938
1939 if (annotationGroupIdentifier.isValid()) {
1940 if (m_hoveredAnnotationGroupIdentifier == annotationGroupIdentifier.id()) {
1941 styleOption.state |= QStyle::State_MouseOver;
1942 } else {
1943 styleOption.state &= ~QStyle::State_MouseOver;
1944 }
1945
1946 if (m_isSameAnnotationGroupsSinceLast) {
1947 ++m_visibleWrappedLineInAnnotationGroup;
1948 } else {
1949 m_visibleWrappedLineInAnnotationGroup = 0;
1950 }
1951
1953 if (!m_isSameAnnotationGroupsSinceLast) {
1955 }
1956 if (!isSameAnnotationGroupsSinceThis) {
1958 }
1959 } else {
1960 m_visibleWrappedLineInAnnotationGroup = 0;
1961 }
1962 styleOption.visibleWrappedLineInGroup = m_visibleWrappedLineInAnnotationGroup;
1963
1964 m_lastAnnotationGroupIdentifier = m_nextAnnotationGroupIdentifier;
1965 m_isSameAnnotationGroupsSinceLast = isSameAnnotationGroupsSinceThis;
1966}
1967
1968void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height)
1969{
1970 const uint h = m_view->renderer()->lineHeight();
1971 const uint startz = (y / h);
1972 const uint endz = qMin(startz + 1 + (height / h), static_cast<uint>(m_viewInternal->cache()->viewCacheLineCount()));
1973 const uint currentLine = m_view->cursorPosition().line();
1974
1975 // center the folding boxes
1976 int m_px = (h - 11) / 2;
1977 if (m_px < 0) {
1978 m_px = 0;
1979 }
1980
1981 // Ensure we miss no change of the count of line number digits
1982 const int newNeededWidth = lineNumberWidth();
1983
1984 if (m_updatePositionToArea || (newNeededWidth != m_lineNumberAreaWidth)) {
1985 m_lineNumberAreaWidth = newNeededWidth;
1986 m_updatePositionToArea = true;
1987 m_positionToArea.clear();
1988 }
1989
1990 QPainter p(this);
1991 p.setRenderHints(QPainter::TextAntialiasing);
1992 p.setFont(m_view->renderer()->currentFont()); // for line numbers
1993
1994 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
1995 KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, m_hoveredAnnotationGroupIdentifier, startz, m_annotationBorderOn);
1996
1997 // Fetch often used data only once, improve readability
1998 const int w = width();
1999 const QColor iconBarColor = m_view->rendererConfig()->iconBarColor(); // Effective our background
2000 const QColor lineNumberColor = m_view->rendererConfig()->lineNumberColor();
2001 const QColor backgroundColor = m_view->rendererConfig()->backgroundColor(); // Of the edit area
2002 const QColor currentLineHighlight = m_view->rendererConfig()->highlightedLineColor(); // Of the edit area
2003
2004 // Paint the border in chunks line by line
2005 for (uint z = startz; z < endz; z++) {
2006 // Painting coordinates, lineHeight * lineNumber
2007 const uint y = h * z;
2008
2009 // Paint the border in chunks left->right, remember used width
2010 uint lnX = 0;
2011
2012 // get line for this coordinates if possible
2013 const KateTextLayout lineLayout = m_viewInternal->cache()->viewLine(z);
2014 const int realLine = lineLayout.line();
2015
2016 // Paint background over full width
2017 p.fillRect(lnX, y, w, h, iconBarColor);
2018
2019 // overpaint with current line highlighting over full width
2020 const bool isCurrentLine = (realLine == static_cast<int>(currentLine)) && lineLayout.includesCursor(m_view->cursorPosition());
2021 if (isCurrentLine) {
2022 p.fillRect(lnX, y, w, h, currentLineHighlight);
2023 }
2024
2025 // for real lines we need to do more stuff ;=)
2026 if (realLine >= 0) {
2027 // icon pane
2028 if (m_iconBorderOn) {
2029 const uint mrk(m_doc->mark(realLine)); // call only once
2030 if (mrk && lineLayout.startCol() == 0) {
2031 for (uint bit = 0; bit < 32; bit++) {
2032 Document::MarkTypes markType = (Document::MarkTypes)(1U << bit);
2033 if (mrk & markType) {
2034 const QIcon markIcon = m_doc->markIcon(markType);
2035
2036 if (!markIcon.isNull() && h > 0 && m_iconAreaWidth > 0) {
2037 const int s = qMin(m_iconAreaWidth, static_cast<int>(h)) - 2;
2038
2039 // center the mark pixmap
2040 const int x_px = qMax(m_iconAreaWidth - s, 0) / 2;
2041 const int y_px = qMax(static_cast<int>(h) - s, 0) / 2;
2042
2043 markIcon.paint(&p, lnX + x_px, y + y_px, s, s);
2044 }
2045 }
2046 }
2047 }
2048
2049 lnX += m_iconAreaWidth;
2050 if (m_updatePositionToArea) {
2051 m_positionToArea.push_back(AreaPosition(lnX, IconBorder));
2052 }
2053 }
2054
2055 // annotation information
2056 if (m_annotationBorderOn) {
2057 // Draw a border line between annotations and the line numbers
2058 p.setPen(lineNumberColor);
2059 p.setBrush(lineNumberColor);
2060
2061 const qreal borderX = lnX + m_annotationAreaWidth + 0.5;
2062 p.drawLine(QPointF(borderX, y + 0.5), QPointF(borderX, y + h - 0.5));
2063
2064 if (model) {
2066 initStyleOption(&styleOption);
2067 styleOption.rect.setRect(lnX, y, m_annotationAreaWidth, h);
2068 annotationGroupPositionState.nextLine(styleOption, z, realLine);
2069
2070 m_annotationItemDelegate->paint(&p, styleOption, model, realLine);
2071 }
2072
2073 lnX += m_annotationAreaWidth + m_separatorWidth;
2074 if (m_updatePositionToArea) {
2075 m_positionToArea.push_back(AreaPosition(lnX, AnnotationBorder));
2076 }
2077 }
2078
2079 // line number
2080 if (m_lineNumbersOn || m_dynWrapIndicatorsOn) {
2081 QColor usedLineNumberColor;
2082 const int distanceToCurrent = abs(realLine - static_cast<int>(currentLine));
2083 if (distanceToCurrent == 0) {
2084 usedLineNumberColor = m_view->rendererConfig()->currentLineNumberColor();
2085 } else {
2086 usedLineNumberColor = lineNumberColor;
2087 }
2088 p.setPen(usedLineNumberColor);
2089 p.setBrush(usedLineNumberColor);
2090
2091 if (lineLayout.startCol() == 0) {
2092 if (m_relLineNumbersOn) {
2093 if (distanceToCurrent == 0) {
2094 p.drawText(lnX + m_maxCharWidth / 2,
2095 y,
2096 m_lineNumberAreaWidth - m_maxCharWidth,
2097 h,
2099 QString::number(realLine + 1));
2100 } else {
2101 p.drawText(lnX + m_maxCharWidth / 2,
2102 y,
2103 m_lineNumberAreaWidth - m_maxCharWidth,
2104 h,
2106 QString::number(distanceToCurrent));
2107 }
2108 if (m_updateRelLineNumbers) {
2109 m_updateRelLineNumbers = false;
2110 update();
2111 }
2112 } else if (m_lineNumbersOn) {
2113 p.drawText(lnX + m_maxCharWidth / 2,
2114 y,
2115 m_lineNumberAreaWidth - m_maxCharWidth,
2116 h,
2118 QString::number(realLine + 1));
2119 }
2120 } else if (m_dynWrapIndicatorsOn) {
2121 p.drawText(lnX + m_maxCharWidth / 2,
2122 y,
2123 m_lineNumberAreaWidth - m_maxCharWidth,
2124 h,
2126 m_dynWrapIndicatorChar);
2127 }
2128
2129 lnX += m_lineNumberAreaWidth + m_separatorWidth;
2130 if (m_updatePositionToArea) {
2131 m_positionToArea.push_back(AreaPosition(lnX, LineNumbers));
2132 }
2133 }
2134
2135 // modified line system
2136 if (m_view->config()->lineModification() && !m_doc->url().isEmpty()) {
2137 const auto tl = m_doc->plainKateTextLine(realLine);
2138 if (tl.markedAsModified()) {
2139 p.fillRect(lnX, y, m_modAreaWidth, h, m_view->rendererConfig()->modifiedLineColor());
2140 } else if (tl.markedAsSavedOnDisk()) {
2141 p.fillRect(lnX, y, m_modAreaWidth, h, m_view->rendererConfig()->savedLineColor());
2142 }
2143
2144 lnX += m_modAreaWidth; // No m_separatorWidth
2145 if (m_updatePositionToArea) {
2146 m_positionToArea.push_back(AreaPosition(lnX, None));
2147 }
2148 }
2149
2150 // folding markers
2151 if (m_foldingMarkersOn) {
2152 const QColor foldingColor(m_view->rendererConfig()->foldingColor());
2153 // possible additional folding highlighting
2154 if (m_foldingRange && m_foldingRange->overlapsLine(realLine)) {
2155 p.fillRect(lnX, y, m_foldingAreaWidth, h, foldingColor);
2156 }
2157
2158 if (lineLayout.startCol() == 0) {
2159 QList<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>> startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine);
2160 bool anyFolded = false;
2161 for (int i = 0; i < startingRanges.size(); ++i) {
2162 if (startingRanges[i].second & Kate::TextFolding::Folded) {
2163 anyFolded = true;
2164 }
2165 }
2166 if (!m_view->config()->showFoldingOnHoverOnly() || m_mouseOver) {
2167 if (!startingRanges.isEmpty() || m_doc->buffer().isFoldingStartingOnLine(realLine).first) {
2168 if (anyFolded) {
2169 paintTriangle(p, foldingColor, lnX, y, m_foldingAreaWidth, h, false);
2170 } else {
2171 // Don't try to use currentLineNumberColor, the folded icon gets also not highligted
2172 paintTriangle(p, lineNumberColor, lnX, y, m_foldingAreaWidth, h, true);
2173 }
2174 }
2175 }
2176 }
2177
2178 lnX += m_foldingAreaWidth;
2179 if (m_updatePositionToArea) {
2180 m_positionToArea.push_back(AreaPosition(lnX, FoldingMarkers));
2181 }
2182 }
2183 }
2184
2185 // Overpaint again the end to simulate some margin to the edit area,
2186 // so that the text not looks like stuck to the border
2187 // we do this AFTER all other painting to ensure this leaves no artifacts
2188 // we kill 2 separator widths as we will below paint a line over this
2189 // otherwise this has some minimal overlap and looks ugly e.g. for scaled rendering
2190 p.fillRect(w - 2 * m_separatorWidth, y, w, h, backgroundColor);
2191
2192 // overpaint again with selection or current line highlighting if necessary
2193 if (realLine >= 0 && m_view->selection() && !m_view->blockSelection() && m_view->selectionRange().start() < lineLayout.start()
2194 && m_view->selectionRange().end() >= lineLayout.start()) {
2195 // selection overpaint to signal the end of the previous line is included in the selection
2196 p.fillRect(w - 2 * m_separatorWidth, y, w, h, m_view->rendererConfig()->selectionColor());
2197 } else if (isCurrentLine) {
2198 // normal current line overpaint
2199 p.fillRect(w - 2 * m_separatorWidth, y, w, h, currentLineHighlight);
2200 }
2201
2202 // add separator line if needed
2203 // we do this AFTER all other painting to ensure this leaves no artifacts
2204 p.setPen(m_view->rendererConfig()->separatorColor());
2205 p.setBrush(m_view->rendererConfig()->separatorColor());
2206 p.drawLine(w - 2 * m_separatorWidth, y, w - 2 * m_separatorWidth, y + h);
2207
2208 // we might need to trigger geometry updates
2209 if ((realLine >= 0) && m_updatePositionToArea) {
2210 m_updatePositionToArea = false;
2211 // Don't forget our "text-stuck-to-border" protector + border line
2212 lnX += 2 * m_separatorWidth;
2213 m_positionToArea.push_back(AreaPosition(lnX, None));
2214
2215 // Now that we know our needed space, ensure we are painted properly
2216 // we still keep painting to not have strange flicker
2217 QTimer::singleShot(0, this, &KateIconBorder::delayedUpdateOfSizeWithRepaint);
2218 }
2219 }
2220}
2221
2222KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const
2223{
2224 auto it = std::find_if(m_positionToArea.cbegin(), m_positionToArea.cend(), [p](const AreaPosition &ap) {
2225 return p.x() <= ap.first;
2226 });
2227 if (it != m_positionToArea.cend()) {
2228 return it->second;
2229 }
2230 return None;
2231}
2232
2233void KateIconBorder::mousePressEvent(QMouseEvent *e)
2234{
2235 const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->position().y());
2236 if (t.isValid()) {
2237 m_lastClickedLine = t.line();
2238 const auto area = positionToArea(e->pos());
2239 // IconBorder and AnnotationBorder have their own behavior; don't forward to view
2240 if (area != IconBorder && area != AnnotationBorder) {
2241 const auto pos = QPoint(0, e->position().y());
2242 if (area == LineNumbers && e->button() == Qt::LeftButton && !(e->modifiers() & Qt::ShiftModifier)) {
2243 // setup view so the following mousePressEvent will select the line
2244 m_viewInternal->beginSelectLine(pos);
2245 }
2247 m_viewInternal->mousePressEvent(&forward);
2248 }
2249 return e->accept();
2250 }
2251
2253}
2254
2255void KateIconBorder::highlightFoldingDelayed(int line)
2256{
2257 if ((line == m_currentLine) || (line >= m_doc->buffer().lines())) {
2258 return;
2259 }
2260
2261 m_currentLine = line;
2262
2263 if (m_foldingRange) {
2264 // We are for a while in the folding area, no need for delay
2265 highlightFolding();
2266
2267 } else if (!m_antiFlickerTimer.isActive()) {
2268 m_antiFlickerTimer.start();
2269 }
2270}
2271
2272void KateIconBorder::highlightFolding()
2273{
2274 // compute to which folding range we belong
2275 // FIXME: optimize + perhaps have some better threshold or use timers!
2277 for (int line = m_currentLine; line >= qMax(0, m_currentLine - 1024); --line) {
2278 // try if we have folding range from that line, should be fast per call
2279 KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line);
2280 if (!foldingRange.isValid()) {
2281 continue;
2282 }
2283
2284 // does the range reach us?
2285 if (foldingRange.overlapsLine(m_currentLine)) {
2286 newRange = foldingRange;
2287 break;
2288 }
2289 }
2290
2291 if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) {
2292 // new range equals the old one, nothing to do.
2293 return;
2294 }
2295
2296 // the ranges differ, delete the old, if it exists
2297 delete m_foldingRange;
2298 m_foldingRange = nullptr;
2299 // New range, new preview!
2300 delete m_foldingPreview;
2301
2302 bool showPreview = false;
2303
2304 if (newRange.isValid()) {
2305 // When next line is not visible we have a folded range, only then we want a preview!
2306 showPreview = !m_view->textFolding().isLineVisible(newRange.start().line() + 1);
2307
2308 // qCDebug(LOG_KTE) << "new folding hl-range:" << newRange;
2309 m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight);
2311
2312 // create highlighting color
2313 // we avoid alpha as overpainting leads to ugly lines (https://bugreports.qt.io/browse/QTBUG-66036)
2314 attr->setBackground(QBrush(m_view->rendererConfig()->foldingColor()));
2315
2316 m_foldingRange->setView(m_view);
2317 // use z depth defined in moving ranges interface
2318 m_foldingRange->setZDepth(-100.0);
2319 m_foldingRange->setAttribute(attr);
2320 }
2321
2322 // show text preview, if a folded region starts here...
2323 // ...but only when main window is active (#392396)
2324 const bool isWindowActive = !window() || window()->isActiveWindow();
2325 if (showPreview && m_view->config()->foldingPreview() && isWindowActive) {
2326 m_foldingPreview = new KateTextPreview(m_view, this);
2327 m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating);
2328 m_foldingPreview->setFrameStyle(QFrame::StyledPanel);
2329
2330 // Calc how many lines can be displayed in the popup
2331 const int lineHeight = m_view->renderer()->lineHeight();
2332 const int foldingStartLine = m_foldingRange->start().line();
2333 // FIXME Is there really no easier way to find lineInDisplay?
2334 const QPoint pos = m_viewInternal->mapFrom(m_view, m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0)));
2335 const int lineInDisplay = pos.y() / lineHeight;
2336 // Allow slightly overpainting of the view bottom to proper cover all lines
2337 const int extra = (m_viewInternal->height() % lineHeight) > (lineHeight * 0.6) ? 1 : 0;
2338 const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, m_viewInternal->linesDisplayed() - lineInDisplay + extra);
2339
2340 m_foldingPreview->resize(m_viewInternal->width(), lineCount * lineHeight + 2 * m_foldingPreview->frameWidth());
2341 const int xGlobal = mapToGlobal(QPoint(width(), 0)).x();
2342 const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0))).y();
2343 m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft());
2344 m_foldingPreview->setLine(foldingStartLine);
2345 m_foldingPreview->setCenterView(false);
2346 m_foldingPreview->setShowFoldedLines(true);
2347 m_foldingPreview->raise();
2348 m_foldingPreview->show();
2349 }
2350}
2351
2352void KateIconBorder::hideFolding()
2353{
2354 if (m_antiFlickerTimer.isActive()) {
2355 m_antiFlickerTimer.stop();
2356 }
2357
2358 m_currentLine = -1;
2359 delete m_foldingRange;
2360 m_foldingRange = nullptr;
2361
2362 delete m_foldingPreview;
2363}
2364
2365void KateIconBorder::enterEvent(QEnterEvent *event)
2366{
2367 m_mouseOver = true;
2368 if (m_view->config()->showFoldingOnHoverOnly())
2369 repaint();
2371}
2372
2373void KateIconBorder::leaveEvent(QEvent *event)
2374{
2375 m_mouseOver = false;
2376 hideFolding();
2377 removeAnnotationHovering();
2378 if (m_view->config()->showFoldingOnHoverOnly())
2379 repaint();
2380
2382}
2383
2384void KateIconBorder::mouseMoveEvent(QMouseEvent *e)
2385{
2386 const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->position().y());
2387 if (!t.isValid()) {
2388 // Cleanup everything which may be shown
2389 removeAnnotationHovering();
2390 hideFolding();
2391
2392 } else {
2393 const BorderArea area = positionToArea(e->pos());
2394 if (area == FoldingMarkers) {
2395 highlightFoldingDelayed(t.line());
2396 } else {
2397 hideFolding();
2398 }
2399 if (area == AnnotationBorder) {
2400 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2401 if (model) {
2402 m_hoveredAnnotationGroupIdentifier = model->data(t.line(), (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole).toString();
2403 const QPoint viewRelativePos = m_view->mapFromGlobal(e->globalPosition()).toPoint();
2404 QHelpEvent helpEvent(QEvent::ToolTip, viewRelativePos, e->globalPosition().toPoint());
2406 initStyleOption(&styleOption);
2407 styleOption.rect = annotationLineRectInView(t.line());
2408 setStyleOptionLineData(&styleOption, e->position().y(), t.line(), model, m_hoveredAnnotationGroupIdentifier);
2409 m_annotationItemDelegate->helpEvent(&helpEvent, m_view, styleOption, model, t.line());
2410
2411 QTimer::singleShot(0, this, SLOT(update()));
2412 }
2413 } else {
2414 if (area == IconBorder) {
2415 m_doc->requestMarkTooltip(t.line(), e->globalPosition().toPoint());
2416 }
2417
2418 m_hoveredAnnotationGroupIdentifier.clear();
2419 QTimer::singleShot(0, this, SLOT(update()));
2420 }
2421 if (area != IconBorder) {
2422 QPoint p = m_viewInternal->mapFromGlobal(e->globalPosition().toPoint());
2423 QMouseEvent forward(QEvent::MouseMove, p, m_viewInternal->mapToGlobal(p), e->button(), e->buttons(), e->modifiers());
2424 m_viewInternal->mouseMoveEvent(&forward);
2425 }
2426 }
2427
2429}
2430
2431void KateIconBorder::mouseReleaseEvent(QMouseEvent *e)
2432{
2433 const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->position().y()).line();
2434 if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) {
2435 const BorderArea area = positionToArea(e->pos());
2436 if (area == IconBorder) {
2437 if (e->button() == Qt::LeftButton) {
2438 if (!m_doc->handleMarkClick(cursorOnLine)) {
2439 KateViewConfig *config = m_view->config();
2440 const uint editBits = m_doc->editableMarks();
2441 // is the default or the only editable mark
2442 bool ctrlPressed = QGuiApplication::keyboardModifiers() == Qt::KeyboardModifier::ControlModifier;
2443 if (qPopulationCount(editBits) == 1 || ctrlPressed) {
2444 const uint singleMark = (qPopulationCount(editBits) > 1) ? (editBits & config->defaultMarkType()) : editBits;
2445 if (m_doc->mark(cursorOnLine) & singleMark) {
2446 m_doc->removeMark(cursorOnLine, singleMark);
2447 } else {
2448 m_doc->addMark(cursorOnLine, singleMark);
2449 }
2450 } else if (config->allowMarkMenu()) {
2451 showMarkMenu(cursorOnLine, QCursor::pos());
2452 }
2453 }
2454 } else if (e->button() == Qt::RightButton) {
2455 showMarkMenu(cursorOnLine, QCursor::pos());
2456 }
2457 }
2458
2459 if (area == FoldingMarkers) {
2460 // Prefer the highlighted range over the exact clicked line
2461 const int lineToToggle = m_foldingRange ? m_foldingRange->toRange().start().line() : cursorOnLine;
2462 if (e->button() == Qt::LeftButton) {
2463 m_view->toggleFoldingOfLine(lineToToggle);
2464 } else if (e->button() == Qt::RightButton) {
2465 m_view->toggleFoldingsInRange(lineToToggle);
2466 }
2467
2468 delete m_foldingPreview;
2469 }
2470
2471 if (area == AnnotationBorder) {
2472 const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this);
2473 if (e->button() == Qt::LeftButton && singleClick) {
2474 Q_EMIT m_view->annotationActivated(m_view, cursorOnLine);
2475 }
2476 }
2477 }
2478
2479 const QPoint pos(0, e->position().y());
2481 m_viewInternal->mouseReleaseEvent(&forward);
2482}
2483
2484void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e)
2485{
2486 int cursorOnLine = m_viewInternal->yToKateTextLayout(e->position().y()).line();
2487
2488 if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) {
2489 const BorderArea area = positionToArea(e->pos());
2490 const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this);
2491 if (area == AnnotationBorder && !singleClick) {
2492 Q_EMIT m_view->annotationActivated(m_view, cursorOnLine);
2493 }
2494 }
2495 const QPoint pos(0, e->position().y());
2497 m_viewInternal->mouseDoubleClickEvent(&forward);
2498}
2499
2500void KateIconBorder::contextMenuEvent(QContextMenuEvent *e)
2501{
2502 const BorderArea area = positionToArea(e->pos());
2503 const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line();
2504
2505 if (area == AnnotationBorder) {
2506 showAnnotationMenu(cursorOnLine, e->globalPos());
2507 return;
2508 }
2509
2510 QMenu menu(this);
2511
2512 KActionCollection *ac = m_view->actionCollection();
2513
2514 // NOTE Assumes cursor position was updated before the menu opens
2515 if (QAction *bookmarkToggle = ac->action(QStringLiteral("bookmarks_toggle"))) {
2516 menu.addAction(bookmarkToggle);
2517 }
2518 if (QAction *bookmarkClear = ac->action(QStringLiteral("bookmarks_clear"))) {
2519 menu.addAction(bookmarkClear);
2520 }
2521
2522 menu.addSeparator();
2523
2524 if (auto a = ac->action(QStringLiteral("edit_copy_file_location"))) {
2525 menu.addAction(a);
2526 }
2527
2528 menu.addSeparator();
2529
2530 if (QAction *toggleDynWrap = ac->action(QStringLiteral("view_dynamic_word_wrap"))) {
2531 menu.addAction(toggleDynWrap);
2532 }
2533 menu.addSeparator();
2534 if (QAction *toggleIconBar = ac->action(QStringLiteral("view_border"))) {
2535 menu.addAction(toggleIconBar);
2536 }
2537 if (QAction *toggleLineNumbers = ac->action(QStringLiteral("view_line_numbers"))) {
2538 menu.addAction(toggleLineNumbers);
2539 }
2540 if (QAction *toggleFoldingMarkers = ac->action(QStringLiteral("view_folding_markers"))) {
2541 menu.addAction(toggleFoldingMarkers);
2542 }
2543
2544 menu.exec(e->globalPos());
2545}
2546
2547void KateIconBorder::wheelEvent(QWheelEvent *e)
2548{
2549 QCoreApplication::sendEvent(m_viewInternal, e);
2550}
2551
2552void KateIconBorder::showMarkMenu(uint line, const QPoint &pos)
2553{
2554 if (m_doc->handleMarkContextMenu(line, pos)) {
2555 return;
2556 }
2557
2558 if (!m_view->config()->allowMarkMenu()) {
2559 return;
2560 }
2561
2562 QMenu markMenu;
2563 QMenu selectDefaultMark;
2564 auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark);
2565
2566 std::vector<int> vec(33);
2567 int i = 1;
2568
2569 for (uint bit = 0; bit < 32; bit++) {
2571 if (!(m_doc->editableMarks() & markType)) {
2572 continue;
2573 }
2574
2575 QAction *mA;
2576 QAction *dMA;
2577 const QIcon icon = m_doc->markIcon(markType);
2578 if (!m_doc->markDescription(markType).isEmpty()) {
2579 mA = markMenu.addAction(icon, m_doc->markDescription(markType));
2580 dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType));
2581 } else {
2582 mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1));
2583 dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1));
2584 }
2585 selectDefaultMarkActionGroup->addAction(dMA);
2586 mA->setData(i);
2587 mA->setCheckable(true);
2588 dMA->setData(i + 100);
2589 dMA->setCheckable(true);
2590 if (m_doc->mark(line) & markType) {
2591 mA->setChecked(true);
2592 }
2593
2594 if (markType & KateViewConfig::global()->defaultMarkType()) {
2595 dMA->setChecked(true);
2596 }
2597
2598 vec[i++] = markType;
2599 }
2600
2601 if (markMenu.actions().count() == 0) {
2602 return;
2603 }
2604
2605 if (markMenu.actions().count() > 1) {
2606 markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark);
2607 }
2608
2609 QAction *rA = markMenu.exec(pos);
2610 if (!rA) {
2611 return;
2612 }
2613 int result = rA->data().toInt();
2614 if (result > 100) {
2615 KateViewConfig::global()->setValue(KateViewConfig::DefaultMarkType, vec[result - 100]);
2616 } else {
2618 if (m_doc->mark(line) & markType) {
2619 m_doc->removeMark(line, markType);
2620 } else {
2621 m_doc->addMark(line, markType);
2622 }
2623 }
2624}
2625
2626KTextEditor::AbstractAnnotationItemDelegate *KateIconBorder::annotationItemDelegate() const
2627{
2628 return m_annotationItemDelegate;
2629}
2630
2631void KateIconBorder::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate)
2632{
2633 if (delegate == m_annotationItemDelegate) {
2634 return;
2635 }
2636
2637 // reset to default, but already on that?
2638 if (!delegate && m_isDefaultAnnotationItemDelegate) {
2639 // nothing to do
2640 return;
2641 }
2642
2643 // make sure the tooltip is hidden
2644 if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
2645 m_hoveredAnnotationGroupIdentifier.clear();
2646 hideAnnotationTooltip();
2647 }
2648
2649 disconnect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2650 if (!m_isDefaultAnnotationItemDelegate) {
2651 disconnect(m_annotationItemDelegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2652 }
2653
2654 if (!delegate) {
2655 // reset to a default delegate
2656 m_annotationItemDelegate = new KateAnnotationItemDelegate(this);
2657 m_isDefaultAnnotationItemDelegate = true;
2658 } else {
2659 // drop any default delegate
2660 if (m_isDefaultAnnotationItemDelegate) {
2661 delete m_annotationItemDelegate;
2662 m_isDefaultAnnotationItemDelegate = false;
2663 }
2664
2665 m_annotationItemDelegate = delegate;
2666 // catch delegate being destroyed
2667 connect(delegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2668 }
2669
2670 connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2671
2672 if (m_annotationBorderOn) {
2673 QTimer::singleShot(0, this, &KateIconBorder::delayedUpdateOfSizeWithRepaint);
2674 }
2675}
2676
2677void KateIconBorder::handleDestroyedAnnotationItemDelegate()
2678{
2679 setAnnotationItemDelegate(nullptr);
2680}
2681
2682void KateIconBorder::delayedUpdateOfSizeWithRepaint()
2683{
2684 // ensure we update size + repaint at once to avoid flicker, see bug 435361
2685 setUpdatesEnabled(false);
2687 repaint();
2688 setUpdatesEnabled(true);
2689}
2690
2691void KateIconBorder::initStyleOption(KTextEditor::StyleOptionAnnotationItem *styleOption) const
2692{
2693 styleOption->initFrom(this);
2694 styleOption->view = m_view;
2695 styleOption->decorationSize = QSize(m_iconAreaWidth, m_iconAreaWidth);
2696 styleOption->contentFontMetrics = m_view->renderer()->currentFontMetrics();
2697}
2698
2699void KateIconBorder::setStyleOptionLineData(KTextEditor::StyleOptionAnnotationItem *styleOption,
2700 int y,
2701 int realLine,
2702 const KTextEditor::AnnotationModel *model,
2703 const QString &annotationGroupIdentifier) const
2704{
2705 // calculate rendered displayed line
2706 const uint h = m_view->renderer()->lineHeight();
2707 const uint z = (y / h);
2708
2709 KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, annotationGroupIdentifier, z, true);
2710 annotationGroupPositionState.nextLine(*styleOption, z, realLine);
2711}
2712
2713QRect KateIconBorder::annotationLineRectInView(int line) const
2714{
2715 int x = 0;
2716 if (m_iconBorderOn) {
2717 x += m_iconAreaWidth + 2;
2718 }
2719 const int y = m_view->m_viewInternal->lineToY(line);
2720
2721 return QRect(x, y, m_annotationAreaWidth, m_view->renderer()->lineHeight());
2722}
2723
2724void KateIconBorder::updateAnnotationLine(int line)
2725{
2726 // TODO: why has the default value been 8, where is that magic number from?
2727 int width = 8;
2728 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2729
2730 if (model) {
2732 initStyleOption(&styleOption);
2733 width = m_annotationItemDelegate->sizeHint(styleOption, model, line).width();
2734 }
2735
2736 if (width > m_annotationAreaWidth) {
2737 m_annotationAreaWidth = width;
2738 m_updatePositionToArea = true;
2739
2740 QTimer::singleShot(0, this, SLOT(update()));
2741 }
2742}
2743
2744void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos)
2745{
2746 QMenu menu;
2747 QAction a(i18n("Disable Annotation Bar"), &menu);
2748 a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2749 menu.addAction(&a);
2750 Q_EMIT m_view->annotationContextMenuAboutToShow(m_view, &menu, line);
2751 if (menu.exec(pos) == &a) {
2752 m_view->setAnnotationBorderVisible(false);
2753 }
2754}
2755
2756void KateIconBorder::hideAnnotationTooltip()
2757{
2758 m_annotationItemDelegate->hideTooltip(m_view);
2759}
2760
2761void KateIconBorder::updateAnnotationBorderWidth()
2762{
2763 calcAnnotationBorderWidth();
2764
2765 m_updatePositionToArea = true;
2766
2767 QTimer::singleShot(0, this, SLOT(update()));
2768}
2769
2770void KateIconBorder::calcAnnotationBorderWidth()
2771{
2772 // TODO: another magic number, not matching the one in updateAnnotationLine()
2773 m_annotationAreaWidth = 6;
2774 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2775
2776 if (model) {
2778 initStyleOption(&styleOption);
2779
2780 const int lineCount = m_view->doc()->lines();
2781 if (lineCount > 0) {
2782 const int checkedLineCount = m_hasUniformAnnotationItemSizes ? 1 : lineCount;
2783 for (int i = 0; i < checkedLineCount; ++i) {
2784 const int curwidth = m_annotationItemDelegate->sizeHint(styleOption, model, i).width();
2785 if (curwidth > m_annotationAreaWidth) {
2786 m_annotationAreaWidth = curwidth;
2787 }
2788 }
2789 }
2790 }
2791}
2792
2793void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel)
2794{
2795 if (oldmodel) {
2796 oldmodel->disconnect(this);
2797 }
2798 if (newmodel) {
2799 connect(newmodel, &KTextEditor::AnnotationModel::reset, this, &KateIconBorder::updateAnnotationBorderWidth);
2800 connect(newmodel, &KTextEditor::AnnotationModel::lineChanged, this, &KateIconBorder::updateAnnotationLine);
2801 }
2802 updateAnnotationBorderWidth();
2803}
2804
2805void KateIconBorder::displayRangeChanged()
2806{
2807 hideFolding();
2808 removeAnnotationHovering();
2809}
2810
2811// END KateIconBorder
2812
2813// BEGIN KateViewEncodingAction
2814// According to https://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
2815// the default/unknown mib value is 2.
2816#define MIB_DEFAULT 2
2817
2818bool lessThanAction(KSelectAction *a, KSelectAction *b)
2819{
2820 return a->text() < b->text();
2821}
2822
2823void KateViewEncodingAction::init()
2824{
2826
2828
2829 int i;
2830 const auto encodingsByScript = KCharsets::charsets()->encodingsByScript();
2831 actions.reserve(encodingsByScript.size());
2832 for (const QStringList &encodingsForScript : encodingsByScript) {
2833 KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), this);
2834
2835 for (i = 1; i < encodingsForScript.size(); ++i) {
2836 tmp->addAction(encodingsForScript.at(i));
2837 }
2839 subActionTriggered(action);
2840 });
2841 // tmp->setCheckable(true);
2842 actions << tmp;
2843 }
2844 std::sort(actions.begin(), actions.end(), lessThanAction);
2845 for (KSelectAction *action : std::as_const(actions)) {
2847 }
2848}
2849
2850void KateViewEncodingAction::subActionTriggered(QAction *action)
2851{
2852 if (currentSubAction == action) {
2853 return;
2854 }
2855 currentSubAction = action;
2857}
2858
2859KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc,
2860 KTextEditor::ViewPrivate *_view,
2861 const QString &text,
2862 QObject *parent,
2863 bool saveAsMode)
2864 : KSelectAction(text, parent)
2865 , doc(_doc)
2866 , view(_view)
2867 , m_saveAsMode(saveAsMode)
2868{
2869 init();
2870
2871 connect(menu(), &QMenu::aboutToShow, this, &KateViewEncodingAction::slotAboutToShow);
2872 connect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2873}
2874
2875void KateViewEncodingAction::slotAboutToShow()
2876{
2877 setCurrentCodec(doc->config()->encoding());
2878}
2879
2880void KateViewEncodingAction::setEncoding(const QString &e)
2881{
2882 // in save as mode => trigger saveAs
2883 if (m_saveAsMode) {
2884 doc->documentSaveAsWithEncoding(e);
2885 return;
2886 }
2887
2888 // else switch encoding
2890 doc->setEncoding(e);
2891 view->reloadFile();
2892}
2893
2894bool KateViewEncodingAction::setCurrentCodec(const QString &codec)
2895{
2896 disconnect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2897
2898 int i;
2899 int j;
2900 for (i = 0; i < actions().size(); ++i) {
2901 if (actions().at(i)->menu()) {
2902 for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) {
2903 if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) {
2904 continue;
2905 }
2906 if (actions().at(i)->menu()->actions().at(j)->isSeparator()) {
2907 continue;
2908 }
2909
2910 if (codec == actions().at(i)->menu()->actions().at(j)->text()) {
2911 currentSubAction = actions().at(i)->menu()->actions().at(j);
2912 currentSubAction->setChecked(true);
2913 } else {
2914 actions().at(i)->menu()->actions().at(j)->setChecked(false);
2915 }
2916 }
2917 }
2918 }
2919
2920 connect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2921 return true;
2922}
2923
2924// END KateViewEncodingAction
2925
2926// BEGIN KateViewBar related classes
2927
2928KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent)
2929 : QWidget(parent)
2930 , m_viewBar(nullptr)
2931{
2932 QHBoxLayout *layout = new QHBoxLayout(this);
2933
2934 // NOTE: Here be cosmetics.
2935 layout->setContentsMargins(0, 0, 0, 0);
2936
2937 // widget to be used as parent for the real content
2938 m_centralWidget = new QWidget(this);
2939 layout->addWidget(m_centralWidget);
2940 setFocusProxy(m_centralWidget);
2941
2942 // hide button
2943 if (addCloseButton) {
2944 m_closeButton = new QToolButton(this);
2945 m_closeButton->setAutoRaise(true);
2946 m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2947 connect(m_closeButton, &QToolButton::clicked, this, &KateViewBarWidget::hideMe);
2948 layout->addWidget(m_closeButton);
2949 layout->setAlignment(m_closeButton, Qt::AlignCenter | Qt::AlignVCenter);
2950 }
2951}
2952
2953KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view)
2954 : QWidget(parent)
2955 , m_external(external)
2956 , m_view(view)
2957 , m_permanentBarWidget(nullptr)
2958
2959{
2960 m_layout = new QVBoxLayout(this);
2961 m_stack = new QStackedWidget(this);
2962 m_layout->addWidget(m_stack);
2963 m_layout->setContentsMargins(0, 0, 0, 0);
2964
2965 m_stack->hide();
2966 hide();
2967}
2968
2969void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget)
2970{
2971 // just ignore additional adds for already existing widgets
2972 if (hasBarWidget(newBarWidget)) {
2973 return;
2974 }
2975
2976 // add new widget, invisible...
2977 newBarWidget->hide();
2978 m_stack->addWidget(newBarWidget);
2979 newBarWidget->setAssociatedViewBar(this);
2980 connect(newBarWidget, &KateViewBarWidget::hideMe, this, &KateViewBar::hideCurrentBarWidget);
2981}
2982
2983void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget)
2984{
2985 // remove only if there
2986 if (!hasBarWidget(barWidget)) {
2987 return;
2988 }
2989
2990 m_stack->removeWidget(barWidget);
2991 barWidget->setAssociatedViewBar(nullptr);
2992 barWidget->hide();
2993 disconnect(barWidget, nullptr, this, nullptr);
2994}
2995
2996void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget)
2997{
2998 Q_ASSERT(barWidget);
2999 Q_ASSERT(!m_permanentBarWidget);
3000
3001 m_layout->addWidget(barWidget);
3002 m_permanentBarWidget = barWidget;
3003 m_permanentBarWidget->show();
3004
3005 setViewBarVisible(true);
3006}
3007
3008void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget)
3009{
3010 Q_ASSERT(m_permanentBarWidget == barWidget);
3011 Q_UNUSED(barWidget);
3012
3013 m_permanentBarWidget->hide();
3014 m_layout->removeWidget(m_permanentBarWidget);
3015 m_permanentBarWidget = nullptr;
3016
3017 if (!barWidgetVisible()) {
3018 setViewBarVisible(false);
3019 }
3020}
3021
3022void KateViewBar::showBarWidget(KateViewBarWidget *barWidget)
3023{
3024 Q_ASSERT(barWidget != nullptr);
3025
3026 if (barWidget != qobject_cast<KateViewBarWidget *>(m_stack->currentWidget())) {
3027 hideCurrentBarWidget();
3028 }
3029
3030 // raise correct widget
3031 m_stack->addWidget(barWidget);
3032 m_stack->setCurrentWidget(barWidget);
3033 barWidget->show();
3035 m_stack->show();
3036 setViewBarVisible(true);
3037}
3038
3039bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const
3040{
3041 return m_stack->indexOf(barWidget) != -1;
3042}
3043
3044void KateViewBar::hideCurrentBarWidget()
3045{
3046 KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
3047 if (current) {
3048 m_stack->removeWidget(current);
3049 current->closed();
3050 }
3051
3052 // hide the bar
3053 m_stack->hide();
3054 if (!m_permanentBarWidget) {
3055 setViewBarVisible(false);
3056 }
3057
3058 m_view->setFocus();
3059}
3060
3061void KateViewBar::setViewBarVisible(bool visible)
3062{
3063 if (m_external) {
3064 if (visible) {
3065 m_view->mainWindow()->showViewBar(m_view);
3066 } else {
3067 m_view->mainWindow()->hideViewBar(m_view);
3068 }
3069 } else {
3071 }
3072}
3073
3074bool KateViewBar::barWidgetVisible() const
3075{
3076 KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
3077 return current && current->isVisible();
3078}
3079
3080void KateViewBar::keyPressEvent(QKeyEvent *event)
3081{
3082 if (event->key() == Qt::Key_Escape) {
3083 hideCurrentBarWidget();
3084 return;
3085 }
3087}
3088
3089void KateViewBar::hideEvent(QHideEvent *event)
3090{
3091 Q_UNUSED(event);
3092 // if (!event->spontaneous())
3093 // m_view->setFocus();
3094}
3095
3096// END KateViewBar related classes
3097
3098// BEGIN SCHEMA ACTION -- the 'View->Color theme' menu action
3099void KateViewSchemaAction::init()
3100{
3101 m_group = nullptr;
3102 m_view = nullptr;
3103 last = 0;
3104
3105 connect(menu(), &QMenu::aboutToShow, this, &KateViewSchemaAction::slotAboutToShow);
3106}
3107
3108void KateViewSchemaAction::updateMenu(KTextEditor::ViewPrivate *view)
3109{
3110 m_view = view;
3111}
3112
3113void KateViewSchemaAction::slotAboutToShow()
3114{
3115 KTextEditor::ViewPrivate *view = m_view;
3116
3117 const auto themes = KateHlManager::self()->sortedThemes();
3118
3119 if (!m_group) {
3120 m_group = new QActionGroup(menu());
3121 m_group->setExclusive(true);
3122 }
3123
3124 for (int z = 0; z < themes.count(); z++) {
3125 QString hlName = themes[z].translatedName();
3126
3127 if (!names.contains(hlName)) {
3128 names << hlName;
3129 QAction *a = menu()->addAction(hlName, this, &KateViewSchemaAction::setSchema);
3130 a->setData(themes[z].name());
3131 a->setCheckable(true);
3132 a->setActionGroup(m_group);
3133 }
3134 }
3135
3136 if (!view) {
3137 return;
3138 }
3139
3140 QString id = view->rendererConfig()->schema();
3141 const auto menuActions = menu()->actions();
3142 for (QAction *a : menuActions) {
3143 a->setChecked(a->data().toString() == id);
3144 }
3145}
3146
3147void KateViewSchemaAction::setSchema()
3148{
3150
3151 if (!action) {
3152 return;
3153 }
3154 QString mode = action->data().toString();
3155
3156 KTextEditor::ViewPrivate *view = m_view;
3157
3158 if (view) {
3159 view->rendererConfig()->setSchema(mode);
3160 }
3161}
3162// END SCHEMA ACTION
3163
3164#include "moc_kateviewhelpers.cpp"
Q_INVOKABLE QAction * action(const QString &name) const
QList< QStringList > encodingsByScript() const
static KCharsets * charsets()
KCompletion * completionObject(bool handleSignals=true)
bool event(QEvent *) override
void setCompletionObject(KCompletion *, bool handle=true) override
virtual void setText(const QString &)
void returnKeyPressed(const QString &text)
void keyPressEvent(QKeyEvent *) override
QAction * addAction(const QIcon &icon, const QString &text)
KSelectAction(const QIcon &icon, const QString &text, QObject *parent)
QList< QAction * > actions() const
void setToolBarMode(ToolBarMode mode)
void textTriggered(const QString &text)
QAction * action(const QString &text, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
void actionTriggered(QAction *action)
A delegate for rendering line annotation information and handling events.
virtual void hideTooltip(KTextEditor::View *view)=0
This pure abstract function must be reimplemented to provide custom tooltips.
virtual bool helpEvent(QHelpEvent *event, KTextEditor::View *view, const KTextEditor::StyleOptionAnnotationItem &option, KTextEditor::AnnotationModel *model, int line)=0
Whenever a help event occurs, this function is called with the event view option and model and line s...
void sizeHintChanged(KTextEditor::AnnotationModel *model, int line)
This signal must be emitted when the sizeHint() for model and line changed.
virtual QSize sizeHint(const KTextEditor::StyleOptionAnnotationItem &option, KTextEditor::AnnotationModel *model, int line) const =0
This pure abstract function must be reimplemented to provide custom rendering.
virtual void paint(QPainter *painter, const KTextEditor::StyleOptionAnnotationItem &option, KTextEditor::AnnotationModel *model, int line) const =0
This pure abstract function must be reimplemented to provide custom rendering.
An model for providing line annotation information.
virtual QVariant data(int line, Qt::ItemDataRole role) const =0
data() is used to retrieve the information needed to present the annotation information from the anno...
void reset()
The model should emit the signal reset() when the text of almost all lines changes.
void lineChanged(int line)
The model should emit the signal lineChanged() when a line has to be updated.
A class which provides customized text decorations.
Definition attribute.h:51
An Editor command line command.
virtual bool supportsRange(const QString &cmd)
Find out if a given command can act on a range.
virtual bool help(KTextEditor::View *view, const QString &cmd, QString &msg)=0
Shows help for the given view and cmd string.
virtual bool wantsToProcessText(const QString &cmdname)
Check, whether the command wants to process text interactively for the given command with name cmdnam...
virtual void processText(KTextEditor::View *view, const QString &text)
This is called by the command line each time the argument text for the command changed,...
virtual KCompletion * completionObject(KTextEditor::View *view, const QString &cmdname)
Return a KCompletion object that will substitute the command line default one while typing the first ...
virtual bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range=KTextEditor::Range::invalid())=0
Execute the command for the given view and cmd string.
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
Backend of KTextEditor::Document related public KTextEditor interfaces.
bool setEncoding(const QString &e) override
Set the encoding for this document.
void userSetEncodingForNextReload()
User did set encoding for next reload => enforce it!
bool handleMarkContextMenu(int line, QPoint position)
Returns true if the context-menu event should not further be processed.
KTextEditor::MovingRange * newMovingRange(KTextEditor::Range range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors=KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::EmptyBehavior emptyBehavior=KTextEditor::MovingRange::AllowEmpty) override
Create a new moving range for this document.
KTextEditor::AnnotationModel * annotationModel() const override
returns the currently set AnnotationModel or 0 if there's none set
QString markDescription(Document::MarkTypes) const override
Get the mark's description to text.
KateBuffer & buffer()
Get access to buffer of this document.
int lines() const override
Get the count of lines of the document.
bool handleMarkClick(int line)
Returns true if the click on the mark should not be further processed.
KateDocumentConfig * config()
Configuration.
QIcon markIcon(Document::MarkTypes markType) const override
Get the mark's icon.
uint editableMarks() const override
Get, which marks can be toggled by the user.
Kate::TextLine plainKateTextLine(int i)
Return line lineno.
uint mark(int line) override
Get all marks set on the line.
const QHash< int, KTextEditor::Mark * > & marks() override
Get a hash holding all marks in the document.
void textChanged(KTextEditor::Document *document)
The document emits this signal whenever its text changes.
void marksChanged(KTextEditor::Document *document)
The document emits this signal whenever a mark mask changed.
MarkTypes
Predefined mark types.
Definition document.h:1551
void hideViewBar(KTextEditor::View *view)
Hide the view bar for the given view.
void showViewBar(KTextEditor::View *view)
Show the view bar for the given view.
uint type
The mark types in the line, combined with logical OR.
Definition document.h:79
int line
The line that contains the mark.
Definition document.h:76
MessagePosition
Message position used to place the message either above or below of the KTextEditor::View.
Definition message.h:117
@ TopInView
show message as view overlay in the top right corner.
Definition message.h:123
@ CenterInView
show message as view overlay in the center of the view.
Definition message.h:128
@ BottomInView
show message as view overlay in the bottom right corner.
Definition message.h:125
virtual int line() const =0
Retrieve the line on which this cursor is situated.
virtual void setAttribute(Attribute::Ptr attribute)=0
Sets the currently active attribute for this range.
virtual const MovingCursor & start() const =0
Retrieve start cursor of this range, read-only.
virtual void setView(View *view)=0
Sets the currently active view for this range.
bool overlapsLine(int line) const
Check whether the range overlaps at least part of line.
int numberOfLines() const Q_DECL_NOEXCEPT
Returns the number of lines separating the start() and end() positions.
const Range toRange() const
Convert this clever range into a dumb one.
virtual void setZDepth(qreal zDepth)=0
Set the current Z-depth of this range.
@ ExpandRight
Expand to encapsulate new characters to the right of the range.
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
constexpr bool isEmpty() const noexcept
Returns true if this range contains no characters, ie.
static constexpr Range invalid() noexcept
Returns an invalid range.
constexpr bool isValid() const noexcept
Validity check.
constexpr bool contains(Range range) const noexcept
Check whether the this range wholly encompasses range.
constexpr bool overlapsLine(int line) const noexcept
Check whether the range overlaps at least part of line.
The style option set for an annotation item, as painted by AbstractAnnotationItemDelegate.
QSize decorationSize
Recommended size for icons or other symbols that will be rendered by the delegate.
@ GroupBegin
Real line is first of consecutive lines from same group.
@ GroupEnd
Real line is last of consecutive lines from same group.
AnnotationItemGroupPositions annotationItemGroupingPosition
Relative position of the real line in the row of consecutive displayed lines which belong to the same...
QFontMetricsF contentFontMetrics
The metrics of the font used for rendering the text document.
int wrappedLineCount
Number of wrapped lines for the given real line.
KTextEditor::View * view
The view where the annotation is shown.
int wrappedLine
Index of the displayed line in the wrapped lines for the given real line.
int visibleWrappedLineInGroup
Index of the displayed line in the displayed lines for the same group.
void annotationContextMenuAboutToShow(KTextEditor::View *view, QMenu *menu, int line)
This signal is emitted before a context menu is shown on the annotation border for the given line and...
void focusOut(KTextEditor::View *view)
This signal is emitted whenever the view loses the focus.
void annotationActivated(KTextEditor::View *view, int line)
This signal is emitted when an entry on the annotation border was activated, for example by clicking ...
void selectionChanged(KTextEditor::View *view)
This signal is emitted whenever the view's selection changes.
void annotationBorderVisibilityChanged(KTextEditor::View *view, bool visible)
This signal is emitted when the annotation border is shown or hidden.
virtual KActionCollection * actionCollection() const
KTextEditor::Range computeFoldingRangeForStartLine(int startLine)
For a given line, compute the folding range that starts there to be used to fold e....
std::pair< bool, bool > isFoldingStartingOnLine(int startLine)
For a given line, compute if folding starts here.
void ensureHighlighted(int line, int lookAhead=64)
Update highlighting of given line line, if needed.
bool setValue(const int key, const QVariant &value)
Set a config value.
KateTextLayout & viewLine(int viewLine)
Returns the layout of the corresponding line in the view.
const QFont & currentFont() const
Access currently used font.
const AttributePtr & attribute(uint pos) const
This takes an in index, and returns all the attributes for it.
const QFontMetricsF & currentFontMetrics() const
Access currently used font metrics.
This class represents one visible line of text; with dynamic wrapping, many KateTextLayouts can be ne...
int viewLine() const
Return the index of this visual line inside the document line (KateLineLayout).
QList< TextRange * > rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
Return the ranges which affect the given line.
int lines() const
Lines currently stored in this buffer.
bool isLineVisible(int line, qint64 *foldedRangeId=nullptr) const
Query if a given line is visible.
void foldingRangesChanged()
If the folding state of existing ranges changes or ranges are added/removed, this signal is emitted.
QList< QPair< qint64, FoldingRangeFlags > > foldingRangesStartingOnLine(int line) const
Queries which folding ranges start at the given line and returns the id + flags for all of them.
int visibleLines() const
Query number of visible lines.
int lineToVisibleLine(int line) const
Convert a text buffer line to a visible line number.
@ Folded
Range is folded away.
int visibleLineToLine(int visibleLine) const
Convert a visible line number to a line number in the text buffer.
Class representing a single text line.
const QString & text() const
Accessor to the text contained in this line.
const QList< Attribute > & attributesList() const
Accessor to attributes.
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
KGUIADDONS_EXPORT QColor darken(const QColor &, qreal amount=0.5, qreal chromaGain=1.0)
KGUIADDONS_EXPORT qreal luma(const QColor &)
KGUIADDONS_EXPORT QColor shade(const QColor &, qreal lumaAmount, qreal chromaAmount=0.0)
KGUIADDONS_EXPORT qreal hue(const QColor &)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
void invokeHelp(const QString &anchor=QString(), const QString &appname=QString())
bool canConvert(const QVariant &value)
QString name(StandardAction id)
KGuiItem forward(BidiMode useBidi=IgnoreRTL)
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
void clicked(bool checked)
void setIcon(const QIcon &icon)
bool isSliderDown() const const
void setSliderPosition(int)
void valueChanged(int value)
void setCheckable(bool)
void setChecked(bool)
QVariant data() const const
bool isSeparator() const const
QMenu * menu() const const
void setActionGroup(QActionGroup *group)
void setData(const QVariant &data)
void setMenu(QMenu *menu)
void setExclusive(bool b)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
const QColor & color() const const
bool isLetterOrNumber(char32_t ucs4)
bool isNull() const const
void getHsl(int *h, int *s, int *l, int *a) const const
int hue() const const
QColor lighter(int factor) const const
int lightness() const const
void setAlpha(int alpha)
void setHsl(int h, int s, int l, int a)
void setHsv(int h, int s, int v, int a)
int value() const const
const QPoint & globalPos() const const
const QPoint & pos() const const
int y() const const
bool sendEvent(QObject *receiver, QEvent *event)
QPoint pos()
MouseButtonPress
void accept()
Type type() const const
qreal height() const const
qreal horizontalAdvance(QChar ch) const const
Qt::KeyboardModifiers keyboardModifiers()
void clear()
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator insert(const Key &key, const T &value)
qsizetype size() const const
bool hasNext() const const
const Key & key() const const
const T & value() const const
QIcon fromTheme(const QString &name)
bool isNull() const const
void paint(QPainter *painter, const QRect &rect, Qt::Alignment alignment, Mode mode, State state) const const
Qt::KeyboardModifiers modifiers() const const
int key() const const
Qt::KeyboardModifiers modifiers() const const
QString text() const const
void removeWidget(QWidget *widget)
bool setAlignment(QLayout *l, Qt::Alignment alignment)
void setContentsMargins(const QMargins &margins)
virtual void setGeometry(const QRect &r) override
virtual void setGeometry(const QRect &r)=0
virtual QSize sizeHint() const const=0
virtual QWidget * widget() const const
void clear()
void end(bool mark)
virtual void focusInEvent(QFocusEvent *e) override
void selectAll()
void setSelection(int start, int length)
const_reference at(qsizetype i) const const
iterator begin()
void clear()
qsizetype count() const const
iterator end()
bool isEmpty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
T takeAt(qsizetype i)
T value(qsizetype i) const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
void aboutToShow()
QAction * exec()
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QPoint pos() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
bool disconnect(const QMetaObject::Connection &connection)
T qobject_cast(QObject *object)
QObject * sender() const const
qreal devicePixelRatioF() const const
SmoothPixmapTransform
bool begin(QPaintDevice *device)
void drawConvexPolygon(const QPoint *points, int pointCount)
void drawLine(const QLine &line)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
void drawPoints(const QPoint *points, int pointCount)
void drawRect(const QRect &rectangle)
bool end()
void fillRect(const QRect &rectangle, QGradient::Preset preset)
const QPen & pen() const const
void setBrush(Qt::BrushStyle style)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
const QRect & rect() const const
QBrush brush() const const
void setColor(const QColor &color)
void setJoinStyle(Qt::PenJoinStyle style)
void setWidth(int width)
void setWidthF(qreal width)
qreal devicePixelRatio() const const
void fill(const QColor &color)
int height() const const
void setDevicePixelRatio(qreal scaleFactor)
int width() const const
int x() const const
int y() const const
QPoint toPoint() const const
qreal y() const const
void adjust(int dx1, int dy1, int dx2, int dy2)
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
int bottom() const const
QPoint center() const const
bool contains(const QPoint &point, bool proper) const const
int height() const const
int left() const const
void moveTop(int y)
int right() const const
void setBottom(int y)
void setHeight(int height)
void setTop(int y)
void setWidth(int width)
void setX(int x)
int top() const const
int width() const const
int x() const const
int y() const const
virtual bool event(QEvent *event) override
virtual void initStyleOption(QStyleOptionSlider *option) const const
virtual void mouseMoveEvent(QMouseEvent *e) override
virtual void mousePressEvent(QMouseEvent *e) override
virtual void mouseReleaseEvent(QMouseEvent *e) override
virtual void paintEvent(QPaintEvent *) override
virtual QSize sizeHint() const const override
virtual void sliderChange(SliderChange change) override
Qt::MouseButton button() const const
Qt::MouseButtons buttons() const const
QPointF globalPosition() const const
QPointF position() const const
int height() const const
int width() const const
int addWidget(QWidget *widget)
QWidget * currentWidget() const const
int indexOf(const QWidget *widget) const const
void removeWidget(QWidget *widget)
void setCurrentWidget(QWidget *widget)
const QChar at(qsizetype position) const const
void clear()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString number(double n, char format, int precision)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
PM_FocusFrameVMargin
SH_ItemView_ActivateItemOnSingleClick
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
virtual int styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const const=0
virtual QRect subControlRect(ComplexControl control, const QStyleOptionComplex *option, SubControl subControl, const QWidget *widget) const const=0
void initFrom(const QWidget *widget)
AlignLeft
UniqueConnection
ShortcutFocusReason
ItemDataRole
Key_Escape
ControlModifier
MiddleButton
Orientation
RoundJoin
TextDontClip
WA_ShowWithoutActivating
QTextStream & left(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isActive() const const
void start()
void stop()
void timeout()
void setAutoRaise(bool enable)
void hideText()
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
bool isEmpty() const const
bool canConvert() const const
bool isValid() const const
int toInt(bool *ok) const const
QString toString() const const
void append(T &&t)
qsizetype size() const const
void showText(const QPoint &pos, const QString &text, QWidget *w)
QList< QAction * > actions() const const
virtual void enterEvent(QEnterEvent *event)
virtual bool event(QEvent *event) override
bool hasFocus() const const
void hide()
virtual void keyPressEvent(QKeyEvent *event)
virtual void leaveEvent(QEvent *event)
QPoint mapFrom(const QWidget *parent, const QPoint &pos) const const
QPoint mapFromGlobal(const QPoint &pos) const const
QPoint mapToGlobal(const QPoint &pos) const const
virtual void mouseMoveEvent(QMouseEvent *event)
virtual void mousePressEvent(QMouseEvent *event)
void repaint()
virtual void resizeEvent(QResizeEvent *event)
void setFocus()
void show()
virtual void showEvent(QShowEvent *event)
QStyle * style() const const
void update()
void updateGeometry()
void setUpdatesEnabled(bool enable)
bool isVisible() const const
void setWhatsThis(const QString &)
QWidget * window() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Sep 13 2024 11:55:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.