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

KDE's Doxygen guidelines are available online.