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