File indexing completed on 2024-05-12 07:53:41
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org> 0004 SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org> 0005 SPDX-FileCopyrightText: 2002, 2003 Christoph Cullmann <cullmann@kde.org> 0006 SPDX-FileCopyrightText: 2002-2007 Hamish Rodda <rodda@kde.org> 0007 SPDX-FileCopyrightText: 2003 Anakim Border <aborder@sources.sourceforge.net> 0008 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch> 0009 SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net> 0010 SPDX-FileCopyrightText: 2008 Erlend Hamberg <ehamberg@gmail.com> 0011 0012 Based on KWriteView: 0013 SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> 0014 0015 SPDX-License-Identifier: LGPL-2.0-only 0016 */ 0017 #include "kateviewinternal.h" 0018 0019 #include "kateabstractinputmode.h" 0020 #include "kateabstractinputmodefactory.h" 0021 #include "katebuffer.h" 0022 #include "katecompletionwidget.h" 0023 #include "kateconfig.h" 0024 #include "kateglobal.h" 0025 #include "katehighlight.h" 0026 #include "katelayoutcache.h" 0027 #include "katemessagewidget.h" 0028 #include "katepartdebug.h" 0029 #include "katerenderer.h" 0030 #include "katetextanimation.h" 0031 #include "katetextpreview.h" 0032 #include "kateview.h" 0033 #include "kateviewaccessible.h" 0034 #include "kateviewhelpers.h" 0035 #include "spellcheck/spellingmenu.h" 0036 0037 #include <KCursor> 0038 #include <ktexteditor/documentcursor.h> 0039 #include <ktexteditor/inlinenoteprovider.h> 0040 #include <ktexteditor/movingrange.h> 0041 #include <ktexteditor/texthintinterface.h> 0042 0043 #include <QAccessible> 0044 #include <QApplication> 0045 #include <QClipboard> 0046 #include <QKeyEvent> 0047 #include <QLayout> 0048 #include <QMenu> 0049 #include <QMimeData> 0050 #include <QPainter> 0051 #include <QPixmap> 0052 #include <QScopeGuard> 0053 #include <QScroller> 0054 #include <QStyle> 0055 #include <QToolTip> 0056 0057 static const bool debugPainting = false; 0058 0059 class ZoomEventFilter 0060 { 0061 public: 0062 ZoomEventFilter() = default; 0063 0064 bool detectZoomingEvent(QWheelEvent *e, Qt::KeyboardModifiers modifier = Qt::ControlModifier) 0065 { 0066 Qt::KeyboardModifiers modState = e->modifiers(); 0067 if (modState == modifier) { 0068 if (m_lastWheelEvent.isValid()) { 0069 const qint64 deltaT = m_lastWheelEvent.elapsed(); 0070 // Pressing the specified modifier key within 200ms of the previous "unmodified" 0071 // wheelevent is not allowed to toggle on text zooming 0072 if (m_lastWheelEventUnmodified && deltaT < 200) { 0073 m_ignoreZoom = true; 0074 } else if (deltaT > 1000) { 0075 // the protection is kept active for 1s after the last wheel event 0076 // TODO: this value should be tuned, preferably by someone using 0077 // Ctrl+Wheel zooming frequently. 0078 m_ignoreZoom = false; 0079 } 0080 } else { 0081 // we can't say anything and have to assume there's nothing 0082 // accidental to the modifier being pressed. 0083 m_ignoreZoom = false; 0084 } 0085 m_lastWheelEventUnmodified = false; 0086 if (m_ignoreZoom) { 0087 // unset the modifier so the view scrollbars can handle the scroll 0088 // event and produce normal, not accelerated scrolling 0089 modState &= ~modifier; 0090 e->setModifiers(modState); 0091 } 0092 } else { 0093 // state is reset after any wheel event without the zoom modifier 0094 m_lastWheelEventUnmodified = true; 0095 m_ignoreZoom = false; 0096 } 0097 m_lastWheelEvent.start(); 0098 0099 // inform the caller whether this event is allowed to trigger text zooming. 0100 return !m_ignoreZoom && modState == modifier; 0101 } 0102 0103 protected: 0104 QElapsedTimer m_lastWheelEvent; 0105 bool m_ignoreZoom = false; 0106 bool m_lastWheelEventUnmodified = false; 0107 }; 0108 0109 KateViewInternal::KateViewInternal(KTextEditor::ViewPrivate *view) 0110 : QWidget(view) 0111 , editSessionNumber(0) 0112 , editIsRunning(false) 0113 , m_view(view) 0114 , m_cursor(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::MoveOnInsert) 0115 , m_mouse() 0116 , m_possibleTripleClick(false) 0117 , m_bm(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0118 , m_bmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0119 , m_bmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0120 , m_bmLastFlashPos(doc()->newMovingCursor(KTextEditor::Cursor::invalid())) 0121 0122 // folding marker 0123 , m_fmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0124 , m_fmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0125 0126 , m_dummy(nullptr) 0127 0128 // stay on cursor will avoid that the view scroll around on press return at beginning 0129 , m_startPos(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::StayOnInsert) 0130 0131 , m_visibleLineCount(0) 0132 , m_madeVisible(false) 0133 , m_shiftKeyPressed(false) 0134 , m_autoCenterLines(0) 0135 , m_minLinesVisible(0) 0136 , m_selChangedByUser(false) 0137 , m_selectAnchor(-1, -1) 0138 , m_selectionMode(Default) 0139 , m_layoutCache(new KateLayoutCache(renderer(), this)) 0140 , m_preserveX(false) 0141 , m_preservedX(0) 0142 , m_cachedMaxStartPos(-1, -1) 0143 , m_dragScrollTimer(this) 0144 , m_scrollTimer(this) 0145 , m_cursorTimer(this) 0146 , m_textHintTimer(this) 0147 , m_textHintDelay(500) 0148 , m_textHintPos(-1, -1) 0149 , m_imPreeditRange(nullptr) 0150 { 0151 // setup input modes 0152 Q_ASSERT(m_inputModes.size() == KTextEditor::EditorPrivate::self()->inputModeFactories().size()); 0153 m_inputModes[KTextEditor::View::NormalInputMode].reset( 0154 KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::NormalInputMode]->createInputMode(this)); 0155 m_inputModes[KTextEditor::View::ViInputMode].reset( 0156 KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::ViInputMode]->createInputMode(this)); 0157 m_currentInputMode = m_inputModes[KTextEditor::View::NormalInputMode].get(); 0158 0159 setMinimumSize(0, 0); 0160 setAttribute(Qt::WA_OpaquePaintEvent); 0161 setAttribute(Qt::WA_InputMethodEnabled); 0162 0163 // invalidate m_selectionCached.start(), or keyb selection is screwed initially 0164 m_selectionCached = KTextEditor::Range::invalid(); 0165 0166 // bracket markers are only for this view and should not be printed 0167 m_bm->setView(m_view); 0168 m_bmStart->setView(m_view); 0169 m_bmEnd->setView(m_view); 0170 m_bm->setAttributeOnlyForViews(true); 0171 m_bmStart->setAttributeOnlyForViews(true); 0172 m_bmEnd->setAttributeOnlyForViews(true); 0173 0174 // use z depth defined in moving ranges interface 0175 m_bm->setZDepth(-1000.0); 0176 m_bmStart->setZDepth(-1000.0); 0177 m_bmEnd->setZDepth(-1000.0); 0178 0179 // update mark attributes 0180 updateBracketMarkAttributes(); 0181 0182 // 0183 // scrollbar for lines 0184 // 0185 m_lineScroll = new KateScrollBar(Qt::Vertical, this); 0186 m_lineScroll->show(); 0187 m_lineScroll->setTracking(true); 0188 m_lineScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); 0189 0190 // Hijack the line scroller's controls, so we can scroll nicely for word-wrap 0191 connect(m_lineScroll, &KateScrollBar::actionTriggered, this, &KateViewInternal::scrollAction); 0192 0193 auto viewScrollLinesSlot = qOverload<int>(&KateViewInternal::scrollLines); 0194 connect(m_lineScroll, &KateScrollBar::sliderMoved, this, viewScrollLinesSlot); 0195 connect(m_lineScroll, &KateScrollBar::sliderMMBMoved, this, viewScrollLinesSlot); 0196 connect(m_lineScroll, &KateScrollBar::valueChanged, this, viewScrollLinesSlot); 0197 0198 // 0199 // scrollbar for columns 0200 // 0201 m_columnScroll = new QScrollBar(Qt::Horizontal, m_view); 0202 m_scroller = QScroller::scroller(this); 0203 QScrollerProperties prop; 0204 prop.setScrollMetric(QScrollerProperties::DecelerationFactor, 0.3); 0205 prop.setScrollMetric(QScrollerProperties::MaximumVelocity, 1); 0206 prop.setScrollMetric(QScrollerProperties::AcceleratingFlickMaximumTime, 0207 0.2); // Workaround for QTBUG-88249 (non-flick gestures recognized as accelerating flick) 0208 prop.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff); 0209 prop.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff); 0210 prop.setScrollMetric(QScrollerProperties::DragStartDistance, 0.0); 0211 m_scroller->setScrollerProperties(prop); 0212 #ifndef Q_OS_MACOS 0213 // On macOS the trackpad also emits touch events which are sometimes picked up by the flick 0214 // gesture recogniser registered by QScroller; this results in some odd scrolling behaviour 0215 // as described in bug #442060. Therefore it's better to not let the QScroller register 0216 // it on that platform. 0217 m_scroller->grabGesture(this); 0218 #endif 0219 0220 if (m_view->dynWordWrap()) { 0221 m_columnScroll->hide(); 0222 } else { 0223 m_columnScroll->show(); 0224 } 0225 0226 m_columnScroll->setTracking(true); 0227 m_startX = 0; 0228 0229 connect(m_columnScroll, &QScrollBar::valueChanged, this, &KateViewInternal::scrollColumns); 0230 0231 // bottom corner box 0232 m_dummy = new QWidget(m_view); 0233 m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); 0234 m_dummy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0235 0236 if (m_view->dynWordWrap()) { 0237 m_dummy->hide(); 0238 } else { 0239 m_dummy->show(); 0240 } 0241 0242 cache()->setWrap(m_view->dynWordWrap()); 0243 0244 // 0245 // iconborder ;) 0246 // 0247 m_leftBorder = new KateIconBorder(this, m_view); 0248 m_leftBorder->show(); 0249 0250 // update view if folding ranges change 0251 connect(&m_view->textFolding(), &Kate::TextFolding::foldingRangesChanged, this, &KateViewInternal::slotRegionVisibilityChanged); 0252 0253 m_displayCursor.setPosition(0, 0); 0254 0255 setAcceptDrops(true); 0256 0257 m_zoomEventFilter.reset(new ZoomEventFilter()); 0258 // event filter 0259 installEventFilter(this); 0260 0261 // set initial cursor 0262 m_mouseCursor = Qt::IBeamCursor; 0263 setCursor(m_mouseCursor); 0264 0265 // call mouseMoveEvent also if no mouse button is pressed 0266 setMouseTracking(true); 0267 0268 m_dragInfo.state = diNone; 0269 0270 // timers 0271 connect(&m_dragScrollTimer, &QTimer::timeout, this, &KateViewInternal::doDragScroll); 0272 0273 connect(&m_scrollTimer, &QTimer::timeout, this, &KateViewInternal::scrollTimeout); 0274 0275 connect(&m_cursorTimer, &QTimer::timeout, this, &KateViewInternal::cursorTimeout); 0276 0277 connect(&m_textHintTimer, &QTimer::timeout, this, &KateViewInternal::textHintTimeout); 0278 0279 // selection changed to set anchor 0280 connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged); 0281 0282 #ifndef QT_NO_ACCESSIBILITY 0283 QAccessible::installFactory(accessibleInterfaceFactory); 0284 #endif 0285 connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &KateViewInternal::documentTextInserted); 0286 connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateViewInternal::documentTextRemoved); 0287 0288 // update is called in KTextEditor::ViewPrivate, after construction and layout is over 0289 // but before any other kateviewinternal call 0290 } 0291 0292 KateViewInternal::~KateViewInternal() 0293 { 0294 // delete text animation object here, otherwise it updates the view in its destructor 0295 delete m_textAnimation; 0296 0297 #ifndef QT_NO_ACCESSIBILITY 0298 QAccessible::removeFactory(accessibleInterfaceFactory); 0299 #endif 0300 } 0301 0302 void KateViewInternal::dynWrapChanged() 0303 { 0304 m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); 0305 if (view()->dynWordWrap()) { 0306 m_columnScroll->hide(); 0307 m_dummy->hide(); 0308 0309 } else { 0310 // column scrollbar + bottom corner box 0311 m_columnScroll->show(); 0312 m_dummy->show(); 0313 } 0314 0315 cache()->setWrap(view()->dynWordWrap()); 0316 updateView(); 0317 0318 if (view()->dynWordWrap()) { 0319 scrollColumns(0); 0320 } 0321 0322 update(); 0323 } 0324 0325 KTextEditor::Cursor KateViewInternal::endPos() const 0326 { 0327 // Hrm, no lines laid out at all?? 0328 if (!cache()->viewCacheLineCount()) { 0329 return KTextEditor::Cursor(); 0330 } 0331 0332 for (int i = qMin(linesDisplayed() - 1, cache()->viewCacheLineCount() - 1); i >= 0; i--) { 0333 const KateTextLayout &thisLine = cache()->viewLine(i); 0334 0335 if (thisLine.line() == -1) { 0336 continue; 0337 } 0338 0339 if (thisLine.virtualLine() >= view()->textFolding().visibleLines()) { 0340 // Cache is too out of date 0341 return KTextEditor::Cursor(view()->textFolding().visibleLines() - 1, 0342 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1))); 0343 } 0344 0345 return KTextEditor::Cursor(thisLine.virtualLine(), thisLine.wrap() ? thisLine.endCol() - 1 : thisLine.endCol()); 0346 } 0347 0348 // can happen, if view is still invisible 0349 return KTextEditor::Cursor(); 0350 } 0351 0352 int KateViewInternal::endLine() const 0353 { 0354 return endPos().line(); 0355 } 0356 0357 KateTextLayout KateViewInternal::yToKateTextLayout(int y) const 0358 { 0359 if (y < 0 || y > size().height()) { 0360 return KateTextLayout::invalid(); 0361 } 0362 0363 int range = y / renderer()->lineHeight(); 0364 0365 // lineRanges is always bigger than 0, after the initial updateView call 0366 if (range >= 0 && range < cache()->viewCacheLineCount()) { 0367 return cache()->viewLine(range); 0368 } 0369 0370 return KateTextLayout::invalid(); 0371 } 0372 0373 int KateViewInternal::lineToY(int viewLine) const 0374 { 0375 return (viewLine - startLine()) * renderer()->lineHeight(); 0376 } 0377 0378 void KateViewInternal::slotIncFontSizes(qreal step) 0379 { 0380 renderer()->increaseFontSizes(step); 0381 } 0382 0383 void KateViewInternal::slotDecFontSizes(qreal step) 0384 { 0385 renderer()->decreaseFontSizes(step); 0386 } 0387 0388 void KateViewInternal::slotResetFontSizes() 0389 { 0390 renderer()->resetFontSizes(); 0391 } 0392 0393 /** 0394 * Line is the real line number to scroll to. 0395 */ 0396 void KateViewInternal::scrollLines(int line) 0397 { 0398 KTextEditor::Cursor newPos(line, 0); 0399 scrollPos(newPos); 0400 } 0401 0402 // This can scroll less than one true line 0403 void KateViewInternal::scrollViewLines(int offset) 0404 { 0405 KTextEditor::Cursor c = viewLineOffset(startPos(), offset); 0406 scrollPos(c); 0407 0408 bool blocked = m_lineScroll->blockSignals(true); 0409 m_lineScroll->setValue(startLine()); 0410 m_lineScroll->blockSignals(blocked); 0411 } 0412 0413 void KateViewInternal::scrollAction(int action) 0414 { 0415 switch (action) { 0416 case QAbstractSlider::SliderSingleStepAdd: 0417 scrollNextLine(); 0418 break; 0419 0420 case QAbstractSlider::SliderSingleStepSub: 0421 scrollPrevLine(); 0422 break; 0423 0424 case QAbstractSlider::SliderPageStepAdd: 0425 scrollNextPage(); 0426 break; 0427 0428 case QAbstractSlider::SliderPageStepSub: 0429 scrollPrevPage(); 0430 break; 0431 0432 case QAbstractSlider::SliderToMinimum: 0433 top_home(); 0434 break; 0435 0436 case QAbstractSlider::SliderToMaximum: 0437 bottom_end(); 0438 break; 0439 } 0440 } 0441 0442 void KateViewInternal::scrollNextPage() 0443 { 0444 scrollViewLines(qMax(linesDisplayed() - 1, 0)); 0445 } 0446 0447 void KateViewInternal::scrollPrevPage() 0448 { 0449 scrollViewLines(-qMax(linesDisplayed() - 1, 0)); 0450 } 0451 0452 void KateViewInternal::scrollPrevLine() 0453 { 0454 scrollViewLines(-1); 0455 } 0456 0457 void KateViewInternal::scrollNextLine() 0458 { 0459 scrollViewLines(1); 0460 } 0461 0462 KTextEditor::Cursor KateViewInternal::maxStartPos(bool changed) 0463 { 0464 cache()->setAcceptDirtyLayouts(true); 0465 0466 if (m_cachedMaxStartPos.line() == -1 || changed) { 0467 KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1, 0468 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1))); 0469 0470 if (view()->config()->scrollPastEnd()) { 0471 m_cachedMaxStartPos = viewLineOffset(end, -m_minLinesVisible); 0472 } else { 0473 m_cachedMaxStartPos = viewLineOffset(end, -(linesDisplayed() - 1)); 0474 } 0475 } 0476 0477 cache()->setAcceptDirtyLayouts(false); 0478 0479 return m_cachedMaxStartPos; 0480 } 0481 0482 // c is a virtual cursor 0483 void KateViewInternal::scrollPos(KTextEditor::Cursor &c, bool force, bool calledExternally, bool emitSignals) 0484 { 0485 if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) { 0486 return; 0487 } 0488 0489 if (c.line() < 0) { 0490 c.setLine(0); 0491 } 0492 0493 KTextEditor::Cursor limit = maxStartPos(); 0494 if (c > limit) { 0495 c = limit; 0496 0497 // Re-check we're not just scrolling to the same place 0498 if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) { 0499 return; 0500 } 0501 } 0502 0503 int viewLinesScrolled = 0; 0504 0505 // only calculate if this is really used and useful, could be wrong here, please recheck 0506 // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on 0507 // try to get it really working ;) 0508 bool viewLinesScrolledUsable = !force && (c.line() >= startLine() - linesDisplayed() - 1) && (c.line() <= endLine() + linesDisplayed() + 1); 0509 0510 if (viewLinesScrolledUsable) { 0511 viewLinesScrolled = cache()->displayViewLine(c); 0512 } 0513 0514 m_startPos.setPosition(c); 0515 0516 // set false here but reversed if we return to makeVisible 0517 m_madeVisible = false; 0518 0519 if (viewLinesScrolledUsable) { 0520 int lines = linesDisplayed(); 0521 if (view()->textFolding().visibleLines() < lines) { 0522 KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1, 0523 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1))); 0524 lines = qMin(linesDisplayed(), cache()->displayViewLine(end) + 1); 0525 } 0526 0527 Q_ASSERT(lines >= 0); 0528 0529 if (!calledExternally && qAbs(viewLinesScrolled) < lines && 0530 // NOTE: on some machines we must update if the floating widget is visible 0531 // otherwise strange painting bugs may occur during scrolling... 0532 !((view()->m_messageWidgets[KTextEditor::Message::TopInView] && view()->m_messageWidgets[KTextEditor::Message::TopInView]->isVisible()) 0533 || (view()->m_messageWidgets[KTextEditor::Message::CenterInView] && view()->m_messageWidgets[KTextEditor::Message::CenterInView]->isVisible()) 0534 || (view()->m_messageWidgets[KTextEditor::Message::BottomInView] && view()->m_messageWidgets[KTextEditor::Message::BottomInView]->isVisible()))) { 0535 updateView(false, viewLinesScrolled); 0536 0537 int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight()); 0538 0539 // scroll excluding child widgets (floating notifications) 0540 scroll(0, scrollHeight, rect()); 0541 m_leftBorder->scroll(0, scrollHeight); 0542 0543 if (emitSignals) { 0544 Q_EMIT view()->verticalScrollPositionChanged(m_view, c); 0545 Q_EMIT view()->displayRangeChanged(m_view); 0546 } 0547 return; 0548 } 0549 } 0550 0551 updateView(); 0552 update(); 0553 m_leftBorder->update(); 0554 if (emitSignals) { 0555 Q_EMIT view()->verticalScrollPositionChanged(m_view, c); 0556 Q_EMIT view()->displayRangeChanged(m_view); 0557 } 0558 } 0559 0560 void KateViewInternal::scrollColumns(int x) 0561 { 0562 if (x < 0) { 0563 x = 0; 0564 } 0565 0566 if (x > m_columnScroll->maximum()) { 0567 x = m_columnScroll->maximum(); 0568 } 0569 0570 if (x == startX()) { 0571 return; 0572 } 0573 0574 int dx = startX() - x; 0575 m_startX = x; 0576 0577 if (qAbs(dx) < width()) { 0578 // scroll excluding child widgets (floating notifications) 0579 scroll(dx, 0, rect()); 0580 } else { 0581 update(); 0582 } 0583 0584 Q_EMIT view()->horizontalScrollPositionChanged(m_view); 0585 Q_EMIT view()->displayRangeChanged(m_view); 0586 0587 bool blocked = m_columnScroll->blockSignals(true); 0588 m_columnScroll->setValue(startX()); 0589 m_columnScroll->blockSignals(blocked); 0590 } 0591 0592 // If changed is true, the lines that have been set dirty have been updated. 0593 void KateViewInternal::updateView(bool changed, int viewLinesScrolled) 0594 { 0595 if (!isVisible() && !viewLinesScrolled && !changed) { 0596 return; // When this view is not visible, don't do anything 0597 } 0598 0599 view()->doc()->delayAutoReload(); // Don't reload while user scrolls around 0600 bool blocked = m_lineScroll->blockSignals(true); 0601 0602 int wrapWidth = width(); 0603 if (view()->config()->dynWrapAtStaticMarker() && view()->config()->dynWordWrap()) { 0604 // We need to transform char count to a pixel width, stolen from PrintPainter::updateCache() 0605 QString s; 0606 s.fill(QLatin1Char('5'), view()->doc()->config()->wordWrapAt()); 0607 wrapWidth = qMin(width(), static_cast<int>(renderer()->currentFontMetrics().boundingRect(s).width())); 0608 } 0609 0610 if (wrapWidth != cache()->viewWidth()) { 0611 cache()->setViewWidth(wrapWidth); 0612 changed = true; 0613 } 0614 0615 /* It was observed that height() could be negative here -- 0616 when the main Kate view has 0 as size (during creation), 0617 and there frame around KateViewInternal. In which 0618 case we'd set the view cache to 0 (or less!) lines, and 0619 start allocating huge chunks of data, later. */ 0620 int newSize = (qMax(0, height()) / renderer()->lineHeight()) + 1; 0621 cache()->updateViewCache(startPos(), newSize, viewLinesScrolled); 0622 m_visibleLineCount = newSize; 0623 0624 KTextEditor::Cursor maxStart = maxStartPos(changed); 0625 int maxLineScrollRange = maxStart.line(); 0626 if (view()->dynWordWrap() && maxStart.column() != 0) { 0627 maxLineScrollRange++; 0628 } 0629 m_lineScroll->setRange(0, maxLineScrollRange); 0630 0631 m_lineScroll->setValue(startLine()); 0632 m_lineScroll->setSingleStep(1); 0633 m_lineScroll->setPageStep(qMax(0, height()) / renderer()->lineHeight()); 0634 m_lineScroll->blockSignals(blocked); 0635 0636 KateViewConfig::ScrollbarMode show_scrollbars = static_cast<KateViewConfig::ScrollbarMode>(view()->config()->showScrollbars()); 0637 0638 bool visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (maxLineScrollRange != 0))); 0639 bool visible_dummy = visible; 0640 0641 m_lineScroll->setVisible(visible); 0642 0643 if (!view()->dynWordWrap()) { 0644 int max = maxLen(startLine()) - width(); 0645 if (max < 0) { 0646 max = 0; 0647 } 0648 0649 // if we lose the ability to scroll horizontally, move view to the far-left 0650 if (max == 0) { 0651 scrollColumns(0); 0652 } 0653 0654 blocked = m_columnScroll->blockSignals(true); 0655 0656 // disable scrollbar 0657 m_columnScroll->setDisabled(max == 0); 0658 0659 visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (max != 0))); 0660 visible_dummy &= visible; 0661 m_columnScroll->setVisible(visible); 0662 0663 m_columnScroll->setRange(0, max + (renderer()->spaceWidth() / 2)); // Add some space for the caret at EOL 0664 0665 m_columnScroll->setValue(startX()); 0666 0667 // Approximate linescroll 0668 m_columnScroll->setSingleStep(renderer()->currentFontMetrics().horizontalAdvance(QLatin1Char('a'))); 0669 m_columnScroll->setPageStep(width()); 0670 0671 m_columnScroll->blockSignals(blocked); 0672 } else { 0673 visible_dummy = false; 0674 } 0675 0676 m_dummy->setVisible(visible_dummy); 0677 0678 if (changed) { 0679 updateDirty(); 0680 } 0681 } 0682 0683 /** 0684 * this function ensures a certain location is visible on the screen. 0685 * if endCol is -1, ignore making the columns visible. 0686 */ 0687 void KateViewInternal::makeVisible(const KTextEditor::Cursor c, int endCol, bool force, bool center, bool calledExternally) 0688 { 0689 // qCDebug(LOG_KTE) << "MakeVisible start " << startPos() << " end " << endPos() << " -> request: " << c;// , new start [" << scroll.line << "," << 0690 // scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height(); if the line is in a folded region, unfold all the way up if ( 0691 // doc()->foldingTree()->findNodeForLine( c.line )->visible ) 0692 // qCDebug(LOG_KTE)<<"line ("<<c.line<<") should be visible"; 0693 0694 const int lnDisp = linesDisplayed(); 0695 const int viewLine = cache()->displayViewLine(c, true); 0696 const bool curBelowScreen = (viewLine == -2); 0697 0698 if (force) { 0699 KTextEditor::Cursor scroll = c; 0700 scrollPos(scroll, force, calledExternally); 0701 } else if (center && (c < startPos() || c > endPos())) { 0702 KTextEditor::Cursor scroll = viewLineOffset(c, -int(lnDisp) / 2); 0703 scrollPos(scroll, false, calledExternally); 0704 } else if ((viewLine >= (lnDisp - m_minLinesVisible)) || (curBelowScreen)) { 0705 KTextEditor::Cursor scroll = viewLineOffset(c, -(lnDisp - m_minLinesVisible - 1)); 0706 scrollPos(scroll, false, calledExternally); 0707 } else if (c < viewLineOffset(startPos(), m_minLinesVisible)) { 0708 KTextEditor::Cursor scroll = viewLineOffset(c, -m_minLinesVisible); 0709 scrollPos(scroll, false, calledExternally); 0710 } else { 0711 // Check to see that we're not showing blank lines 0712 KTextEditor::Cursor max = maxStartPos(); 0713 if (startPos() > max) { 0714 scrollPos(max, max.column(), calledExternally); 0715 } 0716 } 0717 0718 if (!view()->dynWordWrap() && (endCol != -1 || view()->wrapCursor())) { 0719 KTextEditor::Cursor rc = toRealCursor(c); 0720 int sX = renderer()->cursorToX(cache()->textLayout(rc), rc, !view()->wrapCursor()); 0721 0722 int sXborder = sX - 8; 0723 if (sXborder < 0) { 0724 sXborder = 0; 0725 } 0726 0727 if (sX < startX()) { 0728 scrollColumns(sXborder); 0729 } else if (sX > startX() + width()) { 0730 scrollColumns(sX - width() + 8); 0731 } 0732 } 0733 0734 m_madeVisible = !force; 0735 0736 #ifndef QT_NO_ACCESSIBILITY 0737 // FIXME -- is this needed? 0738 // QAccessible::updateAccessibility(this, KateCursorAccessible::ChildId, QAccessible::Focus); 0739 #endif 0740 } 0741 0742 void KateViewInternal::slotRegionVisibilityChanged() 0743 { 0744 qCDebug(LOG_KTE); 0745 0746 // ensure the layout cache is ok for the updateCursor calls below 0747 // without the updateView() the view will jump to the bottom on hiding blocks after 0748 // change cfb0af25bdfac0d8f86b42db0b34a6bc9f9a361e 0749 cache()->clear(); 0750 updateView(); 0751 0752 m_cachedMaxStartPos.setLine(-1); 0753 KTextEditor::Cursor max = maxStartPos(); 0754 if (startPos() > max) { 0755 scrollPos(max, false, false, false /* don't emit signals! */); 0756 } 0757 0758 // if text was folded: make sure the cursor is on a visible line 0759 qint64 foldedRangeId = -1; 0760 if (!view()->textFolding().isLineVisible(m_cursor.line(), &foldedRangeId)) { 0761 KTextEditor::Range foldingRange = view()->textFolding().foldingRange(foldedRangeId); 0762 Q_ASSERT(foldingRange.start().isValid()); 0763 0764 // set cursor to start of folding region 0765 updateCursor(foldingRange.start(), true); 0766 } else { 0767 // force an update of the cursor, since otherwise the m_displayCursor 0768 // line may be below the total amount of visible lines. 0769 updateCursor(m_cursor, true); 0770 } 0771 0772 updateView(); 0773 update(); 0774 m_leftBorder->update(); 0775 0776 // emit signals here, scrollPos has this disabled, to ensure we do this after all stuff is updated! 0777 Q_EMIT view()->verticalScrollPositionChanged(m_view, max); 0778 Q_EMIT view()->displayRangeChanged(m_view); 0779 } 0780 0781 void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int) 0782 { 0783 qCDebug(LOG_KTE); 0784 // FIXME: performance problem 0785 m_leftBorder->update(); 0786 } 0787 0788 void KateViewInternal::showEvent(QShowEvent *e) 0789 { 0790 updateView(); 0791 0792 QWidget::showEvent(e); 0793 } 0794 0795 KTextEditor::Attribute::Ptr KateViewInternal::attributeAt(const KTextEditor::Cursor position) const 0796 { 0797 KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute()); 0798 Kate::TextLine kateLine = doc()->kateTextLine(position.line()); 0799 *attrib = *m_view->renderer()->attribute(kateLine.attribute(position.column())); 0800 return attrib; 0801 } 0802 0803 int KateViewInternal::linesDisplayed() const 0804 { 0805 int h = height(); 0806 0807 // catch zero heights, even if should not happen 0808 int fh = qMax(1, renderer()->lineHeight()); 0809 0810 // default to 1, there is always one line around.... 0811 // too many places calc with linesDisplayed() - 1 0812 return qMax(1, (h - (h % fh)) / fh); 0813 } 0814 0815 QPoint KateViewInternal::cursorToCoordinate(const KTextEditor::Cursor cursor, bool realCursor, bool includeBorder) const 0816 { 0817 if (cursor.line() >= doc()->lines()) { 0818 return QPoint(-1, -1); 0819 } 0820 0821 int viewLine = cache()->displayViewLine(realCursor ? toVirtualCursor(cursor) : cursor, true); 0822 0823 if (viewLine < 0 || viewLine >= cache()->viewCacheLineCount()) { 0824 return QPoint(-1, -1); 0825 } 0826 0827 const int y = (int)viewLine * renderer()->lineHeight(); 0828 0829 KateTextLayout layout = cache()->viewLine(viewLine); 0830 0831 const auto textLength = doc()->lineLength(cursor.line()); 0832 if (cursor.column() > textLength) { 0833 return QPoint(-1, -1); 0834 } 0835 0836 int x = 0; 0837 0838 // only set x value if we have a valid layout (bug #171027) 0839 if (layout.isValid()) { 0840 if (!layout.isRightToLeft() || (layout.isRightToLeft() && view()->dynWordWrap())) { 0841 x = (int)layout.lineLayout().cursorToX(cursor.column()); 0842 } else /* rtl + dynWordWrap == false */ { 0843 // if text is rtl and dynamic wrap is false, the x offsets are in the opposite 0844 // direction i.e., [0] == biggest offset, [1] = next 0845 x = (int)layout.lineLayout().cursorToX(textLength - cursor.column()); 0846 } 0847 } 0848 // else 0849 // qCDebug(LOG_KTE) << "Invalid Layout"; 0850 0851 if (includeBorder) { 0852 x += m_leftBorder->width(); 0853 } 0854 0855 x -= startX(); 0856 0857 return QPoint(x, y); 0858 } 0859 0860 QPoint KateViewInternal::cursorCoordinates(bool includeBorder) const 0861 { 0862 return cursorToCoordinate(m_displayCursor, false, includeBorder); 0863 } 0864 0865 KTextEditor::Cursor KateViewInternal::findMatchingBracket() 0866 { 0867 KTextEditor::Cursor c; 0868 0869 if (!m_bm->toRange().isValid()) { 0870 return KTextEditor::Cursor::invalid(); 0871 } 0872 0873 Q_ASSERT(m_bmEnd->toRange().isValid()); 0874 Q_ASSERT(m_bmStart->toRange().isValid()); 0875 0876 // For e.g. the text "{|}" (where | is the cursor), m_bmStart is equal to [ (0, 0) -> (0, 1) ] 0877 // and the closing bracket is in (0, 1). Thus, we check m_bmEnd first. 0878 if (m_bmEnd->toRange().contains(m_cursor) || m_bmEnd->end() == m_cursor.toCursor()) { 0879 c = m_bmStart->start(); 0880 } else if (m_bmStart->toRange().contains(m_cursor) || m_bmStart->end() == m_cursor.toCursor()) { 0881 c = m_bmEnd->end(); 0882 // We need to adjust the cursor position in case of override mode, BUG-402594 0883 if (doc()->config()->ovr()) { 0884 c.setColumn(c.column() - 1); 0885 } 0886 } else { 0887 // should never happen: a range exists, but the cursor position is 0888 // neither at the start nor at the end... 0889 return KTextEditor::Cursor::invalid(); 0890 } 0891 0892 return c; 0893 } 0894 0895 class CalculatingCursor 0896 { 0897 public: 0898 // These constructors constrain their arguments to valid positions 0899 // before only the third one did, but that leads to crashes 0900 // see bug 227449 0901 CalculatingCursor(KateViewInternal *vi) 0902 : m_vi(vi) 0903 { 0904 makeValid(); 0905 } 0906 0907 CalculatingCursor(KateViewInternal *vi, const KTextEditor::Cursor c) 0908 : m_cursor(c) 0909 , m_vi(vi) 0910 { 0911 makeValid(); 0912 } 0913 0914 CalculatingCursor(KateViewInternal *vi, int line, int col) 0915 : m_cursor(line, col) 0916 , m_vi(vi) 0917 { 0918 makeValid(); 0919 } 0920 0921 virtual ~CalculatingCursor() 0922 { 0923 } 0924 0925 int line() const 0926 { 0927 return m_cursor.line(); 0928 } 0929 0930 int column() const 0931 { 0932 return m_cursor.column(); 0933 } 0934 0935 operator KTextEditor::Cursor() const 0936 { 0937 return m_cursor; 0938 } 0939 0940 virtual CalculatingCursor &operator+=(int n) = 0; 0941 0942 virtual CalculatingCursor &operator-=(int n) = 0; 0943 0944 CalculatingCursor &operator++() 0945 { 0946 return operator+=(1); 0947 } 0948 0949 CalculatingCursor &operator--() 0950 { 0951 return operator-=(1); 0952 } 0953 0954 void makeValid() 0955 { 0956 m_cursor.setLine(qBound(0, line(), int(doc()->lines() - 1))); 0957 if (view()->wrapCursor()) { 0958 m_cursor.setColumn(qBound(0, column(), doc()->lineLength(line()))); 0959 } else { 0960 m_cursor.setColumn(qMax(0, column())); 0961 } 0962 Q_ASSERT(valid()); 0963 } 0964 0965 void toEdge(KateViewInternal::Bias bias) 0966 { 0967 if (bias == KateViewInternal::left) { 0968 m_cursor.setColumn(0); 0969 } else if (bias == KateViewInternal::right) { 0970 m_cursor.setColumn(doc()->lineLength(line())); 0971 } 0972 } 0973 0974 bool atEdge() const 0975 { 0976 return atEdge(KateViewInternal::left) || atEdge(KateViewInternal::right); 0977 } 0978 0979 bool atEdge(KateViewInternal::Bias bias) const 0980 { 0981 switch (bias) { 0982 case KateViewInternal::left: 0983 return column() == 0; 0984 case KateViewInternal::none: 0985 return atEdge(); 0986 case KateViewInternal::right: 0987 return column() >= doc()->lineLength(line()); 0988 default: 0989 Q_ASSERT(false); 0990 return false; 0991 } 0992 } 0993 0994 protected: 0995 bool valid() const 0996 { 0997 return line() >= 0 && line() < doc()->lines() && column() >= 0 && (!view()->wrapCursor() || column() <= doc()->lineLength(line())); 0998 } 0999 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 1019 class BoundedCursor final : public CalculatingCursor 1020 { 1021 public: 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 1092 class WrappingCursor final : public CalculatingCursor 1093 { 1094 public: 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 */ 1267 class CamelCursor final : public CalculatingCursor 1268 { 1269 public: 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 1429 void KateViewInternal::moveChar(KateViewInternal::Bias bias, bool sel) 1430 { 1431 KTextEditor::Cursor c; 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; 1439 QVarLengthArray<CursorPair, 16> multiCursors; 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 1465 void KateViewInternal::cursorPrevChar(bool sel) 1466 { 1467 if (!view()->wrapCursor() && m_cursor.column() == 0) { 1468 return; 1469 } 1470 1471 moveChar(KateViewInternal::left, sel); 1472 } 1473 1474 void KateViewInternal::cursorNextChar(bool sel) 1475 { 1476 moveChar(KateViewInternal::right, sel); 1477 } 1478 1479 void 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 1547 void 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 1611 void 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 1619 KTextEditor::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()) { 1636 return KTextEditor::Cursor::invalid(); 1637 } 1638 1639 Kate::TextLine l = doc()->kateTextLine(cursor.line()); 1640 1641 KTextEditor::Cursor c = cursor; 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 1652 void 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 1675 KTextEditor::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()) { 1694 return KTextEditor::Cursor::invalid(); 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())) { 1701 KTextEditor::Cursor c = cursor; 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 1711 void 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 1736 KateTextLayout KateViewInternal::currentLayout(KTextEditor::Cursor c) const 1737 { 1738 return cache()->textLayout(c); 1739 } 1740 1741 KateTextLayout 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 1752 KateTextLayout 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 1773 KTextEditor::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 1879 int 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 1895 int 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 1906 void 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 1995 void 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 2078 void 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 2088 void 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 2096 void 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 2105 void 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 2117 void KateViewInternal::scrollUp() 2118 { 2119 KTextEditor::Cursor newPos = viewLineOffset(startPos(), -1); 2120 scrollPos(newPos); 2121 } 2122 2123 void KateViewInternal::scrollDown() 2124 { 2125 KTextEditor::Cursor newPos = viewLineOffset(startPos(), 1); 2126 scrollPos(newPos); 2127 } 2128 2129 void 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 2138 void 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 2188 void 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 2239 int 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 2265 bool KateViewInternal::columnScrollingPossible() 2266 { 2267 return !view()->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maximum() > 0); 2268 } 2269 2270 bool KateViewInternal::lineScrollingPossible() 2271 { 2272 return m_lineScroll->minimum() != m_lineScroll->maximum(); 2273 } 2274 2275 void 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 2286 void 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 2297 void 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 2310 void 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 2323 void 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 2351 void 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 2480 void 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 2487 void 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 2508 KTextEditor::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 2564 return KTextEditor::Range::invalid(); 2565 } 2566 2567 void 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 2594 KTextEditor::Attribute::Ptr fill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); 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 2606 void 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 2630 QVarLengthArray<Range> ranges; 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 2650 void 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 2746 void 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 2786 updateMicroFocus(); 2787 2788 if (m_cursorTimer.isActive()) { 2789 if (QApplication::cursorFlashTime() > 0) { 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 2813 void KateViewInternal::updateBracketMarkAttributes() 2814 { 2815 KTextEditor::Attribute::Ptr bracketFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); 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()) { 2827 KTextEditor::Attribute::Ptr expressionFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); 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 2837 void 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 2891 bool 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 2897 bool KateViewInternal::tagLines(int start, int end, bool realLines) 2898 { 2899 return tagLines(KTextEditor::Cursor(start, 0), KTextEditor::Cursor(end, -1), realLines); 2900 } 2901 2902 bool 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 2979 bool KateViewInternal::tagRange(KTextEditor::Range range, bool realCursors) 2980 { 2981 return tagLines(range.start(), range.end(), realCursors); 2982 } 2983 2984 void KateViewInternal::tagAll() 2985 { 2986 // clear the cache... 2987 cache()->clear(); 2988 2989 m_leftBorder->updateFont(); 2990 m_leftBorder->update(); 2991 } 2992 2993 void 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 3011 KTextEditor::Cursor KateViewInternal::cursorForPoint(QPoint p) 3012 { 3013 KateTextLayout thisLine = yToKateTextLayout(p.y()); 3014 KTextEditor::Cursor c; 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()) { 3023 return KTextEditor::Cursor::invalid(); 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 ¬e : 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 3058 void 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 3080 bool 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 3092 bool 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 3106 case QEvent::ShortcutOverride: { 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 3183 case QEvent::WindowDeactivate: 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 3206 void 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 3330 void 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 3348 bool 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 3368 if ((e->modifiers() == Qt::ControlModifier) || (e->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier))) { 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 3376 void 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 3399 void 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()) { 3484 QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); 3485 if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) { 3486 QEvent event(QEvent::RequestSoftwareInputPanel); 3487 QApplication::sendEvent(this, &event); 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 3547 void 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 3631 void KateViewInternal::tripleClickTimeout() 3632 { 3633 m_possibleTripleClick = false; 3634 } 3635 3636 void KateViewInternal::beginSelectLine(const QPoint &pos) 3637 { 3638 placeCursor(pos); 3639 m_possibleTripleClick = true; // set so subsequent mousePressEvent will select line 3640 } 3641 3642 void 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()) { 3684 QString clipboard = QApplication::clipboard()->text(QClipboard::Selection); 3685 view()->paste(&clipboard); 3686 } 3687 3688 e->accept(); 3689 break; 3690 3691 default: 3692 e->ignore(); 3693 break; 3694 } 3695 } 3696 3697 void 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 3710 KTextEditor::Cursor KateViewInternal::coordinatesToCursor(const QPoint &_coord, bool includeBorder) const 3711 { 3712 QPoint coord(_coord); 3713 3714 KTextEditor::Cursor ret = KTextEditor::Cursor::invalid(); 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. 3729 return KTextEditor::Cursor::invalid(); 3730 } 3731 3732 return ret; 3733 } 3734 3735 void 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 3781 if (p.manhattanLength() > QApplication::startDragDistance()) { 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()) { 3846 QToolTip::hideText(); 3847 } 3848 m_textHintTimer.start(m_textHintDelay); 3849 m_textHintPos = e->pos(); 3850 } 3851 } 3852 } 3853 3854 void 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 3893 void KateViewInternal::hideEvent(QHideEvent *e) 3894 { 3895 Q_UNUSED(e); 3896 if (view()->isCompletionActive()) { 3897 view()->completionWidget()->abortCompletion(); 3898 } 3899 } 3900 3901 void 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 3995 void 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 4064 void 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 4071 QWidget::moveEvent(e); 4072 } 4073 4074 void 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 4083 void KateViewInternal::cursorTimeout() 4084 { 4085 if (!debugPainting && m_currentInputMode->blinkCaret()) { 4086 renderer()->setDrawCaret(!renderer()->drawCaret()); 4087 paintCursor(); 4088 } 4089 } 4090 4091 void 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()); 4119 QToolTip::showText(mapToGlobal(pos), hint); 4120 } 4121 } 4122 4123 void KateViewInternal::focusInEvent(QFocusEvent *) 4124 { 4125 if (QApplication::cursorFlashTime() > 0) { 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 4137 void 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 4153 void 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 4252 void 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 4260 void KateViewInternal::fixDropEvent(QDropEvent *event) 4261 { 4262 if (event->source() != this) { 4263 event->setDropAction(Qt::CopyAction); 4264 } else { 4265 Qt::DropAction action = Qt::MoveAction; 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 4279 void 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 4289 void 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 4355 void 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 4366 void 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 { 4391 offset *= QApplication::wheelScrollLines(); 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 4427 void 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 4436 void 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 4444 void KateViewInternal::startDragScroll() 4445 { 4446 if (!m_dragScrollTimer.isActive()) { 4447 m_dragScrollTimer.start(s_scrollTime); 4448 } 4449 } 4450 4451 void KateViewInternal::stopDragScroll() 4452 { 4453 m_dragScrollTimer.stop(); 4454 updateView(); 4455 } 4456 4457 void 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 4490 void 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 4500 void 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 4512 void KateViewInternal::setTextHintDelay(int delay) 4513 { 4514 if (delay <= 0) { 4515 m_textHintDelay = 200; // ms 4516 } else { 4517 m_textHintDelay = delay; // ms 4518 } 4519 } 4520 4521 int KateViewInternal::textHintDelay() const 4522 { 4523 return m_textHintDelay; 4524 } 4525 4526 bool KateViewInternal::textHintsEnabled() 4527 { 4528 return !m_textHintProviders.empty(); 4529 } 4530 4531 // BEGIN EDIT STUFF 4532 void 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 4545 void 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 4603 void KateViewInternal::editSetCursor(const KTextEditor::Cursor _cursor) 4604 { 4605 if (m_cursor.toCursor() != _cursor) { 4606 m_cursor.setPosition(_cursor); 4607 } 4608 } 4609 // END 4610 4611 void 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()); 4625 updateMicroFocus(); 4626 } 4627 4628 KateLayoutCache *KateViewInternal::cache() const 4629 { 4630 return m_layoutCache; 4631 } 4632 4633 KTextEditor::Cursor KateViewInternal::toRealCursor(const KTextEditor::Cursor virtualCursor) const 4634 { 4635 return KTextEditor::Cursor(view()->textFolding().visibleLineToLine(virtualCursor.line()), virtualCursor.column()); 4636 } 4637 4638 KTextEditor::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) { 4643 return KTextEditor::Cursor::invalid(); 4644 } 4645 4646 return KTextEditor::Cursor(view()->textFolding().lineToVisibleLine(realCursor.line()), realCursor.column()); 4647 } 4648 4649 KateRenderer *KateViewInternal::renderer() const 4650 { 4651 return view()->renderer(); 4652 } 4653 4654 void KateViewInternal::mouseMoved() 4655 { 4656 view()->notifyMousePositionChanged(m_mouse); 4657 view()->updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); 4658 } 4659 4660 void 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)); 4667 QAccessible::updateAccessibility(&ev); 4668 } 4669 #endif 4670 } 4671 4672 KTextEditor::DocumentPrivate *KateViewInternal::doc() 4673 { 4674 return m_view->doc(); 4675 } 4676 4677 KTextEditor::DocumentPrivate *KateViewInternal::doc() const 4678 { 4679 return m_view->doc(); 4680 } 4681 4682 bool 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 4696 QVariant 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 4716 case Qt::ImCursorPosition: 4717 return m_cursor.column(); 4718 4719 case Qt::ImAnchorPosition: 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 4728 case Qt::ImSurroundingText: 4729 return doc()->kateTextLine(m_cursor.line()).text(); 4730 4731 case Qt::ImCurrentSelection: 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 4745 void 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( 4757 doc()->newMovingRange(KTextEditor::Range(m_cursor, m_cursor), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight)); 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()); 4774 KTextEditor::Cursor removeEnd = start + KTextEditor::Cursor(0, e->replacementLength()); 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 4784 QKeyEvent ke(QEvent::KeyPress, 0, Qt::NoModifier, e->commitString()); 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 4806 if (QApplication::cursorFlashTime() > 0) { 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)); 4844 KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); 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 4866 void 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 4883 void 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 4926 void KateViewInternal::hideBracketMatchPreview() 4927 { 4928 m_bmPreview.reset(); 4929 } 4930 4931 void 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)); 4937 QAccessible::updateAccessibility(&ev); 4938 } 4939 #endif 4940 } 4941 4942 void 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); 4948 QAccessible::updateAccessibility(&ev); 4949 } 4950 #endif 4951 } 4952 4953 QRect KateViewInternal::inlineNoteRect(const KateInlineNoteData ¬eData) 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 4987 KateInlineNoteData 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 ¬e : 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 5003 bool 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) 5018 QGuiApplication::inputMethod()->invokeAction(QInputMethod::Click, cursorPos.column()); 5019 e->setAccepted(true); 5020 return true; 5021 } 5022 return false; 5023 } 5024 5025 void KateViewInternal::commitPreedit() 5026 { 5027 if (!m_imPreeditRange) { 5028 return; 5029 } 5030 5031 QGuiApplication::inputMethod()->commit(); 5032 } 5033 5034 #include "moc_kateviewinternal.cpp"