KTextEditor

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

KDE's Doxygen guidelines are available online.