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);
207 connect(m_doc, &KTextEditor::DocumentPrivate::textChanged, &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 (QAction *toggleDynWrap = ac->action(QStringLiteral("view_dynamic_word_wrap"))) {
2521 menu.addAction(toggleDynWrap);
2522 }
2523 menu.addSeparator();
2524 if (QAction *toggleIconBar = ac->action(QStringLiteral("view_border"))) {
2525 menu.addAction(toggleIconBar);
2526 }
2527 if (QAction *toggleLineNumbers = ac->action(QStringLiteral("view_line_numbers"))) {
2528 menu.addAction(toggleLineNumbers);
2529 }
2530 if (QAction *toggleFoldingMarkers = ac->action(QStringLiteral("view_folding_markers"))) {
2531 menu.addAction(toggleFoldingMarkers);
2532 }
2533
2534 menu.exec(e->globalPos());
2535}
2536
2537void KateIconBorder::wheelEvent(QWheelEvent *e)
2538{
2539 QCoreApplication::sendEvent(m_viewInternal, e);
2540}
2541
2542void KateIconBorder::showMarkMenu(uint line, const QPoint &pos)
2543{
2544 if (m_doc->handleMarkContextMenu(line, pos)) {
2545 return;
2546 }
2547
2548 if (!m_view->config()->allowMarkMenu()) {
2549 return;
2550 }
2551
2552 QMenu markMenu;
2553 QMenu selectDefaultMark;
2554 auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark);
2555
2556 std::vector<int> vec(33);
2557 int i = 1;
2558
2559 for (uint bit = 0; bit < 32; bit++) {
2561 if (!(m_doc->editableMarks() & markType)) {
2562 continue;
2563 }
2564
2565 QAction *mA;
2566 QAction *dMA;
2567 const QIcon icon = m_doc->markIcon(markType);
2568 if (!m_doc->markDescription(markType).isEmpty()) {
2569 mA = markMenu.addAction(icon, m_doc->markDescription(markType));
2570 dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType));
2571 } else {
2572 mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1));
2573 dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1));
2574 }
2575 selectDefaultMarkActionGroup->addAction(dMA);
2576 mA->setData(i);
2577 mA->setCheckable(true);
2578 dMA->setData(i + 100);
2579 dMA->setCheckable(true);
2580 if (m_doc->mark(line) & markType) {
2581 mA->setChecked(true);
2582 }
2583
2584 if (markType & KateViewConfig::global()->defaultMarkType()) {
2585 dMA->setChecked(true);
2586 }
2587
2588 vec[i++] = markType;
2589 }
2590
2591 if (markMenu.actions().count() == 0) {
2592 return;
2593 }
2594
2595 if (markMenu.actions().count() > 1) {
2596 markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark);
2597 }
2598
2599 QAction *rA = markMenu.exec(pos);
2600 if (!rA) {
2601 return;
2602 }
2603 int result = rA->data().toInt();
2604 if (result > 100) {
2605 KateViewConfig::global()->setValue(KateViewConfig::DefaultMarkType, vec[result - 100]);
2606 } else {
2608 if (m_doc->mark(line) & markType) {
2609 m_doc->removeMark(line, markType);
2610 } else {
2611 m_doc->addMark(line, markType);
2612 }
2613 }
2614}
2615
2616KTextEditor::AbstractAnnotationItemDelegate *KateIconBorder::annotationItemDelegate() const
2617{
2618 return m_annotationItemDelegate;
2619}
2620
2621void KateIconBorder::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate)
2622{
2623 if (delegate == m_annotationItemDelegate) {
2624 return;
2625 }
2626
2627 // reset to default, but already on that?
2628 if (!delegate && m_isDefaultAnnotationItemDelegate) {
2629 // nothing to do
2630 return;
2631 }
2632
2633 // make sure the tooltip is hidden
2634 if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
2635 m_hoveredAnnotationGroupIdentifier.clear();
2636 hideAnnotationTooltip();
2637 }
2638
2639 disconnect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2640 if (!m_isDefaultAnnotationItemDelegate) {
2641 disconnect(m_annotationItemDelegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2642 }
2643
2644 if (!delegate) {
2645 // reset to a default delegate
2646 m_annotationItemDelegate = new KateAnnotationItemDelegate(this);
2647 m_isDefaultAnnotationItemDelegate = true;
2648 } else {
2649 // drop any default delegate
2650 if (m_isDefaultAnnotationItemDelegate) {
2651 delete m_annotationItemDelegate;
2652 m_isDefaultAnnotationItemDelegate = false;
2653 }
2654
2655 m_annotationItemDelegate = delegate;
2656 // catch delegate being destroyed
2657 connect(delegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2658 }
2659
2660 connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2661
2662 if (m_annotationBorderOn) {
2663 QTimer::singleShot(0, this, &KateIconBorder::delayedUpdateOfSizeWithRepaint);
2664 }
2665}
2666
2667void KateIconBorder::handleDestroyedAnnotationItemDelegate()
2668{
2669 setAnnotationItemDelegate(nullptr);
2670}
2671
2672void KateIconBorder::delayedUpdateOfSizeWithRepaint()
2673{
2674 // ensure we update size + repaint at once to avoid flicker, see bug 435361
2675 setUpdatesEnabled(false);
2677 repaint();
2678 setUpdatesEnabled(true);
2679}
2680
2681void KateIconBorder::initStyleOption(KTextEditor::StyleOptionAnnotationItem *styleOption) const
2682{
2683 styleOption->initFrom(this);
2684 styleOption->view = m_view;
2685 styleOption->decorationSize = QSize(m_iconAreaWidth, m_iconAreaWidth);
2686 styleOption->contentFontMetrics = m_view->renderer()->currentFontMetrics();
2687}
2688
2689void KateIconBorder::setStyleOptionLineData(KTextEditor::StyleOptionAnnotationItem *styleOption,
2690 int y,
2691 int realLine,
2692 const KTextEditor::AnnotationModel *model,
2693 const QString &annotationGroupIdentifier) const
2694{
2695 // calculate rendered displayed line
2696 const uint h = m_view->renderer()->lineHeight();
2697 const uint z = (y / h);
2698
2699 KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, annotationGroupIdentifier, z, true);
2700 annotationGroupPositionState.nextLine(*styleOption, z, realLine);
2701}
2702
2703QRect KateIconBorder::annotationLineRectInView(int line) const
2704{
2705 int x = 0;
2706 if (m_iconBorderOn) {
2707 x += m_iconAreaWidth + 2;
2708 }
2709 const int y = m_view->m_viewInternal->lineToY(line);
2710
2711 return QRect(x, y, m_annotationAreaWidth, m_view->renderer()->lineHeight());
2712}
2713
2714void KateIconBorder::updateAnnotationLine(int line)
2715{
2716 // TODO: why has the default value been 8, where is that magic number from?
2717 int width = 8;
2718 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2719
2720 if (model) {
2722 initStyleOption(&styleOption);
2723 width = m_annotationItemDelegate->sizeHint(styleOption, model, line).width();
2724 }
2725
2726 if (width > m_annotationAreaWidth) {
2727 m_annotationAreaWidth = width;
2728 m_updatePositionToArea = true;
2729
2730 QTimer::singleShot(0, this, SLOT(update()));
2731 }
2732}
2733
2734void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos)
2735{
2736 QMenu menu;
2737 QAction a(i18n("Disable Annotation Bar"), &menu);
2738 a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2739 menu.addAction(&a);
2740 Q_EMIT m_view->annotationContextMenuAboutToShow(m_view, &menu, line);
2741 if (menu.exec(pos) == &a) {
2742 m_view->setAnnotationBorderVisible(false);
2743 }
2744}
2745
2746void KateIconBorder::hideAnnotationTooltip()
2747{
2748 m_annotationItemDelegate->hideTooltip(m_view);
2749}
2750
2751void KateIconBorder::updateAnnotationBorderWidth()
2752{
2753 calcAnnotationBorderWidth();
2754
2755 m_updatePositionToArea = true;
2756
2757 QTimer::singleShot(0, this, SLOT(update()));
2758}
2759
2760void KateIconBorder::calcAnnotationBorderWidth()
2761{
2762 // TODO: another magic number, not matching the one in updateAnnotationLine()
2763 m_annotationAreaWidth = 6;
2764 KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2765
2766 if (model) {
2768 initStyleOption(&styleOption);
2769
2770 const int lineCount = m_view->doc()->lines();
2771 if (lineCount > 0) {
2772 const int checkedLineCount = m_hasUniformAnnotationItemSizes ? 1 : lineCount;
2773 for (int i = 0; i < checkedLineCount; ++i) {
2774 const int curwidth = m_annotationItemDelegate->sizeHint(styleOption, model, i).width();
2775 if (curwidth > m_annotationAreaWidth) {
2776 m_annotationAreaWidth = curwidth;
2777 }
2778 }
2779 }
2780 }
2781}
2782
2783void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel)
2784{
2785 if (oldmodel) {
2786 oldmodel->disconnect(this);
2787 }
2788 if (newmodel) {
2789 connect(newmodel, &KTextEditor::AnnotationModel::reset, this, &KateIconBorder::updateAnnotationBorderWidth);
2790 connect(newmodel, &KTextEditor::AnnotationModel::lineChanged, this, &KateIconBorder::updateAnnotationLine);
2791 }
2792 updateAnnotationBorderWidth();
2793}
2794
2795void KateIconBorder::displayRangeChanged()
2796{
2797 hideFolding();
2798 removeAnnotationHovering();
2799}
2800
2801// END KateIconBorder
2802
2803// BEGIN KateViewEncodingAction
2804// According to https://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
2805// the default/unknown mib value is 2.
2806#define MIB_DEFAULT 2
2807
2808bool lessThanAction(KSelectAction *a, KSelectAction *b)
2809{
2810 return a->text() < b->text();
2811}
2812
2813void KateViewEncodingAction::init()
2814{
2816
2818
2819 int i;
2820 const auto encodingsByScript = KCharsets::charsets()->encodingsByScript();
2821 actions.reserve(encodingsByScript.size());
2822 for (const QStringList &encodingsForScript : encodingsByScript) {
2823 KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), this);
2824
2825 for (i = 1; i < encodingsForScript.size(); ++i) {
2826 tmp->addAction(encodingsForScript.at(i));
2827 }
2829 subActionTriggered(action);
2830 });
2831 // tmp->setCheckable(true);
2832 actions << tmp;
2833 }
2834 std::sort(actions.begin(), actions.end(), lessThanAction);
2835 for (KSelectAction *action : std::as_const(actions)) {
2837 }
2838}
2839
2840void KateViewEncodingAction::subActionTriggered(QAction *action)
2841{
2842 if (currentSubAction == action) {
2843 return;
2844 }
2845 currentSubAction = action;
2847}
2848
2849KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc,
2850 KTextEditor::ViewPrivate *_view,
2851 const QString &text,
2852 QObject *parent,
2853 bool saveAsMode)
2854 : KSelectAction(text, parent)
2855 , doc(_doc)
2856 , view(_view)
2857 , m_saveAsMode(saveAsMode)
2858{
2859 init();
2860
2861 connect(menu(), &QMenu::aboutToShow, this, &KateViewEncodingAction::slotAboutToShow);
2862 connect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2863}
2864
2865void KateViewEncodingAction::slotAboutToShow()
2866{
2867 setCurrentCodec(doc->config()->encoding());
2868}
2869
2870void KateViewEncodingAction::setEncoding(const QString &e)
2871{
2872 // in save as mode => trigger saveAs
2873 if (m_saveAsMode) {
2874 doc->documentSaveAsWithEncoding(e);
2875 return;
2876 }
2877
2878 // else switch encoding
2879 doc->userSetEncodingForNextReload();
2880 doc->setEncoding(e);
2881 view->reloadFile();
2882}
2883
2884bool KateViewEncodingAction::setCurrentCodec(const QString &codec)
2885{
2886 disconnect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2887
2888 int i;
2889 int j;
2890 for (i = 0; i < actions().size(); ++i) {
2891 if (actions().at(i)->menu()) {
2892 for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) {
2893 if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) {
2894 continue;
2895 }
2896 if (actions().at(i)->menu()->actions().at(j)->isSeparator()) {
2897 continue;
2898 }
2899
2900 if (codec == actions().at(i)->menu()->actions().at(j)->text()) {
2901 currentSubAction = actions().at(i)->menu()->actions().at(j);
2902 currentSubAction->setChecked(true);
2903 } else {
2904 actions().at(i)->menu()->actions().at(j)->setChecked(false);
2905 }
2906 }
2907 }
2908 }
2909
2910 connect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2911 return true;
2912}
2913
2914// END KateViewEncodingAction
2915
2916// BEGIN KateViewBar related classes
2917
2918KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent)
2919 : QWidget(parent)
2920 , m_viewBar(nullptr)
2921{
2922 QHBoxLayout *layout = new QHBoxLayout(this);
2923
2924 // NOTE: Here be cosmetics.
2925 layout->setContentsMargins(0, 0, 0, 0);
2926
2927 // widget to be used as parent for the real content
2928 m_centralWidget = new QWidget(this);
2929 layout->addWidget(m_centralWidget);
2930 setFocusProxy(m_centralWidget);
2931
2932 // hide button
2933 if (addCloseButton) {
2934 m_closeButton = new QToolButton(this);
2935 m_closeButton->setAutoRaise(true);
2936 m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2937 connect(m_closeButton, &QToolButton::clicked, this, &KateViewBarWidget::hideMe);
2938 layout->addWidget(m_closeButton);
2939 layout->setAlignment(m_closeButton, Qt::AlignCenter | Qt::AlignVCenter);
2940 }
2941}
2942
2943KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view)
2944 : QWidget(parent)
2945 , m_external(external)
2946 , m_view(view)
2947 , m_permanentBarWidget(nullptr)
2948
2949{
2950 m_layout = new QVBoxLayout(this);
2951 m_stack = new QStackedWidget(this);
2952 m_layout->addWidget(m_stack);
2953 m_layout->setContentsMargins(0, 0, 0, 0);
2954
2955 m_stack->hide();
2956 hide();
2957}
2958
2959void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget)
2960{
2961 // just ignore additional adds for already existing widgets
2962 if (hasBarWidget(newBarWidget)) {
2963 return;
2964 }
2965
2966 // add new widget, invisible...
2967 newBarWidget->hide();
2968 m_stack->addWidget(newBarWidget);
2969 newBarWidget->setAssociatedViewBar(this);
2970 connect(newBarWidget, &KateViewBarWidget::hideMe, this, &KateViewBar::hideCurrentBarWidget);
2971}
2972
2973void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget)
2974{
2975 // remove only if there
2976 if (!hasBarWidget(barWidget)) {
2977 return;
2978 }
2979
2980 m_stack->removeWidget(barWidget);
2981 barWidget->setAssociatedViewBar(nullptr);
2982 barWidget->hide();
2983 disconnect(barWidget, nullptr, this, nullptr);
2984}
2985
2986void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget)
2987{
2988 Q_ASSERT(barWidget);
2989 Q_ASSERT(!m_permanentBarWidget);
2990
2991 m_layout->addWidget(barWidget);
2992 m_permanentBarWidget = barWidget;
2993 m_permanentBarWidget->show();
2994
2995 setViewBarVisible(true);
2996}
2997
2998void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget)
2999{
3000 Q_ASSERT(m_permanentBarWidget == barWidget);
3001 Q_UNUSED(barWidget);
3002
3003 m_permanentBarWidget->hide();
3004 m_layout->removeWidget(m_permanentBarWidget);
3005 m_permanentBarWidget = nullptr;
3006
3007 if (!barWidgetVisible()) {
3008 setViewBarVisible(false);
3009 }
3010}
3011
3012void KateViewBar::showBarWidget(KateViewBarWidget *barWidget)
3013{
3014 Q_ASSERT(barWidget != nullptr);
3015
3016 if (barWidget != qobject_cast<KateViewBarWidget *>(m_stack->currentWidget())) {
3017 hideCurrentBarWidget();
3018 }
3019
3020 // raise correct widget
3021 m_stack->addWidget(barWidget);
3022 m_stack->setCurrentWidget(barWidget);
3023 barWidget->show();
3025 m_stack->show();
3026 setViewBarVisible(true);
3027}
3028
3029bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const
3030{
3031 return m_stack->indexOf(barWidget) != -1;
3032}
3033
3034void KateViewBar::hideCurrentBarWidget()
3035{
3036 KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
3037 if (current) {
3038 m_stack->removeWidget(current);
3039 current->closed();
3040 }
3041
3042 // hide the bar
3043 m_stack->hide();
3044 if (!m_permanentBarWidget) {
3045 setViewBarVisible(false);
3046 }
3047
3048 m_view->setFocus();
3049}
3050
3051void KateViewBar::setViewBarVisible(bool visible)
3052{
3053 if (m_external) {
3054 if (visible) {
3055 m_view->mainWindow()->showViewBar(m_view);
3056 } else {
3057 m_view->mainWindow()->hideViewBar(m_view);
3058 }
3059 } else {
3061 }
3062}
3063
3064bool KateViewBar::barWidgetVisible() const
3065{
3066 KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
3067 return current && current->isVisible();
3068}
3069
3070void KateViewBar::keyPressEvent(QKeyEvent *event)
3071{
3072 if (event->key() == Qt::Key_Escape) {
3073 hideCurrentBarWidget();
3074 return;
3075 }
3077}
3078
3079void KateViewBar::hideEvent(QHideEvent *event)
3080{
3081 Q_UNUSED(event);
3082 // if (!event->spontaneous())
3083 // m_view->setFocus();
3084}
3085
3086// END KateViewBar related classes
3087
3088// BEGIN SCHEMA ACTION -- the 'View->Color theme' menu action
3089void KateViewSchemaAction::init()
3090{
3091 m_group = nullptr;
3092 m_view = nullptr;
3093 last = 0;
3094
3095 connect(menu(), &QMenu::aboutToShow, this, &KateViewSchemaAction::slotAboutToShow);
3096}
3097
3098void KateViewSchemaAction::updateMenu(KTextEditor::ViewPrivate *view)
3099{
3100 m_view = view;
3101}
3102
3103void KateViewSchemaAction::slotAboutToShow()
3104{
3105 KTextEditor::ViewPrivate *view = m_view;
3106
3107 const auto themes = KateHlManager::self()->sortedThemes();
3108
3109 if (!m_group) {
3110 m_group = new QActionGroup(menu());
3111 m_group->setExclusive(true);
3112 }
3113
3114 for (int z = 0; z < themes.count(); z++) {
3115 QString hlName = themes[z].translatedName();
3116
3117 if (!names.contains(hlName)) {
3118 names << hlName;
3119 QAction *a = menu()->addAction(hlName, this, &KateViewSchemaAction::setSchema);
3120 a->setData(themes[z].name());
3121 a->setCheckable(true);
3122 a->setActionGroup(m_group);
3123 }
3124 }
3125
3126 if (!view) {
3127 return;
3128 }
3129
3130 QString id = view->rendererConfig()->schema();
3131 const auto menuActions = menu()->actions();
3132 for (QAction *a : menuActions) {
3133 a->setChecked(a->data().toString() == id);
3134 }
3135}
3136
3137void KateViewSchemaAction::setSchema()
3138{
3139 QAction *action = qobject_cast<QAction *>(sender());
3140
3141 if (!action) {
3142 return;
3143 }
3144 QString mode = action->data().toString();
3145
3146 KTextEditor::ViewPrivate *view = m_view;
3147
3148 if (view) {
3149 view->rendererConfig()->setSchema(mode);
3150 }
3151}
3152// END SCHEMA ACTION
3153
3154#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
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
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).
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 invokeHelp(const QString &anchor=QString(), const QString &appname=QString())
bool canConvert(const QVariant &value)
QAction * forward(const QObject *recvr, const char *slot, QObject *parent)
QString name(StandardAction id)
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
QCA_EXPORT void init()
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)
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 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 May 24 2024 12:01:51 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.