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