Warning, file /frameworks/ktexteditor/src/view/kateviewhelpers.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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