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 ;
897 painter.drawLine(6, y, width() - 6, y);
898 }
899
900 it = m_lines;
901 QPen pen;
902 pen.setWidth(2);
903 while (it.hasNext()) {
904 it.next();
905 pen.setColor(it.value());
906 painter.setPen(pen);
907 int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();
908 painter.drawLine(6, y, width() - 6, y);
909 }
910 }
911
912 // slider outline
913 QColor sliderColor(highlightColor);
914 sliderColor.setAlpha(50);
915 painter.fillRect(sliderRect, sliderColor);
916 painter.setPen(QPen(highlightColor, 0));
917 m_sliderRect = sliderRect;
918 // rounded rect looks ugly for some reason, so we draw 4 lines.
919 painter.drawLine(sliderRect.left(), sliderRect.top() + 1, sliderRect.left(), sliderRect.bottom() - 1);
920 painter.drawLine(sliderRect.right(), sliderRect.top() + 1, sliderRect.right(), sliderRect.bottom() - 1);
921 painter.drawLine(sliderRect.left() + 1, sliderRect.top(), sliderRect.right() - 1, sliderRect.top());
922 painter.drawLine(sliderRect.left() + 1, sliderRect.bottom(), sliderRect.right() - 1, sliderRect.bottom());
923}
924
925void KateScrollBar::normalPaintEvent(QPaintEvent *e)
926{
928
929 if (!m_showMarks) {
930 return;
931 }
932
933 QPainter painter(this);
934
936 opt.initFrom(this);
937 opt.subControls = QStyle::SC_None;
938 opt.activeSubControls = QStyle::SC_None;
939 opt.orientation = orientation();
940 opt.minimum = minimum();
941 opt.maximum = maximum();
942 opt.sliderPosition = sliderPosition();
943 opt.sliderValue = value();
944 opt.singleStep = singleStep();
945 opt.pageStep = pageStep();
946
948 int sideMargin = width() - rect.width();
949 if (sideMargin < 4) {
950 sideMargin = 4;
951 }
952 sideMargin /= 2;
953
954 QHashIterator<int, QColor> it = m_lines;
955 while (it.hasNext()) {
956 it.next();
957 painter.setPen(it.value());
958 if (it.key() < rect.top() || it.key() > rect.bottom()) {
959 painter.drawLine(0, it.key(), width(), it.key());
960 } else {
961 painter.drawLine(0, it.key(), sideMargin, it.key());
962 painter.drawLine(width() - sideMargin, it.key(), width(), it.key());
963 }
964 }
965}
966
967void KateScrollBar::resizeEvent(QResizeEvent *e)
968{
970 m_updateTimer.start();
971 m_lines.clear();
972 update();
973}
974
975void KateScrollBar::sliderChange(SliderChange change)
976{
977 // call parents implementation
979
981 redrawMarks();
982 } else if (change == QAbstractSlider::SliderRangeChange) {
983 marksChanged();
984 }
985
986 if (m_leftMouseDown || m_middleMouseDown) {
987 const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
988 const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
989 QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
990 }
991}
992
993void KateScrollBar::marksChanged()
994{
995 m_lines.clear();
996 update();
997}
998
999void KateScrollBar::redrawMarks()
1000{
1001 if (!m_showMarks) {
1002 return;
1003 }
1004 update();
1005}
1006
1007void KateScrollBar::recomputeMarksPositions()
1008{
1009 // get the style options to compute the scrollbar pixels
1011 initStyleOption(&opt);
1013
1014 // cache top margin and groove height
1015 const int top = grooveRect.top();
1016 const int h = grooveRect.height() - 1;
1017
1018 // make sure we have a sane height
1019 if (h <= 0) {
1020 return;
1021 }
1022
1023 // get total visible (=without folded) lines in the document
1024 int visibleLines = m_view->textFolding().visibleLines() - 1;
1025 if (m_view->config()->scrollPastEnd()) {
1026 visibleLines += m_viewInternal->linesDisplayed() - 1;
1027 visibleLines -= m_view->config()->autoCenterLines();
1028 }
1029
1030 // now repopulate the scrollbar lines list
1031 m_lines.clear();
1032 const QHash<int, KTextEditor::Mark *> &marks = m_doc->marks();
1033 for (QHash<int, KTextEditor::Mark *>::const_iterator i = marks.constBegin(); i != marks.constEnd(); ++i) {
1034 KTextEditor::Mark *mark = i.value();
1035 const int line = m_view->textFolding().lineToVisibleLine(mark->line);
1036 const double ratio = static_cast<double>(line) / visibleLines;
1037 m_lines.insert(top + (int)(h * ratio), KateRendererConfig::global()->lineMarkerColor((KTextEditor::Document::MarkTypes)mark->type));
1038 }
1039}
1040
1041void KateScrollBar::sliderMaybeMoved(int value)
1042{
1043 if (m_middleMouseDown) {
1044 // we only need to emit this signal once, as for the following slider
1045 // movements the signal sliderMoved() is already emitted.
1046 // Thus, set m_middleMouseDown to false right away.
1047 m_middleMouseDown = false;
1048 Q_EMIT sliderMMBMoved(value);
1049 }
1050}
1051// END
1052
1053// BEGIN KateCmdLineEditFlagCompletion
1054/**
1055 * This class provide completion of flags. It shows a short description of
1056 * each flag, and flags are appended.
1057 */
1058class KateCmdLineEditFlagCompletion : public KCompletion
1059{
1060public:
1061 KateCmdLineEditFlagCompletion()
1062 {
1063 ;
1064 }
1065
1066 QString makeCompletion(const QString & /*s*/) override
1067 {
1068 return QString();
1069 }
1070};
1071// END KateCmdLineEditFlagCompletion
1072
1073// BEGIN KateCmdLineEdit
1074KateCommandLineBar::KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent)
1075 : KateViewBarWidget(true, parent)
1076{
1077 QHBoxLayout *topLayout = new QHBoxLayout(centralWidget());
1078 topLayout->setContentsMargins(0, 0, 0, 0);
1079 m_lineEdit = new KateCmdLineEdit(this, view);
1080 connect(m_lineEdit, &KateCmdLineEdit::hideRequested, this, &KateCommandLineBar::hideMe);
1081 topLayout->addWidget(m_lineEdit);
1082
1083 QToolButton *helpButton = new QToolButton(this);
1084 helpButton->setAutoRaise(true);
1085 helpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
1086 topLayout->addWidget(helpButton);
1087 connect(helpButton, &QToolButton::clicked, this, &KateCommandLineBar::showHelpPage);
1088
1089 setFocusProxy(m_lineEdit);
1090}
1091
1092void KateCommandLineBar::showHelpPage()
1093{
1094 KHelpClient::invokeHelp(QStringLiteral("advanced-editing-tools-commandline"), QStringLiteral("katepart"));
1095}
1096
1097KateCommandLineBar::~KateCommandLineBar() = default;
1098
1099// inserts the given string in the command line edit and (if selected = true) selects it so the user
1100// can type over it if they want to
1101void KateCommandLineBar::setText(const QString &text, bool selected)
1102{
1103 m_lineEdit->setText(text);
1104 if (selected) {
1105 m_lineEdit->selectAll();
1106 }
1107}
1108
1109void KateCommandLineBar::execute(const QString &text)
1110{
1111 m_lineEdit->slotReturnPressed(text);
1112}
1113
1114KateCmdLineEdit::KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view)
1115 : KLineEdit()
1116 , m_view(view)
1117 , m_bar(bar)
1118 , m_msgMode(false)
1119 , m_histpos(0)
1120 , m_cmdend(0)
1121 , m_command(nullptr)
1122{
1123 connect(this, &KateCmdLineEdit::returnKeyPressed, this, &KateCmdLineEdit::slotReturnPressed);
1124
1125 setCompletionObject(KateCmd::self()->commandCompletionObject());
1126 setAutoDeleteCompletionObject(false);
1127
1128 m_hideTimer = new QTimer(this);
1129 m_hideTimer->setSingleShot(true);
1130 connect(m_hideTimer, &QTimer::timeout, this, &KateCmdLineEdit::hideLineEdit);
1131
1132 // make sure the timer is stopped when the user switches views. if not, focus will be given to the
1133 // wrong view when KateViewBar::hideCurrentBarWidget() is called after 4 seconds. (the timer is
1134 // used for showing things like "Success" for four seconds after the user has used the kate
1135 // command line)
1137}
1138
1139void KateCmdLineEdit::hideEvent(QHideEvent *e)
1140{
1141 Q_UNUSED(e);
1142}
1143
1144QString KateCmdLineEdit::helptext(const QPoint &) const
1145{
1146 const QString beg = QStringLiteral("<qt background=\"white\"><div><table width=\"100%\"><tr><td bgcolor=\"brown\"><font color=\"white\"><b>Help: <big>");
1147 const QString mid = QStringLiteral("</big></b></font></td></tr><tr><td>");
1148 const QString end = QStringLiteral("</td></tr></table></div><qt>");
1149
1150 const QString t = text();
1151 static const QRegularExpression re(QStringLiteral("\\s*help\\s+(.*)"));
1152 auto match = re.match(t);
1153 if (match.hasMatch()) {
1154 QString s;
1155 // get help for command
1156 const QString name = match.captured(1);
1157 if (name == QLatin1String("list")) {
1158 return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(QLatin1Char(' '))
1159 + i18n("<p>For help on individual commands, do <code>'help &lt;command&gt;'</code></p>") + end;
1160 } else if (!name.isEmpty()) {
1161 KTextEditor::Command *cmd = KateCmd::self()->queryCommand(name);
1162 if (cmd) {
1163 if (cmd->help(m_view, name, s)) {
1164 return beg + name + mid + s + end;
1165 } else {
1166 return beg + name + mid + i18n("No help for '%1'", name) + end;
1167 }
1168 } else {
1169 return beg + mid + i18n("No such command <b>%1</b>", name) + end;
1170 }
1171 }
1172 }
1173
1174 return beg + mid
1175 + i18n("<p>This is the Katepart <b>command line</b>.<br />"
1176 "Syntax: <code><b>command [ arguments ]</b></code><br />"
1177 "For a list of available commands, enter <code><b>help list</b></code><br />"
1178 "For help for individual commands, enter <code><b>help &lt;command&gt;</b></code></p>")
1179 + end;
1180}
1181
1182bool KateCmdLineEdit::event(QEvent *e)
1183{
1184 if (e->type() == QEvent::QueryWhatsThis) {
1185 setWhatsThis(helptext(QPoint()));
1186 e->accept();
1187 return true;
1188 }
1189 return KLineEdit::event(e);
1190}
1191
1192/**
1193 * Parse the text as a command.
1194 *
1195 * The following is a simple PEG grammar for the syntax of the command.
1196 * (A PEG grammar is like a BNF grammar, except that "/" stands for
1197 * ordered choice: only the first matching rule is used. In other words,
1198 * the parsing is short-circuited in the manner of the "or" operator in
1199 * programming languages, and so the grammar is unambiguous.)
1200 *
1201 * Text <- Range? Command
1202 * / Position
1203 * Range <- Position ("," Position)?
1204 * / "%"
1205 * Position <- Base Offset?
1206 * Base <- Line
1207 * / LastLine
1208 * / ThisLine
1209 * / Mark
1210 * Offset <- [+-] Base
1211 * Line <- [0-9]+
1212 * LastLine <- "$"
1213 * ThisLine <- "."
1214 * Mark <- "'" [a-z]
1215 */
1216
1217void KateCmdLineEdit::slotReturnPressed(const QString &text)
1218{
1219 static const QRegularExpression focusChangingCommands(QStringLiteral("^(?:buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e)$"));
1220
1221 if (text.isEmpty()) {
1222 return;
1223 }
1224 // silently ignore leading space characters
1225 uint n = 0;
1226 const uint textlen = text.length();
1227 while ((n < textlen) && (text[n].isSpace())) {
1228 n++;
1229 }
1230
1231 if (n >= textlen) {
1232 return;
1233 }
1234
1235 QString cmd = text.mid(n);
1236
1237 // Parse any leading range expression, and strip it (and maybe do some other transforms on the command).
1238 QString leadingRangeExpression;
1239 KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd);
1240
1241 // Built in help: if the command starts with "help", [try to] show some help
1242 if (cmd.startsWith(QLatin1String("help"))) {
1243 QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint()));
1244 clear();
1245 KateCmd::self()->appendHistory(cmd);
1246 m_histpos = KateCmd::self()->historyLength();
1247 m_oldText.clear();
1248 return;
1249 }
1250
1251 if (cmd.length() > 0) {
1252 KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd);
1253
1254 m_oldText = leadingRangeExpression + cmd;
1255 m_msgMode = true;
1256
1257 // if the command changes the focus itself, the bar should be hidden before execution.
1258 if (focusChangingCommands.matchView(QStringView(cmd).left(cmd.indexOf(QLatin1Char(' ')))).hasMatch()) {
1259 Q_EMIT hideRequested();
1260 }
1261
1262 if (!p) {
1263 setText(i18n("No such command: \"%1\"", cmd));
1264 } else if (range.isValid() && !p->supportsRange(cmd)) {
1265 // Raise message, when the command does not support ranges.
1266 setText(i18n("Error: No range allowed for command \"%1\".", cmd));
1267 } else {
1268 QString msg;
1269 if (p->exec(m_view, cmd, msg, range)) {
1270 // append command along with range (will be empty if none given) to history
1271 KateCmd::self()->appendHistory(leadingRangeExpression + cmd);
1272 m_histpos = KateCmd::self()->historyLength();
1273 m_oldText.clear();
1274
1275 if (msg.length() > 0) {
1276 setText(i18n("Success: ") + msg);
1277 } else if (isVisible()) {
1278 // always hide on success without message
1279 Q_EMIT hideRequested();
1280 }
1281 } else {
1282 if (msg.length() > 0) {
1283 if (msg.contains(QLatin1Char('\n'))) {
1284 // multiline error, use widget with more space
1286 } else {
1287 setText(msg);
1288 }
1289 } else {
1290 setText(i18n("Command \"%1\" failed.", cmd));
1291 }
1292 }
1293 }
1294 }
1295
1296 // clean up
1297 if (completionObject() != KateCmd::self()->commandCompletionObject()) {
1299 setCompletionObject(KateCmd::self()->commandCompletionObject());
1300 delete c;
1301 }
1302 m_command = nullptr;
1303 m_cmdend = 0;
1304
1305 if (!focusChangingCommands.matchView(QStringView(cmd).left(cmd.indexOf(QLatin1Char(' ')))).hasMatch()) {
1306 m_view->setFocus();
1307 }
1308
1309 if (isVisible()) {
1310 m_hideTimer->start(4000);
1311 }
1312}
1313
1314void KateCmdLineEdit::hideLineEdit() // unless i have focus ;)
1315{
1316 if (!hasFocus()) {
1317 Q_EMIT hideRequested();
1318 }
1319}
1320
1321void KateCmdLineEdit::focusInEvent(QFocusEvent *ev)
1322{
1323 if (m_msgMode) {
1324 m_msgMode = false;
1325 setText(m_oldText);
1326 selectAll();
1327 }
1328
1330}
1331
1332void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev)
1333{
1334 if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) {
1335 m_view->setFocus();
1336 hideLineEdit();
1337 clear();
1338 } else if (ev->key() == Qt::Key_Up) {
1339 fromHistory(true);
1340 } else if (ev->key() == Qt::Key_Down) {
1341 fromHistory(false);
1342 }
1343
1344 uint cursorpos = cursorPosition();
1346
1347 // during typing, let us see if we have a valid command
1348 if (!m_cmdend || cursorpos <= m_cmdend) {
1349 QChar c;
1350 if (!ev->text().isEmpty()) {
1351 c = ev->text().at(0);
1352 }
1353
1354 if (!m_cmdend && !c.isNull()) { // we have no command, so lets see if we got one
1355 if (!c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) {
1356 m_command = KateCmd::self()->queryCommand(text().trimmed());
1357 if (m_command) {
1358 // qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<<m_command<<". text is '"<<text()<<"'";
1359 // if the typed character is ":",
1360 // we try if the command has flag completions
1361 m_cmdend = cursorpos;
1362 // qCDebug(LOG_KTE)<<"keypress in commandline: Set m_cmdend to "<<m_cmdend;
1363 } else {
1364 m_cmdend = 0;
1365 }
1366 }
1367 } else { // since cursor is inside the command name, we reconsider it
1368 // qCDebug(LOG_KTE) << "keypress in commandline: \\W -- text is " << text();
1369 m_command = KateCmd::self()->queryCommand(text().trimmed());
1370 if (m_command) {
1371 // qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<<m_command;
1372 QString t = text();
1373 m_cmdend = 0;
1374 bool b = false;
1375 for (; (int)m_cmdend < t.length(); m_cmdend++) {
1376 if (t[m_cmdend].isLetter()) {
1377 b = true;
1378 }
1379 if (b && (!t[m_cmdend].isLetterOrNumber() && t[m_cmdend] != QLatin1Char('-') && t[m_cmdend] != QLatin1Char('_'))) {
1380 break;
1381 }
1382 }
1383
1384 if (c == QLatin1Char(':') && cursorpos == m_cmdend) {
1385 // check if this command wants to complete flags
1386 // qCDebug(LOG_KTE)<<"keypress in commandline: Checking if flag completion is desired!";
1387 }
1388 } else {
1389 // clean up if needed
1390 if (completionObject() != KateCmd::self()->commandCompletionObject()) {
1392 setCompletionObject(KateCmd::self()->commandCompletionObject());
1393 delete c;
1394 }
1395
1396 m_cmdend = 0;
1397 }
1398 }
1399
1400 // if we got a command, check if it wants to do something.
1401 if (m_command) {
1402 KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed());
1403 if (cmpl) {
1404 // We need to prepend the current command name + flag string
1405 // when completion is done
1406 // qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!";
1407
1408 setCompletionObject(cmpl);
1409 }
1410 }
1411 } else if (m_command && !ev->text().isEmpty()) {
1412 // check if we should call the commands processText()
1413 if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) {
1414 m_command->processText(m_view, text());
1415 }
1416 }
1417}
1418
1419void KateCmdLineEdit::fromHistory(bool up)
1420{
1421 if (!KateCmd::self()->historyLength()) {
1422 return;
1423 }
1424
1425 QString s;
1426
1427 if (up) {
1428 if (m_histpos > 0) {
1429 m_histpos--;
1430 s = KateCmd::self()->fromHistory(m_histpos);
1431 }
1432 } else {
1433 if (m_histpos < (KateCmd::self()->historyLength() - 1)) {
1434 m_histpos++;
1435 s = KateCmd::self()->fromHistory(m_histpos);
1436 } else {
1437 m_histpos = KateCmd::self()->historyLength();
1438 setText(m_oldText);
1439 }
1440 }
1441 if (!s.isEmpty()) {
1442 // Select the argument part of the command, so that it is easy to overwrite
1443 setText(s);
1444 static const QRegularExpression reCmd(QStringLiteral("^[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)"), QRegularExpression::UseUnicodePropertiesOption);
1445 const auto match = reCmd.match(text());
1446 if (match.hasMatch()) {
1447 setSelection(text().length() - match.capturedLength(1), match.capturedLength(1));
1448 }
1449 }
1450}
1451// END KateCmdLineEdit
1452
1453// BEGIN KateIconBorder
1454using namespace KTextEditor;
1455
1456KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent)
1457 : QWidget(parent)
1458 , m_view(internalView->m_view)
1459 , m_doc(internalView->doc())
1460 , m_viewInternal(internalView)
1461 , m_iconBorderOn(false)
1462 , m_lineNumbersOn(false)
1463 , m_relLineNumbersOn(false)
1464 , m_updateRelLineNumbers(false)
1465 , m_foldingMarkersOn(false)
1466 , m_dynWrapIndicatorsOn(false)
1467 , m_annotationBorderOn(false)
1468 , m_updatePositionToArea(true)
1469 , m_annotationItemDelegate(new KateAnnotationItemDelegate(this))
1470{
1471 setAcceptDrops(true);
1472 setAttribute(Qt::WA_StaticContents);
1473
1474 // See: https://doc.qt.io/qt-5/qwidget.html#update. As this widget does not
1475 // have a background, there's no need for Qt to erase the widget's area
1476 // before repainting. Enabling this prevents flickering when the widget is
1477 // repainted.
1478 setAttribute(Qt::WA_OpaquePaintEvent);
1479
1481 setMouseTracking(true);
1482 m_doc->setMarkDescription(Document::markType01, i18n("Bookmark"));
1483 m_doc->setMarkIcon(Document::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")));
1484
1485 connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
1486
1487 updateFont();
1488
1489 m_antiFlickerTimer.setSingleShot(true);
1490 m_antiFlickerTimer.setInterval(300);
1491 connect(&m_antiFlickerTimer, &QTimer::timeout, this, &KateIconBorder::highlightFolding);
1492
1493 // user interaction (scrolling) hides e.g. preview
1494 connect(m_view, &KTextEditor::ViewPrivate::displayRangeChanged, this, &KateIconBorder::displayRangeChanged);
1495}
1496
1497KateIconBorder::~KateIconBorder()
1498{
1499 delete m_foldingPreview;
1500 delete m_foldingRange;
1501}
1502
1503void KateIconBorder::setIconBorderOn(bool enable)
1504{
1505 if (enable == m_iconBorderOn) {
1506 return;
1507 }
1508
1509 m_iconBorderOn = enable;
1510
1511 m_updatePositionToArea = true;
1512
1513 QTimer::singleShot(0, this, SLOT(update()));
1514}
1515
1516void KateIconBorder::setAnnotationBorderOn(bool enable)
1517{
1518 if (enable == m_annotationBorderOn) {
1519 return;
1520 }
1521
1522 m_annotationBorderOn = enable;
1523
1524 // make sure the tooltip is hidden
1525 if (!m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
1526 m_hoveredAnnotationGroupIdentifier.clear();
1527 hideAnnotationTooltip();
1528 }
1529
1530 Q_EMIT m_view->annotationBorderVisibilityChanged(m_view, enable);
1531
1532 m_updatePositionToArea = true;
1533
1534 QTimer::singleShot(0, this, SLOT(update()));
1535}
1536
1537void KateIconBorder::removeAnnotationHovering()
1538{
1539 // remove hovering if it's still there
1540 if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
1541 m_hoveredAnnotationGroupIdentifier.clear();
1542 QTimer::singleShot(0, this, SLOT(update()));
1543 }
1544}
1545
1546void KateIconBorder::setLineNumbersOn(bool enable)
1547{
1548 if (enable == m_lineNumbersOn) {
1549 return;
1550 }
1551
1552 m_lineNumbersOn = enable;
1553 m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators;
1554
1555 m_updatePositionToArea = true;
1556
1557 QTimer::singleShot(0, this, SLOT(update()));
1558}
1559
1560void KateIconBorder::setRelLineNumbersOn(bool enable)
1561{
1562 if (enable == m_relLineNumbersOn) {
1563 return;
1564 }
1565
1566 m_relLineNumbersOn = enable;
1567 /*
1568 * We don't have to touch the m_dynWrapIndicatorsOn because
1569 * we already got it right from the m_lineNumbersOn
1570 */
1571 m_updatePositionToArea = true;
1572
1573 QTimer::singleShot(0, this, SLOT(update()));
1574}
1575
1576void KateIconBorder::updateForCursorLineChange()
1577{
1578 if (m_relLineNumbersOn) {
1579 m_updateRelLineNumbers = true;
1580 }
1581
1582 // always do normal update, e.g. for different current line color!
1583 update();
1584}
1585
1586void KateIconBorder::setDynWrapIndicators(int state)
1587{
1588 if (state == m_dynWrapIndicators) {
1589 return;
1590 }
1591
1592 m_dynWrapIndicators = state;
1593 m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state;
1594
1595 m_updatePositionToArea = true;
1596
1597 QTimer::singleShot(0, this, SLOT(update()));
1598}
1599
1600void KateIconBorder::setFoldingMarkersOn(bool enable)
1601{
1602 if (enable == m_foldingMarkersOn) {
1603 return;
1604 }
1605
1606 m_foldingMarkersOn = enable;
1607
1608 m_updatePositionToArea = true;
1609
1610 QTimer::singleShot(0, this, SLOT(update()));
1611}
1612
1613QSize KateIconBorder::sizeHint() const
1614{
1615 int w = 1; // Must be any value != 0 or we will never painted!
1616
1617 const int i = m_positionToArea.size();
1618 if (i > 0) {
1619 w = m_positionToArea.at(i - 1).first;
1620 }
1621
1622 return QSize(w, 0);
1623}
1624
1625// This function (re)calculates the maximum width of any of the digit characters (0 -> 9)
1626// for graceful handling of variable-width fonts as the linenumber font.
1627void KateIconBorder::updateFont()
1628{
1629 // Loop to determine the widest numeric character in the current font.
1630 const QFontMetricsF &fm = m_view->renderer()->currentFontMetrics();
1631 m_maxCharWidth = 0.0;
1632 for (char c = '0'; c <= '9'; ++c) {
1633 const qreal charWidth = ceil(fm.horizontalAdvance(QLatin1Char(c)));
1634 m_maxCharWidth = qMax(m_maxCharWidth, charWidth);
1635 }
1636
1637 // NOTE/TODO(or not) Take size of m_dynWrapIndicatorChar into account.
1638 // It's a multi-char and it's reported size is, even with a mono-space font,
1639 // bigger than each digit, e.g. 10 vs 12. Currently it seems to work even with
1640 // "Line Numbers Off" but all these width calculating looks slightly hacky
1641
1642 // the icon pane scales with the font...
1643 m_iconAreaWidth = fm.height();
1644
1645 // Only for now, later may that become an own value
1646 m_foldingAreaWidth = m_iconAreaWidth;
1647
1648 calcAnnotationBorderWidth();
1649
1650 m_updatePositionToArea = true;
1651
1653 this,
1654 [this] {
1655 update();
1656 },
1658}
1659
1660int KateIconBorder::lineNumberWidth() const
1661{
1662 int width = 0;
1663 // Avoid unneeded expensive calculations ;-)
1664 if (m_lineNumbersOn) {
1665 // width = (number of digits + 1) * char width
1666 const int digits = (int)ceil(log10((double)(m_view->doc()->lines() + 1)));
1667 width = (int)ceil((digits + 1) * m_maxCharWidth);
1668 }
1669
1670 if ((width < 1) && m_dynWrapIndicatorsOn && m_view->config()->dynWordWrap()) {
1671 // FIXME Why 2x? because of above (number of digits + 1)
1672 // -> looks to me like a hint for bad calculation elsewhere
1673 width = 2 * m_maxCharWidth;
1674 }
1675
1676 return width;
1677}
1678
1679void KateIconBorder::dragEnterEvent(QDragEnterEvent *event)
1680{
1681 m_view->m_viewInternal->dragEnterEvent(event);
1682}
1683
1684void KateIconBorder::dragMoveEvent(QDragMoveEvent *event)
1685{
1686 // FIXME Just calling m_view->m_viewInternal->dragMoveEvent(e) don't work
1687 // as intended, we need to set the cursor at column 1
1688 // Is there a way to change the pos of the event?
1689 QPoint pos(0, event->position().y());
1690 // Code copy of KateViewInternal::dragMoveEvent
1691 m_view->m_viewInternal->placeCursor(pos, true, false);
1692 m_view->m_viewInternal->fixDropEvent(event);
1693}
1694
1695void KateIconBorder::dropEvent(QDropEvent *event)
1696{
1697 m_view->m_viewInternal->dropEvent(event);
1698}
1699
1700void KateIconBorder::paintEvent(QPaintEvent *e)
1701{
1702 paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height());
1703}
1704
1705static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open)
1706{
1708
1709 qreal size = qMin(width, height);
1710
1711 if (open) {
1712 // Paint unfolded icon less pushy
1713 if (KColorUtils::luma(c) < 0.25) {
1714 c = KColorUtils::darken(c);
1715 } else {
1716 c = KColorUtils::shade(c, 0.1);
1717 }
1718
1719 } else {
1720 // Paint folded icon in contrast to popup highlighting
1721 if (KColorUtils::luma(c) > 0.25) {
1722 c = KColorUtils::darken(c);
1723 } else {
1724 c = KColorUtils::shade(c, 0.1);
1725 }
1726 }
1727
1728 QPen pen;
1730 pen.setColor(c);
1731 pen.setWidthF(1.5);
1732 painter.setPen(pen);
1733 painter.setBrush(c);
1734
1735 // let some border, if possible
1736 size *= 0.6;
1737
1738 qreal halfSize = size / 2;
1739 qreal halfSizeP = halfSize * 0.6;
1740 QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2);
1741
1742 if (open) {
1743 QPointF points[3] = {middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP)};
1744 painter.drawConvexPolygon(points, 3);
1745 } else {
1746 QPointF points[3] = {middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0)};
1747 painter.drawConvexPolygon(points, 3);
1748 }
1749
1750 painter.setRenderHint(QPainter::Antialiasing, false);
1751}
1752
1753/**
1754 * Helper class for an identifier which can be an empty or non-empty string or invalid.
1755 * Avoids complicated explicit statements in code dealing with the identifier
1756 * received as QVariant from a model.
1757 */
1758class KateAnnotationGroupIdentifier
1759{
1760public:
1761 KateAnnotationGroupIdentifier(const QVariant &identifier)
1762 : m_isValid(identifier.isValid() && identifier.canConvert<QString>())
1763 , m_id(m_isValid ? identifier.toString() : QString())
1764 {
1765 }
1766 KateAnnotationGroupIdentifier() = default;
1767 KateAnnotationGroupIdentifier(const KateAnnotationGroupIdentifier &rhs) = default;
1768
1769 KateAnnotationGroupIdentifier &operator=(const KateAnnotationGroupIdentifier &rhs)
1770 {
1771 m_isValid = rhs.m_isValid;
1772 m_id = rhs.m_id;
1773 return *this;
1774 }
1775 KateAnnotationGroupIdentifier &operator=(const QVariant &identifier)
1776 {
1777 m_isValid = (identifier.isValid() && identifier.canConvert<QString>());
1778 if (m_isValid) {
1779 m_id = identifier.toString();
1780 } else {
1781 m_id.clear();
1782 }
1783 return *this;
1784 }
1785
1786 bool operator==(const KateAnnotationGroupIdentifier &rhs) const
1787 {
1788 return (m_isValid == rhs.m_isValid) && (!m_isValid || (m_id == rhs.m_id));
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
1795 void clear()
1796 {
1797 m_isValid = false;
1798 m_id.clear();
1799 }
1800 bool isValid() const
1801 {
1802 return m_isValid;
1803 }
1804 const QString &id() const
1805 {
1806 return m_id;
1807 }
1808
1809private:
1810 bool m_isValid = false;
1811 QString m_id;
1812};
1813
1814/**
1815 * Helper class for iterative calculation of data regarding the position
1816 * of a line with regard to annotation item grouping.
1817 */
1818class KateAnnotationGroupPositionState
1819{
1820public:
1821 /**
1822 * @param startz first rendered displayed line
1823 * @param isUsed flag whether the KateAnnotationGroupPositionState object will
1824 * be used or is just created due to being on the stack
1825 */
1826 KateAnnotationGroupPositionState(KateViewInternal *viewInternal,
1827 const KTextEditor::AnnotationModel *model,
1828 const QString &hoveredAnnotationGroupIdentifier,
1829 uint startz,
1830 bool isUsed);
1831 /**
1832 * @param styleOption option to fill with data for the given line
1833 * @param z rendered displayed line
1834 * @param realLine real line which is rendered here (passed to avoid another look-up)
1835 */
1836 void nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine);
1837
1838private:
1839 KateViewInternal *m_viewInternal;
1840 const KTextEditor::AnnotationModel *const m_model;
1841 const QString m_hoveredAnnotationGroupIdentifier;
1842
1843 int m_visibleWrappedLineInAnnotationGroup = -1;
1844 KateAnnotationGroupIdentifier m_lastAnnotationGroupIdentifier;
1845 KateAnnotationGroupIdentifier m_nextAnnotationGroupIdentifier;
1846 bool m_isSameAnnotationGroupsSinceLast = false;
1847};
1848
1849KateAnnotationGroupPositionState::KateAnnotationGroupPositionState(KateViewInternal *viewInternal,
1850 const KTextEditor::AnnotationModel *model,
1851 const QString &hoveredAnnotationGroupIdentifier,
1852 uint startz,
1853 bool isUsed)
1854 : m_viewInternal(viewInternal)
1855 , m_model(model)
1856 , m_hoveredAnnotationGroupIdentifier(hoveredAnnotationGroupIdentifier)
1857{
1858 if (!isUsed) {
1859 return;
1860 }
1861
1862 if (!m_model || (static_cast<int>(startz) >= m_viewInternal->cache()->viewCacheLineCount())) {
1863 return;
1864 }
1865
1866 const auto realLineAtStart = m_viewInternal->cache()->viewLine(startz).line();
1867 m_nextAnnotationGroupIdentifier = m_model->data(realLineAtStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1868 if (m_nextAnnotationGroupIdentifier.isValid()) {
1869 // estimate state of annotation group before first rendered line
1870 if (startz == 0) {
1871 if (realLineAtStart > 0) {
1872 // TODO: here we would want to scan until the next line that would be displayed,
1873 // to see if there are any group changes until then
1874 // for now simply taking neighbour line into account, not a grave bug on the first displayed line
1875 m_lastAnnotationGroupIdentifier = m_model->data(realLineAtStart - 1, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1876 m_isSameAnnotationGroupsSinceLast = (m_lastAnnotationGroupIdentifier == m_nextAnnotationGroupIdentifier);
1877 }
1878 } else {
1879 const auto realLineBeforeStart = m_viewInternal->cache()->viewLine(startz - 1).line();
1880 m_lastAnnotationGroupIdentifier = m_model->data(realLineBeforeStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1881 if (m_lastAnnotationGroupIdentifier.isValid()) {
1882 if (m_lastAnnotationGroupIdentifier.id() == m_nextAnnotationGroupIdentifier.id()) {
1883 m_isSameAnnotationGroupsSinceLast = true;
1884 // estimate m_visibleWrappedLineInAnnotationGroup from lines before startz
1885 for (uint z = startz; z > 0; --z) {
1886 const auto realLine = m_viewInternal->cache()->viewLine(z - 1).line();
1887 const KateAnnotationGroupIdentifier identifier =
1888 m_model->data(realLine, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1889 if (identifier != m_lastAnnotationGroupIdentifier) {
1890 break;
1891 }
1892 ++m_visibleWrappedLineInAnnotationGroup;
1893 }
1894 }
1895 }
1896 }
1897 }
1898}
1899
1900void KateAnnotationGroupPositionState::nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine)
1901{
1902 styleOption.wrappedLine = m_viewInternal->cache()->viewLine(z).viewLine();
1903 styleOption.wrappedLineCount = m_viewInternal->cache()->viewLineCount(realLine);
1904
1905 // Estimate position in group
1906 const KateAnnotationGroupIdentifier annotationGroupIdentifier = m_nextAnnotationGroupIdentifier;
1907 bool isSameAnnotationGroupsSinceThis = false;
1908 // Calculate next line's group identifier
1909 // shortcut: assuming wrapped lines are always displayed together, test is simple
1910 if (styleOption.wrappedLine + 1 < styleOption.wrappedLineCount) {
1911 m_nextAnnotationGroupIdentifier = annotationGroupIdentifier;
1912 isSameAnnotationGroupsSinceThis = true;
1913 } else {
1914 if (static_cast<int>(z + 1) < m_viewInternal->cache()->viewCacheLineCount()) {
1915 const int realLineAfter = m_viewInternal->cache()->viewLine(z + 1).line();
1916 // search for any realLine with a different group id, also the non-displayed
1917 int rl = realLine + 1;
1918 for (; rl <= realLineAfter; ++rl) {
1919 m_nextAnnotationGroupIdentifier = m_model->data(rl, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1920 if (!m_nextAnnotationGroupIdentifier.isValid() || (m_nextAnnotationGroupIdentifier.id() != annotationGroupIdentifier.id())) {
1921 break;
1922 }
1923 }
1924 isSameAnnotationGroupsSinceThis = (rl > realLineAfter);
1925 if (rl < realLineAfter) {
1926 m_nextAnnotationGroupIdentifier = m_model->data(realLineAfter, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1927 }
1928 } else {
1929 // TODO: check next line after display end
1930 m_nextAnnotationGroupIdentifier.clear();
1931 isSameAnnotationGroupsSinceThis = false;
1932 }
1933 }
1934
1935 if (annotationGroupIdentifier.isValid()) {
1936 if (m_hoveredAnnotationGroupIdentifier == annotationGroupIdentifier.id()) {
1937 styleOption.state |= QStyle::State_MouseOver;
1938 } else {
1939 styleOption.state &= ~QStyle::State_MouseOver;
1940 }
1941
1942 if (m_isSameAnnotationGroupsSinceLast) {
1943 ++m_visibleWrappedLineInAnnotationGroup;
1944 } else {
1945 m_visibleWrappedLineInAnnotationGroup = 0;
1946 }
1947
1949 if (!m_isSameAnnotationGroupsSinceLast) {
1951 }
1952 if (!isSameAnnotationGroupsSinceThis) {
1954 }
1955 } else {
1956 m_visibleWrappedLineInAnnotationGroup = 0;
1957 }
1958 styleOption.visibleWrappedLineInGroup = m_visibleWrappedLineInAnnotationGroup;
1959
1960 m_lastAnnotationGroupIdentifier = m_nextAnnotationGroupIdentifier;
1961 m_isSameAnnotationGroupsSinceLast = isSameAnnotationGroupsSinceThis;
1962}
1963
1964void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height)
1965{
1966 const uint h = m_view->renderer()->lineHeight();
1967 const uint startz = (y / h);
1968 const uint endz = qMin(startz + 1 + (height / h), static_cast<uint>(m_viewInternal->cache()->viewCacheLineCount()));
1969 const uint currentLine = m_view->cursorPosition().line();
1970
1971 // center the folding boxes
1972 int m_px = (h - 11) / 2;
1973 if (m_px < 0) {
1974 m_px = 0;
1975 }
1976
1977 // Ensure we miss no change of the count of line number digits
1978 const int newNeededWidth = lineNumberWidth();
1979
1980 if (m_updatePositionToArea || (newNeededWidth != m_lineNumberAreaWidth)) {
1981 m_lineNumberAreaWidth = newNeededWidth;
1982 m_updatePositionToArea = true;
1983 m_positionToArea.clear();
1984 }
1985
1986 QPainter p(this);
1987 p.setRenderHints(QPainter::TextAntialiasing);
1988 p.setFont(m_view->renderer()->currentFont()); // for line numbers
1989
1990 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
1991 KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, m_hoveredAnnotationGroupIdentifier, startz, m_annotationBorderOn);
1992
1993 // Fetch often used data only once, improve readability
1994 const int w = width();
1995 const QColor iconBarColor = m_view->rendererConfig()->iconBarColor(); // Effective our background
1996 const QColor lineNumberColor = m_view->rendererConfig()->lineNumberColor();
1997 const QColor backgroundColor = m_view->rendererConfig()->backgroundColor(); // Of the edit area
1998 const QColor currentLineHighlight = m_view->rendererConfig()->highlightedLineColor(); // Of the edit area
1999
2000 // Paint the border in chunks line by line
2001 for (uint z = startz; z < endz; z++) {
2002 // Painting coordinates, lineHeight * lineNumber
2003 const uint y = h * z;
2004
2005 // Paint the border in chunks left->right, remember used width
2006 uint lnX = 0;
2007
2008 // get line for this coordinates if possible
2009 const KateTextLayout lineLayout = m_viewInternal->cache()->viewLine(z);
2010 const int realLine = lineLayout.line();
2011
2012 // Paint background over full width
2013 p.fillRect(lnX, y, w, h, iconBarColor);
2014
2015 // overpaint with current line highlighting over full width
2016 const bool isCurrentLine = (realLine == static_cast<int>(currentLine)) && lineLayout.includesCursor(m_view->cursorPosition());
2017 if (isCurrentLine) {
2018 p.fillRect(lnX, y, w, h, currentLineHighlight);
2019 }
2020
2021 // for real lines we need to do more stuff ;=)
2022 if (realLine >= 0) {
2023 // icon pane
2024 if (m_iconBorderOn) {
2025 const uint mrk(m_doc->mark(realLine)); // call only once
2026 if (mrk && lineLayout.startCol() == 0) {
2027 for (uint bit = 0; bit < 32; bit++) {
2028 Document::MarkTypes markType = (Document::MarkTypes)(1U << bit);
2029 if (mrk & markType) {
2030 const QIcon markIcon = m_doc->markIcon(markType);
2031
2032 if (!markIcon.isNull() && h > 0 && m_iconAreaWidth > 0) {
2033 const int s = qMin(m_iconAreaWidth, static_cast<int>(h)) - 2;
2034
2035 // center the mark pixmap
2036 const int x_px = qMax(m_iconAreaWidth - s, 0) / 2;
2037 const int y_px = qMax(static_cast<int>(h) - s, 0) / 2;
2038
2039 markIcon.paint(&p, lnX + x_px, y + y_px, s, s);
2040 }
2041 }
2042 }
2043 }
2044
2045 lnX += m_iconAreaWidth;
2046 if (m_updatePositionToArea) {
2047 m_positionToArea.push_back(AreaPosition(lnX, IconBorder));
2048 }
2049 }
2050
2051 // annotation information
2052 if (m_annotationBorderOn) {
2053 // Draw a border line between annotations and the line numbers
2054 p.setPen(lineNumberColor);
2055 p.setBrush(lineNumberColor);
2056
2057 const qreal borderX = lnX + m_annotationAreaWidth + 0.5;
2058 p.drawLine(QPointF(borderX, y + 0.5), QPointF(borderX, y + h - 0.5));
2059
2060 if (model) {
2062 initStyleOption(&styleOption);
2063 styleOption.rect.setRect(lnX, y, m_annotationAreaWidth, h);
2064 annotationGroupPositionState.nextLine(styleOption, z, realLine);
2065
2066 m_annotationItemDelegate->paint(&p, styleOption, model, realLine);
2067 }
2068
2069 lnX += m_annotationAreaWidth + m_separatorWidth;
2070 if (m_updatePositionToArea) {
2071 m_positionToArea.push_back(AreaPosition(lnX, AnnotationBorder));
2072 }
2073 }
2074
2075 // line number
2076 if (m_lineNumbersOn || m_dynWrapIndicatorsOn) {
2077 QColor usedLineNumberColor;
2078 const int distanceToCurrent = abs(realLine - static_cast<int>(currentLine));
2079 if (distanceToCurrent == 0) {
2080 usedLineNumberColor = m_view->rendererConfig()->currentLineNumberColor();
2081 } else {
2082 usedLineNumberColor = lineNumberColor;
2083 }
2084 p.setPen(usedLineNumberColor);
2085 p.setBrush(usedLineNumberColor);
2086
2087 if (lineLayout.startCol() == 0) {
2088 if (m_relLineNumbersOn) {
2089 if (distanceToCurrent == 0) {
2090 p.drawText(lnX + m_maxCharWidth / 2,
2091 y,
2092 m_lineNumberAreaWidth - m_maxCharWidth,
2093 h,
2095 QString::number(realLine + 1));
2096 } else {
2097 p.drawText(lnX + m_maxCharWidth / 2,
2098 y,
2099 m_lineNumberAreaWidth - m_maxCharWidth,
2100 h,
2102 QString::number(distanceToCurrent));
2103 }
2104 if (m_updateRelLineNumbers) {
2105 m_updateRelLineNumbers = false;
2106 update();
2107 }
2108 } else if (m_lineNumbersOn) {
2109 p.drawText(lnX + m_maxCharWidth / 2,
2110 y,
2111 m_lineNumberAreaWidth - m_maxCharWidth,
2112 h,
2114 QString::number(realLine + 1));
2115 }
2116 } else if (m_dynWrapIndicatorsOn) {
2117 p.drawText(lnX + m_maxCharWidth / 2,
2118 y,
2119 m_lineNumberAreaWidth - m_maxCharWidth,
2120 h,
2122 m_dynWrapIndicatorChar);
2123 }
2124
2125 lnX += m_lineNumberAreaWidth + m_separatorWidth;
2126 if (m_updatePositionToArea) {
2127 m_positionToArea.push_back(AreaPosition(lnX, LineNumbers));
2128 }
2129 }
2130
2131 // modified line system
2132 if (m_view->config()->lineModification() && !m_doc->url().isEmpty()) {
2133 const auto tl = m_doc->plainKateTextLine(realLine);
2134 if (tl.markedAsModified()) {
2135 p.fillRect(lnX, y, m_modAreaWidth, h, m_view->rendererConfig()->modifiedLineColor());
2136 } else if (tl.markedAsSavedOnDisk()) {
2137 p.fillRect(lnX, y, m_modAreaWidth, h, m_view->rendererConfig()->savedLineColor());
2138 }
2139
2140 lnX += m_modAreaWidth; // No m_separatorWidth
2141 if (m_updatePositionToArea) {
2142 m_positionToArea.push_back(AreaPosition(lnX, None));
2143 }
2144 }
2145
2146 // folding markers
2147 if (m_foldingMarkersOn) {
2148 const QColor foldingColor(m_view->rendererConfig()->foldingColor());
2149 // possible additional folding highlighting
2150 if (m_foldingRange && m_foldingRange->overlapsLine(realLine)) {
2151 p.fillRect(lnX, y, m_foldingAreaWidth, h, foldingColor);
2152 }
2153
2154 if (lineLayout.startCol() == 0) {
2155 QList<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>> startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine);
2156 bool anyFolded = false;
2157 for (int i = 0; i < startingRanges.size(); ++i) {
2158 if (startingRanges[i].second & Kate::TextFolding::Folded) {
2159 anyFolded = true;
2160 }
2161 }
2162 if (!m_view->config()->showFoldingOnHoverOnly() || m_mouseOver) {
2163 if (!startingRanges.isEmpty() || m_doc->buffer().isFoldingStartingOnLine(realLine).first) {
2164 if (anyFolded) {
2165 paintTriangle(p, foldingColor, lnX, y, m_foldingAreaWidth, h, false);
2166 } else {
2167 // Don't try to use currentLineNumberColor, the folded icon gets also not highligted
2168 paintTriangle(p, lineNumberColor, lnX, y, m_foldingAreaWidth, h, true);
2169 }
2170 }
2171 }
2172 }
2173
2174 lnX += m_foldingAreaWidth;
2175 if (m_updatePositionToArea) {
2176 m_positionToArea.push_back(AreaPosition(lnX, FoldingMarkers));
2177 }
2178 }
2179 }
2180
2181 // Overpaint again the end to simulate some margin to the edit area,
2182 // so that the text not looks like stuck to the border
2183 // we do this AFTER all other painting to ensure this leaves no artifacts
2184 // we kill 2 separator widths as we will below paint a line over this
2185 // otherwise this has some minimal overlap and looks ugly e.g. for scaled rendering
2186 p.fillRect(w - 2 * m_separatorWidth, y, w, h, backgroundColor);
2187
2188 // overpaint again with selection or current line highlighting if necessary
2189 if (realLine >= 0 && m_view->selection() && !m_view->blockSelection() && m_view->selectionRange().start() < lineLayout.start()
2190 && m_view->selectionRange().end() >= lineLayout.start()) {
2191 // selection overpaint to signal the end of the previous line is included in the selection
2192 p.fillRect(w - 2 * m_separatorWidth, y, w, h, m_view->rendererConfig()->selectionColor());
2193 } else if (isCurrentLine) {
2194 // normal current line overpaint
2195 p.fillRect(w - 2 * m_separatorWidth, y, w, h, currentLineHighlight);
2196 }
2197
2198 // add separator line if needed
2199 // we do this AFTER all other painting to ensure this leaves no artifacts
2200 p.setPen(m_view->rendererConfig()->separatorColor());
2201 p.setBrush(m_view->rendererConfig()->separatorColor());
2202 p.drawLine(w - 2 * m_separatorWidth, y, w - 2 * m_separatorWidth, y + h);
2203
2204 // we might need to trigger geometry updates
2205 if ((realLine >= 0) && m_updatePositionToArea) {
2206 m_updatePositionToArea = false;
2207 // Don't forget our "text-stuck-to-border" protector + border line
2208 lnX += 2 * m_separatorWidth;
2209 m_positionToArea.push_back(AreaPosition(lnX, None));
2210
2211 // Now that we know our needed space, ensure we are painted properly
2212 // we still keep painting to not have strange flicker
2213 QTimer::singleShot(0, this, &KateIconBorder::delayedUpdateOfSizeWithRepaint);
2214 }
2215 }
2216}
2217
2218KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const
2219{
2220 auto it = std::find_if(m_positionToArea.cbegin(), m_positionToArea.cend(), [p](const AreaPosition &ap) {
2221 return p.x() <= ap.first;
2222 });
2223 if (it != m_positionToArea.cend()) {
2224 return it->second;
2225 }
2226 return None;
2227}
2228
2229void KateIconBorder::mousePressEvent(QMouseEvent *e)
2230{
2231 const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->position().y());
2232 if (t.isValid()) {
2233 m_lastClickedLine = t.line();
2234 const auto area = positionToArea(e->pos());
2235 // IconBorder and AnnotationBorder have their own behavior; don't forward to view
2236 if (area != IconBorder && area != AnnotationBorder) {
2237 const auto pos = QPoint(0, e->position().y());
2238 if (area == LineNumbers && e->button() == Qt::LeftButton && !(e->modifiers() & Qt::ShiftModifier)) {
2239 // setup view so the following mousePressEvent will select the line
2240 m_viewInternal->beginSelectLine(pos);
2241 }
2243 m_viewInternal->mousePressEvent(&forward);
2244 }
2245 return e->accept();
2246 }
2247
2249}
2250
2251void KateIconBorder::highlightFoldingDelayed(int line)
2252{
2253 if ((line == m_currentLine) || (line >= m_doc->buffer().lines())) {
2254 return;
2255 }
2256
2257 m_currentLine = line;
2258
2259 if (m_foldingRange) {
2260 // We are for a while in the folding area, no need for delay
2261 highlightFolding();
2262
2263 } else if (!m_antiFlickerTimer.isActive()) {
2264 m_antiFlickerTimer.start();
2265 }
2266}
2267
2268void KateIconBorder::highlightFolding()
2269{
2270 // compute to which folding range we belong
2271 // FIXME: optimize + perhaps have some better threshold or use timers!
2273 for (int line = m_currentLine; line >= qMax(0, m_currentLine - 1024); --line) {
2274 // try if we have folding range from that line, should be fast per call
2275 KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line);
2276 if (!foldingRange.isValid()) {
2277 continue;
2278 }
2279
2280 // does the range reach us?
2281 if (foldingRange.overlapsLine(m_currentLine)) {
2282 newRange = foldingRange;
2283 break;
2284 }
2285 }
2286
2287 if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) {
2288 // new range equals the old one, nothing to do.
2289 return;
2290 }
2291
2292 // the ranges differ, delete the old, if it exists
2293 delete m_foldingRange;
2294 m_foldingRange = nullptr;
2295 // New range, new preview!
2296 delete m_foldingPreview;
2297
2298 bool showPreview = false;
2299
2300 if (newRange.isValid()) {
2301 // When next line is not visible we have a folded range, only then we want a preview!
2302 showPreview = !m_view->textFolding().isLineVisible(newRange.start().line() + 1);
2303
2304 // qCDebug(LOG_KTE) << "new folding hl-range:" << newRange;
2305 m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight);
2307
2308 // create highlighting color
2309 // we avoid alpha as overpainting leads to ugly lines (https://bugreports.qt.io/browse/QTBUG-66036)
2310 attr->setBackground(QBrush(m_view->rendererConfig()->foldingColor()));
2311
2312 m_foldingRange->setView(m_view);
2313 // use z depth defined in moving ranges interface
2314 m_foldingRange->setZDepth(-100.0);
2315 m_foldingRange->setAttribute(attr);
2316 }
2317
2318 // show text preview, if a folded region starts here...
2319 // ...but only when main window is active (#392396)
2320 const bool isWindowActive = !window() || window()->isActiveWindow();
2321 if (showPreview && m_view->config()->foldingPreview() && isWindowActive) {
2322 m_foldingPreview = new KateTextPreview(m_view, this);
2323 m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating);
2324 m_foldingPreview->setFrameStyle(QFrame::StyledPanel);
2325
2326 // Calc how many lines can be displayed in the popup
2327 const int lineHeight = m_view->renderer()->lineHeight();
2328 const int foldingStartLine = m_foldingRange->start().line();
2329 // FIXME Is there really no easier way to find lineInDisplay?
2330 const QPoint pos = m_viewInternal->mapFrom(m_view, m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0)));
2331 const int lineInDisplay = pos.y() / lineHeight;
2332 // Allow slightly overpainting of the view bottom to proper cover all lines
2333 const int extra = (m_viewInternal->height() % lineHeight) > (lineHeight * 0.6) ? 1 : 0;
2334 const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, m_viewInternal->linesDisplayed() - lineInDisplay + extra);
2335
2336 m_foldingPreview->resize(m_viewInternal->width(), lineCount * lineHeight + 2 * m_foldingPreview->frameWidth());
2337 const int xGlobal = mapToGlobal(QPoint(width(), 0)).x();
2338 const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0))).y();
2339 m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft());
2340 m_foldingPreview->setLine(foldingStartLine);
2341 m_foldingPreview->setCenterView(false);
2342 m_foldingPreview->setShowFoldedLines(true);
2343 m_foldingPreview->raise();
2344 m_foldingPreview->show();
2345 }
2346}
2347
2348void KateIconBorder::hideFolding()
2349{
2350 if (m_antiFlickerTimer.isActive()) {
2351 m_antiFlickerTimer.stop();
2352 }
2353
2354 m_currentLine = -1;
2355 delete m_foldingRange;
2356 m_foldingRange = nullptr;
2357
2358 delete m_foldingPreview;
2359}
2360
2361void KateIconBorder::enterEvent(QEnterEvent *event)
2362{
2363 m_mouseOver = true;
2364 if (m_view->config()->showFoldingOnHoverOnly())
2365 repaint();
2367}
2368
2369void KateIconBorder::leaveEvent(QEvent *event)
2370{
2371 m_mouseOver = false;
2372 hideFolding();
2373 removeAnnotationHovering();
2374 if (m_view->config()->showFoldingOnHoverOnly())
2375 repaint();
2376
2378}
2379
2380void KateIconBorder::mouseMoveEvent(QMouseEvent *e)
2381{
2382 const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->position().y());
2383 if (!t.isValid()) {
2384 // Cleanup everything which may be shown
2385 removeAnnotationHovering();
2386 hideFolding();
2387
2388 } else {
2389 const BorderArea area = positionToArea(e->pos());
2390 if (area == FoldingMarkers) {
2391 highlightFoldingDelayed(t.line());
2392 } else {
2393 hideFolding();
2394 }
2395 if (area == AnnotationBorder) {
2396 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2397 if (model) {
2398 m_hoveredAnnotationGroupIdentifier = model->data(t.line(), (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole).toString();
2399 const QPoint viewRelativePos = m_view->mapFromGlobal(e->globalPosition()).toPoint();
2400 QHelpEvent helpEvent(QEvent::ToolTip, viewRelativePos, e->globalPosition().toPoint());
2402 initStyleOption(&styleOption);
2403 styleOption.rect = annotationLineRectInView(t.line());
2404 setStyleOptionLineData(&styleOption, e->position().y(), t.line(), model, m_hoveredAnnotationGroupIdentifier);
2405 m_annotationItemDelegate->helpEvent(&helpEvent, m_view, styleOption, model, t.line());
2406
2407 QTimer::singleShot(0, this, SLOT(update()));
2408 }
2409 } else {
2410 if (area == IconBorder) {
2411 m_doc->requestMarkTooltip(t.line(), e->globalPosition().toPoint());
2412 }
2413
2414 m_hoveredAnnotationGroupIdentifier.clear();
2415 QTimer::singleShot(0, this, SLOT(update()));
2416 }
2417 if (area != IconBorder) {
2418 QPoint p = m_viewInternal->mapFromGlobal(e->globalPosition().toPoint());
2419 QMouseEvent forward(QEvent::MouseMove, p, m_viewInternal->mapToGlobal(p), e->button(), e->buttons(), e->modifiers());
2420 m_viewInternal->mouseMoveEvent(&forward);
2421 }
2422 }
2423
2425}
2426
2427void KateIconBorder::mouseReleaseEvent(QMouseEvent *e)
2428{
2429 const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->position().y()).line();
2430 if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) {
2431 const BorderArea area = positionToArea(e->pos());
2432 if (area == IconBorder) {
2433 if (e->button() == Qt::LeftButton) {
2434 if (!m_doc->handleMarkClick(cursorOnLine)) {
2435 KateViewConfig *config = m_view->config();
2436 const uint editBits = m_doc->editableMarks();
2437 // is the default or the only editable mark
2438 bool ctrlPressed = QGuiApplication::keyboardModifiers() == Qt::KeyboardModifier::ControlModifier;
2439 if (qPopulationCount(editBits) == 1 || ctrlPressed) {
2440 const uint singleMark = (qPopulationCount(editBits) > 1) ? (editBits & config->defaultMarkType()) : editBits;
2441 if (m_doc->mark(cursorOnLine) & singleMark) {
2442 m_doc->removeMark(cursorOnLine, singleMark);
2443 } else {
2444 m_doc->addMark(cursorOnLine, singleMark);
2445 }
2446 } else if (config->allowMarkMenu()) {
2447 showMarkMenu(cursorOnLine, QCursor::pos());
2448 }
2449 }
2450 } else if (e->button() == Qt::RightButton) {
2451 showMarkMenu(cursorOnLine, QCursor::pos());
2452 }
2453 }
2454
2455 if (area == FoldingMarkers) {
2456 // Prefer the highlighted range over the exact clicked line
2457 const int lineToToggle = m_foldingRange ? m_foldingRange->toRange().start().line() : cursorOnLine;
2458 if (e->button() == Qt::LeftButton) {
2459 m_view->toggleFoldingOfLine(lineToToggle);
2460 } else if (e->button() == Qt::RightButton) {
2461 m_view->toggleFoldingsInRange(lineToToggle);
2462 }
2463
2464 delete m_foldingPreview;
2465 }
2466
2467 if (area == AnnotationBorder) {
2468 const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this);
2469 if (e->button() == Qt::LeftButton && singleClick) {
2470 Q_EMIT m_view->annotationActivated(m_view, cursorOnLine);
2471 }
2472 }
2473 }
2474
2475 const QPoint pos(0, e->position().y());
2477 m_viewInternal->mouseReleaseEvent(&forward);
2478}
2479
2480void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e)
2481{
2482 int cursorOnLine = m_viewInternal->yToKateTextLayout(e->position().y()).line();
2483
2484 if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) {
2485 const BorderArea area = positionToArea(e->pos());
2486 const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this);
2487 if (area == AnnotationBorder && !singleClick) {
2488 Q_EMIT m_view->annotationActivated(m_view, cursorOnLine);
2489 }
2490 }
2491 const QPoint pos(0, e->position().y());
2493 m_viewInternal->mouseDoubleClickEvent(&forward);
2494}
2495
2496void KateIconBorder::contextMenuEvent(QContextMenuEvent *e)
2497{
2498 const BorderArea area = positionToArea(e->pos());
2499 const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line();
2500
2501 if (area == AnnotationBorder) {
2502 showAnnotationMenu(cursorOnLine, e->globalPos());
2503 return;
2504 }
2505
2506 QMenu menu(this);
2507
2508 KActionCollection *ac = m_view->actionCollection();
2509
2510 // NOTE Assumes cursor position was updated before the menu opens
2511 if (QAction *bookmarkToggle = ac->action(QStringLiteral("bookmarks_toggle"))) {
2512 menu.addAction(bookmarkToggle);
2513 }
2514 if (QAction *bookmarkClear = ac->action(QStringLiteral("bookmarks_clear"))) {
2515 menu.addAction(bookmarkClear);
2516 }
2517
2518 menu.addSeparator();
2519
2520 if (auto a = ac->action(QStringLiteral("edit_copy_file_location"))) {
2521 menu.addAction(a);
2522 }
2523
2524 menu.addSeparator();
2525
2526 if (QAction *toggleDynWrap = ac->action(QStringLiteral("view_dynamic_word_wrap"))) {
2527 menu.addAction(toggleDynWrap);
2528 }
2529 menu.addSeparator();
2530 if (QAction *toggleIconBar = ac->action(QStringLiteral("view_border"))) {
2531 menu.addAction(toggleIconBar);
2532 }
2533 if (QAction *toggleLineNumbers = ac->action(QStringLiteral("view_line_numbers"))) {
2534 menu.addAction(toggleLineNumbers);
2535 }
2536 if (QAction *toggleFoldingMarkers = ac->action(QStringLiteral("view_folding_markers"))) {
2537 menu.addAction(toggleFoldingMarkers);
2538 }
2539
2540 menu.exec(e->globalPos());
2541}
2542
2543void KateIconBorder::wheelEvent(QWheelEvent *e)
2544{
2545 QCoreApplication::sendEvent(m_viewInternal, e);
2546}
2547
2548void KateIconBorder::showMarkMenu(uint line, const QPoint &pos)
2549{
2550 if (m_doc->handleMarkContextMenu(line, pos)) {
2551 return;
2552 }
2553
2554 if (!m_view->config()->allowMarkMenu()) {
2555 return;
2556 }
2557
2558 QMenu markMenu;
2559 QMenu selectDefaultMark;
2560 auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark);
2561
2562 std::vector<int> vec(33);
2563 int i = 1;
2564
2565 for (uint bit = 0; bit < 32; bit++) {
2567 if (!(m_doc->editableMarks() & markType)) {
2568 continue;
2569 }
2570
2571 QAction *mA;
2572 QAction *dMA;
2573 const QIcon icon = m_doc->markIcon(markType);
2574 if (!m_doc->markDescription(markType).isEmpty()) {
2575 mA = markMenu.addAction(icon, m_doc->markDescription(markType));
2576 dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType));
2577 } else {
2578 mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1));
2579 dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1));
2580 }
2581 selectDefaultMarkActionGroup->addAction(dMA);
2582 mA->setData(i);
2583 mA->setCheckable(true);
2584 dMA->setData(i + 100);
2585 dMA->setCheckable(true);
2586 if (m_doc->mark(line) & markType) {
2587 mA->setChecked(true);
2588 }
2589
2590 if (markType & KateViewConfig::global()->defaultMarkType()) {
2591 dMA->setChecked(true);
2592 }
2593
2594 vec[i++] = markType;
2595 }
2596
2597 if (markMenu.actions().count() == 0) {
2598 return;
2599 }
2600
2601 if (markMenu.actions().count() > 1) {
2602 markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark);
2603 }
2604
2605 QAction *rA = markMenu.exec(pos);
2606 if (!rA) {
2607 return;
2608 }
2609 int result = rA->data().toInt();
2610 if (result > 100) {
2611 KateViewConfig::global()->setValue(KateViewConfig::DefaultMarkType, vec[result - 100]);
2612 } else {
2614 if (m_doc->mark(line) & markType) {
2615 m_doc->removeMark(line, markType);
2616 } else {
2617 m_doc->addMark(line, markType);
2618 }
2619 }
2620}
2621
2622KTextEditor::AbstractAnnotationItemDelegate *KateIconBorder::annotationItemDelegate() const
2623{
2624 return m_annotationItemDelegate;
2625}
2626
2627void KateIconBorder::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate)
2628{
2629 if (delegate == m_annotationItemDelegate) {
2630 return;
2631 }
2632
2633 // reset to default, but already on that?
2634 if (!delegate && m_isDefaultAnnotationItemDelegate) {
2635 // nothing to do
2636 return;
2637 }
2638
2639 // make sure the tooltip is hidden
2640 if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
2641 m_hoveredAnnotationGroupIdentifier.clear();
2642 hideAnnotationTooltip();
2643 }
2644
2645 disconnect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2646 if (!m_isDefaultAnnotationItemDelegate) {
2647 disconnect(m_annotationItemDelegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2648 }
2649
2650 if (!delegate) {
2651 // reset to a default delegate
2652 m_annotationItemDelegate = new KateAnnotationItemDelegate(this);
2653 m_isDefaultAnnotationItemDelegate = true;
2654 } else {
2655 // drop any default delegate
2656 if (m_isDefaultAnnotationItemDelegate) {
2657 delete m_annotationItemDelegate;
2658 m_isDefaultAnnotationItemDelegate = false;
2659 }
2660
2661 m_annotationItemDelegate = delegate;
2662 // catch delegate being destroyed
2663 connect(delegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2664 }
2665
2666 connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2667
2668 if (m_annotationBorderOn) {
2669 QTimer::singleShot(0, this, &KateIconBorder::delayedUpdateOfSizeWithRepaint);
2670 }
2671}
2672
2673void KateIconBorder::handleDestroyedAnnotationItemDelegate()
2674{
2675 setAnnotationItemDelegate(nullptr);
2676}
2677
2678void KateIconBorder::delayedUpdateOfSizeWithRepaint()
2679{
2680 // ensure we update size + repaint at once to avoid flicker, see bug 435361
2681 setUpdatesEnabled(false);
2683 repaint();
2684 setUpdatesEnabled(true);
2685}
2686
2687void KateIconBorder::initStyleOption(KTextEditor::StyleOptionAnnotationItem *styleOption) const
2688{
2689 styleOption->initFrom(this);
2690 styleOption->view = m_view;
2691 styleOption->decorationSize = QSize(m_iconAreaWidth, m_iconAreaWidth);
2692 styleOption->contentFontMetrics = m_view->renderer()->currentFontMetrics();
2693}
2694
2695void KateIconBorder::setStyleOptionLineData(KTextEditor::StyleOptionAnnotationItem *styleOption,
2696 int y,
2697 int realLine,
2698 const KTextEditor::AnnotationModel *model,
2699 const QString &annotationGroupIdentifier) const
2700{
2701 // calculate rendered displayed line
2702 const uint h = m_view->renderer()->lineHeight();
2703 const uint z = (y / h);
2704
2705 KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, annotationGroupIdentifier, z, true);
2706 annotationGroupPositionState.nextLine(*styleOption, z, realLine);
2707}
2708
2709QRect KateIconBorder::annotationLineRectInView(int line) const
2710{
2711 int x = 0;
2712 if (m_iconBorderOn) {
2713 x += m_iconAreaWidth + 2;
2714 }
2715 const int y = m_view->m_viewInternal->lineToY(line);
2716
2717 return QRect(x, y, m_annotationAreaWidth, m_view->renderer()->lineHeight());
2718}
2719
2720void KateIconBorder::updateAnnotationLine(int line)
2721{
2722 // TODO: why has the default value been 8, where is that magic number from?
2723 int width = 8;
2724 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2725
2726 if (model) {
2728 initStyleOption(&styleOption);
2729 width = m_annotationItemDelegate->sizeHint(styleOption, model, line).width();
2730 }
2731
2732 if (width > m_annotationAreaWidth) {
2733 m_annotationAreaWidth = width;
2734 m_updatePositionToArea = true;
2735
2736 QTimer::singleShot(0, this, SLOT(update()));
2737 }
2738}
2739
2740void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos)
2741{
2742 QMenu menu;
2743 QAction a(i18n("Disable Annotation Bar"), &menu);
2744 a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2745 menu.addAction(&a);
2746 Q_EMIT m_view->annotationContextMenuAboutToShow(m_view, &menu, line);
2747 if (menu.exec(pos) == &a) {
2748 m_view->setAnnotationBorderVisible(false);
2749 }
2750}
2751
2752void KateIconBorder::hideAnnotationTooltip()
2753{
2754 m_annotationItemDelegate->hideTooltip(m_view);
2755}
2756
2757void KateIconBorder::updateAnnotationBorderWidth()
2758{
2759 calcAnnotationBorderWidth();
2760
2761 m_updatePositionToArea = true;
2762
2763 QTimer::singleShot(0, this, SLOT(update()));
2764}
2765
2766void KateIconBorder::calcAnnotationBorderWidth()
2767{
2768 // TODO: another magic number, not matching the one in updateAnnotationLine()
2769 m_annotationAreaWidth = 6;
2770 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2771
2772 if (model) {
2774 initStyleOption(&styleOption);
2775
2776 const int lineCount = m_view->doc()->lines();
2777 if (lineCount > 0) {
2778 const int checkedLineCount = m_hasUniformAnnotationItemSizes ? 1 : lineCount;
2779 for (int i = 0; i < checkedLineCount; ++i) {
2780 const int curwidth = m_annotationItemDelegate->sizeHint(styleOption, model, i).width();
2781 if (curwidth > m_annotationAreaWidth) {
2782 m_annotationAreaWidth = curwidth;
2783 }
2784 }
2785 }
2786 }
2787}
2788
2789void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel)
2790{
2791 if (oldmodel) {
2792 oldmodel->disconnect(this);
2793 }
2794 if (newmodel) {
2795 connect(newmodel, &KTextEditor::AnnotationModel::reset, this, &KateIconBorder::updateAnnotationBorderWidth);
2796 connect(newmodel, &KTextEditor::AnnotationModel::lineChanged, this, &KateIconBorder::updateAnnotationLine);
2797 }
2798 updateAnnotationBorderWidth();
2799}
2800
2801void KateIconBorder::displayRangeChanged()
2802{
2803 hideFolding();
2804 removeAnnotationHovering();
2805}
2806
2807// END KateIconBorder
2808
2809// BEGIN KateViewEncodingAction
2810// According to https://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
2811// the default/unknown mib value is 2.
2812#define MIB_DEFAULT 2
2813
2814bool lessThanAction(KSelectAction *a, KSelectAction *b)
2815{
2816 return a->text() < b->text();
2817}
2818
2819void KateViewEncodingAction::init()
2820{
2822
2824
2825 int i;
2826 const auto encodingsByScript = KCharsets::charsets()->encodingsByScript();
2827 actions.reserve(encodingsByScript.size());
2828 for (const QStringList &encodingsForScript : encodingsByScript) {
2829 KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), this);
2830
2831 for (i = 1; i < encodingsForScript.size(); ++i) {
2832 tmp->addAction(encodingsForScript.at(i));
2833 }
2835 subActionTriggered(action);
2836 });
2837 // tmp->setCheckable(true);
2838 actions << tmp;
2839 }
2840 std::sort(actions.begin(), actions.end(), lessThanAction);
2841 for (KSelectAction *action : std::as_const(actions)) {
2843 }
2844}
2845
2846void KateViewEncodingAction::subActionTriggered(QAction *action)
2847{
2848 if (currentSubAction == action) {
2849 return;
2850 }
2851 currentSubAction = action;
2853}
2854
2855KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc,
2856 KTextEditor::ViewPrivate *_view,
2857 const QString &text,
2858 QObject *parent,
2859 bool saveAsMode)
2860 : KSelectAction(text, parent)
2861 , doc(_doc)
2862 , view(_view)
2863 , m_saveAsMode(saveAsMode)
2864{
2865 init();
2866
2867 connect(menu(), &QMenu::aboutToShow, this, &KateViewEncodingAction::slotAboutToShow);
2868 connect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2869}
2870
2871void KateViewEncodingAction::slotAboutToShow()
2872{
2873 setCurrentCodec(doc->config()->encoding());
2874}
2875
2876void KateViewEncodingAction::setEncoding(const QString &e)
2877{
2878 // in save as mode => trigger saveAs
2879 if (m_saveAsMode) {
2880 doc->documentSaveAsWithEncoding(e);
2881 return;
2882 }
2883
2884 // else switch encoding
2886 doc->setEncoding(e);
2887 view->reloadFile();
2888}
2889
2890bool KateViewEncodingAction::setCurrentCodec(const QString &codec)
2891{
2892 disconnect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2893
2894 int i;
2895 int j;
2896 for (i = 0; i < actions().size(); ++i) {
2897 if (actions().at(i)->menu()) {
2898 for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) {
2899 if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) {
2900 continue;
2901 }
2902 if (actions().at(i)->menu()->actions().at(j)->isSeparator()) {
2903 continue;
2904 }
2905
2906 if (codec == actions().at(i)->menu()->actions().at(j)->text()) {
2907 currentSubAction = actions().at(i)->menu()->actions().at(j);
2908 currentSubAction->setChecked(true);
2909 } else {
2910 actions().at(i)->menu()->actions().at(j)->setChecked(false);
2911 }
2912 }
2913 }
2914 }
2915
2916 connect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2917 return true;
2918}
2919
2920// END KateViewEncodingAction
2921
2922// BEGIN KateViewBar related classes
2923
2924KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent)
2925 : QWidget(parent)
2926 , m_viewBar(nullptr)
2927{
2928 QHBoxLayout *layout = new QHBoxLayout(this);
2929
2930 // NOTE: Here be cosmetics.
2931 layout->setContentsMargins(0, 0, 0, 0);
2932
2933 // widget to be used as parent for the real content
2934 m_centralWidget = new QWidget(this);
2935 layout->addWidget(m_centralWidget);
2936 setFocusProxy(m_centralWidget);
2937
2938 // hide button
2939 if (addCloseButton) {
2940 m_closeButton = new QToolButton(this);
2941 m_closeButton->setAutoRaise(true);
2942 m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2943 connect(m_closeButton, &QToolButton::clicked, this, &KateViewBarWidget::hideMe);
2944 layout->addWidget(m_closeButton);
2945 layout->setAlignment(m_closeButton, Qt::AlignCenter | Qt::AlignVCenter);
2946 }
2947}
2948
2949KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view)
2950 : QWidget(parent)
2951 , m_external(external)
2952 , m_view(view)
2953 , m_permanentBarWidget(nullptr)
2954
2955{
2956 m_layout = new QVBoxLayout(this);
2957 m_stack = new QStackedWidget(this);
2958 m_layout->addWidget(m_stack);
2959 m_layout->setContentsMargins(0, 0, 0, 0);
2960
2961 m_stack->hide();
2962 hide();
2963}
2964
2965void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget)
2966{
2967 // just ignore additional adds for already existing widgets
2968 if (hasBarWidget(newBarWidget)) {
2969 return;
2970 }
2971
2972 // add new widget, invisible...
2973 newBarWidget->hide();
2974 m_stack->addWidget(newBarWidget);
2975 newBarWidget->setAssociatedViewBar(this);
2976 connect(newBarWidget, &KateViewBarWidget::hideMe, this, &KateViewBar::hideCurrentBarWidget);
2977}
2978
2979void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget)
2980{
2981 // remove only if there
2982 if (!hasBarWidget(barWidget)) {
2983 return;
2984 }
2985
2986 m_stack->removeWidget(barWidget);
2987 barWidget->setAssociatedViewBar(nullptr);
2988 barWidget->hide();
2989 disconnect(barWidget, nullptr, this, nullptr);
2990}
2991
2992void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget)
2993{
2994 Q_ASSERT(barWidget);
2995 Q_ASSERT(!m_permanentBarWidget);
2996
2997 m_layout->addWidget(barWidget);
2998 m_permanentBarWidget = barWidget;
2999 m_permanentBarWidget->show();
3000
3001 setViewBarVisible(true);
3002}
3003
3004void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget)
3005{
3006 Q_ASSERT(m_permanentBarWidget == barWidget);
3007 Q_UNUSED(barWidget);
3008
3009 m_permanentBarWidget->hide();
3010 m_layout->removeWidget(m_permanentBarWidget);
3011 m_permanentBarWidget = nullptr;
3012
3013 if (!barWidgetVisible()) {
3014 setViewBarVisible(false);
3015 }
3016}
3017
3018void KateViewBar::showBarWidget(KateViewBarWidget *barWidget)
3019{
3020 Q_ASSERT(barWidget != nullptr);
3021
3022 if (barWidget != qobject_cast<KateViewBarWidget *>(m_stack->currentWidget())) {
3023 hideCurrentBarWidget();
3024 }
3025
3026 // raise correct widget
3027 m_stack->addWidget(barWidget);
3028 m_stack->setCurrentWidget(barWidget);
3029 barWidget->show();
3031 m_stack->show();
3032 setViewBarVisible(true);
3033}
3034
3035bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const
3036{
3037 return m_stack->indexOf(barWidget) != -1;
3038}
3039
3040void KateViewBar::hideCurrentBarWidget()
3041{
3042 KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
3043 if (current) {
3044 m_stack->removeWidget(current);
3045 current->closed();
3046 }
3047
3048 // hide the bar
3049 m_stack->hide();
3050 if (!m_permanentBarWidget) {
3051 setViewBarVisible(false);
3052 }
3053
3054 m_view->setFocus();
3055}
3056
3057void KateViewBar::setViewBarVisible(bool visible)
3058{
3059 if (m_external) {
3060 if (visible) {
3061 m_view->mainWindow()->showViewBar(m_view);
3062 } else {
3063 m_view->mainWindow()->hideViewBar(m_view);
3064 }
3065 } else {
3067 }
3068}
3069
3070bool KateViewBar::barWidgetVisible() const
3071{
3072 KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
3073 return current && current->isVisible();
3074}
3075
3076void KateViewBar::keyPressEvent(QKeyEvent *event)
3077{
3078 if (event->key() == Qt::Key_Escape) {
3079 hideCurrentBarWidget();
3080 return;
3081 }
3083}
3084
3085void KateViewBar::hideEvent(QHideEvent *event)
3086{
3087 Q_UNUSED(event);
3088 // if (!event->spontaneous())
3089 // m_view->setFocus();
3090}
3091
3092// END KateViewBar related classes
3093
3094// BEGIN SCHEMA ACTION -- the 'View->Color theme' menu action
3095void KateViewSchemaAction::init()
3096{
3097 m_group = nullptr;
3098 m_view = nullptr;
3099 last = 0;
3100
3101 connect(menu(), &QMenu::aboutToShow, this, &KateViewSchemaAction::slotAboutToShow);
3102}
3103
3104void KateViewSchemaAction::updateMenu(KTextEditor::ViewPrivate *view)
3105{
3106 m_view = view;
3107}
3108
3109void KateViewSchemaAction::slotAboutToShow()
3110{
3111 KTextEditor::ViewPrivate *view = m_view;
3112
3113 const auto themes = KateHlManager::self()->sortedThemes();
3114
3115 if (!m_group) {
3116 m_group = new QActionGroup(menu());
3117 m_group->setExclusive(true);
3118 }
3119
3120 for (int z = 0; z < themes.count(); z++) {
3121 QString hlName = themes[z].translatedName();
3122
3123 if (!names.contains(hlName)) {
3124 names << hlName;
3125 QAction *a = menu()->addAction(hlName, this, &KateViewSchemaAction::setSchema);
3126 a->setData(themes[z].name());
3127 a->setCheckable(true);
3128 a->setActionGroup(m_group);
3129 }
3130 }
3131
3132 if (!view) {
3133 return;
3134 }
3135
3136 QString id = view->rendererConfig()->schema();
3137 const auto menuActions = menu()->actions();
3138 for (QAction *a : menuActions) {
3139 a->setChecked(a->data().toString() == id);
3140 }
3141}
3142
3143void KateViewSchemaAction::setSchema()
3144{
3146
3147 if (!action) {
3148 return;
3149 }
3150 QString mode = action->data().toString();
3151
3152 KTextEditor::ViewPrivate *view = m_view;
3153
3154 if (view) {
3155 view->rendererConfig()->setSchema(mode);
3156 }
3157}
3158// END SCHEMA ACTION
3159
3160#include "moc_kateviewhelpers.cpp"
Q_INVOKABLE QAction * action(const QString &name) const
QAction * addAction(const QString &name, const QObject *receiver=nullptr, const char *member=nullptr)
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
bool overlapsLine(int line) const
Check whether the range overlaps at least part of line.
@ 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)
QString name(GameStandardAction id)
void invokeHelp(const QString &anchor=QString(), const QString &appname=QString())
bool canConvert(const QVariant &value)
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
QMenu * menu() const const
void setActionGroup(QActionGroup *group)
void setData(const QVariant &data)
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
void * data()
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 Jul 26 2024 11:56:21 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.