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(index: 0)) {
79 delete item;
80 }
81}
82
83void KateMessageLayout::addItem(QLayoutItem *item)
84{
85 Q_ASSERT(false);
86 add(item, pos: KTextEditor::Message::CenterInView);
87}
88
89void KateMessageLayout::addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos)
90{
91 add(item: 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(i: index).item;
102}
103
104void KateMessageLayout::setGeometry(const QRect &rect)
105{
106 QLayout::setGeometry(rect);
107 const int s = spacing();
108 const QRect adjustedRect = rect.adjusted(xp1: s, yp1: s, xp2: -s, yp2: -s);
109
110 for (const auto &wrapper : std::as_const(t&: 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(p: 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(i: index).item;
142 }
143 return nullptr;
144}
145
146void KateMessageLayout::add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos)
147{
148 m_items.push_back(t: {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(sender: this, signal: &KateScrollBar::valueChanged, context: this, slot: &KateScrollBar::sliderMaybeMoved);
173 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::marksChanged, context: this, slot: &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(sender: &m_delayTextPreviewTimer, signal: &QTimer::timeout, context: this, slot: &KateScrollBar::showTextPreview);
185}
186
187void KateScrollBar::showEvent(QShowEvent *event)
188{
189 QScrollBar::showEvent(event);
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(sender: m_view, signal: &KTextEditor::ViewPrivate::selectionChanged, context: &m_updateTimer, slot&: timerSlot, type: Qt::UniqueConnection);
207 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::textChanged, context: &m_updateTimer, slot&: timerSlot, type: Qt::UniqueConnection);
208 connect(sender: m_view, signal: &KTextEditor::ViewPrivate::delayedUpdateOfView, context: &m_updateTimer, slot&: timerSlot, type: Qt::UniqueConnection);
209 connect(sender: &m_updateTimer, signal: &QTimer::timeout, context: this, slot: &KateScrollBar::updatePixmap, type: Qt::UniqueConnection);
210 connect(sender: &(m_view->textFolding()), signal: &Kate::TextFolding::foldingRangesChanged, context: &m_updateTimer, slot&: timerSlot, type: Qt::UniqueConnection);
211 } else if (!b) {
212 disconnect(receiver: &m_updateTimer);
213 }
214
215 m_showMiniMap = b;
216
217 updateGeometry();
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(p: 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(min: 0, val: newVal, max: maximum());
275 setSliderPosition(newVal);
276 }
277 const QPoint pos(6, minimapYToStdY(y: e->pos().y()));
278 QMouseEvent eMod(QEvent::MouseButtonPress, pos, mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
279 QScrollBar::mousePressEvent(&eMod);
280 } else {
281 QScrollBar::mousePressEvent(e);
282 }
283
284 m_toolTipPos = e->globalPosition().toPoint() - QPoint(e->pos().x(), 0);
285 const int fromLine = m_viewInternal->toRealCursor(virtualCursor: m_viewInternal->startPos()).line() + 1;
286 const int lastLine = m_viewInternal->toRealCursor(virtualCursor: m_viewInternal->endPos()).line() + 1;
287 QToolTip::showText(pos: m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), w: 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) {
303 QToolTip::hideText();
304 }
305
306 if (m_showMiniMap) {
307 const QPoint pos(e->pos().x(), minimapYToStdY(y: e->pos().y()));
308 QMouseEvent eMod(QEvent::MouseButtonRelease, pos, mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
309 QScrollBar::mouseReleaseEvent(&eMod);
310 } else {
311 QScrollBar::mouseReleaseEvent(e);
312 }
313}
314
315void KateScrollBar::mouseMoveEvent(QMouseEvent *e)
316{
317 if (m_showMiniMap) {
318 const QPoint pos(e->pos().x(), minimapYToStdY(y: e->pos().y()));
319 QMouseEvent eMod(QEvent::MouseMove, pos, mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
320 QScrollBar::mouseMoveEvent(&eMod);
321 } else {
322 QScrollBar::mouseMoveEvent(e);
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(virtualCursor: m_viewInternal->startPos()).line() + 1;
331 const int lastLine = m_viewInternal->toRealCursor(virtualCursor: m_viewInternal->endPos()).line() + 1;
332 QToolTip::showText(pos: m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), w: this);
333 }
334
335 showTextPreviewDelayed();
336}
337
338void KateScrollBar::leaveEvent(QEvent *event)
339{
340 hideTextPreview();
341
342 QAbstractSlider::leaveEvent(event);
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 {
396 QStyleOptionSlider opt;
397 opt.initFrom(w: 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
408 grooveRect = style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: QStyle::SC_ScrollBarGroove, widget: this);
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(dx1: 0, dy1: 0, dx2: 0, dy2: -adjust);
415 }
416
417 const QPoint cursorPos = mapFromGlobal(QCursor::pos());
418 if (grooveRect.contains(p: 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(filterObj: 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(w: m_view->width() / 2, h: m_view->height() / 5);
432 const int xGlobal = mapToGlobal(QPoint(0, 0)).x();
433 const int yGlobal = qMin(a: mapToGlobal(QPoint(0, height())).y() - m_textPreview->height(),
434 b: qMax(a: mapToGlobal(QPoint(0, 0)).y(), b: mapToGlobal(cursorPos).y() - m_textPreview->height() / 2));
435 m_textPreview->move(ax: xGlobal - m_textPreview->width(), ay: 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(obj: 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,
460 QList<KateScrollBar::ColumnRangeWithColor> &ranges,
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(first: penCache.begin(), last: penCache.end(), pred: [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(t: {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(column: 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(pos: 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(defaultStyle: KSyntaxHighlighting::Theme::TextStyle::Normal)->background();
568 const QBrush defaultTextColor = m_view->defaultStyleAttribute(defaultStyle: 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(h: modifiedLineColor.hue(), s: 255, v: 255 - backgroundColor.color().value() / 3);
575 savedLineColor.setHsv(h: savedLineColor.hue(), s: 100, v: 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(fillColor: 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
588 QList<KateScrollBar::ColumnRangeWithColor> colorRangesForLine;
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
605 QVarLengthArray<std::pair<QRgb, QPen>, 20> penCache;
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(visibleLine: virtualLine);
610 const Kate::TextLine kateline = m_doc->plainKateTextLine(i: realLineNumber);
611 const QString lineText = kateline.text();
612
613 if (!simpleMode) {
614 m_doc->buffer().ensureHighlighted(line: 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(line: realLineNumber, view: m_view, rangesWithAttributeOnly: true, outRanges&: 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(cursor: KTextEditor::Cursor(realLineNumber, 0)) && lineText.size() == 0) {
629 if (selectionBgColor != painter.pen().brush()) {
630 painter.setPen(QPen(selectionBgColor, 1));
631 }
632 painter.drawLine(x1: s_pixelMargin, y1: pixelY, x2: s_pixelMargin + s_lineWidth - 1, y2: 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(cursor: 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(a: 4 / charIncrement, b: 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(x1: selStartX, y1: pixelY, x2: selEndX, y2: pixelY);
664 }
665 }
666
667 // Iterate over all the characters in the current line
668 getCharColorRanges(attributes, decorations, text: lineText, ranges&: 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(a: 4 / charIncrement, b: 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.
699 QVarLengthArray<QPoint, 100> points;
700 for (; x < rangeEnd; x += charIncrement) {
701 if (pixelX >= s_lineWidth + s_pixelMargin) {
702 break;
703 }
704 points.append(t: {pixelX++, pixelY});
705 }
706 painter.drawPoints(points: points.data(), pointCount: 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(visibleLine: lineno);
721 const auto line = m_doc->plainKateTextLine(i: realLineNo);
722 const QBrush &col = line.markedAsModified() ? modifiedLineBrush : savedLineBrush;
723 if (line.markedAsModified() || line.markedAsSavedOnDisk()) {
724 int pos = (lineno * pixmapLineCount) / pixmapLinesUnscaled;
725 painter.fillRect(x: 2, y: pos, w: 3, h: 1, b: 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{
744 QScrollBar::paintEvent(e);
745
746 QPainter painter(this);
747
748 QStyleOptionSlider opt;
749 opt.initFrom(w: 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
760 QRect grooveRect = style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: QStyle::SC_ScrollBarGroove, widget: this);
761 m_stdGroveRect = grooveRect;
762 if (style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: QStyle::SC_ScrollBarSubLine, widget: this).height() == 0) {
763 int alignMargin = style()->pixelMetric(metric: QStyle::PM_FocusFrameVMargin, option: &opt, widget: this);
764 grooveRect.moveTop(pos: alignMargin);
765 grooveRect.setHeight(grooveRect.height() - alignMargin);
766 }
767 if (style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: QStyle::SC_ScrollBarAddLine, widget: this).height() == 0) {
768 int alignMargin = style()->pixelMetric(metric: QStyle::PM_FocusFrameVMargin, option: &opt, widget: 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(a: grooveRect.height(), b: 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(a: maximum() + 1, b: 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(pos: visibleStart);
789 visibleRect.setHeight(visibleEnd - visibleStart);
790
791 // calculate colors
792 const QColor backgroundColor = m_view->defaultStyleAttribute(defaultStyle: KSyntaxHighlighting::Theme::TextStyle::Normal)->background().color();
793 const QColor foregroundColor = m_view->defaultStyleAttribute(defaultStyle: 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(cr: QPalette::Mid);
802 int hue;
803 int sat;
804 int light;
805 darkShieldColor.getHsl(h: &hue, s: &sat, l: &light);
806 // apply suitable lightness
807 darkShieldColor.setHsl(h: hue, s: sat, l: backgroundLightness + lighnessDiff * 0.35);
808 // gradient for nicer results
809 QLinearGradient gradient(0, 0, width(), 0);
810 gradient.setColorAt(pos: 0, color: darkShieldColor);
811 gradient.setColorAt(pos: 0.3, color: darkShieldColor.lighter(f: 115));
812 gradient.setColorAt(pos: 1, color: darkShieldColor);
813
814 QColor lightShieldColor;
815 lightShieldColor.setHsl(h: hue, s: sat, l: backgroundLightness + lighnessDiff * 0.15);
816
817 QColor outlineColor;
818 outlineColor.setHsl(h: hue, s: sat, l: 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(r: grooveRect);
824
825 // adjust the rectangles
826 QRect sliderRect = style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: QStyle::SC_ScrollBarSlider, widget: this);
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(dx1: 2, dy1: 0, dx2: -3, dy2: 0);
832 } else {
833 visibleRect.adjust(dx1: 1, dy1: 0, dx2: -1, dy2: 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()) {
840 painter.setRenderHint(hint: QPainter::SmoothPixmapTransform);
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(targetRect: docPixmapMarginRect, pixmap: m_pixmap, sourceRect: 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(targetRect: docPixmapRect, pixmap: m_pixmap, sourceRect: 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(x1: grooveRect.x() + 1, y1: y + 2, x2: width() - 1, y2: 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, color: faded);
878 painter.fillRect(bottom, color: 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(x1: 0, y1: 0, x2: 0, y2: 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(x1: 6, y1: y, x2: width() - 6, y2: 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(x1: 6, y1: y, x2: width() - 6, y2: y);
909 }
910 }
911
912 // slider outline
913 QColor sliderColor(highlightColor);
914 sliderColor.setAlpha(50);
915 painter.fillRect(sliderRect, color: 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(x1: sliderRect.left(), y1: sliderRect.top() + 1, x2: sliderRect.left(), y2: sliderRect.bottom() - 1);
920 painter.drawLine(x1: sliderRect.right(), y1: sliderRect.top() + 1, x2: sliderRect.right(), y2: sliderRect.bottom() - 1);
921 painter.drawLine(x1: sliderRect.left() + 1, y1: sliderRect.top(), x2: sliderRect.right() - 1, y2: sliderRect.top());
922 painter.drawLine(x1: sliderRect.left() + 1, y1: sliderRect.bottom(), x2: sliderRect.right() - 1, y2: sliderRect.bottom());
923}
924
925void KateScrollBar::normalPaintEvent(QPaintEvent *e)
926{
927 QScrollBar::paintEvent(e);
928
929 if (!m_showMarks) {
930 return;
931 }
932
933 QPainter painter(this);
934
935 QStyleOptionSlider opt;
936 opt.initFrom(w: 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
947 QRect rect = style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: QStyle::SC_ScrollBarSlider, widget: this);
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(x1: 0, y1: it.key(), x2: width(), y2: it.key());
960 } else {
961 painter.drawLine(x1: 0, y1: it.key(), x2: sideMargin, y2: it.key());
962 painter.drawLine(x1: width() - sideMargin, y1: it.key(), x2: width(), y2: it.key());
963 }
964 }
965}
966
967void KateScrollBar::resizeEvent(QResizeEvent *e)
968{
969 QScrollBar::resizeEvent(event: e);
970 m_updateTimer.start();
971 m_lines.clear();
972 update();
973}
974
975void KateScrollBar::sliderChange(SliderChange change)
976{
977 // call parents implementation
978 QScrollBar::sliderChange(change);
979
980 if (change == QAbstractSlider::SliderValueChange) {
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(virtualCursor: m_viewInternal->startPos()).line() + 1;
988 const int lastLine = m_viewInternal->toRealCursor(virtualCursor: m_viewInternal->endPos()).line() + 1;
989 QToolTip::showText(pos: m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), w: 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
1010 QStyleOptionSlider opt;
1011 initStyleOption(option: &opt);
1012 QRect grooveRect = style()->subControlRect(cc: QStyle::CC_ScrollBar, opt: &opt, sc: QStyle::SC_ScrollBarGroove, widget: this);
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(line: mark->line);
1036 const double ratio = static_cast<double>(line) / visibleLines;
1037 m_lines.insert(key: top + (int)(h * ratio), value: KateRendererConfig::global()->lineMarkerColor(type: (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(left: 0, top: 0, right: 0, bottom: 0);
1079 m_lineEdit = new KateCmdLineEdit(this, view);
1080 connect(sender: m_lineEdit, signal: &KateCmdLineEdit::hideRequested, context: this, slot: &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(sender: helpButton, signal: &QToolButton::clicked, context: this, slot: &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(cmd: 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(sender: this, signal: &KateCmdLineEdit::returnKeyPressed, context: this, slot: &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(sender: m_hideTimer, signal: &QTimer::timeout, context: this, slot: &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)
1136 connect(sender: m_view, signal: &KTextEditor::ViewPrivate::focusOut, context: m_hideTimer, slot: &QTimer::stop);
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(subject: t);
1153 if (match.hasMatch()) {
1154 QString s;
1155 // get help for command
1156 const QString name = match.captured(nth: 1);
1157 if (name == QLatin1String("list")) {
1158 return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(sep: 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(cmd: name);
1162 if (cmd) {
1163 if (cmd->help(view: m_view, cmd: name, msg&: 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(position: 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(command: cmd, view: m_view, destRangeExpression&: leadingRangeExpression, destTransformedCommand&: cmd);
1240
1241 // Built in help: if the command starts with "help", [try to] show some help
1242 if (cmd.startsWith(s: QLatin1String("help"))) {
1243 QWhatsThis::showText(pos: mapToGlobal(QPoint(0, 0)), text: 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(subjectView: QStringView(cmd).left(n: cmd.indexOf(c: 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(view: m_view, cmd, msg, range)) {
1270 // append command along with range (will be empty if none given) to history
1271 KateCmd::self()->appendHistory(cmd: 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(c: QLatin1Char('\n'))) {
1284 // multiline error, use widget with more space
1285 QWhatsThis::showText(pos: mapToGlobal(QPoint(0, 0)), text: msg);
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()) {
1298 KCompletion *c = completionObject();
1299 setCompletionObject(KateCmd::self()->commandCompletionObject());
1300 delete c;
1301 }
1302 m_command = nullptr;
1303 m_cmdend = 0;
1304
1305 if (!focusChangingCommands.matchView(subjectView: QStringView(cmd).left(n: cmd.indexOf(c: QLatin1Char(' ')))).hasMatch()) {
1306 m_view->setFocus();
1307 }
1308
1309 if (isVisible()) {
1310 m_hideTimer->start(msec: 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
1329 KLineEdit::focusInEvent(ev);
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(up: true);
1340 } else if (ev->key() == Qt::Key_Down) {
1341 fromHistory(up: false);
1342 }
1343
1344 uint cursorpos = cursorPosition();
1345 KLineEdit::keyPressEvent(ev);
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(i: 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(cmd: 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(cmd: 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()) {
1391 KCompletion *c = completionObject();
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(view: m_view, cmdname: text().left(n: 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(cmdname: text().left(n: m_cmdend).trimmed())) {
1414 m_command->processText(view: m_view, text: 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(i: m_histpos);
1431 }
1432 } else {
1433 if (m_histpos < (KateCmd::self()->historyLength() - 1)) {
1434 m_histpos++;
1435 s = KateCmd::self()->fromHistory(i: 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(subject: text());
1446 if (match.hasMatch()) {
1447 setSelection(text().length() - match.capturedLength(nth: 1), match.capturedLength(nth: 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
1480 setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Minimum);
1481 setMouseTracking(true);
1482 m_doc->setMarkDescription(Document::markType01, i18n("Bookmark"));
1483 m_doc->setMarkIcon(markType: Document::markType01, icon: QIcon::fromTheme(QStringLiteral("bookmarks")));
1484
1485 connect(sender: m_annotationItemDelegate, signal: &AbstractAnnotationItemDelegate::sizeHintChanged, context: this, slot: &KateIconBorder::updateAnnotationBorderWidth);
1486
1487 updateFont();
1488
1489 m_antiFlickerTimer.setSingleShot(true);
1490 m_antiFlickerTimer.setInterval(300);
1491 connect(sender: &m_antiFlickerTimer, signal: &QTimer::timeout, context: this, slot: &KateIconBorder::highlightFolding);
1492
1493 // user interaction (scrolling) hides e.g. preview
1494 connect(sender: m_view, signal: &KTextEditor::ViewPrivate::displayRangeChanged, context: this, slot: &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(msec: 0, receiver: 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(view: m_view, visible: enable);
1531
1532 m_updatePositionToArea = true;
1533
1534 QTimer::singleShot(msec: 0, receiver: 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(msec: 0, receiver: 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(msec: 0, receiver: 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(msec: 0, receiver: 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(msec: 0, receiver: 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(msec: 0, receiver: 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(n: 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(x: fm.horizontalAdvance(QLatin1Char(c)));
1634 m_maxCharWidth = qMax(a: m_maxCharWidth, b: 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
1652 QMetaObject::invokeMethod(
1653 object: this,
1654 function: [this] {
1655 update();
1656 },
1657 type: Qt::QueuedConnection);
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(x: log10(x: (double)(m_view->doc()->lines() + 1)));
1667 width = (int)ceil(x: (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(p: pos, keepSelection: true, updateSelection: 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(x: e->rect().x(), y: e->rect().y(), width: e->rect().width(), height: e->rect().height());
1703}
1704
1705static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open)
1706{
1707 painter.setRenderHint(hint: QPainter::Antialiasing);
1708
1709 qreal size = qMin(a: width, b: 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, lumaAmount: 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, lumaAmount: 0.1);
1725 }
1726 }
1727
1728 QPen pen;
1729 pen.setJoinStyle(Qt::RoundJoin);
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, pointCount: 3);
1745 } else {
1746 QPointF points[3] = {middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0)};
1747 painter.drawConvexPolygon(points, pointCount: 3);
1748 }
1749
1750 painter.setRenderHint(hint: QPainter::Antialiasing, on: 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(viewLine: startz).line();
1867 m_nextAnnotationGroupIdentifier = m_model->data(line: realLineAtStart, role: (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(line: realLineAtStart - 1, role: (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1876 m_isSameAnnotationGroupsSinceLast = (m_lastAnnotationGroupIdentifier == m_nextAnnotationGroupIdentifier);
1877 }
1878 } else {
1879 const auto realLineBeforeStart = m_viewInternal->cache()->viewLine(viewLine: startz - 1).line();
1880 m_lastAnnotationGroupIdentifier = m_model->data(line: realLineBeforeStart, role: (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(viewLine: z - 1).line();
1887 const KateAnnotationGroupIdentifier identifier =
1888 m_model->data(line: realLine, role: (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(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(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(line: rl, role: (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(line: realLineAfter, role: (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
1948 styleOption.annotationItemGroupingPosition = StyleOptionAnnotationItem::InGroup;
1949 if (!m_isSameAnnotationGroupsSinceLast) {
1950 styleOption.annotationItemGroupingPosition |= StyleOptionAnnotationItem::GroupBegin;
1951 }
1952 if (!isSameAnnotationGroupsSinceThis) {
1953 styleOption.annotationItemGroupingPosition |= StyleOptionAnnotationItem::GroupEnd;
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(a: startz + 1 + (height / h), b: 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(hints: 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(viewLine: z);
2010 const int realLine = lineLayout.line();
2011
2012 // Paint background over full width
2013 p.fillRect(x: lnX, y, w, h, b: iconBarColor);
2014
2015 // overpaint with current line highlighting over full width
2016 const bool isCurrentLine = (realLine == static_cast<int>(currentLine)) && lineLayout.includesCursor(realCursor: m_view->cursorPosition());
2017 if (isCurrentLine) {
2018 p.fillRect(x: lnX, y, w, h, b: 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(line: 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(a: m_iconAreaWidth, b: static_cast<int>(h)) - 2;
2034
2035 // center the mark pixmap
2036 const int x_px = qMax(a: m_iconAreaWidth - s, b: 0) / 2;
2037 const int y_px = qMax(a: static_cast<int>(h) - s, b: 0) / 2;
2038
2039 markIcon.paint(painter: &p, x: lnX + x_px, y: y + y_px, w: s, h: s);
2040 }
2041 }
2042 }
2043 }
2044
2045 lnX += m_iconAreaWidth;
2046 if (m_updatePositionToArea) {
2047 m_positionToArea.push_back(x: 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(p1: QPointF(borderX, y + 0.5), p2: QPointF(borderX, y + h - 0.5));
2059
2060 if (model) {
2061 KTextEditor::StyleOptionAnnotationItem styleOption;
2062 initStyleOption(styleOption: &styleOption);
2063 styleOption.rect.setRect(ax: lnX, ay: y, aw: m_annotationAreaWidth, ah: h);
2064 annotationGroupPositionState.nextLine(styleOption, z, realLine);
2065
2066 m_annotationItemDelegate->paint(painter: &p, option: styleOption, model, line: realLine);
2067 }
2068
2069 lnX += m_annotationAreaWidth + m_separatorWidth;
2070 if (m_updatePositionToArea) {
2071 m_positionToArea.push_back(x: AreaPosition(lnX, AnnotationBorder));
2072 }
2073 }
2074
2075 // line number
2076 if (m_lineNumbersOn || m_dynWrapIndicatorsOn) {
2077 QColor usedLineNumberColor;
2078 const int distanceToCurrent = abs(x: 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(x: lnX + m_maxCharWidth / 2,
2091 y,
2092 w: m_lineNumberAreaWidth - m_maxCharWidth,
2093 h,
2094 flags: Qt::TextDontClip | Qt::AlignLeft | Qt::AlignVCenter,
2095 str: QString::number(realLine + 1));
2096 } else {
2097 p.drawText(x: lnX + m_maxCharWidth / 2,
2098 y,
2099 w: m_lineNumberAreaWidth - m_maxCharWidth,
2100 h,
2101 flags: Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter,
2102 str: QString::number(distanceToCurrent));
2103 }
2104 if (m_updateRelLineNumbers) {
2105 m_updateRelLineNumbers = false;
2106 update();
2107 }
2108 } else if (m_lineNumbersOn) {
2109 p.drawText(x: lnX + m_maxCharWidth / 2,
2110 y,
2111 w: m_lineNumberAreaWidth - m_maxCharWidth,
2112 h,
2113 flags: Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter,
2114 str: QString::number(realLine + 1));
2115 }
2116 } else if (m_dynWrapIndicatorsOn) {
2117 p.drawText(x: lnX + m_maxCharWidth / 2,
2118 y,
2119 w: m_lineNumberAreaWidth - m_maxCharWidth,
2120 h,
2121 flags: Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter,
2122 str: m_dynWrapIndicatorChar);
2123 }
2124
2125 lnX += m_lineNumberAreaWidth + m_separatorWidth;
2126 if (m_updatePositionToArea) {
2127 m_positionToArea.push_back(x: 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(i: realLine);
2134 if (tl.markedAsModified()) {
2135 p.fillRect(x: lnX, y, w: m_modAreaWidth, h, b: m_view->rendererConfig()->modifiedLineColor());
2136 } else if (tl.markedAsSavedOnDisk()) {
2137 p.fillRect(x: lnX, y, w: m_modAreaWidth, h, b: m_view->rendererConfig()->savedLineColor());
2138 }
2139
2140 lnX += m_modAreaWidth; // No m_separatorWidth
2141 if (m_updatePositionToArea) {
2142 m_positionToArea.push_back(x: 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(line: realLine)) {
2151 p.fillRect(x: lnX, y, w: m_foldingAreaWidth, h, b: foldingColor);
2152 }
2153
2154 if (lineLayout.startCol() == 0) {
2155 QList<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>> startingRanges = m_view->textFolding().foldingRangesStartingOnLine(line: 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(startLine: realLine).first) {
2164 if (anyFolded) {
2165 paintTriangle(painter&: p, c: foldingColor, xOffset: lnX, yOffset: y, width: m_foldingAreaWidth, height: h, open: false);
2166 } else {
2167 // Don't try to use currentLineNumberColor, the folded icon gets also not highligted
2168 paintTriangle(painter&: p, c: lineNumberColor, xOffset: lnX, yOffset: y, width: m_foldingAreaWidth, height: h, open: true);
2169 }
2170 }
2171 }
2172 }
2173
2174 lnX += m_foldingAreaWidth;
2175 if (m_updatePositionToArea) {
2176 m_positionToArea.push_back(x: 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(x: w - 2 * m_separatorWidth, y, w, h, b: 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(x: w - 2 * m_separatorWidth, y, w, h, b: m_view->rendererConfig()->selectionColor());
2193 } else if (isCurrentLine) {
2194 // normal current line overpaint
2195 p.fillRect(x: w - 2 * m_separatorWidth, y, w, h, b: 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(x1: w - 2 * m_separatorWidth, y1: y, x2: w - 2 * m_separatorWidth, y2: 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(x: 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(interval: 0, receiver: this, slot: &KateIconBorder::delayedUpdateOfSizeWithRepaint);
2214 }
2215 }
2216}
2217
2218KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const
2219{
2220 auto it = std::find_if(first: m_positionToArea.cbegin(), last: m_positionToArea.cend(), pred: [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(y: e->position().y());
2232 if (t.isValid()) {
2233 m_lastClickedLine = t.line();
2234 const auto area = positionToArea(p: 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 }
2242 QMouseEvent forward(QEvent::MouseButtonPress, pos, m_viewInternal->mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
2243 m_viewInternal->mousePressEvent(&forward);
2244 }
2245 return e->accept();
2246 }
2247
2248 QWidget::mousePressEvent(event: e);
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!
2272 KTextEditor::Range newRange = KTextEditor::Range::invalid();
2273 for (int line = m_currentLine; line >= qMax(a: 0, b: 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(startLine: line);
2276 if (!foldingRange.isValid()) {
2277 continue;
2278 }
2279
2280 // does the range reach us?
2281 if (foldingRange.overlapsLine(line: 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(line: newRange.start().line() + 1);
2303
2304 // qCDebug(LOG_KTE) << "new folding hl-range:" << newRange;
2305 m_foldingRange = m_doc->newMovingRange(range: newRange, insertBehaviors: KTextEditor::MovingRange::ExpandRight);
2306 KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute());
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(cursor: 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(a: m_foldingRange->numberOfLines() + 1, b: m_viewInternal->linesDisplayed() - lineInDisplay + extra);
2335
2336 m_foldingPreview->resize(w: m_viewInternal->width(), h: 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(cursor: 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();
2366 QWidget::enterEvent(event);
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
2377 QWidget::leaveEvent(event);
2378}
2379
2380void KateIconBorder::mouseMoveEvent(QMouseEvent *e)
2381{
2382 const KateTextLayout &t = m_viewInternal->yToKateTextLayout(y: 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(p: e->pos());
2390 if (area == FoldingMarkers) {
2391 highlightFoldingDelayed(line: 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(line: t.line(), role: (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());
2401 KTextEditor::StyleOptionAnnotationItem styleOption;
2402 initStyleOption(styleOption: &styleOption);
2403 styleOption.rect = annotationLineRectInView(line: t.line());
2404 setStyleOptionLineData(styleOption: &styleOption, y: e->position().y(), realLine: t.line(), model, annotationGroupIdentifier: m_hoveredAnnotationGroupIdentifier);
2405 m_annotationItemDelegate->helpEvent(event: &helpEvent, view: m_view, option: styleOption, model, line: t.line());
2406
2407 QTimer::singleShot(msec: 0, receiver: this, SLOT(update()));
2408 }
2409 } else {
2410 if (area == IconBorder) {
2411 m_doc->requestMarkTooltip(line: t.line(), position: e->globalPosition().toPoint());
2412 }
2413
2414 m_hoveredAnnotationGroupIdentifier.clear();
2415 QTimer::singleShot(msec: 0, receiver: 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
2424 QWidget::mouseMoveEvent(event: e);
2425}
2426
2427void KateIconBorder::mouseReleaseEvent(QMouseEvent *e)
2428{
2429 const int cursorOnLine = m_viewInternal->yToKateTextLayout(y: e->position().y()).line();
2430 if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) {
2431 const BorderArea area = positionToArea(p: e->pos());
2432 if (area == IconBorder) {
2433 if (e->button() == Qt::LeftButton) {
2434 if (!m_doc->handleMarkClick(line: 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(v: editBits) == 1 || ctrlPressed) {
2440 const uint singleMark = (qPopulationCount(v: editBits) > 1) ? (editBits & config->defaultMarkType()) : editBits;
2441 if (m_doc->mark(line: cursorOnLine) & singleMark) {
2442 m_doc->removeMark(line: cursorOnLine, markType: singleMark);
2443 } else {
2444 m_doc->addMark(line: cursorOnLine, markType: singleMark);
2445 }
2446 } else if (config->allowMarkMenu()) {
2447 showMarkMenu(line: cursorOnLine, pos: QCursor::pos());
2448 }
2449 }
2450 } else if (e->button() == Qt::RightButton) {
2451 showMarkMenu(line: cursorOnLine, pos: 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(line: lineToToggle);
2460 } else if (e->button() == Qt::RightButton) {
2461 m_view->toggleFoldingsInRange(line: lineToToggle);
2462 }
2463
2464 delete m_foldingPreview;
2465 }
2466
2467 if (area == AnnotationBorder) {
2468 const bool singleClick = style()->styleHint(stylehint: QStyle::SH_ItemView_ActivateItemOnSingleClick, opt: nullptr, widget: this);
2469 if (e->button() == Qt::LeftButton && singleClick) {
2470 Q_EMIT m_view->annotationActivated(view: m_view, line: cursorOnLine);
2471 }
2472 }
2473 }
2474
2475 const QPoint pos(0, e->position().y());
2476 QMouseEvent forward(QEvent::MouseButtonRelease, pos, m_viewInternal->mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
2477 m_viewInternal->mouseReleaseEvent(&forward);
2478}
2479
2480void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e)
2481{
2482 int cursorOnLine = m_viewInternal->yToKateTextLayout(y: e->position().y()).line();
2483
2484 if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) {
2485 const BorderArea area = positionToArea(p: e->pos());
2486 const bool singleClick = style()->styleHint(stylehint: QStyle::SH_ItemView_ActivateItemOnSingleClick, opt: nullptr, widget: this);
2487 if (area == AnnotationBorder && !singleClick) {
2488 Q_EMIT m_view->annotationActivated(view: m_view, line: cursorOnLine);
2489 }
2490 }
2491 const QPoint pos(0, e->position().y());
2492 QMouseEvent forward(QEvent::MouseButtonDblClick, pos, m_viewInternal->mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
2493 m_viewInternal->mouseDoubleClickEvent(&forward);
2494}
2495
2496void KateIconBorder::contextMenuEvent(QContextMenuEvent *e)
2497{
2498 const BorderArea area = positionToArea(p: e->pos());
2499 const int cursorOnLine = m_viewInternal->yToKateTextLayout(y: e->y()).line();
2500
2501 if (area == AnnotationBorder) {
2502 showAnnotationMenu(line: cursorOnLine, pos: 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(action: bookmarkToggle);
2513 }
2514 if (QAction *bookmarkClear = ac->action(QStringLiteral("bookmarks_clear"))) {
2515 menu.addAction(action: bookmarkClear);
2516 }
2517
2518 menu.addSeparator();
2519
2520 if (QAction *toggleDynWrap = ac->action(QStringLiteral("view_dynamic_word_wrap"))) {
2521 menu.addAction(action: toggleDynWrap);
2522 }
2523 menu.addSeparator();
2524 if (QAction *toggleIconBar = ac->action(QStringLiteral("view_border"))) {
2525 menu.addAction(action: toggleIconBar);
2526 }
2527 if (QAction *toggleLineNumbers = ac->action(QStringLiteral("view_line_numbers"))) {
2528 menu.addAction(action: toggleLineNumbers);
2529 }
2530 if (QAction *toggleFoldingMarkers = ac->action(QStringLiteral("view_folding_markers"))) {
2531 menu.addAction(action: toggleFoldingMarkers);
2532 }
2533
2534 menu.exec(pos: e->globalPos());
2535}
2536
2537void KateIconBorder::wheelEvent(QWheelEvent *e)
2538{
2539 QCoreApplication::sendEvent(receiver: m_viewInternal, event: e);
2540}
2541
2542void KateIconBorder::showMarkMenu(uint line, const QPoint &pos)
2543{
2544 if (m_doc->handleMarkContextMenu(line, position: 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++) {
2560 KTextEditor::Document::MarkTypes markType = (KTextEditor::Document::MarkTypes)(1U << 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, text: m_doc->markDescription(markType));
2570 dMA = selectDefaultMark.addAction(icon, text: 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(a: 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(key: KateViewConfig::DefaultMarkType, value: vec[result - 100]);
2606 } else {
2607 KTextEditor::Document::MarkTypes markType = (KTextEditor::Document::MarkTypes)vec[result];
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(sender: m_annotationItemDelegate, signal: &AbstractAnnotationItemDelegate::sizeHintChanged, receiver: this, slot: &KateIconBorder::updateAnnotationBorderWidth);
2640 if (!m_isDefaultAnnotationItemDelegate) {
2641 disconnect(sender: m_annotationItemDelegate, signal: &QObject::destroyed, receiver: this, slot: &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(sender: delegate, signal: &QObject::destroyed, context: this, slot: &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2658 }
2659
2660 connect(sender: m_annotationItemDelegate, signal: &AbstractAnnotationItemDelegate::sizeHintChanged, context: this, slot: &KateIconBorder::updateAnnotationBorderWidth);
2661
2662 if (m_annotationBorderOn) {
2663 QTimer::singleShot(interval: 0, receiver: this, slot: &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);
2676 updateGeometry();
2677 repaint();
2678 setUpdatesEnabled(true);
2679}
2680
2681void KateIconBorder::initStyleOption(KTextEditor::StyleOptionAnnotationItem *styleOption) const
2682{
2683 styleOption->initFrom(w: 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&: *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(viewLine: 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) {
2721 KTextEditor::StyleOptionAnnotationItem styleOption;
2722 initStyleOption(styleOption: &styleOption);
2723 width = m_annotationItemDelegate->sizeHint(option: styleOption, model, line).width();
2724 }
2725
2726 if (width > m_annotationAreaWidth) {
2727 m_annotationAreaWidth = width;
2728 m_updatePositionToArea = true;
2729
2730 QTimer::singleShot(msec: 0, receiver: 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(action: &a);
2740 Q_EMIT m_view->annotationContextMenuAboutToShow(view: m_view, menu: &menu, line);
2741 if (menu.exec(pos) == &a) {
2742 m_view->setAnnotationBorderVisible(false);
2743 }
2744}
2745
2746void KateIconBorder::hideAnnotationTooltip()
2747{
2748 m_annotationItemDelegate->hideTooltip(view: m_view);
2749}
2750
2751void KateIconBorder::updateAnnotationBorderWidth()
2752{
2753 calcAnnotationBorderWidth();
2754
2755 m_updatePositionToArea = true;
2756
2757 QTimer::singleShot(msec: 0, receiver: 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) {
2767 KTextEditor::StyleOptionAnnotationItem styleOption;
2768 initStyleOption(styleOption: &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(option: styleOption, model, line: 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(receiver: this);
2787 }
2788 if (newmodel) {
2789 connect(sender: newmodel, signal: &KTextEditor::AnnotationModel::reset, context: this, slot: &KateIconBorder::updateAnnotationBorderWidth);
2790 connect(sender: newmodel, signal: &KTextEditor::AnnotationModel::lineChanged, context: this, slot: &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{
2815 QList<KSelectAction *> actions;
2816
2817 setToolBarMode(MenuMode);
2818
2819 int i;
2820 const auto encodingsByScript = KCharsets::charsets()->encodingsByScript();
2821 actions.reserve(asize: encodingsByScript.size());
2822 for (const QStringList &encodingsForScript : encodingsByScript) {
2823 KSelectAction *tmp = new KSelectAction(encodingsForScript.at(i: 0), this);
2824
2825 for (i = 1; i < encodingsForScript.size(); ++i) {
2826 tmp->addAction(text: encodingsForScript.at(i));
2827 }
2828 connect(sender: tmp, signal: &KSelectAction::actionTriggered, context: this, slot: [this](QAction *action) {
2829 subActionTriggered(action);
2830 });
2831 // tmp->setCheckable(true);
2832 actions << tmp;
2833 }
2834 std::sort(first: actions.begin(), last: actions.end(), comp: lessThanAction);
2835 for (KSelectAction *action : std::as_const(t&: actions)) {
2836 addAction(action);
2837 }
2838}
2839
2840void KateViewEncodingAction::subActionTriggered(QAction *action)
2841{
2842 if (currentSubAction == action) {
2843 return;
2844 }
2845 currentSubAction = action;
2846 Q_EMIT textTriggered(text: action->text());
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(sender: menu(), signal: &QMenu::aboutToShow, context: this, slot: &KateViewEncodingAction::slotAboutToShow);
2862 connect(sender: this, signal: &KSelectAction::textTriggered, context: this, slot: &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(encoding: 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(sender: this, signal: &KSelectAction::textTriggered, receiver: this, slot: &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(i: j)->data().isNull()) {
2894 continue;
2895 }
2896 if (actions().at(i)->menu()->actions().at(i: j)->isSeparator()) {
2897 continue;
2898 }
2899
2900 if (codec == actions().at(i)->menu()->actions().at(i: j)->text()) {
2901 currentSubAction = actions().at(i)->menu()->actions().at(i: j);
2902 currentSubAction->setChecked(true);
2903 } else {
2904 actions().at(i)->menu()->actions().at(i: j)->setChecked(false);
2905 }
2906 }
2907 }
2908 }
2909
2910 connect(sender: this, signal: &KSelectAction::textTriggered, context: this, slot: &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(left: 0, top: 0, right: 0, bottom: 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(sender: m_closeButton, signal: &QToolButton::clicked, context: this, slot: &KateViewBarWidget::hideMe);
2938 layout->addWidget(m_closeButton);
2939 layout->setAlignment(w: m_closeButton, alignment: 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(left: 0, top: 0, right: 0, bottom: 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(barWidget: newBarWidget)) {
2963 return;
2964 }
2965
2966 // add new widget, invisible...
2967 newBarWidget->hide();
2968 m_stack->addWidget(w: newBarWidget);
2969 newBarWidget->setAssociatedViewBar(this);
2970 connect(sender: newBarWidget, signal: &KateViewBarWidget::hideMe, context: this, slot: &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(w: barWidget);
2981 barWidget->setAssociatedViewBar(nullptr);
2982 barWidget->hide();
2983 disconnect(sender: barWidget, signal: nullptr, receiver: this, member: 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(w: 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 *>(object: m_stack->currentWidget())) {
3017 hideCurrentBarWidget();
3018 }
3019
3020 // raise correct widget
3021 m_stack->addWidget(w: barWidget);
3022 m_stack->setCurrentWidget(barWidget);
3023 barWidget->show();
3024 barWidget->setFocus(Qt::ShortcutFocusReason);
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 *>(object: m_stack->currentWidget());
3037 if (current) {
3038 m_stack->removeWidget(w: 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(view: m_view);
3056 } else {
3057 m_view->mainWindow()->hideViewBar(view: m_view);
3058 }
3059 } else {
3060 setVisible(visible);
3061 }
3062}
3063
3064bool KateViewBar::barWidgetVisible() const
3065{
3066 KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(object: 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 }
3076 QWidget::keyPressEvent(event);
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(sender: menu(), signal: &QMenu::aboutToShow, context: this, slot: &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(str: hlName)) {
3118 names << hlName;
3119 QAction *a = menu()->addAction(text: hlName, args: this, args: &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 *>(object: 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"
3155

source code of ktexteditor/src/view/kateviewhelpers.cpp