KTextEditor

kateviewinternal.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
4 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
5 SPDX-FileCopyrightText: 2002, 2003 Christoph Cullmann <cullmann@kde.org>
6 SPDX-FileCopyrightText: 2002-2007 Hamish Rodda <rodda@kde.org>
7 SPDX-FileCopyrightText: 2003 Anakim Border <aborder@sources.sourceforge.net>
8 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
9 SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
10 SPDX-FileCopyrightText: 2008 Erlend Hamberg <ehamberg@gmail.com>
11
12 Based on KWriteView:
13 SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
14
15 SPDX-License-Identifier: LGPL-2.0-only
16*/
17#include "kateviewinternal.h"
18
19#include "kateabstractinputmode.h"
20#include "kateabstractinputmodefactory.h"
21#include "katebuffer.h"
22#include "katecompletionwidget.h"
23#include "kateconfig.h"
24#include "kateglobal.h"
25#include "katehighlight.h"
26#include "katelayoutcache.h"
27#include "katemessagewidget.h"
28#include "katepartdebug.h"
29#include "katerenderer.h"
30#include "katetextanimation.h"
31#include "katetextpreview.h"
32#include "kateview.h"
33#include "kateviewaccessible.h"
34#include "kateviewhelpers.h"
35#include "spellcheck/spellingmenu.h"
36
37#include <KCursor>
38#include <ktexteditor/documentcursor.h>
39#include <ktexteditor/inlinenoteprovider.h>
40#include <ktexteditor/movingrange.h>
41#include <ktexteditor/texthintinterface.h>
42
43#include <QAccessible>
44#include <QApplication>
45#include <QClipboard>
46#include <QKeyEvent>
47#include <QLayout>
48#include <QMenu>
49#include <QMimeData>
50#include <QPainter>
51#include <QPixmap>
52#include <QScopeGuard>
53#include <QScroller>
54#include <QStyle>
55#include <QToolTip>
56
57static const bool debugPainting = false;
58
59class ZoomEventFilter
60{
61public:
62 ZoomEventFilter() = default;
63
64 bool detectZoomingEvent(QWheelEvent *e, Qt::KeyboardModifiers modifier = Qt::ControlModifier)
65 {
66 Qt::KeyboardModifiers modState = e->modifiers();
67 if (modState == modifier) {
68 if (m_lastWheelEvent.isValid()) {
69 const qint64 deltaT = m_lastWheelEvent.elapsed();
70 // Pressing the specified modifier key within 200ms of the previous "unmodified"
71 // wheelevent is not allowed to toggle on text zooming
72 if (m_lastWheelEventUnmodified && deltaT < 200) {
73 m_ignoreZoom = true;
74 } else if (deltaT > 1000) {
75 // the protection is kept active for 1s after the last wheel event
76 // TODO: this value should be tuned, preferably by someone using
77 // Ctrl+Wheel zooming frequently.
78 m_ignoreZoom = false;
79 }
80 } else {
81 // we can't say anything and have to assume there's nothing
82 // accidental to the modifier being pressed.
83 m_ignoreZoom = false;
84 }
85 m_lastWheelEventUnmodified = false;
86 if (m_ignoreZoom) {
87 // unset the modifier so the view scrollbars can handle the scroll
88 // event and produce normal, not accelerated scrolling
89 modState &= ~modifier;
90 e->setModifiers(modState);
91 }
92 } else {
93 // state is reset after any wheel event without the zoom modifier
94 m_lastWheelEventUnmodified = true;
95 m_ignoreZoom = false;
96 }
97 m_lastWheelEvent.start();
98
99 // inform the caller whether this event is allowed to trigger text zooming.
100 return !m_ignoreZoom && modState == modifier;
101 }
102
103protected:
104 QElapsedTimer m_lastWheelEvent;
105 bool m_ignoreZoom = false;
106 bool m_lastWheelEventUnmodified = false;
107};
108
109KateViewInternal::KateViewInternal(KTextEditor::ViewPrivate *view)
110 : QWidget(view)
111 , editSessionNumber(0)
112 , editIsRunning(false)
113 , m_view(view)
114 , m_cursor(&doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::MoveOnInsert)
115 , m_mouse()
116 , m_possibleTripleClick(false)
117 , m_bm(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
118 , m_bmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
119 , m_bmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
120 , m_bmLastFlashPos(doc()->newMovingCursor(KTextEditor::Cursor::invalid()))
121
122 // folding marker
123 , m_fmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
124 , m_fmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand))
125
126 , m_dummy(nullptr)
127
128 // stay on cursor will avoid that the view scroll around on press return at beginning
129 , m_startPos(&doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::StayOnInsert)
130
131 , m_visibleLineCount(0)
132 , m_madeVisible(false)
133 , m_shiftKeyPressed(false)
134 , m_autoCenterLines(0)
135 , m_minLinesVisible(0)
136 , m_selChangedByUser(false)
137 , m_selectAnchor(-1, -1)
138 , m_selectionMode(Default)
139 , m_layoutCache(new KateLayoutCache(renderer(), this))
140 , m_preserveX(false)
141 , m_preservedX(0)
142 , m_cachedMaxStartPos(-1, -1)
143 , m_dragScrollTimer(this)
144 , m_scrollTimer(this)
145 , m_cursorTimer(this)
146 , m_textHintTimer(this)
147 , m_textHintDelay(500)
148 , m_textHintPos(-1, -1)
149 , m_imPreeditRange(nullptr)
150{
151 // setup input modes
152 Q_ASSERT(m_inputModes.size() == KTextEditor::EditorPrivate::self()->inputModeFactories().size());
153 m_inputModes[KTextEditor::View::NormalInputMode].reset(
154 KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::NormalInputMode]->createInputMode(this));
155 m_inputModes[KTextEditor::View::ViInputMode].reset(
156 KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::ViInputMode]->createInputMode(this));
157 m_currentInputMode = m_inputModes[KTextEditor::View::NormalInputMode].get();
158
159 setMinimumSize(0, 0);
160 setAttribute(Qt::WA_OpaquePaintEvent);
161 setAttribute(Qt::WA_InputMethodEnabled);
162
163 // invalidate m_selectionCached.start(), or keyb selection is screwed initially
164 m_selectionCached = KTextEditor::Range::invalid();
165
166 // bracket markers are only for this view and should not be printed
167 m_bm->setView(m_view);
168 m_bmStart->setView(m_view);
169 m_bmEnd->setView(m_view);
170 m_bm->setAttributeOnlyForViews(true);
171 m_bmStart->setAttributeOnlyForViews(true);
172 m_bmEnd->setAttributeOnlyForViews(true);
173
174 // use z depth defined in moving ranges interface
175 m_bm->setZDepth(-1000.0);
176 m_bmStart->setZDepth(-1000.0);
177 m_bmEnd->setZDepth(-1000.0);
178
179 // update mark attributes
180 updateBracketMarkAttributes();
181
182 //
183 // scrollbar for lines
184 //
185 m_lineScroll = new KateScrollBar(Qt::Vertical, this);
186 m_lineScroll->show();
187 m_lineScroll->setTracking(true);
188 m_lineScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
189
190 // Hijack the line scroller's controls, so we can scroll nicely for word-wrap
191 connect(m_lineScroll, &KateScrollBar::actionTriggered, this, &KateViewInternal::scrollAction);
192
193 auto viewScrollLinesSlot = qOverload<int>(&KateViewInternal::scrollLines);
194 connect(m_lineScroll, &KateScrollBar::sliderMoved, this, viewScrollLinesSlot);
195 connect(m_lineScroll, &KateScrollBar::sliderMMBMoved, this, viewScrollLinesSlot);
196 connect(m_lineScroll, &KateScrollBar::valueChanged, this, viewScrollLinesSlot);
197
198 //
199 // scrollbar for columns
200 //
201 m_columnScroll = new QScrollBar(Qt::Horizontal, m_view);
202 m_scroller = QScroller::scroller(this);
207 0.2); // Workaround for QTBUG-88249 (non-flick gestures recognized as accelerating flick)
211 m_scroller->setScrollerProperties(prop);
212#ifndef Q_OS_MACOS
213 // On macOS the trackpad also emits touch events which are sometimes picked up by the flick
214 // gesture recogniser registered by QScroller; this results in some odd scrolling behaviour
215 // as described in bug #442060. Therefore it's better to not let the QScroller register
216 // it on that platform.
217 m_scroller->grabGesture(this);
218#endif
219
220 if (m_view->dynWordWrap()) {
221 m_columnScroll->hide();
222 } else {
223 m_columnScroll->show();
224 }
225
226 m_columnScroll->setTracking(true);
227 m_startX = 0;
228
229 connect(m_columnScroll, &QScrollBar::valueChanged, this, &KateViewInternal::scrollColumns);
230
231 // bottom corner box
232 m_dummy = new QWidget(m_view);
233 m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height());
234 m_dummy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
235
236 if (m_view->dynWordWrap()) {
237 m_dummy->hide();
238 } else {
239 m_dummy->show();
240 }
241
242 cache()->setWrap(m_view->dynWordWrap());
243
244 //
245 // iconborder ;)
246 //
247 m_leftBorder = new KateIconBorder(this, m_view);
248 m_leftBorder->show();
249
250 // update view if folding ranges change
251 connect(&m_view->textFolding(), &Kate::TextFolding::foldingRangesChanged, this, &KateViewInternal::slotRegionVisibilityChanged);
252
253 m_displayCursor.setPosition(0, 0);
254
255 setAcceptDrops(true);
256
257 m_zoomEventFilter.reset(new ZoomEventFilter());
258 // event filter
259 installEventFilter(this);
260
261 // set initial cursor
262 m_mouseCursor = Qt::IBeamCursor;
263 setCursor(m_mouseCursor);
264
265 // call mouseMoveEvent also if no mouse button is pressed
266 setMouseTracking(true);
267
268 m_dragInfo.state = diNone;
269
270 // timers
271 connect(&m_dragScrollTimer, &QTimer::timeout, this, &KateViewInternal::doDragScroll);
272
273 connect(&m_scrollTimer, &QTimer::timeout, this, &KateViewInternal::scrollTimeout);
274
275 connect(&m_cursorTimer, &QTimer::timeout, this, &KateViewInternal::cursorTimeout);
276
277 connect(&m_textHintTimer, &QTimer::timeout, this, &KateViewInternal::textHintTimeout);
278
279 // selection changed to set anchor
280 connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged);
281
282#ifndef QT_NO_ACCESSIBILITY
283 QAccessible::installFactory(accessibleInterfaceFactory);
284#endif
285 connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &KateViewInternal::documentTextInserted);
286 connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateViewInternal::documentTextRemoved);
287
288 // update is called in KTextEditor::ViewPrivate, after construction and layout is over
289 // but before any other kateviewinternal call
290}
291
292KateViewInternal::~KateViewInternal()
293{
294#ifndef QT_NO_ACCESSIBILITY
295 QAccessible::removeFactory(accessibleInterfaceFactory);
296#endif
297
298 // delete text animation object here, otherwise it updates the view in its destructor
299 delete m_textAnimation;
300
301 // delete border here to not trigger crash later due to access to stuff in painting
302 delete m_leftBorder;
303 m_leftBorder = nullptr;
304}
305
306void KateViewInternal::dynWrapChanged()
307{
308 m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height());
309 if (view()->dynWordWrap()) {
310 m_columnScroll->hide();
311 m_dummy->hide();
312
313 } else {
314 // column scrollbar + bottom corner box
315 m_columnScroll->show();
316 m_dummy->show();
317 }
318
319 cache()->setWrap(view()->dynWordWrap());
320 updateView();
321
322 if (view()->dynWordWrap()) {
323 scrollColumns(0);
324 }
325
326 update();
327}
328
329KTextEditor::Cursor KateViewInternal::endPos() const
330{
331 // Hrm, no lines laid out at all??
332 if (!cache()->viewCacheLineCount()) {
333 return KTextEditor::Cursor();
334 }
335
336 for (int i = qMin(linesDisplayed() - 1, cache()->viewCacheLineCount() - 1); i >= 0; i--) {
337 const KateTextLayout &thisLine = cache()->viewLine(i);
338
339 if (thisLine.line() == -1) {
340 continue;
341 }
342
343 if (thisLine.virtualLine() >= view()->textFolding().visibleLines()) {
344 // Cache is too out of date
345 return KTextEditor::Cursor(view()->textFolding().visibleLines() - 1,
346 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
347 }
348
349 return KTextEditor::Cursor(thisLine.virtualLine(), thisLine.wrap() ? thisLine.endCol() - 1 : thisLine.endCol());
350 }
351
352 // can happen, if view is still invisible
353 return KTextEditor::Cursor();
354}
355
356int KateViewInternal::endLine() const
357{
358 return endPos().line();
359}
360
361KateTextLayout KateViewInternal::yToKateTextLayout(int y) const
362{
363 if (y < 0 || y > size().height()) {
364 return KateTextLayout::invalid();
365 }
366
367 int range = y / renderer()->lineHeight();
368
369 // lineRanges is always bigger than 0, after the initial updateView call
370 if (range >= 0 && range < cache()->viewCacheLineCount()) {
371 return cache()->viewLine(range);
372 }
373
374 return KateTextLayout::invalid();
375}
376
377int KateViewInternal::lineToY(int viewLine) const
378{
379 return (viewLine - startLine()) * renderer()->lineHeight();
380}
381
382void KateViewInternal::slotIncFontSizes(qreal step)
383{
384 renderer()->increaseFontSizes(step);
385}
386
387void KateViewInternal::slotDecFontSizes(qreal step)
388{
389 renderer()->decreaseFontSizes(step);
390}
391
392void KateViewInternal::slotResetFontSizes()
393{
394 renderer()->resetFontSizes();
395}
396
397/**
398 * Line is the real line number to scroll to.
399 */
400void KateViewInternal::scrollLines(int line)
401{
402 KTextEditor::Cursor newPos(line, 0);
403 scrollPos(newPos);
404}
405
406// This can scroll less than one true line
407void KateViewInternal::scrollViewLines(int offset)
408{
409 KTextEditor::Cursor c = viewLineOffset(startPos(), offset);
410 scrollPos(c);
411
412 bool blocked = m_lineScroll->blockSignals(true);
413 m_lineScroll->setValue(startLine());
414 m_lineScroll->blockSignals(blocked);
415}
416
417void KateViewInternal::scrollAction(int action)
418{
419 switch (action) {
421 scrollNextLine();
422 break;
423
425 scrollPrevLine();
426 break;
427
429 scrollNextPage();
430 break;
431
433 scrollPrevPage();
434 break;
435
437 top_home();
438 break;
439
441 bottom_end();
442 break;
443 }
444}
445
446void KateViewInternal::scrollNextPage()
447{
448 scrollViewLines(qMax(linesDisplayed() - 1, 0));
449}
450
451void KateViewInternal::scrollPrevPage()
452{
453 scrollViewLines(-qMax(linesDisplayed() - 1, 0));
454}
455
456void KateViewInternal::scrollPrevLine()
457{
458 scrollViewLines(-1);
459}
460
461void KateViewInternal::scrollNextLine()
462{
463 scrollViewLines(1);
464}
465
466KTextEditor::Cursor KateViewInternal::maxStartPos(bool changed)
467{
468 cache()->setAcceptDirtyLayouts(true);
469
470 if (m_cachedMaxStartPos.line() == -1 || changed) {
471 KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1,
472 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
473
474 if (view()->config()->scrollPastEnd()) {
475 m_cachedMaxStartPos = viewLineOffset(end, -m_minLinesVisible);
476 } else {
477 m_cachedMaxStartPos = viewLineOffset(end, -(linesDisplayed() - 1));
478 }
479 }
480
481 cache()->setAcceptDirtyLayouts(false);
482
483 return m_cachedMaxStartPos;
484}
485
486// c is a virtual cursor
487void KateViewInternal::scrollPos(KTextEditor::Cursor &c, bool force, bool calledExternally, bool emitSignals)
488{
489 if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) {
490 return;
491 }
492
493 if (c.line() < 0) {
494 c.setLine(0);
495 }
496
497 KTextEditor::Cursor limit = maxStartPos();
498 if (c > limit) {
499 c = limit;
500
501 // Re-check we're not just scrolling to the same place
502 if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) {
503 return;
504 }
505 }
506
507 int viewLinesScrolled = 0;
508
509 // only calculate if this is really used and useful, could be wrong here, please recheck
510 // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on
511 // try to get it really working ;)
512 bool viewLinesScrolledUsable = !force && (c.line() >= startLine() - linesDisplayed() - 1) && (c.line() <= endLine() + linesDisplayed() + 1);
513
514 if (viewLinesScrolledUsable) {
515 viewLinesScrolled = cache()->displayViewLine(c);
516 }
517
518 m_startPos.setPosition(c);
519
520 // set false here but reversed if we return to makeVisible
521 m_madeVisible = false;
522
523 if (viewLinesScrolledUsable) {
524 int lines = linesDisplayed();
525 if (view()->textFolding().visibleLines() < lines) {
526 KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1,
527 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
528 lines = qMin(linesDisplayed(), cache()->displayViewLine(end) + 1);
529 }
530
531 Q_ASSERT(lines >= 0);
532
533 if (!calledExternally && qAbs(viewLinesScrolled) < lines &&
534 // NOTE: on some machines we must update if the floating widget is visible
535 // otherwise strange painting bugs may occur during scrolling...
536 !((view()->m_messageWidgets[KTextEditor::Message::TopInView] && view()->m_messageWidgets[KTextEditor::Message::TopInView]->isVisible())
537 || (view()->m_messageWidgets[KTextEditor::Message::CenterInView] && view()->m_messageWidgets[KTextEditor::Message::CenterInView]->isVisible())
538 || (view()->m_messageWidgets[KTextEditor::Message::BottomInView] && view()->m_messageWidgets[KTextEditor::Message::BottomInView]->isVisible()))) {
539 updateView(false, viewLinesScrolled);
540
541 int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight());
542
543 // scroll excluding child widgets (floating notifications)
544 scroll(0, scrollHeight, rect());
545 m_leftBorder->scroll(0, scrollHeight);
546
547 if (emitSignals) {
548 Q_EMIT view()->verticalScrollPositionChanged(m_view, c);
549 Q_EMIT view()->displayRangeChanged(m_view);
550 }
551 return;
552 }
553 }
554
555 updateView();
556 update();
557 m_leftBorder->update();
558 if (emitSignals) {
559 Q_EMIT view()->verticalScrollPositionChanged(m_view, c);
560 Q_EMIT view()->displayRangeChanged(m_view);
561 }
562}
563
564void KateViewInternal::scrollColumns(int x)
565{
566 if (x < 0) {
567 x = 0;
568 }
569
570 if (x > m_columnScroll->maximum()) {
571 x = m_columnScroll->maximum();
572 }
573
574 if (x == startX()) {
575 return;
576 }
577
578 int dx = startX() - x;
579 m_startX = x;
580
581 if (qAbs(dx) < width()) {
582 // scroll excluding child widgets (floating notifications)
583 scroll(dx, 0, rect());
584 } else {
585 update();
586 }
587
589 Q_EMIT view()->displayRangeChanged(m_view);
590
591 bool blocked = m_columnScroll->blockSignals(true);
592 m_columnScroll->setValue(startX());
593 m_columnScroll->blockSignals(blocked);
594}
595
596// If changed is true, the lines that have been set dirty have been updated.
597void KateViewInternal::updateView(bool changed, int viewLinesScrolled)
598{
599 if (!isVisible() && !viewLinesScrolled && !changed) {
600 return; // When this view is not visible, don't do anything
601 }
602
603 view()->doc()->delayAutoReload(); // Don't reload while user scrolls around
604 bool blocked = m_lineScroll->blockSignals(true);
605
606 int wrapWidth = width();
607 if (view()->config()->dynWrapAtStaticMarker() && view()->config()->dynWordWrap()) {
608 // We need to transform char count to a pixel width, stolen from PrintPainter::updateCache()
609 QString s;
610 s.fill(QLatin1Char('5'), view()->doc()->config()->wordWrapAt());
611 wrapWidth = qMin(width(), static_cast<int>(renderer()->currentFontMetrics().boundingRect(s).width()));
612 }
613
614 if (wrapWidth != cache()->viewWidth()) {
615 cache()->setViewWidth(wrapWidth);
616 changed = true;
617 }
618
619 /* It was observed that height() could be negative here --
620 when the main Kate view has 0 as size (during creation),
621 and there frame around KateViewInternal. In which
622 case we'd set the view cache to 0 (or less!) lines, and
623 start allocating huge chunks of data, later. */
624 int newSize = (qMax(0, height()) / renderer()->lineHeight()) + 1;
625 cache()->updateViewCache(startPos(), newSize, viewLinesScrolled);
626 m_visibleLineCount = newSize;
627
628 KTextEditor::Cursor maxStart = maxStartPos(changed);
629 int maxLineScrollRange = maxStart.line();
630 if (view()->dynWordWrap() && maxStart.column() != 0) {
631 maxLineScrollRange++;
632 }
633 m_lineScroll->setRange(0, maxLineScrollRange);
634
635 m_lineScroll->setValue(startLine());
636 m_lineScroll->setSingleStep(1);
637 m_lineScroll->setPageStep(qMax(0, height()) / renderer()->lineHeight());
638 m_lineScroll->blockSignals(blocked);
639
640 KateViewConfig::ScrollbarMode show_scrollbars = static_cast<KateViewConfig::ScrollbarMode>(view()->config()->showScrollbars());
641
642 bool visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (maxLineScrollRange != 0)));
643 bool visible_dummy = visible;
644
645 m_lineScroll->setVisible(visible);
646
647 if (!view()->dynWordWrap()) {
648 int max = maxLen(startLine()) - width();
649 if (max < 0) {
650 max = 0;
651 }
652
653 // if we lose the ability to scroll horizontally, move view to the far-left
654 if (max == 0) {
655 scrollColumns(0);
656 }
657
658 blocked = m_columnScroll->blockSignals(true);
659
660 // disable scrollbar
661 m_columnScroll->setDisabled(max == 0);
662
663 visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (max != 0)));
664 visible_dummy &= visible;
665 m_columnScroll->setVisible(visible);
666
667 m_columnScroll->setRange(0, max + (renderer()->spaceWidth() / 2)); // Add some space for the caret at EOL
668
669 m_columnScroll->setValue(startX());
670
671 // Approximate linescroll
672 m_columnScroll->setSingleStep(renderer()->currentFontMetrics().horizontalAdvance(QLatin1Char('a')));
673 m_columnScroll->setPageStep(width());
674
675 m_columnScroll->blockSignals(blocked);
676 } else {
677 visible_dummy = false;
678 }
679
680 m_dummy->setVisible(visible_dummy);
681
682 if (changed) {
683 updateDirty();
684 }
685}
686
687/**
688 * this function ensures a certain location is visible on the screen.
689 * if endCol is -1, ignore making the columns visible.
690 */
691void KateViewInternal::makeVisible(const KTextEditor::Cursor c, int endCol, bool force, bool center, bool calledExternally)
692{
693 // qCDebug(LOG_KTE) << "MakeVisible start " << startPos() << " end " << endPos() << " -> request: " << c;// , new start [" << scroll.line << "," <<
694 // scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height(); if the line is in a folded region, unfold all the way up if (
695 // doc()->foldingTree()->findNodeForLine( c.line )->visible )
696 // qCDebug(LOG_KTE)<<"line ("<<c.line<<") should be visible";
697
698 const int lnDisp = linesDisplayed();
699 const int viewLine = cache()->displayViewLine(c, true);
700 const bool curBelowScreen = (viewLine == -2);
701
702 if (force) {
704 scrollPos(scroll, force, calledExternally);
705 } else if (center && (c < startPos() || c > endPos())) {
706 KTextEditor::Cursor scroll = viewLineOffset(c, -int(lnDisp) / 2);
707 scrollPos(scroll, false, calledExternally);
708 } else if ((viewLine >= (lnDisp - m_minLinesVisible)) || (curBelowScreen)) {
709 KTextEditor::Cursor scroll = viewLineOffset(c, -(lnDisp - m_minLinesVisible - 1));
710 scrollPos(scroll, false, calledExternally);
711 } else if (c < viewLineOffset(startPos(), m_minLinesVisible)) {
712 KTextEditor::Cursor scroll = viewLineOffset(c, -m_minLinesVisible);
713 scrollPos(scroll, false, calledExternally);
714 } else {
715 // Check to see that we're not showing blank lines
716 KTextEditor::Cursor max = maxStartPos();
717 if (startPos() > max) {
718 scrollPos(max, max.column(), calledExternally);
719 }
720 }
721
722 if (!view()->dynWordWrap() && (endCol != -1 || view()->wrapCursor())) {
723 KTextEditor::Cursor rc = toRealCursor(c);
724 int sX = renderer()->cursorToX(cache()->textLayout(rc), rc, !view()->wrapCursor());
725
726 int sXborder = sX - 8;
727 if (sXborder < 0) {
728 sXborder = 0;
729 }
730
731 if (sX < startX()) {
732 scrollColumns(sXborder);
733 } else if (sX > startX() + width()) {
734 scrollColumns(sX - width() + 8);
735 }
736 }
737
738 m_madeVisible = !force;
739
740#ifndef QT_NO_ACCESSIBILITY
741 // FIXME -- is this needed?
742// QAccessible::updateAccessibility(this, KateCursorAccessible::ChildId, QAccessible::Focus);
743#endif
744}
745
746void KateViewInternal::slotRegionVisibilityChanged()
747{
748 qCDebug(LOG_KTE);
749
750 // ensure the layout cache is ok for the updateCursor calls below
751 // without the updateView() the view will jump to the bottom on hiding blocks after
752 // change cfb0af25bdfac0d8f86b42db0b34a6bc9f9a361e
753 cache()->clear();
754 updateView();
755
756 m_cachedMaxStartPos.setLine(-1);
757 KTextEditor::Cursor max = maxStartPos();
758 if (startPos() > max) {
759 scrollPos(max, false, false, false /* don't emit signals! */);
760 }
761
762 // if text was folded: make sure the cursor is on a visible line
763 qint64 foldedRangeId = -1;
764 if (!view()->textFolding().isLineVisible(m_cursor.line(), &foldedRangeId)) {
765 KTextEditor::Range foldingRange = view()->textFolding().foldingRange(foldedRangeId);
766 Q_ASSERT(foldingRange.start().isValid());
767
768 // set cursor to start of folding region
769 updateCursor(foldingRange.start(), true);
770 } else {
771 // force an update of the cursor, since otherwise the m_displayCursor
772 // line may be below the total amount of visible lines.
773 updateCursor(m_cursor, true);
774 }
775
776 updateView();
777 update();
778 m_leftBorder->update();
779
780 // emit signals here, scrollPos has this disabled, to ensure we do this after all stuff is updated!
781 Q_EMIT view()->verticalScrollPositionChanged(m_view, max);
782 Q_EMIT view()->displayRangeChanged(m_view);
783}
784
785void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int)
786{
787 qCDebug(LOG_KTE);
788 // FIXME: performance problem
789 m_leftBorder->update();
790}
791
792void KateViewInternal::showEvent(QShowEvent *e)
793{
794 updateView();
795
797}
798
799KTextEditor::Attribute::Ptr KateViewInternal::attributeAt(const KTextEditor::Cursor position) const
800{
802 Kate::TextLine kateLine = doc()->kateTextLine(position.line());
803 *attrib = *m_view->renderer()->attribute(kateLine.attribute(position.column()));
804 return attrib;
805}
806
807int KateViewInternal::linesDisplayed() const
808{
809 int h = height();
810
811 // catch zero heights, even if should not happen
812 int fh = qMax(1, renderer()->lineHeight());
813
814 // default to 1, there is always one line around....
815 // too many places calc with linesDisplayed() - 1
816 return qMax(1, (h - (h % fh)) / fh);
817}
818
819QPoint KateViewInternal::cursorToCoordinate(const KTextEditor::Cursor cursor, bool realCursor, bool includeBorder) const
820{
821 if (cursor.line() >= doc()->lines()) {
822 return QPoint(-1, -1);
823 }
824
825 int viewLine = cache()->displayViewLine(realCursor ? toVirtualCursor(cursor) : cursor, true);
826
827 if (viewLine < 0 || viewLine >= cache()->viewCacheLineCount()) {
828 return QPoint(-1, -1);
829 }
830
831 const int y = (int)viewLine * renderer()->lineHeight();
832
833 KateTextLayout layout = cache()->viewLine(viewLine);
834
835 const auto textLength = doc()->lineLength(cursor.line());
836 if (cursor.column() > textLength) {
837 return QPoint(-1, -1);
838 }
839
840 int x = 0;
841
842 // only set x value if we have a valid layout (bug #171027)
843 if (layout.isValid()) {
844 if (!layout.isRightToLeft() || (layout.isRightToLeft() && view()->dynWordWrap())) {
845 x = (int)layout.lineLayout().cursorToX(cursor.column());
846 } else /* rtl + dynWordWrap == false */ {
847 // if text is rtl and dynamic wrap is false, the x offsets are in the opposite
848 // direction i.e., [0] == biggest offset, [1] = next
849 x = (int)layout.lineLayout().cursorToX(textLength - cursor.column());
850 }
851 }
852 // else
853 // qCDebug(LOG_KTE) << "Invalid Layout";
854
855 if (includeBorder) {
856 x += m_leftBorder->width();
857 }
858
859 x -= startX();
860
861 return QPoint(x, y);
862}
863
864QPoint KateViewInternal::cursorCoordinates(bool includeBorder) const
865{
866 return cursorToCoordinate(m_displayCursor, false, includeBorder);
867}
868
869KTextEditor::Cursor KateViewInternal::findMatchingBracket()
870{
872
873 if (!m_bm->toRange().isValid()) {
875 }
876
877 Q_ASSERT(m_bmEnd->toRange().isValid());
878 Q_ASSERT(m_bmStart->toRange().isValid());
879
880 // For e.g. the text "{|}" (where | is the cursor), m_bmStart is equal to [ (0, 0) -> (0, 1) ]
881 // and the closing bracket is in (0, 1). Thus, we check m_bmEnd first.
882 if (m_bmEnd->toRange().contains(m_cursor) || m_bmEnd->end() == m_cursor.toCursor()) {
883 c = m_bmStart->start();
884 } else if (m_bmStart->toRange().contains(m_cursor) || m_bmStart->end() == m_cursor.toCursor()) {
885 c = m_bmEnd->end();
886 // We need to adjust the cursor position in case of override mode, BUG-402594
887 if (doc()->config()->ovr()) {
888 c.setColumn(c.column() - 1);
889 }
890 } else {
891 // should never happen: a range exists, but the cursor position is
892 // neither at the start nor at the end...
894 }
895
896 return c;
897}
898
899class CalculatingCursor
900{
901public:
902 // These constructors constrain their arguments to valid positions
903 // before only the third one did, but that leads to crashes
904 // see bug 227449
905 CalculatingCursor(KateViewInternal *vi)
906 : m_vi(vi)
907 {
908 makeValid();
909 }
910
911 CalculatingCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
912 : m_cursor(c)
913 , m_vi(vi)
914 {
915 makeValid();
916 }
917
918 CalculatingCursor(KateViewInternal *vi, int line, int col)
919 : m_cursor(line, col)
920 , m_vi(vi)
921 {
922 makeValid();
923 }
924
925 virtual ~CalculatingCursor()
926 {
927 }
928
929 int line() const
930 {
931 return m_cursor.line();
932 }
933
934 int column() const
935 {
936 return m_cursor.column();
937 }
938
939 operator KTextEditor::Cursor() const
940 {
941 return m_cursor;
942 }
943
944 virtual CalculatingCursor &operator+=(int n) = 0;
945
946 virtual CalculatingCursor &operator-=(int n) = 0;
947
948 CalculatingCursor &operator++()
949 {
950 return operator+=(1);
951 }
952
953 CalculatingCursor &operator--()
954 {
955 return operator-=(1);
956 }
957
958 void makeValid()
959 {
960 m_cursor.setLine(qBound(0, line(), int(doc()->lines() - 1)));
961 if (view()->wrapCursor()) {
962 m_cursor.setColumn(qBound(0, column(), doc()->lineLength(line())));
963 } else {
964 m_cursor.setColumn(qMax(0, column()));
965 }
966 Q_ASSERT(valid());
967 }
968
969 void toEdge(KateViewInternal::Bias bias)
970 {
971 if (bias == KateViewInternal::left) {
972 m_cursor.setColumn(0);
973 } else if (bias == KateViewInternal::right) {
974 m_cursor.setColumn(doc()->lineLength(line()));
975 }
976 }
977
978 bool atEdge() const
979 {
980 return atEdge(KateViewInternal::left) || atEdge(KateViewInternal::right);
981 }
982
983 bool atEdge(KateViewInternal::Bias bias) const
984 {
985 switch (bias) {
986 case KateViewInternal::left:
987 return column() == 0;
988 case KateViewInternal::none:
989 return atEdge();
990 case KateViewInternal::right:
991 return column() >= doc()->lineLength(line());
992 default:
993 Q_ASSERT(false);
994 return false;
995 }
996 }
997
998protected:
999 bool valid() const
1000 {
1001 return line() >= 0 && line() < doc()->lines() && column() >= 0 && (!view()->wrapCursor() || column() <= doc()->lineLength(line()));
1002 }
1003 KTextEditor::ViewPrivate *view()
1004 {
1005 return m_vi->m_view;
1006 }
1007 const KTextEditor::ViewPrivate *view() const
1008 {
1009 return m_vi->m_view;
1010 }
1012 {
1013 return view()->doc();
1014 }
1015 const KTextEditor::DocumentPrivate *doc() const
1016 {
1017 return view()->doc();
1018 }
1019 KTextEditor::Cursor m_cursor;
1020 KateViewInternal *m_vi;
1021};
1022
1023class BoundedCursor final : public CalculatingCursor
1024{
1025public:
1026 BoundedCursor(KateViewInternal *vi)
1027 : CalculatingCursor(vi)
1028 {
1029 }
1030 BoundedCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
1031 : CalculatingCursor(vi, c)
1032 {
1033 }
1034 BoundedCursor(KateViewInternal *vi, int line, int col)
1035 : CalculatingCursor(vi, line, col)
1036 {
1037 }
1038 CalculatingCursor &operator+=(int n) override
1039 {
1040 KateLineLayout *thisLine = m_vi->cache()->line(line());
1041 if (!thisLine || !thisLine->isValid()) {
1042 qCWarning(LOG_KTE) << "Did not retrieve valid layout for line " << line();
1043 return *this;
1044 }
1045
1046 const bool wrapCursor = view()->wrapCursor();
1047 int maxColumn = -1;
1048 if (n >= 0) {
1049 for (int i = 0; i < n; i++) {
1050 if (column() >= thisLine->length()) {
1051 if (wrapCursor) {
1052 break;
1053
1054 } else if (view()->dynWordWrap()) {
1055 // Don't go past the edge of the screen in dynamic wrapping mode
1056 if (maxColumn == -1) {
1057 maxColumn = thisLine->length() + ((m_vi->width() - thisLine->widthOfLastLine()) / m_vi->renderer()->spaceWidth()) - 1;
1058 }
1059
1060 if (column() >= maxColumn) {
1061 m_cursor.setColumn(maxColumn);
1062 break;
1063 }
1064
1065 m_cursor.setColumn(column() + 1);
1066
1067 } else {
1068 m_cursor.setColumn(column() + 1);
1069 }
1070
1071 } else {
1072 m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column()));
1073 }
1074 }
1075 } else {
1076 for (int i = 0; i > n; i--) {
1077 if (column() >= thisLine->length()) {
1078 m_cursor.setColumn(column() - 1);
1079 } else if (column() == 0) {
1080 break;
1081 } else {
1082 m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column()));
1083 }
1084 }
1085 }
1086
1087 Q_ASSERT(valid());
1088 return *this;
1089 }
1090 CalculatingCursor &operator-=(int n) override
1091 {
1092 return operator+=(-n);
1093 }
1094};
1095
1096class WrappingCursor final : public CalculatingCursor
1097{
1098public:
1099 WrappingCursor(KateViewInternal *vi)
1100 : CalculatingCursor(vi)
1101 {
1102 }
1103 WrappingCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
1104 : CalculatingCursor(vi, c)
1105 {
1106 }
1107 WrappingCursor(KateViewInternal *vi, int line, int col)
1108 : CalculatingCursor(vi, line, col)
1109 {
1110 }
1111
1112 CalculatingCursor &operator+=(int n) override
1113 {
1114 KateLineLayout *thisLine = m_vi->cache()->line(line());
1115 if (!thisLine || !thisLine->isValid()) {
1116 qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line();
1117 return *this;
1118 }
1119
1120 if (n >= 0) {
1121 for (int i = 0; i < n; i++) {
1122 if (column() >= thisLine->length()) {
1123 // Have come to the end of a line
1124 if (line() >= doc()->lines() - 1)
1125 // Have come to the end of the document
1126 {
1127 break;
1128 }
1129
1130 // Advance to the beginning of the next line
1131 m_cursor.setColumn(0);
1132 m_cursor.setLine(line() + 1);
1133
1134 // Retrieve the next text range
1135 thisLine = m_vi->cache()->line(line());
1136 if (!thisLine || !thisLine->isValid()) {
1137 qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line();
1138 return *this;
1139 }
1140
1141 continue;
1142 }
1143
1144 m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column()));
1145 }
1146
1147 } else {
1148 for (int i = 0; i > n; i--) {
1149 if (column() == 0) {
1150 // Have come to the start of the document
1151 if (line() == 0) {
1152 break;
1153 }
1154
1155 // Start going back to the end of the last line
1156 m_cursor.setLine(line() - 1);
1157
1158 // Retrieve the next text range
1159 thisLine = m_vi->cache()->line(line());
1160 if (!thisLine || !thisLine->isValid()) {
1161 qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line();
1162 return *this;
1163 }
1164
1165 // Finish going back to the end of the last line
1166 m_cursor.setColumn(thisLine->length());
1167
1168 continue;
1169 }
1170
1171 if (column() > thisLine->length()) {
1172 m_cursor.setColumn(column() - 1);
1173 } else {
1174 m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column()));
1175 }
1176 }
1177 }
1178
1179 Q_ASSERT(valid());
1180 return *this;
1181 }
1182 CalculatingCursor &operator-=(int n) override
1183 {
1184 return operator+=(-n);
1185 }
1186};
1187
1188/**
1189 * @brief The CamelCursor class
1190 *
1191 * This class allows for "camel humps" when moving the cursor
1192 * using Ctrl + Left / Right. Similarly, this will also get triggered
1193 * when you press Ctrl+Shift+Left/Right for selection and Ctrl+Del
1194 * Ctrl + backspace for deletion.
1195 *
1196 * It is absoloutely essential that if you move through a word in 'n'
1197 * jumps, you should be able to move back with exactly same 'n' movements
1198 * which you made when moving forward. Example:
1199 *
1200 * Word: KateViewInternal
1201 *
1202 * Moving cursor towards right while holding control will result in cursor
1203 * landing in the following places (assuming you start from column 0)
1204 *
1205 * | | |
1206 * KateViewInternal
1207 *
1208 * Moving the cursor back to get to the starting position should also
1209 * take exactly 3 movements:
1210 *
1211 * | | |
1212 * KateViewInternal
1213 *
1214 * In addition to simple camel case, this class also handles snake_case
1215 * capitalized snake, and mixtures of Camel + snake/underscore, for example
1216 * m_someMember. If a word has underscores in it, for example:
1217 *
1218 * snake_case_word
1219 *
1220 * the leading underscore is considered part of the word and thus a cursor
1221 * movement will land right after the underscore. Moving the cursor to end
1222 * for the above word should be like this:
1223 *
1224 * startpos: 0
1225 * | | |
1226 * snake_case_word
1227 *
1228 * When jumping back to startpos, exact same "spots" need to be hit on your way
1229 * back.
1230 *
1231 * If a word has multiple leading underscores: snake___case, the underscores will
1232 * be considered part of the word and thus a jump wil land us "after" the last underscore.
1233 *
1234 * There are some other variations in this, for example Capitalized words, or identifiers
1235 * with numbers in betweens. For such cases, All capitalized words are skipped in one go
1236 * till we are on some "non-capitalized" word. In this context, a number is a non-capitalized
1237 * word so will break the jump. Examples:
1238 *
1239 * | |
1240 * W1RD
1241 *
1242 * but for WORD_CAPITAL, following will happen:
1243 *
1244 * | |
1245 * WORD_CAPITAL
1246 *
1247 * The first case here is tricky to handle for reverse movement. I haven't noticed any
1248 * cases which the current implementation is unable to handle but there might be some.
1249 *
1250 * With languages like PHP where you have $ as part of the identifier, the cursor jump
1251 * will break "after" dollar. Consider: $phpVar, Following will happen:
1252 *
1253 * | | |
1254 * $phpVar
1255 *
1256 * And of course, the reverse will be exact opposite.
1257 *
1258 * Similar to PHP, with CSS Colors, jump will break on '#' charachter
1259 *
1260 * Please see the test cases testWordMovementSingleRow() for more examples/data.
1261 *
1262 * It is absoloutely essential to know that this class *only* gets triggered for
1263 * cursor movement if we are in a word.
1264 *
1265 * Note to bugfixer: If some bug occurs, before changing anything please add a test
1266 * case for the bug and make sure everything passes before and after. The test case
1267 * for this can be found autotests/src/camelcursortest.cpp.
1268 *
1269 * @author Waqar Ahmed <waqar.17a@gmail.com>
1270 */
1271class CamelCursor final : public CalculatingCursor
1272{
1273public:
1274 CamelCursor(KateViewInternal *vi, const KTextEditor::Cursor c)
1275 : CalculatingCursor(vi, c)
1276 {
1277 }
1278
1279 CalculatingCursor &operator+=(int n) override
1280 {
1281 KateLineLayout *thisLine = m_vi->cache()->line(line());
1282 if (!thisLine || !thisLine->isValid()) {
1283 qCWarning(LOG_KTE) << "Did not retrieve valid layout for line " << line();
1284 return *this;
1285 }
1286
1287 auto isSurrogate = [](QChar c) {
1288 return c.isLowSurrogate() || c.isHighSurrogate();
1289 };
1290
1291 if (n >= 0) {
1292 auto skipCaps = [](QStringView text, int &col) {
1293 int count = 0;
1294 while (col < text.size() && text.at(col).isUpper()) {
1295 ++count;
1296 ++col;
1297 }
1298 // if this is a letter, then it means we are in the
1299 // middle of a word, step back one position so that
1300 // we are at the last Cap letter
1301 // Otherwise, it's an all cap word
1302 if (count > 1 && col < text.size() && text.at(col).isLetterOrNumber()) {
1303 --col;
1304 }
1305 };
1306
1307 int jump = -1;
1308 int col = column();
1309 const QString &text = thisLine->textLine().text();
1310
1311 if (col < text.size() && text.at(col).isUpper()) {
1312 skipCaps(text, col);
1313 }
1314
1315 for (int i = col; i < thisLine->length(); ++i) {
1316 const auto c = text.at(i);
1317 if (isSurrogate(c)) {
1318 col++;
1319 continue;
1320 } else if (c.isUpper() || !c.isLetterOrNumber()) {
1321 break;
1322 }
1323 ++col;
1324 }
1325
1326 // eat any '_' that are after the word BEFORE any space happens
1327 if (col < text.size() && text.at(col) == QLatin1Char('_')) {
1328 while (col < text.size() && text.at(col) == QLatin1Char('_')) {
1329 ++col;
1330 }
1331 }
1332
1333 // Underscores eaten, so now eat any spaces till next word
1334 if (col < text.size() && text.at(col).isSpace()) {
1335 while (col < text.size() && text.at(col).isSpace()) {
1336 ++col;
1337 }
1338 }
1339
1340 jump = col < 0 || (column() == col) ? (column() + 1) : col;
1341 m_cursor.setColumn(jump);
1342 } else {
1343 int jump = -1;
1344
1345 auto skipCapsRev = [](QStringView text, int &col) {
1346 int count = 0;
1347 while (col > 0 && text.at(col).isUpper()) {
1348 ++count;
1349 --col;
1350 }
1351
1352 // if more than one cap found, and current
1353 // column is not upper, we want to move ahead
1354 // to the upper
1355 if (count >= 1 && col >= 0 && !text.at(col).isUpper()) {
1356 ++col;
1357 }
1358 };
1359
1360 const QString &text = thisLine->textLine().text();
1361 int col = std::min<int>(column(), text.size() - 1);
1362 // decrement because we might be standing at a camel hump
1363 // already and don't want to return the same position
1364 col = col - 1;
1365
1366 // if we are at the end of line
1367 if (column() == text.size()) {
1368 // there is a letter before
1369 if (text.at(col + 1).isLetter()) {
1370 // and a space before that
1371 if (col >= 0 && text.at(col).isSpace()) {
1372 // we have a one letter word,
1373 // so move forward to end of word
1374 ++col;
1375 }
1376 }
1377 }
1378
1379 // skip any spaces
1380 if (col > 0 && text.at(col).isSpace()) {
1381 while (text.at(col).isSpace() && col > 0) {
1382 --col;
1383 }
1384 }
1385
1386 // Skip Underscores
1387 if (col > 0 && text.at(col) == QLatin1Char('_')) {
1388 while (col > 0 && text.at(col) == QLatin1Char('_')) {
1389 --col;
1390 }
1391 }
1392
1393 if (col > 0 && text.at(col).isUpper()) {
1394 skipCapsRev(text, col);
1395 }
1396
1397 for (int i = col; i > 0; --i) {
1398 const auto c = text.at(i);
1399 if (isSurrogate(c)) {
1400 --col;
1401 continue;
1402 } else if (c.isUpper() || !c.isLetterOrNumber()) {
1403 break;
1404 }
1405 --col;
1406 }
1407
1408 if (col >= 0 && !text.at(col).isLetterOrNumber() && !isSurrogate(text.at(col))) {
1409 ++col;
1410 }
1411
1412 if (col < 0) {
1413 jump = 0;
1414 } else if (col == column() && column() > 0) {
1415 jump = column() - 1;
1416 } else {
1417 jump = col;
1418 }
1419
1420 m_cursor.setColumn(jump);
1421 }
1422
1423 Q_ASSERT(valid());
1424 return *this;
1425 }
1426
1427 CalculatingCursor &operator-=(int n) override
1428 {
1429 return operator+=(-n);
1430 }
1431};
1432
1433void KateViewInternal::moveChar(KateViewInternal::Bias bias, bool sel)
1434{
1436 if (view()->wrapCursor()) {
1437 c = WrappingCursor(this, m_cursor) += bias;
1438 } else {
1439 c = BoundedCursor(this, m_cursor) += bias;
1440 }
1441
1442 const auto &sc = view()->m_secondaryCursors;
1444 const int lastLine = doc()->lastLine();
1445 bool shouldEnsureUniqueCursors = false;
1446 for (const auto &c : sc) {
1447 auto oldPos = c.cursor();
1448 if (view()->wrapCursor()) {
1449 c.pos->setPosition(WrappingCursor(this, oldPos) += bias);
1450 } else {
1451 c.pos->setPosition(BoundedCursor(this, oldPos) += bias);
1452 }
1453 const auto newPos = c.pos->toCursor();
1454 multiCursors.push_back({.oldPos = oldPos, .newPos = newPos});
1455 // We only need to do this if cursors were in first or last line
1456 if (!shouldEnsureUniqueCursors) {
1457 shouldEnsureUniqueCursors = newPos.line() == 0 || newPos.line() == lastLine;
1458 }
1459 }
1460
1461 updateSelection(c, sel);
1462 updateCursor(c);
1463 updateSecondaryCursors(multiCursors, sel);
1464 if (shouldEnsureUniqueCursors) {
1465 view()->ensureUniqueCursors();
1466 }
1467}
1468
1469void KateViewInternal::cursorPrevChar(bool sel)
1470{
1471 if (!view()->wrapCursor() && m_cursor.column() == 0) {
1472 return;
1473 }
1474
1475 moveChar(KateViewInternal::left, sel);
1476}
1477
1478void KateViewInternal::cursorNextChar(bool sel)
1479{
1480 moveChar(KateViewInternal::right, sel);
1481}
1482
1483void KateViewInternal::wordPrev(bool sel)
1484{
1485 auto characterAtPreviousColumn = [this](KTextEditor::Cursor cursor) -> QChar {
1486 return doc()->characterAt({cursor.line(), cursor.column() - 1});
1487 };
1488
1489 auto wordPrevious = [this, &characterAtPreviousColumn](KTextEditor::Cursor cursor) -> KTextEditor::Cursor {
1490 WrappingCursor c(this, cursor);
1491
1492 // First we skip backwards all space.
1493 // Then we look up into which category the current position falls:
1494 // 1. a "word" character
1495 // 2. a "non-word" character (except space)
1496 // 3. the beginning of the line
1497 // and skip all preceding characters that fall into this class.
1498 // The code assumes that space is never part of the word character class.
1499
1500 KateHighlighting *h = doc()->highlight();
1501
1502 while (!c.atEdge(left) && (c.column() > doc()->lineLength(c.line()) || characterAtPreviousColumn(c).isSpace())) {
1503 --c;
1504 }
1505
1506 if (c.atEdge(left)) {
1507 --c;
1508 } else if (h->isInWord(characterAtPreviousColumn(c))) {
1509 if (doc()->config()->camelCursor()) {
1510 CamelCursor cc(this, cursor);
1511 --cc;
1512 return cc;
1513 } else {
1514 while (!c.atEdge(left) && h->isInWord(characterAtPreviousColumn(c))) {
1515 --c;
1516 }
1517 }
1518 } else {
1519 while (!c.atEdge(left)
1520 && !h->isInWord(characterAtPreviousColumn(c))
1521 // in order to stay symmetric to wordLeft()
1522 // we must not skip space preceding a non-word sequence
1523 && !characterAtPreviousColumn(c).isSpace()) {
1524 --c;
1525 }
1526 }
1527
1528 return c;
1529 };
1530
1531 const auto &secondaryCursors = view()->m_secondaryCursors;
1532 QVarLengthArray<CursorPair, 16> cursorsToUpdate;
1533 for (const auto &cursor : secondaryCursors) {
1534 auto oldPos = cursor.cursor();
1535 auto newCursorPos = wordPrevious(cursor.cursor());
1536 cursor.pos->setPosition(newCursorPos);
1537 cursorsToUpdate.push_back({.oldPos = oldPos, .newPos = newCursorPos});
1538 }
1539
1540 // update primary cursor
1541 const auto c = wordPrevious(m_cursor);
1542 updateSelection(c, sel);
1543 updateCursor(c);
1544
1545 if (!sel) {
1546 view()->ensureUniqueCursors();
1547 }
1548 updateSecondaryCursors(cursorsToUpdate, sel);
1549}
1550
1551void KateViewInternal::wordNext(bool sel)
1552{
1553 auto nextWord = [this](KTextEditor::Cursor cursor) -> KTextEditor::Cursor {
1554 WrappingCursor c(this, cursor);
1555
1556 // We look up into which category the current position falls:
1557 // 1. a "word" character
1558 // 2. a "non-word" character (except space)
1559 // 3. the end of the line
1560 // and skip all following characters that fall into this class.
1561 // If the skipped characters are followed by space, we skip that too.
1562 // The code assumes that space is never part of the word character class.
1563
1564 KateHighlighting *h = doc()->highlight();
1565 if (c.atEdge(right)) {
1566 ++c;
1567 } else if (h->isInWord(doc()->characterAt(c))) {
1568 if (doc()->config()->camelCursor()) {
1569 CamelCursor cc(this, cursor);
1570 ++cc;
1571 return cc;
1572 } else {
1573 while (!c.atEdge(right) && h->isInWord(doc()->characterAt(c))) {
1574 ++c;
1575 }
1576 }
1577 } else {
1578 while (!c.atEdge(right)
1579 && !h->isInWord(doc()->characterAt(c))
1580 // we must not skip space, because if that space is followed
1581 // by more non-word characters, we would skip them, too
1582 && !doc()->characterAt(c).isSpace()) {
1583 ++c;
1584 }
1585 }
1586
1587 while (!c.atEdge(right) && doc()->characterAt(c).isSpace()) {
1588 ++c;
1589 }
1590
1591 return c;
1592 };
1593
1594 const auto &secondaryCursors = view()->m_secondaryCursors;
1595 QVarLengthArray<CursorPair, 16> cursorsToUpdate;
1596 for (const auto &cursor : secondaryCursors) {
1597 auto oldPos = cursor.cursor();
1598 auto newCursorPos = nextWord(cursor.cursor());
1599 cursor.pos->setPosition(newCursorPos);
1600 cursorsToUpdate.push_back({.oldPos = oldPos, .newPos = newCursorPos});
1601 }
1602
1603 // update primary cursor
1604 const auto c = nextWord(m_cursor);
1605 updateSelection(c, sel);
1606 updateCursor(c);
1607
1608 // Remove cursors which have same position
1609 if (!sel) {
1610 view()->ensureUniqueCursors();
1611 }
1612 updateSecondaryCursors(cursorsToUpdate, sel);
1613}
1614
1615void KateViewInternal::moveEdge(KateViewInternal::Bias bias, bool sel)
1616{
1617 BoundedCursor c(this, m_cursor);
1618 c.toEdge(bias);
1619 updateSelection(c, sel);
1620 updateCursor(c);
1621}
1622
1623KTextEditor::Cursor KateViewInternal::moveCursorToLineStart(KTextEditor::Cursor cursor)
1624{
1625 if (view()->dynWordWrap() && currentLayout(cursor).startCol()) {
1626 // Allow us to go to the real start if we're already at the start of the view line
1627 if (cursor.column() != currentLayout(cursor).startCol()) {
1628 KTextEditor::Cursor c = currentLayout(cursor).start();
1629 return c;
1630 }
1631 }
1632
1633 if (!doc()->config()->smartHome()) {
1634 BoundedCursor c(this, cursor);
1635 c.toEdge(left);
1636 return c;
1637 }
1638
1639 if (cursor.line() < 0 || cursor.line() >= doc()->lines()) {
1641 }
1642
1643 Kate::TextLine l = doc()->kateTextLine(cursor.line());
1644
1646 int lc = l.firstChar();
1647
1648 if (lc < 0 || c.column() == lc) {
1649 c.setColumn(0);
1650 } else {
1651 c.setColumn(lc);
1652 }
1653 return c;
1654}
1655
1656void KateViewInternal::home(bool sel)
1657{
1658 // Multicursor
1659 view()->ensureUniqueCursors(/*matchLine*/ true);
1660 const auto &secondaryCursors = view()->m_secondaryCursors;
1661 QVarLengthArray<CursorPair, 16> cursorsToUpdate;
1662 for (const auto &c : secondaryCursors) {
1663 auto oldPos = c.cursor();
1664 // These will end up in same place so just remove
1665 auto newPos = moveCursorToLineStart(oldPos);
1666 c.pos->setPosition(newPos);
1667 cursorsToUpdate.push_back({.oldPos = oldPos, .newPos = newPos});
1668 }
1669
1670 // Primary cursor
1671 auto newPos = moveCursorToLineStart(m_cursor);
1672 if (newPos.isValid()) {
1673 updateSelection(newPos, sel);
1674 updateCursor(newPos, true);
1675 }
1676 updateSecondaryCursors(cursorsToUpdate, sel);
1677}
1678
1679KTextEditor::Cursor KateViewInternal::moveCursorToLineEnd(KTextEditor::Cursor cursor)
1680{
1681 KateTextLayout layout = currentLayout(cursor);
1682
1683 if (view()->dynWordWrap() && layout.wrap()) {
1684 // Allow us to go to the real end if we're already at the end of the view line
1685 if (cursor.column() < layout.endCol() - 1) {
1686 KTextEditor::Cursor c(cursor.line(), layout.endCol() - 1);
1687 return c;
1688 }
1689 }
1690
1691 if (!doc()->config()->smartHome()) {
1692 BoundedCursor c(this, cursor);
1693 c.toEdge(right);
1694 return c;
1695 }
1696
1697 if (cursor.line() < 0 || cursor.line() >= doc()->lines()) {
1699 }
1700
1701 Kate::TextLine l = doc()->kateTextLine(cursor.line());
1702
1703 // "Smart End", as requested in bugs #78258 and #106970
1704 if (cursor.column() == doc()->lineLength(cursor.line())) {
1706 c.setColumn(l.lastChar() + 1);
1707 return c;
1708 } else {
1709 BoundedCursor c(this, cursor);
1710 c.toEdge(right);
1711 return c;
1712 }
1713}
1714
1715void KateViewInternal::end(bool sel)
1716{
1717 // Multicursor
1718 view()->ensureUniqueCursors(/*matchLine*/ true);
1719
1720 QVarLengthArray<CursorPair, 16> cursorsToUpdate;
1721 const auto &secondaryCursors = view()->m_secondaryCursors;
1722 for (const auto &c : secondaryCursors) {
1723 auto oldPos = c.cursor();
1724 // These will end up in same place so just remove
1725 auto newPos = moveCursorToLineEnd(oldPos);
1726 c.pos->setPosition(newPos);
1727 cursorsToUpdate.push_back({.oldPos = oldPos, .newPos = newPos});
1728 }
1729
1730 auto newPos = moveCursorToLineEnd(m_cursor);
1731 if (newPos.isValid()) {
1732 updateSelection(newPos, sel);
1733 updateCursor(newPos);
1734 }
1735
1736 updateSecondaryCursors(cursorsToUpdate, sel);
1737 paintCursor();
1738}
1739
1740KateTextLayout KateViewInternal::currentLayout(KTextEditor::Cursor c) const
1741{
1742 return cache()->textLayout(c);
1743}
1744
1745KateTextLayout KateViewInternal::previousLayout(KTextEditor::Cursor c) const
1746{
1747 int currentViewLine = cache()->viewLine(c);
1748
1749 if (currentViewLine) {
1750 return cache()->textLayout(c.line(), currentViewLine - 1);
1751 } else {
1752 return cache()->textLayout(view()->textFolding().visibleLineToLine(toVirtualCursor(c).line() - 1), -1);
1753 }
1754}
1755
1756KateTextLayout KateViewInternal::nextLayout(KTextEditor::Cursor c) const
1757{
1758 int currentViewLine = cache()->viewLine(c) + 1;
1759
1760 const KateLineLayout *thisLine = cache()->line(c.line());
1761 if (thisLine && currentViewLine >= thisLine->viewLineCount()) {
1762 currentViewLine = 0;
1763 return cache()->textLayout(view()->textFolding().visibleLineToLine(toVirtualCursor(c).line() + 1), currentViewLine);
1764 } else {
1765 return cache()->textLayout(c.line(), currentViewLine);
1766 }
1767}
1768
1769/*
1770 * This returns the cursor which is offset by (offset) view lines.
1771 * This is the main function which is called by code not specifically dealing with word-wrap.
1772 * The opposite conversion (cursor to offset) can be done with cache()->displayViewLine().
1773 *
1774 * The cursors involved are virtual cursors (ie. equivalent to m_displayCursor)
1775 */
1776
1777KTextEditor::Cursor KateViewInternal::viewLineOffset(const KTextEditor::Cursor virtualCursor, int offset, bool keepX)
1778{
1779 if (!view()->dynWordWrap()) {
1780 KTextEditor::Cursor ret(qMin((int)view()->textFolding().visibleLines() - 1, virtualCursor.line() + offset), 0);
1781
1782 if (ret.line() < 0) {
1783 ret.setLine(0);
1784 }
1785
1786 if (keepX) {
1787 int realLine = view()->textFolding().visibleLineToLine(ret.line());
1788 KateTextLayout t = cache()->textLayout(realLine, 0);
1789 Q_ASSERT(t.isValid());
1790
1791 ret.setColumn(renderer()->xToCursor(t, m_preservedX, !view()->wrapCursor()).column());
1792 }
1793
1794 return ret;
1795 }
1796
1797 KTextEditor::Cursor realCursor = virtualCursor;
1798 realCursor.setLine(view()->textFolding().visibleLineToLine(view()->textFolding().lineToVisibleLine(virtualCursor.line())));
1799
1800 int cursorViewLine = cache()->viewLine(realCursor);
1801
1802 int currentOffset = 0;
1803 int virtualLine = 0;
1804
1805 bool forwards = (offset > 0) ? true : false;
1806
1807 if (forwards) {
1808 currentOffset = cache()->lastViewLine(realCursor.line()) - cursorViewLine;
1809 if (offset <= currentOffset) {
1810 // the answer is on the same line
1811 KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine + offset);
1812 Q_ASSERT(thisLine.virtualLine() == (int)view()->textFolding().lineToVisibleLine(virtualCursor.line()));
1813 return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol());
1814 }
1815
1816 virtualLine = virtualCursor.line() + 1;
1817
1818 } else {
1819 offset = -offset;
1820 currentOffset = cursorViewLine;
1821 if (offset <= currentOffset) {
1822 // the answer is on the same line
1823 KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine - offset);
1824 Q_ASSERT(thisLine.virtualLine() == (int)view()->textFolding().lineToVisibleLine(virtualCursor.line()));
1825 return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol());
1826 }
1827
1828 virtualLine = virtualCursor.line() - 1;
1829 }
1830
1831 currentOffset++;
1832
1833 while (virtualLine >= 0 && virtualLine < (int)view()->textFolding().visibleLines()) {
1834 int realLine = view()->textFolding().visibleLineToLine(virtualLine);
1835 KateLineLayout *thisLine = cache()->line(realLine, virtualLine);
1836 if (!thisLine) {
1837 break;
1838 }
1839
1840 for (int i = 0; i < thisLine->viewLineCount(); ++i) {
1841 if (offset == currentOffset) {
1842 KateTextLayout thisViewLine = thisLine->viewLine(i);
1843
1844 if (!forwards) {
1845 // We actually want it the other way around
1846 int requiredViewLine = cache()->lastViewLine(realLine) - thisViewLine.viewLine();
1847 if (requiredViewLine != thisViewLine.viewLine()) {
1848 thisViewLine = thisLine->viewLine(requiredViewLine);
1849 }
1850 }
1851
1852 KTextEditor::Cursor ret(virtualLine, thisViewLine.startCol());
1853
1854 // keep column position
1855 if (keepX) {
1856 realCursor = renderer()->xToCursor(thisViewLine, m_preservedX, !view()->wrapCursor());
1857 ret.setColumn(realCursor.column());
1858 }
1859
1860 return ret;
1861 }
1862
1863 currentOffset++;
1864 }
1865
1866 if (forwards) {
1867 virtualLine++;
1868 } else {
1869 virtualLine--;
1870 }
1871 }
1872
1873 // Looks like we were asked for something a bit exotic.
1874 // Return the max/min valid position.
1875 if (forwards) {
1876 return KTextEditor::Cursor(view()->textFolding().visibleLines() - 1,
1877 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1)));
1878 } else {
1879 return KTextEditor::Cursor(0, 0);
1880 }
1881}
1882
1883int KateViewInternal::lineMaxCursorX(const KateTextLayout &range)
1884{
1885 if (!view()->wrapCursor() && !range.wrap()) {
1886 return INT_MAX;
1887 }
1888
1889 int maxX = range.endX();
1890
1891 if (maxX && range.wrap()) {
1892 QChar lastCharInLine = doc()->kateTextLine(range.line()).at(range.endCol() - 1);
1893 maxX -= renderer()->currentFontMetrics().horizontalAdvance(lastCharInLine);
1894 }
1895
1896 return maxX;
1897}
1898
1899int KateViewInternal::lineMaxCol(const KateTextLayout &range)
1900{
1901 int maxCol = range.endCol();
1902
1903 if (maxCol && range.wrap()) {
1904 maxCol--;
1905 }
1906
1907 return maxCol;
1908}
1909
1910void KateViewInternal::cursorUp(bool sel)
1911{
1912 if (!sel && view()->completionWidget()->isCompletionActive()) {
1913 view()->completionWidget()->cursorUp();
1914 return;
1915 }
1916
1917 m_preserveX = true;
1918
1919 // Handle Multi cursors
1920 // usually this will have only one element, rarely
1921 int i = 0;
1922 for (const auto &c : view()->m_secondaryCursors) {
1923 auto cursor = c.pos->toCursor();
1924 auto vCursor = toVirtualCursor(cursor);
1925
1926 // our cursor is in the first line already
1927 if (vCursor.line() == 0 && (!view()->dynWordWrap() || cache()->viewLine(cursor) == 0)) {
1928 auto newPos = moveCursorToLineStart(cursor);
1929 c.pos->setPosition(newPos);
1930 auto newVcursor = toVirtualCursor(newPos);
1931 if (sel) {
1932 updateSecondarySelection(i, cursor, newPos);
1933 } else {
1934 view()->clearSecondarySelections();
1935 }
1936 tagLines(newVcursor.line(), vCursor.line());
1937 i++;
1938 continue;
1939 }
1940
1941 auto lineLayout = currentLayout(cursor);
1942 Q_ASSERT(lineLayout.line() == cursor.line());
1943 Q_ASSERT(lineLayout.startCol() <= cursor.column());
1944 Q_ASSERT(!lineLayout.wrap() || cursor.column() < lineLayout.endCol());
1945
1946 KateTextLayout pRange = previousLayout(cursor);
1947
1948 KTextEditor::Cursor newPos = renderer()->xToCursor(pRange, m_preservedX, !view()->wrapCursor());
1949 c.pos->setPosition(newPos);
1950
1951 auto newVcursor = toVirtualCursor(newPos);
1952 if (sel) {
1953 updateSecondarySelection(i, cursor, newPos);
1954 } else {
1955 view()->clearSecondarySelections();
1956 }
1957 tagLines(newVcursor.line(), vCursor.line());
1958 i++;
1959 }
1960
1961 auto mergeOnFuncEnd = qScopeGuard([this, sel] {
1962 if (sel) {
1963 mergeSelections();
1964 } else {
1965 view()->ensureUniqueCursors();
1966 }
1967 });
1968
1969 // Normal single cursor
1970
1971 // assert that the display cursor is in visible lines
1972 Q_ASSERT(m_displayCursor.line() < view()->textFolding().visibleLines());
1973
1974 // move cursor to start of line, if we are at first line!
1975 if (m_displayCursor.line() == 0 && (!view()->dynWordWrap() || cache()->viewLine(m_cursor) == 0)) {
1976 auto newPos = moveCursorToLineStart(m_cursor);
1977 if (newPos.isValid()) {
1978 updateSelection(newPos, sel);
1979 updateCursor(newPos, true);
1980 }
1981 return;
1982 }
1983
1984 KateTextLayout thisLine = currentLayout(m_cursor);
1985 // This is not the first line because that is already simplified out above
1986 KateTextLayout pRange = previousLayout(m_cursor);
1987
1988 // Ensure we're in the right spot
1989 Q_ASSERT(m_cursor.line() == thisLine.line());
1990 Q_ASSERT(m_cursor.column() >= thisLine.startCol());
1991 Q_ASSERT(!thisLine.wrap() || m_cursor.column() < thisLine.endCol());
1992
1993 KTextEditor::Cursor c = renderer()->xToCursor(pRange, m_preservedX, !view()->wrapCursor());
1994
1995 updateSelection(c, sel);
1996 updateCursor(c);
1997}
1998
1999void KateViewInternal::cursorDown(bool sel)
2000{
2001 if (!sel && view()->completionWidget()->isCompletionActive()) {
2002 view()->completionWidget()->cursorDown();
2003 return;
2004 }
2005
2006 m_preserveX = true;
2007
2008 // Handle multiple cursors
2009 int i = 0;
2010 for (const auto &c : view()->m_secondaryCursors) {
2011 auto cursor = c.cursor();
2012 auto vCursor = toVirtualCursor(cursor);
2013
2014 // at end?
2015 if ((vCursor.line() >= view()->textFolding().visibleLines() - 1)
2016 && (!view()->dynWordWrap() || cache()->viewLine(cursor) == cache()->lastViewLine(cursor.line()))) {
2017 KTextEditor::Cursor newPos = moveCursorToLineEnd(cursor);
2018 c.pos->setPosition(newPos);
2019 if (sel) {
2020 updateSecondarySelection(i, cursor, newPos);
2021 } else {
2022 view()->clearSecondarySelections();
2023 }
2024 auto vNewPos = toVirtualCursor(newPos);
2025 tagLines(vCursor.line(), vNewPos.line());
2026 i++;
2027 continue;
2028 }
2029
2030 KateTextLayout thisLine = currentLayout(cursor);
2031 // This is not the last line because that is already simplified out above
2032 KateTextLayout nRange = nextLayout(cursor);
2033
2034 // Ensure we're in the right spot
2035 Q_ASSERT((cursor.line() == thisLine.line()) && (cursor.column() >= thisLine.startCol()) && (!thisLine.wrap() || cursor.column() < thisLine.endCol()));
2036 KTextEditor::Cursor newPos = renderer()->xToCursor(nRange, m_preservedX, !view()->wrapCursor());
2037
2038 c.pos->setPosition(newPos);
2039 if (sel) {
2040 updateSecondarySelection(i, cursor, newPos);
2041 } else {
2042 view()->clearSecondarySelections();
2043 }
2044 auto vNewPos = toVirtualCursor(newPos);
2045 tagLines(vCursor.line(), vNewPos.line());
2046 i++;
2047 }
2048 auto mergeOnFuncEnd = qScopeGuard([this, sel] {
2049 if (sel) {
2050 mergeSelections();
2051 } else {
2052 view()->ensureUniqueCursors();
2053 }
2054 });
2055
2056 // Handle normal single cursor
2057
2058 // move cursor to end of line, if we are at last line!
2059 if ((m_displayCursor.line() >= view()->textFolding().visibleLines() - 1)
2060 && (!view()->dynWordWrap() || cache()->viewLine(m_cursor) == cache()->lastViewLine(m_cursor.line()))) {
2061 auto newPos = moveCursorToLineEnd(m_cursor);
2062 if (newPos.isValid()) {
2063 updateSelection(newPos, sel);
2064 updateCursor(newPos);
2065 }
2066 return;
2067 }
2068
2069 KateTextLayout thisLine = currentLayout(m_cursor);
2070 // This is not the last line because that is already simplified out above
2071 KateTextLayout nRange = nextLayout(m_cursor);
2072
2073 // Ensure we're in the right spot
2074 Q_ASSERT((m_cursor.line() == thisLine.line()) && (m_cursor.column() >= thisLine.startCol()) && (!thisLine.wrap() || m_cursor.column() < thisLine.endCol()));
2075
2076 KTextEditor::Cursor c = renderer()->xToCursor(nRange, m_preservedX, !view()->wrapCursor());
2077
2078 updateSelection(c, sel);
2079 updateCursor(c);
2080}
2081
2082void KateViewInternal::cursorToMatchingBracket(bool sel)
2083{
2084 KTextEditor::Cursor c = findMatchingBracket();
2085
2086 if (c.isValid()) {
2087 updateSelection(c, sel);
2088 updateCursor(c);
2089 }
2090}
2091
2092void KateViewInternal::topOfView(bool sel)
2093{
2094 view()->clearSecondaryCursors();
2095 KTextEditor::Cursor c = viewLineOffset(startPos(), m_minLinesVisible);
2096 updateSelection(toRealCursor(c), sel);
2097 updateCursor(toRealCursor(c));
2098}
2099
2100void KateViewInternal::bottomOfView(bool sel)
2101{
2102 view()->clearSecondaryCursors();
2103 KTextEditor::Cursor c = viewLineOffset(endPos(), -m_minLinesVisible);
2104 updateSelection(toRealCursor(c), sel);
2105 updateCursor(toRealCursor(c));
2106}
2107
2108// lines is the offset to scroll by
2109void KateViewInternal::scrollLines(int lines, bool sel)
2110{
2111 KTextEditor::Cursor c = viewLineOffset(m_displayCursor, lines, true);
2112
2113 // Fix the virtual cursor -> real cursor
2114 c.setLine(view()->textFolding().visibleLineToLine(c.line()));
2115
2116 updateSelection(c, sel);
2117 updateCursor(c);
2118}
2119
2120// This is a bit misleading... it's asking for the view to be scrolled, not the cursor
2121void KateViewInternal::scrollUp()
2122{
2123 KTextEditor::Cursor newPos = viewLineOffset(startPos(), -1);
2124 scrollPos(newPos);
2125}
2126
2127void KateViewInternal::scrollDown()
2128{
2129 KTextEditor::Cursor newPos = viewLineOffset(startPos(), 1);
2130 scrollPos(newPos);
2131}
2132
2133void KateViewInternal::setAutoCenterLines(int viewLines, bool updateView)
2134{
2135 m_autoCenterLines = viewLines;
2136 m_minLinesVisible = qMin(int((linesDisplayed() - 1) / 2), m_autoCenterLines);
2137 if (updateView) {
2138 KateViewInternal::updateView();
2139 }
2140}
2141
2142void KateViewInternal::pageUp(bool sel, bool half)
2143{
2144 if (view()->isCompletionActive()) {
2145 view()->completionWidget()->pageUp();
2146 return;
2147 }
2148 view()->clearSecondaryCursors();
2149
2150 // jump back to where the cursor is, otherwise it is super
2151 // slow to call cache()->displayViewLine
2152 if (!view()->visibleRange().contains(m_displayCursor)) {
2153 scrollLines(m_displayCursor.line());
2154 }
2155
2156 // remember the view line and x pos
2157 int viewLine = cache()->displayViewLine(m_displayCursor);
2158 bool atTop = startPos().atStartOfDocument();
2159
2160 // Adjust for an auto-centering cursor
2161 int lineadj = m_minLinesVisible;
2162
2163 int linesToScroll;
2164 if (!half) {
2165 linesToScroll = -qMax((linesDisplayed() - 1) - lineadj, 0);
2166 } else {
2167 linesToScroll = -qMax((linesDisplayed() / 2 - 1) - lineadj, 0);
2168 }
2169
2170 m_preserveX = true;
2171
2172 if (!doc()->pageUpDownMovesCursor() && !atTop) {
2173 KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1);
2174 scrollPos(newStartPos);
2175
2176 // put the cursor back approximately where it was
2177 KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true));
2178
2179 KateTextLayout newLine = cache()->textLayout(newPos);
2180
2181 newPos = renderer()->xToCursor(newLine, m_preservedX, !view()->wrapCursor());
2182
2183 m_preserveX = true;
2184 updateSelection(newPos, sel);
2185 updateCursor(newPos);
2186
2187 } else {
2188 scrollLines(linesToScroll, sel);
2189 }
2190}
2191
2192void KateViewInternal::pageDown(bool sel, bool half)
2193{
2194 if (view()->isCompletionActive()) {
2195 view()->completionWidget()->pageDown();
2196 return;
2197 }
2198
2199 view()->clearSecondaryCursors();
2200
2201 // jump back to where the cursor is, otherwise it is super
2202 // slow to call cache()->displayViewLine
2203 if (!view()->visibleRange().contains(m_displayCursor)) {
2204 scrollLines(m_displayCursor.line());
2205 }
2206
2207 // remember the view line
2208 int viewLine = cache()->displayViewLine(m_displayCursor);
2209 bool atEnd = startPos() >= m_cachedMaxStartPos;
2210
2211 // Adjust for an auto-centering cursor
2212 int lineadj = m_minLinesVisible;
2213
2214 int linesToScroll;
2215 if (!half) {
2216 linesToScroll = qMax((linesDisplayed() - 1) - lineadj, 0);
2217 } else {
2218 linesToScroll = qMax((linesDisplayed() / 2 - 1) - lineadj, 0);
2219 }
2220
2221 m_preserveX = true;
2222
2223 if (!doc()->pageUpDownMovesCursor() && !atEnd) {
2224 KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1);
2225 scrollPos(newStartPos);
2226
2227 // put the cursor back approximately where it was
2228 KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true));
2229
2230 KateTextLayout newLine = cache()->textLayout(newPos);
2231
2232 newPos = renderer()->xToCursor(newLine, m_preservedX, !view()->wrapCursor());
2233
2234 m_preserveX = true;
2235 updateSelection(newPos, sel);
2236 updateCursor(newPos);
2237
2238 } else {
2239 scrollLines(linesToScroll, sel);
2240 }
2241}
2242
2243int KateViewInternal::maxLen(int startLine)
2244{
2245 Q_ASSERT(!view()->dynWordWrap());
2246
2247 int displayLines = (view()->height() / renderer()->lineHeight()) + 1;
2248
2249 int maxLen = 0;
2250
2251 for (int z = 0; z < displayLines; z++) {
2252 int virtualLine = startLine + z;
2253
2254 if (virtualLine < 0 || virtualLine >= (int)view()->textFolding().visibleLines()) {
2255 break;
2256 }
2257
2258 const KateLineLayout *line = cache()->line(view()->textFolding().visibleLineToLine(virtualLine));
2259 if (!line) {
2260 continue;
2261 }
2262
2263 maxLen = qMax(maxLen, line->width());
2264 }
2265
2266 return maxLen;
2267}
2268
2269bool KateViewInternal::columnScrollingPossible()
2270{
2271 return !view()->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maximum() > 0);
2272}
2273
2274bool KateViewInternal::lineScrollingPossible()
2275{
2276 return m_lineScroll->minimum() != m_lineScroll->maximum();
2277}
2278
2279void KateViewInternal::top(bool sel)
2280{
2281 KTextEditor::Cursor newCursor(0, 0);
2282
2283 newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !view()->wrapCursor());
2284
2285 view()->clearSecondaryCursors();
2286 updateSelection(newCursor, sel);
2287 updateCursor(newCursor);
2288}
2289
2290void KateViewInternal::bottom(bool sel)
2291{
2292 KTextEditor::Cursor newCursor(doc()->lastLine(), 0);
2293
2294 newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !view()->wrapCursor());
2295
2296 view()->clearSecondaryCursors();
2297 updateSelection(newCursor, sel);
2298 updateCursor(newCursor);
2299}
2300
2301void KateViewInternal::top_home(bool sel)
2302{
2303 if (view()->isCompletionActive()) {
2304 view()->completionWidget()->top();
2305 return;
2306 }
2307
2308 view()->clearSecondaryCursors();
2309 KTextEditor::Cursor c(0, 0);
2310 updateSelection(c, sel);
2311 updateCursor(c);
2312}
2313
2314void KateViewInternal::bottom_end(bool sel)
2315{
2316 if (view()->isCompletionActive()) {
2317 view()->completionWidget()->bottom();
2318 return;
2319 }
2320
2321 view()->clearSecondaryCursors();
2322 KTextEditor::Cursor c(doc()->lastLine(), doc()->lineLength(doc()->lastLine()));
2323 updateSelection(c, sel);
2324 updateCursor(c);
2325}
2326
2327void KateViewInternal::updateSecondarySelection(int cursorIdx, KTextEditor::Cursor old, KTextEditor::Cursor newPos) const
2328{
2329 if (m_selectionMode != SelectionMode::Default) {
2330 view()->clearSecondaryCursors();
2331 }
2332
2333 auto &secondaryCursors = view()->m_secondaryCursors;
2334 if (secondaryCursors.empty()) {
2335 qWarning() << "Invalid updateSecondarySelection with no secondaryCursors";
2336 return;
2337 }
2338 Q_ASSERT(secondaryCursors.size() > (size_t)cursorIdx);
2339
2340 auto &cursor = secondaryCursors[cursorIdx];
2341 if (cursor.cursor() != newPos) {
2342 qWarning() << "Unexpected different cursor at cursorIdx" << cursorIdx << "found" << cursor.cursor() << "looking for: " << newPos;
2343 return;
2344 }
2345
2346 if (cursor.range) {
2347 Q_ASSERT(cursor.anchor.isValid());
2348 cursor.range->setRange(cursor.anchor, newPos);
2349 } else {
2350 cursor.range.reset(view()->newSecondarySelectionRange({old, newPos}));
2351 cursor.anchor = old;
2352 }
2353}
2354
2355void KateViewInternal::updateSelection(const KTextEditor::Cursor _newCursor, bool keepSel)
2356{
2357 KTextEditor::Cursor newCursor = _newCursor;
2358 if (keepSel) {
2359 if (!view()->selection()
2360 || (m_selectAnchor.line() == -1)
2361 // don't kill the selection if we have a persistent selection and
2362 // the cursor is inside or at the boundaries of the selected area
2363 || (view()->config()->persistentSelection()
2364 && !(view()->selectionRange().contains(m_cursor) || view()->selectionRange().boundaryAtCursor(m_cursor)))) {
2365 m_selectAnchor = m_cursor;
2366 setSelection(KTextEditor::Range(m_cursor, newCursor));
2367 } else {
2368 bool doSelect = true;
2369 switch (m_selectionMode) {
2370 case Word: {
2371 // Restore selStartCached if needed. It gets nuked by
2372 // viewSelectionChanged if we drag the selection into non-existence,
2373 // which can legitimately happen if a shift+DC selection is unable to
2374 // set a "proper" (i.e. non-empty) cached selection, e.g. because the
2375 // start was on something that isn't a word. Word select mode relies
2376 // on the cached selection being set properly, even if it is empty
2377 // (i.e. selStartCached == selEndCached).
2378 if (!m_selectionCached.isValid()) {
2379 m_selectionCached.setStart(m_selectionCached.end());
2380 }
2381
2382 int c;
2383 if (newCursor > m_selectionCached.start()) {
2384 m_selectAnchor = m_selectionCached.start();
2385
2386 Kate::TextLine l = doc()->kateTextLine(newCursor.line());
2387
2388 c = newCursor.column();
2389 if (c > 0 && doc()->highlight()->isInWord(l.at(c - 1))) {
2390 for (; c < l.length(); c++) {
2391 if (!doc()->highlight()->isInWord(l.at(c))) {
2392 break;
2393 }
2394 }
2395 }
2396
2397 newCursor.setColumn(c);
2398 } else if (newCursor < m_selectionCached.start()) {
2399 m_selectAnchor = m_selectionCached.end();
2400
2401 Kate::TextLine l = doc()->kateTextLine(newCursor.line());
2402
2403 c = newCursor.column();
2404 if (c > 0 && c < doc()->lineLength(newCursor.line()) && doc()->highlight()->isInWord(l.at(c))
2405 && doc()->highlight()->isInWord(l.at(c - 1))) {
2406 for (c -= 2; c >= 0; c--) {
2407 if (!doc()->highlight()->isInWord(l.at(c))) {
2408 break;
2409 }
2410 }
2411 newCursor.setColumn(c + 1);
2412 }
2413 } else {
2414 doSelect = false;
2415 }
2416
2417 } break;
2418 case Line:
2419 if (!m_selectionCached.isValid()) {
2420 m_selectionCached = KTextEditor::Range(endLine(), 0, endLine(), 0);
2421 }
2422 if (newCursor.line() > m_selectionCached.start().line()) {
2423 if (newCursor.line() + 1 >= doc()->lines()) {
2424 newCursor.setColumn(doc()->line(newCursor.line()).length());
2425 } else {
2426 newCursor.setPosition(newCursor.line() + 1, 0);
2427 }
2428 // Grow to include the entire line
2429 m_selectAnchor = m_selectionCached.start();
2430 m_selectAnchor.setColumn(0);
2431 } else if (newCursor.line() < m_selectionCached.start().line()) {
2432 newCursor.setColumn(0);
2433 // Grow to include entire line
2434 m_selectAnchor = m_selectionCached.end();
2435 if (m_selectAnchor.column() > 0) {
2436 if (m_selectAnchor.line() + 1 >= doc()->lines()) {
2437 m_selectAnchor.setColumn(doc()->line(newCursor.line()).length());
2438 } else {
2439 m_selectAnchor.setPosition(m_selectAnchor.line() + 1, 0);
2440 }
2441 }
2442 } else { // same line, ignore
2443 doSelect = false;
2444 }
2445 break;
2446 case Mouse: {
2447 if (!m_selectionCached.isValid()) {
2448 break;
2449 }
2450
2451 if (newCursor > m_selectionCached.end()) {
2452 m_selectAnchor = m_selectionCached.start();
2453 } else if (newCursor < m_selectionCached.start()) {
2454 m_selectAnchor = m_selectionCached.end();
2455 } else {
2456 doSelect = false;
2457 }
2458 } break;
2459 default: /* nothing special to do */;
2460 }
2461
2462 if (doSelect) {
2463 setSelection(KTextEditor::Range(m_selectAnchor, newCursor));
2464 } else if (m_selectionCached.isValid()) { // we have a cached selection, so we restore that
2465 setSelection(m_selectionCached);
2466 }
2467 }
2468
2469 m_selChangedByUser = true;
2470 } else if (!view()->config()->persistentSelection()) {
2471 view()->clearSelection();
2472
2473 m_selectionCached = KTextEditor::Range::invalid();
2474 m_selectAnchor = KTextEditor::Cursor::invalid();
2475 }
2476
2477#ifndef QT_NO_ACCESSIBILITY
2478// FIXME KF5
2479// QAccessibleTextSelectionEvent ev(this, /* selection start, selection end*/);
2480// QAccessible::updateAccessibility(&ev);
2481#endif
2482}
2483
2484void KateViewInternal::setSelection(KTextEditor::Range range)
2485{
2486 disconnect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged);
2487 view()->setSelection(range);
2488 connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged);
2489}
2490
2491void KateViewInternal::moveCursorToSelectionEdge(bool scroll)
2492{
2493 if (!view()->selection()) {
2494 return;
2495 }
2496
2497 int tmp = m_minLinesVisible;
2498 m_minLinesVisible = 0;
2499
2500 if (view()->selectionRange().start() < m_selectAnchor) {
2501 updateCursor(view()->selectionRange().start(), false, false, false, scroll);
2502 } else {
2503 updateCursor(view()->selectionRange().end(), false, false, false, scroll);
2504 }
2505 if (!scroll) {
2506 m_madeVisible = false;
2507 }
2508
2509 m_minLinesVisible = tmp;
2510}
2511
2512KTextEditor::Range KateViewInternal::findMatchingFoldingMarker(const KTextEditor::Cursor currentCursorPos,
2513 const KSyntaxHighlighting::FoldingRegion foldingRegion,
2514 const int maxLines)
2515{
2516 const int direction = (foldingRegion.type() == KSyntaxHighlighting::FoldingRegion::Begin) ? 1 : -1;
2517 int foldCounter = 0;
2518 int lineCounter = 0;
2519 const auto foldMarkers = m_view->doc()->buffer().computeFoldings(currentCursorPos.line());
2520
2521 // searching a end folding marker? go left to right
2522 // otherwise, go right to left
2523 long i = direction == 1 ? 0 : (long)foldMarkers.size() - 1;
2524
2525 // For the first line, we start considering the first folding after the cursor
2526 for (; i >= 0 && i < (long)foldMarkers.size(); i += direction) {
2527 if ((foldMarkers[i].offset - currentCursorPos.column()) * direction > 0 && foldMarkers[i].foldingRegion.id() == foldingRegion.id()) {
2528 if (foldMarkers[i].foldingRegion.type() == foldingRegion.type()) {
2529 foldCounter += 1;
2530 } else if (foldCounter > 0) {
2531 foldCounter -= 1;
2532 } else if (foldCounter == 0) {
2533 return KTextEditor::Range(currentCursorPos.line(),
2534 getStartOffset(direction, foldMarkers[i].offset, foldMarkers[i].length),
2535 currentCursorPos.line(),
2536 getEndOffset(direction, foldMarkers[i].offset, foldMarkers[i].length));
2537 }
2538 }
2539 }
2540
2541 // for the other lines
2542 int currentLine = currentCursorPos.line() + direction;
2543 for (; currentLine >= 0 && currentLine < m_view->doc()->lines() && lineCounter < maxLines; currentLine += direction) {
2544 // update line attributes
2545 const auto foldMarkers = m_view->doc()->buffer().computeFoldings(currentLine);
2546 i = direction == 1 ? 0 : (long)foldMarkers.size() - 1;
2547
2548 // iterate through the markers
2549 for (; i >= 0 && i < (long)foldMarkers.size(); i += direction) {
2550 if (foldMarkers[i].foldingRegion.id() == foldingRegion.id()) {
2551 if (foldMarkers[i].foldingRegion.type() == foldingRegion.type()) {
2552 foldCounter += 1;
2553 } else if (foldCounter != 0) {
2554 foldCounter -= 1;
2555 } else if (foldCounter == 0) {
2556 return KTextEditor::Range(currentLine,
2557 getStartOffset(direction, foldMarkers[i].offset, foldMarkers[i].length),
2558 currentLine,
2559 getEndOffset(direction, foldMarkers[i].offset, foldMarkers[i].length));
2560 }
2561 }
2562 }
2563 lineCounter += 1;
2564 }
2565
2566 // got out of loop, no matching folding found
2567 // returns a invalid folding range
2569}
2570
2571void KateViewInternal::updateFoldingMarkersHighlighting()
2572{
2573 const auto foldings = m_view->doc()->buffer().computeFoldings(m_cursor.line());
2574 for (unsigned long i = 0; i < foldings.size(); i++) {
2575 // 1 -> left to right, the current folding is start type
2576 // -1 -> right to left, the current folding is end type
2577 int direction = (foldings[i].foldingRegion.type() == KSyntaxHighlighting::FoldingRegion::Begin) ? 1 : -1;
2578
2579 int startOffset = getStartOffset(-direction, foldings[i].offset, foldings[i].length);
2580 int endOffset = getEndOffset(-direction, foldings[i].offset, foldings[i].length);
2581
2582 if (m_cursor.column() >= startOffset && m_cursor.column() <= endOffset) {
2583 const auto foldingMarkerMatch = findMatchingFoldingMarker(KTextEditor::Cursor(m_cursor.line(), m_cursor.column()), foldings[i].foldingRegion, 2000);
2584
2585 if (!foldingMarkerMatch.isValid()) {
2586 break;
2587 }
2588
2589 // set fmStart to Opening Folding Marker and fmEnd to Ending Folding Marker
2590 if (direction == 1) {
2591 m_fmStart->setRange(KTextEditor::Range(m_cursor.line(), startOffset, m_cursor.line(), endOffset));
2592 m_fmEnd->setRange(foldingMarkerMatch);
2593 } else {
2594 m_fmStart->setRange(foldingMarkerMatch);
2595 m_fmEnd->setRange(KTextEditor::Range(m_cursor.line(), startOffset, m_cursor.line(), endOffset));
2596 }
2597
2599 fill->setBackground(view()->rendererConfig()->highlightedBracketColor());
2600
2601 m_fmStart->setAttribute(fill);
2602 m_fmEnd->setAttribute(fill);
2603 return;
2604 }
2605 }
2606 m_fmStart->setRange(KTextEditor::Range::invalid());
2607 m_fmEnd->setRange(KTextEditor::Range::invalid());
2608}
2609
2610void KateViewInternal::updateSecondaryCursors(const QVarLengthArray<CursorPair, 16> &cursors, bool sel)
2611{
2612 if (sel) {
2613 for (int i = 0; i < cursors.size(); ++i) {
2614 updateSecondarySelection(i, cursors[i].oldPos, cursors[i].newPos);
2615 }
2616 if (!cursors.isEmpty()) {
2617 mergeSelections();
2618 }
2619 } else {
2620 view()->clearSecondarySelections();
2621 }
2622
2623 QVarLengthArray<int> linesToUpdate;
2624 for (auto cpair : cursors) {
2625 linesToUpdate.push_back(cpair.oldPos.line());
2626 linesToUpdate.push_back(cpair.newPos.line());
2627 }
2628 // Remove duplicate stuff
2629 std::sort(linesToUpdate.begin(), linesToUpdate.end());
2630 auto it = std::unique(linesToUpdate.begin(), linesToUpdate.end());
2631
2632 // Collapse ranges to avoid extra work
2633 using Range = std::pair<int, int>; // start, length
2635 int prev = 0;
2636 for (auto i = linesToUpdate.begin(); i != it; ++i) {
2637 int curLine = *i;
2638 if (!ranges.isEmpty() && prev + 1 == curLine) {
2639 ranges.back().second++;
2640 } else {
2641 ranges.push_back({curLine, 0});
2642 }
2643 prev = curLine;
2644 }
2645
2646 for (auto range : ranges) {
2647 int startLine = range.first;
2648 int endLine = range.first + range.second;
2649 tagLines(startLine, endLine, /*realLines=*/true);
2650 }
2651 updateDirty();
2652}
2653
2654void KateViewInternal::mergeSelections()
2655{
2656 using SecondaryCursor = KTextEditor::ViewPrivate::SecondaryCursor;
2657 using Range = KTextEditor::Range;
2658 auto doMerge = [](Range newRange, SecondaryCursor &a, SecondaryCursor &b) {
2659 a.range->setRange(newRange);
2660
2661 b.pos.reset();
2662 b.range.reset();
2663 };
2664
2665 auto &cursors = view()->m_secondaryCursors;
2666 for (auto it = cursors.begin(); it != cursors.end(); ++it) {
2667 if (!it->range)
2668 continue;
2669 if (it + 1 == cursors.end()) {
2670 break;
2671 }
2672
2673 auto n = std::next(it);
2674 if (/*!it->range || */ !n->range) {
2675 continue;
2676 }
2677
2678 auto curRange = it->range->toRange();
2679 auto nextRange = n->range->toRange();
2680 if (!curRange.overlaps(nextRange)) {
2681 continue;
2682 }
2683
2684 bool isLefSel = it->cursor() < it->anchor;
2685
2686 // if the ranges overlap we expand the next range
2687 // to include the current one. This allows us to
2688 // check all ranges for overlap in one go
2689 auto curPos = it->cursor();
2690 nextRange.expandToRange(curRange);
2691 if (isLefSel) {
2692 // in left selection our next cursor
2693 // is ahead somewhere, we want to keep
2694 // the smallest position
2695 n->pos->setPosition(curPos);
2696 n->anchor = qMax(n->anchor, it->anchor);
2697 } else {
2698 n->anchor = qMin(n->anchor, it->anchor);
2699 }
2700 doMerge(nextRange, *n, *it);
2701 }
2702
2703 if (view()->selection()) {
2704 auto primarySel = view()->m_selection.toRange();
2705 auto primCursor = cursorPosition();
2706 for (auto it = cursors.begin(); it != cursors.end(); ++it) {
2707 // If range is valid, we merge selection into primary
2708 // Otherwise if cursor is inside primary selection, it
2709 // is removed
2710 auto curRange = it->range ? it->range->toRange() : Range::invalid();
2711 if (curRange.isValid() && primarySel.overlaps(curRange)) {
2712 primarySel.expandToRange(curRange);
2713 const bool isLeftSel = it->cursor() < it->anchor;
2714 const bool isPrimaryLeftSel = primCursor < m_selectAnchor;
2715 const bool sameDirection = isLeftSel == isPrimaryLeftSel;
2716
2717 if (sameDirection) {
2718 if (isLeftSel) {
2719 if (it->cursor() < primCursor) {
2720 updateCursor(it->cursor());
2721 }
2722 m_selectAnchor = qMax(m_selectAnchor, it->anchor);
2723 } else {
2724 if (it->cursor() > primCursor) {
2725 updateCursor(it->cursor());
2726 }
2727 m_selectAnchor = qMin(m_selectAnchor, it->anchor);
2728 }
2729 } else {
2730 updateCursor(it->anchor);
2731 if (isPrimaryLeftSel) {
2732 m_selectAnchor = qMax(m_selectAnchor, it->anchor);
2733 } else {
2734 m_selectAnchor = qMin(m_selectAnchor, it->anchor);
2735 }
2736 }
2737
2738 setSelection(primarySel);
2739 it->pos.reset();
2740 it->range.reset();
2741 } else if (it->pos && !curRange.isValid() && primarySel.boundaryAtCursor(it->cursor())) {
2742 // the cursor doesn't have selection and it is on the boundary of primary selection
2743 it->pos.reset();
2744 } else if (it->pos) {
2745 // This only needs to be done for primary selection
2746 // because remember, mouse selection is always with
2747 // primary cursor
2748 auto pos = it->cursor();
2749 if (!primarySel.boundaryAtCursor(pos) && primarySel.contains(pos)) {
2750 it->pos.reset();
2751 }
2752 }
2753 }
2754 }
2755
2756 cursors.erase(std::remove_if(cursors.begin(),
2757 cursors.end(),
2758 [](const SecondaryCursor &c) {
2759 return !c.pos.get();
2760 }),
2761 cursors.end());
2762}
2763
2764void KateViewInternal::updateCursor(const KTextEditor::Cursor newCursor, bool force, bool center, bool calledExternally, bool scroll)
2765{
2766 if (!force && (m_cursor.toCursor() == newCursor)) {
2767 m_displayCursor = toVirtualCursor(newCursor);
2768 if (scroll && !m_madeVisible && m_view == doc()->activeView()) {
2769 // unfold if required
2770 view()->textFolding().ensureLineIsVisible(newCursor.line());
2771
2772 makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally);
2773 }
2774
2775 return;
2776 }
2777
2778 if (m_cursor.line() != newCursor.line()) {
2779 m_leftBorder->updateForCursorLineChange();
2780 }
2781
2782 // unfold if required
2783 view()->textFolding().ensureLineIsVisible(newCursor.line());
2784
2785 KTextEditor::Cursor oldDisplayCursor = m_displayCursor;
2786
2787 m_displayCursor = toVirtualCursor(newCursor);
2788 m_cursor.setPosition(newCursor);
2789
2790 if (m_view == doc()->activeView() && scroll) {
2791 makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally);
2792 }
2793
2794 updateBracketMarks();
2795
2796 updateFoldingMarkersHighlighting();
2797
2798 // avoid double work, tagLine => tagLines => not that cheap, much more costly than to compare 2 ints
2799 tagLine(oldDisplayCursor);
2800 if (oldDisplayCursor.line() != m_displayCursor.line()) {
2801 tagLine(m_displayCursor);
2802 }
2803
2805
2806 if (m_cursorTimer.isActive()) {
2808 m_cursorTimer.start(QApplication::cursorFlashTime() / 2);
2809 }
2810 renderer()->setDrawCaret(true);
2811 }
2812
2813 // Remember the maximum X position if requested
2814 if (m_preserveX) {
2815 m_preserveX = false;
2816 } else {
2817 m_preservedX = renderer()->cursorToX(cache()->textLayout(m_cursor), m_cursor, !view()->wrapCursor());
2818 }
2819
2820 // qCDebug(LOG_KTE) << "m_preservedX: " << m_preservedX << " (was "<< oldmaxx << "), m_cursorX: " << m_cursorX;
2821 // qCDebug(LOG_KTE) << "Cursor now located at real " << cursor.line << "," << cursor.col << ", virtual " << m_displayCursor.line << ", " <<
2822 // m_displayCursor.col << "; Top is " << startLine() << ", " << startPos().col;
2823
2824 cursorMoved();
2825
2826 updateDirty(); // paintText(0, 0, width(), height(), true);
2827
2828 Q_EMIT view()->cursorPositionChanged(m_view, m_cursor);
2829}
2830
2831void KateViewInternal::updateBracketMarkAttributes()
2832{
2834 bracketFill->setBackground(view()->rendererConfig()->highlightedBracketColor());
2835 bracketFill->setBackgroundFillWhitespace(false);
2836 if (QFontInfo(renderer()->currentFont()).fixedPitch()) {
2837 // make font bold only for fixed fonts, otherwise text jumps around
2838 bracketFill->setFontBold();
2839 }
2840
2841 m_bmStart->setAttribute(bracketFill);
2842 m_bmEnd->setAttribute(bracketFill);
2843
2844 if (view()->rendererConfig()->showWholeBracketExpression()) {
2846 expressionFill->setBackground(view()->rendererConfig()->highlightedBracketColor());
2847 expressionFill->setBackgroundFillWhitespace(false);
2848
2849 m_bm->setAttribute(expressionFill);
2850 } else {
2851 m_bm->setAttribute(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()));
2852 }
2853}
2854
2855void KateViewInternal::updateBracketMarks()
2856{
2857 // add some limit to this, this is really endless on big files without limit
2858 const int maxLines = 5000;
2859 const KTextEditor::Range newRange = doc()->findMatchingBracket(m_cursor, maxLines);
2860
2861 // new range valid, then set ranges to it
2862 if (newRange.isValid()) {
2863 if (m_bm->toRange() == newRange) {
2864 // hide preview as it now (probably) blocks the top of the view
2865 hideBracketMatchPreview();
2866 return;
2867 }
2868
2869 // modify full range
2870 m_bm->setRange(newRange);
2871
2872 // modify start and end ranges
2873 m_bmStart->setRange(KTextEditor::Range(m_bm->start(), KTextEditor::Cursor(m_bm->start().line(), m_bm->start().column() + 1)));
2874 m_bmEnd->setRange(KTextEditor::Range(m_bm->end(), KTextEditor::Cursor(m_bm->end().line(), m_bm->end().column() + 1)));
2875
2876 // show preview of the matching bracket's line
2877 if (m_view->config()->value(KateViewConfig::ShowBracketMatchPreview).toBool()) {
2878 showBracketMatchPreview();
2879 }
2880
2881 // flash matching bracket
2882 if (!m_view->rendererConfig()->animateBracketMatching()) {
2883 return;
2884 }
2885
2886 const KTextEditor::Cursor flashPos = (m_cursor == m_bmStart->start() || m_cursor == m_bmStart->end()) ? m_bmEnd->start() : m_bm->start();
2887 if (flashPos != m_bmLastFlashPos->toCursor()) {
2888 m_bmLastFlashPos->setPosition(flashPos);
2889
2890 KTextEditor::Attribute::Ptr attribute = attributeAt(flashPos);
2891 attribute->setBackground(view()->rendererConfig()->highlightedBracketColor());
2892 if (m_bmStart->attribute()->fontBold()) {
2893 attribute->setFontBold(true);
2894 }
2895
2896 flashChar(flashPos, attribute);
2897 }
2898 return;
2899 }
2900
2901 // new range was invalid
2902 m_bm->setRange(KTextEditor::Range::invalid());
2903 m_bmStart->setRange(KTextEditor::Range::invalid());
2904 m_bmEnd->setRange(KTextEditor::Range::invalid());
2905 m_bmLastFlashPos->setPosition(KTextEditor::Cursor::invalid());
2906 hideBracketMatchPreview();
2907}
2908
2909bool KateViewInternal::tagLine(const KTextEditor::Cursor virtualCursor)
2910{
2911 // we had here some special case handling for one line, it was just randomly wrong for dyn. word wrapped stuff => use the generic function
2912 return tagLines(virtualCursor, virtualCursor, false);
2913}
2914
2915bool KateViewInternal::tagLines(int start, int end, bool realLines)
2916{
2917 return tagLines(KTextEditor::Cursor(start, 0), KTextEditor::Cursor(end, -1), realLines);
2918}
2919
2920bool KateViewInternal::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors)
2921{
2922 if (realCursors) {
2923 cache()->relayoutLines(start.line(), end.line());
2924
2925 // qCDebug(LOG_KTE)<<"realLines is true";
2926 start = toVirtualCursor(start);
2927 end = toVirtualCursor(end);
2928
2929 } else {
2930 cache()->relayoutLines(toRealCursor(start).line(), toRealCursor(end).line());
2931 }
2932
2933 if (end.line() < startLine()) {
2934 // qCDebug(LOG_KTE)<<"end<startLine";
2935 return false;
2936 }
2937 // Used to be > endLine(), but cache may not be valid when checking, so use a
2938 // less optimal but still adequate approximation (potential overestimation but minimal performance difference)
2939 if (start.line() > startLine() + cache()->viewCacheLineCount()) {
2940 // qCDebug(LOG_KTE)<<"start> endLine"<<start<<" "<<(endLine());
2941 return false;
2942 }
2943
2944 cache()->updateViewCache(startPos());
2945
2946 // qCDebug(LOG_KTE) << "tagLines( [" << start << "], [" << end << "] )";
2947
2948 bool ret = false;
2949
2950 for (int z = 0; z < cache()->viewCacheLineCount(); z++) {
2951 KateTextLayout &line = cache()->viewLine(z);
2952 if ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1))
2953 && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1)))) {
2954 ret = true;
2955 break;
2956 // qCDebug(LOG_KTE) << "Tagged line " << line.line();
2957 }
2958 }
2959
2960 // full update of border it we have indentation based hl and show the markers always
2961 if (!m_view->config()->showFoldingOnHoverOnly() && doc()->highlight() && doc()->highlight()->foldingIndentationSensitive()) {
2962 // not the default setting, can be optimized if ever some issue
2963 m_leftBorder->update();
2964 } else if (!view()->dynWordWrap()) {
2965 int y = lineToY(start.line());
2966 // FIXME is this enough for when multiple lines are deleted
2967 int h = (end.line() - start.line() + 2) * renderer()->lineHeight();
2968 if (end.line() >= view()->textFolding().visibleLines() - 1) {
2969 h = height();
2970 }
2971
2972 m_leftBorder->update(0, y, m_leftBorder->width(), h);
2973 } else {
2974 // FIXME Do we get enough good info in editRemoveText to optimize this more?
2975 // bool justTagged = false;
2976 for (int z = 0; z < cache()->viewCacheLineCount(); z++) {
2977 KateTextLayout &line = cache()->viewLine(z);
2978 if (!line.isValid()
2979 || ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1))
2980 && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1))))) {
2981 // justTagged = true;
2982 m_leftBorder->update(0, z * renderer()->lineHeight(), m_leftBorder->width(), m_leftBorder->height());
2983 break;
2984 }
2985 /*else if (justTagged)
2986 {
2987 justTagged = false;
2988 leftBorder->update (0, z * doc()->viewFont.fontHeight, leftBorder->width(), doc()->viewFont.fontHeight);
2989 break;
2990 }*/
2991 }
2992 }
2993
2994 return ret;
2995}
2996
2997bool KateViewInternal::tagRange(KTextEditor::Range range, bool realCursors)
2998{
2999 return tagLines(range.start(), range.end(), realCursors);
3000}
3001
3002void KateViewInternal::tagAll()
3003{
3004 // clear the cache...
3005 cache()->clear();
3006
3007 m_leftBorder->updateFont();
3008 m_leftBorder->update();
3009}
3010
3011void KateViewInternal::paintCursor()
3012{
3013 QVarLengthArray<int, 64> updatedLines;
3014 if (tagLine(m_displayCursor)) {
3015 updatedLines.push_back(m_displayCursor.line());
3016 }
3017
3018 const int s = view()->firstDisplayedLine();
3019 const int e = view()->lastDisplayedLine();
3020 for (const auto &c : view()->m_secondaryCursors) {
3021 auto p = c.cursor();
3022 if (p.line() >= s - 1 && p.line() <= e + 1 && !updatedLines.contains(p.line())) {
3023 updatedLines.push_back(p.line());
3024 tagLines(p, p, true);
3025 }
3026 }
3027
3028 if (!updatedLines.isEmpty()) {
3029 updateDirty(); // paintText (0,0,width(), height(), true);
3030 }
3031}
3032
3033KTextEditor::Cursor KateViewInternal::cursorForPoint(QPoint p)
3034{
3035 KateTextLayout thisLine = yToKateTextLayout(p.y());
3037
3038 if (!thisLine.isValid()) { // probably user clicked below the last line -> use the last line
3039 thisLine = cache()->textLayout(doc()->lines() - 1, -1);
3040 }
3041
3042 c = renderer()->xToCursor(thisLine, startX() + p.x(), !view()->wrapCursor());
3043
3044 if (c.line() < 0 || c.line() >= doc()->lines()) {
3046 }
3047
3048 // loop over all notes and check if the point is inside it
3049 const auto inlineNotes = view()->inlineNotes(c.line());
3050 p = mapToGlobal(p);
3051 for (const auto &note : inlineNotes) {
3052 auto noteCursor = note.m_position;
3053 // we are not interested in notes that are past the end or at 0
3054 if (note.m_position.column() >= doc()->lineLength(c.line()) || note.m_position.column() == 0) {
3055 continue;
3056 }
3057 // an inline note is "part" of a char i.e., char width is increased so that
3058 // an inline note can be painted beside it. So we need to find the actual
3059 // char width
3060 const auto caretWidth = renderer()->caretStyle() == KTextEditor::caretStyles::Line ? 2. : 0.;
3061 const auto width = KTextEditor::InlineNote(note).width() + caretWidth;
3062 const auto charWidth = renderer()->currentFontMetrics().horizontalAdvance(doc()->characterAt(noteCursor));
3063 const auto halfCharWidth = (charWidth / 2);
3064 // we leave the first half of the char width. If the user has clicked in the first half, let it go the
3065 // previous column.
3066 const auto totalWidth = width + halfCharWidth;
3067 auto start = mapToGlobal(cursorToCoordinate(noteCursor, true, false));
3068 start = start - QPoint(totalWidth, 0);
3069 QRect r(start, QSize{(int)halfCharWidth, renderer()->lineHeight()});
3070 if (r.contains(p)) {
3071 c = noteCursor;
3072 break;
3073 }
3074 }
3075
3076 return c;
3077}
3078
3079// Point in content coordinates
3080void KateViewInternal::placeCursor(const QPoint &p, bool keepSelection, bool updateSelection)
3081{
3082 KTextEditor::Cursor c = cursorForPoint(p);
3083 if (!c.isValid()) {
3084 return;
3085 }
3086
3087 if (updateSelection) {
3088 KateViewInternal::updateSelection(c, keepSelection);
3089 }
3090
3091 int tmp = m_minLinesVisible;
3092 m_minLinesVisible = 0;
3093 updateCursor(c);
3094 m_minLinesVisible = tmp;
3095
3096 if (updateSelection && keepSelection) {
3097 moveCursorToSelectionEdge();
3098 }
3099}
3100
3101// Point in content coordinates
3102bool KateViewInternal::isTargetSelected(const QPoint &p)
3103{
3104 const KateTextLayout &thisLine = yToKateTextLayout(p.y());
3105 if (!thisLine.isValid()) {
3106 return false;
3107 }
3108
3109 return view()->cursorSelected(renderer()->xToCursor(thisLine, startX() + p.x(), !view()->wrapCursor()));
3110}
3111
3112// BEGIN EVENT HANDLING STUFF
3113
3114bool KateViewInternal::eventFilter(QObject *obj, QEvent *e)
3115{
3116 switch (e->type()) {
3117 case QEvent::ChildAdded:
3118 case QEvent::ChildRemoved: {
3119 QChildEvent *c = static_cast<QChildEvent *>(e);
3120 if (c->added()) {
3121 c->child()->installEventFilter(this);
3122
3123 } else if (c->removed()) {
3124 c->child()->removeEventFilter(this);
3125 }
3126 } break;
3127
3129 QKeyEvent *k = static_cast<QKeyEvent *>(e);
3130
3131 if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) {
3132 if (!view()->m_secondaryCursors.empty()) {
3133 view()->clearSecondaryCursors();
3134 k->accept();
3135 return true;
3136 }
3137
3138 if (view()->isCompletionActive()) {
3139 view()->abortCompletion();
3140 k->accept();
3141 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "aborting completion";
3142 return true;
3143 } else if (view()->bottomViewBar()->barWidgetVisible()) {
3144 view()->bottomViewBar()->hideCurrentBarWidget();
3145 k->accept();
3146 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "closing view bar";
3147 return true;
3148 } else if (!view()->config()->persistentSelection() && view()->selection()) {
3149 m_currentInputMode->clearSelection();
3150 k->accept();
3151 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "clearing selection";
3152 return true;
3153 }
3154 }
3155
3156 if (k->modifiers() == Qt::AltModifier && view()->isCompletionActive()) {
3157 if (view()->completionWidget()->handleShortcutOverride(k)) {
3158 k->accept();
3159 return true;
3160 }
3161 }
3162
3163 if (m_currentInputMode->stealKey(k)) {
3164 k->accept();
3165 return true;
3166 }
3167
3168 // CompletionReplayer.replay only gets called when a Ctrl-Space gets to InsertViMode::handleKeyPress
3169 // Workaround for BUG: 334032 (https://bugs.kde.org/show_bug.cgi?id=334032)
3170 if (k->key() == Qt::Key_Space && k->modifiers() == Qt::ControlModifier) {
3171 keyPressEvent(k);
3172 if (k->isAccepted()) {
3173 return true;
3174 }
3175 }
3176 } break;
3177
3178 case QEvent::KeyPress: {
3179 QKeyEvent *k = static_cast<QKeyEvent *>(e);
3180
3181 // Override all other single key shortcuts which do not use a modifier other than Shift
3182 if (obj == this && (!k->modifiers() || k->modifiers() == Qt::ShiftModifier)) {
3183 keyPressEvent(k);
3184 if (k->isAccepted()) {
3185 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "using keystroke";
3186 return true;
3187 }
3188 }
3189
3190 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "ignoring";
3191 } break;
3192
3193 case QEvent::DragMove: {
3194 QPoint currentPoint = ((QDragMoveEvent *)e)->position().toPoint();
3195
3196 QRect doNotScrollRegion(s_scrollMargin, s_scrollMargin, width() - s_scrollMargin * 2, height() - s_scrollMargin * 2);
3197
3198 if (!doNotScrollRegion.contains(currentPoint)) {
3199 startDragScroll();
3200 // Keep sending move events
3201 ((QDragMoveEvent *)e)->accept(QRect(0, 0, 0, 0));
3202 }
3203
3204 dragMoveEvent((QDragMoveEvent *)e);
3205 } break;
3206
3207 case QEvent::DragLeave:
3208 // happens only when pressing ESC while dragging
3209 stopDragScroll();
3210 break;
3211
3213 hideBracketMatchPreview();
3214 break;
3215
3216 case QEvent::ScrollPrepare: {
3217 QScrollPrepareEvent *s = static_cast<QScrollPrepareEvent *>(e);
3218 scrollPrepareEvent(s);
3219 }
3220 return true;
3221
3222 case QEvent::Scroll: {
3223 QScrollEvent *s = static_cast<QScrollEvent *>(e);
3224 scrollEvent(s);
3225 }
3226 return true;
3227
3228 default:
3229 break;
3230 }
3231
3232 return QWidget::eventFilter(obj, e);
3233}
3234
3235void KateViewInternal::keyPressEvent(QKeyEvent *e)
3236{
3237 m_shiftKeyPressed = e->modifiers() & Qt::ShiftModifier;
3238 if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) {
3239 view()->emitNavigateLeft();
3240 e->setAccepted(true);
3241 return;
3242 }
3243 if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) {
3244 view()->emitNavigateRight();
3245 e->setAccepted(true);
3246 return;
3247 }
3248 if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) {
3249 view()->emitNavigateUp();
3250 e->setAccepted(true);
3251 return;
3252 }
3253 if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) {
3254 view()->emitNavigateDown();
3255 e->setAccepted(true);
3256 return;
3257 }
3258 if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) {
3259 view()->emitNavigateAccept();
3260 e->setAccepted(true);
3261 return;
3262 }
3263 if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) {
3264 view()->emitNavigateBack();
3265 e->setAccepted(true);
3266 return;
3267 }
3268
3269 if (e->key() == Qt::Key_Alt && view()->completionWidget()->isCompletionActive()) {
3270 view()->completionWidget()->toggleDocumentation();
3271 }
3272
3273 // Note: AND'ing with <Shift> is a quick hack to fix Key_Enter
3274 const int key = e->key() | (e->modifiers() & Qt::ShiftModifier);
3275
3276 if (m_currentInputMode->keyPress(e)) {
3277 return;
3278 }
3279
3280 if (!doc()->isReadWrite()) {
3281 e->ignore();
3282 return;
3283 }
3284
3285 if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) || key == (Qt::SHIFT | Qt::Key_Return).toCombined()
3286 || key == (Qt::SHIFT | Qt::Key_Enter).toCombined()) {
3287 view()->keyReturn();
3288 e->accept();
3289 return;
3290 }
3291
3292 if (key == Qt::Key_Backspace || key == (Qt::SHIFT | Qt::Key_Backspace).toCombined()) {
3293 // view()->backspace();
3294 e->accept();
3295
3296 return;
3297 }
3298
3299 if (key == Qt::Key_Tab || key == (Qt::SHIFT | Qt::Key_Backtab).toCombined() || key == Qt::Key_Backtab) {
3300 if (key == Qt::Key_Tab) {
3301 uint tabHandling = doc()->config()->tabHandling();
3302 // convert tabSmart into tabInsertsTab or tabIndents:
3303 if (tabHandling == KateDocumentConfig::tabSmart) {
3304 // multiple lines selected
3305 if (view()->selection() && !view()->selectionRange().onSingleLine() && !view()->blockSelection()) {
3306 tabHandling = KateDocumentConfig::tabIndents;
3307 }
3308
3309 // otherwise: take look at cursor position
3310 else {
3311 // if the cursor is at or before the first non-space character
3312 // or on an empty line,
3313 // Tab indents, otherwise it inserts a tab character.
3314 Kate::TextLine line = doc()->kateTextLine(m_cursor.line());
3315 int first = line.firstChar();
3316 if (first < 0 || m_cursor.column() <= first) {
3317 tabHandling = KateDocumentConfig::tabIndents;
3318 } else {
3319 tabHandling = KateDocumentConfig::tabInsertsTab;
3320 }
3321 }
3322 }
3323
3324 // either we just insert a tab or we convert that into an indent action
3325 if (tabHandling == KateDocumentConfig::tabInsertsTab) {
3326 doc()->typeChars(m_view, QStringLiteral("\t"));
3327 } else {
3328 doc()->editStart();
3329 for (const auto &c : std::as_const(m_view->m_secondaryCursors)) {
3330 auto cursor = c.cursor();
3331 doc()->indent(KTextEditor::Range(cursor.line(), 0, cursor.line(), 0), 1);
3332 }
3333
3334 doc()->indent(view()->selection() ? view()->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), 1);
3335 doc()->editEnd();
3336 }
3337
3338 e->accept();
3339
3340 return;
3341 } else if (doc()->config()->tabHandling() != KateDocumentConfig::tabInsertsTab) {
3342 // key == Qt::SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab
3343 doc()->indent(view()->selection() ? view()->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), -1);
3344 e->accept();
3345
3346 return;
3347 }
3348 }
3349
3350 if (isAcceptableInput(e)) {
3351 doc()->typeChars(m_view, e->text());
3352 e->accept();
3353 return;
3354 }
3355
3356 e->ignore();
3357}
3358
3359void KateViewInternal::keyReleaseEvent(QKeyEvent *e)
3360{
3361 if (m_shiftKeyPressed && (e->modifiers() & Qt::ShiftModifier) == 0) {
3362 m_shiftKeyPressed = false;
3363
3364 if (m_selChangedByUser) {
3365 if (view()->selection()) {
3366 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3367 }
3368
3369 m_selChangedByUser = false;
3370 }
3371 }
3372
3373 e->ignore();
3374 return;
3375}
3376
3377bool KateViewInternal::isAcceptableInput(const QKeyEvent *e)
3378{
3379 // reimplemented from QInputControl::isAcceptableInput()
3380
3381 const QString text = e->text();
3382 if (text.isEmpty()) {
3383 return false;
3384 }
3385
3386 const QChar c = text.at(0);
3387
3388 // Formatting characters such as ZWNJ, ZWJ, RLM, etc. This needs to go before the
3389 // next test, since CTRL+SHIFT is sometimes used to input it on Windows.
3390 // see bug 389796 (typing formatting characters such as ZWNJ)
3391 // and bug 396764 (typing soft-hyphens)
3392 if (c.category() == QChar::Other_Format) {
3393 return true;
3394 }
3395
3396 // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards
3398 return false;
3399 }
3400
3401 if (c.isPrint() || (c.category() == QChar::Other_PrivateUse)) {
3402 return true;
3403 };
3404
3405 if (c.isHighSurrogate() && text.size() > 1 && text.at(1).isLowSurrogate()) {
3406 return true;
3407 }
3408
3409 return false;
3410}
3411
3412void KateViewInternal::contextMenuEvent(QContextMenuEvent *e)
3413{
3414 // calculate where to show the context menu
3415
3416 QPoint p = e->pos();
3417
3418 if (e->reason() == QContextMenuEvent::Keyboard) {
3419 makeVisible(m_displayCursor, 0);
3420 p = cursorCoordinates(false);
3421 p.rx() -= startX();
3422 } else if (!view()->selection() || view()->config()->persistentSelection()) {
3423 placeCursor(e->pos());
3424 }
3425
3426 // show it
3427 QMenu *cm = view()->contextMenu();
3428 if (cm) {
3429 view()->spellingMenu()->prepareToBeShown(cm);
3430 cm->popup(mapToGlobal(p));
3431 e->accept();
3432 }
3433}
3434
3435void KateViewInternal::mousePressEvent(QMouseEvent *e)
3436{
3437 if (sendMouseEventToInputContext(e)) {
3438 return;
3439 }
3440
3441 // was an inline note clicked?
3442 const auto noteData = inlineNoteAt(e->globalPosition().toPoint());
3443 const KTextEditor::InlineNote note(noteData);
3444 if (note.position().isValid()) {
3445 note.provider()->inlineNoteActivated(noteData, e->button(), e->globalPosition().toPoint());
3446 return;
3447 }
3448
3449 commitPreedit();
3450
3451 // no -- continue with normal handling
3452 switch (e->button()) {
3453 case Qt::LeftButton:
3454
3455 m_selChangedByUser = false;
3456
3457 if (!view()->isMulticursorNotAllowed() && e->modifiers() == view()->config()->multiCursorModifiers()) {
3458 auto pos = cursorForPoint(e->pos());
3459 if (pos.isValid()) {
3460 view()->addSecondaryCursor(pos);
3461 e->accept();
3462 return;
3463 }
3464 } else {
3465 view()->clearSecondaryCursors();
3466 }
3467
3468 if (m_possibleTripleClick) {
3469 m_possibleTripleClick = false;
3470
3471 m_selectionMode = Line;
3472
3473 if (e->modifiers() & Qt::ShiftModifier) {
3474 updateSelection(m_cursor, true);
3475 } else {
3476 view()->selectLine(m_cursor);
3477 if (view()->selection()) {
3478 m_selectAnchor = view()->selectionRange().start();
3479 }
3480 }
3481
3482 if (view()->selection()) {
3483 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3484 }
3485
3486 // Keep the line at the select anchor selected during further
3487 // mouse selection
3488 if (m_selectAnchor.line() > view()->selectionRange().start().line()) {
3489 // Preserve the last selected line
3490 if (m_selectAnchor == view()->selectionRange().end() && m_selectAnchor.column() == 0) {
3491 m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line() - 1, 0));
3492 } else {
3493 m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), 0));
3494 }
3495 m_selectionCached.setEnd(view()->selectionRange().end());
3496 } else {
3497 // Preserve the first selected line
3498 m_selectionCached.setStart(view()->selectionRange().start());
3499 if (view()->selectionRange().end().line() > view()->selectionRange().start().line()) {
3500 m_selectionCached.setEnd(KTextEditor::Cursor(view()->selectionRange().start().line() + 1, 0));
3501 } else {
3502 m_selectionCached.setEnd(view()->selectionRange().end());
3503 }
3504 }
3505
3506 moveCursorToSelectionEdge();
3507
3508 m_scrollX = 0;
3509 m_scrollY = 0;
3510 m_scrollTimer.start(50);
3511
3512 e->accept();
3513 return;
3514 } else if (m_selectionMode == Default) {
3515 m_selectionMode = Mouse;
3516 }
3517
3518 // request the software keyboard, if any
3519 if (e->button() == Qt::LeftButton && qApp->autoSipEnabled()) {
3521 if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) {
3524 }
3525 }
3526
3527 if (e->modifiers() & Qt::ShiftModifier) {
3528 if (!m_selectAnchor.isValid()) {
3529 m_selectAnchor = m_cursor;
3530 }
3531 } else {
3532 m_selectionCached = KTextEditor::Range::invalid();
3533 }
3534
3535 if (view()->config()->textDragAndDrop() && !(e->modifiers() & Qt::ShiftModifier) && isTargetSelected(e->pos())) {
3536 m_dragInfo.state = diPending;
3537 m_dragInfo.start = e->pos();
3538 } else {
3539 m_dragInfo.state = diNone;
3540
3541 if (e->modifiers() & Qt::ShiftModifier) {
3542 placeCursor(e->pos(), true, false);
3543 if (m_selectionCached.start().isValid()) {
3544 if (m_cursor.toCursor() < m_selectionCached.start()) {
3545 m_selectAnchor = m_selectionCached.end();
3546 } else {
3547 m_selectAnchor = m_selectionCached.start();
3548 }
3549 }
3550
3551 // update selection and do potential clipboard update, see bug 443642
3552 setSelection(KTextEditor::Range(m_selectAnchor, m_cursor));
3553 if (view()->selection()) {
3554 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3555 }
3556 } else {
3557 placeCursor(e->pos());
3558 }
3559
3560 m_scrollX = 0;
3561 m_scrollY = 0;
3562
3563 m_scrollTimer.start(50);
3564 }
3565
3566 e->accept();
3567 break;
3568
3569 case Qt::RightButton:
3570 if (e->pos().x() == 0) {
3571 // Special handling for folding by right click
3572 placeCursor(e->pos());
3573 e->accept();
3574 }
3575 break;
3576
3577 default:
3578 e->ignore();
3579 break;
3580 }
3581}
3582
3583void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e)
3584{
3585 if (sendMouseEventToInputContext(e)) {
3586 return;
3587 }
3588 if (e->button() == Qt::LeftButton) {
3589 m_selectionMode = Word;
3590
3591 if (e->modifiers() & Qt::ShiftModifier) {
3592 // Now select the word under the select anchor
3593 int cs;
3594 int ce;
3595 Kate::TextLine l = doc()->kateTextLine(m_selectAnchor.line());
3596
3597 ce = m_selectAnchor.column();
3598 if (ce > 0 && doc()->highlight()->isInWord(l.at(ce))) {
3599 for (; ce < l.length(); ce++) {
3600 if (!doc()->highlight()->isInWord(l.at(ce))) {
3601 break;
3602 }
3603 }
3604 }
3605
3606 cs = m_selectAnchor.column() - 1;
3607 if (cs < doc()->lineLength(m_selectAnchor.line()) && doc()->highlight()->isInWord(l.at(cs))) {
3608 for (cs--; cs >= 0; cs--) {
3609 if (!doc()->highlight()->isInWord(l.at(cs))) {
3610 break;
3611 }
3612 }
3613 }
3614
3615 // ...and keep it selected
3616 if (cs + 1 < ce) {
3617 m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), cs + 1));
3618 m_selectionCached.setEnd(KTextEditor::Cursor(m_selectAnchor.line(), ce));
3619 } else {
3620 m_selectionCached.setStart(m_selectAnchor);
3621 m_selectionCached.setEnd(m_selectAnchor);
3622 }
3623 // Now word select to the mouse cursor
3624 placeCursor(e->pos(), true);
3625 } else {
3626 // first clear the selection, otherwise we run into bug #106402
3627 // ...and set the cursor position, for the same reason (otherwise there
3628 // are *other* idiosyncrasies we can't fix without reintroducing said
3629 // bug)
3630 // Parameters: don't redraw, and don't emit selectionChanged signal yet
3631 view()->clearSelection(false, false);
3632 placeCursor(e->pos());
3633 view()->selectWord(m_cursor);
3634 cursorToMatchingBracket(true);
3635
3636 if (view()->selection()) {
3637 m_selectAnchor = view()->selectionRange().start();
3638 m_selectionCached = view()->selectionRange();
3639 } else {
3640 m_selectAnchor = m_cursor;
3641 m_selectionCached = KTextEditor::Range(m_cursor, m_cursor);
3642 }
3643 }
3644
3645 // Move cursor to end (or beginning) of selected word
3646#ifndef Q_OS_MACOS
3647 if (view()->selection()) {
3648 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3649 }
3650#endif
3651
3652 moveCursorToSelectionEdge();
3653 m_possibleTripleClick = true;
3654 QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout()));
3655
3656 m_scrollX = 0;
3657 m_scrollY = 0;
3658
3659 m_scrollTimer.start(50);
3660
3661 e->accept();
3662 } else {
3663 e->ignore();
3664 }
3665}
3666
3667void KateViewInternal::tripleClickTimeout()
3668{
3669 m_possibleTripleClick = false;
3670}
3671
3672void KateViewInternal::beginSelectLine(const QPoint &pos)
3673{
3674 placeCursor(pos);
3675 m_possibleTripleClick = true; // set so subsequent mousePressEvent will select line
3676}
3677
3678void KateViewInternal::mouseReleaseEvent(QMouseEvent *e)
3679{
3680 if (sendMouseEventToInputContext(e)) {
3681 return;
3682 }
3683
3684 switch (e->button()) {
3685 case Qt::LeftButton:
3686 m_selectionMode = Default;
3687 // m_selectionCached.start().setLine( -1 );
3688
3689 if (m_selChangedByUser) {
3690 if (view()->selection()) {
3691 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection);
3692 }
3693 moveCursorToSelectionEdge();
3694
3695 m_selChangedByUser = false;
3696 }
3697
3698 if (m_dragInfo.state == diPending) {
3699 placeCursor(e->pos(), e->modifiers() & Qt::ShiftModifier);
3700 } else if (m_dragInfo.state == diNone) {
3701 m_scrollTimer.stop();
3702 }
3703
3704 m_dragInfo.state = diNone;
3705
3706 // merge any overlapping selections/cursors
3707 if (view()->selection() && !view()->m_secondaryCursors.empty()) {
3708 mergeSelections();
3709 }
3710
3711 e->accept();
3712 break;
3713
3714 case Qt::MiddleButton:
3715 if (!view()->config()->mousePasteAtCursorPosition()) {
3716 placeCursor(e->pos());
3717 }
3718
3719 if (doc()->isReadWrite()) {
3721 view()->paste(&clipboard);
3722 }
3723
3724 e->accept();
3725 break;
3726
3727 default:
3728 e->ignore();
3729 break;
3730 }
3731}
3732
3733void KateViewInternal::leaveEvent(QEvent *)
3734{
3735 m_textHintTimer.stop();
3736
3737 // fix bug 194452, scrolling keeps going if you scroll via mouse drag and press and other mouse
3738 // button outside the view area
3739 if (m_dragInfo.state == diNone) {
3740 m_scrollTimer.stop();
3741 }
3742
3743 hideBracketMatchPreview();
3744}
3745
3746KTextEditor::Cursor KateViewInternal::coordinatesToCursor(const QPoint &_coord, bool includeBorder) const
3747{
3748 QPoint coord(_coord);
3749
3751
3752 if (includeBorder) {
3753 coord.rx() -= m_leftBorder->width();
3754 }
3755 coord.rx() += startX();
3756
3757 const KateTextLayout &thisLine = yToKateTextLayout(coord.y());
3758 if (thisLine.isValid()) {
3759 ret = renderer()->xToCursor(thisLine, coord.x(), !view()->wrapCursor());
3760 }
3761
3762 if (ret.column() > view()->document()->lineLength(ret.line())) {
3763 // The cursor is beyond the end of the line; in that case the renderer
3764 // gives the index of the character behind the last one.
3766 }
3767
3768 return ret;
3769}
3770
3771void KateViewInternal::mouseMoveEvent(QMouseEvent *e)
3772{
3773 if (m_scroller->state() != QScroller::Inactive) {
3774 // Touchscreen is handled by scrollEvent()
3775 return;
3776 }
3777 KTextEditor::Cursor newPosition = coordinatesToCursor(e->pos(), false);
3778 if (newPosition != m_mouse) {
3779 m_mouse = newPosition;
3780 mouseMoved();
3781 }
3782
3783 if (e->buttons() == Qt::NoButton) {
3784 auto noteData = inlineNoteAt(e->globalPosition().toPoint());
3785 auto focusChanged = false;
3786 if (noteData.m_position.isValid()) {
3787 if (!m_activeInlineNote.m_position.isValid()) {
3788 // no active note -- focus in
3789 tagLine(noteData.m_position);
3790 focusChanged = true;
3791 noteData.m_underMouse = true;
3792 noteData.m_provider->inlineNoteFocusInEvent(KTextEditor::InlineNote(noteData), e->globalPosition().toPoint());
3793 m_activeInlineNote = noteData;
3794 } else {
3795 noteData.m_provider->inlineNoteMouseMoveEvent(KTextEditor::InlineNote(noteData), e->globalPosition().toPoint());
3796 }
3797 } else if (m_activeInlineNote.m_position.isValid()) {
3798 tagLine(m_activeInlineNote.m_position);
3799 focusChanged = true;
3800 m_activeInlineNote.m_underMouse = false;
3801 m_activeInlineNote.m_provider->inlineNoteFocusOutEvent(KTextEditor::InlineNote(m_activeInlineNote));
3802 m_activeInlineNote = {};
3803 }
3804 if (focusChanged) {
3805 // the note might change its appearance in reaction to the focus event
3806 updateDirty();
3807 }
3808 }
3809
3810 if (e->buttons() & Qt::LeftButton) {
3811 if (m_dragInfo.state == diPending) {
3812 // we had a mouse down, but haven't confirmed a drag yet
3813 // if the mouse has moved sufficiently, we will confirm
3814 QPoint p(e->pos() - m_dragInfo.start);
3815
3816 // we've left the drag square, we can start a real drag operation now
3818 doDrag();
3819 }
3820
3821 return;
3822 } else if (m_dragInfo.state == diDragging) {
3823 // Don't do anything after a canceled drag until the user lets go of
3824 // the mouse button!
3825 return;
3826 }
3827
3828 m_mouseX = e->position().x();
3829 m_mouseY = e->position().y();
3830
3831 m_scrollX = 0;
3832 m_scrollY = 0;
3833 int d = renderer()->lineHeight();
3834
3835 if (m_mouseX < 0) {
3836 m_scrollX = -d;
3837 }
3838
3839 if (m_mouseX > width()) {
3840 m_scrollX = d;
3841 }
3842
3843 if (m_mouseY < 0) {
3844 m_mouseY = 0;
3845 m_scrollY = -d;
3846 }
3847
3848 if (m_mouseY > height()) {
3849 m_mouseY = height();
3850 m_scrollY = d;
3851 }
3852
3853 if (!m_scrollY) {
3854 // We are on top of line number area and dragging
3855 // Since we never get negative y, ensure to scroll up
3856 // a bit otherwise we get stuck on the topview line
3857 if (!m_mouseY) {
3858 m_scrollY -= d;
3859 }
3860 placeCursor(QPoint(m_mouseX, m_mouseY), true);
3861 }
3862 } else {
3863 if (view()->config()->textDragAndDrop() && isTargetSelected(e->pos())) {
3864 // mouse is over selected text. indicate that the text is draggable by setting
3865 // the arrow cursor as other Qt text editing widgets do
3866 if (m_mouseCursor != Qt::ArrowCursor) {
3867 m_mouseCursor = Qt::ArrowCursor;
3868 setCursor(m_mouseCursor);
3869 }
3870 } else {
3871 // normal text cursor
3872 if (m_mouseCursor != Qt::IBeamCursor) {
3873 m_mouseCursor = Qt::IBeamCursor;
3874 setCursor(m_mouseCursor);
3875 }
3876 }
3877 // We need to check whether the mouse position is actually within the widget,
3878 // because other widgets like the icon border forward their events to this,
3879 // and we will create invalid text hint requests if we don't check
3880 if (textHintsEnabled() && geometry().contains(parentWidget()->mapFromGlobal(e->globalPosition()).toPoint())) {
3881 if (QToolTip::isVisible()) {
3883 }
3884 m_textHintTimer.start(m_textHintDelay);
3885 m_textHintPos = e->pos();
3886 }
3887 }
3888}
3889
3890void KateViewInternal::updateDirty()
3891{
3892 const int h = renderer()->lineHeight();
3893
3894 int currentRectStart = -1;
3895 int currentRectEnd = -1;
3896
3897 QRegion updateRegion;
3898
3899 {
3900 for (int i = 0; i < cache()->viewCacheLineCount(); ++i) {
3901 if (cache()->viewLine(i).isDirty()) {
3902 if (currentRectStart == -1) {
3903 currentRectStart = h * i;
3904 currentRectEnd = h;
3905 } else {
3906 currentRectEnd += h;
3907 }
3908
3909 } else if (currentRectStart != -1) {
3910 updateRegion += QRect(0, currentRectStart, width(), currentRectEnd);
3911 currentRectStart = -1;
3912 currentRectEnd = -1;
3913 }
3914 }
3915 }
3916
3917 if (currentRectStart != -1) {
3918 updateRegion += QRect(0, currentRectStart, width(), currentRectEnd);
3919 }
3920
3921 if (!updateRegion.isEmpty()) {
3922 if (debugPainting) {
3923 qCDebug(LOG_KTE) << "Update dirty region " << updateRegion;
3924 }
3925 update(updateRegion);
3926 }
3927}
3928
3929void KateViewInternal::hideEvent(QHideEvent *e)
3930{
3931 Q_UNUSED(e);
3932 if (view()->isCompletionActive()) {
3933 view()->completionWidget()->abortCompletion();
3934 }
3935}
3936
3937void KateViewInternal::paintEvent(QPaintEvent *e)
3938{
3939 if (debugPainting) {
3940 qCDebug(LOG_KTE) << "GOT PAINT EVENT: Region" << e->region();
3941 }
3942
3943 const QRect &unionRect = e->rect();
3944
3945 int xStart = startX() + unionRect.x();
3946 int xEnd = xStart + unionRect.width();
3947 uint h = renderer()->lineHeight();
3948 uint startz = (unionRect.y() / h);
3949 uint endz = startz + 1 + (unionRect.height() / h);
3950 uint lineRangesSize = cache()->viewCacheLineCount();
3951 const KTextEditor::Cursor pos = m_cursor;
3952
3953 QPainter paint(this);
3954
3955 // THIS IS ULTRA EVIL AND ADDS STRANGE RENDERING ARTIFACTS WITH SCALING!!!!
3956 // SEE BUG https://bugreports.qt.io/browse/QTBUG-66036
3957 // paint.setRenderHints(QPainter::TextAntialiasing);
3958
3959 paint.save();
3960
3961 renderer()->setCaretStyle(m_currentInputMode->caretStyle());
3962 renderer()->setShowTabs(doc()->config()->showTabs());
3963 renderer()->setShowSpaces(doc()->config()->showSpaces());
3964 renderer()->updateMarkerSize();
3965
3966 // paint line by line
3967 // this includes parts that span areas without real lines
3968 // translate to first line to paint
3969 paint.translate(unionRect.x(), startz * h);
3970 for (uint z = startz; z <= endz; z++) {
3971 // paint regions without lines mapped to
3972 if ((z >= lineRangesSize) || (cache()->viewLine(z).line() == -1)) {
3973 if (!(z >= lineRangesSize)) {
3974 cache()->viewLine(z).setDirty(false);
3975 }
3976 paint.fillRect(0, 0, unionRect.width(), h, m_view->rendererConfig()->backgroundColor());
3977 }
3978
3979 // paint text lines
3980 else {
3981 // If viewLine() returns non-zero, then a document line was split
3982 // in several visual lines, and we're trying to paint visual line
3983 // that is not the first. In that case, this line was already
3984 // painted previously, since KateRenderer::paintTextLine paints
3985 // all visual lines.
3986 //
3987 // Except if we're at the start of the region that needs to
3988 // be painted -- when no previous calls to paintTextLine were made.
3989 KateTextLayout &thisLine = cache()->viewLine(z);
3990 if (!thisLine.viewLine() || z == startz) {
3991 // paint our line
3992 // set clipping region to only paint the relevant parts
3993 paint.save();
3994 const int thisLineTop = h * thisLine.viewLine();
3995 paint.translate(QPoint(0, -thisLineTop));
3996
3997 // compute rect for line, fill the stuff
3998 // important: as we allow some ARGB colors for other stuff, it is REALLY important to fill the full range once!
3999 const QRectF lineRect(0, 0, unionRect.width(), h * thisLine.kateLineLayout()->viewLineCount());
4000 paint.fillRect(lineRect, m_view->rendererConfig()->backgroundColor());
4001
4002 // THIS IS ULTRA EVIL AND ADDS STRANGE RENDERING ARTIFACTS WITH SCALING!!!!
4003 // SEE BUG https://bugreports.qt.io/browse/QTBUG-66036
4004 // => using a QRectF solves the cut of 1 pixel, the same call with QRect does create artifacts!
4005 paint.setClipRect(lineRect);
4006
4007 // QTextLayout::draw does not take into account QPainter's viewport, so it will try to render
4008 // lines outside visible bounds. This causes a significant slowdown when a very long line
4009 // dynamically broken into multiple lines. To avoid this, an explicit text clip rect is set.
4010 const QRect textClipRect{xStart, thisLineTop, xEnd - xStart, height()};
4011
4012 renderer()->paintTextLine(paint, thisLine.kateLineLayout(), xStart, xEnd, textClipRect.toRectF(), &pos);
4013 paint.restore();
4014
4015 // line painted, reset and state + mark line as non-dirty
4016 thisLine.setDirty(false);
4017 }
4018 }
4019
4020 // translate to next line
4021 paint.translate(0, h);
4022 }
4023
4024 paint.restore();
4025
4026 if (m_textAnimation) {
4027 m_textAnimation->draw(paint);
4028 }
4029}
4030
4031void KateViewInternal::resizeEvent(QResizeEvent *e)
4032{
4033 bool expandedHorizontally = width() > e->oldSize().width();
4034 bool expandedVertically = height() > e->oldSize().height();
4035 bool heightChanged = height() != e->oldSize().height();
4036
4037 m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height());
4038 m_madeVisible = false;
4039
4040 // resize the bracket match preview
4041 if (m_bmPreview) {
4042 showBracketMatchPreview();
4043 }
4044
4045 if (heightChanged) {
4046 setAutoCenterLines(m_autoCenterLines, false);
4047 m_cachedMaxStartPos.setPosition(-1, -1);
4048 }
4049
4050 if (view()->dynWordWrap()) {
4051 bool dirtied = false;
4052
4053 for (int i = 0; i < cache()->viewCacheLineCount(); i++) {
4054 // find the first dirty line
4055 // the word wrap updateView algorithm is forced to check all lines after a dirty one
4056 KateTextLayout viewLine = cache()->viewLine(i);
4057
4058 if (viewLine.wrap() || viewLine.isRightToLeft() || viewLine.width() > width()) {
4059 dirtied = true;
4060 viewLine.setDirty();
4061 break;
4062 }
4063 }
4064
4065 if (dirtied || heightChanged) {
4066 updateView(true);
4067 m_leftBorder->update();
4068 }
4069 } else {
4070 updateView();
4071
4072 if (expandedHorizontally && startX() > 0) {
4073 scrollColumns(startX() - (width() - e->oldSize().width()));
4074 }
4075 }
4076
4077 if (width() < e->oldSize().width() && !view()->wrapCursor()) {
4078 // May have to restrain cursor to new smaller width...
4079 if (m_cursor.column() > doc()->lineLength(m_cursor.line())) {
4080 KateTextLayout thisLine = currentLayout(m_cursor);
4081
4082 KTextEditor::Cursor newCursor(m_cursor.line(),
4083 thisLine.endCol() + ((width() - thisLine.xOffset() - (thisLine.width() - startX())) / renderer()->spaceWidth()) - 1);
4084 if (newCursor.column() < m_cursor.column()) {
4085 updateCursor(newCursor);
4086 }
4087 }
4088 }
4089
4090 if (expandedVertically) {
4091 KTextEditor::Cursor max = maxStartPos();
4092 if (startPos() > max) {
4093 scrollPos(max);
4094 return; // already fired displayRangeChanged
4095 }
4096 }
4097 Q_EMIT view()->displayRangeChanged(m_view);
4098}
4099
4100void KateViewInternal::moveEvent(QMoveEvent *e)
4101{
4102 // move the bracket match preview to the new location
4103 if (e->pos() != e->oldPos() && m_bmPreview) {
4104 showBracketMatchPreview();
4105 }
4106
4108}
4109
4110void KateViewInternal::scrollTimeout()
4111{
4112 if (m_scrollX || m_scrollY) {
4113 const int scrollTo = startPos().line() + (m_scrollY / (int)renderer()->lineHeight());
4114 placeCursor(QPoint(m_mouseX, m_mouseY), true);
4115 scrollLines(scrollTo);
4116 }
4117}
4118
4119void KateViewInternal::cursorTimeout()
4120{
4121 if (!debugPainting && m_currentInputMode->blinkCaret()) {
4122 renderer()->setDrawCaret(!renderer()->drawCaret());
4123 paintCursor();
4124 }
4125}
4126
4127void KateViewInternal::textHintTimeout()
4128{
4129 m_textHintTimer.stop();
4130
4131 KTextEditor::Cursor c = coordinatesToCursor(m_textHintPos, false);
4132 if (!c.isValid()) {
4133 return;
4134 }
4135
4136 QStringList textHints;
4137 for (KTextEditor::TextHintProvider *const p : m_textHintProviders) {
4138 if (!p) {
4139 continue;
4140 }
4141
4142 const QString hint = p->textHint(m_view, c);
4143 if (!hint.isEmpty()) {
4144 textHints.append(hint);
4145 }
4146 }
4147
4148 if (!textHints.isEmpty()) {
4149 qCDebug(LOG_KTE) << "Hint text: " << textHints;
4150 QString hint;
4151 for (const QString &str : std::as_const(textHints)) {
4152 hint += QStringLiteral("<p>%1</p>").arg(str);
4153 }
4154 QPoint pos(startX() + m_textHintPos.x(), m_textHintPos.y());
4156 }
4157}
4158
4159void KateViewInternal::focusInEvent(QFocusEvent *)
4160{
4162 m_cursorTimer.start(QApplication::cursorFlashTime() / 2);
4163 }
4164
4165 paintCursor();
4166
4167 doc()->setActiveView(m_view);
4168
4169 // this will handle focus stuff in kateview
4170 view()->slotGotFocus();
4171}
4172
4173void KateViewInternal::focusOutEvent(QFocusEvent *)
4174{
4175 // if (view()->isCompletionActive())
4176 // view()->abortCompletion();
4177
4178 m_cursorTimer.stop();
4179 view()->renderer()->setDrawCaret(true);
4180 paintCursor();
4181
4182 m_textHintTimer.stop();
4183
4184 view()->slotLostFocus();
4185
4186 hideBracketMatchPreview();
4187}
4188
4189void KateViewInternal::doDrag()
4190{
4191 m_dragInfo.state = diDragging;
4192 m_dragInfo.dragObject = new QDrag(this);
4193 std::unique_ptr<QMimeData> mimeData(new QMimeData());
4194 mimeData->setText(view()->selectionText());
4195
4196 const auto startCur = view()->selectionRange().start();
4197 const auto endCur = view()->selectionRange().end();
4198 if (!startCur.isValid() || !endCur.isValid()) {
4199 return;
4200 }
4201
4202 int startLine = startCur.line();
4203 int endLine = endCur.line();
4204
4205 /**
4206 * Get real first and last visible line nos.
4207 * This is important as startLine() / endLine() are virtual and we can't use
4208 * them here
4209 */
4210 const int firstVisibleLine = view()->firstDisplayedLineInternal(KTextEditor::View::RealLine);
4211 const int lastVisibleLine = view()->lastDisplayedLineInternal(KTextEditor::View::RealLine);
4212
4213 // get visible selected lines
4214 for (int l = startLine; l <= endLine; ++l) {
4215 if (l >= firstVisibleLine) {
4216 break;
4217 }
4218 ++startLine;
4219 }
4220 for (int l = endLine; l >= startLine; --l) {
4221 if (l <= lastVisibleLine) {
4222 break;
4223 }
4224 --endLine;
4225 }
4226
4227 // calculate the height / width / scale
4228 int w = 0;
4229 int h = 0;
4230 const QFontMetricsF &fm = renderer()->currentFontMetrics();
4231 for (int l = startLine; l <= endLine; ++l) {
4232 w = std::max((int)fm.horizontalAdvance(doc()->line(l)), w);
4233 if (l == endLine) {
4234 h += renderer()->lineHeight() * (cache()->viewLine(endCur) + 1);
4235 } else {
4236 h += renderer()->lineHeight() * cache()->viewLineCount(l);
4237 }
4238 }
4239 qreal scale = h > m_view->height() / 2 ? 0.75 : 1.0;
4240
4241 // Calculate start x pos on start line
4242 int sX = 0;
4243 if (startLine == startCur.line()) {
4244 sX = renderer()->cursorToX(cache()->textLayout(startCur), startCur, false);
4245 }
4246
4247 // Calculate end x pos on end line
4248 int eX = 0;
4249 if (endLine == endCur.line()) {
4250 eX = renderer()->cursorToX(cache()->textLayout(endCur), endCur, false);
4251 }
4252
4253 // These are the occurunce highlights, the ones you get when you select a word
4254 // We clear them here so that they don't come up in the dragged pixmap
4255 // After we are done creating the pixmap, we restore them
4256 if (view()->selection()) {
4257 view()->clearHighlights();
4258 }
4259
4260 // Create a pixmap this selection
4261 const qreal dpr = devicePixelRatioF();
4262 QPixmap pixmap(w * dpr, h * dpr);
4263 if (!pixmap.isNull()) {
4264 pixmap.setDevicePixelRatio(dpr);
4265 pixmap.fill(Qt::transparent);
4266 renderer()->paintSelection(&pixmap, startLine, sX, endLine, eX, cache()->viewWidth(), scale);
4267
4268 if (view()->selection()) {
4269 // Tell the view to restore the highlights
4270 Q_EMIT view()->selectionChanged(view());
4271 }
4272 }
4273
4274 // Calculate position where pixmap will appear when user
4275 // starts dragging
4276 const int x = 0;
4277 /**
4278 * lineToVisibleLine() = real line => virtual line
4279 * This is necessary here because if there is a folding in the current
4280 * view lines, the y pos can be incorrect. So, we make sure to convert
4281 * it to virtual line before calculating y
4282 */
4283 const int y = lineToY(view()->m_textFolding.lineToVisibleLine(startLine));
4284 const QPoint pos = mapFromGlobal(QCursor::pos()) - QPoint(x, y);
4285
4286 m_dragInfo.dragObject->setPixmap(pixmap);
4287 m_dragInfo.dragObject->setHotSpot(pos);
4288 m_dragInfo.dragObject->setMimeData(mimeData.release());
4289 m_dragInfo.dragObject->exec(Qt::MoveAction | Qt::CopyAction);
4290}
4291
4292void KateViewInternal::dragEnterEvent(QDragEnterEvent *event)
4293{
4294 if (event->source() == this) {
4295 event->setDropAction(Qt::MoveAction);
4296 }
4297 event->setAccepted((event->mimeData()->hasText() && doc()->isReadWrite()) || event->mimeData()->hasUrls());
4298}
4299
4300void KateViewInternal::fixDropEvent(QDropEvent *event)
4301{
4302 if (event->source() != this) {
4303 event->setDropAction(Qt::CopyAction);
4304 } else {
4306#ifdef Q_WS_MAC
4307 if (event->modifiers() & Qt::AltModifier) {
4308 action = Qt::CopyAction;
4309 }
4310#else
4311 if (event->modifiers() & Qt::ControlModifier) {
4312 action = Qt::CopyAction;
4313 }
4314#endif
4315 event->setDropAction(action);
4316 }
4317}
4318
4319void KateViewInternal::dragMoveEvent(QDragMoveEvent *event)
4320{
4321 // track the cursor to the current drop location
4322 placeCursor(event->position().toPoint(), true, false);
4323
4324 // important: accept action to switch between copy and move mode
4325 // without this, the text will always be copied.
4326 fixDropEvent(event);
4327}
4328
4329void KateViewInternal::dropEvent(QDropEvent *event)
4330{
4331 // if we have urls, pass this event off to the hosting application
4332 if (event->mimeData()->hasUrls()) {
4333 Q_EMIT dropEventPass(event);
4334 return;
4335 }
4336
4337 if (event->mimeData()->hasText() && doc()->isReadWrite()) {
4338 const QString text = event->mimeData()->text();
4339 const bool blockMode = view()->blockSelection();
4340
4341 fixDropEvent(event);
4342
4343 // Remember where to paste/move...
4344 KTextEditor::Cursor targetCursor(m_cursor);
4345 // Use powerful MovingCursor to track our changes we may do
4346 std::unique_ptr<KTextEditor::MovingCursor> targetCursor2(doc()->newMovingCursor(m_cursor));
4347
4348 // As always need the BlockMode some special treatment
4349 const KTextEditor::Range selRange(view()->selectionRange());
4350 const KTextEditor::Cursor blockAdjust(selRange.numberOfLines(), selRange.columnWidth());
4351
4352 // Restore the cursor position before editStart(), so that it is correctly stored for the undo action
4353 if (event->dropAction() != Qt::CopyAction) {
4354 editSetCursor(selRange.end());
4355 } else {
4356 view()->clearSelection();
4357 }
4358
4359 // use one transaction
4360 doc()->editStart();
4361
4362 if (event->dropAction() != Qt::CopyAction) {
4363 view()->removeSelectedText();
4364 if (targetCursor2->toCursor() != targetCursor) {
4365 // Hm, multi line selection moved down, we need to adjust our dumb cursor
4366 targetCursor = targetCursor2->toCursor();
4367 }
4368 doc()->insertText(targetCursor2->toCursor(), text, blockMode);
4369
4370 } else {
4371 doc()->insertText(targetCursor, text, blockMode);
4372 }
4373
4374 if (blockMode) {
4375 setSelection(KTextEditor::Range(targetCursor, targetCursor + blockAdjust));
4376 editSetCursor(targetCursor + blockAdjust);
4377 } else {
4378 setSelection(KTextEditor::Range(targetCursor, targetCursor2->toCursor()));
4379 editSetCursor(targetCursor2->toCursor()); // Just to satisfy autotest
4380 }
4381
4382 doc()->editEnd();
4383
4384 event->acceptProposedAction();
4385 updateView();
4386 }
4387
4388 // finally finish drag and drop mode
4389 m_dragInfo.state = diNone;
4390 // important, because the eventFilter`s DragLeave does not occur
4391 stopDragScroll();
4392}
4393// END EVENT HANDLING STUFF
4394
4395void KateViewInternal::clear()
4396{
4397 m_startPos.setPosition(0, 0);
4398 m_displayCursor = KTextEditor::Cursor(0, 0);
4399 m_cursor.setPosition(0, 0);
4400 view()->clearSecondaryCursors();
4401 cache()->clear();
4402 updateView(true);
4403 m_lineScroll->updatePixmap();
4404}
4405
4406void KateViewInternal::wheelEvent(QWheelEvent *e)
4407{
4408 // check if this event should change the font size (Ctrl pressed, angle reported and not accidentally so)
4409 // Note: if detectZoomingEvent() doesn't unset the ControlModifier we'll get accelerated scrolling.
4410 if (m_zoomEventFilter->detectZoomingEvent(e)) {
4411 if (e->angleDelta().y() > 0) {
4412 slotIncFontSizes(qreal(e->angleDelta().y()) / (qreal)QWheelEvent::DefaultDeltasPerStep);
4413 } else if (e->angleDelta().y() < 0) {
4414 slotDecFontSizes(qreal(-e->angleDelta().y()) / (qreal)QWheelEvent::DefaultDeltasPerStep);
4415 }
4416
4417 // accept always and be done for zooming
4418 e->accept();
4419 return;
4420 }
4421
4422 // handle vertical scrolling via the scrollbar
4423 if (e->angleDelta().y() != 0) {
4424 // compute distance
4425 auto sign = m_lineScroll->invertedControls() ? -1 : 1;
4426 auto offset = sign * qreal(e->angleDelta().y()) / 120.0;
4427 if (e->modifiers() & Qt::ShiftModifier) {
4428 const auto pageStep = m_lineScroll->pageStep();
4429 offset = qBound(-pageStep, int(offset * pageStep), pageStep);
4430 } else {
4432 }
4433
4434 // handle accumulation
4435 m_accumulatedScroll += offset - int(offset);
4436 const auto extraAccumulated = int(m_accumulatedScroll);
4437 m_accumulatedScroll -= extraAccumulated;
4438
4439 // do scroll
4440 scrollViewLines(int(offset) + extraAccumulated);
4441 e->accept();
4442 }
4443
4444 // handle horizontal scrolling via the scrollbar
4445 if (e->angleDelta().x() != 0) {
4446 // if we have dyn word wrap, we should ignore the scroll events
4447 if (view()->dynWordWrap()) {
4448 e->accept();
4449 return;
4450 }
4451
4452 // if we scroll up/down we do not want to trigger unintended sideways scrolls
4453 if (qAbs(e->angleDelta().y()) > qAbs(e->angleDelta().x())) {
4454 e->accept();
4455 return;
4456 }
4457
4458 if (QApplication::sendEvent(m_columnScroll, e)) {
4459 e->accept();
4460 }
4461 }
4462
4463 // hide bracket match preview so that it won't linger while scrolling'
4464 hideBracketMatchPreview();
4465}
4466
4467void KateViewInternal::scrollPrepareEvent(QScrollPrepareEvent *event)
4468{
4469 int lineHeight = renderer()->lineHeight();
4470 event->setViewportSize(QSizeF(0.0, 0.0));
4471 event->setContentPosRange(QRectF(0.0, 0.0, 0.0, m_lineScroll->maximum() * lineHeight));
4472 event->setContentPos(QPointF(0.0, m_lineScroll->value() * lineHeight));
4473 event->accept();
4474}
4475
4476void KateViewInternal::scrollEvent(QScrollEvent *event)
4477{
4478 // FIXME Add horizontal scrolling, overscroll, scroll between lines, and word wrap awareness
4479 KTextEditor::Cursor newPos((int)event->contentPos().y() / renderer()->lineHeight(), 0);
4480 scrollPos(newPos);
4481 event->accept();
4482}
4483
4484void KateViewInternal::startDragScroll()
4485{
4486 if (!m_dragScrollTimer.isActive()) {
4487 m_dragScrollTimer.start(s_scrollTime);
4488 }
4489}
4490
4491void KateViewInternal::stopDragScroll()
4492{
4493 m_dragScrollTimer.stop();
4494 updateView();
4495}
4496
4497void KateViewInternal::doDragScroll()
4498{
4499 QPoint p = this->mapFromGlobal(QCursor::pos());
4500
4501 int dx = 0;
4502 int dy = 0;
4503 if (p.y() < s_scrollMargin) {
4504 dy = p.y() - s_scrollMargin;
4505 } else if (p.y() > height() - s_scrollMargin) {
4506 dy = s_scrollMargin - (height() - p.y());
4507 }
4508
4509 if (p.x() < s_scrollMargin) {
4510 dx = p.x() - s_scrollMargin;
4511 } else if (p.x() > width() - s_scrollMargin) {
4512 dx = s_scrollMargin - (width() - p.x());
4513 }
4514
4515 dy /= 4;
4516
4517 if (dy) {
4518 scrollLines(startLine() + dy);
4519 }
4520
4521 if (columnScrollingPossible() && dx) {
4522 scrollColumns(qMin(startX() + dx, m_columnScroll->maximum()));
4523 }
4524
4525 if (!dy && !dx) {
4526 stopDragScroll();
4527 }
4528}
4529
4530void KateViewInternal::registerTextHintProvider(KTextEditor::TextHintProvider *provider)
4531{
4532 if (std::find(m_textHintProviders.cbegin(), m_textHintProviders.cend(), provider) == m_textHintProviders.cend()) {
4533 m_textHintProviders.push_back(provider);
4534 }
4535
4536 // we have a client, so start timeout
4537 m_textHintTimer.start(m_textHintDelay);
4538}
4539
4540void KateViewInternal::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider)
4541{
4542 const auto it = std::find(m_textHintProviders.cbegin(), m_textHintProviders.cend(), provider);
4543 if (it != m_textHintProviders.cend()) {
4544 m_textHintProviders.erase(it);
4545 }
4546
4547 if (m_textHintProviders.empty()) {
4548 m_textHintTimer.stop();
4549 }
4550}
4551
4552void KateViewInternal::setTextHintDelay(int delay)
4553{
4554 if (delay <= 0) {
4555 m_textHintDelay = 200; // ms
4556 } else {
4557 m_textHintDelay = delay; // ms
4558 }
4559}
4560
4561int KateViewInternal::textHintDelay() const
4562{
4563 return m_textHintDelay;
4564}
4565
4566bool KateViewInternal::textHintsEnabled()
4567{
4568 return !m_textHintProviders.empty();
4569}
4570
4571// BEGIN EDIT STUFF
4572void KateViewInternal::editStart()
4573{
4574 editSessionNumber++;
4575
4576 if (editSessionNumber > 1) {
4577 return;
4578 }
4579
4580 editIsRunning = true;
4581 editOldCursor = m_cursor;
4582 editOldSelection = view()->selectionRange();
4583}
4584
4585void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom)
4586{
4587 if (editSessionNumber == 0) {
4588 return;
4589 }
4590
4591 editSessionNumber--;
4592
4593 if (editSessionNumber > 0) {
4594 return;
4595 }
4596
4597 // fix start position, might have moved from column 0
4598 // try to clever calculate the right start column for the tricky dyn word wrap case
4599 int col = 0;
4600 if (view()->dynWordWrap()) {
4601 if (KateLineLayout *layout = cache()->line(startLine())) {
4602 int index = layout->viewLineForColumn(startPos().column());
4603 if (index >= 0 && index < layout->viewLineCount()) {
4604 col = layout->viewLine(index).startCol();
4605 }
4606 }
4607 }
4608 m_startPos.setPosition(startLine(), col);
4609
4610 if (tagFrom && (editTagLineStart <= int(view()->textFolding().visibleLineToLine(startLine())))) {
4611 tagAll();
4612 } else {
4613 tagLines(editTagLineStart, tagFrom ? qMax(doc()->lastLine() + 1, editTagLineEnd) : editTagLineEnd, true);
4614 }
4615
4616 if (editOldCursor == m_cursor.toCursor()) {
4617 updateBracketMarks();
4618 }
4619
4620 updateView(true);
4621
4622 if (editOldCursor != m_cursor.toCursor() || m_view == doc()->activeView()) {
4623 // Only scroll the view to the cursor if the insertion happens at the cursor.
4624 // This might not be the case for e.g. collaborative editing, when a remote user
4625 // inserts text at a position not at the caret.
4626 if (m_cursor.line() >= editTagLineStart && m_cursor.line() <= editTagLineEnd) {
4627 m_madeVisible = false;
4628 updateCursor(m_cursor, true);
4629 }
4630 }
4631
4632 // selection changed?
4633 // fixes bug 316226
4634 if (editOldSelection != view()->selectionRange()
4635 || (editOldSelection.isValid() && !editOldSelection.isEmpty()
4636 && !(editTagLineStart > editOldSelection.end().line() && editTagLineEnd < editOldSelection.start().line()))) {
4637 Q_EMIT view()->selectionChanged(m_view);
4638 }
4639
4640 editIsRunning = false;
4641}
4642
4643void KateViewInternal::editSetCursor(const KTextEditor::Cursor _cursor)
4644{
4645 if (m_cursor.toCursor() != _cursor) {
4646 m_cursor.setPosition(_cursor);
4647 }
4648}
4649// END
4650
4651void KateViewInternal::viewSelectionChanged()
4652{
4653 if (!view()->selection()) {
4654 m_selectAnchor = KTextEditor::Cursor::invalid();
4655 } else {
4656 const auto r = view()->selectionRange();
4657 m_selectAnchor = r.start() == m_cursor ? r.end() : r.start();
4658 }
4659 // Do NOT nuke the entire range! The reason is that a shift+DC selection
4660 // might (correctly) set the range to be empty (i.e. start() == end()), and
4661 // subsequent dragging might shrink the selection into non-existence. When
4662 // this happens, we use the cached end to restore the cached start so that
4663 // updateSelection is not confused. See also comments in updateSelection.
4664 m_selectionCached.setStart(KTextEditor::Cursor::invalid());
4666}
4667
4668KateLayoutCache *KateViewInternal::cache() const
4669{
4670 return m_layoutCache;
4671}
4672
4673KTextEditor::Cursor KateViewInternal::toRealCursor(const KTextEditor::Cursor virtualCursor) const
4674{
4675 return KTextEditor::Cursor(view()->textFolding().visibleLineToLine(virtualCursor.line()), virtualCursor.column());
4676}
4677
4678KTextEditor::Cursor KateViewInternal::toVirtualCursor(const KTextEditor::Cursor realCursor) const
4679{
4680 // only convert valid lines, folding doesn't like invalid input!
4681 // don't validate whole cursor, column might be -1
4682 if (realCursor.line() < 0) {
4684 }
4685
4686 return KTextEditor::Cursor(view()->textFolding().lineToVisibleLine(realCursor.line()), realCursor.column());
4687}
4688
4689KateRenderer *KateViewInternal::renderer() const
4690{
4691 return view()->renderer();
4692}
4693
4694void KateViewInternal::mouseMoved()
4695{
4696 view()->notifyMousePositionChanged(m_mouse);
4697 view()->updateRangesIn(KTextEditor::Attribute::ActivateMouseIn);
4698}
4699
4700void KateViewInternal::cursorMoved()
4701{
4702 view()->updateRangesIn(KTextEditor::Attribute::ActivateCaretIn);
4703
4704#ifndef QT_NO_ACCESSIBILITY
4705 if (view()->m_accessibilityEnabled && QAccessible::isActive()) {
4706 QAccessibleTextCursorEvent ev(this, static_cast<KateViewAccessible *>(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, m_cursor));
4708 }
4709#endif
4710}
4711
4712KTextEditor::DocumentPrivate *KateViewInternal::doc()
4713{
4714 return m_view->doc();
4715}
4716
4717KTextEditor::DocumentPrivate *KateViewInternal::doc() const
4718{
4719 return m_view->doc();
4720}
4721
4722bool KateViewInternal::rangeAffectsView(KTextEditor::Range range, bool realCursors) const
4723{
4724 int startLine = KateViewInternal::startLine();
4725 int endLine = startLine + (int)m_visibleLineCount;
4726
4727 if (realCursors) {
4728 startLine = (int)view()->textFolding().visibleLineToLine(startLine);
4729 endLine = (int)view()->textFolding().visibleLineToLine(endLine);
4730 }
4731
4732 return (range.end().line() >= startLine) || (range.start().line() <= endLine);
4733}
4734
4735// BEGIN IM INPUT STUFF
4736QVariant KateViewInternal::inputMethodQuery(Qt::InputMethodQuery query) const
4737{
4738 switch (query) {
4739 case Qt::ImCursorRectangle: {
4740 // Cursor placement code is changed for Asian input method that
4741 // shows candidate window. This behavior is same as Qt/E 2.3.7
4742 // which supports Asian input methods. Asian input methods need
4743 // start point of IM selection text to place candidate window as
4744 // adjacent to the selection text.
4745 //
4746 // in Qt5, cursor rectangle is used as QRectF internally, and it
4747 // will be checked by QRectF::isValid(), which will mark rectangle
4748 // with width == 0 or height == 0 as invalid.
4749 auto lineHeight = renderer()->lineHeight();
4750 return QRect(cursorToCoordinate(m_cursor, true, false), QSize(1, lineHeight ? lineHeight : 1));
4751 }
4752
4753 case Qt::ImFont:
4754 return renderer()->currentFont();
4755
4757 return m_cursor.column();
4758
4760 // If selectAnchor is at the same line, return the real anchor position
4761 // Otherwise return the same position of cursor
4762 if (view()->selection() && m_selectAnchor.line() == m_cursor.line()) {
4763 return m_selectAnchor.column();
4764 } else {
4765 return m_cursor.column();
4766 }
4767
4769 return doc()->kateTextLine(m_cursor.line()).text();
4770
4772 if (view()->selection()) {
4773 return view()->selectionText();
4774 } else {
4775 return QString();
4776 }
4777 default:
4778 /* values: ImMaximumTextLength */
4779 break;
4780 }
4781
4782 return QWidget::inputMethodQuery(query);
4783}
4784
4785void KateViewInternal::inputMethodEvent(QInputMethodEvent *e)
4786{
4787 if (doc()->readOnly()) {
4788 e->ignore();
4789 return;
4790 }
4791
4792 // qCDebug(LOG_KTE) << "Event: cursor" << m_cursor << "commit" << e->commitString() << "preedit" << e->preeditString() << "replacement start" <<
4793 // e->replacementStart() << "length" << e->replacementLength();
4794
4795 if (!m_imPreeditRange) {
4796 m_imPreeditRange.reset(
4798 }
4799
4800 if (!m_imPreeditRange->toRange().isEmpty()) {
4801 doc()->inputMethodStart();
4802 doc()->removeText(*m_imPreeditRange);
4803 doc()->inputMethodEnd();
4804 }
4805
4806 if (!e->commitString().isEmpty() || e->replacementLength() || !e->preeditString().isEmpty()) {
4807 view()->removeSelectedText();
4808 }
4809
4810 if (!e->commitString().isEmpty() || e->replacementLength()) {
4811 KTextEditor::Range preeditRange = *m_imPreeditRange;
4812
4813 KTextEditor::Cursor start(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + e->replacementStart());
4815
4816 doc()->editStart();
4817 if (start != removeEnd) {
4818 doc()->removeText(KTextEditor::Range(start, removeEnd));
4819 }
4820
4821 // if the input method event is text that should be inserted, call KTextEditor::DocumentPrivate::typeChars()
4822 // with the text. that method will handle the input and take care of overwrite mode, etc.
4823 // we call this via keyPressEvent, as that handles our input methods, see bug 357062
4825 keyPressEvent(&ke);
4826
4827 doc()->editEnd();
4828
4829 // Revert to the same range as above
4830 m_imPreeditRange->setRange(preeditRange);
4831 }
4832
4833 if (!e->preeditString().isEmpty()) {
4834 doc()->inputMethodStart();
4835 doc()->insertText(m_imPreeditRange->start(), e->preeditString());
4836 doc()->inputMethodEnd();
4837 // The preedit range gets automatically repositioned
4838 }
4839
4840 // Finished this input method context?
4841 if (m_imPreeditRange && e->preeditString().isEmpty()) {
4842 // delete the range and reset the pointer
4843 m_imPreeditRange.reset();
4844 m_imPreeditRangeChildren.clear();
4845
4847 renderer()->setDrawCaret(false);
4848 }
4849 renderer()->setCaretOverrideColor(QColor());
4850
4851 e->accept();
4852 return;
4853 }
4854
4855 KTextEditor::Cursor newCursor = m_cursor;
4856 bool hideCursor = false;
4857 QColor caretColor;
4858
4859 if (m_imPreeditRange) {
4860 m_imPreeditRangeChildren.clear();
4861
4862 int decorationColumn = 0;
4863 const auto &attributes = e->attributes();
4864 for (auto &a : attributes) {
4865 if (a.type == QInputMethodEvent::Cursor) {
4866 const int cursor = qMin(a.start, e->preeditString().length());
4867 newCursor = m_imPreeditRange->start() + KTextEditor::Cursor(0, cursor);
4868 hideCursor = !a.length;
4869 QColor c = qvariant_cast<QColor>(a.value);
4870 if (c.isValid()) {
4871 caretColor = c;
4872 }
4873
4874 } else if (a.type == QInputMethodEvent::TextFormat) {
4875 QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
4876 const int start = qMin(a.start, e->preeditString().length());
4877 const int end = qMin(a.start + a.length, e->preeditString().length());
4878 if (f.isValid() && decorationColumn <= start && start != end) {
4879 const KTextEditor::MovingCursor &preEditRangeStart = m_imPreeditRange->start();
4880 const int startLine = preEditRangeStart.line();
4881 const int startCol = preEditRangeStart.column();
4882 KTextEditor::Range fr(startLine, startCol + start, startLine, startCol + end);
4883 std::unique_ptr<KTextEditor::MovingRange> formatRange(doc()->newMovingRange(fr));
4885 attribute->merge(f);
4886 formatRange->setAttribute(attribute);
4887 decorationColumn = end;
4888 m_imPreeditRangeChildren.push_back(std::move(formatRange));
4889 }
4890 }
4891 }
4892 }
4893
4894 renderer()->setDrawCaret(hideCursor);
4895 renderer()->setCaretOverrideColor(caretColor);
4896
4897 if (newCursor != m_cursor.toCursor()) {
4898 updateCursor(newCursor);
4899 }
4900
4901 e->accept();
4902}
4903
4904// END IM INPUT STUFF
4905
4906void KateViewInternal::flashChar(const KTextEditor::Cursor pos, KTextEditor::Attribute::Ptr attribute)
4907{
4908 Q_ASSERT(pos.isValid());
4909 Q_ASSERT(attribute.constData());
4910
4911 // if line is folded away, do nothing
4912 if (!view()->textFolding().isLineVisible(pos.line())) {
4913 return;
4914 }
4915
4916 KTextEditor::Range range(pos, KTextEditor::Cursor(pos.line(), pos.column() + 1));
4917 if (m_textAnimation) {
4918 m_textAnimation->deleteLater();
4919 }
4920 m_textAnimation = new KateTextAnimation(range, std::move(attribute), this);
4921}
4922
4923void KateViewInternal::showBracketMatchPreview()
4924{
4925 // only show when main window is active
4926 if (window() && !window()->isActiveWindow()) {
4927 return;
4928 }
4929
4930 const KTextEditor::Cursor openBracketCursor = m_bmStart->start();
4931 // make sure that the matching bracket is an opening bracket that is not visible on the current view, and that the preview won't be blocking the cursor
4932 if (m_cursor == openBracketCursor || toVirtualCursor(openBracketCursor).line() >= startLine() || m_cursor.line() - startLine() < 2) {
4933 hideBracketMatchPreview();
4934 return;
4935 }
4936
4937 if (!m_bmPreview) {
4938 m_bmPreview.reset(new KateTextPreview(m_view, this));
4939 m_bmPreview->setAttribute(Qt::WA_ShowWithoutActivating);
4940 m_bmPreview->setFrameStyle(QFrame::Box);
4941 }
4942
4943 const int previewLine = openBracketCursor.line();
4944 KateRenderer *const renderer_ = renderer();
4945 KateLineLayout *lineLayout(new KateLineLayout(*renderer_));
4946 lineLayout->setLine(previewLine, -1);
4947
4948 // If the opening bracket is on its own line, start preview at the line above it instead (where the context is likely to be)
4949 const int col = lineLayout->textLine().firstChar();
4950 if (previewLine > 0 && (col == -1 || col == openBracketCursor.column())) {
4951 lineLayout->setLine(previewLine - 1, lineLayout->virtualLine() - 1);
4952 }
4953
4954 renderer_->layoutLine(lineLayout, -1 /* no wrap */, false /* no layout cache */);
4955 const int lineWidth =
4956 qBound(m_view->width() / 5, int(lineLayout->width() + renderer_->spaceWidth() * 2), m_view->width() - m_leftBorder->width() - m_lineScroll->width());
4957 m_bmPreview->resize(lineWidth, renderer_->lineHeight() * 2);
4958 const QPoint topLeft = mapToGlobal(QPoint(0, 0));
4959 m_bmPreview->move(topLeft.x(), topLeft.y());
4960 m_bmPreview->setLine(lineLayout->virtualLine());
4961 m_bmPreview->setCenterView(false);
4962 m_bmPreview->raise();
4963 m_bmPreview->show();
4964}
4965
4966void KateViewInternal::hideBracketMatchPreview()
4967{
4968 m_bmPreview.reset();
4969}
4970
4971void KateViewInternal::documentTextInserted(KTextEditor::Document *, KTextEditor::Range range)
4972{
4973#ifndef QT_NO_ACCESSIBILITY
4974 if (view()->m_accessibilityEnabled && QAccessible::isActive()) {
4975 auto doc = view()->doc();
4976 QAccessibleTextInsertEvent ev(this, doc->cursorToOffset(range.start()), doc->text(range));
4978 }
4979#endif
4980}
4981
4982void KateViewInternal::documentTextRemoved(KTextEditor::Document * /*document*/, KTextEditor::Range range, const QString &oldText)
4983{
4984#ifndef QT_NO_ACCESSIBILITY
4985 if (view()->m_accessibilityEnabled && QAccessible::isActive()) {
4986 auto doc = view()->doc();
4987 QAccessibleTextRemoveEvent ev(this, doc->cursorToOffset(range.start()), oldText);
4989 }
4990#endif
4991}
4992
4993QRect KateViewInternal::inlineNoteRect(const KateInlineNoteData &noteData) const
4994{
4995 KTextEditor::InlineNote note(noteData);
4996 // compute note width and position
4997 const auto noteWidth = note.width();
4998 auto noteCursor = note.position();
4999
5000 // The cursor might be outside of the text. In that case, clamp it to the text and
5001 // later on add the missing x offset.
5002 const auto lineLength = view()->document()->lineLength(noteCursor.line());
5003 int extraOffset = -noteWidth;
5004 if (noteCursor.column() == lineLength) {
5005 extraOffset = 0;
5006 } else if (noteCursor.column() > lineLength) {
5007 extraOffset = (noteCursor.column() - lineLength) * renderer()->spaceWidth();
5008 noteCursor.setColumn(lineLength);
5009 }
5010 auto noteStartPos = mapToGlobal(cursorToCoordinate(noteCursor, true, false));
5011 bool rtl = false;
5012 if (view()->dynWordWrap()) {
5013 const KateLineLayout *lineLayout = cache()->line(noteCursor.line());
5014 rtl = lineLayout && lineLayout->layout()->textOption().textDirection() == Qt::RightToLeft;
5015 }
5016 if (rtl) {
5017 noteStartPos.rx() -= note.width();
5018 extraOffset = -extraOffset;
5019 }
5020
5021 // compute the note's rect
5022 auto globalNoteRect = QRect(noteStartPos + QPoint{extraOffset, 0}, QSize(noteWidth, renderer()->lineHeight()));
5023
5024 return globalNoteRect;
5025}
5026
5027KateInlineNoteData KateViewInternal::inlineNoteAt(const QPoint &globalPos) const
5028{
5029 // compute the associated cursor to get the right line
5030 const int line = coordinatesToCursor(mapFromGlobal(globalPos)).line();
5031 const auto inlineNotes = view()->inlineNotes(line);
5032 // loop over all notes and check if the point is inside it
5033 for (const auto &note : inlineNotes) {
5034 auto globalNoteRect = inlineNoteRect(note);
5035 if (globalNoteRect.contains(globalPos)) {
5036 return note;
5037 }
5038 }
5039 // none found -- return an invalid note
5040 return {};
5041}
5042
5043bool KateViewInternal::sendMouseEventToInputContext(QMouseEvent *e)
5044{
5045 if (!m_imPreeditRange) {
5046 return false;
5047 }
5048
5049 KTextEditor::Cursor c = cursorForPoint(e->pos());
5050 if (!m_imPreeditRange->contains(c) && c != m_imPreeditRange->end()) {
5051 return false;
5052 }
5053
5054 auto cursorPos = (c - m_imPreeditRange->start());
5055
5056 if (cursorPos.column() >= 0) {
5057 if (e->type() == QEvent::MouseButtonRelease)
5059 e->setAccepted(true);
5060 return true;
5061 }
5062 return false;
5063}
5064
5065void KateViewInternal::commitPreedit()
5066{
5067 if (!m_imPreeditRange) {
5068 return;
5069 }
5070
5072}
5073
5074#include "moc_kateviewinternal.cpp"
A class which provides customized text decorations.
Definition attribute.h:51
QExplicitlySharedDataPointer< Attribute > Ptr
Shared data pointer for Attribute.
Definition attribute.h:56
@ ActivateMouseIn
Activate attribute on mouse in.
Definition attribute.h:246
@ ActivateCaretIn
Activate attribute on caret in.
Definition attribute.h:248
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
void setColumn(int column) noexcept
Set the cursor column to column.
Definition cursor.h:201
void setPosition(Cursor position) noexcept
Set the current cursor position to position.
Definition cursor.h:150
constexpr bool isValid() const noexcept
Returns whether the current position of this cursor is a valid position (line + column must both be >...
Definition cursor.h:102
constexpr bool atStartOfDocument() const noexcept
Determine if this cursor is located at the start of a document (= at position (0, 0)).
Definition cursor.h:219
void setLine(int line) noexcept
Set the cursor line to line.
Definition cursor.h:183
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
static constexpr Cursor invalid() noexcept
Returns an invalid cursor.
Definition cursor.h:112
Backend of KTextEditor::Document related public KTextEditor interfaces.
QString text(KTextEditor::Range range, bool blockwise=false) const override
Get the document content within the given range.
QChar characterAt(KTextEditor::Cursor position) const override
Get the character at text position cursor.
qsizetype cursorToOffset(KTextEditor::Cursor c) const override
Retrives the offset for the given cursor position NOTE: It will return -1 if the cursor was invalid o...
int lastLine() const
gets the last line number (lines() - 1)
KateBuffer & buffer()
Get access to buffer of this document.
int lines() const override
Get the count of lines of the document.
void textRemoved(KTextEditor::Document *document, KTextEditor::Range range, const QString &oldText)
The document emits this signal whenever range was removed, i.e.
bool editStart()
Enclose editor actions with editStart() and editEnd() to group them.
KateDocumentConfig * config()
Configuration.
void typeChars(KTextEditor::ViewPrivate *view, QString chars)
Type chars in a view.
void textInsertedRange(KTextEditor::Document *document, KTextEditor::Range range)
The document emits this signal whenever text was inserted.
Kate::TextLine kateTextLine(int i)
Same as plainKateTextLine(), except that it is made sure the line is highlighted.
bool editEnd()
End a editor operation.
int lineLength(int line) const override
Get the length of a given line in characters.
A KParts derived class representing a text document.
Definition document.h:284
virtual int lineLength(int line) const =0
Get the length of a given line in characters.
const std::array< std::unique_ptr< KateAbstractInputModeFactory >, KTextEditor::View::ViInputMode+1 > & inputModeFactories()
Definition kateglobal.h:382
static KTextEditor::EditorPrivate * self()
Kate Part Internal stuff ;)
virtual void inlineNoteFocusOutEvent(const InlineNote &note)
Invoked when the mouse cursor leaves the note.
Describes an inline note.
Definition inlinenote.h:40
qreal width() const
Returns the width of this note in pixels.
@ TopInView
show message as view overlay in the top right corner.
Definition message.h:123
@ CenterInView
show message as view overlay in the center of the view.
Definition message.h:128
@ BottomInView
show message as view overlay in the bottom right corner.
Definition message.h:125
A Cursor which is bound to a specific Document, and maintains its position.
const Cursor toCursor() const
Convert this clever cursor into a dumb one.
virtual int column() const =0
Retrieve the column on which this cursor is situated.
virtual int line() const =0
Retrieve the line on which this cursor is situated.
@ ExpandRight
Expand to encapsulate new characters to the right of the range.
@ ExpandLeft
Expand to encapsulate new characters to the left of the range.
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
void setEnd(Cursor end) noexcept
Set the end cursor to end.
constexpr bool isEmpty() const noexcept
Returns true if this range contains no characters, ie.
static constexpr Range invalid() noexcept
Returns an invalid range.
constexpr bool isValid() const noexcept
Validity check.
void setStart(Cursor start) noexcept
Set the start cursor to start.
Class to provide text hints for a View.
void horizontalScrollPositionChanged(KTextEditor::View *view)
This signal should be emitted whenever the view is scrolled horizontally.
void displayRangeChanged(KTextEditor::View *view)
This signal is emitted whenever the displayed range changes.
int lastDisplayedLine(LineType lineType=RealLine) const
Get the last displayed line in the view.
int firstDisplayedLine(LineType lineType=RealLine) const
Get the first displayed line in the view.
@ ViInputMode
Vi mode.
Definition view.h:288
@ NormalInputMode
Normal Mode.
Definition view.h:287
@ RealLine
Real line.
Definition view.h:312
void cursorPositionChanged(KTextEditor::View *view, KTextEditor::Cursor newPosition)
This signal is emitted whenever the view's cursor position changed.
void selectionChanged(KTextEditor::View *view)
This signal is emitted whenever the view's selection changes.
void verticalScrollPositionChanged(KTextEditor::View *view, KTextEditor::Cursor newPos)
This signal should be emitted whenever the view is scrolled vertically.
KateHighlighting::Foldings computeFoldings(int line)
Compute folding vector for the given line, will internally do a re-highlighting.
QVariant value(const int key) const
Get a config value.
Internal data container for KTextEditor::InlineNote interface.
This class handles Kate's caching of layouting information (in KateLineLayout and KateTextLayout).
KateTextLayout & viewLine(int viewLine)
Returns the layout of the corresponding line in the view.
KateLineLayout * line(int realLine, int virtualLine=-1)
Returns the KateLineLayout for the specified line.
KateTextLayout textLayout(const KTextEditor::Cursor realCursor)
Returns the layout describing the text line which is occupied by realCursor.
int displayViewLine(const KTextEditor::Cursor virtualCursor, bool limitToVisible=false)
Find the view line of the cursor, relative to the display (0 = top line of view, 1 = second line,...
Handles all of the work of rendering the text (used for the views and printing)
void increaseFontSizes(qreal step=1.0) const
Change to a different font (soon to be font set?)
void setShowTabs(bool showTabs)
Set whether a mark should be painted to help identifying tabs.
const QFont & currentFont() const
Access currently used font.
const AttributePtr & attribute(uint pos) const
This takes an in index, and returns all the attributes for it.
KTextEditor::caretStyles caretStyle() const
The style of the caret (text cursor) to be painted.
void setDrawCaret(bool drawCaret)
Set whether the caret (text cursor) will be drawn.
int cursorToX(const KateTextLayout &range, int col, bool returnPastLine=false) const
Returns the x position of cursor col on the line range.
KTextEditor::Cursor xToCursor(const KateTextLayout &range, int x, bool returnPastLine=false) const
Returns the real cursor which is occupied by the specified x value, or that closest to it.
void setShowSpaces(KateDocumentConfig::WhitespaceRendering showSpaces)
Set which spaces should be rendered.
const QFontMetricsF & currentFontMetrics() const
Access currently used font metrics.
void setCaretOverrideColor(const QColor &color)
Set a brush with which to override drawing of the caret.
void setCaretStyle(KTextEditor::caretStyles style)
Set the style of caret to be painted.
void layoutLine(KateLineLayout *line, int maxwidth=-1, bool cacheLayout=false) const
Text width & height calculation functions...
void paintSelection(QPaintDevice *d, int startLine, int xStart, int endLine, int xEnd, int viewWidth, qreal scale=1.0)
Paints a range of text into d.
void updateMarkerSize()
Update marker size shown.
void paintTextLine(QPainter &paint, KateLineLayout *range, int xStart, int xEnd, const QRectF &textClipRect=QRectF(), const KTextEditor::Cursor *cursor=nullptr, PaintTextLineFlags flags=PaintTextLineFlags())
This is the ultimate function to perform painting of a text line.
This class is required because QScrollBar's sliderMoved() signal is really supposed to be a sliderDra...
This class is used to flash text in the text view.
This class represents one visible line of text; with dynamic wrapping, many KateTextLayouts can be ne...
int endCol(bool indicateEOL=false) const
Return the end column of this text line.
int viewLine() const
Return the index of this visual line inside the document line (KateLineLayout).
This class implements a QAccessible-interface for a KateViewInternal.
int column() const override
Retrieve the column on which this cursor is situated.
void setPosition(const TextCursor &position)
Fast way to set the current cursor position to position.
int line() const override
Retrieve the line on which this cursor is situated.
void foldingRangesChanged()
If the folding state of existing ranges changes or ranges are added/removed, this signal is emitted.
KTextEditor::Range foldingRange(qint64 id) const
Returns the folding range associated with id.
int visibleLineToLine(int visibleLine) const
Convert a visible line number to a line number in the text buffer.
void ensureLineIsVisible(int line)
Ensure that a given line will be visible.
Class representing a single text line.
int attribute(int pos) const
Gets the attribute at the given position use KRenderer::attributes to get the KTextAttribute for this...
const QString & text() const
Accessor to the text contained in this line.
int length() const
Returns the line's length.
int lastChar() const
Returns the position of the last non-whitespace character.
QChar at(int column) const
Returns the character at the given column.
int firstChar() const
Returns the position of the first non-whitespace character.
const KTextEditor::Range toRange() const
Convert this clever range into a dumb one.
Q_SCRIPTABLE Q_NOREPLY void start()
QAction * hint(const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & end()
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
@ Default
Default settings.
Definition document.h:48
void actionTriggered(int action)
void setPageStep(int)
void setRange(int min, int max)
void setSingleStep(int)
void sliderMoved(int value)
void setValue(int)
void valueChanged(int value)
void installFactory(InterfaceFactory factory)
bool isActive()
QAccessibleInterface * queryAccessibleInterface(QObject *object)
void removeFactory(InterfaceFactory factory)
void updateAccessibility(QAccessibleEvent *event)
Category category(char32_t ucs4)
bool isHighSurrogate(char32_t ucs4)
bool isLetter(char32_t ucs4)
bool isLetterOrNumber(char32_t ucs4)
bool isLowSurrogate(char32_t ucs4)
bool isPrint(char32_t ucs4)
bool isSpace(char32_t ucs4)
bool isUpper(char32_t ucs4)
bool added() const const
QObject * child() const const
bool removed() const const
void setText(const QString &text, Mode mode)
QString text(Mode mode) const const
bool isValid() const const
const QPoint & pos() const const
Reason reason() const const
bool sendEvent(QObject *receiver, QEvent *event)
QPoint pos()
Qt::DropAction exec(Qt::DropActions supportedActions)
void setHotSpot(const QPoint &hotspot)
void setMimeData(QMimeData *data)
void setPixmap(const QPixmap &pixmap)
qint64 elapsed() const const
bool isValid() const const
void accept()
bool isAccepted() const const
void ignore()
Type type() const const
const T * constData() const const
qreal horizontalAdvance(QChar ch) const const
QClipboard * clipboard()
QInputMethod * inputMethod()
Qt::KeyboardModifiers modifiers() const const
void invokeAction(Action a, int cursorPosition)
const QList< Attribute > & attributes() const const
const QString & commitString() const const
const QString & preeditString() const const
int replacementLength() const const
int replacementStart() const const
int key() const const
Qt::KeyboardModifiers modifiers() const const
QString text() const const
void append(QList< T > &&value)
bool isEmpty() const const
void popup(const QPoint &p, QAction *atAction)
QPoint pos() const const
const QPoint & oldPos() const const
const QPoint & pos() const const
Q_EMITQ_EMIT
bool blockSignals(bool block)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool eventFilter(QObject *watched, QEvent *event)
void installEventFilter(QObject *filterObj)
void removeEventFilter(QObject *obj)
qreal devicePixelRatioF() const const
const QRect & rect() const const
const QRegion & region() const const
int manhattanLength() const const
int & rx()
int x() const const
int y() const const
virtual void setAccepted(bool accepted) override
QPoint toPoint() const const
qreal x() const const
qreal y() const const
int height() const const
int width() const const
int x() const const
int y() const const
bool isEmpty() const const
const QSize & oldSize() const const
virtual QSize sizeHint() const const override
QScroller * scroller(QObject *target)
void setScrollMetric(ScrollMetric metric, const QVariant &value)
Qt::MouseButton button() const const
Qt::MouseButtons buttons() const const
QPointF globalPosition() const const
QPointF position() const const
int height() const const
int width() const const
const QChar at(qsizetype position) const const
QString & fill(QChar ch, qsizetype size)
bool isEmpty() const const
qsizetype length() const const
qsizetype size() const const
QChar at(qsizetype n) const const
qsizetype size() const const
RequestSoftwareInputPanel
SH_RequestSoftwareInputPanel
IBeamCursor
MoveAction
transparent
InputMethodQuery
Key_Escape
typedef KeyboardModifiers
RightToLeft
LeftButton
Vertical
WA_OpaquePaintEvent
bool isValid() const const
QTextCharFormat toCharFormat() const const
int nextCursorPosition(int oldPos, CursorMode mode) const const
int previousCursorPosition(int oldPos, CursorMode mode) const const
const QTextOption & textOption() const const
Qt::LayoutDirection textDirection() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isActive() const const
void start()
void stop()
void timeout()
void hideText()
bool isVisible()
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
bool toBool() const const
iterator begin()
bool contains(const AT &value) const const
iterator end()
bool isEmpty() const const
void push_back(T &&t)
qsizetype size() const const
QPoint angleDelta() const const
bool isEnabled() const const
virtual bool event(QEvent *event) override
bool hasFocus() const const
void hide()
virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const const
QLayout * layout() const const
QPoint mapFromGlobal(const QPoint &pos) const const
QPoint mapToGlobal(const QPoint &pos) const const
virtual void moveEvent(QMoveEvent *event)
QWidget * parentWidget() const const
void scroll(int dx, int dy)
void setDisabled(bool disable)
void setFixedSize(const QSize &s)
void show()
virtual void showEvent(QShowEvent *event)
QStyle * style() const const
void update()
void updateMicroFocus(Qt::InputMethodQuery query)
bool isVisible() const const
QWidget * window() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:11:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.