File indexing completed on 2024-05-05 16:18:27
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 "katetextanimation.h" 0030 #include "katetextpreview.h" 0031 #include "kateview.h" 0032 #include "kateviewaccessible.h" 0033 #include "kateviewhelpers.h" 0034 #include "spellcheck/spellingmenu.h" 0035 0036 #include <KCursor> 0037 #include <ktexteditor/documentcursor.h> 0038 #include <ktexteditor/inlinenoteprovider.h> 0039 #include <ktexteditor/movingrange.h> 0040 #include <ktexteditor/texthintinterface.h> 0041 0042 #include <QAccessible> 0043 #include <QApplication> 0044 #include <QClipboard> 0045 #include <QKeyEvent> 0046 #include <QLayout> 0047 #include <QMenu> 0048 #include <QMimeData> 0049 #include <QPainter> 0050 #include <QPixmap> 0051 #include <QScopeGuard> 0052 #include <QScroller> 0053 #include <QStyle> 0054 #include <QToolTip> 0055 0056 static const bool debugPainting = false; 0057 0058 class ZoomEventFilter 0059 { 0060 public: 0061 ZoomEventFilter() = default; 0062 0063 bool detectZoomingEvent(QWheelEvent *e, Qt::KeyboardModifiers modifier = Qt::ControlModifier) 0064 { 0065 Qt::KeyboardModifiers modState = e->modifiers(); 0066 if (modState == modifier) { 0067 if (m_lastWheelEvent.isValid()) { 0068 const qint64 deltaT = m_lastWheelEvent.elapsed(); 0069 // Pressing the specified modifier key within 200ms of the previous "unmodified" 0070 // wheelevent is not allowed to toggle on text zooming 0071 if (m_lastWheelEventUnmodified && deltaT < 200) { 0072 m_ignoreZoom = true; 0073 } else if (deltaT > 1000) { 0074 // the protection is kept active for 1s after the last wheel event 0075 // TODO: this value should be tuned, preferably by someone using 0076 // Ctrl+Wheel zooming frequently. 0077 m_ignoreZoom = false; 0078 } 0079 } else { 0080 // we can't say anything and have to assume there's nothing 0081 // accidental to the modifier being pressed. 0082 m_ignoreZoom = false; 0083 } 0084 m_lastWheelEventUnmodified = false; 0085 if (m_ignoreZoom) { 0086 // unset the modifier so the view scrollbars can handle the scroll 0087 // event and produce normal, not accelerated scrolling 0088 modState &= ~modifier; 0089 e->setModifiers(modState); 0090 } 0091 } else { 0092 // state is reset after any wheel event without the zoom modifier 0093 m_lastWheelEventUnmodified = true; 0094 m_ignoreZoom = false; 0095 } 0096 m_lastWheelEvent.start(); 0097 0098 // inform the caller whether this event is allowed to trigger text zooming. 0099 return !m_ignoreZoom && modState == modifier; 0100 } 0101 0102 protected: 0103 QElapsedTimer m_lastWheelEvent; 0104 bool m_ignoreZoom = false; 0105 bool m_lastWheelEventUnmodified = false; 0106 }; 0107 0108 KateViewInternal::KateViewInternal(KTextEditor::ViewPrivate *view) 0109 : QWidget(view) 0110 , editSessionNumber(0) 0111 , editIsRunning(false) 0112 , m_view(view) 0113 , m_cursor(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::MoveOnInsert) 0114 , m_mouse() 0115 , m_possibleTripleClick(false) 0116 , m_bm(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0117 , m_bmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0118 , m_bmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0119 , m_bmLastFlashPos(doc()->newMovingCursor(KTextEditor::Cursor::invalid())) 0120 0121 // folding marker 0122 , m_fmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0123 , m_fmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) 0124 0125 , m_dummy(nullptr) 0126 0127 // stay on cursor will avoid that the view scroll around on press return at beginning 0128 , m_startPos(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::StayOnInsert) 0129 0130 , m_visibleLineCount(0) 0131 , m_madeVisible(false) 0132 , m_shiftKeyPressed(false) 0133 , m_autoCenterLines(0) 0134 , m_minLinesVisible(0) 0135 , m_selChangedByUser(false) 0136 , m_selectAnchor(-1, -1) 0137 , m_selectionMode(Default) 0138 , m_layoutCache(new KateLayoutCache(renderer(), this)) 0139 , m_preserveX(false) 0140 , m_preservedX(0) 0141 , m_cachedMaxStartPos(-1, -1) 0142 , m_dragScrollTimer(this) 0143 , m_scrollTimer(this) 0144 , m_cursorTimer(this) 0145 , m_textHintTimer(this) 0146 , m_textHintDelay(500) 0147 , m_textHintPos(-1, -1) 0148 , m_imPreeditRange(nullptr) 0149 { 0150 // setup input modes 0151 Q_ASSERT(m_inputModes.size() == KTextEditor::EditorPrivate::self()->inputModeFactories().size()); 0152 m_inputModes[KTextEditor::View::NormalInputMode].reset( 0153 KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::NormalInputMode]->createInputMode(this)); 0154 m_inputModes[KTextEditor::View::ViInputMode].reset( 0155 KTextEditor::EditorPrivate::self()->inputModeFactories()[KTextEditor::View::ViInputMode]->createInputMode(this)); 0156 m_currentInputMode = m_inputModes[KTextEditor::View::NormalInputMode].get(); 0157 0158 setMinimumSize(0, 0); 0159 setAttribute(Qt::WA_OpaquePaintEvent); 0160 setAttribute(Qt::WA_InputMethodEnabled); 0161 0162 // invalidate m_selectionCached.start(), or keyb selection is screwed initially 0163 m_selectionCached = KTextEditor::Range::invalid(); 0164 0165 // bracket markers are only for this view and should not be printed 0166 m_bm->setView(m_view); 0167 m_bmStart->setView(m_view); 0168 m_bmEnd->setView(m_view); 0169 m_bm->setAttributeOnlyForViews(true); 0170 m_bmStart->setAttributeOnlyForViews(true); 0171 m_bmEnd->setAttributeOnlyForViews(true); 0172 0173 // use z depth defined in moving ranges interface 0174 m_bm->setZDepth(-1000.0); 0175 m_bmStart->setZDepth(-1000.0); 0176 m_bmEnd->setZDepth(-1000.0); 0177 0178 // update mark attributes 0179 updateBracketMarkAttributes(); 0180 0181 // 0182 // scrollbar for lines 0183 // 0184 m_lineScroll = new KateScrollBar(Qt::Vertical, this); 0185 m_lineScroll->show(); 0186 m_lineScroll->setTracking(true); 0187 m_lineScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); 0188 0189 // Hijack the line scroller's controls, so we can scroll nicely for word-wrap 0190 connect(m_lineScroll, &KateScrollBar::actionTriggered, this, &KateViewInternal::scrollAction); 0191 0192 auto viewScrollLinesSlot = qOverload<int>(&KateViewInternal::scrollLines); 0193 connect(m_lineScroll, &KateScrollBar::sliderMoved, this, viewScrollLinesSlot); 0194 connect(m_lineScroll, &KateScrollBar::sliderMMBMoved, this, viewScrollLinesSlot); 0195 connect(m_lineScroll, &KateScrollBar::valueChanged, this, viewScrollLinesSlot); 0196 0197 // 0198 // scrollbar for columns 0199 // 0200 m_columnScroll = new QScrollBar(Qt::Horizontal, m_view); 0201 m_scroller = QScroller::scroller(this); 0202 QScrollerProperties prop; 0203 prop.setScrollMetric(QScrollerProperties::DecelerationFactor, 0.3); 0204 prop.setScrollMetric(QScrollerProperties::MaximumVelocity, 1); 0205 prop.setScrollMetric(QScrollerProperties::AcceleratingFlickMaximumTime, 0.2); // Workaround for QTBUG-88249 (non-flick gestures recognized as accelerating flick) 0206 prop.setScrollMetric(QScrollerProperties::HorizontalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff); 0207 prop.setScrollMetric(QScrollerProperties::VerticalOvershootPolicy, QScrollerProperties::OvershootAlwaysOff); 0208 prop.setScrollMetric(QScrollerProperties::DragStartDistance, 0.0); 0209 m_scroller->setScrollerProperties(prop); 0210 #ifndef Q_OS_MACOS 0211 // On macOS the trackpad also emits touch events which are sometimes picked up by the flick 0212 // gesture recogniser registered by QScroller; this results in some odd scrolling behaviour 0213 // as described in bug #442060. Therefore it's better to not let the QScroller register 0214 // it on that platform. 0215 m_scroller->grabGesture(this); 0216 #endif 0217 0218 if (m_view->dynWordWrap()) { 0219 m_columnScroll->hide(); 0220 } else { 0221 m_columnScroll->show(); 0222 } 0223 0224 m_columnScroll->setTracking(true); 0225 m_startX = 0; 0226 0227 connect(m_columnScroll, &QScrollBar::valueChanged, this, &KateViewInternal::scrollColumns); 0228 0229 // bottom corner box 0230 m_dummy = new QWidget(m_view); 0231 m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); 0232 m_dummy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0233 0234 if (m_view->dynWordWrap()) { 0235 m_dummy->hide(); 0236 } else { 0237 m_dummy->show(); 0238 } 0239 0240 cache()->setWrap(m_view->dynWordWrap()); 0241 0242 // 0243 // iconborder ;) 0244 // 0245 m_leftBorder = new KateIconBorder(this, m_view); 0246 m_leftBorder->show(); 0247 0248 // update view if folding ranges change 0249 connect(&m_view->textFolding(), &Kate::TextFolding::foldingRangesChanged, this, &KateViewInternal::slotRegionVisibilityChanged); 0250 0251 m_displayCursor.setPosition(0, 0); 0252 0253 setAcceptDrops(true); 0254 0255 m_zoomEventFilter.reset(new ZoomEventFilter()); 0256 // event filter 0257 installEventFilter(this); 0258 0259 // set initial cursor 0260 m_mouseCursor = Qt::IBeamCursor; 0261 setCursor(m_mouseCursor); 0262 0263 // call mouseMoveEvent also if no mouse button is pressed 0264 setMouseTracking(true); 0265 0266 m_dragInfo.state = diNone; 0267 0268 // timers 0269 connect(&m_dragScrollTimer, &QTimer::timeout, this, &KateViewInternal::doDragScroll); 0270 0271 connect(&m_scrollTimer, &QTimer::timeout, this, &KateViewInternal::scrollTimeout); 0272 0273 connect(&m_cursorTimer, &QTimer::timeout, this, &KateViewInternal::cursorTimeout); 0274 0275 connect(&m_textHintTimer, &QTimer::timeout, this, &KateViewInternal::textHintTimeout); 0276 0277 // selection changed to set anchor 0278 connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged); 0279 0280 #ifndef QT_NO_ACCESSIBILITY 0281 QAccessible::installFactory(accessibleInterfaceFactory); 0282 #endif 0283 connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &KateViewInternal::documentTextInserted); 0284 connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateViewInternal::documentTextRemoved); 0285 0286 // update is called in KTextEditor::ViewPrivate, after construction and layout is over 0287 // but before any other kateviewinternal call 0288 } 0289 0290 KateViewInternal::~KateViewInternal() 0291 { 0292 // delete text animation object here, otherwise it updates the view in its destructor 0293 delete m_textAnimation; 0294 0295 #ifndef QT_NO_ACCESSIBILITY 0296 QAccessible::removeFactory(accessibleInterfaceFactory); 0297 #endif 0298 } 0299 0300 void KateViewInternal::dynWrapChanged() 0301 { 0302 m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); 0303 if (view()->dynWordWrap()) { 0304 m_columnScroll->hide(); 0305 m_dummy->hide(); 0306 0307 } else { 0308 // column scrollbar + bottom corner box 0309 m_columnScroll->show(); 0310 m_dummy->show(); 0311 } 0312 0313 cache()->setWrap(view()->dynWordWrap()); 0314 updateView(); 0315 0316 if (view()->dynWordWrap()) { 0317 scrollColumns(0); 0318 } 0319 0320 update(); 0321 } 0322 0323 KTextEditor::Cursor KateViewInternal::endPos() const 0324 { 0325 // Hrm, no lines laid out at all?? 0326 if (!cache()->viewCacheLineCount()) { 0327 return KTextEditor::Cursor(); 0328 } 0329 0330 for (int i = qMin(linesDisplayed() - 1, cache()->viewCacheLineCount() - 1); i >= 0; i--) { 0331 const KateTextLayout &thisLine = cache()->viewLine(i); 0332 0333 if (thisLine.line() == -1) { 0334 continue; 0335 } 0336 0337 if (thisLine.virtualLine() >= view()->textFolding().visibleLines()) { 0338 // Cache is too out of date 0339 return KTextEditor::Cursor(view()->textFolding().visibleLines() - 1, 0340 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1))); 0341 } 0342 0343 return KTextEditor::Cursor(thisLine.virtualLine(), thisLine.wrap() ? thisLine.endCol() - 1 : thisLine.endCol()); 0344 } 0345 0346 // can happen, if view is still invisible 0347 return KTextEditor::Cursor(); 0348 } 0349 0350 int KateViewInternal::endLine() const 0351 { 0352 return endPos().line(); 0353 } 0354 0355 KateTextLayout KateViewInternal::yToKateTextLayout(int y) const 0356 { 0357 if (y < 0 || y > size().height()) { 0358 return KateTextLayout::invalid(); 0359 } 0360 0361 int range = y / renderer()->lineHeight(); 0362 0363 // lineRanges is always bigger than 0, after the initial updateView call 0364 if (range >= 0 && range < cache()->viewCacheLineCount()) { 0365 return cache()->viewLine(range); 0366 } 0367 0368 return KateTextLayout::invalid(); 0369 } 0370 0371 int KateViewInternal::lineToY(int viewLine) const 0372 { 0373 return (viewLine - startLine()) * renderer()->lineHeight(); 0374 } 0375 0376 void KateViewInternal::slotIncFontSizes(qreal step) 0377 { 0378 renderer()->increaseFontSizes(step); 0379 } 0380 0381 void KateViewInternal::slotDecFontSizes(qreal step) 0382 { 0383 renderer()->decreaseFontSizes(step); 0384 } 0385 0386 void KateViewInternal::slotResetFontSizes() 0387 { 0388 renderer()->resetFontSizes(); 0389 } 0390 0391 /** 0392 * Line is the real line number to scroll to. 0393 */ 0394 void KateViewInternal::scrollLines(int line) 0395 { 0396 KTextEditor::Cursor newPos(line, 0); 0397 scrollPos(newPos); 0398 } 0399 0400 // This can scroll less than one true line 0401 void KateViewInternal::scrollViewLines(int offset) 0402 { 0403 KTextEditor::Cursor c = viewLineOffset(startPos(), offset); 0404 scrollPos(c); 0405 0406 bool blocked = m_lineScroll->blockSignals(true); 0407 m_lineScroll->setValue(startLine()); 0408 m_lineScroll->blockSignals(blocked); 0409 } 0410 0411 void KateViewInternal::scrollAction(int action) 0412 { 0413 switch (action) { 0414 case QAbstractSlider::SliderSingleStepAdd: 0415 scrollNextLine(); 0416 break; 0417 0418 case QAbstractSlider::SliderSingleStepSub: 0419 scrollPrevLine(); 0420 break; 0421 0422 case QAbstractSlider::SliderPageStepAdd: 0423 scrollNextPage(); 0424 break; 0425 0426 case QAbstractSlider::SliderPageStepSub: 0427 scrollPrevPage(); 0428 break; 0429 0430 case QAbstractSlider::SliderToMinimum: 0431 top_home(); 0432 break; 0433 0434 case QAbstractSlider::SliderToMaximum: 0435 bottom_end(); 0436 break; 0437 } 0438 } 0439 0440 void KateViewInternal::scrollNextPage() 0441 { 0442 scrollViewLines(qMax(linesDisplayed() - 1, 0)); 0443 } 0444 0445 void KateViewInternal::scrollPrevPage() 0446 { 0447 scrollViewLines(-qMax(linesDisplayed() - 1, 0)); 0448 } 0449 0450 void KateViewInternal::scrollPrevLine() 0451 { 0452 scrollViewLines(-1); 0453 } 0454 0455 void KateViewInternal::scrollNextLine() 0456 { 0457 scrollViewLines(1); 0458 } 0459 0460 KTextEditor::Cursor KateViewInternal::maxStartPos(bool changed) 0461 { 0462 cache()->setAcceptDirtyLayouts(true); 0463 0464 if (m_cachedMaxStartPos.line() == -1 || changed) { 0465 KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1, 0466 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1))); 0467 0468 if (view()->config()->scrollPastEnd()) { 0469 m_cachedMaxStartPos = viewLineOffset(end, -m_minLinesVisible); 0470 } else { 0471 m_cachedMaxStartPos = viewLineOffset(end, -(linesDisplayed() - 1)); 0472 } 0473 } 0474 0475 cache()->setAcceptDirtyLayouts(false); 0476 0477 return m_cachedMaxStartPos; 0478 } 0479 0480 // c is a virtual cursor 0481 void KateViewInternal::scrollPos(KTextEditor::Cursor &c, bool force, bool calledExternally, bool emitSignals) 0482 { 0483 if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) { 0484 return; 0485 } 0486 0487 if (c.line() < 0) { 0488 c.setLine(0); 0489 } 0490 0491 KTextEditor::Cursor limit = maxStartPos(); 0492 if (c > limit) { 0493 c = limit; 0494 0495 // Re-check we're not just scrolling to the same place 0496 if (!force && ((!view()->dynWordWrap() && c.line() == startLine()) || c == startPos())) { 0497 return; 0498 } 0499 } 0500 0501 int viewLinesScrolled = 0; 0502 0503 // only calculate if this is really used and useful, could be wrong here, please recheck 0504 // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on 0505 // try to get it really working ;) 0506 bool viewLinesScrolledUsable = !force && (c.line() >= startLine() - linesDisplayed() - 1) && (c.line() <= endLine() + linesDisplayed() + 1); 0507 0508 if (viewLinesScrolledUsable) { 0509 viewLinesScrolled = cache()->displayViewLine(c); 0510 } 0511 0512 m_startPos.setPosition(c); 0513 0514 // set false here but reversed if we return to makeVisible 0515 m_madeVisible = false; 0516 0517 if (viewLinesScrolledUsable) { 0518 int lines = linesDisplayed(); 0519 if (view()->textFolding().visibleLines() < lines) { 0520 KTextEditor::Cursor end(view()->textFolding().visibleLines() - 1, 0521 doc()->lineLength(view()->textFolding().visibleLineToLine(view()->textFolding().visibleLines() - 1))); 0522 lines = qMin(linesDisplayed(), cache()->displayViewLine(end) + 1); 0523 } 0524 0525 Q_ASSERT(lines >= 0); 0526 0527 if (!calledExternally && qAbs(viewLinesScrolled) < lines && 0528 // NOTE: on some machines we must update if the floating widget is visible 0529 // otherwise strange painting bugs may occur during scrolling... 0530 !((view()->m_messageWidgets[KTextEditor::Message::TopInView] && view()->m_messageWidgets[KTextEditor::Message::TopInView]->isVisible()) 0531 || (view()->m_messageWidgets[KTextEditor::Message::CenterInView] && view()->m_messageWidgets[KTextEditor::Message::CenterInView]->isVisible()) 0532 || (view()->m_messageWidgets[KTextEditor::Message::BottomInView] && view()->m_messageWidgets[KTextEditor::Message::BottomInView]->isVisible()))) { 0533 updateView(false, viewLinesScrolled); 0534 0535 int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight()); 0536 0537 // scroll excluding child widgets (floating notifications) 0538 scroll(0, scrollHeight, rect()); 0539 m_leftBorder->scroll(0, scrollHeight); 0540 0541 if (emitSignals) { 0542 Q_EMIT view()->verticalScrollPositionChanged(m_view, c); 0543 Q_EMIT view()->displayRangeChanged(m_view); 0544 } 0545 return; 0546 } 0547 } 0548 0549 updateView(); 0550 update(); 0551 m_leftBorder->update(); 0552 if (emitSignals) { 0553 Q_EMIT view()->verticalScrollPositionChanged(m_view, c); 0554 Q_EMIT view()->displayRangeChanged(m_view); 0555 } 0556 } 0557 0558 void KateViewInternal::scrollColumns(int x) 0559 { 0560 if (x < 0) { 0561 x = 0; 0562 } 0563 0564 if (x > m_columnScroll->maximum()) { 0565 x = m_columnScroll->maximum(); 0566 } 0567 0568 if (x == startX()) { 0569 return; 0570 } 0571 0572 int dx = startX() - x; 0573 m_startX = x; 0574 0575 if (qAbs(dx) < width()) { 0576 // scroll excluding child widgets (floating notifications) 0577 scroll(dx, 0, rect()); 0578 } else { 0579 update(); 0580 } 0581 0582 Q_EMIT view()->horizontalScrollPositionChanged(m_view); 0583 Q_EMIT view()->displayRangeChanged(m_view); 0584 0585 bool blocked = m_columnScroll->blockSignals(true); 0586 m_columnScroll->setValue(startX()); 0587 m_columnScroll->blockSignals(blocked); 0588 } 0589 0590 // If changed is true, the lines that have been set dirty have been updated. 0591 void KateViewInternal::updateView(bool changed, int viewLinesScrolled) 0592 { 0593 if (!isVisible() && !viewLinesScrolled && !changed) { 0594 return; // When this view is not visible, don't do anything 0595 } 0596 0597 view()->doc()->delayAutoReload(); // Don't reload while user scrolls around 0598 bool blocked = m_lineScroll->blockSignals(true); 0599 0600 int wrapWidth = width(); 0601 if (view()->config()->dynWrapAtStaticMarker() && view()->config()->dynWordWrap()) { 0602 // We need to transform char count to a pixel width, stolen from PrintPainter::updateCache() 0603 QString s; 0604 s.fill(QLatin1Char('5'), view()->doc()->config()->wordWrapAt()); 0605 wrapWidth = qMin(width(), static_cast<int>(renderer()->currentFontMetrics().boundingRect(s).width())); 0606 } 0607 0608 if (wrapWidth != cache()->viewWidth()) { 0609 cache()->setViewWidth(wrapWidth); 0610 changed = true; 0611 } 0612 0613 /* It was observed that height() could be negative here -- 0614 when the main Kate view has 0 as size (during creation), 0615 and there frame around KateViewInternal. In which 0616 case we'd set the view cache to 0 (or less!) lines, and 0617 start allocating huge chunks of data, later. */ 0618 int newSize = (qMax(0, height()) / renderer()->lineHeight()) + 1; 0619 cache()->updateViewCache(startPos(), newSize, viewLinesScrolled); 0620 m_visibleLineCount = newSize; 0621 0622 KTextEditor::Cursor maxStart = maxStartPos(changed); 0623 int maxLineScrollRange = maxStart.line(); 0624 if (view()->dynWordWrap() && maxStart.column() != 0) { 0625 maxLineScrollRange++; 0626 } 0627 m_lineScroll->setRange(0, maxLineScrollRange); 0628 0629 m_lineScroll->setValue(startLine()); 0630 m_lineScroll->setSingleStep(1); 0631 m_lineScroll->setPageStep(qMax(0, height()) / renderer()->lineHeight()); 0632 m_lineScroll->blockSignals(blocked); 0633 0634 KateViewConfig::ScrollbarMode show_scrollbars = static_cast<KateViewConfig::ScrollbarMode>(view()->config()->showScrollbars()); 0635 0636 bool visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (maxLineScrollRange != 0))); 0637 bool visible_dummy = visible; 0638 0639 m_lineScroll->setVisible(visible); 0640 0641 if (!view()->dynWordWrap()) { 0642 int max = maxLen(startLine()) - width(); 0643 if (max < 0) { 0644 max = 0; 0645 } 0646 0647 // if we lose the ability to scroll horizontally, move view to the far-left 0648 if (max == 0) { 0649 scrollColumns(0); 0650 } 0651 0652 blocked = m_columnScroll->blockSignals(true); 0653 0654 // disable scrollbar 0655 m_columnScroll->setDisabled(max == 0); 0656 0657 visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (max != 0))); 0658 visible_dummy &= visible; 0659 m_columnScroll->setVisible(visible); 0660 0661 m_columnScroll->setRange(0, max + (renderer()->spaceWidth() / 2)); // Add some space for the caret at EOL 0662 0663 m_columnScroll->setValue(startX()); 0664 0665 // Approximate linescroll 0666 m_columnScroll->setSingleStep(renderer()->currentFontMetrics().horizontalAdvance(QLatin1Char('a'))); 0667 m_columnScroll->setPageStep(width()); 0668 0669 m_columnScroll->blockSignals(blocked); 0670 } else { 0671 visible_dummy = false; 0672 } 0673 0674 m_dummy->setVisible(visible_dummy); 0675 0676 if (changed) { 0677 updateDirty(); 0678 } 0679 } 0680 0681 /** 0682 * this function ensures a certain location is visible on the screen. 0683 * if endCol is -1, ignore making the columns visible. 0684 */ 0685 void KateViewInternal::makeVisible(const KTextEditor::Cursor c, int endCol, bool force, bool center, bool calledExternally) 0686 { 0687 // qCDebug(LOG_KTE) << "MakeVisible start " << startPos() << " end " << endPos() << " -> request: " << c;// , new start [" << scroll.line << "," << 0688 // scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height(); if the line is in a folded region, unfold all the way up if ( 0689 // doc()->foldingTree()->findNodeForLine( c.line )->visible ) 0690 // qCDebug(LOG_KTE)<<"line ("<<c.line<<") should be visible"; 0691 0692 const int lnDisp = linesDisplayed(); 0693 const int viewLine = cache()->displayViewLine(c, true); 0694 const bool curBelowScreen = (viewLine == -2); 0695 0696 if (force) { 0697 KTextEditor::Cursor scroll = c; 0698 scrollPos(scroll, force, calledExternally); 0699 } else if (center && (c < startPos() || c > endPos())) { 0700 KTextEditor::Cursor scroll = viewLineOffset(c, -int(lnDisp) / 2); 0701 scrollPos(scroll, false, calledExternally); 0702 } else if ((viewLine >= (lnDisp - m_minLinesVisible)) || (curBelowScreen)) { 0703 KTextEditor::Cursor scroll = viewLineOffset(c, -(lnDisp - m_minLinesVisible - 1)); 0704 scrollPos(scroll, false, calledExternally); 0705 } else if (c < viewLineOffset(startPos(), m_minLinesVisible)) { 0706 KTextEditor::Cursor scroll = viewLineOffset(c, -m_minLinesVisible); 0707 scrollPos(scroll, false, calledExternally); 0708 } else { 0709 // Check to see that we're not showing blank lines 0710 KTextEditor::Cursor max = maxStartPos(); 0711 if (startPos() > max) { 0712 scrollPos(max, max.column(), calledExternally); 0713 } 0714 } 0715 0716 if (!view()->dynWordWrap() && (endCol != -1 || view()->wrapCursor())) { 0717 KTextEditor::Cursor rc = toRealCursor(c); 0718 int sX = renderer()->cursorToX(cache()->textLayout(rc), rc, !view()->wrapCursor()); 0719 0720 int sXborder = sX - 8; 0721 if (sXborder < 0) { 0722 sXborder = 0; 0723 } 0724 0725 if (sX < startX()) { 0726 scrollColumns(sXborder); 0727 } else if (sX > startX() + width()) { 0728 scrollColumns(sX - width() + 8); 0729 } 0730 } 0731 0732 m_madeVisible = !force; 0733 0734 #ifndef QT_NO_ACCESSIBILITY 0735 // FIXME -- is this needed? 0736 // QAccessible::updateAccessibility(this, KateCursorAccessible::ChildId, QAccessible::Focus); 0737 #endif 0738 } 0739 0740 void KateViewInternal::slotRegionVisibilityChanged() 0741 { 0742 qCDebug(LOG_KTE); 0743 0744 // ensure the layout cache is ok for the updateCursor calls below 0745 // without the updateView() the view will jump to the bottom on hiding blocks after 0746 // change cfb0af25bdfac0d8f86b42db0b34a6bc9f9a361e 0747 cache()->clear(); 0748 updateView(); 0749 0750 m_cachedMaxStartPos.setLine(-1); 0751 KTextEditor::Cursor max = maxStartPos(); 0752 if (startPos() > max) { 0753 scrollPos(max, false, false, false /* don't emit signals! */); 0754 } 0755 0756 // if text was folded: make sure the cursor is on a visible line 0757 qint64 foldedRangeId = -1; 0758 if (!view()->textFolding().isLineVisible(m_cursor.line(), &foldedRangeId)) { 0759 KTextEditor::Range foldingRange = view()->textFolding().foldingRange(foldedRangeId); 0760 Q_ASSERT(foldingRange.start().isValid()); 0761 0762 // set cursor to start of folding region 0763 updateCursor(foldingRange.start(), true); 0764 } else { 0765 // force an update of the cursor, since otherwise the m_displayCursor 0766 // line may be below the total amount of visible lines. 0767 updateCursor(m_cursor, true); 0768 } 0769 0770 updateView(); 0771 update(); 0772 m_leftBorder->update(); 0773 0774 // emit signals here, scrollPos has this disabled, to ensure we do this after all stuff is updated! 0775 Q_EMIT view()->verticalScrollPositionChanged(m_view, max); 0776 Q_EMIT view()->displayRangeChanged(m_view); 0777 } 0778 0779 void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int) 0780 { 0781 qCDebug(LOG_KTE); 0782 // FIXME: performance problem 0783 m_leftBorder->update(); 0784 } 0785 0786 void KateViewInternal::showEvent(QShowEvent *e) 0787 { 0788 updateView(); 0789 0790 QWidget::showEvent(e); 0791 } 0792 0793 KTextEditor::Attribute::Ptr KateViewInternal::attributeAt(const KTextEditor::Cursor position) const 0794 { 0795 KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute()); 0796 0797 Kate::TextLine kateLine = doc()->kateTextLine(position.line()); 0798 if (!kateLine) { 0799 return attrib; 0800 } 0801 0802 *attrib = *m_view->renderer()->attribute(kateLine->attribute(position.column())); 0803 0804 return attrib; 0805 } 0806 0807 int KateViewInternal::linesDisplayed() const 0808 { 0809 int h = height(); 0810 0811 // catch zero heights, even if should not happen 0812 int fh = qMax(1, renderer()->lineHeight()); 0813 0814 // default to 1, there is always one line around.... 0815 // too many places calc with linesDisplayed() - 1 0816 return qMax(1, (h - (h % fh)) / fh); 0817 } 0818 0819 QPoint KateViewInternal::cursorToCoordinate(const KTextEditor::Cursor cursor, bool realCursor, bool includeBorder) const 0820 { 0821 if (cursor.line() >= doc()->lines()) { 0822 return QPoint(-1, -1); 0823 } 0824 0825 int viewLine = cache()->displayViewLine(realCursor ? toVirtualCursor(cursor) : cursor, true); 0826 0827 if (viewLine < 0 || viewLine >= cache()->viewCacheLineCount()) { 0828 return QPoint(-1, -1); 0829 } 0830 0831 const int y = (int)viewLine * renderer()->lineHeight(); 0832 0833 KateTextLayout layout = cache()->viewLine(viewLine); 0834 0835 if (cursor.column() > doc()->lineLength(cursor.line())) { 0836 return QPoint(-1, -1); 0837 } 0838 0839 int x = 0; 0840 0841 // only set x value if we have a valid layout (bug #171027) 0842 if (layout.isValid()) { 0843 x = (int)layout.lineLayout().cursorToX(cursor.column()); 0844 } 0845 // else 0846 // qCDebug(LOG_KTE) << "Invalid Layout"; 0847 0848 if (includeBorder) { 0849 x += m_leftBorder->width(); 0850 } 0851 0852 x -= startX(); 0853 0854 return QPoint(x, y); 0855 } 0856 0857 QPoint KateViewInternal::cursorCoordinates(bool includeBorder) const 0858 { 0859 return cursorToCoordinate(m_displayCursor, false, includeBorder); 0860 } 0861 0862 KTextEditor::Cursor KateViewInternal::findMatchingBracket() 0863 { 0864 KTextEditor::Cursor c; 0865 0866 if (!m_bm->toRange().isValid()) { 0867 return KTextEditor::Cursor::invalid(); 0868 } 0869 0870 Q_ASSERT(m_bmEnd->toRange().isValid()); 0871 Q_ASSERT(m_bmStart->toRange().isValid()); 0872 0873 // For e.g. the text "{|}" (where | is the cursor), m_bmStart is equal to [ (0, 0) -> (0, 1) ] 0874 // and the closing bracket is in (0, 1). Thus, we check m_bmEnd first. 0875 if (m_bmEnd->toRange().contains(m_cursor) || m_bmEnd->end() == m_cursor.toCursor()) { 0876 c = m_bmStart->start(); 0877 } else if (m_bmStart->toRange().contains(m_cursor) || m_bmStart->end() == m_cursor.toCursor()) { 0878 c = m_bmEnd->end(); 0879 // We need to adjust the cursor position in case of override mode, BUG-402594 0880 if (doc()->config()->ovr()) { 0881 c.setColumn(c.column() - 1); 0882 } 0883 } else { 0884 // should never happen: a range exists, but the cursor position is 0885 // neither at the start nor at the end... 0886 return KTextEditor::Cursor::invalid(); 0887 } 0888 0889 return c; 0890 } 0891 0892 class CalculatingCursor 0893 { 0894 public: 0895 // These constructors constrain their arguments to valid positions 0896 // before only the third one did, but that leads to crashes 0897 // see bug 227449 0898 CalculatingCursor(KateViewInternal *vi) 0899 : m_vi(vi) 0900 { 0901 makeValid(); 0902 } 0903 0904 CalculatingCursor(KateViewInternal *vi, const KTextEditor::Cursor c) 0905 : m_cursor(c) 0906 , m_vi(vi) 0907 { 0908 makeValid(); 0909 } 0910 0911 CalculatingCursor(KateViewInternal *vi, int line, int col) 0912 : m_cursor(line, col) 0913 , m_vi(vi) 0914 { 0915 makeValid(); 0916 } 0917 0918 virtual ~CalculatingCursor() 0919 { 0920 } 0921 0922 int line() const 0923 { 0924 return m_cursor.line(); 0925 } 0926 0927 int column() const 0928 { 0929 return m_cursor.column(); 0930 } 0931 0932 operator KTextEditor::Cursor() const 0933 { 0934 return m_cursor; 0935 } 0936 0937 virtual CalculatingCursor &operator+=(int n) = 0; 0938 0939 virtual CalculatingCursor &operator-=(int n) = 0; 0940 0941 CalculatingCursor &operator++() 0942 { 0943 return operator+=(1); 0944 } 0945 0946 CalculatingCursor &operator--() 0947 { 0948 return operator-=(1); 0949 } 0950 0951 void makeValid() 0952 { 0953 m_cursor.setLine(qBound(0, line(), int(doc()->lines() - 1))); 0954 if (view()->wrapCursor()) { 0955 m_cursor.setColumn(qBound(0, column(), doc()->lineLength(line()))); 0956 } else { 0957 m_cursor.setColumn(qMax(0, column())); 0958 } 0959 Q_ASSERT(valid()); 0960 } 0961 0962 void toEdge(KateViewInternal::Bias bias) 0963 { 0964 if (bias == KateViewInternal::left) { 0965 m_cursor.setColumn(0); 0966 } else if (bias == KateViewInternal::right) { 0967 m_cursor.setColumn(doc()->lineLength(line())); 0968 } 0969 } 0970 0971 bool atEdge() const 0972 { 0973 return atEdge(KateViewInternal::left) || atEdge(KateViewInternal::right); 0974 } 0975 0976 bool atEdge(KateViewInternal::Bias bias) const 0977 { 0978 switch (bias) { 0979 case KateViewInternal::left: 0980 return column() == 0; 0981 case KateViewInternal::none: 0982 return atEdge(); 0983 case KateViewInternal::right: 0984 return column() >= doc()->lineLength(line()); 0985 default: 0986 Q_ASSERT(false); 0987 return false; 0988 } 0989 } 0990 0991 protected: 0992 bool valid() const 0993 { 0994 return line() >= 0 && line() < doc()->lines() && column() >= 0 && (!view()->wrapCursor() || column() <= doc()->lineLength(line())); 0995 } 0996 KTextEditor::ViewPrivate *view() 0997 { 0998 return m_vi->m_view; 0999 } 1000 const KTextEditor::ViewPrivate *view() const 1001 { 1002 return m_vi->m_view; 1003 } 1004 KTextEditor::DocumentPrivate *doc() 1005 { 1006 return view()->doc(); 1007 } 1008 const KTextEditor::DocumentPrivate *doc() const 1009 { 1010 return view()->doc(); 1011 } 1012 KTextEditor::Cursor m_cursor; 1013 KateViewInternal *m_vi; 1014 }; 1015 1016 class BoundedCursor final : public CalculatingCursor 1017 { 1018 public: 1019 BoundedCursor(KateViewInternal *vi) 1020 : CalculatingCursor(vi) 1021 { 1022 } 1023 BoundedCursor(KateViewInternal *vi, const KTextEditor::Cursor c) 1024 : CalculatingCursor(vi, c) 1025 { 1026 } 1027 BoundedCursor(KateViewInternal *vi, int line, int col) 1028 : CalculatingCursor(vi, line, col) 1029 { 1030 } 1031 CalculatingCursor &operator+=(int n) override 1032 { 1033 KateLineLayoutPtr thisLine = m_vi->cache()->line(line()); 1034 if (!thisLine->isValid()) { 1035 qCWarning(LOG_KTE) << "Did not retrieve valid layout for line " << line(); 1036 return *this; 1037 } 1038 1039 const bool wrapCursor = view()->wrapCursor(); 1040 int maxColumn = -1; 1041 if (n >= 0) { 1042 for (int i = 0; i < n; i++) { 1043 if (column() >= thisLine->length()) { 1044 if (wrapCursor) { 1045 break; 1046 1047 } else if (view()->dynWordWrap()) { 1048 // Don't go past the edge of the screen in dynamic wrapping mode 1049 if (maxColumn == -1) { 1050 maxColumn = thisLine->length() + ((m_vi->width() - thisLine->widthOfLastLine()) / m_vi->renderer()->spaceWidth()) - 1; 1051 } 1052 1053 if (column() >= maxColumn) { 1054 m_cursor.setColumn(maxColumn); 1055 break; 1056 } 1057 1058 m_cursor.setColumn(column() + 1); 1059 1060 } else { 1061 m_cursor.setColumn(column() + 1); 1062 } 1063 1064 } else { 1065 m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column())); 1066 } 1067 } 1068 } else { 1069 for (int i = 0; i > n; i--) { 1070 if (column() >= thisLine->length()) { 1071 m_cursor.setColumn(column() - 1); 1072 } else if (column() == 0) { 1073 break; 1074 } else { 1075 m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column())); 1076 } 1077 } 1078 } 1079 1080 Q_ASSERT(valid()); 1081 return *this; 1082 } 1083 CalculatingCursor &operator-=(int n) override 1084 { 1085 return operator+=(-n); 1086 } 1087 }; 1088 1089 class WrappingCursor final : public CalculatingCursor 1090 { 1091 public: 1092 WrappingCursor(KateViewInternal *vi) 1093 : CalculatingCursor(vi) 1094 { 1095 } 1096 WrappingCursor(KateViewInternal *vi, const KTextEditor::Cursor c) 1097 : CalculatingCursor(vi, c) 1098 { 1099 } 1100 WrappingCursor(KateViewInternal *vi, int line, int col) 1101 : CalculatingCursor(vi, line, col) 1102 { 1103 } 1104 1105 CalculatingCursor &operator+=(int n) override 1106 { 1107 KateLineLayoutPtr thisLine = m_vi->cache()->line(line()); 1108 if (!thisLine->isValid()) { 1109 qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line(); 1110 return *this; 1111 } 1112 1113 if (n >= 0) { 1114 for (int i = 0; i < n; i++) { 1115 if (column() >= thisLine->length()) { 1116 // Have come to the end of a line 1117 if (line() >= doc()->lines() - 1) 1118 // Have come to the end of the document 1119 { 1120 break; 1121 } 1122 1123 // Advance to the beginning of the next line 1124 m_cursor.setColumn(0); 1125 m_cursor.setLine(line() + 1); 1126 1127 // Retrieve the next text range 1128 thisLine = m_vi->cache()->line(line()); 1129 if (!thisLine->isValid()) { 1130 qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line(); 1131 return *this; 1132 } 1133 1134 continue; 1135 } 1136 1137 m_cursor.setColumn(thisLine->layout()->nextCursorPosition(column())); 1138 } 1139 1140 } else { 1141 for (int i = 0; i > n; i--) { 1142 if (column() == 0) { 1143 // Have come to the start of the document 1144 if (line() == 0) { 1145 break; 1146 } 1147 1148 // Start going back to the end of the last line 1149 m_cursor.setLine(line() - 1); 1150 1151 // Retrieve the next text range 1152 thisLine = m_vi->cache()->line(line()); 1153 if (!thisLine->isValid()) { 1154 qCWarning(LOG_KTE) << "Did not retrieve a valid layout for line " << line(); 1155 return *this; 1156 } 1157 1158 // Finish going back to the end of the last line 1159 m_cursor.setColumn(thisLine->length()); 1160 1161 continue; 1162 } 1163 1164 if (column() > thisLine->length()) { 1165 m_cursor.setColumn(column() - 1); 1166 } else { 1167 m_cursor.setColumn(thisLine->layout()->previousCursorPosition(column())); 1168 } 1169 } 1170 } 1171 1172 Q_ASSERT(valid()); 1173 return *this; 1174 } 1175 CalculatingCursor &operator-=(int n) override 1176 { 1177 return operator+=(-n); 1178 } 1179 }; 1180 1181 /** 1182 * @brief The CamelCursor class 1183 * 1184 * This class allows for "camel humps" when moving the cursor 1185 * using Ctrl + Left / Right. Similarly, this will also get triggered 1186 * when you press Ctrl+Shift+Left/Right for selection and Ctrl+Del 1187 * Ctrl + backspace for deletion. 1188 * 1189 * It is absoloutely essential that if you move through a word in 'n' 1190 * jumps, you should be able to move back with exactly same 'n' movements 1191 * which you made when moving forward. Example: 1192 * 1193 * Word: KateViewInternal 1194 * 1195 * Moving cursor towards right while holding control will result in cursor 1196 * landing in the following places (assuming you start from column 0) 1197 * 1198 * | | | 1199 * KateViewInternal 1200 * 1201 * Moving the cursor back to get to the starting position should also 1202 * take exactly 3 movements: 1203 * 1204 * | | | 1205 * KateViewInternal 1206 * 1207 * In addition to simple camel case, this class also handles snake_case 1208 * capitalized snake, and mixtures of Camel + snake/underscore, for example 1209 * m_someMember. If a word has underscores in it, for example: 1210 * 1211 * snake_case_word 1212 * 1213 * the leading underscore is considered part of the word and thus a cursor 1214 * movement will land right after the underscore. Moving the cursor to end 1215 * for the above word should be like this: 1216 * 1217 * startpos: 0 1218 * | | | 1219 * snake_case_word 1220 * 1221 * When jumping back to startpos, exact same "spots" need to be hit on your way 1222 * back. 1223 * 1224 * If a word has multiple leading underscores: snake___case, the underscores will 1225 * be considered part of the word and thus a jump wil land us "after" the last underscore. 1226 * 1227 * There are some other variations in this, for example Capitalized words, or identifiers 1228 * with numbers in betweens. For such cases, All capitalized words are skipped in one go 1229 * till we are on some "non-capitalized" word. In this context, a number is a non-capitalized 1230 * word so will break the jump. Examples: 1231 * 1232 * | | 1233 * W1RD 1234 * 1235 * but for WORD_CAPITAL, following will happen: 1236 * 1237 * | | 1238 * WORD_CAPITAL 1239 * 1240 * The first case here is tricky to handle for reverse movement. I haven't noticed any 1241 * cases which the current implementation is unable to handle but there might be some. 1242 * 1243 * With languages like PHP where you have $ as part of the identifier, the cursor jump 1244 * will break "after" dollar. Consider: $phpVar, Following will happen: 1245 * 1246 * | | | 1247 * $phpVar 1248 * 1249 * And of course, the reverse will be exact opposite. 1250 * 1251 * Similar to PHP, with CSS Colors, jump will break on '#' charachter 1252 * 1253 * Please see the test cases testWordMovementSingleRow() for more examples/data. 1254 * 1255 * It is absoloutely essential to know that this class *only* gets triggered for 1256 * cursor movement if we are in a word. 1257 * 1258 * Note to bugfixer: If some bug occurs, before changing anything please add a test 1259 * case for the bug and make sure everything passes before and after. The test case 1260 * for this can be found autotests/src/camelcursortest.cpp. 1261 * 1262 * @author Waqar Ahmed <waqar.17a@gmail.com> 1263 */ 1264 class CamelCursor final : public CalculatingCursor 1265 { 1266 public: 1267 CamelCursor(KateViewInternal *vi, const KTextEditor::Cursor c) 1268 : CalculatingCursor(vi, c) 1269 { 1270 } 1271 1272 CalculatingCursor &operator+=(int n) override 1273 { 1274 KateLineLayoutPtr thisLine = m_vi->cache()->line(line()); 1275 if (!thisLine->isValid()) { 1276 qCWarning(LOG_KTE) << "Did not retrieve valid layout for line " << line(); 1277 return *this; 1278 } 1279 1280 auto isSurrogate = [](QChar c) { 1281 return c.isLowSurrogate() || c.isHighSurrogate(); 1282 }; 1283 1284 if (n >= 0) { 1285 auto skipCaps = [](QStringView text, int &col) { 1286 int count = 0; 1287 while (col < text.size() && text.at(col).isUpper()) { 1288 ++count; 1289 ++col; 1290 } 1291 // if this is a letter, then it means we are in the 1292 // middle of a word, step back one position so that 1293 // we are at the last Cap letter 1294 // Otherwise, it's an all cap word 1295 if (count > 1 && col < text.size() && text.at(col).isLetterOrNumber()) { 1296 --col; 1297 } 1298 }; 1299 1300 int jump = -1; 1301 int col = column(); 1302 const QString &text = thisLine->textLine()->text(); 1303 1304 if (col < text.size() && text.at(col).isUpper()) { 1305 skipCaps(text, col); 1306 } 1307 1308 for (int i = col; i < thisLine->length(); ++i) { 1309 const auto c = text.at(i); 1310 if (isSurrogate(c)) { 1311 col++; 1312 continue; 1313 } else if (c.isUpper() || !c.isLetterOrNumber()) { 1314 break; 1315 } 1316 ++col; 1317 } 1318 1319 // eat any '_' that are after the word BEFORE any space happens 1320 if (col < text.size() && text.at(col) == QLatin1Char('_')) { 1321 while (col < text.size() && text.at(col) == QLatin1Char('_')) { 1322 ++col; 1323 } 1324 } 1325 1326 // Underscores eaten, so now eat any spaces till next word 1327 if (col < text.size() && text.at(col).isSpace()) { 1328 while (col < text.size() && text.at(col).isSpace()) { 1329 ++col; 1330 } 1331 } 1332 1333 jump = col < 0 || (column() == col) ? (column() + 1) : col; 1334 m_cursor.setColumn(jump); 1335 } else { 1336 int jump = -1; 1337 1338 auto skipCapsRev = [](QStringView text, int &col) { 1339 int count = 0; 1340 while (col > 0 && text.at(col).isUpper()) { 1341 ++count; 1342 --col; 1343 } 1344 1345 // if more than one cap found, and current 1346 // column is not upper, we want to move ahead 1347 // to the upper 1348 if (count >= 1 && col >= 0 && !text.at(col).isUpper()) { 1349 ++col; 1350 } 1351 }; 1352 1353 const QString &text = thisLine->textLine()->text(); 1354 int col = std::min<int>(column(), text.size() - 1); 1355 // decrement because we might be standing at a camel hump 1356 // already and don't want to return the same position 1357 col = col - 1; 1358 1359 // if we are at the end of line 1360 if (column() == text.size()) { 1361 // there is a letter before 1362 if (text.at(col + 1).isLetter()) { 1363 // and a space before that 1364 if (col >= 0 && text.at(col).isSpace()) { 1365 // we have a one letter word, 1366 // so move forward to end of word 1367 ++col; 1368 } 1369 } 1370 } 1371 1372 // skip any spaces 1373 if (col > 0 && text.at(col).isSpace()) { 1374 while (text.at(col).isSpace() && col > 0) { 1375 --col; 1376 } 1377 } 1378 1379 // Skip Underscores 1380 if (col > 0 && text.at(col) == QLatin1Char('_')) { 1381 while (col > 0 && text.at(col) == QLatin1Char('_')) { 1382 --col; 1383 } 1384 } 1385 1386 if (col > 0 && text.at(col).isUpper()) { 1387 skipCapsRev(text, col); 1388 } 1389 1390 for (int i = col; i > 0; --i) { 1391 const auto c = text.at(i); 1392 if (isSurrogate(c)) { 1393 --col; 1394 continue; 1395 } else if (c.isUpper() || !c.isLetterOrNumber()) { 1396 break; 1397 } 1398 --col; 1399 } 1400 1401 if (col >= 0 && !text.at(col).isLetterOrNumber() && !isSurrogate(text.at(col))) { 1402 ++col; 1403 } 1404 1405 if (col < 0) { 1406 jump = 0; 1407 } else if (col == column() && column() > 0) { 1408 jump = column() - 1; 1409 } else { 1410 jump = col; 1411 } 1412 1413 m_cursor.setColumn(jump); 1414 } 1415 1416 Q_ASSERT(valid()); 1417 return *this; 1418 } 1419 1420 CalculatingCursor &operator-=(int n) override 1421 { 1422 return operator+=(-n); 1423 } 1424 }; 1425 1426 void KateViewInternal::moveChar(KateViewInternal::Bias bias, bool sel) 1427 { 1428 KTextEditor::Cursor c; 1429 if (view()->wrapCursor()) { 1430 c = WrappingCursor(this, m_cursor) += bias; 1431 } else { 1432 c = BoundedCursor(this, m_cursor) += bias; 1433 } 1434 1435 const auto &sc = view()->m_secondaryCursors; 1436 QVarLengthArray<CursorPair, 16> multiCursors; 1437 const int lastLine = doc()->lastLine(); 1438 bool shouldEnsureUniqueCursors = false; 1439 for (const auto &c : sc) { 1440 auto oldPos = c.cursor(); 1441 if (view()->wrapCursor()) { 1442 c.pos->setPosition(WrappingCursor(this, oldPos) += bias); 1443 } else { 1444 c.pos->setPosition(BoundedCursor(this, oldPos) += bias); 1445 } 1446 const auto newPos = c.pos->toCursor(); 1447 multiCursors.push_back({oldPos, newPos}); 1448 // We only need to do this if cursors were in first or last line 1449 if (!shouldEnsureUniqueCursors) { 1450 shouldEnsureUniqueCursors = newPos.line() == 0 || newPos.line() == lastLine; 1451 } 1452 } 1453 1454 updateSelection(c, sel); 1455 updateCursor(c); 1456 updateSecondaryCursors(multiCursors, sel); 1457 if (shouldEnsureUniqueCursors) { 1458 view()->ensureUniqueCursors(); 1459 } 1460 } 1461 1462 void KateViewInternal::cursorPrevChar(bool sel) 1463 { 1464 if (!view()->wrapCursor() && m_cursor.column() == 0) { 1465 return; 1466 } 1467 1468 moveChar(KateViewInternal::left, sel); 1469 } 1470 1471 void KateViewInternal::cursorNextChar(bool sel) 1472 { 1473 moveChar(KateViewInternal::right, sel); 1474 } 1475 1476 void KateViewInternal::wordPrev(bool sel) 1477 { 1478 auto characterAtPreviousColumn = [this](KTextEditor::Cursor cursor) -> QChar { 1479 return doc()->characterAt({cursor.line(), cursor.column() - 1}); 1480 }; 1481 1482 auto wordPrevious = [this, &characterAtPreviousColumn](KTextEditor::Cursor cursor) -> KTextEditor::Cursor { 1483 WrappingCursor c(this, cursor); 1484 1485 // First we skip backwards all space. 1486 // Then we look up into which category the current position falls: 1487 // 1. a "word" character 1488 // 2. a "non-word" character (except space) 1489 // 3. the beginning of the line 1490 // and skip all preceding characters that fall into this class. 1491 // The code assumes that space is never part of the word character class. 1492 1493 KateHighlighting *h = doc()->highlight(); 1494 1495 while (!c.atEdge(left) && (c.column() > doc()->lineLength(c.line()) || characterAtPreviousColumn(c).isSpace())) { 1496 --c; 1497 } 1498 1499 if (c.atEdge(left)) { 1500 --c; 1501 } else if (h->isInWord(characterAtPreviousColumn(c))) { 1502 if (doc()->config()->camelCursor()) { 1503 CamelCursor cc(this, cursor); 1504 --cc; 1505 return cc; 1506 } else { 1507 while (!c.atEdge(left) && h->isInWord(characterAtPreviousColumn(c))) { 1508 --c; 1509 } 1510 } 1511 } else { 1512 while (!c.atEdge(left) 1513 && !h->isInWord(characterAtPreviousColumn(c)) 1514 // in order to stay symmetric to wordLeft() 1515 // we must not skip space preceding a non-word sequence 1516 && !characterAtPreviousColumn(c).isSpace()) { 1517 --c; 1518 } 1519 } 1520 1521 return c; 1522 }; 1523 1524 const auto &secondaryCursors = view()->m_secondaryCursors; 1525 QVarLengthArray<CursorPair, 16> cursorsToUpdate; 1526 for (const auto &cursor : secondaryCursors) { 1527 auto oldPos = cursor.cursor(); 1528 auto newCursorPos = wordPrevious(cursor.cursor()); 1529 cursor.pos->setPosition(newCursorPos); 1530 cursorsToUpdate.push_back({oldPos, newCursorPos}); 1531 } 1532 1533 // update primary cursor 1534 const auto c = wordPrevious(m_cursor); 1535 updateSelection(c, sel); 1536 updateCursor(c); 1537 1538 if (!sel) { 1539 view()->ensureUniqueCursors(); 1540 } 1541 updateSecondaryCursors(cursorsToUpdate, sel); 1542 } 1543 1544 void KateViewInternal::wordNext(bool sel) 1545 { 1546 auto nextWord = [this](KTextEditor::Cursor cursor) -> KTextEditor::Cursor { 1547 WrappingCursor c(this, cursor); 1548 1549 // We look up into which category the current position falls: 1550 // 1. a "word" character 1551 // 2. a "non-word" character (except space) 1552 // 3. the end of the line 1553 // and skip all following characters that fall into this class. 1554 // If the skipped characters are followed by space, we skip that too. 1555 // The code assumes that space is never part of the word character class. 1556 1557 KateHighlighting *h = doc()->highlight(); 1558 if (c.atEdge(right)) { 1559 ++c; 1560 } else if (h->isInWord(doc()->characterAt(c))) { 1561 if (doc()->config()->camelCursor()) { 1562 CamelCursor cc(this, cursor); 1563 ++cc; 1564 return cc; 1565 } else { 1566 while (!c.atEdge(right) && h->isInWord(doc()->characterAt(c))) { 1567 ++c; 1568 } 1569 } 1570 } else { 1571 while (!c.atEdge(right) 1572 && !h->isInWord(doc()->characterAt(c)) 1573 // we must not skip space, because if that space is followed 1574 // by more non-word characters, we would skip them, too 1575 && !doc()->characterAt(c).isSpace()) { 1576 ++c; 1577 } 1578 } 1579 1580 while (!c.atEdge(right) && doc()->characterAt(c).isSpace()) { 1581 ++c; 1582 } 1583 1584 return c; 1585 }; 1586 1587 const auto &secondaryCursors = view()->m_secondaryCursors; 1588 QVarLengthArray<CursorPair, 16> cursorsToUpdate; 1589 for (const auto &cursor : secondaryCursors) { 1590 auto oldPos = cursor.cursor(); 1591 auto newCursorPos = nextWord(cursor.cursor()); 1592 cursor.pos->setPosition(newCursorPos); 1593 cursorsToUpdate.push_back({oldPos, newCursorPos}); 1594 } 1595 1596 // update primary cursor 1597 const auto c = nextWord(m_cursor); 1598 updateSelection(c, sel); 1599 updateCursor(c); 1600 1601 // Remove cursors which have same position 1602 if (!sel) { 1603 view()->ensureUniqueCursors(); 1604 } 1605 updateSecondaryCursors(cursorsToUpdate, sel); 1606 } 1607 1608 void KateViewInternal::moveEdge(KateViewInternal::Bias bias, bool sel) 1609 { 1610 BoundedCursor c(this, m_cursor); 1611 c.toEdge(bias); 1612 updateSelection(c, sel); 1613 updateCursor(c); 1614 } 1615 1616 KTextEditor::Cursor KateViewInternal::moveCursorToLineStart(KTextEditor::Cursor cursor) 1617 { 1618 if (view()->dynWordWrap() && currentLayout(cursor).startCol()) { 1619 // Allow us to go to the real start if we're already at the start of the view line 1620 if (m_cursor.column() != currentLayout(cursor).startCol()) { 1621 KTextEditor::Cursor c = currentLayout(cursor).start(); 1622 return c; 1623 } 1624 } 1625 1626 if (!doc()->config()->smartHome()) { 1627 BoundedCursor c(this, cursor); 1628 c.toEdge(left); 1629 return c; 1630 } 1631 1632 Kate::TextLine l = doc()->kateTextLine(cursor.line()); 1633 1634 if (!l) { 1635 return KTextEditor::Cursor::invalid(); 1636 } 1637 1638 KTextEditor::Cursor c = cursor; 1639 int lc = l->firstChar(); 1640 1641 if (lc < 0 || c.column() == lc) { 1642 c.setColumn(0); 1643 } else { 1644 c.setColumn(lc); 1645 } 1646 return c; 1647 } 1648 1649 void KateViewInternal::home(bool sel) 1650 { 1651 // Multicursor 1652 view()->ensureUniqueCursors(/*matchLine*/ true); 1653 const auto &secondaryCursors = view()->m_secondaryCursors; 1654 QVarLengthArray<CursorPair, 16> cursorsToUpdate; 1655 for (const auto &c : secondaryCursors) { 1656 auto oldPos = c.cursor(); 1657 // These will end up in same place so just remove 1658 auto newPos = moveCursorToLineStart(oldPos); 1659 c.pos->setPosition(newPos); 1660 cursorsToUpdate.push_back({oldPos, newPos}); 1661 } 1662 1663 // Primary cursor 1664 auto newPos = moveCursorToLineStart(m_cursor); 1665 if (newPos.isValid()) { 1666 updateSelection(newPos, sel); 1667 updateCursor(newPos, true); 1668 } 1669 updateSecondaryCursors(cursorsToUpdate, sel); 1670 } 1671 1672 KTextEditor::Cursor KateViewInternal::moveCursorToLineEnd(KTextEditor::Cursor cursor) 1673 { 1674 KateTextLayout layout = currentLayout(cursor); 1675 1676 if (view()->dynWordWrap() && layout.wrap()) { 1677 // Allow us to go to the real end if we're already at the end of the view line 1678 if (cursor.column() < layout.endCol() - 1) { 1679 KTextEditor::Cursor c(cursor.line(), layout.endCol() - 1); 1680 return c; 1681 } 1682 } 1683 1684 if (!doc()->config()->smartHome()) { 1685 BoundedCursor c(this, cursor); 1686 c.toEdge(right); 1687 return c; 1688 } 1689 1690 Kate::TextLine l = doc()->kateTextLine(cursor.line()); 1691 1692 if (!l) { 1693 return KTextEditor::Cursor::invalid(); 1694 } 1695 1696 // "Smart End", as requested in bugs #78258 and #106970 1697 if (cursor.column() == doc()->lineLength(cursor.line())) { 1698 KTextEditor::Cursor c = cursor; 1699 c.setColumn(l->lastChar() + 1); 1700 return c; 1701 } else { 1702 BoundedCursor c(this, cursor); 1703 c.toEdge(right); 1704 return c; 1705 } 1706 } 1707 1708 void KateViewInternal::end(bool sel) 1709 { 1710 // Multicursor 1711 view()->ensureUniqueCursors(/*matchLine*/ true); 1712 1713 QVarLengthArray<CursorPair, 16> cursorsToUpdate; 1714 const auto &secondaryCursors = view()->m_secondaryCursors; 1715 for (const auto &c : secondaryCursors) { 1716 auto oldPos = c.cursor(); 1717 // These will end up in same place so just remove 1718 auto newPos = moveCursorToLineEnd(oldPos); 1719 c.pos->setPosition(newPos); 1720 cursorsToUpdate.push_back({oldPos, newPos}); 1721 } 1722 1723 auto newPos = moveCursorToLineEnd(m_cursor); 1724 if (newPos.isValid()) { 1725 updateSelection(newPos, sel); 1726 updateCursor(newPos); 1727 } 1728 1729 updateSecondaryCursors(cursorsToUpdate, sel); 1730 paintCursor(); 1731 } 1732 1733 KateTextLayout KateViewInternal::currentLayout(KTextEditor::Cursor c) const 1734 { 1735 return cache()->textLayout(c); 1736 } 1737 1738 KateTextLayout KateViewInternal::previousLayout(KTextEditor::Cursor c) const 1739 { 1740 int currentViewLine = cache()->viewLine(c); 1741 1742 if (currentViewLine) { 1743 return cache()->textLayout(c.line(), currentViewLine - 1); 1744 } else { 1745 return cache()->textLayout(view()->textFolding().visibleLineToLine(toVirtualCursor(c).line() - 1), -1); 1746 } 1747 } 1748 1749 KateTextLayout KateViewInternal::nextLayout(KTextEditor::Cursor c) const 1750 { 1751 int currentViewLine = cache()->viewLine(c) + 1; 1752 1753 if (currentViewLine >= cache()->line(c.line())->viewLineCount()) { 1754 currentViewLine = 0; 1755 return cache()->textLayout(view()->textFolding().visibleLineToLine(toVirtualCursor(c).line() + 1), currentViewLine); 1756 } else { 1757 return cache()->textLayout(c.line(), currentViewLine); 1758 } 1759 } 1760 1761 /* 1762 * This returns the cursor which is offset by (offset) view lines. 1763 * This is the main function which is called by code not specifically dealing with word-wrap. 1764 * The opposite conversion (cursor to offset) can be done with cache()->displayViewLine(). 1765 * 1766 * The cursors involved are virtual cursors (ie. equivalent to m_displayCursor) 1767 */ 1768 1769 KTextEditor::Cursor KateViewInternal::viewLineOffset(const KTextEditor::Cursor virtualCursor, int offset, bool keepX) 1770 { 1771 if (!view()->dynWordWrap()) { 1772 KTextEditor::Cursor ret(qMin((int)view()->textFolding().visibleLines() - 1, virtualCursor.line() + offset), 0); 1773 1774 if (ret.line() < 0) { 1775 ret.setLine(0); 1776 } 1777 1778 if (keepX) { 1779 int realLine = view()->textFolding().visibleLineToLine(ret.line()); 1780 KateTextLayout t = cache()->textLayout(realLine, 0); 1781 Q_ASSERT(t.isValid()); 1782 1783 ret.setColumn(renderer()->xToCursor(t, m_preservedX, !view()->wrapCursor()).column()); 1784 } 1785 1786 return ret; 1787 } 1788 1789 KTextEditor::Cursor realCursor = virtualCursor; 1790 realCursor.setLine(view()->textFolding().visibleLineToLine(view()->textFolding().lineToVisibleLine(virtualCursor.line()))); 1791 1792 int cursorViewLine = cache()->viewLine(realCursor); 1793 1794 int currentOffset = 0; 1795 int virtualLine = 0; 1796 1797 bool forwards = (offset > 0) ? true : false; 1798 1799 if (forwards) { 1800 currentOffset = cache()->lastViewLine(realCursor.line()) - cursorViewLine; 1801 if (offset <= currentOffset) { 1802 // the answer is on the same line 1803 KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine + offset); 1804 Q_ASSERT(thisLine.virtualLine() == (int)view()->textFolding().lineToVisibleLine(virtualCursor.line())); 1805 return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); 1806 } 1807 1808 virtualLine = virtualCursor.line() + 1; 1809 1810 } else { 1811 offset = -offset; 1812 currentOffset = cursorViewLine; 1813 if (offset <= currentOffset) { 1814 // the answer is on the same line 1815 KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine - offset); 1816 Q_ASSERT(thisLine.virtualLine() == (int)view()->textFolding().lineToVisibleLine(virtualCursor.line())); 1817 return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); 1818 } 1819 1820 virtualLine = virtualCursor.line() - 1; 1821 } 1822 1823 currentOffset++; 1824 1825 while (virtualLine >= 0 && virtualLine < (int)view()->textFolding().visibleLines()) { 1826 int realLine = view()->textFolding().visibleLineToLine(virtualLine); 1827 KateLineLayoutPtr thisLine = cache()->line(realLine, virtualLine); 1828 if (!thisLine) { 1829 break; 1830 } 1831 1832 for (int i = 0; i < thisLine->viewLineCount(); ++i) { 1833 if (offset == currentOffset) { 1834 KateTextLayout thisViewLine = thisLine->viewLine(i); 1835 1836 if (!forwards) { 1837 // We actually want it the other way around 1838 int requiredViewLine = cache()->lastViewLine(realLine) - thisViewLine.viewLine(); 1839 if (requiredViewLine != thisViewLine.viewLine()) { 1840 thisViewLine = thisLine->viewLine(requiredViewLine); 1841 } 1842 } 1843 1844 KTextEditor::Cursor ret(virtualLine, thisViewLine.startCol()); 1845 1846 // keep column position 1847 if (keepX) { 1848 KTextEditor::Cursor realCursor = toRealCursor(virtualCursor); 1849 KateTextLayout t = cache()->textLayout(realCursor); 1850 // renderer()->cursorToX(t, realCursor, !view()->wrapCursor()); 1851 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 // remember the view line and x pos 2147 int viewLine = cache()->displayViewLine(m_displayCursor); 2148 bool atTop = startPos().atStartOfDocument(); 2149 2150 // Adjust for an auto-centering cursor 2151 int lineadj = m_minLinesVisible; 2152 2153 int linesToScroll; 2154 if (!half) { 2155 linesToScroll = -qMax((linesDisplayed() - 1) - lineadj, 0); 2156 } else { 2157 linesToScroll = -qMax((linesDisplayed() / 2 - 1) - lineadj, 0); 2158 } 2159 2160 m_preserveX = true; 2161 2162 if (!doc()->pageUpDownMovesCursor() && !atTop) { 2163 KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1); 2164 scrollPos(newStartPos); 2165 2166 // put the cursor back approximately where it was 2167 KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true)); 2168 2169 KateTextLayout newLine = cache()->textLayout(newPos); 2170 2171 newPos = renderer()->xToCursor(newLine, m_preservedX, !view()->wrapCursor()); 2172 2173 m_preserveX = true; 2174 updateSelection(newPos, sel); 2175 updateCursor(newPos); 2176 2177 } else { 2178 scrollLines(linesToScroll, sel); 2179 } 2180 } 2181 2182 void KateViewInternal::pageDown(bool sel, bool half) 2183 { 2184 if (view()->isCompletionActive()) { 2185 view()->completionWidget()->pageDown(); 2186 return; 2187 } 2188 2189 view()->clearSecondaryCursors(); 2190 2191 // remember the view line 2192 int viewLine = cache()->displayViewLine(m_displayCursor); 2193 bool atEnd = startPos() >= m_cachedMaxStartPos; 2194 2195 // Adjust for an auto-centering cursor 2196 int lineadj = m_minLinesVisible; 2197 2198 int linesToScroll; 2199 if (!half) { 2200 linesToScroll = qMax((linesDisplayed() - 1) - lineadj, 0); 2201 } else { 2202 linesToScroll = qMax((linesDisplayed() / 2 - 1) - lineadj, 0); 2203 } 2204 2205 m_preserveX = true; 2206 2207 if (!doc()->pageUpDownMovesCursor() && !atEnd) { 2208 KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1); 2209 scrollPos(newStartPos); 2210 2211 // put the cursor back approximately where it was 2212 KTextEditor::Cursor newPos = toRealCursor(viewLineOffset(newStartPos, viewLine, true)); 2213 2214 KateTextLayout newLine = cache()->textLayout(newPos); 2215 2216 newPos = renderer()->xToCursor(newLine, m_preservedX, !view()->wrapCursor()); 2217 2218 m_preserveX = true; 2219 updateSelection(newPos, sel); 2220 updateCursor(newPos); 2221 2222 } else { 2223 scrollLines(linesToScroll, sel); 2224 } 2225 } 2226 2227 int KateViewInternal::maxLen(int startLine) 2228 { 2229 Q_ASSERT(!view()->dynWordWrap()); 2230 2231 int displayLines = (view()->height() / renderer()->lineHeight()) + 1; 2232 2233 int maxLen = 0; 2234 2235 for (int z = 0; z < displayLines; z++) { 2236 int virtualLine = startLine + z; 2237 2238 if (virtualLine < 0 || virtualLine >= (int)view()->textFolding().visibleLines()) { 2239 break; 2240 } 2241 2242 maxLen = qMax(maxLen, cache()->line(view()->textFolding().visibleLineToLine(virtualLine))->width()); 2243 } 2244 2245 return maxLen; 2246 } 2247 2248 bool KateViewInternal::columnScrollingPossible() 2249 { 2250 return !view()->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maximum() > 0); 2251 } 2252 2253 bool KateViewInternal::lineScrollingPossible() 2254 { 2255 return m_lineScroll->minimum() != m_lineScroll->maximum(); 2256 } 2257 2258 void KateViewInternal::top(bool sel) 2259 { 2260 KTextEditor::Cursor newCursor(0, 0); 2261 2262 newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !view()->wrapCursor()); 2263 2264 view()->clearSecondaryCursors(); 2265 updateSelection(newCursor, sel); 2266 updateCursor(newCursor); 2267 } 2268 2269 void KateViewInternal::bottom(bool sel) 2270 { 2271 KTextEditor::Cursor newCursor(doc()->lastLine(), 0); 2272 2273 newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !view()->wrapCursor()); 2274 2275 view()->clearSecondaryCursors(); 2276 updateSelection(newCursor, sel); 2277 updateCursor(newCursor); 2278 } 2279 2280 void KateViewInternal::top_home(bool sel) 2281 { 2282 if (view()->isCompletionActive()) { 2283 view()->completionWidget()->top(); 2284 return; 2285 } 2286 2287 view()->clearSecondaryCursors(); 2288 KTextEditor::Cursor c(0, 0); 2289 updateSelection(c, sel); 2290 updateCursor(c); 2291 } 2292 2293 void KateViewInternal::bottom_end(bool sel) 2294 { 2295 if (view()->isCompletionActive()) { 2296 view()->completionWidget()->bottom(); 2297 return; 2298 } 2299 2300 view()->clearSecondaryCursors(); 2301 KTextEditor::Cursor c(doc()->lastLine(), doc()->lineLength(doc()->lastLine())); 2302 updateSelection(c, sel); 2303 updateCursor(c); 2304 } 2305 2306 void KateViewInternal::updateSecondarySelection(int cursorIdx, KTextEditor::Cursor old, KTextEditor::Cursor newPos) 2307 { 2308 if (m_selectionMode != SelectionMode::Default) { 2309 view()->clearSecondaryCursors(); 2310 } 2311 2312 auto &secondaryCursors = view()->m_secondaryCursors; 2313 if (secondaryCursors.empty()) { 2314 qWarning() << "Invalid updateSecondarySelection with no secondaryCursors"; 2315 return; 2316 } 2317 Q_ASSERT(secondaryCursors.size() > (size_t)cursorIdx); 2318 2319 auto &cursor = secondaryCursors[cursorIdx]; 2320 if (cursor.cursor() != newPos) { 2321 qWarning() << "Unexpected different cursor at cursorIdx" << cursorIdx << "found" << cursor.cursor() << "looking for: " << newPos; 2322 return; 2323 } 2324 2325 if (cursor.range) { 2326 Q_ASSERT(cursor.anchor.isValid()); 2327 cursor.range->setRange(cursor.anchor, newPos); 2328 } else { 2329 cursor.range.reset(view()->newSecondarySelectionRange({old, newPos})); 2330 cursor.anchor = old; 2331 } 2332 } 2333 2334 void KateViewInternal::updateSelection(const KTextEditor::Cursor _newCursor, bool keepSel) 2335 { 2336 KTextEditor::Cursor newCursor = _newCursor; 2337 if (keepSel) { 2338 if (!view()->selection() 2339 || (m_selectAnchor.line() == -1) 2340 // don't kill the selection if we have a persistent selection and 2341 // the cursor is inside or at the boundaries of the selected area 2342 || (view()->config()->persistentSelection() 2343 && !(view()->selectionRange().contains(m_cursor) || view()->selectionRange().boundaryAtCursor(m_cursor)))) { 2344 m_selectAnchor = m_cursor; 2345 setSelection(KTextEditor::Range(m_cursor, newCursor)); 2346 } else { 2347 bool doSelect = true; 2348 switch (m_selectionMode) { 2349 case Word: { 2350 // Restore selStartCached if needed. It gets nuked by 2351 // viewSelectionChanged if we drag the selection into non-existence, 2352 // which can legitimately happen if a shift+DC selection is unable to 2353 // set a "proper" (i.e. non-empty) cached selection, e.g. because the 2354 // start was on something that isn't a word. Word select mode relies 2355 // on the cached selection being set properly, even if it is empty 2356 // (i.e. selStartCached == selEndCached). 2357 if (!m_selectionCached.isValid()) { 2358 m_selectionCached.setStart(m_selectionCached.end()); 2359 } 2360 2361 int c; 2362 if (newCursor > m_selectionCached.start()) { 2363 m_selectAnchor = m_selectionCached.start(); 2364 2365 Kate::TextLine l = doc()->kateTextLine(newCursor.line()); 2366 2367 c = newCursor.column(); 2368 if (c > 0 && doc()->highlight()->isInWord(l->at(c - 1))) { 2369 for (; c < l->length(); c++) { 2370 if (!doc()->highlight()->isInWord(l->at(c))) { 2371 break; 2372 } 2373 } 2374 } 2375 2376 newCursor.setColumn(c); 2377 } else if (newCursor < m_selectionCached.start()) { 2378 m_selectAnchor = m_selectionCached.end(); 2379 2380 Kate::TextLine l = doc()->kateTextLine(newCursor.line()); 2381 2382 c = newCursor.column(); 2383 if (c > 0 && c < doc()->lineLength(newCursor.line()) && doc()->highlight()->isInWord(l->at(c)) 2384 && doc()->highlight()->isInWord(l->at(c - 1))) { 2385 for (c -= 2; c >= 0; c--) { 2386 if (!doc()->highlight()->isInWord(l->at(c))) { 2387 break; 2388 } 2389 } 2390 newCursor.setColumn(c + 1); 2391 } 2392 } else { 2393 doSelect = false; 2394 } 2395 2396 } break; 2397 case Line: 2398 if (!m_selectionCached.isValid()) { 2399 m_selectionCached = KTextEditor::Range(endLine(), 0, endLine(), 0); 2400 } 2401 if (newCursor.line() > m_selectionCached.start().line()) { 2402 if (newCursor.line() + 1 >= doc()->lines()) { 2403 newCursor.setColumn(doc()->line(newCursor.line()).length()); 2404 } else { 2405 newCursor.setPosition(newCursor.line() + 1, 0); 2406 } 2407 // Grow to include the entire line 2408 m_selectAnchor = m_selectionCached.start(); 2409 m_selectAnchor.setColumn(0); 2410 } else if (newCursor.line() < m_selectionCached.start().line()) { 2411 newCursor.setColumn(0); 2412 // Grow to include entire line 2413 m_selectAnchor = m_selectionCached.end(); 2414 if (m_selectAnchor.column() > 0) { 2415 if (m_selectAnchor.line() + 1 >= doc()->lines()) { 2416 m_selectAnchor.setColumn(doc()->line(newCursor.line()).length()); 2417 } else { 2418 m_selectAnchor.setPosition(m_selectAnchor.line() + 1, 0); 2419 } 2420 } 2421 } else { // same line, ignore 2422 doSelect = false; 2423 } 2424 break; 2425 case Mouse: { 2426 if (!m_selectionCached.isValid()) { 2427 break; 2428 } 2429 2430 if (newCursor > m_selectionCached.end()) { 2431 m_selectAnchor = m_selectionCached.start(); 2432 } else if (newCursor < m_selectionCached.start()) { 2433 m_selectAnchor = m_selectionCached.end(); 2434 } else { 2435 doSelect = false; 2436 } 2437 } break; 2438 default: /* nothing special to do */; 2439 } 2440 2441 if (doSelect) { 2442 setSelection(KTextEditor::Range(m_selectAnchor, newCursor)); 2443 } else if (m_selectionCached.isValid()) { // we have a cached selection, so we restore that 2444 setSelection(m_selectionCached); 2445 } 2446 } 2447 2448 m_selChangedByUser = true; 2449 } else if (!view()->config()->persistentSelection()) { 2450 view()->clearSelection(); 2451 2452 m_selectionCached = KTextEditor::Range::invalid(); 2453 m_selectAnchor = KTextEditor::Cursor::invalid(); 2454 } 2455 2456 #ifndef QT_NO_ACCESSIBILITY 2457 // FIXME KF5 2458 // QAccessibleTextSelectionEvent ev(this, /* selection start, selection end*/); 2459 // QAccessible::updateAccessibility(&ev); 2460 #endif 2461 } 2462 2463 void KateViewInternal::setSelection(KTextEditor::Range range) 2464 { 2465 disconnect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged); 2466 view()->setSelection(range); 2467 connect(m_view, &KTextEditor::ViewPrivate::selectionChanged, this, &KateViewInternal::viewSelectionChanged); 2468 } 2469 2470 void KateViewInternal::moveCursorToSelectionEdge(bool scroll) 2471 { 2472 if (!view()->selection()) { 2473 return; 2474 } 2475 2476 int tmp = m_minLinesVisible; 2477 m_minLinesVisible = 0; 2478 2479 if (view()->selectionRange().start() < m_selectAnchor) { 2480 updateCursor(view()->selectionRange().start(), false, false, false, scroll); 2481 } else { 2482 updateCursor(view()->selectionRange().end(), false, false, false, scroll); 2483 } 2484 if (!scroll) { 2485 m_madeVisible = false; 2486 } 2487 2488 m_minLinesVisible = tmp; 2489 } 2490 2491 KTextEditor::Range KateViewInternal::findMatchingFoldingMarker(const KTextEditor::Cursor currentCursorPos, const int value, const int maxLines) 2492 { 2493 const int direction = !(value < 0) ? 1 : -1; 2494 int foldCounter = 0; 2495 int lineCounter = 0; 2496 auto &foldMarkers = m_view->doc()->buffer().plainLine(currentCursorPos.line())->foldings(); 2497 2498 // searching a end folding marker? go left to right 2499 // otherwise, go right to left 2500 long i = direction == 1 ? 0 : (long)foldMarkers.size() - 1; 2501 2502 // For the first line, we start considering the first folding after the cursor 2503 for (; i >= 0 && i < (long)foldMarkers.size(); i += direction) { 2504 if ((foldMarkers[i].offset - currentCursorPos.column()) * direction > 0) { 2505 if (foldMarkers[i].foldingValue == value) { 2506 foldCounter += 1; 2507 } else if (foldMarkers[i].foldingValue == -value && foldCounter > 0) { 2508 foldCounter -= 1; 2509 } else if (foldMarkers[i].foldingValue == -value && foldCounter == 0) { 2510 return KTextEditor::Range(currentCursorPos.line(), 2511 getStartOffset(direction, foldMarkers[i].offset, foldMarkers[i].length), 2512 currentCursorPos.line(), 2513 getEndOffset(direction, foldMarkers[i].offset, foldMarkers[i].length)); 2514 } 2515 } 2516 } 2517 2518 // for the other lines 2519 int currentLine = currentCursorPos.line() + direction; 2520 for (; currentLine >= 0 && currentLine < m_view->doc()->lines() && lineCounter < maxLines; currentLine += direction) { 2521 // update line attributes 2522 auto &foldMarkers = m_view->doc()->buffer().plainLine(currentLine)->foldings(); 2523 i = direction == 1 ? 0 : (long)foldMarkers.size() - 1; 2524 2525 // iterate through the markers 2526 for (; i >= 0 && i < (long)foldMarkers.size(); i += direction) { 2527 if (foldMarkers[i].foldingValue == value) { 2528 foldCounter += 1; 2529 } else if (foldMarkers[i].foldingValue == -value && foldCounter != 0) { 2530 foldCounter -= 1; 2531 } else if (foldMarkers[i].foldingValue == -value && foldCounter == 0) { 2532 return KTextEditor::Range(currentLine, 2533 getStartOffset(direction, foldMarkers[i].offset, foldMarkers[i].length), 2534 currentLine, 2535 getEndOffset(direction, foldMarkers[i].offset, foldMarkers[i].length)); 2536 } 2537 } 2538 lineCounter += 1; 2539 } 2540 2541 // got out of loop, no matching folding found 2542 // returns a invalid folding range 2543 return KTextEditor::Range::invalid(); 2544 } 2545 2546 void KateViewInternal::updateFoldingMarkersHighlighting() 2547 { 2548 auto &foldings = m_view->doc()->buffer().plainLine(m_cursor.line())->foldings(); 2549 2550 for (unsigned long i = 0; i < foldings.size(); i++) { 2551 // 1 -> left to right, the current folding is start type 2552 // -1 -> right to left, the current folding is end type 2553 int direction = !(foldings[i].foldingValue < 0) ? 1 : -1; 2554 2555 int startOffset = getStartOffset(-direction, foldings[i].offset, foldings[i].length); 2556 int endOffset = getEndOffset(-direction, foldings[i].offset, foldings[i].length); 2557 2558 if (m_cursor.column() >= startOffset && m_cursor.column() <= endOffset) { 2559 const auto foldingMarkerMatch = findMatchingFoldingMarker(KTextEditor::Cursor(m_cursor.line(), m_cursor.column()), foldings[i].foldingValue, 2000); 2560 2561 if (!foldingMarkerMatch.isValid()) { 2562 break; 2563 } 2564 2565 // set fmStart to Opening Folding Marker and fmEnd to Ending Folding Marker 2566 if (direction == 1) { 2567 m_fmStart->setRange(KTextEditor::Range(m_cursor.line(), startOffset, m_cursor.line(), endOffset)); 2568 m_fmEnd->setRange(foldingMarkerMatch); 2569 } else { 2570 m_fmStart->setRange(foldingMarkerMatch); 2571 m_fmEnd->setRange(KTextEditor::Range(m_cursor.line(), startOffset, m_cursor.line(), endOffset)); 2572 } 2573 2574 KTextEditor::Attribute::Ptr fill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); 2575 fill->setBackground(view()->m_renderer->config()->highlightedBracketColor()); 2576 2577 m_fmStart->setAttribute(fill); 2578 m_fmEnd->setAttribute(fill); 2579 return; 2580 } 2581 } 2582 m_fmStart->setRange(KTextEditor::Range::invalid()); 2583 m_fmEnd->setRange(KTextEditor::Range::invalid()); 2584 } 2585 2586 void KateViewInternal::updateSecondaryCursors(const QVarLengthArray<CursorPair, 16> &cursors, bool sel) 2587 { 2588 if (sel) { 2589 for (int i = 0; i < cursors.size(); ++i) { 2590 updateSecondarySelection(i, cursors[i].oldPos, cursors[i].newPos); 2591 } 2592 if (!cursors.isEmpty()) { 2593 mergeSelections(); 2594 } 2595 } else { 2596 view()->clearSecondarySelections(); 2597 } 2598 2599 QVarLengthArray<int> linesToUpdate; 2600 for (auto cpair : cursors) { 2601 linesToUpdate.push_back(cpair.oldPos.line()); 2602 linesToUpdate.push_back(cpair.newPos.line()); 2603 } 2604 // Remove duplicate stuff 2605 std::sort(linesToUpdate.begin(), linesToUpdate.end()); 2606 auto it = std::unique(linesToUpdate.begin(), linesToUpdate.end()); 2607 2608 // Collapse ranges to avoid extra work 2609 using Range = std::pair<int, int>; // start, length 2610 QVarLengthArray<Range> ranges; 2611 int prev = 0; 2612 for (auto i = linesToUpdate.begin(); i != it; ++i) { 2613 int curLine = *i; 2614 if (!ranges.isEmpty() && prev + 1 == curLine) { 2615 ranges.back().second++; 2616 } else { 2617 ranges.push_back({curLine, 0}); 2618 } 2619 prev = curLine; 2620 } 2621 2622 for (auto range : ranges) { 2623 int startLine = range.first; 2624 int endLine = range.first + range.second; 2625 tagLines(startLine, endLine, /*realLines=*/true); 2626 } 2627 updateDirty(); 2628 } 2629 2630 void KateViewInternal::mergeSelections() 2631 { 2632 using SecondaryCursor = KTextEditor::ViewPrivate::SecondaryCursor; 2633 using Range = KTextEditor::Range; 2634 auto doMerge = [](Range newRange, SecondaryCursor &a, SecondaryCursor &b) { 2635 a.range->setRange(newRange); 2636 2637 b.pos.reset(); 2638 b.range.reset(); 2639 }; 2640 2641 auto &cursors = view()->m_secondaryCursors; 2642 for (auto it = cursors.begin(); it != cursors.end(); ++it) { 2643 if (!it->range) 2644 continue; 2645 if (it + 1 == cursors.end()) { 2646 break; 2647 } 2648 2649 auto n = std::next(it); 2650 if (/*!it->range || */ !n->range) { 2651 continue; 2652 } 2653 2654 auto curRange = it->range->toRange(); 2655 auto nextRange = n->range->toRange(); 2656 if (!curRange.overlaps(nextRange)) { 2657 continue; 2658 } 2659 2660 bool isLefSel = it->cursor() < it->anchor; 2661 2662 // if the ranges overlap we expand the next range 2663 // to include the current one. This allows us to 2664 // check all ranges for overlap in one go 2665 auto curPos = it->cursor(); 2666 nextRange.expandToRange(curRange); 2667 if (isLefSel) { 2668 // in left selection our next cursor 2669 // is ahead somewhere, we want to keep 2670 // the smallest position 2671 n->pos->setPosition(curPos); 2672 n->anchor = qMax(n->anchor, it->anchor); 2673 } else { 2674 n->anchor = qMin(n->anchor, it->anchor); 2675 } 2676 doMerge(nextRange, *n, *it); 2677 } 2678 2679 if (view()->selection()) { 2680 auto primarySel = view()->m_selection.toRange(); 2681 auto primCursor = cursorPosition(); 2682 for (auto it = cursors.begin(); it != cursors.end(); ++it) { 2683 // If range is valid, we merge selection into primary 2684 // Otherwise if cursor is inside primary selection, it 2685 // is removed 2686 auto curRange = it->range ? it->range->toRange() : Range::invalid(); 2687 if (curRange.isValid() && primarySel.overlaps(curRange)) { 2688 primarySel.expandToRange(curRange); 2689 bool isLeft = it->cursor() < it->anchor; 2690 2691 if (isLeft) { 2692 if (it->cursor() < primCursor) { 2693 updateCursor(it->cursor()); 2694 } 2695 m_selectAnchor = qMax(m_selectAnchor, it->anchor); 2696 } else { 2697 if (it->cursor() > primCursor) { 2698 updateCursor(it->cursor()); 2699 } 2700 m_selectAnchor = qMin(m_selectAnchor, it->anchor); 2701 } 2702 2703 setSelection(primarySel); 2704 it->pos.reset(); 2705 it->range.reset(); 2706 } else if (it->pos) { 2707 // This only needs to be done for primary selection 2708 // because remember, mouse selection is always with 2709 // primary cursor 2710 auto pos = it->cursor(); 2711 if (!primarySel.boundaryAtCursor(pos) && primarySel.contains(pos)) { 2712 it->pos.reset(); 2713 } 2714 } 2715 } 2716 } 2717 2718 cursors.erase(std::remove_if(cursors.begin(), 2719 cursors.end(), 2720 [](const SecondaryCursor &c) { 2721 return !c.pos.get(); 2722 }), 2723 cursors.end()); 2724 } 2725 2726 void KateViewInternal::updateCursor(const KTextEditor::Cursor newCursor, bool force, bool center, bool calledExternally, bool scroll) 2727 { 2728 if (!force && (m_cursor.toCursor() == newCursor)) { 2729 m_displayCursor = toVirtualCursor(newCursor); 2730 if (scroll && !m_madeVisible && m_view == doc()->activeView()) { 2731 // unfold if required 2732 view()->textFolding().ensureLineIsVisible(newCursor.line()); 2733 2734 makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); 2735 } 2736 2737 return; 2738 } 2739 2740 if (m_cursor.line() != newCursor.line()) { 2741 m_leftBorder->updateForCursorLineChange(); 2742 } 2743 2744 // unfold if required 2745 view()->textFolding().ensureLineIsVisible(newCursor.line()); 2746 2747 KTextEditor::Cursor oldDisplayCursor = m_displayCursor; 2748 2749 m_displayCursor = toVirtualCursor(newCursor); 2750 m_cursor.setPosition(newCursor); 2751 2752 if (m_view == doc()->activeView() && scroll) { 2753 makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); 2754 } 2755 2756 updateBracketMarks(); 2757 2758 updateFoldingMarkersHighlighting(); 2759 2760 // avoid double work, tagLine => tagLines => not that cheap, much more costly than to compare 2 ints 2761 tagLine(oldDisplayCursor); 2762 if (oldDisplayCursor.line() != m_displayCursor.line()) { 2763 tagLine(m_displayCursor); 2764 } 2765 2766 updateMicroFocus(); 2767 2768 if (m_cursorTimer.isActive()) { 2769 if (QApplication::cursorFlashTime() > 0) { 2770 m_cursorTimer.start(QApplication::cursorFlashTime() / 2); 2771 } 2772 renderer()->setDrawCaret(true); 2773 } 2774 2775 // Remember the maximum X position if requested 2776 if (m_preserveX) { 2777 m_preserveX = false; 2778 } else { 2779 m_preservedX = renderer()->cursorToX(cache()->textLayout(m_cursor), m_cursor, !view()->wrapCursor()); 2780 } 2781 2782 // qCDebug(LOG_KTE) << "m_preservedX: " << m_preservedX << " (was "<< oldmaxx << "), m_cursorX: " << m_cursorX; 2783 // qCDebug(LOG_KTE) << "Cursor now located at real " << cursor.line << "," << cursor.col << ", virtual " << m_displayCursor.line << ", " << 2784 // m_displayCursor.col << "; Top is " << startLine() << ", " << startPos().col; 2785 2786 cursorMoved(); 2787 2788 updateDirty(); // paintText(0, 0, width(), height(), true); 2789 2790 Q_EMIT view()->cursorPositionChanged(m_view, m_cursor); 2791 } 2792 2793 void KateViewInternal::updateBracketMarkAttributes() 2794 { 2795 KTextEditor::Attribute::Ptr bracketFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); 2796 bracketFill->setBackground(view()->m_renderer->config()->highlightedBracketColor()); 2797 bracketFill->setBackgroundFillWhitespace(false); 2798 if (QFontInfo(renderer()->currentFont()).fixedPitch()) { 2799 // make font bold only for fixed fonts, otherwise text jumps around 2800 bracketFill->setFontBold(); 2801 } 2802 2803 m_bmStart->setAttribute(bracketFill); 2804 m_bmEnd->setAttribute(bracketFill); 2805 2806 if (view()->m_renderer->config()->showWholeBracketExpression()) { 2807 KTextEditor::Attribute::Ptr expressionFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); 2808 expressionFill->setBackground(view()->m_renderer->config()->highlightedBracketColor()); 2809 expressionFill->setBackgroundFillWhitespace(false); 2810 2811 m_bm->setAttribute(expressionFill); 2812 } else { 2813 m_bm->setAttribute(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute())); 2814 } 2815 } 2816 2817 void KateViewInternal::updateBracketMarks() 2818 { 2819 // add some limit to this, this is really endless on big files without limit 2820 const int maxLines = 5000; 2821 const KTextEditor::Range newRange = doc()->findMatchingBracket(m_cursor, maxLines); 2822 2823 // new range valid, then set ranges to it 2824 if (newRange.isValid()) { 2825 if (m_bm->toRange() == newRange) { 2826 // hide preview as it now (probably) blocks the top of the view 2827 hideBracketMatchPreview(); 2828 return; 2829 } 2830 2831 // modify full range 2832 m_bm->setRange(newRange); 2833 2834 // modify start and end ranges 2835 m_bmStart->setRange(KTextEditor::Range(m_bm->start(), KTextEditor::Cursor(m_bm->start().line(), m_bm->start().column() + 1))); 2836 m_bmEnd->setRange(KTextEditor::Range(m_bm->end(), KTextEditor::Cursor(m_bm->end().line(), m_bm->end().column() + 1))); 2837 2838 // show preview of the matching bracket's line 2839 if (m_view->config()->value(KateViewConfig::ShowBracketMatchPreview).toBool()) { 2840 showBracketMatchPreview(); 2841 } 2842 2843 // flash matching bracket 2844 if (!renderer()->config()->animateBracketMatching()) { 2845 return; 2846 } 2847 2848 const KTextEditor::Cursor flashPos = (m_cursor == m_bmStart->start() || m_cursor == m_bmStart->end()) ? m_bmEnd->start() : m_bm->start(); 2849 if (flashPos != m_bmLastFlashPos->toCursor()) { 2850 m_bmLastFlashPos->setPosition(flashPos); 2851 2852 KTextEditor::Attribute::Ptr attribute = attributeAt(flashPos); 2853 attribute->setBackground(view()->m_renderer->config()->highlightedBracketColor()); 2854 if (m_bmStart->attribute()->fontBold()) { 2855 attribute->setFontBold(true); 2856 } 2857 2858 flashChar(flashPos, attribute); 2859 } 2860 return; 2861 } 2862 2863 // new range was invalid 2864 m_bm->setRange(KTextEditor::Range::invalid()); 2865 m_bmStart->setRange(KTextEditor::Range::invalid()); 2866 m_bmEnd->setRange(KTextEditor::Range::invalid()); 2867 m_bmLastFlashPos->setPosition(KTextEditor::Cursor::invalid()); 2868 hideBracketMatchPreview(); 2869 } 2870 2871 bool KateViewInternal::tagLine(const KTextEditor::Cursor virtualCursor) 2872 { 2873 // we had here some special case handling for one line, it was just randomly wrong for dyn. word wrapped stuff => use the generic function 2874 return tagLines(virtualCursor, virtualCursor, false); 2875 } 2876 2877 bool KateViewInternal::tagLines(int start, int end, bool realLines) 2878 { 2879 return tagLines(KTextEditor::Cursor(start, 0), KTextEditor::Cursor(end, -1), realLines); 2880 } 2881 2882 bool KateViewInternal::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) 2883 { 2884 if (realCursors) { 2885 cache()->relayoutLines(start.line(), end.line()); 2886 2887 // qCDebug(LOG_KTE)<<"realLines is true"; 2888 start = toVirtualCursor(start); 2889 end = toVirtualCursor(end); 2890 2891 } else { 2892 cache()->relayoutLines(toRealCursor(start).line(), toRealCursor(end).line()); 2893 } 2894 2895 if (end.line() < startLine()) { 2896 // qCDebug(LOG_KTE)<<"end<startLine"; 2897 return false; 2898 } 2899 // Used to be > endLine(), but cache may not be valid when checking, so use a 2900 // less optimal but still adequate approximation (potential overestimation but minimal performance difference) 2901 if (start.line() > startLine() + cache()->viewCacheLineCount()) { 2902 // qCDebug(LOG_KTE)<<"start> endLine"<<start<<" "<<(endLine()); 2903 return false; 2904 } 2905 2906 cache()->updateViewCache(startPos()); 2907 2908 // qCDebug(LOG_KTE) << "tagLines( [" << start << "], [" << end << "] )"; 2909 2910 bool ret = false; 2911 2912 for (int z = 0; z < cache()->viewCacheLineCount(); z++) { 2913 KateTextLayout &line = cache()->viewLine(z); 2914 if ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) 2915 && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1)))) { 2916 ret = true; 2917 break; 2918 // qCDebug(LOG_KTE) << "Tagged line " << line.line(); 2919 } 2920 } 2921 2922 // full update of border it we have indentation based hl and show the markers always 2923 if (!m_view->config()->showFoldingOnHoverOnly() && doc()->highlight() && doc()->highlight()->foldingIndentationSensitive()) { 2924 // not the default setting, can be optimized if ever some issue 2925 m_leftBorder->update(); 2926 } else if (!view()->dynWordWrap()) { 2927 int y = lineToY(start.line()); 2928 // FIXME is this enough for when multiple lines are deleted 2929 int h = (end.line() - start.line() + 2) * renderer()->lineHeight(); 2930 if (end.line() >= view()->textFolding().visibleLines() - 1) { 2931 h = height(); 2932 } 2933 2934 m_leftBorder->update(0, y, m_leftBorder->width(), h); 2935 } else { 2936 // FIXME Do we get enough good info in editRemoveText to optimize this more? 2937 // bool justTagged = false; 2938 for (int z = 0; z < cache()->viewCacheLineCount(); z++) { 2939 KateTextLayout &line = cache()->viewLine(z); 2940 if (!line.isValid() 2941 || ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) 2942 && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1))))) { 2943 // justTagged = true; 2944 m_leftBorder->update(0, z * renderer()->lineHeight(), m_leftBorder->width(), m_leftBorder->height()); 2945 break; 2946 } 2947 /*else if (justTagged) 2948 { 2949 justTagged = false; 2950 leftBorder->update (0, z * doc()->viewFont.fontHeight, leftBorder->width(), doc()->viewFont.fontHeight); 2951 break; 2952 }*/ 2953 } 2954 } 2955 2956 return ret; 2957 } 2958 2959 bool KateViewInternal::tagRange(KTextEditor::Range range, bool realCursors) 2960 { 2961 return tagLines(range.start(), range.end(), realCursors); 2962 } 2963 2964 void KateViewInternal::tagAll() 2965 { 2966 // clear the cache... 2967 cache()->clear(); 2968 2969 m_leftBorder->updateFont(); 2970 m_leftBorder->update(); 2971 } 2972 2973 void KateViewInternal::paintCursor() 2974 { 2975 if (tagLine(m_displayCursor)) { 2976 updateDirty(); // paintText (0,0,width(), height(), true); 2977 } 2978 2979 const int s = view()->firstDisplayedLine(); 2980 const int e = view()->lastDisplayedLine(); 2981 for (const auto &c : view()->m_secondaryCursors) { 2982 auto p = c.cursor(); 2983 if (p.line() >= s - 1 && p.line() <= e + 1) { 2984 tagLines(p, p, true); 2985 } 2986 } 2987 2988 updateDirty(); // paintText (0,0,width(), height(), true); 2989 } 2990 2991 KTextEditor::Cursor KateViewInternal::cursorForPoint(QPoint p) 2992 { 2993 KateTextLayout thisLine = yToKateTextLayout(p.y()); 2994 KTextEditor::Cursor c; 2995 2996 if (!thisLine.isValid()) { // probably user clicked below the last line -> use the last line 2997 thisLine = cache()->textLayout(doc()->lines() - 1, -1); 2998 } 2999 3000 c = renderer()->xToCursor(thisLine, startX() + p.x(), !view()->wrapCursor()); 3001 3002 if (c.line() < 0 || c.line() >= doc()->lines()) { 3003 return KTextEditor::Cursor::invalid(); 3004 } 3005 3006 // loop over all notes and check if the point is inside it 3007 const auto inlineNotes = view()->inlineNotes(c.line()); 3008 p = mapToGlobal(p); 3009 for (const auto ¬e : inlineNotes) { 3010 auto noteCursor = note.m_position; 3011 // we are not interested in notes that are past the end or at 0 3012 if (note.m_position.column() >= doc()->lineLength(c.line()) || note.m_position.column() == 0) { 3013 continue; 3014 } 3015 // an inline note is "part" of a char i.e., char width is increased so that 3016 // an inline note can be painted beside it. So we need to find the actual 3017 // char width 3018 const auto caretWidth = renderer()->caretStyle() == KateRenderer::Line ? 2. : 0.; 3019 const auto width = KTextEditor::InlineNote(note).width() + caretWidth; 3020 const auto charWidth = renderer()->currentFontMetrics().horizontalAdvance(doc()->characterAt(noteCursor)); 3021 const auto halfCharWidth = (charWidth / 2); 3022 // we leave the first half of the char width. If the user has clicked in the first half, let it go the 3023 // previous column. 3024 const auto totalWidth = width + halfCharWidth; 3025 auto start = mapToGlobal(cursorToCoordinate(noteCursor, true, false)); 3026 start = start - QPoint(totalWidth, 0); 3027 QRect r(start, QSize{(int)halfCharWidth, renderer()->lineHeight()}); 3028 if (r.contains(p)) { 3029 c = noteCursor; 3030 break; 3031 } 3032 } 3033 3034 return c; 3035 } 3036 3037 // Point in content coordinates 3038 void KateViewInternal::placeCursor(const QPoint &p, bool keepSelection, bool updateSelection) 3039 { 3040 KTextEditor::Cursor c = cursorForPoint(p); 3041 if (!c.isValid()) { 3042 return; 3043 } 3044 3045 if (updateSelection) { 3046 KateViewInternal::updateSelection(c, keepSelection); 3047 } 3048 3049 int tmp = m_minLinesVisible; 3050 m_minLinesVisible = 0; 3051 updateCursor(c); 3052 m_minLinesVisible = tmp; 3053 3054 if (updateSelection && keepSelection) { 3055 moveCursorToSelectionEdge(); 3056 } 3057 } 3058 3059 // Point in content coordinates 3060 bool KateViewInternal::isTargetSelected(const QPoint &p) 3061 { 3062 const KateTextLayout &thisLine = yToKateTextLayout(p.y()); 3063 if (!thisLine.isValid()) { 3064 return false; 3065 } 3066 3067 return view()->cursorSelected(renderer()->xToCursor(thisLine, startX() + p.x(), !view()->wrapCursor())); 3068 } 3069 3070 // BEGIN EVENT HANDLING STUFF 3071 3072 bool KateViewInternal::eventFilter(QObject *obj, QEvent *e) 3073 { 3074 switch (e->type()) { 3075 case QEvent::ChildAdded: 3076 case QEvent::ChildRemoved: { 3077 QChildEvent *c = static_cast<QChildEvent *>(e); 3078 if (c->added()) { 3079 c->child()->installEventFilter(this); 3080 3081 } else if (c->removed()) { 3082 c->child()->removeEventFilter(this); 3083 } 3084 } break; 3085 3086 case QEvent::ShortcutOverride: { 3087 QKeyEvent *k = static_cast<QKeyEvent *>(e); 3088 3089 if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { 3090 if (!view()->m_secondaryCursors.empty()) { 3091 view()->clearSecondaryCursors(); 3092 k->accept(); 3093 return true; 3094 } 3095 3096 if (view()->isCompletionActive()) { 3097 view()->abortCompletion(); 3098 k->accept(); 3099 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "aborting completion"; 3100 return true; 3101 } else if (view()->bottomViewBar()->barWidgetVisible()) { 3102 view()->bottomViewBar()->hideCurrentBarWidget(); 3103 k->accept(); 3104 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "closing view bar"; 3105 return true; 3106 } else if (!view()->config()->persistentSelection() && view()->selection()) { 3107 m_currentInputMode->clearSelection(); 3108 k->accept(); 3109 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "clearing selection"; 3110 return true; 3111 } 3112 } 3113 3114 if (m_currentInputMode->stealKey(k)) { 3115 k->accept(); 3116 return true; 3117 } 3118 3119 // CompletionReplayer.replay only gets called when a Ctrl-Space gets to InsertViMode::handleKeyPress 3120 // Workaround for BUG: 334032 (https://bugs.kde.org/show_bug.cgi?id=334032) 3121 if (k->key() == Qt::Key_Space && k->modifiers() == Qt::ControlModifier) { 3122 keyPressEvent(k); 3123 if (k->isAccepted()) { 3124 return true; 3125 } 3126 } 3127 } break; 3128 3129 case QEvent::KeyPress: { 3130 QKeyEvent *k = static_cast<QKeyEvent *>(e); 3131 3132 // Override all other single key shortcuts which do not use a modifier other than Shift 3133 if (obj == this && (!k->modifiers() || k->modifiers() == Qt::ShiftModifier)) { 3134 keyPressEvent(k); 3135 if (k->isAccepted()) { 3136 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "using keystroke"; 3137 return true; 3138 } 3139 } 3140 3141 // qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "ignoring"; 3142 } break; 3143 3144 case QEvent::DragMove: { 3145 QPoint currentPoint = ((QDragMoveEvent *)e)->pos(); 3146 3147 QRect doNotScrollRegion(s_scrollMargin, s_scrollMargin, width() - s_scrollMargin * 2, height() - s_scrollMargin * 2); 3148 3149 if (!doNotScrollRegion.contains(currentPoint)) { 3150 startDragScroll(); 3151 // Keep sending move events 3152 ((QDragMoveEvent *)e)->accept(QRect(0, 0, 0, 0)); 3153 } 3154 3155 dragMoveEvent((QDragMoveEvent *)e); 3156 } break; 3157 3158 case QEvent::DragLeave: 3159 // happens only when pressing ESC while dragging 3160 stopDragScroll(); 3161 break; 3162 3163 case QEvent::WindowDeactivate: 3164 hideBracketMatchPreview(); 3165 break; 3166 3167 case QEvent::ScrollPrepare: { 3168 QScrollPrepareEvent *s = static_cast<QScrollPrepareEvent *>(e); 3169 scrollPrepareEvent(s); 3170 } return true; 3171 3172 case QEvent::Scroll: { 3173 QScrollEvent *s = static_cast<QScrollEvent *>(e); 3174 scrollEvent(s); 3175 } return true; 3176 3177 default: 3178 break; 3179 } 3180 3181 return QWidget::eventFilter(obj, e); 3182 } 3183 3184 void KateViewInternal::keyPressEvent(QKeyEvent *e) 3185 { 3186 m_shiftKeyPressed = e->modifiers() & Qt::ShiftModifier; 3187 if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) { 3188 view()->emitNavigateLeft(); 3189 e->setAccepted(true); 3190 return; 3191 } 3192 if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) { 3193 view()->emitNavigateRight(); 3194 e->setAccepted(true); 3195 return; 3196 } 3197 if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) { 3198 view()->emitNavigateUp(); 3199 e->setAccepted(true); 3200 return; 3201 } 3202 if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) { 3203 view()->emitNavigateDown(); 3204 e->setAccepted(true); 3205 return; 3206 } 3207 if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) { 3208 view()->emitNavigateAccept(); 3209 e->setAccepted(true); 3210 return; 3211 } 3212 if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) { 3213 view()->emitNavigateBack(); 3214 e->setAccepted(true); 3215 return; 3216 } 3217 3218 if (e->key() == Qt::Key_Alt && view()->completionWidget()->isCompletionActive()) { 3219 view()->completionWidget()->toggleDocumentation(); 3220 } 3221 3222 // Note: AND'ing with <Shift> is a quick hack to fix Key_Enter 3223 const int key = e->key() | (e->modifiers() & Qt::ShiftModifier); 3224 3225 if (m_currentInputMode->keyPress(e)) { 3226 return; 3227 } 3228 3229 if (!doc()->isReadWrite()) { 3230 e->ignore(); 3231 return; 3232 } 3233 3234 if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) || (key == Qt::SHIFT + Qt::Key_Return) || (key == Qt::SHIFT + Qt::Key_Enter)) { 3235 view()->keyReturn(); 3236 e->accept(); 3237 return; 3238 } 3239 3240 if (key == Qt::Key_Backspace || key == Qt::SHIFT + Qt::Key_Backspace) { 3241 // view()->backspace(); 3242 e->accept(); 3243 3244 return; 3245 } 3246 3247 if (key == Qt::Key_Tab || key == Qt::SHIFT + Qt::Key_Backtab || key == Qt::Key_Backtab) { 3248 if (key == Qt::Key_Tab) { 3249 uint tabHandling = doc()->config()->tabHandling(); 3250 // convert tabSmart into tabInsertsTab or tabIndents: 3251 if (tabHandling == KateDocumentConfig::tabSmart) { 3252 // multiple lines selected 3253 if (view()->selection() && !view()->selectionRange().onSingleLine()) { 3254 tabHandling = KateDocumentConfig::tabIndents; 3255 } 3256 3257 // otherwise: take look at cursor position 3258 else { 3259 // if the cursor is at or before the first non-space character 3260 // or on an empty line, 3261 // Tab indents, otherwise it inserts a tab character. 3262 Kate::TextLine line = doc()->kateTextLine(m_cursor.line()); 3263 int first = line->firstChar(); 3264 if (first < 0 || m_cursor.column() <= first) { 3265 tabHandling = KateDocumentConfig::tabIndents; 3266 } else { 3267 tabHandling = KateDocumentConfig::tabInsertsTab; 3268 } 3269 } 3270 } 3271 3272 // either we just insert a tab or we convert that into an indent action 3273 if (tabHandling == KateDocumentConfig::tabInsertsTab) { 3274 doc()->typeChars(m_view, QStringLiteral("\t")); 3275 } else { 3276 doc()->editBegin(); 3277 for (const auto &c : std::as_const(m_view->m_secondaryCursors)) { 3278 auto cursor = c.cursor(); 3279 doc()->indent(KTextEditor::Range(cursor.line(), 0, cursor.line(), 0), 1); 3280 } 3281 3282 doc()->indent(view()->selection() ? view()->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), 1); 3283 doc()->editEnd(); 3284 } 3285 3286 e->accept(); 3287 3288 return; 3289 } else if (doc()->config()->tabHandling() != KateDocumentConfig::tabInsertsTab) { 3290 // key == Qt::SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab 3291 doc()->indent(view()->selection() ? view()->selectionRange() : KTextEditor::Range(m_cursor.line(), 0, m_cursor.line(), 0), -1); 3292 e->accept(); 3293 3294 return; 3295 } 3296 } 3297 3298 if (isAcceptableInput(e)) { 3299 doc()->typeChars(m_view, e->text()); 3300 e->accept(); 3301 return; 3302 } 3303 3304 e->ignore(); 3305 } 3306 3307 void KateViewInternal::keyReleaseEvent(QKeyEvent *e) 3308 { 3309 if (m_shiftKeyPressed && (e->modifiers() & Qt::ShiftModifier) == 0) { 3310 m_shiftKeyPressed = false; 3311 3312 if (m_selChangedByUser) { 3313 if (view()->selection()) { 3314 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection); 3315 } 3316 3317 m_selChangedByUser = false; 3318 } 3319 } 3320 3321 e->ignore(); 3322 return; 3323 } 3324 3325 bool KateViewInternal::isAcceptableInput(const QKeyEvent *e) 3326 { 3327 // reimplemented from QInputControl::isAcceptableInput() 3328 3329 const QString text = e->text(); 3330 if (text.isEmpty()) { 3331 return false; 3332 } 3333 3334 const QChar c = text.at(0); 3335 3336 // Formatting characters such as ZWNJ, ZWJ, RLM, etc. This needs to go before the 3337 // next test, since CTRL+SHIFT is sometimes used to input it on Windows. 3338 // see bug 389796 (typing formatting characters such as ZWNJ) 3339 // and bug 396764 (typing soft-hyphens) 3340 if (c.category() == QChar::Other_Format) { 3341 return true; 3342 } 3343 3344 // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards 3345 if ((e->modifiers() == Qt::ControlModifier) || (e->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier))) { 3346 return false; 3347 } 3348 3349 // printable or private use is good, see e.g. bug 366424 (typing "private use" unicode characters) 3350 return c.isPrint() || (c.category() == QChar::Other_PrivateUse); 3351 } 3352 3353 void KateViewInternal::contextMenuEvent(QContextMenuEvent *e) 3354 { 3355 // calculate where to show the context menu 3356 3357 QPoint p = e->pos(); 3358 3359 if (e->reason() == QContextMenuEvent::Keyboard) { 3360 makeVisible(m_displayCursor, 0); 3361 p = cursorCoordinates(false); 3362 p.rx() -= startX(); 3363 } else if (!view()->selection() || view()->config()->persistentSelection()) { 3364 placeCursor(e->pos()); 3365 } 3366 3367 // show it 3368 QMenu *cm = view()->contextMenu(); 3369 if (cm) { 3370 view()->spellingMenu()->prepareToBeShown(cm); 3371 cm->popup(mapToGlobal(p)); 3372 e->accept(); 3373 } 3374 } 3375 3376 void KateViewInternal::mousePressEvent(QMouseEvent *e) 3377 { 3378 if (sendMouseEventToInputContext(e)) { 3379 return; 3380 } 3381 3382 // was an inline note clicked? 3383 const auto noteData = inlineNoteAt(e->globalPos()); 3384 const KTextEditor::InlineNote note(noteData); 3385 if (note.position().isValid()) { 3386 note.provider()->inlineNoteActivated(noteData, e->button(), e->globalPos()); 3387 return; 3388 } 3389 3390 commitPreedit(); 3391 3392 // no -- continue with normal handling 3393 switch (e->button()) { 3394 case Qt::LeftButton: 3395 3396 m_selChangedByUser = false; 3397 3398 if (!view()->isMulticursorNotAllowed() && e->modifiers() == view()->config()->multiCursorModifiers()) { 3399 auto pos = cursorForPoint(e->pos()); 3400 if (pos.isValid()) { 3401 view()->addSecondaryCursor(pos); 3402 e->accept(); 3403 return; 3404 } 3405 } else { 3406 view()->clearSecondaryCursors(); 3407 } 3408 3409 if (m_possibleTripleClick) { 3410 m_possibleTripleClick = false; 3411 3412 m_selectionMode = Line; 3413 3414 if (e->modifiers() & Qt::ShiftModifier) { 3415 updateSelection(m_cursor, true); 3416 } else { 3417 view()->selectLine(m_cursor); 3418 if (view()->selection()) { 3419 m_selectAnchor = view()->selectionRange().start(); 3420 } 3421 } 3422 3423 if (view()->selection()) { 3424 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection); 3425 } 3426 3427 // Keep the line at the select anchor selected during further 3428 // mouse selection 3429 if (m_selectAnchor.line() > view()->selectionRange().start().line()) { 3430 // Preserve the last selected line 3431 if (m_selectAnchor == view()->selectionRange().end() && m_selectAnchor.column() == 0) { 3432 m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line() - 1, 0)); 3433 } else { 3434 m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), 0)); 3435 } 3436 m_selectionCached.setEnd(view()->selectionRange().end()); 3437 } else { 3438 // Preserve the first selected line 3439 m_selectionCached.setStart(view()->selectionRange().start()); 3440 if (view()->selectionRange().end().line() > view()->selectionRange().start().line()) { 3441 m_selectionCached.setEnd(KTextEditor::Cursor(view()->selectionRange().start().line() + 1, 0)); 3442 } else { 3443 m_selectionCached.setEnd(view()->selectionRange().end()); 3444 } 3445 } 3446 3447 moveCursorToSelectionEdge(); 3448 3449 m_scrollX = 0; 3450 m_scrollY = 0; 3451 m_scrollTimer.start(50); 3452 3453 e->accept(); 3454 return; 3455 } else if (m_selectionMode == Default) { 3456 m_selectionMode = Mouse; 3457 } 3458 3459 // request the software keyboard, if any 3460 if (e->button() == Qt::LeftButton && qApp->autoSipEnabled()) { 3461 QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); 3462 if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) { 3463 QEvent event(QEvent::RequestSoftwareInputPanel); 3464 QApplication::sendEvent(this, &event); 3465 } 3466 } 3467 3468 if (e->modifiers() & Qt::ShiftModifier) { 3469 if (!m_selectAnchor.isValid()) { 3470 m_selectAnchor = m_cursor; 3471 } 3472 } else { 3473 m_selectionCached = KTextEditor::Range::invalid(); 3474 } 3475 3476 if (view()->config()->textDragAndDrop() && !(e->modifiers() & Qt::ShiftModifier) && isTargetSelected(e->pos())) { 3477 m_dragInfo.state = diPending; 3478 m_dragInfo.start = e->pos(); 3479 } else { 3480 m_dragInfo.state = diNone; 3481 3482 if (e->modifiers() & Qt::ShiftModifier) { 3483 placeCursor(e->pos(), true, false); 3484 if (m_selectionCached.start().isValid()) { 3485 if (m_cursor.toCursor() < m_selectionCached.start()) { 3486 m_selectAnchor = m_selectionCached.end(); 3487 } else { 3488 m_selectAnchor = m_selectionCached.start(); 3489 } 3490 } 3491 3492 // update selection and do potential clipboard update, see bug 443642 3493 setSelection(KTextEditor::Range(m_selectAnchor, m_cursor)); 3494 if (view()->selection()) { 3495 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection); 3496 } 3497 } else { 3498 placeCursor(e->pos()); 3499 } 3500 3501 m_scrollX = 0; 3502 m_scrollY = 0; 3503 3504 m_scrollTimer.start(50); 3505 } 3506 3507 e->accept(); 3508 break; 3509 3510 case Qt::RightButton: 3511 if (e->pos().x() == 0) { 3512 // Special handling for folding by right click 3513 placeCursor(e->pos()); 3514 e->accept(); 3515 } 3516 break; 3517 3518 default: 3519 e->ignore(); 3520 break; 3521 } 3522 } 3523 3524 void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e) 3525 { 3526 if (sendMouseEventToInputContext(e)) { 3527 return; 3528 } 3529 if (e->button() == Qt::LeftButton) { 3530 m_selectionMode = Word; 3531 3532 if (e->modifiers() & Qt::ShiftModifier) { 3533 // Now select the word under the select anchor 3534 int cs; 3535 int ce; 3536 Kate::TextLine l = doc()->kateTextLine(m_selectAnchor.line()); 3537 3538 ce = m_selectAnchor.column(); 3539 if (ce > 0 && doc()->highlight()->isInWord(l->at(ce))) { 3540 for (; ce < l->length(); ce++) { 3541 if (!doc()->highlight()->isInWord(l->at(ce))) { 3542 break; 3543 } 3544 } 3545 } 3546 3547 cs = m_selectAnchor.column() - 1; 3548 if (cs < doc()->lineLength(m_selectAnchor.line()) && doc()->highlight()->isInWord(l->at(cs))) { 3549 for (cs--; cs >= 0; cs--) { 3550 if (!doc()->highlight()->isInWord(l->at(cs))) { 3551 break; 3552 } 3553 } 3554 } 3555 3556 // ...and keep it selected 3557 if (cs + 1 < ce) { 3558 m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), cs + 1)); 3559 m_selectionCached.setEnd(KTextEditor::Cursor(m_selectAnchor.line(), ce)); 3560 } else { 3561 m_selectionCached.setStart(m_selectAnchor); 3562 m_selectionCached.setEnd(m_selectAnchor); 3563 } 3564 // Now word select to the mouse cursor 3565 placeCursor(e->pos(), true); 3566 } else { 3567 // first clear the selection, otherwise we run into bug #106402 3568 // ...and set the cursor position, for the same reason (otherwise there 3569 // are *other* idiosyncrasies we can't fix without reintroducing said 3570 // bug) 3571 // Parameters: don't redraw, and don't emit selectionChanged signal yet 3572 view()->clearSelection(false, false); 3573 placeCursor(e->pos()); 3574 view()->selectWord(m_cursor); 3575 cursorToMatchingBracket(true); 3576 3577 if (view()->selection()) { 3578 m_selectAnchor = view()->selectionRange().start(); 3579 m_selectionCached = view()->selectionRange(); 3580 } else { 3581 m_selectAnchor = m_cursor; 3582 m_selectionCached = KTextEditor::Range(m_cursor, m_cursor); 3583 } 3584 } 3585 3586 // Move cursor to end (or beginning) of selected word 3587 #ifndef Q_OS_MACOS 3588 if (view()->selection()) { 3589 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection); 3590 } 3591 #endif 3592 3593 moveCursorToSelectionEdge(); 3594 m_possibleTripleClick = true; 3595 QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout())); 3596 3597 m_scrollX = 0; 3598 m_scrollY = 0; 3599 3600 m_scrollTimer.start(50); 3601 3602 e->accept(); 3603 } else { 3604 e->ignore(); 3605 } 3606 } 3607 3608 void KateViewInternal::tripleClickTimeout() 3609 { 3610 m_possibleTripleClick = false; 3611 } 3612 3613 void KateViewInternal::beginSelectLine(const QPoint &pos) 3614 { 3615 placeCursor(pos); 3616 m_possibleTripleClick = true; // set so subsequent mousePressEvent will select line 3617 } 3618 3619 void KateViewInternal::mouseReleaseEvent(QMouseEvent *e) 3620 { 3621 if (sendMouseEventToInputContext(e)) { 3622 return; 3623 } 3624 3625 switch (e->button()) { 3626 case Qt::LeftButton: 3627 m_selectionMode = Default; 3628 // m_selectionCached.start().setLine( -1 ); 3629 3630 if (m_selChangedByUser) { 3631 if (view()->selection()) { 3632 QApplication::clipboard()->setText(view()->selectionText(), QClipboard::Selection); 3633 } 3634 moveCursorToSelectionEdge(); 3635 3636 m_selChangedByUser = false; 3637 } 3638 3639 if (m_dragInfo.state == diPending) { 3640 placeCursor(e->pos(), e->modifiers() & Qt::ShiftModifier); 3641 } else if (m_dragInfo.state == diNone) { 3642 m_scrollTimer.stop(); 3643 } 3644 3645 m_dragInfo.state = diNone; 3646 3647 // merge any overlapping selections/cursors 3648 if (view()->selection() && !view()->m_secondaryCursors.empty()) { 3649 mergeSelections(); 3650 } 3651 3652 e->accept(); 3653 break; 3654 3655 case Qt::MiddleButton: 3656 if (!view()->config()->mousePasteAtCursorPosition()) { 3657 placeCursor(e->pos()); 3658 } 3659 3660 if (doc()->isReadWrite()) { 3661 QString clipboard = QApplication::clipboard()->text(QClipboard::Selection); 3662 view()->paste(&clipboard); 3663 } 3664 3665 e->accept(); 3666 break; 3667 3668 default: 3669 e->ignore(); 3670 break; 3671 } 3672 } 3673 3674 void KateViewInternal::leaveEvent(QEvent *) 3675 { 3676 m_textHintTimer.stop(); 3677 3678 // fix bug 194452, scrolling keeps going if you scroll via mouse drag and press and other mouse 3679 // button outside the view area 3680 if (m_dragInfo.state == diNone) { 3681 m_scrollTimer.stop(); 3682 } 3683 3684 hideBracketMatchPreview(); 3685 } 3686 3687 KTextEditor::Cursor KateViewInternal::coordinatesToCursor(const QPoint &_coord, bool includeBorder) const 3688 { 3689 QPoint coord(_coord); 3690 3691 KTextEditor::Cursor ret = KTextEditor::Cursor::invalid(); 3692 3693 if (includeBorder) { 3694 coord.rx() -= m_leftBorder->width(); 3695 } 3696 coord.rx() += startX(); 3697 3698 const KateTextLayout &thisLine = yToKateTextLayout(coord.y()); 3699 if (thisLine.isValid()) { 3700 ret = renderer()->xToCursor(thisLine, coord.x(), !view()->wrapCursor()); 3701 } 3702 3703 if (ret.column() > view()->document()->lineLength(ret.line())) { 3704 // The cursor is beyond the end of the line; in that case the renderer 3705 // gives the index of the character behind the last one. 3706 return KTextEditor::Cursor::invalid(); 3707 } 3708 3709 return ret; 3710 } 3711 3712 void KateViewInternal::mouseMoveEvent(QMouseEvent *e) 3713 { 3714 if (m_scroller->state() != QScroller::Inactive) { 3715 // Touchscreen is handled by scrollEvent() 3716 return; 3717 } 3718 KTextEditor::Cursor newPosition = coordinatesToCursor(e->pos(), false); 3719 if (newPosition != m_mouse) { 3720 m_mouse = newPosition; 3721 mouseMoved(); 3722 } 3723 3724 if (e->buttons() == Qt::NoButton) { 3725 auto noteData = inlineNoteAt(e->globalPos()); 3726 auto focusChanged = false; 3727 if (noteData.m_position.isValid()) { 3728 if (!m_activeInlineNote.m_position.isValid()) { 3729 // no active note -- focus in 3730 tagLine(noteData.m_position); 3731 focusChanged = true; 3732 noteData.m_underMouse = true; 3733 noteData.m_provider->inlineNoteFocusInEvent(KTextEditor::InlineNote(noteData), e->globalPos()); 3734 m_activeInlineNote = noteData; 3735 } else { 3736 noteData.m_provider->inlineNoteMouseMoveEvent(KTextEditor::InlineNote(noteData), e->globalPos()); 3737 } 3738 } else if (m_activeInlineNote.m_position.isValid()) { 3739 tagLine(m_activeInlineNote.m_position); 3740 focusChanged = true; 3741 m_activeInlineNote.m_underMouse = false; 3742 m_activeInlineNote.m_provider->inlineNoteFocusOutEvent(KTextEditor::InlineNote(m_activeInlineNote)); 3743 m_activeInlineNote = {}; 3744 } 3745 if (focusChanged) { 3746 // the note might change its appearance in reaction to the focus event 3747 updateDirty(); 3748 } 3749 } 3750 3751 if (e->buttons() & Qt::LeftButton) { 3752 if (m_dragInfo.state == diPending) { 3753 // we had a mouse down, but haven't confirmed a drag yet 3754 // if the mouse has moved sufficiently, we will confirm 3755 QPoint p(e->pos() - m_dragInfo.start); 3756 3757 // we've left the drag square, we can start a real drag operation now 3758 if (p.manhattanLength() > QApplication::startDragDistance()) { 3759 doDrag(); 3760 } 3761 3762 return; 3763 } else if (m_dragInfo.state == diDragging) { 3764 // Don't do anything after a canceled drag until the user lets go of 3765 // the mouse button! 3766 return; 3767 } 3768 3769 m_mouseX = e->x(); 3770 m_mouseY = e->y(); 3771 3772 m_scrollX = 0; 3773 m_scrollY = 0; 3774 int d = renderer()->lineHeight(); 3775 3776 if (m_mouseX < 0) { 3777 m_scrollX = -d; 3778 } 3779 3780 if (m_mouseX > width()) { 3781 m_scrollX = d; 3782 } 3783 3784 if (m_mouseY < 0) { 3785 m_mouseY = 0; 3786 m_scrollY = -d; 3787 } 3788 3789 if (m_mouseY > height()) { 3790 m_mouseY = height(); 3791 m_scrollY = d; 3792 } 3793 3794 if (!m_scrollY) { 3795 // We are on top of line number area and dragging 3796 // Since we never get negative y, ensure to scroll up 3797 // a bit otherwise we get stuck on the topview line 3798 if (!m_mouseY) { 3799 m_scrollY -= d; 3800 } 3801 placeCursor(QPoint(m_mouseX, m_mouseY), true); 3802 } 3803 } else { 3804 if (view()->config()->textDragAndDrop() && isTargetSelected(e->pos())) { 3805 // mouse is over selected text. indicate that the text is draggable by setting 3806 // the arrow cursor as other Qt text editing widgets do 3807 if (m_mouseCursor != Qt::ArrowCursor) { 3808 m_mouseCursor = Qt::ArrowCursor; 3809 setCursor(m_mouseCursor); 3810 } 3811 } else { 3812 // normal text cursor 3813 if (m_mouseCursor != Qt::IBeamCursor) { 3814 m_mouseCursor = Qt::IBeamCursor; 3815 setCursor(m_mouseCursor); 3816 } 3817 } 3818 // We need to check whether the mouse position is actually within the widget, 3819 // because other widgets like the icon border forward their events to this, 3820 // and we will create invalid text hint requests if we don't check 3821 if (textHintsEnabled() && geometry().contains(parentWidget()->mapFromGlobal(e->globalPos()))) { 3822 if (QToolTip::isVisible()) { 3823 QToolTip::hideText(); 3824 } 3825 m_textHintTimer.start(m_textHintDelay); 3826 m_textHintPos = e->pos(); 3827 } 3828 } 3829 } 3830 3831 void KateViewInternal::updateDirty() 3832 { 3833 const int h = renderer()->lineHeight(); 3834 3835 int currentRectStart = -1; 3836 int currentRectEnd = -1; 3837 3838 QRegion updateRegion; 3839 3840 { 3841 for (int i = 0; i < cache()->viewCacheLineCount(); ++i) { 3842 if (cache()->viewLine(i).isDirty()) { 3843 if (currentRectStart == -1) { 3844 currentRectStart = h * i; 3845 currentRectEnd = h; 3846 } else { 3847 currentRectEnd += h; 3848 } 3849 3850 } else if (currentRectStart != -1) { 3851 updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); 3852 currentRectStart = -1; 3853 currentRectEnd = -1; 3854 } 3855 } 3856 } 3857 3858 if (currentRectStart != -1) { 3859 updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); 3860 } 3861 3862 if (!updateRegion.isEmpty()) { 3863 if (debugPainting) { 3864 qCDebug(LOG_KTE) << "Update dirty region " << updateRegion; 3865 } 3866 update(updateRegion); 3867 } 3868 } 3869 3870 void KateViewInternal::hideEvent(QHideEvent *e) 3871 { 3872 Q_UNUSED(e); 3873 if (view()->isCompletionActive()) { 3874 view()->completionWidget()->abortCompletion(); 3875 } 3876 } 3877 3878 void KateViewInternal::paintEvent(QPaintEvent *e) 3879 { 3880 if (debugPainting) { 3881 qCDebug(LOG_KTE) << "GOT PAINT EVENT: Region" << e->region(); 3882 } 3883 3884 const QRect &unionRect = e->rect(); 3885 3886 int xStart = startX() + unionRect.x(); 3887 int xEnd = xStart + unionRect.width(); 3888 uint h = renderer()->lineHeight(); 3889 uint startz = (unionRect.y() / h); 3890 uint endz = startz + 1 + (unionRect.height() / h); 3891 uint lineRangesSize = cache()->viewCacheLineCount(); 3892 const KTextEditor::Cursor pos = m_cursor; 3893 3894 QPainter paint(this); 3895 3896 // THIS IS ULTRA EVIL AND ADDS STRANGE RENDERING ARTIFACTS WITH SCALING!!!! 3897 // SEE BUG https://bugreports.qt.io/browse/QTBUG-66036 3898 // paint.setRenderHints(QPainter::TextAntialiasing); 3899 3900 paint.save(); 3901 3902 renderer()->setCaretStyle(m_currentInputMode->caretStyle()); 3903 renderer()->setShowTabs(doc()->config()->showTabs()); 3904 renderer()->setShowSpaces(doc()->config()->showSpaces()); 3905 renderer()->updateMarkerSize(); 3906 3907 // paint line by line 3908 // this includes parts that span areas without real lines 3909 // translate to first line to paint 3910 paint.translate(unionRect.x(), startz * h); 3911 for (uint z = startz; z <= endz; z++) { 3912 // paint regions without lines mapped to 3913 if ((z >= lineRangesSize) || (cache()->viewLine(z).line() == -1)) { 3914 if (!(z >= lineRangesSize)) { 3915 cache()->viewLine(z).setDirty(false); 3916 } 3917 paint.fillRect(0, 0, unionRect.width(), h, renderer()->config()->backgroundColor()); 3918 } 3919 3920 // paint text lines 3921 else { 3922 // If viewLine() returns non-zero, then a document line was split 3923 // in several visual lines, and we're trying to paint visual line 3924 // that is not the first. In that case, this line was already 3925 // painted previously, since KateRenderer::paintTextLine paints 3926 // all visual lines. 3927 // 3928 // Except if we're at the start of the region that needs to 3929 // be painted -- when no previous calls to paintTextLine were made. 3930 KateTextLayout &thisLine = cache()->viewLine(z); 3931 if (!thisLine.viewLine() || z == startz) { 3932 // paint our line 3933 // set clipping region to only paint the relevant parts 3934 paint.save(); 3935 paint.translate(QPoint(0, h * -thisLine.viewLine())); 3936 3937 // compute rect for line, fill the stuff 3938 // important: as we allow some ARGB colors for other stuff, it is REALLY important to fill the full range once! 3939 const QRectF lineRect(0, 0, unionRect.width(), h * thisLine.kateLineLayout()->viewLineCount()); 3940 paint.fillRect(lineRect, renderer()->config()->backgroundColor()); 3941 3942 // THIS IS ULTRA EVIL AND ADDS STRANGE RENDERING ARTIFACTS WITH SCALING!!!! 3943 // SEE BUG https://bugreports.qt.io/browse/QTBUG-66036 3944 // => using a QRectF solves the cut of 1 pixel, the same call with QRect does create artifacts! 3945 paint.setClipRect(lineRect); 3946 renderer()->paintTextLine(paint, thisLine.kateLineLayout(), xStart, xEnd, &pos); 3947 paint.restore(); 3948 3949 // line painted, reset and state + mark line as non-dirty 3950 thisLine.setDirty(false); 3951 } 3952 } 3953 3954 // translate to next line 3955 paint.translate(0, h); 3956 } 3957 3958 paint.restore(); 3959 3960 if (m_textAnimation) { 3961 m_textAnimation->draw(paint); 3962 } 3963 } 3964 3965 void KateViewInternal::resizeEvent(QResizeEvent *e) 3966 { 3967 bool expandedHorizontally = width() > e->oldSize().width(); 3968 bool expandedVertically = height() > e->oldSize().height(); 3969 bool heightChanged = height() != e->oldSize().height(); 3970 3971 m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); 3972 m_madeVisible = false; 3973 3974 // resize the bracket match preview 3975 if (m_bmPreview) { 3976 showBracketMatchPreview(); 3977 } 3978 3979 if (heightChanged) { 3980 setAutoCenterLines(m_autoCenterLines, false); 3981 m_cachedMaxStartPos.setPosition(-1, -1); 3982 } 3983 3984 if (view()->dynWordWrap()) { 3985 bool dirtied = false; 3986 3987 for (int i = 0; i < cache()->viewCacheLineCount(); i++) { 3988 // find the first dirty line 3989 // the word wrap updateView algorithm is forced to check all lines after a dirty one 3990 KateTextLayout viewLine = cache()->viewLine(i); 3991 3992 if (viewLine.wrap() || viewLine.isRightToLeft() || viewLine.width() > width()) { 3993 dirtied = true; 3994 viewLine.setDirty(); 3995 break; 3996 } 3997 } 3998 3999 if (dirtied || heightChanged) { 4000 updateView(true); 4001 m_leftBorder->update(); 4002 } 4003 } else { 4004 updateView(); 4005 4006 if (expandedHorizontally && startX() > 0) { 4007 scrollColumns(startX() - (width() - e->oldSize().width())); 4008 } 4009 } 4010 4011 if (width() < e->oldSize().width() && !view()->wrapCursor()) { 4012 // May have to restrain cursor to new smaller width... 4013 if (m_cursor.column() > doc()->lineLength(m_cursor.line())) { 4014 KateTextLayout thisLine = currentLayout(m_cursor); 4015 4016 KTextEditor::Cursor newCursor(m_cursor.line(), 4017 thisLine.endCol() + ((width() - thisLine.xOffset() - (thisLine.width() - startX())) / renderer()->spaceWidth()) - 1); 4018 if (newCursor.column() < m_cursor.column()) { 4019 updateCursor(newCursor); 4020 } 4021 } 4022 } 4023 4024 if (expandedVertically) { 4025 KTextEditor::Cursor max = maxStartPos(); 4026 if (startPos() > max) { 4027 scrollPos(max); 4028 return; // already fired displayRangeChanged 4029 } 4030 } 4031 Q_EMIT view()->displayRangeChanged(m_view); 4032 } 4033 4034 void KateViewInternal::moveEvent(QMoveEvent *e) 4035 { 4036 // move the bracket match preview to the new location 4037 if (e->pos() != e->oldPos() && m_bmPreview) { 4038 showBracketMatchPreview(); 4039 } 4040 4041 QWidget::moveEvent(e); 4042 } 4043 4044 void KateViewInternal::scrollTimeout() 4045 { 4046 if (m_scrollX || m_scrollY) { 4047 const int scrollTo = startPos().line() + (m_scrollY / (int)renderer()->lineHeight()); 4048 placeCursor(QPoint(m_mouseX, m_mouseY), true); 4049 scrollLines(scrollTo); 4050 } 4051 } 4052 4053 void KateViewInternal::cursorTimeout() 4054 { 4055 if (!debugPainting && m_currentInputMode->blinkCaret()) { 4056 renderer()->setDrawCaret(!renderer()->drawCaret()); 4057 paintCursor(); 4058 } 4059 } 4060 4061 void KateViewInternal::textHintTimeout() 4062 { 4063 m_textHintTimer.stop(); 4064 4065 KTextEditor::Cursor c = coordinatesToCursor(m_textHintPos, false); 4066 if (!c.isValid()) { 4067 return; 4068 } 4069 4070 QStringList textHints; 4071 for (KTextEditor::TextHintProvider *const p : m_textHintProviders) { 4072 if (!p) { 4073 continue; 4074 } 4075 4076 const QString hint = p->textHint(m_view, c); 4077 if (!hint.isEmpty()) { 4078 textHints.append(hint); 4079 } 4080 } 4081 4082 if (!textHints.isEmpty()) { 4083 qCDebug(LOG_KTE) << "Hint text: " << textHints; 4084 QString hint; 4085 for (const QString &str : std::as_const(textHints)) { 4086 hint += QStringLiteral("<p>%1</p>").arg(str); 4087 } 4088 QPoint pos(startX() + m_textHintPos.x(), m_textHintPos.y()); 4089 QToolTip::showText(mapToGlobal(pos), hint); 4090 } 4091 } 4092 4093 void KateViewInternal::focusInEvent(QFocusEvent *) 4094 { 4095 if (QApplication::cursorFlashTime() > 0) { 4096 m_cursorTimer.start(QApplication::cursorFlashTime() / 2); 4097 } 4098 4099 paintCursor(); 4100 4101 doc()->setActiveView(m_view); 4102 4103 // this will handle focus stuff in kateview 4104 view()->slotGotFocus(); 4105 } 4106 4107 void KateViewInternal::focusOutEvent(QFocusEvent *) 4108 { 4109 // if (view()->isCompletionActive()) 4110 // view()->abortCompletion(); 4111 4112 m_cursorTimer.stop(); 4113 view()->renderer()->setDrawCaret(true); 4114 paintCursor(); 4115 4116 m_textHintTimer.stop(); 4117 4118 view()->slotLostFocus(); 4119 4120 hideBracketMatchPreview(); 4121 } 4122 4123 void KateViewInternal::doDrag() 4124 { 4125 m_dragInfo.state = diDragging; 4126 m_dragInfo.dragObject = new QDrag(this); 4127 std::unique_ptr<QMimeData> mimeData(new QMimeData()); 4128 mimeData->setText(view()->selectionText()); 4129 4130 const auto startCur = view()->selectionRange().start(); 4131 const auto endCur = view()->selectionRange().end(); 4132 if (!startCur.isValid() || !endCur.isValid()) { 4133 return; 4134 } 4135 4136 int startLine = startCur.line(); 4137 int endLine = endCur.line(); 4138 4139 /** 4140 * Get real first and last visible line nos. 4141 * This is important as startLine() / endLine() are virtual and we can't use 4142 * them here 4143 */ 4144 const int firstVisibleLine = view()->firstDisplayedLineInternal(KTextEditor::View::RealLine); 4145 const int lastVisibleLine = view()->lastDisplayedLineInternal(KTextEditor::View::RealLine); 4146 4147 // get visible selected lines 4148 for (int l = startLine; l <= endLine; ++l) { 4149 if (l >= firstVisibleLine) { 4150 break; 4151 } 4152 ++startLine; 4153 } 4154 for (int l = endLine; l >= startLine; --l) { 4155 if (l <= lastVisibleLine) { 4156 break; 4157 } 4158 --endLine; 4159 } 4160 4161 // calculate the height / width / scale 4162 int w = 0; 4163 int h = 0; 4164 const QFontMetricsF &fm = renderer()->currentFontMetrics(); 4165 for (int l = startLine; l <= endLine; ++l) { 4166 w = std::max((int)fm.horizontalAdvance(doc()->line(l)), w); 4167 h += renderer()->lineHeight(); 4168 } 4169 qreal scale = h > m_view->height() / 2 ? 0.75 : 1.0; 4170 4171 // Calculate start x pos on start line 4172 int sX = 0; 4173 if (startLine == startCur.line()) { 4174 sX = renderer()->cursorToX(cache()->textLayout(startCur), startCur, !view()->wrapCursor()); 4175 } 4176 4177 // Calculate end x pos on end line 4178 int eX = 0; 4179 if (endLine == endCur.line()) { 4180 eX = renderer()->cursorToX(cache()->textLayout(endCur), endCur, !view()->wrapCursor()); 4181 } 4182 4183 // These are the occurunce highlights, the ones you get when you select a word 4184 // We clear them here so that they don't come up in the dragged pixmap 4185 // After we are done creating the pixmap, we restore them 4186 if (view()->selection()) { 4187 view()->clearHighlights(); 4188 } 4189 4190 // Create a pixmap this selection 4191 const qreal dpr = devicePixelRatioF(); 4192 QPixmap pixmap(w * dpr, h * dpr); 4193 if (!pixmap.isNull()) { 4194 pixmap.setDevicePixelRatio(dpr); 4195 pixmap.fill(Qt::transparent); 4196 renderer()->paintSelection(&pixmap, startLine, sX, endLine, eX, scale); 4197 4198 if (view()->selection()) { 4199 // Tell the view to restore the highlights 4200 Q_EMIT view()->selectionChanged(view()); 4201 } 4202 } 4203 4204 // Calculate position where pixmap will appear when user 4205 // starts dragging 4206 const int x = 0; 4207 /** 4208 * lineToVisibleLine() = real line => virtual line 4209 * This is necessary here because if there is a folding in the current 4210 * view lines, the y pos can be incorrect. So, we make sure to convert 4211 * it to virtual line before calculating y 4212 */ 4213 const int y = lineToY(view()->m_textFolding.lineToVisibleLine(startLine)); 4214 const QPoint pos = mapFromGlobal(QCursor::pos()) - QPoint(x, y); 4215 4216 m_dragInfo.dragObject->setPixmap(pixmap); 4217 m_dragInfo.dragObject->setHotSpot(pos); 4218 m_dragInfo.dragObject->setMimeData(mimeData.release()); 4219 m_dragInfo.dragObject->exec(Qt::MoveAction | Qt::CopyAction); 4220 } 4221 4222 void KateViewInternal::dragEnterEvent(QDragEnterEvent *event) 4223 { 4224 if (event->source() == this) { 4225 event->setDropAction(Qt::MoveAction); 4226 } 4227 event->setAccepted((event->mimeData()->hasText() && doc()->isReadWrite()) || event->mimeData()->hasUrls()); 4228 } 4229 4230 void KateViewInternal::fixDropEvent(QDropEvent *event) 4231 { 4232 if (event->source() != this) { 4233 event->setDropAction(Qt::CopyAction); 4234 } else { 4235 Qt::DropAction action = Qt::MoveAction; 4236 #ifdef Q_WS_MAC 4237 if (event->keyboardModifiers() & Qt::AltModifier) { 4238 action = Qt::CopyAction; 4239 } 4240 #else 4241 if (event->keyboardModifiers() & Qt::ControlModifier) { 4242 action = Qt::CopyAction; 4243 } 4244 #endif 4245 event->setDropAction(action); 4246 } 4247 } 4248 4249 void KateViewInternal::dragMoveEvent(QDragMoveEvent *event) 4250 { 4251 // track the cursor to the current drop location 4252 placeCursor(event->pos(), true, false); 4253 4254 // important: accept action to switch between copy and move mode 4255 // without this, the text will always be copied. 4256 fixDropEvent(event); 4257 } 4258 4259 void KateViewInternal::dropEvent(QDropEvent *event) 4260 { 4261 // if we have urls, pass this event off to the hosting application 4262 if (event->mimeData()->hasUrls()) { 4263 Q_EMIT dropEventPass(event); 4264 return; 4265 } 4266 4267 if (event->mimeData()->hasText() && doc()->isReadWrite()) { 4268 const QString text = event->mimeData()->text(); 4269 const bool blockMode = view()->blockSelection(); 4270 4271 fixDropEvent(event); 4272 4273 // Remember where to paste/move... 4274 KTextEditor::Cursor targetCursor(m_cursor); 4275 // Use powerful MovingCursor to track our changes we may do 4276 std::unique_ptr<KTextEditor::MovingCursor> targetCursor2(doc()->newMovingCursor(m_cursor)); 4277 4278 // As always need the BlockMode some special treatment 4279 const KTextEditor::Range selRange(view()->selectionRange()); 4280 const KTextEditor::Cursor blockAdjust(selRange.numberOfLines(), selRange.columnWidth()); 4281 4282 // Restore the cursor position before editStart(), so that it is correctly stored for the undo action 4283 if (event->dropAction() != Qt::CopyAction) { 4284 editSetCursor(selRange.end()); 4285 } else { 4286 view()->clearSelection(); 4287 } 4288 4289 // use one transaction 4290 doc()->editStart(); 4291 4292 if (event->dropAction() != Qt::CopyAction) { 4293 view()->removeSelectedText(); 4294 if (targetCursor2->toCursor() != targetCursor) { 4295 // Hm, multi line selection moved down, we need to adjust our dumb cursor 4296 targetCursor = targetCursor2->toCursor(); 4297 } 4298 doc()->insertText(targetCursor2->toCursor(), text, blockMode); 4299 4300 } else { 4301 doc()->insertText(targetCursor, text, blockMode); 4302 } 4303 4304 if (blockMode) { 4305 setSelection(KTextEditor::Range(targetCursor, targetCursor + blockAdjust)); 4306 editSetCursor(targetCursor + blockAdjust); 4307 } else { 4308 setSelection(KTextEditor::Range(targetCursor, targetCursor2->toCursor())); 4309 editSetCursor(targetCursor2->toCursor()); // Just to satisfy autotest 4310 } 4311 4312 doc()->editEnd(); 4313 4314 event->acceptProposedAction(); 4315 updateView(); 4316 } 4317 4318 // finally finish drag and drop mode 4319 m_dragInfo.state = diNone; 4320 // important, because the eventFilter`s DragLeave does not occur 4321 stopDragScroll(); 4322 } 4323 // END EVENT HANDLING STUFF 4324 4325 void KateViewInternal::clear() 4326 { 4327 m_startPos.setPosition(0, 0); 4328 m_displayCursor = KTextEditor::Cursor(0, 0); 4329 m_cursor.setPosition(0, 0); 4330 view()->clearSecondaryCursors(); 4331 cache()->clear(); 4332 updateView(true); 4333 m_lineScroll->updatePixmap(); 4334 } 4335 4336 void KateViewInternal::wheelEvent(QWheelEvent *e) 4337 { 4338 // check if this event should change the font size (Ctrl pressed, angle reported and not accidentally so) 4339 // Note: if detectZoomingEvent() doesn't unset the ControlModifier we'll get accelerated scrolling. 4340 if (m_zoomEventFilter->detectZoomingEvent(e)) { 4341 if (e->angleDelta().y() > 0) { 4342 slotIncFontSizes(qreal(e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep); 4343 } else if (e->angleDelta().y() < 0) { 4344 slotDecFontSizes(qreal(-e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep); 4345 } 4346 4347 // accept always and be done for zooming 4348 e->accept(); 4349 return; 4350 } 4351 4352 // handle vertical scrolling via the scrollbar 4353 if (e->angleDelta().y() != 0) { 4354 // compute distance 4355 auto sign = m_lineScroll->invertedControls() ? -1 : 1; 4356 auto offset = sign * qreal(e->angleDelta().y()) / 120.0; 4357 if (e->modifiers() & Qt::ShiftModifier) { 4358 const auto pageStep = m_lineScroll->pageStep(); 4359 offset = qBound(-pageStep, int(offset * pageStep), pageStep); 4360 } else { 4361 offset *= QApplication::wheelScrollLines(); 4362 } 4363 4364 // handle accumulation 4365 m_accumulatedScroll += offset - int(offset); 4366 const auto extraAccumulated = int(m_accumulatedScroll); 4367 m_accumulatedScroll -= extraAccumulated; 4368 4369 // do scroll 4370 scrollViewLines(int(offset) + extraAccumulated); 4371 e->accept(); 4372 } 4373 4374 // handle horizontal scrolling via the scrollbar 4375 if (e->angleDelta().x() != 0) { 4376 // if we have dyn word wrap, we should ignore the scroll events 4377 if (view()->dynWordWrap()) { 4378 e->accept(); 4379 return; 4380 } 4381 4382 // if we scroll up/down we do not want to trigger unintended sideways scrolls 4383 if (qAbs(e->angleDelta().y()) > qAbs(e->angleDelta().x())) { 4384 e->accept(); 4385 return; 4386 } 4387 4388 if (QApplication::sendEvent(m_columnScroll, e)) { 4389 e->accept(); 4390 } 4391 } 4392 4393 // hide bracket match preview so that it won't linger while scrolling' 4394 hideBracketMatchPreview(); 4395 } 4396 4397 void KateViewInternal::scrollPrepareEvent(QScrollPrepareEvent *event) 4398 { 4399 int lineHeight = renderer()->lineHeight(); 4400 event->setViewportSize(QSizeF(0.0, 0.0)); 4401 event->setContentPosRange(QRectF(0.0, 0.0, 0.0, m_lineScroll->maximum() * lineHeight)); 4402 event->setContentPos(QPointF(0.0, m_lineScroll->value() * lineHeight)); 4403 event->accept(); 4404 } 4405 4406 void KateViewInternal::scrollEvent(QScrollEvent *event) 4407 { 4408 // FIXME Add horizontal scrolling, overscroll, scroll between lines, and word wrap awareness 4409 KTextEditor::Cursor newPos((int) event->contentPos().y() / renderer()->lineHeight(), 0); 4410 scrollPos(newPos); 4411 event->accept(); 4412 } 4413 4414 void KateViewInternal::startDragScroll() 4415 { 4416 if (!m_dragScrollTimer.isActive()) { 4417 m_dragScrollTimer.start(s_scrollTime); 4418 } 4419 } 4420 4421 void KateViewInternal::stopDragScroll() 4422 { 4423 m_dragScrollTimer.stop(); 4424 updateView(); 4425 } 4426 4427 void KateViewInternal::doDragScroll() 4428 { 4429 QPoint p = this->mapFromGlobal(QCursor::pos()); 4430 4431 int dx = 0; 4432 int dy = 0; 4433 if (p.y() < s_scrollMargin) { 4434 dy = p.y() - s_scrollMargin; 4435 } else if (p.y() > height() - s_scrollMargin) { 4436 dy = s_scrollMargin - (height() - p.y()); 4437 } 4438 4439 if (p.x() < s_scrollMargin) { 4440 dx = p.x() - s_scrollMargin; 4441 } else if (p.x() > width() - s_scrollMargin) { 4442 dx = s_scrollMargin - (width() - p.x()); 4443 } 4444 4445 dy /= 4; 4446 4447 if (dy) { 4448 scrollLines(startLine() + dy); 4449 } 4450 4451 if (columnScrollingPossible() && dx) { 4452 scrollColumns(qMin(startX() + dx, m_columnScroll->maximum())); 4453 } 4454 4455 if (!dy && !dx) { 4456 stopDragScroll(); 4457 } 4458 } 4459 4460 void KateViewInternal::registerTextHintProvider(KTextEditor::TextHintProvider *provider) 4461 { 4462 if (std::find(m_textHintProviders.cbegin(), m_textHintProviders.cend(), provider) == m_textHintProviders.cend()) { 4463 m_textHintProviders.push_back(provider); 4464 } 4465 4466 // we have a client, so start timeout 4467 m_textHintTimer.start(m_textHintDelay); 4468 } 4469 4470 void KateViewInternal::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) 4471 { 4472 const auto it = std::find(m_textHintProviders.cbegin(), m_textHintProviders.cend(), provider); 4473 if (it != m_textHintProviders.cend()) { 4474 m_textHintProviders.erase(it); 4475 } 4476 4477 if (m_textHintProviders.empty()) { 4478 m_textHintTimer.stop(); 4479 } 4480 } 4481 4482 void KateViewInternal::setTextHintDelay(int delay) 4483 { 4484 if (delay <= 0) { 4485 m_textHintDelay = 200; // ms 4486 } else { 4487 m_textHintDelay = delay; // ms 4488 } 4489 } 4490 4491 int KateViewInternal::textHintDelay() const 4492 { 4493 return m_textHintDelay; 4494 } 4495 4496 bool KateViewInternal::textHintsEnabled() 4497 { 4498 return !m_textHintProviders.empty(); 4499 } 4500 4501 // BEGIN EDIT STUFF 4502 void KateViewInternal::editStart() 4503 { 4504 editSessionNumber++; 4505 4506 if (editSessionNumber > 1) { 4507 return; 4508 } 4509 4510 editIsRunning = true; 4511 editOldCursor = m_cursor; 4512 editOldSelection = view()->selectionRange(); 4513 } 4514 4515 void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) 4516 { 4517 if (editSessionNumber == 0) { 4518 return; 4519 } 4520 4521 editSessionNumber--; 4522 4523 if (editSessionNumber > 0) { 4524 return; 4525 } 4526 4527 // fix start position, might have moved from column 0 4528 // try to clever calculate the right start column for the tricky dyn word wrap case 4529 int col = 0; 4530 if (view()->dynWordWrap()) { 4531 if (KateLineLayoutPtr layout = cache()->line(startLine())) { 4532 int index = layout->viewLineForColumn(startPos().column()); 4533 if (index >= 0 && index < layout->viewLineCount()) { 4534 col = layout->viewLine(index).startCol(); 4535 } 4536 } 4537 } 4538 m_startPos.setPosition(startLine(), col); 4539 4540 if (tagFrom && (editTagLineStart <= int(view()->textFolding().visibleLineToLine(startLine())))) { 4541 tagAll(); 4542 } else { 4543 tagLines(editTagLineStart, tagFrom ? qMax(doc()->lastLine() + 1, editTagLineEnd) : editTagLineEnd, true); 4544 } 4545 4546 if (editOldCursor == m_cursor.toCursor()) { 4547 updateBracketMarks(); 4548 } 4549 4550 updateView(true); 4551 4552 if (editOldCursor != m_cursor.toCursor() || m_view == doc()->activeView()) { 4553 // Only scroll the view to the cursor if the insertion happens at the cursor. 4554 // This might not be the case for e.g. collaborative editing, when a remote user 4555 // inserts text at a position not at the caret. 4556 if (m_cursor.line() >= editTagLineStart && m_cursor.line() <= editTagLineEnd) { 4557 m_madeVisible = false; 4558 updateCursor(m_cursor, true); 4559 } 4560 } 4561 4562 // selection changed? 4563 // fixes bug 316226 4564 if (editOldSelection != view()->selectionRange() 4565 || (editOldSelection.isValid() && !editOldSelection.isEmpty() 4566 && !(editTagLineStart > editOldSelection.end().line() && editTagLineEnd < editOldSelection.start().line()))) { 4567 Q_EMIT view()->selectionChanged(m_view); 4568 } 4569 4570 editIsRunning = false; 4571 } 4572 4573 void KateViewInternal::editSetCursor(const KTextEditor::Cursor _cursor) 4574 { 4575 if (m_cursor.toCursor() != _cursor) { 4576 m_cursor.setPosition(_cursor); 4577 } 4578 } 4579 // END 4580 4581 void KateViewInternal::viewSelectionChanged() 4582 { 4583 if (!view()->selection()) { 4584 m_selectAnchor = KTextEditor::Cursor::invalid(); 4585 } else { 4586 const auto r = view()->selectionRange(); 4587 m_selectAnchor = r.start() == m_cursor ? r.end() : r.start(); 4588 } 4589 // Do NOT nuke the entire range! The reason is that a shift+DC selection 4590 // might (correctly) set the range to be empty (i.e. start() == end()), and 4591 // subsequent dragging might shrink the selection into non-existence. When 4592 // this happens, we use the cached end to restore the cached start so that 4593 // updateSelection is not confused. See also comments in updateSelection. 4594 m_selectionCached.setStart(KTextEditor::Cursor::invalid()); 4595 updateMicroFocus(); 4596 } 4597 4598 KateLayoutCache *KateViewInternal::cache() const 4599 { 4600 return m_layoutCache; 4601 } 4602 4603 KTextEditor::Cursor KateViewInternal::toRealCursor(const KTextEditor::Cursor virtualCursor) const 4604 { 4605 return KTextEditor::Cursor(view()->textFolding().visibleLineToLine(virtualCursor.line()), virtualCursor.column()); 4606 } 4607 4608 KTextEditor::Cursor KateViewInternal::toVirtualCursor(const KTextEditor::Cursor realCursor) const 4609 { 4610 // only convert valid lines, folding doesn't like invalid input! 4611 // don't validate whole cursor, column might be -1 4612 if (realCursor.line() < 0) { 4613 return KTextEditor::Cursor::invalid(); 4614 } 4615 4616 return KTextEditor::Cursor(view()->textFolding().lineToVisibleLine(realCursor.line()), realCursor.column()); 4617 } 4618 4619 KateRenderer *KateViewInternal::renderer() const 4620 { 4621 return view()->renderer(); 4622 } 4623 4624 void KateViewInternal::mouseMoved() 4625 { 4626 view()->notifyMousePositionChanged(m_mouse); 4627 view()->updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); 4628 } 4629 4630 void KateViewInternal::cursorMoved() 4631 { 4632 view()->updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); 4633 4634 #ifndef QT_NO_ACCESSIBILITY 4635 if (QAccessible::isActive()) { 4636 QAccessibleTextCursorEvent ev(this, static_cast<KateViewAccessible *>(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, m_cursor)); 4637 QAccessible::updateAccessibility(&ev); 4638 } 4639 #endif 4640 } 4641 4642 KTextEditor::DocumentPrivate *KateViewInternal::doc() 4643 { 4644 return m_view->doc(); 4645 } 4646 4647 KTextEditor::DocumentPrivate *KateViewInternal::doc() const 4648 { 4649 return m_view->doc(); 4650 } 4651 4652 bool KateViewInternal::rangeAffectsView(KTextEditor::Range range, bool realCursors) const 4653 { 4654 int startLine = KateViewInternal::startLine(); 4655 int endLine = startLine + (int)m_visibleLineCount; 4656 4657 if (realCursors) { 4658 startLine = (int)view()->textFolding().visibleLineToLine(startLine); 4659 endLine = (int)view()->textFolding().visibleLineToLine(endLine); 4660 } 4661 4662 return (range.end().line() >= startLine) || (range.start().line() <= endLine); 4663 } 4664 4665 // BEGIN IM INPUT STUFF 4666 QVariant KateViewInternal::inputMethodQuery(Qt::InputMethodQuery query) const 4667 { 4668 switch (query) { 4669 case Qt::ImCursorRectangle: { 4670 // Cursor placement code is changed for Asian input method that 4671 // shows candidate window. This behavior is same as Qt/E 2.3.7 4672 // which supports Asian input methods. Asian input methods need 4673 // start point of IM selection text to place candidate window as 4674 // adjacent to the selection text. 4675 // 4676 // in Qt5, cursor rectangle is used as QRectF internally, and it 4677 // will be checked by QRectF::isValid(), which will mark rectangle 4678 // with width == 0 or height == 0 as invalid. 4679 auto lineHeight = renderer()->lineHeight(); 4680 return QRect(cursorToCoordinate(m_cursor, true, false), QSize(1, lineHeight ? lineHeight : 1)); 4681 } 4682 4683 case Qt::ImFont: 4684 return renderer()->currentFont(); 4685 4686 case Qt::ImCursorPosition: 4687 return m_cursor.column(); 4688 4689 case Qt::ImAnchorPosition: 4690 // If selectAnchor is at the same line, return the real anchor position 4691 // Otherwise return the same position of cursor 4692 if (view()->selection() && m_selectAnchor.line() == m_cursor.line()) { 4693 return m_selectAnchor.column(); 4694 } else { 4695 return m_cursor.column(); 4696 } 4697 4698 case Qt::ImSurroundingText: 4699 if (Kate::TextLine l = doc()->kateTextLine(m_cursor.line())) { 4700 return l->text(); 4701 } else { 4702 return QString(); 4703 } 4704 4705 case Qt::ImCurrentSelection: 4706 if (view()->selection()) { 4707 return view()->selectionText(); 4708 } else { 4709 return QString(); 4710 } 4711 default: 4712 /* values: ImMaximumTextLength */ 4713 break; 4714 } 4715 4716 return QWidget::inputMethodQuery(query); 4717 } 4718 4719 void KateViewInternal::inputMethodEvent(QInputMethodEvent *e) 4720 { 4721 if (doc()->readOnly()) { 4722 e->ignore(); 4723 return; 4724 } 4725 4726 // qCDebug(LOG_KTE) << "Event: cursor" << m_cursor << "commit" << e->commitString() << "preedit" << e->preeditString() << "replacement start" << 4727 // e->replacementStart() << "length" << e->replacementLength(); 4728 4729 if (!m_imPreeditRange) { 4730 m_imPreeditRange.reset( 4731 doc()->newMovingRange(KTextEditor::Range(m_cursor, m_cursor), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight)); 4732 } 4733 4734 if (!m_imPreeditRange->toRange().isEmpty()) { 4735 doc()->inputMethodStart(); 4736 doc()->removeText(*m_imPreeditRange); 4737 doc()->inputMethodEnd(); 4738 } 4739 4740 if (!e->commitString().isEmpty() || e->replacementLength() || !e->preeditString().isEmpty()) { 4741 view()->removeSelectedText(); 4742 } 4743 4744 if (!e->commitString().isEmpty() || e->replacementLength()) { 4745 KTextEditor::Range preeditRange = *m_imPreeditRange; 4746 4747 KTextEditor::Cursor start(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + e->replacementStart()); 4748 KTextEditor::Cursor removeEnd = start + KTextEditor::Cursor(0, e->replacementLength()); 4749 4750 doc()->editStart(); 4751 if (start != removeEnd) { 4752 doc()->removeText(KTextEditor::Range(start, removeEnd)); 4753 } 4754 4755 // if the input method event is text that should be inserted, call KTextEditor::DocumentPrivate::typeChars() 4756 // with the text. that method will handle the input and take care of overwrite mode, etc. 4757 doc()->typeChars(m_view, e->commitString()); 4758 4759 doc()->editEnd(); 4760 4761 // Revert to the same range as above 4762 m_imPreeditRange->setRange(preeditRange); 4763 } 4764 4765 if (!e->preeditString().isEmpty()) { 4766 doc()->inputMethodStart(); 4767 doc()->insertText(m_imPreeditRange->start(), e->preeditString()); 4768 doc()->inputMethodEnd(); 4769 // The preedit range gets automatically repositioned 4770 } 4771 4772 // Finished this input method context? 4773 if (m_imPreeditRange && e->preeditString().isEmpty()) { 4774 // delete the range and reset the pointer 4775 m_imPreeditRange.reset(); 4776 m_imPreeditRangeChildren.clear(); 4777 4778 if (QApplication::cursorFlashTime() > 0) { 4779 renderer()->setDrawCaret(false); 4780 } 4781 renderer()->setCaretOverrideColor(QColor()); 4782 4783 e->accept(); 4784 return; 4785 } 4786 4787 KTextEditor::Cursor newCursor = m_cursor; 4788 bool hideCursor = false; 4789 QColor caretColor; 4790 4791 if (m_imPreeditRange) { 4792 m_imPreeditRangeChildren.clear(); 4793 4794 int decorationColumn = 0; 4795 const auto &attributes = e->attributes(); 4796 for (auto &a : attributes) { 4797 if (a.type == QInputMethodEvent::Cursor) { 4798 const int cursor = qMin(a.start, e->preeditString().length()); 4799 newCursor = m_imPreeditRange->start() + KTextEditor::Cursor(0, cursor); 4800 hideCursor = !a.length; 4801 QColor c = qvariant_cast<QColor>(a.value); 4802 if (c.isValid()) { 4803 caretColor = c; 4804 } 4805 4806 } else if (a.type == QInputMethodEvent::TextFormat) { 4807 QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat(); 4808 const int start = qMin(a.start, e->preeditString().length()); 4809 const int end = qMin(a.start + a.length, e->preeditString().length()); 4810 if (f.isValid() && decorationColumn <= start && start != end) { 4811 const KTextEditor::MovingCursor &preEditRangeStart = m_imPreeditRange->start(); 4812 const int startLine = preEditRangeStart.line(); 4813 const int startCol = preEditRangeStart.column(); 4814 KTextEditor::Range fr(startLine, startCol + start, startLine, startCol + end); 4815 std::unique_ptr<KTextEditor::MovingRange> formatRange(doc()->newMovingRange(fr)); 4816 KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); 4817 attribute->merge(f); 4818 formatRange->setAttribute(attribute); 4819 decorationColumn = end; 4820 m_imPreeditRangeChildren.push_back(std::move(formatRange)); 4821 } 4822 } 4823 } 4824 } 4825 4826 renderer()->setDrawCaret(hideCursor); 4827 renderer()->setCaretOverrideColor(caretColor); 4828 4829 if (newCursor != m_cursor.toCursor()) { 4830 updateCursor(newCursor); 4831 } 4832 4833 e->accept(); 4834 } 4835 4836 // END IM INPUT STUFF 4837 4838 void KateViewInternal::flashChar(const KTextEditor::Cursor pos, KTextEditor::Attribute::Ptr attribute) 4839 { 4840 Q_ASSERT(pos.isValid()); 4841 Q_ASSERT(attribute.constData()); 4842 4843 // if line is folded away, do nothing 4844 if (!view()->textFolding().isLineVisible(pos.line())) { 4845 return; 4846 } 4847 4848 KTextEditor::Range range(pos, KTextEditor::Cursor(pos.line(), pos.column() + 1)); 4849 if (m_textAnimation) { 4850 m_textAnimation->deleteLater(); 4851 } 4852 m_textAnimation = new KateTextAnimation(range, std::move(attribute), this); 4853 } 4854 4855 void KateViewInternal::showBracketMatchPreview() 4856 { 4857 // only show when main window is active 4858 if (window() && !window()->isActiveWindow()) { 4859 return; 4860 } 4861 4862 const KTextEditor::Cursor openBracketCursor = m_bmStart->start(); 4863 // 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 4864 if (m_cursor == openBracketCursor || toVirtualCursor(openBracketCursor).line() >= startLine() || m_cursor.line() - startLine() < 2) { 4865 hideBracketMatchPreview(); 4866 return; 4867 } 4868 4869 if (!m_bmPreview) { 4870 m_bmPreview.reset(new KateTextPreview(m_view, this)); 4871 m_bmPreview->setAttribute(Qt::WA_ShowWithoutActivating); 4872 m_bmPreview->setFrameStyle(QFrame::Box); 4873 } 4874 4875 const int previewLine = openBracketCursor.line(); 4876 KateRenderer *const renderer_ = renderer(); 4877 KateLineLayoutPtr lineLayout(new KateLineLayout(*renderer_)); 4878 lineLayout->setLine(previewLine, -1); 4879 4880 // If the opening bracket is on its own line, start preview at the line above it instead (where the context is likely to be) 4881 const int col = lineLayout->textLine()->firstChar(); 4882 if (previewLine > 0 && (col == -1 || col == openBracketCursor.column())) { 4883 lineLayout->setLine(previewLine - 1, lineLayout->virtualLine() - 1); 4884 } 4885 4886 renderer_->layoutLine(lineLayout, -1 /* no wrap */, false /* no layout cache */); 4887 const int lineWidth = 4888 qBound(m_view->width() / 5, int(lineLayout->width() + renderer_->spaceWidth() * 2), m_view->width() - m_leftBorder->width() - m_lineScroll->width()); 4889 m_bmPreview->resize(lineWidth, renderer_->lineHeight() * 2); 4890 const QPoint topLeft = mapToGlobal(QPoint(0, 0)); 4891 m_bmPreview->move(topLeft.x(), topLeft.y()); 4892 m_bmPreview->setLine(lineLayout->virtualLine()); 4893 m_bmPreview->setCenterView(false); 4894 m_bmPreview->raise(); 4895 m_bmPreview->show(); 4896 } 4897 4898 void KateViewInternal::hideBracketMatchPreview() 4899 { 4900 m_bmPreview.reset(); 4901 } 4902 4903 void KateViewInternal::documentTextInserted(KTextEditor::Document *document, KTextEditor::Range range) 4904 { 4905 #ifndef QT_NO_ACCESSIBILITY 4906 if (QAccessible::isActive()) { 4907 QAccessibleTextInsertEvent ev(this, 4908 static_cast<KateViewAccessible *>(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, range.start()), 4909 document->text(range)); 4910 QAccessible::updateAccessibility(&ev); 4911 } 4912 #endif 4913 } 4914 4915 void KateViewInternal::documentTextRemoved(KTextEditor::Document * /*document*/, KTextEditor::Range range, const QString &oldText) 4916 { 4917 #ifndef QT_NO_ACCESSIBILITY 4918 if (QAccessible::isActive()) { 4919 QAccessibleTextRemoveEvent ev(this, 4920 static_cast<KateViewAccessible *>(QAccessible::queryAccessibleInterface(this))->positionFromCursor(this, range.start()), 4921 oldText); 4922 QAccessible::updateAccessibility(&ev); 4923 } 4924 #endif 4925 } 4926 4927 QRect KateViewInternal::inlineNoteRect(const KateInlineNoteData ¬eData) const 4928 { 4929 KTextEditor::InlineNote note(noteData); 4930 // compute note width and position 4931 const auto noteWidth = note.width(); 4932 auto noteCursor = note.position(); 4933 4934 // The cursor might be outside of the text. In that case, clamp it to the text and 4935 // later on add the missing x offset. 4936 const auto lineLength = view()->document()->lineLength(noteCursor.line()); 4937 int extraOffset = -noteWidth; 4938 if (noteCursor.column() == lineLength) { 4939 extraOffset = 0; 4940 } else if (noteCursor.column() > lineLength) { 4941 extraOffset = (noteCursor.column() - lineLength) * renderer()->spaceWidth(); 4942 noteCursor.setColumn(lineLength); 4943 } 4944 auto noteStartPos = mapToGlobal(cursorToCoordinate(noteCursor, true, false)); 4945 4946 // compute the note's rect 4947 auto globalNoteRect = QRect(noteStartPos + QPoint{extraOffset, 0}, QSize(noteWidth, renderer()->lineHeight())); 4948 4949 return globalNoteRect; 4950 } 4951 4952 KateInlineNoteData KateViewInternal::inlineNoteAt(const QPoint &globalPos) const 4953 { 4954 // compute the associated cursor to get the right line 4955 const int line = coordinatesToCursor(mapFromGlobal(globalPos)).line(); 4956 const auto inlineNotes = view()->inlineNotes(line); 4957 // loop over all notes and check if the point is inside it 4958 for (const auto ¬e : inlineNotes) { 4959 auto globalNoteRect = inlineNoteRect(note); 4960 if (globalNoteRect.contains(globalPos)) { 4961 return note; 4962 } 4963 } 4964 // none found -- return an invalid note 4965 return {}; 4966 } 4967 4968 bool KateViewInternal::sendMouseEventToInputContext(QMouseEvent *e) 4969 { 4970 if (!m_imPreeditRange) { 4971 return false; 4972 } 4973 4974 KTextEditor::Cursor c = cursorForPoint(e->pos()); 4975 if (!m_imPreeditRange->contains(c) && c != m_imPreeditRange->end()) { 4976 return false; 4977 } 4978 4979 auto cursorPos = (c - m_imPreeditRange->start()); 4980 4981 if (cursorPos.column() >= 0) { 4982 if (e->type() == QEvent::MouseButtonRelease) 4983 QGuiApplication::inputMethod()->invokeAction(QInputMethod::Click, cursorPos.column()); 4984 e->setAccepted(true); 4985 return true; 4986 } 4987 return false; 4988 } 4989 4990 void KateViewInternal::commitPreedit() 4991 { 4992 if (!m_imPreeditRange) { 4993 return; 4994 } 4995 4996 QGuiApplication::inputMethod()->commit(); 4997 } 4998 4999 #include "moc_kateviewinternal.cpp"