File indexing completed on 2024-04-21 03:57:57

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2008, 2009 Matthew Woehlke <mw_triad@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
0005     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
0006     SPDX-FileCopyrightText: 2001 Anders Lund <anders@alweb.dk>
0007     SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
0008     SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
0009     SPDX-FileCopyrightText: 2012 Kåre Särs <kare.sars@iki.fi> (Minimap)
0010     SPDX-FileCopyrightText: 2017-2018 Friedrich W. H. Kossebau <kossebau@kde.org>
0011 
0012     SPDX-License-Identifier: LGPL-2.0-only
0013 */
0014 
0015 #include "kateviewhelpers.h"
0016 
0017 #include "kateabstractinputmode.h"
0018 #include "kateannotationitemdelegate.h"
0019 #include "katecmd.h"
0020 #include "katecommandrangeexpressionparser.h"
0021 #include "kateconfig.h"
0022 #include "katedocument.h"
0023 #include "kateglobal.h"
0024 #include "katelayoutcache.h"
0025 #include "katepartdebug.h"
0026 #include "katerenderer.h"
0027 #include "katesyntaxmanager.h"
0028 #include "katetextlayout.h"
0029 #include "katetextpreview.h"
0030 #include "kateview.h"
0031 #include "kateviewinternal.h"
0032 #include <katebuffer.h>
0033 #include <ktexteditor/annotationinterface.h>
0034 #include <ktexteditor/attribute.h>
0035 #include <ktexteditor/command.h>
0036 #include <ktexteditor/movingrange.h>
0037 
0038 #include <KActionCollection>
0039 #include <KCharsets>
0040 #include <KColorUtils>
0041 #include <KConfigGroup>
0042 #include <KHelpClient>
0043 #include <KLocalizedString>
0044 
0045 #include <QAction>
0046 #include <QActionGroup>
0047 #include <QBoxLayout>
0048 #include <QCursor>
0049 #include <QGuiApplication>
0050 #include <QKeyEvent>
0051 #include <QLinearGradient>
0052 #include <QMenu>
0053 #include <QPainter>
0054 #include <QPainterPath>
0055 #include <QPalette>
0056 #include <QPen>
0057 #include <QRegularExpression>
0058 #include <QStackedWidget>
0059 #include <QStyle>
0060 #include <QStyleOption>
0061 #include <QToolButton>
0062 #include <QToolTip>
0063 #include <QVariant>
0064 #include <QWhatsThis>
0065 #include <QtAlgorithms>
0066 
0067 #include <math.h>
0068 
0069 // BEGIN KateMessageLayout
0070 KateMessageLayout::KateMessageLayout(QWidget *parent)
0071     : QLayout(parent)
0072 {
0073     qCDebug(LOG_KTE);
0074 }
0075 
0076 KateMessageLayout::~KateMessageLayout()
0077 {
0078     while (QLayoutItem *item = takeAt(0)) {
0079         delete item;
0080     }
0081 }
0082 
0083 void KateMessageLayout::addItem(QLayoutItem *item)
0084 {
0085     Q_ASSERT(false);
0086     add(item, KTextEditor::Message::CenterInView);
0087 }
0088 
0089 void KateMessageLayout::addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos)
0090 {
0091     add(new QWidgetItem(widget), pos);
0092 }
0093 
0094 int KateMessageLayout::count() const
0095 {
0096     return m_items.size();
0097 }
0098 
0099 QLayoutItem *KateMessageLayout::itemAt(int index) const
0100 {
0101     return m_items.value(index).item;
0102 }
0103 
0104 void KateMessageLayout::setGeometry(const QRect &rect)
0105 {
0106     QLayout::setGeometry(rect);
0107     const int s = spacing();
0108     const QRect adjustedRect = rect.adjusted(s, s, -s, -s);
0109 
0110     for (const auto &wrapper : std::as_const(m_items)) {
0111         QLayoutItem *item = wrapper.item;
0112         auto position = wrapper.position;
0113 
0114         if (position == KTextEditor::Message::TopInView) {
0115             const QRect r(adjustedRect.width() - item->sizeHint().width(), s, item->sizeHint().width(), item->sizeHint().height());
0116             item->setGeometry(r);
0117         } else if (position == KTextEditor::Message::BottomInView) {
0118             const QRect r(adjustedRect.width() - item->sizeHint().width(),
0119                           adjustedRect.height() - item->sizeHint().height(),
0120                           item->sizeHint().width(),
0121                           item->sizeHint().height());
0122             item->setGeometry(r);
0123         } else if (position == KTextEditor::Message::CenterInView) {
0124             QRect r(0, 0, item->sizeHint().width(), item->sizeHint().height());
0125             r.moveCenter(adjustedRect.center());
0126             item->setGeometry(r);
0127         } else {
0128             Q_ASSERT_X(false, "setGeometry", "Only TopInView, CenterInView, and BottomInView are supported.");
0129         }
0130     }
0131 }
0132 
0133 QSize KateMessageLayout::sizeHint() const
0134 {
0135     return QSize();
0136 }
0137 
0138 QLayoutItem *KateMessageLayout::takeAt(int index)
0139 {
0140     if (index >= 0 && index < m_items.size()) {
0141         return m_items.takeAt(index).item;
0142     }
0143     return nullptr;
0144 }
0145 
0146 void KateMessageLayout::add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos)
0147 {
0148     m_items.push_back({item, pos});
0149 }
0150 // END KateMessageLayout
0151 
0152 // BEGIN KateScrollBar
0153 static const int s_lineWidth = 100;
0154 static const int s_pixelMargin = 8;
0155 static const int s_linePixelIncLimit = 6;
0156 
0157 KateScrollBar::KateScrollBar(Qt::Orientation orientation, KateViewInternal *parent)
0158     : QScrollBar(orientation, parent->m_view)
0159     , m_middleMouseDown(false)
0160     , m_leftMouseDown(false)
0161     , m_view(parent->m_view)
0162     , m_doc(parent->doc())
0163     , m_viewInternal(parent)
0164     , m_textPreview(nullptr)
0165     , m_showMarks(false)
0166     , m_showMiniMap(false)
0167     , m_miniMapAll(true)
0168     , m_needsUpdateOnShow(false)
0169     , m_miniMapWidth(40)
0170     , m_grooveHeight(height())
0171 {
0172     connect(this, &KateScrollBar::valueChanged, this, &KateScrollBar::sliderMaybeMoved);
0173     connect(m_doc, &KTextEditor::DocumentPrivate::marksChanged, this, &KateScrollBar::marksChanged);
0174 
0175     m_updateTimer.setInterval(300);
0176     m_updateTimer.setSingleShot(true);
0177 
0178     // track mouse for text preview widget
0179     setMouseTracking(orientation == Qt::Vertical);
0180 
0181     // setup text preview timer
0182     m_delayTextPreviewTimer.setSingleShot(true);
0183     m_delayTextPreviewTimer.setInterval(250);
0184     connect(&m_delayTextPreviewTimer, &QTimer::timeout, this, &KateScrollBar::showTextPreview);
0185 }
0186 
0187 void KateScrollBar::showEvent(QShowEvent *event)
0188 {
0189     QScrollBar::showEvent(event);
0190 
0191     if (m_needsUpdateOnShow) {
0192         m_needsUpdateOnShow = false;
0193         updatePixmap();
0194     }
0195 }
0196 
0197 KateScrollBar::~KateScrollBar()
0198 {
0199     delete m_textPreview;
0200 }
0201 
0202 void KateScrollBar::setShowMiniMap(bool b)
0203 {
0204     if (b && !m_showMiniMap) {
0205         auto timerSlot = qOverload<>(&QTimer::start);
0206         connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, &m_updateTimer, timerSlot, Qt::UniqueConnection);
0207         connect(m_doc, &KTextEditor::DocumentPrivate::textChanged, &m_updateTimer, timerSlot, Qt::UniqueConnection);
0208         connect(m_view, &KTextEditor::ViewPrivate::delayedUpdateOfView, &m_updateTimer, timerSlot, Qt::UniqueConnection);
0209         connect(&m_updateTimer, &QTimer::timeout, this, &KateScrollBar::updatePixmap, Qt::UniqueConnection);
0210         connect(&(m_view->textFolding()), &Kate::TextFolding::foldingRangesChanged, &m_updateTimer, timerSlot, Qt::UniqueConnection);
0211     } else if (!b) {
0212         disconnect(&m_updateTimer);
0213     }
0214 
0215     m_showMiniMap = b;
0216 
0217     updateGeometry();
0218     update();
0219 }
0220 
0221 QSize KateScrollBar::sizeHint() const
0222 {
0223     if (m_showMiniMap) {
0224         return QSize(m_miniMapWidth, QScrollBar::sizeHint().height());
0225     }
0226     return QScrollBar::sizeHint();
0227 }
0228 
0229 int KateScrollBar::minimapYToStdY(int y)
0230 {
0231     // Check if the minimap fills the whole scrollbar
0232     if (m_stdGroveRect.height() == m_mapGroveRect.height()) {
0233         return y;
0234     }
0235 
0236     // check if y is on the step up/down
0237     if ((y < m_stdGroveRect.top()) || (y > m_stdGroveRect.bottom())) {
0238         return y;
0239     }
0240 
0241     if (y < m_mapGroveRect.top()) {
0242         return m_stdGroveRect.top() + 1;
0243     }
0244 
0245     if (y > m_mapGroveRect.bottom()) {
0246         return m_stdGroveRect.bottom() - 1;
0247     }
0248 
0249     // check for div/0
0250     if (m_mapGroveRect.height() == 0) {
0251         return y;
0252     }
0253 
0254     int newY = (y - m_mapGroveRect.top()) * m_stdGroveRect.height() / m_mapGroveRect.height();
0255     newY += m_stdGroveRect.top();
0256     return newY;
0257 }
0258 
0259 void KateScrollBar::mousePressEvent(QMouseEvent *e)
0260 {
0261     // delete text preview
0262     hideTextPreview();
0263 
0264     if (e->button() == Qt::MiddleButton) {
0265         m_middleMouseDown = true;
0266     } else if (e->button() == Qt::LeftButton) {
0267         m_leftMouseDown = true;
0268     }
0269 
0270     if (m_showMiniMap) {
0271         if (!m_sliderRect.contains(e->pos()) && m_leftMouseDown && e->pos().y() > m_mapGroveRect.top() && e->pos().y() < m_mapGroveRect.bottom()) {
0272             // if we show the minimap left-click jumps directly to the selected position
0273             int newVal = (e->pos().y() - m_mapGroveRect.top()) / (double)m_mapGroveRect.height() * (double)(maximum() + pageStep()) - pageStep() / 2;
0274             newVal = qBound(0, newVal, maximum());
0275             setSliderPosition(newVal);
0276         }
0277         const QPoint pos(6, minimapYToStdY(e->pos().y()));
0278         QMouseEvent eMod(QEvent::MouseButtonPress, pos, mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
0279         QScrollBar::mousePressEvent(&eMod);
0280     } else {
0281         QScrollBar::mousePressEvent(e);
0282     }
0283 
0284     m_toolTipPos = e->globalPosition().toPoint() - QPoint(e->pos().x(), 0);
0285     const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
0286     const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
0287     QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
0288 
0289     redrawMarks();
0290 }
0291 
0292 void KateScrollBar::mouseReleaseEvent(QMouseEvent *e)
0293 {
0294     if (e->button() == Qt::MiddleButton) {
0295         m_middleMouseDown = false;
0296     } else if (e->button() == Qt::LeftButton) {
0297         m_leftMouseDown = false;
0298     }
0299 
0300     redrawMarks();
0301 
0302     if (m_leftMouseDown || m_middleMouseDown) {
0303         QToolTip::hideText();
0304     }
0305 
0306     if (m_showMiniMap) {
0307         const QPoint pos(e->pos().x(), minimapYToStdY(e->pos().y()));
0308         QMouseEvent eMod(QEvent::MouseButtonRelease, pos, mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
0309         QScrollBar::mouseReleaseEvent(&eMod);
0310     } else {
0311         QScrollBar::mouseReleaseEvent(e);
0312     }
0313 }
0314 
0315 void KateScrollBar::mouseMoveEvent(QMouseEvent *e)
0316 {
0317     if (m_showMiniMap) {
0318         const QPoint pos(e->pos().x(), minimapYToStdY(e->pos().y()));
0319         QMouseEvent eMod(QEvent::MouseMove, pos, mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
0320         QScrollBar::mouseMoveEvent(&eMod);
0321     } else {
0322         QScrollBar::mouseMoveEvent(e);
0323     }
0324 
0325     if (e->buttons() & (Qt::LeftButton | Qt::MiddleButton)) {
0326         redrawMarks();
0327 
0328         // current line tool tip
0329         m_toolTipPos = e->globalPosition().toPoint() - QPoint(e->pos().x(), 0);
0330         const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
0331         const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
0332         QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
0333     }
0334 
0335     showTextPreviewDelayed();
0336 }
0337 
0338 void KateScrollBar::leaveEvent(QEvent *event)
0339 {
0340     hideTextPreview();
0341 
0342     QAbstractSlider::leaveEvent(event);
0343 }
0344 
0345 bool KateScrollBar::eventFilter(QObject *object, QEvent *event)
0346 {
0347     Q_UNUSED(object)
0348 
0349     if (m_textPreview && event->type() == QEvent::WindowDeactivate) {
0350         // We need hide the scrollbar TextPreview widget
0351         hideTextPreview();
0352     }
0353 
0354     return false;
0355 }
0356 
0357 void KateScrollBar::paintEvent(QPaintEvent *e)
0358 {
0359     if (m_doc->marks().size() != m_lines.size()) {
0360         recomputeMarksPositions();
0361     }
0362     if (m_showMiniMap) {
0363         miniMapPaintEvent(e);
0364     } else {
0365         normalPaintEvent(e);
0366     }
0367 }
0368 
0369 void KateScrollBar::showTextPreviewDelayed()
0370 {
0371     if (!m_textPreview) {
0372         if (!m_delayTextPreviewTimer.isActive()) {
0373             m_delayTextPreviewTimer.start();
0374         }
0375     } else {
0376         showTextPreview();
0377     }
0378 }
0379 
0380 void KateScrollBar::showTextPreview()
0381 {
0382     if (orientation() != Qt::Vertical || isSliderDown() || (minimum() == maximum()) || !m_view->config()->scrollBarPreview()) {
0383         return;
0384     }
0385 
0386     // only show when main window is active (#392396)
0387     if (window() && !window()->isActiveWindow()) {
0388         return;
0389     }
0390 
0391     QRect grooveRect;
0392     if (m_showMiniMap) {
0393         // If mini-map is shown, the height of the map might not be the whole height
0394         grooveRect = m_mapGroveRect;
0395     } else {
0396         QStyleOptionSlider opt;
0397         opt.initFrom(this);
0398         opt.subControls = QStyle::SC_None;
0399         opt.activeSubControls = QStyle::SC_None;
0400         opt.orientation = orientation();
0401         opt.minimum = minimum();
0402         opt.maximum = maximum();
0403         opt.sliderPosition = sliderPosition();
0404         opt.sliderValue = value();
0405         opt.singleStep = singleStep();
0406         opt.pageStep = pageStep();
0407 
0408         grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this);
0409     }
0410 
0411     if (m_view->config()->scrollPastEnd()) {
0412         // Adjust the grove size to accommodate the added pageStep at the bottom
0413         int adjust = pageStep() * grooveRect.height() / (maximum() + pageStep() - minimum());
0414         grooveRect.adjust(0, 0, 0, -adjust);
0415     }
0416 
0417     const QPoint cursorPos = mapFromGlobal(QCursor::pos());
0418     if (grooveRect.contains(cursorPos)) {
0419         if (!m_textPreview) {
0420             m_textPreview = new KateTextPreview(m_view, this);
0421             m_textPreview->setAttribute(Qt::WA_ShowWithoutActivating);
0422             m_textPreview->setFrameStyle(QFrame::StyledPanel);
0423 
0424             // event filter to catch application WindowDeactivate event, to hide the preview window
0425             qApp->installEventFilter(this);
0426         }
0427 
0428         const qreal posInPercent = static_cast<double>(cursorPos.y() - grooveRect.top()) / grooveRect.height();
0429         const qreal startLine = posInPercent * m_view->textFolding().visibleLines();
0430 
0431         m_textPreview->resize(m_view->width() / 2, m_view->height() / 5);
0432         const int xGlobal = mapToGlobal(QPoint(0, 0)).x();
0433         const int yGlobal = qMin(mapToGlobal(QPoint(0, height())).y() - m_textPreview->height(),
0434                                  qMax(mapToGlobal(QPoint(0, 0)).y(), mapToGlobal(cursorPos).y() - m_textPreview->height() / 2));
0435         m_textPreview->move(xGlobal - m_textPreview->width(), yGlobal);
0436         m_textPreview->setLine(startLine);
0437         m_textPreview->setCenterView(true);
0438         m_textPreview->setScaleFactor(0.75);
0439         m_textPreview->raise();
0440         m_textPreview->show();
0441     } else {
0442         hideTextPreview();
0443     }
0444 }
0445 
0446 void KateScrollBar::hideTextPreview()
0447 {
0448     if (m_delayTextPreviewTimer.isActive()) {
0449         m_delayTextPreviewTimer.stop();
0450     }
0451 
0452     qApp->removeEventFilter(this);
0453     delete m_textPreview;
0454 }
0455 
0456 // This function is optimized for bing called in sequence.
0457 void KateScrollBar::getCharColorRanges(const QList<Kate::TextLine::Attribute> &attributes,
0458                                        const QList<Kate::TextRange *> &decorations,
0459                                        const QString &text,
0460                                        QList<KateScrollBar::ColumnRangeWithColor> &ranges,
0461                                        QVarLengthArray<std::pair<QRgb, QPen>, 20> &penCache)
0462 {
0463     ranges.clear();
0464 
0465     auto getPen = [&](const QBrush &color) -> int {
0466         uint rgb = color.color().rgb();
0467         auto it = std::find_if(penCache.begin(), penCache.end(), [rgb](const std::pair<QRgb, QPen> &rgbToPen) {
0468             return rgb == rgbToPen.first;
0469         });
0470         if (it != penCache.end()) {
0471             return it - penCache.begin();
0472         }
0473         penCache.push_back({rgb, QPen(color, 1)});
0474         return (int)penCache.size() - 1;
0475     };
0476 
0477     constexpr QChar space = QLatin1Char(' ');
0478     constexpr QChar tab = QLatin1Char('\t');
0479 
0480     for (int i = 0; i < text.size() && i < s_lineWidth; ++i) {
0481         if (text[i] == space || text[i] == tab) {
0482             continue;
0483         }
0484 
0485         bool styleFound = false;
0486         for (auto range : decorations) {
0487             if (range->containsColumn(i)) {
0488                 QBrush color = range->attribute()->foreground();
0489                 styleFound = true;
0490                 int startCol = range->start().column();
0491                 int endCol = range->end().column();
0492                 ranges << ColumnRangeWithColor{getPen(color), startCol, endCol};
0493                 i = endCol;
0494                 break;
0495             }
0496         }
0497 
0498         if (styleFound) {
0499             continue;
0500         }
0501 
0502         // If there's no decoration set for the current character (this will mostly be the case for
0503         // plain Kate), query the styles, that is, the default kate syntax highlighting.
0504         // go to the block containing x
0505         qsizetype attributeIndex = 0;
0506         while ((attributeIndex < attributes.size()) && ((attributes[attributeIndex].offset + attributes[attributeIndex].length) < i)) {
0507             ++attributeIndex;
0508         }
0509         if (attributeIndex < attributes.size()) {
0510             const auto attr = attributes[attributeIndex];
0511             if ((i < attr.offset + attr.length)) {
0512                 QBrush color = m_view->renderer()->attribute(attr.attributeValue)->foreground();
0513                 int startCol = attr.offset;
0514                 int endCol = attr.offset + attr.length;
0515                 ranges << ColumnRangeWithColor{getPen(color), startCol, endCol};
0516                 i = endCol;
0517             }
0518         }
0519     }
0520 }
0521 
0522 void KateScrollBar::updatePixmap()
0523 {
0524     // QElapsedTimer time;
0525     // time.start();
0526 
0527     if (!m_showMiniMap) {
0528         // make sure no time is wasted if the option is disabled
0529         return;
0530     }
0531 
0532     if (!isVisible()) {
0533         // don't update now if the document is not visible; do it when
0534         // the document is shown again instead
0535         m_needsUpdateOnShow = true;
0536         return;
0537     }
0538 
0539     // For performance reason, only every n-th line will be drawn if the widget is
0540     // sufficiently small compared to the amount of lines in the document.
0541     int docLineCount = m_view->textFolding().visibleLines();
0542     int pixmapLineCount = docLineCount;
0543     if (m_view->config()->scrollPastEnd()) {
0544         pixmapLineCount += pageStep();
0545     }
0546     int pixmapLinesUnscaled = pixmapLineCount;
0547     if (m_grooveHeight < 5) {
0548         m_grooveHeight = 5;
0549     }
0550     int charIncrement = 1;
0551     int lineIncrement = 1;
0552     if ((m_grooveHeight > 10) && (pixmapLineCount >= m_grooveHeight * 2)) {
0553         charIncrement = pixmapLineCount / m_grooveHeight;
0554         while (charIncrement > s_linePixelIncLimit) {
0555             lineIncrement++;
0556             pixmapLineCount = pixmapLinesUnscaled / lineIncrement;
0557             charIncrement = pixmapLineCount / m_grooveHeight;
0558         }
0559         pixmapLineCount /= charIncrement;
0560     }
0561 
0562     int pixmapLineWidth = s_pixelMargin + s_lineWidth / charIncrement;
0563 
0564     // qCDebug(LOG_KTE) << "l" << lineIncrement << "c" << charIncrement << "d";
0565     // qCDebug(LOG_KTE) << "pixmap" << pixmapLineCount << pixmapLineWidth << "docLines" << m_view->textFolding().visibleLines() << "height" << m_grooveHeight;
0566 
0567     const QBrush backgroundColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->background();
0568     const QBrush defaultTextColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground();
0569     const QBrush selectionBgColor = m_view->rendererConfig()->selectionColor();
0570 
0571     QColor modifiedLineColor = m_view->rendererConfig()->modifiedLineColor();
0572     QColor savedLineColor = m_view->rendererConfig()->savedLineColor();
0573     // move the modified line color away from the background color
0574     modifiedLineColor.setHsv(modifiedLineColor.hue(), 255, 255 - backgroundColor.color().value() / 3);
0575     savedLineColor.setHsv(savedLineColor.hue(), 100, 255 - backgroundColor.color().value() / 3);
0576 
0577     const QBrush modifiedLineBrush = modifiedLineColor;
0578     const QBrush savedLineBrush = savedLineColor;
0579 
0580     // increase dimensions by ratio
0581     m_pixmap = QPixmap(pixmapLineWidth * m_view->devicePixelRatioF(), pixmapLineCount * m_view->devicePixelRatioF());
0582     m_pixmap.fill(QColor("transparent"));
0583 
0584     // The text currently selected in the document, to be drawn later.
0585     const KTextEditor::Range selection = m_view->selectionRange();
0586     const bool hasSelection = !selection.isEmpty();
0587     // reusable buffer for color->range
0588     QList<KateScrollBar::ColumnRangeWithColor> colorRangesForLine;
0589     const QPen defaultTextPen = QPen(defaultTextColor, 1);
0590     // resusable buffer for line ranges;
0591     QList<Kate::TextRange *> decorations;
0592 
0593     QPainter painter;
0594     if (painter.begin(&m_pixmap)) {
0595         // init pen once, afterwards, only change it if color changes to avoid a lot of allocation for setPen
0596         painter.setPen(QPen(selectionBgColor, 1));
0597 
0598         // Do not force updates of the highlighting if the document is very large
0599         const bool simpleMode = m_doc->lines() > 7500;
0600 
0601         int pixelY = 0;
0602         int drawnLines = 0;
0603 
0604         // pen cache to avoid a lot of allocations from pen creation
0605         QVarLengthArray<std::pair<QRgb, QPen>, 20> penCache;
0606 
0607         // Iterate over all visible lines, drawing them.
0608         for (int virtualLine = 0; virtualLine < docLineCount; virtualLine += lineIncrement) {
0609             int realLineNumber = m_view->textFolding().visibleLineToLine(virtualLine);
0610             const Kate::TextLine kateline = m_doc->plainKateTextLine(realLineNumber);
0611             const QString lineText = kateline.text();
0612 
0613             if (!simpleMode) {
0614                 m_doc->buffer().ensureHighlighted(realLineNumber);
0615             }
0616 
0617             // get normal highlighting stuff
0618             const auto &attributes = kateline.attributesList();
0619 
0620             // get moving ranges with attribs (semantic highlighting and co.)
0621             m_view->doc()->buffer().rangesForLine(realLineNumber, m_view, true, decorations);
0622 
0623             // Draw selection if it is on an empty line
0624 
0625             int pixelX = s_pixelMargin; // use this to control the offset of the text from the left
0626 
0627             if (hasSelection) {
0628                 if (selection.contains(KTextEditor::Cursor(realLineNumber, 0)) && lineText.size() == 0) {
0629                     if (selectionBgColor != painter.pen().brush()) {
0630                         painter.setPen(QPen(selectionBgColor, 1));
0631                     }
0632                     painter.drawLine(s_pixelMargin, pixelY, s_pixelMargin + s_lineWidth - 1, pixelY);
0633                 }
0634                 // Iterate over the line to draw the background
0635                 int selStartX = -1;
0636                 int selEndX = -1;
0637                 for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) {
0638                     if (pixelX >= s_lineWidth + s_pixelMargin) {
0639                         break;
0640                     }
0641                     // Query the selection and draw it behind the character
0642                     if (selection.contains(KTextEditor::Cursor(realLineNumber, x))) {
0643                         if (selStartX == -1) {
0644                             selStartX = pixelX;
0645                         }
0646                         selEndX = pixelX;
0647                         if (lineText.size() - 1 == x) {
0648                             selEndX = s_lineWidth + s_pixelMargin - 1;
0649                         }
0650                     }
0651 
0652                     if (lineText[x] == QLatin1Char('\t')) {
0653                         pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width...
0654                     } else {
0655                         pixelX++;
0656                     }
0657                 }
0658 
0659                 if (selStartX != -1) {
0660                     if (selectionBgColor != painter.pen().brush()) {
0661                         painter.setPen(QPen(selectionBgColor, 1));
0662                     }
0663                     painter.drawLine(selStartX, pixelY, selEndX, pixelY);
0664                 }
0665             }
0666 
0667             // Iterate over all the characters in the current line
0668             getCharColorRanges(attributes, decorations, lineText, colorRangesForLine, penCache);
0669             pixelX = s_pixelMargin;
0670             for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) {
0671                 if (pixelX >= s_lineWidth + s_pixelMargin) {
0672                     break;
0673                 }
0674 
0675                 // draw the pixels
0676                 if (lineText[x] == QLatin1Char(' ')) {
0677                     pixelX++;
0678                 } else if (lineText[x] == QLatin1Char('\t')) {
0679                     pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width...
0680                 } else {
0681                     const QPen *pen = nullptr;
0682                     int rangeEnd = x + 1;
0683                     for (const auto &cr : colorRangesForLine) {
0684                         if (cr.startColumn <= x && x <= cr.endColumn) {
0685                             rangeEnd = cr.endColumn;
0686                             if (cr.penIndex != -1) {
0687                                 pen = &penCache[cr.penIndex].second;
0688                             }
0689                         }
0690                     }
0691 
0692                     if (!pen) {
0693                         pen = &defaultTextPen;
0694                     }
0695                     // get the column range and color in which this 'x' lies
0696                     painter.setPen(*pen);
0697 
0698                     // Actually draw the pixels with the color queried from the renderer.
0699                     QVarLengthArray<QPoint, 100> points;
0700                     for (; x < rangeEnd; x += charIncrement) {
0701                         if (pixelX >= s_lineWidth + s_pixelMargin) {
0702                             break;
0703                         }
0704                         points.append({pixelX++, pixelY});
0705                     }
0706                     painter.drawPoints(points.data(), points.size());
0707                 }
0708             }
0709             drawnLines++;
0710             if (((drawnLines) % charIncrement) == 0) {
0711                 pixelY++;
0712             }
0713         }
0714         // qCDebug(LOG_KTE) << drawnLines;
0715         // Draw line modification marker map.
0716         // Disable this if the document is really huge,
0717         // since it requires querying every line.
0718         if (m_doc->lines() < 50000) {
0719             for (int lineno = 0; lineno < docLineCount; lineno++) {
0720                 int realLineNo = m_view->textFolding().visibleLineToLine(lineno);
0721                 const auto line = m_doc->plainKateTextLine(realLineNo);
0722                 const QBrush &col = line.markedAsModified() ? modifiedLineBrush : savedLineBrush;
0723                 if (line.markedAsModified() || line.markedAsSavedOnDisk()) {
0724                     int pos = (lineno * pixmapLineCount) / pixmapLinesUnscaled;
0725                     painter.fillRect(2, pos, 3, 1, col);
0726                 }
0727             }
0728         }
0729 
0730         // end painting
0731         painter.end();
0732     }
0733 
0734     // set right ratio
0735     m_pixmap.setDevicePixelRatio(m_view->devicePixelRatioF());
0736 
0737     // qCDebug(LOG_KTE) << time.elapsed();
0738     // Redraw the scrollbar widget with the updated pixmap.
0739     update();
0740 }
0741 
0742 void KateScrollBar::miniMapPaintEvent(QPaintEvent *e)
0743 {
0744     QScrollBar::paintEvent(e);
0745 
0746     QPainter painter(this);
0747 
0748     QStyleOptionSlider opt;
0749     opt.initFrom(this);
0750     opt.subControls = QStyle::SC_None;
0751     opt.activeSubControls = QStyle::SC_None;
0752     opt.orientation = orientation();
0753     opt.minimum = minimum();
0754     opt.maximum = maximum();
0755     opt.sliderPosition = sliderPosition();
0756     opt.sliderValue = value();
0757     opt.singleStep = singleStep();
0758     opt.pageStep = pageStep();
0759 
0760     QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this);
0761     m_stdGroveRect = grooveRect;
0762     if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this).height() == 0) {
0763         int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this);
0764         grooveRect.moveTop(alignMargin);
0765         grooveRect.setHeight(grooveRect.height() - alignMargin);
0766     }
0767     if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this).height() == 0) {
0768         int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this);
0769         grooveRect.setHeight(grooveRect.height() - alignMargin);
0770     }
0771     m_grooveHeight = grooveRect.height();
0772 
0773     const int docXMargin = 1;
0774     // style()->drawControl(QStyle::CE_ScrollBarAddLine, &opt, &painter, this);
0775     // style()->drawControl(QStyle::CE_ScrollBarSubLine, &opt, &painter, this);
0776 
0777     // calculate the document size and position
0778     const int docHeight = qMin(grooveRect.height(), int(m_pixmap.height() / m_pixmap.devicePixelRatio() * 2)) - 2 * docXMargin;
0779     const int yoffset = 1; // top-aligned in stead of center-aligned (grooveRect.height() - docHeight) / 2;
0780     const QRect docRect(QPoint(grooveRect.left() + docXMargin, yoffset + grooveRect.top()), QSize(grooveRect.width() - docXMargin, docHeight));
0781     m_mapGroveRect = docRect;
0782 
0783     // calculate the visible area
0784     int max = qMax(maximum() + 1, 1);
0785     int visibleStart = value() * docHeight / (max + pageStep()) + docRect.top() + 0.5;
0786     int visibleEnd = (value() + pageStep()) * docHeight / (max + pageStep()) + docRect.top();
0787     QRect visibleRect = docRect;
0788     visibleRect.moveTop(visibleStart);
0789     visibleRect.setHeight(visibleEnd - visibleStart);
0790 
0791     // calculate colors
0792     const QColor backgroundColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->background().color();
0793     const QColor foregroundColor = m_view->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground().color();
0794     const QColor highlightColor = palette().highlight().color();
0795 
0796     const int backgroundLightness = backgroundColor.lightness();
0797     const int foregroundLightness = foregroundColor.lightness();
0798     const int lighnessDiff = (foregroundLightness - backgroundLightness);
0799 
0800     // get a color suited for the color theme
0801     QColor darkShieldColor = palette().color(QPalette::Mid);
0802     int hue;
0803     int sat;
0804     int light;
0805     darkShieldColor.getHsl(&hue, &sat, &light);
0806     // apply suitable lightness
0807     darkShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.35);
0808     // gradient for nicer results
0809     QLinearGradient gradient(0, 0, width(), 0);
0810     gradient.setColorAt(0, darkShieldColor);
0811     gradient.setColorAt(0.3, darkShieldColor.lighter(115));
0812     gradient.setColorAt(1, darkShieldColor);
0813 
0814     QColor lightShieldColor;
0815     lightShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.15);
0816 
0817     QColor outlineColor;
0818     outlineColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.5);
0819 
0820     // draw the grove background in case the document is small
0821     painter.setPen(Qt::NoPen);
0822     painter.setBrush(backgroundColor);
0823     painter.drawRect(grooveRect);
0824 
0825     // adjust the rectangles
0826     QRect sliderRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this);
0827     sliderRect.setX(docXMargin);
0828     sliderRect.setWidth(width() - docXMargin * 2);
0829 
0830     if ((docHeight + 2 * docXMargin >= grooveRect.height()) && (sliderRect.height() > visibleRect.height() + 2)) {
0831         visibleRect.adjust(2, 0, -3, 0);
0832     } else {
0833         visibleRect.adjust(1, 0, -1, 2);
0834         sliderRect.setTop(visibleRect.top() - 1);
0835         sliderRect.setBottom(visibleRect.bottom() + 1);
0836     }
0837 
0838     // Smooth transform only when squeezing
0839     if (grooveRect.height() < m_pixmap.height() / m_pixmap.devicePixelRatio()) {
0840         painter.setRenderHint(QPainter::SmoothPixmapTransform);
0841     }
0842 
0843     // draw the modified lines margin
0844     QRect pixmapMarginRect(QPoint(0, 0), QSize(s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio()));
0845     QRect docPixmapMarginRect(QPoint(0, docRect.top()), QSize(s_pixelMargin, docRect.height()));
0846     painter.drawPixmap(docPixmapMarginRect, m_pixmap, pixmapMarginRect);
0847 
0848     // calculate the stretch and draw the stretched lines (scrollbar marks)
0849     QRect pixmapRect(QPoint(s_pixelMargin, 0),
0850                      QSize(m_pixmap.width() / m_pixmap.devicePixelRatio() - s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio()));
0851     QRect docPixmapRect(QPoint(s_pixelMargin, docRect.top()), QSize(docRect.width() - s_pixelMargin, docRect.height()));
0852     painter.drawPixmap(docPixmapRect, m_pixmap, pixmapRect);
0853 
0854     // delimit the end of the document
0855     const int y = docPixmapRect.height() + grooveRect.y();
0856     if (y + 2 < grooveRect.y() + grooveRect.height()) {
0857         QColor fg(foregroundColor);
0858         fg.setAlpha(30);
0859         painter.setBrush(Qt::NoBrush);
0860         painter.setPen(QPen(fg, 1));
0861         painter.drawLine(grooveRect.x() + 1, y + 2, width() - 1, y + 2);
0862     }
0863 
0864     // fade the invisible sections
0865     const QRect top(grooveRect.x(),
0866                     grooveRect.y(),
0867                     grooveRect.width(),
0868                     visibleRect.y() - grooveRect.y() // Pen width
0869     );
0870     const QRect bottom(grooveRect.x(),
0871                        grooveRect.y() + visibleRect.y() + visibleRect.height() - grooveRect.y(), // Pen width
0872                        grooveRect.width(),
0873                        grooveRect.height() - (visibleRect.y() - grooveRect.y()) - visibleRect.height());
0874 
0875     QColor faded(backgroundColor);
0876     faded.setAlpha(110);
0877     painter.fillRect(top, faded);
0878     painter.fillRect(bottom, faded);
0879 
0880     // add a thin line to limit the scrollbar
0881     QColor c(foregroundColor);
0882     c.setAlpha(10);
0883     painter.setPen(QPen(c, 1));
0884     painter.drawLine(0, 0, 0, height());
0885 
0886     if (m_showMarks) {
0887         QHashIterator<int, QColor> it = m_lines;
0888         QPen penBg;
0889         penBg.setWidth(4);
0890         lightShieldColor.setAlpha(180);
0891         penBg.setColor(lightShieldColor);
0892         painter.setPen(penBg);
0893         while (it.hasNext()) {
0894             it.next();
0895             int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();
0896             ;
0897             painter.drawLine(6, y, width() - 6, y);
0898         }
0899 
0900         it = m_lines;
0901         QPen pen;
0902         pen.setWidth(2);
0903         while (it.hasNext()) {
0904             it.next();
0905             pen.setColor(it.value());
0906             painter.setPen(pen);
0907             int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();
0908             painter.drawLine(6, y, width() - 6, y);
0909         }
0910     }
0911 
0912     // slider outline
0913     QColor sliderColor(highlightColor);
0914     sliderColor.setAlpha(50);
0915     painter.fillRect(sliderRect, sliderColor);
0916     painter.setPen(QPen(highlightColor, 0));
0917     m_sliderRect = sliderRect;
0918     // rounded rect looks ugly for some reason, so we draw 4 lines.
0919     painter.drawLine(sliderRect.left(), sliderRect.top() + 1, sliderRect.left(), sliderRect.bottom() - 1);
0920     painter.drawLine(sliderRect.right(), sliderRect.top() + 1, sliderRect.right(), sliderRect.bottom() - 1);
0921     painter.drawLine(sliderRect.left() + 1, sliderRect.top(), sliderRect.right() - 1, sliderRect.top());
0922     painter.drawLine(sliderRect.left() + 1, sliderRect.bottom(), sliderRect.right() - 1, sliderRect.bottom());
0923 }
0924 
0925 void KateScrollBar::normalPaintEvent(QPaintEvent *e)
0926 {
0927     QScrollBar::paintEvent(e);
0928 
0929     if (!m_showMarks) {
0930         return;
0931     }
0932 
0933     QPainter painter(this);
0934 
0935     QStyleOptionSlider opt;
0936     opt.initFrom(this);
0937     opt.subControls = QStyle::SC_None;
0938     opt.activeSubControls = QStyle::SC_None;
0939     opt.orientation = orientation();
0940     opt.minimum = minimum();
0941     opt.maximum = maximum();
0942     opt.sliderPosition = sliderPosition();
0943     opt.sliderValue = value();
0944     opt.singleStep = singleStep();
0945     opt.pageStep = pageStep();
0946 
0947     QRect rect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this);
0948     int sideMargin = width() - rect.width();
0949     if (sideMargin < 4) {
0950         sideMargin = 4;
0951     }
0952     sideMargin /= 2;
0953 
0954     QHashIterator<int, QColor> it = m_lines;
0955     while (it.hasNext()) {
0956         it.next();
0957         painter.setPen(it.value());
0958         if (it.key() < rect.top() || it.key() > rect.bottom()) {
0959             painter.drawLine(0, it.key(), width(), it.key());
0960         } else {
0961             painter.drawLine(0, it.key(), sideMargin, it.key());
0962             painter.drawLine(width() - sideMargin, it.key(), width(), it.key());
0963         }
0964     }
0965 }
0966 
0967 void KateScrollBar::resizeEvent(QResizeEvent *e)
0968 {
0969     QScrollBar::resizeEvent(e);
0970     m_updateTimer.start();
0971     m_lines.clear();
0972     update();
0973 }
0974 
0975 void KateScrollBar::sliderChange(SliderChange change)
0976 {
0977     // call parents implementation
0978     QScrollBar::sliderChange(change);
0979 
0980     if (change == QAbstractSlider::SliderValueChange) {
0981         redrawMarks();
0982     } else if (change == QAbstractSlider::SliderRangeChange) {
0983         marksChanged();
0984     }
0985 
0986     if (m_leftMouseDown || m_middleMouseDown) {
0987         const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1;
0988         const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1;
0989         QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "<center>%1<br/>&#x2014;<br/>%2</center>", fromLine, lastLine), this);
0990     }
0991 }
0992 
0993 void KateScrollBar::marksChanged()
0994 {
0995     m_lines.clear();
0996     update();
0997 }
0998 
0999 void KateScrollBar::redrawMarks()
1000 {
1001     if (!m_showMarks) {
1002         return;
1003     }
1004     update();
1005 }
1006 
1007 void KateScrollBar::recomputeMarksPositions()
1008 {
1009     // get the style options to compute the scrollbar pixels
1010     QStyleOptionSlider opt;
1011     initStyleOption(&opt);
1012     QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this);
1013 
1014     // cache top margin and groove height
1015     const int top = grooveRect.top();
1016     const int h = grooveRect.height() - 1;
1017 
1018     // make sure we have a sane height
1019     if (h <= 0) {
1020         return;
1021     }
1022 
1023     // get total visible (=without folded) lines in the document
1024     int visibleLines = m_view->textFolding().visibleLines() - 1;
1025     if (m_view->config()->scrollPastEnd()) {
1026         visibleLines += m_viewInternal->linesDisplayed() - 1;
1027         visibleLines -= m_view->config()->autoCenterLines();
1028     }
1029 
1030     // now repopulate the scrollbar lines list
1031     m_lines.clear();
1032     const QHash<int, KTextEditor::Mark *> &marks = m_doc->marks();
1033     for (QHash<int, KTextEditor::Mark *>::const_iterator i = marks.constBegin(); i != marks.constEnd(); ++i) {
1034         KTextEditor::Mark *mark = i.value();
1035         const int line = m_view->textFolding().lineToVisibleLine(mark->line);
1036         const double ratio = static_cast<double>(line) / visibleLines;
1037         m_lines.insert(top + (int)(h * ratio), KateRendererConfig::global()->lineMarkerColor((KTextEditor::Document::MarkTypes)mark->type));
1038     }
1039 }
1040 
1041 void KateScrollBar::sliderMaybeMoved(int value)
1042 {
1043     if (m_middleMouseDown) {
1044         // we only need to emit this signal once, as for the following slider
1045         // movements the signal sliderMoved() is already emitted.
1046         // Thus, set m_middleMouseDown to false right away.
1047         m_middleMouseDown = false;
1048         Q_EMIT sliderMMBMoved(value);
1049     }
1050 }
1051 // END
1052 
1053 // BEGIN KateCmdLineEditFlagCompletion
1054 /**
1055  * This class provide completion of flags. It shows a short description of
1056  * each flag, and flags are appended.
1057  */
1058 class KateCmdLineEditFlagCompletion : public KCompletion
1059 {
1060 public:
1061     KateCmdLineEditFlagCompletion()
1062     {
1063         ;
1064     }
1065 
1066     QString makeCompletion(const QString & /*s*/) override
1067     {
1068         return QString();
1069     }
1070 };
1071 // END KateCmdLineEditFlagCompletion
1072 
1073 // BEGIN KateCmdLineEdit
1074 KateCommandLineBar::KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent)
1075     : KateViewBarWidget(true, parent)
1076 {
1077     QHBoxLayout *topLayout = new QHBoxLayout(centralWidget());
1078     topLayout->setContentsMargins(0, 0, 0, 0);
1079     m_lineEdit = new KateCmdLineEdit(this, view);
1080     connect(m_lineEdit, &KateCmdLineEdit::hideRequested, this, &KateCommandLineBar::hideMe);
1081     topLayout->addWidget(m_lineEdit);
1082 
1083     QToolButton *helpButton = new QToolButton(this);
1084     helpButton->setAutoRaise(true);
1085     helpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
1086     topLayout->addWidget(helpButton);
1087     connect(helpButton, &QToolButton::clicked, this, &KateCommandLineBar::showHelpPage);
1088 
1089     setFocusProxy(m_lineEdit);
1090 }
1091 
1092 void KateCommandLineBar::showHelpPage()
1093 {
1094     KHelpClient::invokeHelp(QStringLiteral("advanced-editing-tools-commandline"), QStringLiteral("katepart"));
1095 }
1096 
1097 KateCommandLineBar::~KateCommandLineBar() = default;
1098 
1099 // inserts the given string in the command line edit and (if selected = true) selects it so the user
1100 // can type over it if they want to
1101 void KateCommandLineBar::setText(const QString &text, bool selected)
1102 {
1103     m_lineEdit->setText(text);
1104     if (selected) {
1105         m_lineEdit->selectAll();
1106     }
1107 }
1108 
1109 void KateCommandLineBar::execute(const QString &text)
1110 {
1111     m_lineEdit->slotReturnPressed(text);
1112 }
1113 
1114 KateCmdLineEdit::KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view)
1115     : KLineEdit()
1116     , m_view(view)
1117     , m_bar(bar)
1118     , m_msgMode(false)
1119     , m_histpos(0)
1120     , m_cmdend(0)
1121     , m_command(nullptr)
1122 {
1123     connect(this, &KateCmdLineEdit::returnKeyPressed, this, &KateCmdLineEdit::slotReturnPressed);
1124 
1125     setCompletionObject(KateCmd::self()->commandCompletionObject());
1126     setAutoDeleteCompletionObject(false);
1127 
1128     m_hideTimer = new QTimer(this);
1129     m_hideTimer->setSingleShot(true);
1130     connect(m_hideTimer, &QTimer::timeout, this, &KateCmdLineEdit::hideLineEdit);
1131 
1132     // make sure the timer is stopped when the user switches views. if not, focus will be given to the
1133     // wrong view when KateViewBar::hideCurrentBarWidget() is called after 4 seconds. (the timer is
1134     // used for showing things like "Success" for four seconds after the user has used the kate
1135     // command line)
1136     connect(m_view, &KTextEditor::ViewPrivate::focusOut, m_hideTimer, &QTimer::stop);
1137 }
1138 
1139 void KateCmdLineEdit::hideEvent(QHideEvent *e)
1140 {
1141     Q_UNUSED(e);
1142 }
1143 
1144 QString KateCmdLineEdit::helptext(const QPoint &) const
1145 {
1146     const QString beg = QStringLiteral("<qt background=\"white\"><div><table width=\"100%\"><tr><td bgcolor=\"brown\"><font color=\"white\"><b>Help: <big>");
1147     const QString mid = QStringLiteral("</big></b></font></td></tr><tr><td>");
1148     const QString end = QStringLiteral("</td></tr></table></div><qt>");
1149 
1150     const QString t = text();
1151     static const QRegularExpression re(QStringLiteral("\\s*help\\s+(.*)"));
1152     auto match = re.match(t);
1153     if (match.hasMatch()) {
1154         QString s;
1155         // get help for command
1156         const QString name = match.captured(1);
1157         if (name == QLatin1String("list")) {
1158             return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(QLatin1Char(' '))
1159                 + i18n("<p>For help on individual commands, do <code>'help &lt;command&gt;'</code></p>") + end;
1160         } else if (!name.isEmpty()) {
1161             KTextEditor::Command *cmd = KateCmd::self()->queryCommand(name);
1162             if (cmd) {
1163                 if (cmd->help(m_view, name, s)) {
1164                     return beg + name + mid + s + end;
1165                 } else {
1166                     return beg + name + mid + i18n("No help for '%1'", name) + end;
1167                 }
1168             } else {
1169                 return beg + mid + i18n("No such command <b>%1</b>", name) + end;
1170             }
1171         }
1172     }
1173 
1174     return beg + mid
1175         + i18n("<p>This is the Katepart <b>command line</b>.<br />"
1176                "Syntax: <code><b>command [ arguments ]</b></code><br />"
1177                "For a list of available commands, enter <code><b>help list</b></code><br />"
1178                "For help for individual commands, enter <code><b>help &lt;command&gt;</b></code></p>")
1179         + end;
1180 }
1181 
1182 bool KateCmdLineEdit::event(QEvent *e)
1183 {
1184     if (e->type() == QEvent::QueryWhatsThis) {
1185         setWhatsThis(helptext(QPoint()));
1186         e->accept();
1187         return true;
1188     }
1189     return KLineEdit::event(e);
1190 }
1191 
1192 /**
1193  * Parse the text as a command.
1194  *
1195  * The following is a simple PEG grammar for the syntax of the command.
1196  * (A PEG grammar is like a BNF grammar, except that "/" stands for
1197  * ordered choice: only the first matching rule is used. In other words,
1198  * the parsing is short-circuited in the manner of the "or" operator in
1199  * programming languages, and so the grammar is unambiguous.)
1200  *
1201  * Text <- Range? Command
1202  *       / Position
1203  * Range <- Position ("," Position)?
1204  *        / "%"
1205  * Position <- Base Offset?
1206  * Base <- Line
1207  *       / LastLine
1208  *       / ThisLine
1209  *       / Mark
1210  * Offset <- [+-] Base
1211  * Line <- [0-9]+
1212  * LastLine <- "$"
1213  * ThisLine <- "."
1214  * Mark <- "'" [a-z]
1215  */
1216 
1217 void KateCmdLineEdit::slotReturnPressed(const QString &text)
1218 {
1219     static const QRegularExpression focusChangingCommands(QStringLiteral("^(?:buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e)$"));
1220 
1221     if (text.isEmpty()) {
1222         return;
1223     }
1224     // silently ignore leading space characters
1225     uint n = 0;
1226     const uint textlen = text.length();
1227     while ((n < textlen) && (text[n].isSpace())) {
1228         n++;
1229     }
1230 
1231     if (n >= textlen) {
1232         return;
1233     }
1234 
1235     QString cmd = text.mid(n);
1236 
1237     // Parse any leading range expression, and strip it (and maybe do some other transforms on the command).
1238     QString leadingRangeExpression;
1239     KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd);
1240 
1241     // Built in help: if the command starts with "help", [try to] show some help
1242     if (cmd.startsWith(QLatin1String("help"))) {
1243         QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint()));
1244         clear();
1245         KateCmd::self()->appendHistory(cmd);
1246         m_histpos = KateCmd::self()->historyLength();
1247         m_oldText.clear();
1248         return;
1249     }
1250 
1251     if (cmd.length() > 0) {
1252         KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd);
1253 
1254         m_oldText = leadingRangeExpression + cmd;
1255         m_msgMode = true;
1256 
1257         // if the command changes the focus itself, the bar should be hidden before execution.
1258         if (focusChangingCommands.matchView(QStringView(cmd).left(cmd.indexOf(QLatin1Char(' ')))).hasMatch()) {
1259             Q_EMIT hideRequested();
1260         }
1261 
1262         if (!p) {
1263             setText(i18n("No such command: \"%1\"", cmd));
1264         } else if (range.isValid() && !p->supportsRange(cmd)) {
1265             // Raise message, when the command does not support ranges.
1266             setText(i18n("Error: No range allowed for command \"%1\".", cmd));
1267         } else {
1268             QString msg;
1269             if (p->exec(m_view, cmd, msg, range)) {
1270                 // append command along with range (will be empty if none given) to history
1271                 KateCmd::self()->appendHistory(leadingRangeExpression + cmd);
1272                 m_histpos = KateCmd::self()->historyLength();
1273                 m_oldText.clear();
1274 
1275                 if (msg.length() > 0) {
1276                     setText(i18n("Success: ") + msg);
1277                 } else if (isVisible()) {
1278                     // always hide on success without message
1279                     Q_EMIT hideRequested();
1280                 }
1281             } else {
1282                 if (msg.length() > 0) {
1283                     if (msg.contains(QLatin1Char('\n'))) {
1284                         // multiline error, use widget with more space
1285                         QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), msg);
1286                     } else {
1287                         setText(msg);
1288                     }
1289                 } else {
1290                     setText(i18n("Command \"%1\" failed.", cmd));
1291                 }
1292             }
1293         }
1294     }
1295 
1296     // clean up
1297     if (completionObject() != KateCmd::self()->commandCompletionObject()) {
1298         KCompletion *c = completionObject();
1299         setCompletionObject(KateCmd::self()->commandCompletionObject());
1300         delete c;
1301     }
1302     m_command = nullptr;
1303     m_cmdend = 0;
1304 
1305     if (!focusChangingCommands.matchView(QStringView(cmd).left(cmd.indexOf(QLatin1Char(' ')))).hasMatch()) {
1306         m_view->setFocus();
1307     }
1308 
1309     if (isVisible()) {
1310         m_hideTimer->start(4000);
1311     }
1312 }
1313 
1314 void KateCmdLineEdit::hideLineEdit() // unless i have focus ;)
1315 {
1316     if (!hasFocus()) {
1317         Q_EMIT hideRequested();
1318     }
1319 }
1320 
1321 void KateCmdLineEdit::focusInEvent(QFocusEvent *ev)
1322 {
1323     if (m_msgMode) {
1324         m_msgMode = false;
1325         setText(m_oldText);
1326         selectAll();
1327     }
1328 
1329     KLineEdit::focusInEvent(ev);
1330 }
1331 
1332 void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev)
1333 {
1334     if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) {
1335         m_view->setFocus();
1336         hideLineEdit();
1337         clear();
1338     } else if (ev->key() == Qt::Key_Up) {
1339         fromHistory(true);
1340     } else if (ev->key() == Qt::Key_Down) {
1341         fromHistory(false);
1342     }
1343 
1344     uint cursorpos = cursorPosition();
1345     KLineEdit::keyPressEvent(ev);
1346 
1347     // during typing, let us see if we have a valid command
1348     if (!m_cmdend || cursorpos <= m_cmdend) {
1349         QChar c;
1350         if (!ev->text().isEmpty()) {
1351             c = ev->text().at(0);
1352         }
1353 
1354         if (!m_cmdend && !c.isNull()) { // we have no command, so lets see if we got one
1355             if (!c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) {
1356                 m_command = KateCmd::self()->queryCommand(text().trimmed());
1357                 if (m_command) {
1358                     // qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<<m_command<<". text is '"<<text()<<"'";
1359                     // if the typed character is ":",
1360                     // we try if the command has flag completions
1361                     m_cmdend = cursorpos;
1362                     // qCDebug(LOG_KTE)<<"keypress in commandline: Set m_cmdend to "<<m_cmdend;
1363                 } else {
1364                     m_cmdend = 0;
1365                 }
1366             }
1367         } else { // since cursor is inside the command name, we reconsider it
1368             // qCDebug(LOG_KTE) << "keypress in commandline: \\W -- text is " << text();
1369             m_command = KateCmd::self()->queryCommand(text().trimmed());
1370             if (m_command) {
1371                 // qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<<m_command;
1372                 QString t = text();
1373                 m_cmdend = 0;
1374                 bool b = false;
1375                 for (; (int)m_cmdend < t.length(); m_cmdend++) {
1376                     if (t[m_cmdend].isLetter()) {
1377                         b = true;
1378                     }
1379                     if (b && (!t[m_cmdend].isLetterOrNumber() && t[m_cmdend] != QLatin1Char('-') && t[m_cmdend] != QLatin1Char('_'))) {
1380                         break;
1381                     }
1382                 }
1383 
1384                 if (c == QLatin1Char(':') && cursorpos == m_cmdend) {
1385                     // check if this command wants to complete flags
1386                     // qCDebug(LOG_KTE)<<"keypress in commandline: Checking if flag completion is desired!";
1387                 }
1388             } else {
1389                 // clean up if needed
1390                 if (completionObject() != KateCmd::self()->commandCompletionObject()) {
1391                     KCompletion *c = completionObject();
1392                     setCompletionObject(KateCmd::self()->commandCompletionObject());
1393                     delete c;
1394                 }
1395 
1396                 m_cmdend = 0;
1397             }
1398         }
1399 
1400         // if we got a command, check if it wants to do something.
1401         if (m_command) {
1402             KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed());
1403             if (cmpl) {
1404                 // We need to prepend the current command name + flag string
1405                 // when completion is done
1406                 // qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!";
1407 
1408                 setCompletionObject(cmpl);
1409             }
1410         }
1411     } else if (m_command && !ev->text().isEmpty()) {
1412         // check if we should call the commands processText()
1413         if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) {
1414             m_command->processText(m_view, text());
1415         }
1416     }
1417 }
1418 
1419 void KateCmdLineEdit::fromHistory(bool up)
1420 {
1421     if (!KateCmd::self()->historyLength()) {
1422         return;
1423     }
1424 
1425     QString s;
1426 
1427     if (up) {
1428         if (m_histpos > 0) {
1429             m_histpos--;
1430             s = KateCmd::self()->fromHistory(m_histpos);
1431         }
1432     } else {
1433         if (m_histpos < (KateCmd::self()->historyLength() - 1)) {
1434             m_histpos++;
1435             s = KateCmd::self()->fromHistory(m_histpos);
1436         } else {
1437             m_histpos = KateCmd::self()->historyLength();
1438             setText(m_oldText);
1439         }
1440     }
1441     if (!s.isEmpty()) {
1442         // Select the argument part of the command, so that it is easy to overwrite
1443         setText(s);
1444         static const QRegularExpression reCmd(QStringLiteral("^[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)"), QRegularExpression::UseUnicodePropertiesOption);
1445         const auto match = reCmd.match(text());
1446         if (match.hasMatch()) {
1447             setSelection(text().length() - match.capturedLength(1), match.capturedLength(1));
1448         }
1449     }
1450 }
1451 // END KateCmdLineEdit
1452 
1453 // BEGIN KateIconBorder
1454 using namespace KTextEditor;
1455 
1456 KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent)
1457     : QWidget(parent)
1458     , m_view(internalView->m_view)
1459     , m_doc(internalView->doc())
1460     , m_viewInternal(internalView)
1461     , m_iconBorderOn(false)
1462     , m_lineNumbersOn(false)
1463     , m_relLineNumbersOn(false)
1464     , m_updateRelLineNumbers(false)
1465     , m_foldingMarkersOn(false)
1466     , m_dynWrapIndicatorsOn(false)
1467     , m_annotationBorderOn(false)
1468     , m_updatePositionToArea(true)
1469     , m_annotationItemDelegate(new KateAnnotationItemDelegate(this))
1470 {
1471     setAcceptDrops(true);
1472     setAttribute(Qt::WA_StaticContents);
1473 
1474     // See: https://doc.qt.io/qt-5/qwidget.html#update. As this widget does not
1475     // have a background, there's no need for Qt to erase the widget's area
1476     // before repainting. Enabling this prevents flickering when the widget is
1477     // repainted.
1478     setAttribute(Qt::WA_OpaquePaintEvent);
1479 
1480     setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
1481     setMouseTracking(true);
1482     m_doc->setMarkDescription(Document::markType01, i18n("Bookmark"));
1483     m_doc->setMarkIcon(Document::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")));
1484 
1485     connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
1486 
1487     updateFont();
1488 
1489     m_antiFlickerTimer.setSingleShot(true);
1490     m_antiFlickerTimer.setInterval(300);
1491     connect(&m_antiFlickerTimer, &QTimer::timeout, this, &KateIconBorder::highlightFolding);
1492 
1493     // user interaction (scrolling) hides e.g. preview
1494     connect(m_view, &KTextEditor::ViewPrivate::displayRangeChanged, this, &KateIconBorder::displayRangeChanged);
1495 }
1496 
1497 KateIconBorder::~KateIconBorder()
1498 {
1499     delete m_foldingPreview;
1500     delete m_foldingRange;
1501 }
1502 
1503 void KateIconBorder::setIconBorderOn(bool enable)
1504 {
1505     if (enable == m_iconBorderOn) {
1506         return;
1507     }
1508 
1509     m_iconBorderOn = enable;
1510 
1511     m_updatePositionToArea = true;
1512 
1513     QTimer::singleShot(0, this, SLOT(update()));
1514 }
1515 
1516 void KateIconBorder::setAnnotationBorderOn(bool enable)
1517 {
1518     if (enable == m_annotationBorderOn) {
1519         return;
1520     }
1521 
1522     m_annotationBorderOn = enable;
1523 
1524     // make sure the tooltip is hidden
1525     if (!m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
1526         m_hoveredAnnotationGroupIdentifier.clear();
1527         hideAnnotationTooltip();
1528     }
1529 
1530     Q_EMIT m_view->annotationBorderVisibilityChanged(m_view, enable);
1531 
1532     m_updatePositionToArea = true;
1533 
1534     QTimer::singleShot(0, this, SLOT(update()));
1535 }
1536 
1537 void KateIconBorder::removeAnnotationHovering()
1538 {
1539     // remove hovering if it's still there
1540     if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
1541         m_hoveredAnnotationGroupIdentifier.clear();
1542         QTimer::singleShot(0, this, SLOT(update()));
1543     }
1544 }
1545 
1546 void KateIconBorder::setLineNumbersOn(bool enable)
1547 {
1548     if (enable == m_lineNumbersOn) {
1549         return;
1550     }
1551 
1552     m_lineNumbersOn = enable;
1553     m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators;
1554 
1555     m_updatePositionToArea = true;
1556 
1557     QTimer::singleShot(0, this, SLOT(update()));
1558 }
1559 
1560 void KateIconBorder::setRelLineNumbersOn(bool enable)
1561 {
1562     if (enable == m_relLineNumbersOn) {
1563         return;
1564     }
1565 
1566     m_relLineNumbersOn = enable;
1567     /*
1568      * We don't have to touch the m_dynWrapIndicatorsOn because
1569      * we already got it right from the m_lineNumbersOn
1570      */
1571     m_updatePositionToArea = true;
1572 
1573     QTimer::singleShot(0, this, SLOT(update()));
1574 }
1575 
1576 void KateIconBorder::updateForCursorLineChange()
1577 {
1578     if (m_relLineNumbersOn) {
1579         m_updateRelLineNumbers = true;
1580     }
1581 
1582     // always do normal update, e.g. for different current line color!
1583     update();
1584 }
1585 
1586 void KateIconBorder::setDynWrapIndicators(int state)
1587 {
1588     if (state == m_dynWrapIndicators) {
1589         return;
1590     }
1591 
1592     m_dynWrapIndicators = state;
1593     m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state;
1594 
1595     m_updatePositionToArea = true;
1596 
1597     QTimer::singleShot(0, this, SLOT(update()));
1598 }
1599 
1600 void KateIconBorder::setFoldingMarkersOn(bool enable)
1601 {
1602     if (enable == m_foldingMarkersOn) {
1603         return;
1604     }
1605 
1606     m_foldingMarkersOn = enable;
1607 
1608     m_updatePositionToArea = true;
1609 
1610     QTimer::singleShot(0, this, SLOT(update()));
1611 }
1612 
1613 QSize KateIconBorder::sizeHint() const
1614 {
1615     int w = 1; // Must be any value != 0 or we will never painted!
1616 
1617     const int i = m_positionToArea.size();
1618     if (i > 0) {
1619         w = m_positionToArea.at(i - 1).first;
1620     }
1621 
1622     return QSize(w, 0);
1623 }
1624 
1625 // This function (re)calculates the maximum width of any of the digit characters (0 -> 9)
1626 // for graceful handling of variable-width fonts as the linenumber font.
1627 void KateIconBorder::updateFont()
1628 {
1629     // Loop to determine the widest numeric character in the current font.
1630     const QFontMetricsF &fm = m_view->renderer()->currentFontMetrics();
1631     m_maxCharWidth = 0.0;
1632     for (char c = '0'; c <= '9'; ++c) {
1633         const qreal charWidth = ceil(fm.horizontalAdvance(QLatin1Char(c)));
1634         m_maxCharWidth = qMax(m_maxCharWidth, charWidth);
1635     }
1636 
1637     // NOTE/TODO(or not) Take size of m_dynWrapIndicatorChar into account.
1638     // It's a multi-char and it's reported size is, even with a mono-space font,
1639     // bigger than each digit, e.g. 10 vs 12. Currently it seems to work even with
1640     // "Line Numbers Off" but all these width calculating looks slightly hacky
1641 
1642     // the icon pane scales with the font...
1643     m_iconAreaWidth = fm.height();
1644 
1645     // Only for now, later may that become an own value
1646     m_foldingAreaWidth = m_iconAreaWidth;
1647 
1648     calcAnnotationBorderWidth();
1649 
1650     m_updatePositionToArea = true;
1651 
1652     QMetaObject::invokeMethod(
1653         this,
1654         [this] {
1655             update();
1656         },
1657         Qt::QueuedConnection);
1658 }
1659 
1660 int KateIconBorder::lineNumberWidth() const
1661 {
1662     int width = 0;
1663     // Avoid unneeded expensive calculations ;-)
1664     if (m_lineNumbersOn) {
1665         // width = (number of digits + 1) * char width
1666         const int digits = (int)ceil(log10((double)(m_view->doc()->lines() + 1)));
1667         width = (int)ceil((digits + 1) * m_maxCharWidth);
1668     }
1669 
1670     if ((width < 1) && m_dynWrapIndicatorsOn && m_view->config()->dynWordWrap()) {
1671         // FIXME Why 2x? because of above (number of digits + 1)
1672         // -> looks to me like a hint for bad calculation elsewhere
1673         width = 2 * m_maxCharWidth;
1674     }
1675 
1676     return width;
1677 }
1678 
1679 void KateIconBorder::dragEnterEvent(QDragEnterEvent *event)
1680 {
1681     m_view->m_viewInternal->dragEnterEvent(event);
1682 }
1683 
1684 void KateIconBorder::dragMoveEvent(QDragMoveEvent *event)
1685 {
1686     // FIXME Just calling m_view->m_viewInternal->dragMoveEvent(e) don't work
1687     // as intended, we need to set the cursor at column 1
1688     // Is there a way to change the pos of the event?
1689     QPoint pos(0, event->position().y());
1690     // Code copy of KateViewInternal::dragMoveEvent
1691     m_view->m_viewInternal->placeCursor(pos, true, false);
1692     m_view->m_viewInternal->fixDropEvent(event);
1693 }
1694 
1695 void KateIconBorder::dropEvent(QDropEvent *event)
1696 {
1697     m_view->m_viewInternal->dropEvent(event);
1698 }
1699 
1700 void KateIconBorder::paintEvent(QPaintEvent *e)
1701 {
1702     paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height());
1703 }
1704 
1705 static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open)
1706 {
1707     painter.setRenderHint(QPainter::Antialiasing);
1708 
1709     qreal size = qMin(width, height);
1710 
1711     if (open) {
1712         // Paint unfolded icon less pushy
1713         if (KColorUtils::luma(c) < 0.25) {
1714             c = KColorUtils::darken(c);
1715         } else {
1716             c = KColorUtils::shade(c, 0.1);
1717         }
1718 
1719     } else {
1720         // Paint folded icon in contrast to popup highlighting
1721         if (KColorUtils::luma(c) > 0.25) {
1722             c = KColorUtils::darken(c);
1723         } else {
1724             c = KColorUtils::shade(c, 0.1);
1725         }
1726     }
1727 
1728     QPen pen;
1729     pen.setJoinStyle(Qt::RoundJoin);
1730     pen.setColor(c);
1731     pen.setWidthF(1.5);
1732     painter.setPen(pen);
1733     painter.setBrush(c);
1734 
1735     // let some border, if possible
1736     size *= 0.6;
1737 
1738     qreal halfSize = size / 2;
1739     qreal halfSizeP = halfSize * 0.6;
1740     QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2);
1741 
1742     if (open) {
1743         QPointF points[3] = {middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP)};
1744         painter.drawConvexPolygon(points, 3);
1745     } else {
1746         QPointF points[3] = {middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0)};
1747         painter.drawConvexPolygon(points, 3);
1748     }
1749 
1750     painter.setRenderHint(QPainter::Antialiasing, false);
1751 }
1752 
1753 /**
1754  * Helper class for an identifier which can be an empty or non-empty string or invalid.
1755  * Avoids complicated explicit statements in code dealing with the identifier
1756  * received as QVariant from a model.
1757  */
1758 class KateAnnotationGroupIdentifier
1759 {
1760 public:
1761     KateAnnotationGroupIdentifier(const QVariant &identifier)
1762         : m_isValid(identifier.isValid() && identifier.canConvert<QString>())
1763         , m_id(m_isValid ? identifier.toString() : QString())
1764     {
1765     }
1766     KateAnnotationGroupIdentifier() = default;
1767     KateAnnotationGroupIdentifier(const KateAnnotationGroupIdentifier &rhs) = default;
1768 
1769     KateAnnotationGroupIdentifier &operator=(const KateAnnotationGroupIdentifier &rhs)
1770     {
1771         m_isValid = rhs.m_isValid;
1772         m_id = rhs.m_id;
1773         return *this;
1774     }
1775     KateAnnotationGroupIdentifier &operator=(const QVariant &identifier)
1776     {
1777         m_isValid = (identifier.isValid() && identifier.canConvert<QString>());
1778         if (m_isValid) {
1779             m_id = identifier.toString();
1780         } else {
1781             m_id.clear();
1782         }
1783         return *this;
1784     }
1785 
1786     bool operator==(const KateAnnotationGroupIdentifier &rhs) const
1787     {
1788         return (m_isValid == rhs.m_isValid) && (!m_isValid || (m_id == rhs.m_id));
1789     }
1790     bool operator!=(const KateAnnotationGroupIdentifier &rhs) const
1791     {
1792         return (m_isValid != rhs.m_isValid) || (m_isValid && (m_id != rhs.m_id));
1793     }
1794 
1795     void clear()
1796     {
1797         m_isValid = false;
1798         m_id.clear();
1799     }
1800     bool isValid() const
1801     {
1802         return m_isValid;
1803     }
1804     const QString &id() const
1805     {
1806         return m_id;
1807     }
1808 
1809 private:
1810     bool m_isValid = false;
1811     QString m_id;
1812 };
1813 
1814 /**
1815  * Helper class for iterative calculation of data regarding the position
1816  * of a line with regard to annotation item grouping.
1817  */
1818 class KateAnnotationGroupPositionState
1819 {
1820 public:
1821     /**
1822      * @param startz first rendered displayed line
1823      * @param isUsed flag whether the KateAnnotationGroupPositionState object will
1824      *               be used or is just created due to being on the stack
1825      */
1826     KateAnnotationGroupPositionState(KateViewInternal *viewInternal,
1827                                      const KTextEditor::AnnotationModel *model,
1828                                      const QString &hoveredAnnotationGroupIdentifier,
1829                                      uint startz,
1830                                      bool isUsed);
1831     /**
1832      * @param styleOption option to fill with data for the given line
1833      * @param z rendered displayed line
1834      * @param realLine real line which is rendered here (passed to avoid another look-up)
1835      */
1836     void nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine);
1837 
1838 private:
1839     KateViewInternal *m_viewInternal;
1840     const KTextEditor::AnnotationModel *const m_model;
1841     const QString m_hoveredAnnotationGroupIdentifier;
1842 
1843     int m_visibleWrappedLineInAnnotationGroup = -1;
1844     KateAnnotationGroupIdentifier m_lastAnnotationGroupIdentifier;
1845     KateAnnotationGroupIdentifier m_nextAnnotationGroupIdentifier;
1846     bool m_isSameAnnotationGroupsSinceLast = false;
1847 };
1848 
1849 KateAnnotationGroupPositionState::KateAnnotationGroupPositionState(KateViewInternal *viewInternal,
1850                                                                    const KTextEditor::AnnotationModel *model,
1851                                                                    const QString &hoveredAnnotationGroupIdentifier,
1852                                                                    uint startz,
1853                                                                    bool isUsed)
1854     : m_viewInternal(viewInternal)
1855     , m_model(model)
1856     , m_hoveredAnnotationGroupIdentifier(hoveredAnnotationGroupIdentifier)
1857 {
1858     if (!isUsed) {
1859         return;
1860     }
1861 
1862     if (!m_model || (static_cast<int>(startz) >= m_viewInternal->cache()->viewCacheLineCount())) {
1863         return;
1864     }
1865 
1866     const auto realLineAtStart = m_viewInternal->cache()->viewLine(startz).line();
1867     m_nextAnnotationGroupIdentifier = m_model->data(realLineAtStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1868     if (m_nextAnnotationGroupIdentifier.isValid()) {
1869         // estimate state of annotation group before first rendered line
1870         if (startz == 0) {
1871             if (realLineAtStart > 0) {
1872                 // TODO: here we would want to scan until the next line that would be displayed,
1873                 // to see if there are any group changes until then
1874                 // for now simply taking neighbour line into account, not a grave bug on the first displayed line
1875                 m_lastAnnotationGroupIdentifier = m_model->data(realLineAtStart - 1, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1876                 m_isSameAnnotationGroupsSinceLast = (m_lastAnnotationGroupIdentifier == m_nextAnnotationGroupIdentifier);
1877             }
1878         } else {
1879             const auto realLineBeforeStart = m_viewInternal->cache()->viewLine(startz - 1).line();
1880             m_lastAnnotationGroupIdentifier = m_model->data(realLineBeforeStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1881             if (m_lastAnnotationGroupIdentifier.isValid()) {
1882                 if (m_lastAnnotationGroupIdentifier.id() == m_nextAnnotationGroupIdentifier.id()) {
1883                     m_isSameAnnotationGroupsSinceLast = true;
1884                     // estimate m_visibleWrappedLineInAnnotationGroup from lines before startz
1885                     for (uint z = startz; z > 0; --z) {
1886                         const auto realLine = m_viewInternal->cache()->viewLine(z - 1).line();
1887                         const KateAnnotationGroupIdentifier identifier =
1888                             m_model->data(realLine, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1889                         if (identifier != m_lastAnnotationGroupIdentifier) {
1890                             break;
1891                         }
1892                         ++m_visibleWrappedLineInAnnotationGroup;
1893                     }
1894                 }
1895             }
1896         }
1897     }
1898 }
1899 
1900 void KateAnnotationGroupPositionState::nextLine(KTextEditor::StyleOptionAnnotationItem &styleOption, uint z, int realLine)
1901 {
1902     styleOption.wrappedLine = m_viewInternal->cache()->viewLine(z).viewLine();
1903     styleOption.wrappedLineCount = m_viewInternal->cache()->viewLineCount(realLine);
1904 
1905     // Estimate position in group
1906     const KateAnnotationGroupIdentifier annotationGroupIdentifier = m_nextAnnotationGroupIdentifier;
1907     bool isSameAnnotationGroupsSinceThis = false;
1908     // Calculate next line's group identifier
1909     // shortcut: assuming wrapped lines are always displayed together, test is simple
1910     if (styleOption.wrappedLine + 1 < styleOption.wrappedLineCount) {
1911         m_nextAnnotationGroupIdentifier = annotationGroupIdentifier;
1912         isSameAnnotationGroupsSinceThis = true;
1913     } else {
1914         if (static_cast<int>(z + 1) < m_viewInternal->cache()->viewCacheLineCount()) {
1915             const int realLineAfter = m_viewInternal->cache()->viewLine(z + 1).line();
1916             // search for any realLine with a different group id, also the non-displayed
1917             int rl = realLine + 1;
1918             for (; rl <= realLineAfter; ++rl) {
1919                 m_nextAnnotationGroupIdentifier = m_model->data(rl, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1920                 if (!m_nextAnnotationGroupIdentifier.isValid() || (m_nextAnnotationGroupIdentifier.id() != annotationGroupIdentifier.id())) {
1921                     break;
1922                 }
1923             }
1924             isSameAnnotationGroupsSinceThis = (rl > realLineAfter);
1925             if (rl < realLineAfter) {
1926                 m_nextAnnotationGroupIdentifier = m_model->data(realLineAfter, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole);
1927             }
1928         } else {
1929             // TODO: check next line after display end
1930             m_nextAnnotationGroupIdentifier.clear();
1931             isSameAnnotationGroupsSinceThis = false;
1932         }
1933     }
1934 
1935     if (annotationGroupIdentifier.isValid()) {
1936         if (m_hoveredAnnotationGroupIdentifier == annotationGroupIdentifier.id()) {
1937             styleOption.state |= QStyle::State_MouseOver;
1938         } else {
1939             styleOption.state &= ~QStyle::State_MouseOver;
1940         }
1941 
1942         if (m_isSameAnnotationGroupsSinceLast) {
1943             ++m_visibleWrappedLineInAnnotationGroup;
1944         } else {
1945             m_visibleWrappedLineInAnnotationGroup = 0;
1946         }
1947 
1948         styleOption.annotationItemGroupingPosition = StyleOptionAnnotationItem::InGroup;
1949         if (!m_isSameAnnotationGroupsSinceLast) {
1950             styleOption.annotationItemGroupingPosition |= StyleOptionAnnotationItem::GroupBegin;
1951         }
1952         if (!isSameAnnotationGroupsSinceThis) {
1953             styleOption.annotationItemGroupingPosition |= StyleOptionAnnotationItem::GroupEnd;
1954         }
1955     } else {
1956         m_visibleWrappedLineInAnnotationGroup = 0;
1957     }
1958     styleOption.visibleWrappedLineInGroup = m_visibleWrappedLineInAnnotationGroup;
1959 
1960     m_lastAnnotationGroupIdentifier = m_nextAnnotationGroupIdentifier;
1961     m_isSameAnnotationGroupsSinceLast = isSameAnnotationGroupsSinceThis;
1962 }
1963 
1964 void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height)
1965 {
1966     const uint h = m_view->renderer()->lineHeight();
1967     const uint startz = (y / h);
1968     const uint endz = qMin(startz + 1 + (height / h), static_cast<uint>(m_viewInternal->cache()->viewCacheLineCount()));
1969     const uint currentLine = m_view->cursorPosition().line();
1970 
1971     // center the folding boxes
1972     int m_px = (h - 11) / 2;
1973     if (m_px < 0) {
1974         m_px = 0;
1975     }
1976 
1977     // Ensure we miss no change of the count of line number digits
1978     const int newNeededWidth = lineNumberWidth();
1979 
1980     if (m_updatePositionToArea || (newNeededWidth != m_lineNumberAreaWidth)) {
1981         m_lineNumberAreaWidth = newNeededWidth;
1982         m_updatePositionToArea = true;
1983         m_positionToArea.clear();
1984     }
1985 
1986     QPainter p(this);
1987     p.setRenderHints(QPainter::TextAntialiasing);
1988     p.setFont(m_view->renderer()->currentFont()); // for line numbers
1989 
1990     KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
1991     KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, m_hoveredAnnotationGroupIdentifier, startz, m_annotationBorderOn);
1992 
1993     // Fetch often used data only once, improve readability
1994     const int w = width();
1995     const QColor iconBarColor = m_view->rendererConfig()->iconBarColor(); // Effective our background
1996     const QColor lineNumberColor = m_view->rendererConfig()->lineNumberColor();
1997     const QColor backgroundColor = m_view->rendererConfig()->backgroundColor(); // Of the edit area
1998     const QColor currentLineHighlight = m_view->rendererConfig()->highlightedLineColor(); // Of the edit area
1999 
2000     // Paint the border in chunks line by line
2001     for (uint z = startz; z < endz; z++) {
2002         // Painting coordinates, lineHeight * lineNumber
2003         const uint y = h * z;
2004 
2005         // Paint the border in chunks left->right, remember used width
2006         uint lnX = 0;
2007 
2008         // get line for this coordinates if possible
2009         const KateTextLayout lineLayout = m_viewInternal->cache()->viewLine(z);
2010         const int realLine = lineLayout.line();
2011 
2012         // Paint background over full width
2013         p.fillRect(lnX, y, w, h, iconBarColor);
2014 
2015         // overpaint with current line highlighting over full width
2016         const bool isCurrentLine = (realLine == static_cast<int>(currentLine)) && lineLayout.includesCursor(m_view->cursorPosition());
2017         if (isCurrentLine) {
2018             p.fillRect(lnX, y, w, h, currentLineHighlight);
2019         }
2020 
2021         // for real lines we need to do more stuff ;=)
2022         if (realLine >= 0) {
2023             // icon pane
2024             if (m_iconBorderOn) {
2025                 const uint mrk(m_doc->mark(realLine)); // call only once
2026                 if (mrk && lineLayout.startCol() == 0) {
2027                     for (uint bit = 0; bit < 32; bit++) {
2028                         Document::MarkTypes markType = (Document::MarkTypes)(1U << bit);
2029                         if (mrk & markType) {
2030                             const QIcon markIcon = m_doc->markIcon(markType);
2031 
2032                             if (!markIcon.isNull() && h > 0 && m_iconAreaWidth > 0) {
2033                                 const int s = qMin(m_iconAreaWidth, static_cast<int>(h)) - 2;
2034 
2035                                 // center the mark pixmap
2036                                 const int x_px = qMax(m_iconAreaWidth - s, 0) / 2;
2037                                 const int y_px = qMax(static_cast<int>(h) - s, 0) / 2;
2038 
2039                                 markIcon.paint(&p, lnX + x_px, y + y_px, s, s);
2040                             }
2041                         }
2042                     }
2043                 }
2044 
2045                 lnX += m_iconAreaWidth;
2046                 if (m_updatePositionToArea) {
2047                     m_positionToArea.push_back(AreaPosition(lnX, IconBorder));
2048                 }
2049             }
2050 
2051             // annotation information
2052             if (m_annotationBorderOn) {
2053                 // Draw a border line between annotations and the line numbers
2054                 p.setPen(lineNumberColor);
2055                 p.setBrush(lineNumberColor);
2056 
2057                 const qreal borderX = lnX + m_annotationAreaWidth + 0.5;
2058                 p.drawLine(QPointF(borderX, y + 0.5), QPointF(borderX, y + h - 0.5));
2059 
2060                 if (model) {
2061                     KTextEditor::StyleOptionAnnotationItem styleOption;
2062                     initStyleOption(&styleOption);
2063                     styleOption.rect.setRect(lnX, y, m_annotationAreaWidth, h);
2064                     annotationGroupPositionState.nextLine(styleOption, z, realLine);
2065 
2066                     m_annotationItemDelegate->paint(&p, styleOption, model, realLine);
2067                 }
2068 
2069                 lnX += m_annotationAreaWidth + m_separatorWidth;
2070                 if (m_updatePositionToArea) {
2071                     m_positionToArea.push_back(AreaPosition(lnX, AnnotationBorder));
2072                 }
2073             }
2074 
2075             // line number
2076             if (m_lineNumbersOn || m_dynWrapIndicatorsOn) {
2077                 QColor usedLineNumberColor;
2078                 const int distanceToCurrent = abs(realLine - static_cast<int>(currentLine));
2079                 if (distanceToCurrent == 0) {
2080                     usedLineNumberColor = m_view->rendererConfig()->currentLineNumberColor();
2081                 } else {
2082                     usedLineNumberColor = lineNumberColor;
2083                 }
2084                 p.setPen(usedLineNumberColor);
2085                 p.setBrush(usedLineNumberColor);
2086 
2087                 if (lineLayout.startCol() == 0) {
2088                     if (m_relLineNumbersOn) {
2089                         if (distanceToCurrent == 0) {
2090                             p.drawText(lnX + m_maxCharWidth / 2,
2091                                        y,
2092                                        m_lineNumberAreaWidth - m_maxCharWidth,
2093                                        h,
2094                                        Qt::TextDontClip | Qt::AlignLeft | Qt::AlignVCenter,
2095                                        QString::number(realLine + 1));
2096                         } else {
2097                             p.drawText(lnX + m_maxCharWidth / 2,
2098                                        y,
2099                                        m_lineNumberAreaWidth - m_maxCharWidth,
2100                                        h,
2101                                        Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter,
2102                                        QString::number(distanceToCurrent));
2103                         }
2104                         if (m_updateRelLineNumbers) {
2105                             m_updateRelLineNumbers = false;
2106                             update();
2107                         }
2108                     } else if (m_lineNumbersOn) {
2109                         p.drawText(lnX + m_maxCharWidth / 2,
2110                                    y,
2111                                    m_lineNumberAreaWidth - m_maxCharWidth,
2112                                    h,
2113                                    Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter,
2114                                    QString::number(realLine + 1));
2115                     }
2116                 } else if (m_dynWrapIndicatorsOn) {
2117                     p.drawText(lnX + m_maxCharWidth / 2,
2118                                y,
2119                                m_lineNumberAreaWidth - m_maxCharWidth,
2120                                h,
2121                                Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter,
2122                                m_dynWrapIndicatorChar);
2123                 }
2124 
2125                 lnX += m_lineNumberAreaWidth + m_separatorWidth;
2126                 if (m_updatePositionToArea) {
2127                     m_positionToArea.push_back(AreaPosition(lnX, LineNumbers));
2128                 }
2129             }
2130 
2131             // modified line system
2132             if (m_view->config()->lineModification() && !m_doc->url().isEmpty()) {
2133                 const auto tl = m_doc->plainKateTextLine(realLine);
2134                 if (tl.markedAsModified()) {
2135                     p.fillRect(lnX, y, m_modAreaWidth, h, m_view->rendererConfig()->modifiedLineColor());
2136                 } else if (tl.markedAsSavedOnDisk()) {
2137                     p.fillRect(lnX, y, m_modAreaWidth, h, m_view->rendererConfig()->savedLineColor());
2138                 }
2139 
2140                 lnX += m_modAreaWidth; // No m_separatorWidth
2141                 if (m_updatePositionToArea) {
2142                     m_positionToArea.push_back(AreaPosition(lnX, None));
2143                 }
2144             }
2145 
2146             // folding markers
2147             if (m_foldingMarkersOn) {
2148                 const QColor foldingColor(m_view->rendererConfig()->foldingColor());
2149                 // possible additional folding highlighting
2150                 if (m_foldingRange && m_foldingRange->overlapsLine(realLine)) {
2151                     p.fillRect(lnX, y, m_foldingAreaWidth, h, foldingColor);
2152                 }
2153 
2154                 if (lineLayout.startCol() == 0) {
2155                     QList<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>> startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine);
2156                     bool anyFolded = false;
2157                     for (int i = 0; i < startingRanges.size(); ++i) {
2158                         if (startingRanges[i].second & Kate::TextFolding::Folded) {
2159                             anyFolded = true;
2160                         }
2161                     }
2162                     if (!m_view->config()->showFoldingOnHoverOnly() || m_mouseOver) {
2163                         if (!startingRanges.isEmpty() || m_doc->buffer().isFoldingStartingOnLine(realLine).first) {
2164                             if (anyFolded) {
2165                                 paintTriangle(p, foldingColor, lnX, y, m_foldingAreaWidth, h, false);
2166                             } else {
2167                                 // Don't try to use currentLineNumberColor, the folded icon gets also not highligted
2168                                 paintTriangle(p, lineNumberColor, lnX, y, m_foldingAreaWidth, h, true);
2169                             }
2170                         }
2171                     }
2172                 }
2173 
2174                 lnX += m_foldingAreaWidth;
2175                 if (m_updatePositionToArea) {
2176                     m_positionToArea.push_back(AreaPosition(lnX, FoldingMarkers));
2177                 }
2178             }
2179         }
2180 
2181         // Overpaint again the end to simulate some margin to the edit area,
2182         // so that the text not looks like stuck to the border
2183         // we do this AFTER all other painting to ensure this leaves no artifacts
2184         // we kill 2 separator widths as we will below paint a line over this
2185         // otherwise this has some minimal overlap and looks ugly e.g. for scaled rendering
2186         p.fillRect(w - 2 * m_separatorWidth, y, w, h, backgroundColor);
2187 
2188         // overpaint again with selection or current line highlighting if necessary
2189         if (realLine >= 0 && m_view->selection() && !m_view->blockSelection() && m_view->selectionRange().start() < lineLayout.start()
2190             && m_view->selectionRange().end() >= lineLayout.start()) {
2191             // selection overpaint to signal the end of the previous line is included in the selection
2192             p.fillRect(w - 2 * m_separatorWidth, y, w, h, m_view->rendererConfig()->selectionColor());
2193         } else if (isCurrentLine) {
2194             // normal current line overpaint
2195             p.fillRect(w - 2 * m_separatorWidth, y, w, h, currentLineHighlight);
2196         }
2197 
2198         // add separator line if needed
2199         // we do this AFTER all other painting to ensure this leaves no artifacts
2200         p.setPen(m_view->rendererConfig()->separatorColor());
2201         p.setBrush(m_view->rendererConfig()->separatorColor());
2202         p.drawLine(w - 2 * m_separatorWidth, y, w - 2 * m_separatorWidth, y + h);
2203 
2204         // we might need to trigger geometry updates
2205         if ((realLine >= 0) && m_updatePositionToArea) {
2206             m_updatePositionToArea = false;
2207             // Don't forget our "text-stuck-to-border" protector + border line
2208             lnX += 2 * m_separatorWidth;
2209             m_positionToArea.push_back(AreaPosition(lnX, None));
2210 
2211             // Now that we know our needed space, ensure we are painted properly
2212             // we still keep painting to not have strange flicker
2213             QTimer::singleShot(0, this, &KateIconBorder::delayedUpdateOfSizeWithRepaint);
2214         }
2215     }
2216 }
2217 
2218 KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const
2219 {
2220     auto it = std::find_if(m_positionToArea.cbegin(), m_positionToArea.cend(), [p](const AreaPosition &ap) {
2221         return p.x() <= ap.first;
2222     });
2223     if (it != m_positionToArea.cend()) {
2224         return it->second;
2225     }
2226     return None;
2227 }
2228 
2229 void KateIconBorder::mousePressEvent(QMouseEvent *e)
2230 {
2231     const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->position().y());
2232     if (t.isValid()) {
2233         m_lastClickedLine = t.line();
2234         const auto area = positionToArea(e->pos());
2235         // IconBorder and AnnotationBorder have their own behavior; don't forward to view
2236         if (area != IconBorder && area != AnnotationBorder) {
2237             const auto pos = QPoint(0, e->position().y());
2238             if (area == LineNumbers && e->button() == Qt::LeftButton && !(e->modifiers() & Qt::ShiftModifier)) {
2239                 // setup view so the following mousePressEvent will select the line
2240                 m_viewInternal->beginSelectLine(pos);
2241             }
2242             QMouseEvent forward(QEvent::MouseButtonPress, pos, m_viewInternal->mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
2243             m_viewInternal->mousePressEvent(&forward);
2244         }
2245         return e->accept();
2246     }
2247 
2248     QWidget::mousePressEvent(e);
2249 }
2250 
2251 void KateIconBorder::highlightFoldingDelayed(int line)
2252 {
2253     if ((line == m_currentLine) || (line >= m_doc->buffer().lines())) {
2254         return;
2255     }
2256 
2257     m_currentLine = line;
2258 
2259     if (m_foldingRange) {
2260         // We are for a while in the folding area, no need for delay
2261         highlightFolding();
2262 
2263     } else if (!m_antiFlickerTimer.isActive()) {
2264         m_antiFlickerTimer.start();
2265     }
2266 }
2267 
2268 void KateIconBorder::highlightFolding()
2269 {
2270     // compute to which folding range we belong
2271     // FIXME: optimize + perhaps have some better threshold or use timers!
2272     KTextEditor::Range newRange = KTextEditor::Range::invalid();
2273     for (int line = m_currentLine; line >= qMax(0, m_currentLine - 1024); --line) {
2274         // try if we have folding range from that line, should be fast per call
2275         KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line);
2276         if (!foldingRange.isValid()) {
2277             continue;
2278         }
2279 
2280         // does the range reach us?
2281         if (foldingRange.overlapsLine(m_currentLine)) {
2282             newRange = foldingRange;
2283             break;
2284         }
2285     }
2286 
2287     if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) {
2288         // new range equals the old one, nothing to do.
2289         return;
2290     }
2291 
2292     // the ranges differ, delete the old, if it exists
2293     delete m_foldingRange;
2294     m_foldingRange = nullptr;
2295     // New range, new preview!
2296     delete m_foldingPreview;
2297 
2298     bool showPreview = false;
2299 
2300     if (newRange.isValid()) {
2301         // When next line is not visible we have a folded range, only then we want a preview!
2302         showPreview = !m_view->textFolding().isLineVisible(newRange.start().line() + 1);
2303 
2304         // qCDebug(LOG_KTE) << "new folding hl-range:" << newRange;
2305         m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight);
2306         KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute());
2307 
2308         // create highlighting color
2309         // we avoid alpha as overpainting leads to ugly lines (https://bugreports.qt.io/browse/QTBUG-66036)
2310         attr->setBackground(QBrush(m_view->rendererConfig()->foldingColor()));
2311 
2312         m_foldingRange->setView(m_view);
2313         // use z depth defined in moving ranges interface
2314         m_foldingRange->setZDepth(-100.0);
2315         m_foldingRange->setAttribute(attr);
2316     }
2317 
2318     // show text preview, if a folded region starts here...
2319     // ...but only when main window is active (#392396)
2320     const bool isWindowActive = !window() || window()->isActiveWindow();
2321     if (showPreview && m_view->config()->foldingPreview() && isWindowActive) {
2322         m_foldingPreview = new KateTextPreview(m_view, this);
2323         m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating);
2324         m_foldingPreview->setFrameStyle(QFrame::StyledPanel);
2325 
2326         // Calc how many lines can be displayed in the popup
2327         const int lineHeight = m_view->renderer()->lineHeight();
2328         const int foldingStartLine = m_foldingRange->start().line();
2329         // FIXME Is there really no easier way to find lineInDisplay?
2330         const QPoint pos = m_viewInternal->mapFrom(m_view, m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0)));
2331         const int lineInDisplay = pos.y() / lineHeight;
2332         // Allow slightly overpainting of the view bottom to proper cover all lines
2333         const int extra = (m_viewInternal->height() % lineHeight) > (lineHeight * 0.6) ? 1 : 0;
2334         const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, m_viewInternal->linesDisplayed() - lineInDisplay + extra);
2335 
2336         m_foldingPreview->resize(m_viewInternal->width(), lineCount * lineHeight + 2 * m_foldingPreview->frameWidth());
2337         const int xGlobal = mapToGlobal(QPoint(width(), 0)).x();
2338         const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(foldingStartLine, 0))).y();
2339         m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft());
2340         m_foldingPreview->setLine(foldingStartLine);
2341         m_foldingPreview->setCenterView(false);
2342         m_foldingPreview->setShowFoldedLines(true);
2343         m_foldingPreview->raise();
2344         m_foldingPreview->show();
2345     }
2346 }
2347 
2348 void KateIconBorder::hideFolding()
2349 {
2350     if (m_antiFlickerTimer.isActive()) {
2351         m_antiFlickerTimer.stop();
2352     }
2353 
2354     m_currentLine = -1;
2355     delete m_foldingRange;
2356     m_foldingRange = nullptr;
2357 
2358     delete m_foldingPreview;
2359 }
2360 
2361 void KateIconBorder::enterEvent(QEnterEvent *event)
2362 {
2363     m_mouseOver = true;
2364     if (m_view->config()->showFoldingOnHoverOnly())
2365         repaint();
2366     QWidget::enterEvent(event);
2367 }
2368 
2369 void KateIconBorder::leaveEvent(QEvent *event)
2370 {
2371     m_mouseOver = false;
2372     hideFolding();
2373     removeAnnotationHovering();
2374     if (m_view->config()->showFoldingOnHoverOnly())
2375         repaint();
2376 
2377     QWidget::leaveEvent(event);
2378 }
2379 
2380 void KateIconBorder::mouseMoveEvent(QMouseEvent *e)
2381 {
2382     const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->position().y());
2383     if (!t.isValid()) {
2384         // Cleanup everything which may be shown
2385         removeAnnotationHovering();
2386         hideFolding();
2387 
2388     } else {
2389         const BorderArea area = positionToArea(e->pos());
2390         if (area == FoldingMarkers) {
2391             highlightFoldingDelayed(t.line());
2392         } else {
2393             hideFolding();
2394         }
2395         if (area == AnnotationBorder) {
2396             KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2397             if (model) {
2398                 m_hoveredAnnotationGroupIdentifier = model->data(t.line(), (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole).toString();
2399                 const QPoint viewRelativePos = m_view->mapFromGlobal(e->globalPosition()).toPoint();
2400                 QHelpEvent helpEvent(QEvent::ToolTip, viewRelativePos, e->globalPosition().toPoint());
2401                 KTextEditor::StyleOptionAnnotationItem styleOption;
2402                 initStyleOption(&styleOption);
2403                 styleOption.rect = annotationLineRectInView(t.line());
2404                 setStyleOptionLineData(&styleOption, e->position().y(), t.line(), model, m_hoveredAnnotationGroupIdentifier);
2405                 m_annotationItemDelegate->helpEvent(&helpEvent, m_view, styleOption, model, t.line());
2406 
2407                 QTimer::singleShot(0, this, SLOT(update()));
2408             }
2409         } else {
2410             if (area == IconBorder) {
2411                 m_doc->requestMarkTooltip(t.line(), e->globalPosition().toPoint());
2412             }
2413 
2414             m_hoveredAnnotationGroupIdentifier.clear();
2415             QTimer::singleShot(0, this, SLOT(update()));
2416         }
2417         if (area != IconBorder) {
2418             QPoint p = m_viewInternal->mapFromGlobal(e->globalPosition().toPoint());
2419             QMouseEvent forward(QEvent::MouseMove, p, m_viewInternal->mapToGlobal(p), e->button(), e->buttons(), e->modifiers());
2420             m_viewInternal->mouseMoveEvent(&forward);
2421         }
2422     }
2423 
2424     QWidget::mouseMoveEvent(e);
2425 }
2426 
2427 void KateIconBorder::mouseReleaseEvent(QMouseEvent *e)
2428 {
2429     const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->position().y()).line();
2430     if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) {
2431         const BorderArea area = positionToArea(e->pos());
2432         if (area == IconBorder) {
2433             if (e->button() == Qt::LeftButton) {
2434                 if (!m_doc->handleMarkClick(cursorOnLine)) {
2435                     KateViewConfig *config = m_view->config();
2436                     const uint editBits = m_doc->editableMarks();
2437                     // is the default or the only editable mark
2438                     bool ctrlPressed = QGuiApplication::keyboardModifiers() == Qt::KeyboardModifier::ControlModifier;
2439                     if (qPopulationCount(editBits) == 1 || ctrlPressed) {
2440                         const uint singleMark = (qPopulationCount(editBits) > 1) ? (editBits & config->defaultMarkType()) : editBits;
2441                         if (m_doc->mark(cursorOnLine) & singleMark) {
2442                             m_doc->removeMark(cursorOnLine, singleMark);
2443                         } else {
2444                             m_doc->addMark(cursorOnLine, singleMark);
2445                         }
2446                     } else if (config->allowMarkMenu()) {
2447                         showMarkMenu(cursorOnLine, QCursor::pos());
2448                     }
2449                 }
2450             } else if (e->button() == Qt::RightButton) {
2451                 showMarkMenu(cursorOnLine, QCursor::pos());
2452             }
2453         }
2454 
2455         if (area == FoldingMarkers) {
2456             // Prefer the highlighted range over the exact clicked line
2457             const int lineToToggle = m_foldingRange ? m_foldingRange->toRange().start().line() : cursorOnLine;
2458             if (e->button() == Qt::LeftButton) {
2459                 m_view->toggleFoldingOfLine(lineToToggle);
2460             } else if (e->button() == Qt::RightButton) {
2461                 m_view->toggleFoldingsInRange(lineToToggle);
2462             }
2463 
2464             delete m_foldingPreview;
2465         }
2466 
2467         if (area == AnnotationBorder) {
2468             const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this);
2469             if (e->button() == Qt::LeftButton && singleClick) {
2470                 Q_EMIT m_view->annotationActivated(m_view, cursorOnLine);
2471             }
2472         }
2473     }
2474 
2475     const QPoint pos(0, e->position().y());
2476     QMouseEvent forward(QEvent::MouseButtonRelease, pos, m_viewInternal->mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
2477     m_viewInternal->mouseReleaseEvent(&forward);
2478 }
2479 
2480 void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e)
2481 {
2482     int cursorOnLine = m_viewInternal->yToKateTextLayout(e->position().y()).line();
2483 
2484     if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) {
2485         const BorderArea area = positionToArea(e->pos());
2486         const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this);
2487         if (area == AnnotationBorder && !singleClick) {
2488             Q_EMIT m_view->annotationActivated(m_view, cursorOnLine);
2489         }
2490     }
2491     const QPoint pos(0, e->position().y());
2492     QMouseEvent forward(QEvent::MouseButtonDblClick, pos, m_viewInternal->mapToGlobal(pos), e->button(), e->buttons(), e->modifiers());
2493     m_viewInternal->mouseDoubleClickEvent(&forward);
2494 }
2495 
2496 void KateIconBorder::contextMenuEvent(QContextMenuEvent *e)
2497 {
2498     const BorderArea area = positionToArea(e->pos());
2499     const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line();
2500 
2501     if (area == AnnotationBorder) {
2502         showAnnotationMenu(cursorOnLine, e->globalPos());
2503         return;
2504     }
2505 
2506     QMenu menu(this);
2507 
2508     KActionCollection *ac = m_view->actionCollection();
2509 
2510     // NOTE Assumes cursor position was updated before the menu opens
2511     if (QAction *bookmarkToggle = ac->action(QStringLiteral("bookmarks_toggle"))) {
2512         menu.addAction(bookmarkToggle);
2513     }
2514     if (QAction *bookmarkClear = ac->action(QStringLiteral("bookmarks_clear"))) {
2515         menu.addAction(bookmarkClear);
2516     }
2517 
2518     menu.addSeparator();
2519 
2520     if (QAction *toggleDynWrap = ac->action(QStringLiteral("view_dynamic_word_wrap"))) {
2521         menu.addAction(toggleDynWrap);
2522     }
2523     menu.addSeparator();
2524     if (QAction *toggleIconBar = ac->action(QStringLiteral("view_border"))) {
2525         menu.addAction(toggleIconBar);
2526     }
2527     if (QAction *toggleLineNumbers = ac->action(QStringLiteral("view_line_numbers"))) {
2528         menu.addAction(toggleLineNumbers);
2529     }
2530     if (QAction *toggleFoldingMarkers = ac->action(QStringLiteral("view_folding_markers"))) {
2531         menu.addAction(toggleFoldingMarkers);
2532     }
2533 
2534     menu.exec(e->globalPos());
2535 }
2536 
2537 void KateIconBorder::wheelEvent(QWheelEvent *e)
2538 {
2539     QCoreApplication::sendEvent(m_viewInternal, e);
2540 }
2541 
2542 void KateIconBorder::showMarkMenu(uint line, const QPoint &pos)
2543 {
2544     if (m_doc->handleMarkContextMenu(line, pos)) {
2545         return;
2546     }
2547 
2548     if (!m_view->config()->allowMarkMenu()) {
2549         return;
2550     }
2551 
2552     QMenu markMenu;
2553     QMenu selectDefaultMark;
2554     auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark);
2555 
2556     std::vector<int> vec(33);
2557     int i = 1;
2558 
2559     for (uint bit = 0; bit < 32; bit++) {
2560         KTextEditor::Document::MarkTypes markType = (KTextEditor::Document::MarkTypes)(1U << bit);
2561         if (!(m_doc->editableMarks() & markType)) {
2562             continue;
2563         }
2564 
2565         QAction *mA;
2566         QAction *dMA;
2567         const QIcon icon = m_doc->markIcon(markType);
2568         if (!m_doc->markDescription(markType).isEmpty()) {
2569             mA = markMenu.addAction(icon, m_doc->markDescription(markType));
2570             dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType));
2571         } else {
2572             mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1));
2573             dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1));
2574         }
2575         selectDefaultMarkActionGroup->addAction(dMA);
2576         mA->setData(i);
2577         mA->setCheckable(true);
2578         dMA->setData(i + 100);
2579         dMA->setCheckable(true);
2580         if (m_doc->mark(line) & markType) {
2581             mA->setChecked(true);
2582         }
2583 
2584         if (markType & KateViewConfig::global()->defaultMarkType()) {
2585             dMA->setChecked(true);
2586         }
2587 
2588         vec[i++] = markType;
2589     }
2590 
2591     if (markMenu.actions().count() == 0) {
2592         return;
2593     }
2594 
2595     if (markMenu.actions().count() > 1) {
2596         markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark);
2597     }
2598 
2599     QAction *rA = markMenu.exec(pos);
2600     if (!rA) {
2601         return;
2602     }
2603     int result = rA->data().toInt();
2604     if (result > 100) {
2605         KateViewConfig::global()->setValue(KateViewConfig::DefaultMarkType, vec[result - 100]);
2606     } else {
2607         KTextEditor::Document::MarkTypes markType = (KTextEditor::Document::MarkTypes)vec[result];
2608         if (m_doc->mark(line) & markType) {
2609             m_doc->removeMark(line, markType);
2610         } else {
2611             m_doc->addMark(line, markType);
2612         }
2613     }
2614 }
2615 
2616 KTextEditor::AbstractAnnotationItemDelegate *KateIconBorder::annotationItemDelegate() const
2617 {
2618     return m_annotationItemDelegate;
2619 }
2620 
2621 void KateIconBorder::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate)
2622 {
2623     if (delegate == m_annotationItemDelegate) {
2624         return;
2625     }
2626 
2627     // reset to default, but already on that?
2628     if (!delegate && m_isDefaultAnnotationItemDelegate) {
2629         // nothing to do
2630         return;
2631     }
2632 
2633     // make sure the tooltip is hidden
2634     if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) {
2635         m_hoveredAnnotationGroupIdentifier.clear();
2636         hideAnnotationTooltip();
2637     }
2638 
2639     disconnect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2640     if (!m_isDefaultAnnotationItemDelegate) {
2641         disconnect(m_annotationItemDelegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2642     }
2643 
2644     if (!delegate) {
2645         // reset to a default delegate
2646         m_annotationItemDelegate = new KateAnnotationItemDelegate(this);
2647         m_isDefaultAnnotationItemDelegate = true;
2648     } else {
2649         // drop any default delegate
2650         if (m_isDefaultAnnotationItemDelegate) {
2651             delete m_annotationItemDelegate;
2652             m_isDefaultAnnotationItemDelegate = false;
2653         }
2654 
2655         m_annotationItemDelegate = delegate;
2656         // catch delegate being destroyed
2657         connect(delegate, &QObject::destroyed, this, &KateIconBorder::handleDestroyedAnnotationItemDelegate);
2658     }
2659 
2660     connect(m_annotationItemDelegate, &AbstractAnnotationItemDelegate::sizeHintChanged, this, &KateIconBorder::updateAnnotationBorderWidth);
2661 
2662     if (m_annotationBorderOn) {
2663         QTimer::singleShot(0, this, &KateIconBorder::delayedUpdateOfSizeWithRepaint);
2664     }
2665 }
2666 
2667 void KateIconBorder::handleDestroyedAnnotationItemDelegate()
2668 {
2669     setAnnotationItemDelegate(nullptr);
2670 }
2671 
2672 void KateIconBorder::delayedUpdateOfSizeWithRepaint()
2673 {
2674     // ensure we update size + repaint at once to avoid flicker, see bug 435361
2675     setUpdatesEnabled(false);
2676     updateGeometry();
2677     repaint();
2678     setUpdatesEnabled(true);
2679 }
2680 
2681 void KateIconBorder::initStyleOption(KTextEditor::StyleOptionAnnotationItem *styleOption) const
2682 {
2683     styleOption->initFrom(this);
2684     styleOption->view = m_view;
2685     styleOption->decorationSize = QSize(m_iconAreaWidth, m_iconAreaWidth);
2686     styleOption->contentFontMetrics = m_view->renderer()->currentFontMetrics();
2687 }
2688 
2689 void KateIconBorder::setStyleOptionLineData(KTextEditor::StyleOptionAnnotationItem *styleOption,
2690                                             int y,
2691                                             int realLine,
2692                                             const KTextEditor::AnnotationModel *model,
2693                                             const QString &annotationGroupIdentifier) const
2694 {
2695     // calculate rendered displayed line
2696     const uint h = m_view->renderer()->lineHeight();
2697     const uint z = (y / h);
2698 
2699     KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, annotationGroupIdentifier, z, true);
2700     annotationGroupPositionState.nextLine(*styleOption, z, realLine);
2701 }
2702 
2703 QRect KateIconBorder::annotationLineRectInView(int line) const
2704 {
2705     int x = 0;
2706     if (m_iconBorderOn) {
2707         x += m_iconAreaWidth + 2;
2708     }
2709     const int y = m_view->m_viewInternal->lineToY(line);
2710 
2711     return QRect(x, y, m_annotationAreaWidth, m_view->renderer()->lineHeight());
2712 }
2713 
2714 void KateIconBorder::updateAnnotationLine(int line)
2715 {
2716     // TODO: why has the default value been 8, where is that magic number from?
2717     int width = 8;
2718     KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2719 
2720     if (model) {
2721         KTextEditor::StyleOptionAnnotationItem styleOption;
2722         initStyleOption(&styleOption);
2723         width = m_annotationItemDelegate->sizeHint(styleOption, model, line).width();
2724     }
2725 
2726     if (width > m_annotationAreaWidth) {
2727         m_annotationAreaWidth = width;
2728         m_updatePositionToArea = true;
2729 
2730         QTimer::singleShot(0, this, SLOT(update()));
2731     }
2732 }
2733 
2734 void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos)
2735 {
2736     QMenu menu;
2737     QAction a(i18n("Disable Annotation Bar"), &menu);
2738     a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2739     menu.addAction(&a);
2740     Q_EMIT m_view->annotationContextMenuAboutToShow(m_view, &menu, line);
2741     if (menu.exec(pos) == &a) {
2742         m_view->setAnnotationBorderVisible(false);
2743     }
2744 }
2745 
2746 void KateIconBorder::hideAnnotationTooltip()
2747 {
2748     m_annotationItemDelegate->hideTooltip(m_view);
2749 }
2750 
2751 void KateIconBorder::updateAnnotationBorderWidth()
2752 {
2753     calcAnnotationBorderWidth();
2754 
2755     m_updatePositionToArea = true;
2756 
2757     QTimer::singleShot(0, this, SLOT(update()));
2758 }
2759 
2760 void KateIconBorder::calcAnnotationBorderWidth()
2761 {
2762     // TODO: another magic number, not matching the one in updateAnnotationLine()
2763     m_annotationAreaWidth = 6;
2764     KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel();
2765 
2766     if (model) {
2767         KTextEditor::StyleOptionAnnotationItem styleOption;
2768         initStyleOption(&styleOption);
2769 
2770         const int lineCount = m_view->doc()->lines();
2771         if (lineCount > 0) {
2772             const int checkedLineCount = m_hasUniformAnnotationItemSizes ? 1 : lineCount;
2773             for (int i = 0; i < checkedLineCount; ++i) {
2774                 const int curwidth = m_annotationItemDelegate->sizeHint(styleOption, model, i).width();
2775                 if (curwidth > m_annotationAreaWidth) {
2776                     m_annotationAreaWidth = curwidth;
2777                 }
2778             }
2779         }
2780     }
2781 }
2782 
2783 void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel)
2784 {
2785     if (oldmodel) {
2786         oldmodel->disconnect(this);
2787     }
2788     if (newmodel) {
2789         connect(newmodel, &KTextEditor::AnnotationModel::reset, this, &KateIconBorder::updateAnnotationBorderWidth);
2790         connect(newmodel, &KTextEditor::AnnotationModel::lineChanged, this, &KateIconBorder::updateAnnotationLine);
2791     }
2792     updateAnnotationBorderWidth();
2793 }
2794 
2795 void KateIconBorder::displayRangeChanged()
2796 {
2797     hideFolding();
2798     removeAnnotationHovering();
2799 }
2800 
2801 // END KateIconBorder
2802 
2803 // BEGIN KateViewEncodingAction
2804 // According to https://www.iana.org/assignments/ianacharset-mib/ianacharset-mib
2805 // the default/unknown mib value is 2.
2806 #define MIB_DEFAULT 2
2807 
2808 bool lessThanAction(KSelectAction *a, KSelectAction *b)
2809 {
2810     return a->text() < b->text();
2811 }
2812 
2813 void KateViewEncodingAction::init()
2814 {
2815     QList<KSelectAction *> actions;
2816 
2817     setToolBarMode(MenuMode);
2818 
2819     int i;
2820     const auto encodingsByScript = KCharsets::charsets()->encodingsByScript();
2821     actions.reserve(encodingsByScript.size());
2822     for (const QStringList &encodingsForScript : encodingsByScript) {
2823         KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), this);
2824 
2825         for (i = 1; i < encodingsForScript.size(); ++i) {
2826             tmp->addAction(encodingsForScript.at(i));
2827         }
2828         connect(tmp, &KSelectAction::actionTriggered, this, [this](QAction *action) {
2829             subActionTriggered(action);
2830         });
2831         // tmp->setCheckable(true);
2832         actions << tmp;
2833     }
2834     std::sort(actions.begin(), actions.end(), lessThanAction);
2835     for (KSelectAction *action : std::as_const(actions)) {
2836         addAction(action);
2837     }
2838 }
2839 
2840 void KateViewEncodingAction::subActionTriggered(QAction *action)
2841 {
2842     if (currentSubAction == action) {
2843         return;
2844     }
2845     currentSubAction = action;
2846     Q_EMIT textTriggered(action->text());
2847 }
2848 
2849 KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc,
2850                                                KTextEditor::ViewPrivate *_view,
2851                                                const QString &text,
2852                                                QObject *parent,
2853                                                bool saveAsMode)
2854     : KSelectAction(text, parent)
2855     , doc(_doc)
2856     , view(_view)
2857     , m_saveAsMode(saveAsMode)
2858 {
2859     init();
2860 
2861     connect(menu(), &QMenu::aboutToShow, this, &KateViewEncodingAction::slotAboutToShow);
2862     connect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2863 }
2864 
2865 void KateViewEncodingAction::slotAboutToShow()
2866 {
2867     setCurrentCodec(doc->config()->encoding());
2868 }
2869 
2870 void KateViewEncodingAction::setEncoding(const QString &e)
2871 {
2872     // in save as mode => trigger saveAs
2873     if (m_saveAsMode) {
2874         doc->documentSaveAsWithEncoding(e);
2875         return;
2876     }
2877 
2878     // else switch encoding
2879     doc->userSetEncodingForNextReload();
2880     doc->setEncoding(e);
2881     view->reloadFile();
2882 }
2883 
2884 bool KateViewEncodingAction::setCurrentCodec(const QString &codec)
2885 {
2886     disconnect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2887 
2888     int i;
2889     int j;
2890     for (i = 0; i < actions().size(); ++i) {
2891         if (actions().at(i)->menu()) {
2892             for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) {
2893                 if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) {
2894                     continue;
2895                 }
2896                 if (actions().at(i)->menu()->actions().at(j)->isSeparator()) {
2897                     continue;
2898                 }
2899 
2900                 if (codec == actions().at(i)->menu()->actions().at(j)->text()) {
2901                     currentSubAction = actions().at(i)->menu()->actions().at(j);
2902                     currentSubAction->setChecked(true);
2903                 } else {
2904                     actions().at(i)->menu()->actions().at(j)->setChecked(false);
2905                 }
2906             }
2907         }
2908     }
2909 
2910     connect(this, &KSelectAction::textTriggered, this, &KateViewEncodingAction::setEncoding);
2911     return true;
2912 }
2913 
2914 // END KateViewEncodingAction
2915 
2916 // BEGIN KateViewBar related classes
2917 
2918 KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent)
2919     : QWidget(parent)
2920     , m_viewBar(nullptr)
2921 {
2922     QHBoxLayout *layout = new QHBoxLayout(this);
2923 
2924     // NOTE: Here be cosmetics.
2925     layout->setContentsMargins(0, 0, 0, 0);
2926 
2927     // widget to be used as parent for the real content
2928     m_centralWidget = new QWidget(this);
2929     layout->addWidget(m_centralWidget);
2930     setFocusProxy(m_centralWidget);
2931 
2932     // hide button
2933     if (addCloseButton) {
2934         m_closeButton = new QToolButton(this);
2935         m_closeButton->setAutoRaise(true);
2936         m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
2937         connect(m_closeButton, &QToolButton::clicked, this, &KateViewBarWidget::hideMe);
2938         layout->addWidget(m_closeButton);
2939         layout->setAlignment(m_closeButton, Qt::AlignCenter | Qt::AlignVCenter);
2940     }
2941 }
2942 
2943 KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view)
2944     : QWidget(parent)
2945     , m_external(external)
2946     , m_view(view)
2947     , m_permanentBarWidget(nullptr)
2948 
2949 {
2950     m_layout = new QVBoxLayout(this);
2951     m_stack = new QStackedWidget(this);
2952     m_layout->addWidget(m_stack);
2953     m_layout->setContentsMargins(0, 0, 0, 0);
2954 
2955     m_stack->hide();
2956     hide();
2957 }
2958 
2959 void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget)
2960 {
2961     // just ignore additional adds for already existing widgets
2962     if (hasBarWidget(newBarWidget)) {
2963         return;
2964     }
2965 
2966     // add new widget, invisible...
2967     newBarWidget->hide();
2968     m_stack->addWidget(newBarWidget);
2969     newBarWidget->setAssociatedViewBar(this);
2970     connect(newBarWidget, &KateViewBarWidget::hideMe, this, &KateViewBar::hideCurrentBarWidget);
2971 }
2972 
2973 void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget)
2974 {
2975     // remove only if there
2976     if (!hasBarWidget(barWidget)) {
2977         return;
2978     }
2979 
2980     m_stack->removeWidget(barWidget);
2981     barWidget->setAssociatedViewBar(nullptr);
2982     barWidget->hide();
2983     disconnect(barWidget, nullptr, this, nullptr);
2984 }
2985 
2986 void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget)
2987 {
2988     Q_ASSERT(barWidget);
2989     Q_ASSERT(!m_permanentBarWidget);
2990 
2991     m_layout->addWidget(barWidget);
2992     m_permanentBarWidget = barWidget;
2993     m_permanentBarWidget->show();
2994 
2995     setViewBarVisible(true);
2996 }
2997 
2998 void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget)
2999 {
3000     Q_ASSERT(m_permanentBarWidget == barWidget);
3001     Q_UNUSED(barWidget);
3002 
3003     m_permanentBarWidget->hide();
3004     m_layout->removeWidget(m_permanentBarWidget);
3005     m_permanentBarWidget = nullptr;
3006 
3007     if (!barWidgetVisible()) {
3008         setViewBarVisible(false);
3009     }
3010 }
3011 
3012 void KateViewBar::showBarWidget(KateViewBarWidget *barWidget)
3013 {
3014     Q_ASSERT(barWidget != nullptr);
3015 
3016     if (barWidget != qobject_cast<KateViewBarWidget *>(m_stack->currentWidget())) {
3017         hideCurrentBarWidget();
3018     }
3019 
3020     // raise correct widget
3021     m_stack->addWidget(barWidget);
3022     m_stack->setCurrentWidget(barWidget);
3023     barWidget->show();
3024     barWidget->setFocus(Qt::ShortcutFocusReason);
3025     m_stack->show();
3026     setViewBarVisible(true);
3027 }
3028 
3029 bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const
3030 {
3031     return m_stack->indexOf(barWidget) != -1;
3032 }
3033 
3034 void KateViewBar::hideCurrentBarWidget()
3035 {
3036     KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
3037     if (current) {
3038         m_stack->removeWidget(current);
3039         current->closed();
3040     }
3041 
3042     // hide the bar
3043     m_stack->hide();
3044     if (!m_permanentBarWidget) {
3045         setViewBarVisible(false);
3046     }
3047 
3048     m_view->setFocus();
3049 }
3050 
3051 void KateViewBar::setViewBarVisible(bool visible)
3052 {
3053     if (m_external) {
3054         if (visible) {
3055             m_view->mainWindow()->showViewBar(m_view);
3056         } else {
3057             m_view->mainWindow()->hideViewBar(m_view);
3058         }
3059     } else {
3060         setVisible(visible);
3061     }
3062 }
3063 
3064 bool KateViewBar::barWidgetVisible() const
3065 {
3066     KateViewBarWidget *current = qobject_cast<KateViewBarWidget *>(m_stack->currentWidget());
3067     return current && current->isVisible();
3068 }
3069 
3070 void KateViewBar::keyPressEvent(QKeyEvent *event)
3071 {
3072     if (event->key() == Qt::Key_Escape) {
3073         hideCurrentBarWidget();
3074         return;
3075     }
3076     QWidget::keyPressEvent(event);
3077 }
3078 
3079 void KateViewBar::hideEvent(QHideEvent *event)
3080 {
3081     Q_UNUSED(event);
3082     //   if (!event->spontaneous())
3083     //     m_view->setFocus();
3084 }
3085 
3086 // END KateViewBar related classes
3087 
3088 // BEGIN SCHEMA ACTION -- the 'View->Color theme' menu action
3089 void KateViewSchemaAction::init()
3090 {
3091     m_group = nullptr;
3092     m_view = nullptr;
3093     last = 0;
3094 
3095     connect(menu(), &QMenu::aboutToShow, this, &KateViewSchemaAction::slotAboutToShow);
3096 }
3097 
3098 void KateViewSchemaAction::updateMenu(KTextEditor::ViewPrivate *view)
3099 {
3100     m_view = view;
3101 }
3102 
3103 void KateViewSchemaAction::slotAboutToShow()
3104 {
3105     KTextEditor::ViewPrivate *view = m_view;
3106 
3107     const auto themes = KateHlManager::self()->sortedThemes();
3108 
3109     if (!m_group) {
3110         m_group = new QActionGroup(menu());
3111         m_group->setExclusive(true);
3112     }
3113 
3114     for (int z = 0; z < themes.count(); z++) {
3115         QString hlName = themes[z].translatedName();
3116 
3117         if (!names.contains(hlName)) {
3118             names << hlName;
3119             QAction *a = menu()->addAction(hlName, this, &KateViewSchemaAction::setSchema);
3120             a->setData(themes[z].name());
3121             a->setCheckable(true);
3122             a->setActionGroup(m_group);
3123         }
3124     }
3125 
3126     if (!view) {
3127         return;
3128     }
3129 
3130     QString id = view->rendererConfig()->schema();
3131     const auto menuActions = menu()->actions();
3132     for (QAction *a : menuActions) {
3133         a->setChecked(a->data().toString() == id);
3134     }
3135 }
3136 
3137 void KateViewSchemaAction::setSchema()
3138 {
3139     QAction *action = qobject_cast<QAction *>(sender());
3140 
3141     if (!action) {
3142         return;
3143     }
3144     QString mode = action->data().toString();
3145 
3146     KTextEditor::ViewPrivate *view = m_view;
3147 
3148     if (view) {
3149         view->rendererConfig()->setSchema(mode);
3150     }
3151 }
3152 // END SCHEMA ACTION
3153 
3154 #include "moc_kateviewhelpers.cpp"