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