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