/* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2002 Joseph Wenninger Copyright (C) 2002,2003 Christoph Cullmann Copyright (C) 2002,2003 Hamish Rodda Copyright (C) 2003 Anakim Border Based on: KWriteView : Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateviewinternal.h" #include "kateviewinternal.moc" #include "kateview.h" #include "katecodefoldinghelpers.h" #include "kateviewhelpers.h" #include "katehighlight.h" #include "katesupercursor.h" #include "katerenderer.h" #include "katecodecompletion.h" #include "kateconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include KateViewInternal::KateViewInternal(KateView *view, KateDocument *doc) : QWidget (view, "", Qt::WStaticContents | Qt::WRepaintNoErase | Qt::WResizeNoErase ) , editSessionNumber (0) , editIsRunning (false) , m_view (view) , m_doc (doc) , cursor (doc, true, 0, 0, this) , possibleTripleClick (false) , m_dummy (0) , m_startPos(doc, true, 0,0) , m_madeVisible(false) , m_shiftKeyPressed (false) , m_autoCenterLines (false) , m_selChangedByUser (false) , selectAnchor (-1, -1) , m_selectionMode( Default ) , m_preserveMaxX(false) , m_currentMaxX(0) , m_usePlainLines(false) , m_updatingView(true) , m_cachedMaxStartPos(-1, -1) , m_dragScrollTimer(this) , m_scrollTimer (this) , m_cursorTimer (this) , m_textHintTimer (this) , m_textHintEnabled(false) , m_textHintMouseX(-1) , m_textHintMouseY(-1) , m_imPreeditStartLine(0) , m_imPreeditStart(0) , m_imPreeditLength(0) , m_imPreeditSelStart(0) { setMinimumSize (0,0); // cursor cursor.setMoveOnInsert (true); // invalidate selStartCached, or keyb selection is screwed initially selStartCached.setLine( -1 ); // // scrollbar for lines // m_lineScroll = new KateScrollBar(QScrollBar::Vertical, this); m_lineScroll->show(); m_lineScroll->setTracking (true); m_lineLayout = new QVBoxLayout(); m_colLayout = new QHBoxLayout(); m_colLayout->addWidget(m_lineScroll); m_lineLayout->addLayout(m_colLayout); // bottom corner box m_dummy = new QWidget(m_view); m_dummy->setFixedHeight(style().scrollBarExtent().width()); if (m_view->dynWordWrap()) m_dummy->hide(); else m_dummy->show(); m_lineLayout->addWidget(m_dummy); // Hijack the line scroller's controls, so we can scroll nicely for word-wrap connect(m_lineScroll, SIGNAL(prevPage()), SLOT(scrollPrevPage())); connect(m_lineScroll, SIGNAL(nextPage()), SLOT(scrollNextPage())); connect(m_lineScroll, SIGNAL(prevLine()), SLOT(scrollPrevLine())); connect(m_lineScroll, SIGNAL(nextLine()), SLOT(scrollNextLine())); connect(m_lineScroll, SIGNAL(sliderMoved(int)), SLOT(scrollLines(int))); connect(m_lineScroll, SIGNAL(sliderMMBMoved(int)), SLOT(scrollLines(int))); // catch wheel events, completing the hijack m_lineScroll->installEventFilter(this); // // scrollbar for columns // m_columnScroll = new QScrollBar(QScrollBar::Horizontal,m_view); // hide the column scrollbar in the dynamic word wrap mode if (m_view->dynWordWrap()) m_columnScroll->hide(); else m_columnScroll->show(); m_columnScroll->setTracking(true); m_startX = 0; connect( m_columnScroll, SIGNAL( valueChanged (int) ), this, SLOT( scrollColumns (int) ) ); // // iconborder ;) // leftBorder = new KateIconBorder( this, m_view ); leftBorder->show (); connect( leftBorder, SIGNAL(toggleRegionVisibility(unsigned int)), m_doc->foldingTree(), SLOT(toggleRegionVisibility(unsigned int))); connect( doc->foldingTree(), SIGNAL(regionVisibilityChangedAt(unsigned int)), this, SLOT(slotRegionVisibilityChangedAt(unsigned int))); connect( doc, SIGNAL(codeFoldingUpdated()), this, SLOT(slotCodeFoldingChanged()) ); displayCursor.setPos(0, 0); cursor.setPos(0, 0); cXPos = 0; setAcceptDrops( true ); setBackgroundMode( NoBackground ); // event filter installEventFilter(this); // im setInputMethodEnabled(true); // set initial cursor setCursor( KCursor::ibeamCursor() ); m_mouseCursor = IbeamCursor; // call mouseMoveEvent also if no mouse button is pressed setMouseTracking(true); dragInfo.state = diNone; // timers connect( &m_dragScrollTimer, SIGNAL( timeout() ), this, SLOT( doDragScroll() ) ); connect( &m_scrollTimer, SIGNAL( timeout() ), this, SLOT( scrollTimeout() ) ); connect( &m_cursorTimer, SIGNAL( timeout() ), this, SLOT( cursorTimeout() ) ); connect( &m_textHintTimer, SIGNAL( timeout() ), this, SLOT( textHintTimeout() ) ); // selection changed to set anchor connect( m_view, SIGNAL( selectionChanged() ), this, SLOT( viewSelectionChanged() ) ); // this is a work arround for RTL desktops // should be changed in kde 3.3 // BTW: this comment has been "ported" from 3.1.X tree // any hacker with BIDI knowlege is welcomed to fix kate problems :) if (QApplication::reverseLayout()){ m_view->m_grid->addMultiCellWidget(leftBorder, 0, 1, 2, 2); m_view->m_grid->addMultiCellWidget(m_columnScroll, 1, 1, 0, 1); m_view->m_grid->addMultiCellLayout(m_lineLayout, 0, 0, 0, 0); } else{ m_view->m_grid->addMultiCellLayout(m_lineLayout, 0, 1, 2, 2); m_view->m_grid->addMultiCellWidget(m_columnScroll, 1, 1, 0, 1); m_view->m_grid->addWidget(leftBorder, 0, 0); } updateView (); } KateViewInternal::~KateViewInternal () { } void KateViewInternal::prepareForDynWrapChange() { // Which is the current view line? m_wrapChangeViewLine = displayViewLine(displayCursor, true); } void KateViewInternal::dynWrapChanged() { if (m_view->dynWordWrap()) { m_columnScroll->hide(); m_dummy->hide (); } else { m_columnScroll->show(); m_dummy->show (); } tagAll(); updateView(); if (m_view->dynWordWrap()) scrollColumns(0); // Determine where the cursor should be to get the cursor on the same view line if (m_wrapChangeViewLine != -1) { KateTextCursor newStart = viewLineOffset(displayCursor, -m_wrapChangeViewLine); makeVisible(newStart, newStart.col(), true); } else { update(); } } KateTextCursor KateViewInternal::endPos() const { int viewLines = linesDisplayed() - 1; if (viewLines < 0) { kdDebug(13030) << "WARNING: viewLines wrong!" << endl; viewLines = 0; } // Check to make sure that lineRanges isn't invalid if (!lineRanges.count() || lineRanges[0].line == -1 || viewLines >= (int)lineRanges.count()) { // Switch off use of the cache return KateTextCursor(m_doc->numVisLines() - 1, m_doc->lineLength(m_doc->getRealLine(m_doc->numVisLines() - 1))); } for (int i = viewLines; i >= 0; i--) { KateLineRange& thisRange = lineRanges[i]; if (thisRange.line == -1) continue; if (thisRange.virtualLine >= (int)m_doc->numVisLines()) { // Cache is too out of date return KateTextCursor(m_doc->numVisLines() - 1, m_doc->lineLength(m_doc->getRealLine(m_doc->numVisLines() - 1))); } return KateTextCursor(thisRange.virtualLine, thisRange.wrap ? thisRange.endCol - 1 : thisRange.endCol); } Q_ASSERT(false); kdDebug(13030) << "WARNING: could not find a lineRange at all" << endl; return KateTextCursor(-1, -1); } uint KateViewInternal::endLine() const { return endPos().line(); } KateLineRange KateViewInternal::yToKateLineRange(uint y) const { uint range = y / m_view->renderer()->fontHeight(); // lineRanges is always bigger than 0, after the initial updateView call if (range >= lineRanges.size()) return lineRanges[lineRanges.size()-1]; return lineRanges[range]; } int KateViewInternal::lineToY(uint viewLine) const { return (viewLine-startLine()) * m_view->renderer()->fontHeight(); } void KateViewInternal::slotIncFontSizes() { m_view->renderer()->increaseFontSizes(); } void KateViewInternal::slotDecFontSizes() { m_view->renderer()->decreaseFontSizes(); } /** * Line is the real line number to scroll to. */ void KateViewInternal::scrollLines ( int line ) { KateTextCursor newPos(line, 0); scrollPos(newPos); } // This can scroll less than one true line void KateViewInternal::scrollViewLines(int offset) { KateTextCursor c = viewLineOffset(startPos(), offset); scrollPos(c); m_lineScroll->blockSignals(true); m_lineScroll->setValue(startLine()); m_lineScroll->blockSignals(false); } void KateViewInternal::scrollNextPage() { scrollViewLines(kMax( (int)linesDisplayed() - 1, 0 )); } void KateViewInternal::scrollPrevPage() { scrollViewLines(-kMax( (int)linesDisplayed() - 1, 0 )); } void KateViewInternal::scrollPrevLine() { scrollViewLines(-1); } void KateViewInternal::scrollNextLine() { scrollViewLines(1); } KateTextCursor KateViewInternal::maxStartPos(bool changed) { m_usePlainLines = true; if (m_cachedMaxStartPos.line() == -1 || changed) { KateTextCursor end(m_doc->numVisLines() - 1, m_doc->lineLength(m_doc->getRealLine(m_doc->numVisLines() - 1))); m_cachedMaxStartPos = viewLineOffset(end, -((int)linesDisplayed() - 1)); } m_usePlainLines = false; return m_cachedMaxStartPos; } // c is a virtual cursor void KateViewInternal::scrollPos(KateTextCursor& c, bool force, bool calledExternally) { if (!force && ((!m_view->dynWordWrap() && c.line() == (int)startLine()) || c == startPos())) return; if (c.line() < 0) c.setLine(0); KateTextCursor limit = maxStartPos(); if (c > limit) { c = limit; // Re-check we're not just scrolling to the same place if (!force && ((!m_view->dynWordWrap() && c.line() == (int)startLine()) || c == startPos())) return; } int viewLinesScrolled = 0; // only calculate if this is really used and usefull, could be wrong here, please recheck // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on // try to get it really working ;) bool viewLinesScrolledUsable = !force && (c.line() >= (int)startLine()-(int)linesDisplayed()-1) && (c.line() <= (int)endLine()+(int)linesDisplayed()+1); if (viewLinesScrolledUsable) viewLinesScrolled = displayViewLine(c); m_startPos.setPos(c); // set false here but reversed if we return to makeVisible m_madeVisible = false; if (viewLinesScrolledUsable) { int lines = linesDisplayed(); if ((int)m_doc->numVisLines() < lines) { KateTextCursor end(m_doc->numVisLines() - 1, m_doc->lineLength(m_doc->getRealLine(m_doc->numVisLines() - 1))); lines = kMin((int)linesDisplayed(), displayViewLine(end) + 1); } Q_ASSERT(lines >= 0); if (!calledExternally && QABS(viewLinesScrolled) < lines) { updateView(false, viewLinesScrolled); int scrollHeight = -(viewLinesScrolled * (int)m_view->renderer()->fontHeight()); int scrollbarWidth = style().scrollBarExtent().width(); // // updates are for working around the scrollbar leaving blocks in the view // scroll(0, scrollHeight); update(0, height()+scrollHeight-scrollbarWidth, width(), 2*scrollbarWidth); leftBorder->scroll(0, scrollHeight); leftBorder->update(0, leftBorder->height()+scrollHeight-scrollbarWidth, leftBorder->width(), 2*scrollbarWidth); return; } } updateView(); update(); leftBorder->update(); } void KateViewInternal::scrollColumns ( int x ) { if (x == m_startX) return; if (x < 0) x = 0; int dx = m_startX - x; m_startX = x; if (QABS(dx) < width()) scroll(dx, 0); else update(); m_columnScroll->blockSignals(true); m_columnScroll->setValue(m_startX); m_columnScroll->blockSignals(false); } // If changed is true, the lines that have been set dirty have been updated. void KateViewInternal::updateView(bool changed, int viewLinesScrolled) { m_updatingView = true; uint contentLines = m_doc->visibleLines(); m_lineScroll->blockSignals(true); KateTextCursor maxStart = maxStartPos(changed); int maxLineScrollRange = maxStart.line(); if (m_view->dynWordWrap() && maxStart.col() != 0) maxLineScrollRange++; m_lineScroll->setRange(0, maxLineScrollRange); m_lineScroll->setValue(startPos().line()); m_lineScroll->setSteps(1, height() / m_view->renderer()->fontHeight()); m_lineScroll->blockSignals(false); uint oldSize = lineRanges.size (); uint newSize = (height() / m_view->renderer()->fontHeight()) + 1; if (oldSize != newSize) { lineRanges.resize((height() / m_view->renderer()->fontHeight()) + 1); if (newSize > oldSize) { static KateLineRange blank; for (uint i = oldSize; i < newSize; i++) { lineRanges[i] = blank; } } } if (oldSize < lineRanges.size ()) { for (uint i=oldSize; i < lineRanges.size(); i++) lineRanges[i].dirty = true; } // Move the lineRanges data if we've just scrolled... if (viewLinesScrolled != 0) { // loop backwards if we've just scrolled up... bool forwards = viewLinesScrolled >= 0 ? true : false; for (uint z = forwards ? 0 : lineRanges.count() - 1; z < lineRanges.count(); forwards ? z++ : z--) { uint oldZ = z + viewLinesScrolled; if (oldZ < lineRanges.count()) { lineRanges[z] = lineRanges[oldZ]; } else { lineRanges[z].dirty = true; } } } if (m_view->dynWordWrap()) { KateTextCursor realStart = startPos(); realStart.setLine(m_doc->getRealLine(realStart.line())); KateLineRange startRange = range(realStart); uint line = startRange.virtualLine; int realLine = startRange.line; uint oldLine = line; int startCol = startRange.startCol; int startX = startRange.startX; int endX = startRange.startX; int shiftX = startRange.startCol ? startRange.shiftX : 0; bool wrap = false; int newViewLine = startRange.viewLine; // z is the current display view line KateTextLine::Ptr text = textLine(realLine); bool alreadyDirty = false; for (uint z = 0; z < lineRanges.size(); z++) { if (oldLine != line) { realLine = (int)m_doc->getRealLine(line); if (z) lineRanges[z-1].startsInvisibleBlock = (realLine != lineRanges[z-1].line + 1); text = textLine(realLine); startCol = 0; startX = 0; endX = 0; shiftX = 0; newViewLine = 0; oldLine = line; } if (line >= contentLines || !text) { if (lineRanges[z].line != -1) lineRanges[z].dirty = true; lineRanges[z].clear(); line++; } else { if (lineRanges[z].line != realLine || lineRanges[z].startCol != startCol) alreadyDirty = lineRanges[z].dirty = true; if (lineRanges[z].dirty || changed || alreadyDirty) { alreadyDirty = true; lineRanges[z].virtualLine = line; lineRanges[z].line = realLine; lineRanges[z].startsInvisibleBlock = false; int tempEndX = 0; int endCol = m_view->renderer()->textWidth(text, startCol, width() - shiftX, &wrap, &tempEndX); endX += tempEndX; if (wrap) { if (m_view->config()->dynWordWrapAlignIndent() > 0) { if (startX == 0) { int pos = text->nextNonSpaceChar(0); if (pos > 0) shiftX = m_view->renderer()->textWidth(text, pos); if (shiftX > ((double)width() / 100 * m_view->config()->dynWordWrapAlignIndent())) shiftX = 0; } } if ((lineRanges[z].startX != startX) || (lineRanges[z].endX != endX) || (lineRanges[z].startCol != startCol) || (lineRanges[z].endCol != endCol) || (lineRanges[z].shiftX != shiftX)) lineRanges[z].dirty = true; lineRanges[z].startCol = startCol; lineRanges[z].endCol = endCol; lineRanges[z].startX = startX; lineRanges[z].endX = endX; lineRanges[z].viewLine = newViewLine; lineRanges[z].wrap = true; startCol = endCol; startX = endX; } else { if ((lineRanges[z].startX != startX) || (lineRanges[z].endX != endX) || (lineRanges[z].startCol != startCol) || (lineRanges[z].endCol != endCol)) lineRanges[z].dirty = true; lineRanges[z].startCol = startCol; lineRanges[z].endCol = endCol; lineRanges[z].startX = startX; lineRanges[z].endX = endX; lineRanges[z].viewLine = newViewLine; lineRanges[z].wrap = false; line++; } lineRanges[z].shiftX = shiftX; } else { // The cached data is still intact if (lineRanges[z].wrap) { startCol = lineRanges[z].endCol; startX = lineRanges[z].endX; endX = lineRanges[z].endX; } else { line++; } shiftX = lineRanges[z].shiftX; } } newViewLine++; } } else { uint z = 0; for(; (z + startLine() < contentLines) && (z < lineRanges.size()); z++) { if (lineRanges[z].dirty || lineRanges[z].line != (int)m_doc->getRealLine(z + startLine())) { lineRanges[z].dirty = true; lineRanges[z].line = m_doc->getRealLine( z + startLine() ); if (z) lineRanges[z-1].startsInvisibleBlock = (lineRanges[z].line != lineRanges[z-1].line + 1); lineRanges[z].virtualLine = z + startLine(); lineRanges[z].startCol = 0; lineRanges[z].endCol = m_doc->lineLength(lineRanges[z].line); lineRanges[z].startX = 0; lineRanges[z].endX = m_view->renderer()->textWidth( textLine( lineRanges[z].line ), -1 ); lineRanges[z].shiftX = 0; lineRanges[z].viewLine = 0; lineRanges[z].wrap = false; } else if (z && lineRanges[z-1].dirty) { lineRanges[z-1].startsInvisibleBlock = (lineRanges[z].line != lineRanges[z-1].line + 1); } } for (; z < lineRanges.size(); z++) { if (lineRanges[z].line != -1) lineRanges[z].dirty = true; lineRanges[z].clear(); } int max = maxLen(startLine()) - width(); if (max < 0) max = 0; // if we lose the ability to scroll horizontally, move view to the far-left if (max == 0) { scrollColumns(0); } m_columnScroll->blockSignals(true); // disable scrollbar m_columnScroll->setDisabled (max == 0); m_columnScroll->setRange(0, max); m_columnScroll->setValue(m_startX); // Approximate linescroll m_columnScroll->setSteps(m_view->renderer()->config()->fontMetrics()->width('a'), width()); m_columnScroll->blockSignals(false); } m_updatingView = false; if (changed) paintText(0, 0, width(), height(), true); } void KateViewInternal::paintText (int x, int y, int width, int height, bool paintOnlyDirty) { //kdDebug() << k_funcinfo << x << " " << y << " " << width << " " << height << " " << paintOnlyDirty << endl; int xStart = startX() + x; int xEnd = xStart + width; uint h = m_view->renderer()->fontHeight(); uint startz = (y / h); uint endz = startz + 1 + (height / h); uint lineRangesSize = lineRanges.size(); static QPixmap drawBuffer; if (drawBuffer.width() < KateViewInternal::width() || drawBuffer.height() < (int)h) drawBuffer.resize(KateViewInternal::width(), (int)h); if (drawBuffer.isNull()) return; QPainter paint(this); QPainter paintDrawBuffer(&drawBuffer); // TODO put in the proper places m_view->renderer()->setCaretStyle(m_view->isOverwriteMode() ? KateRenderer::Replace : KateRenderer::Insert); m_view->renderer()->setShowTabs(m_doc->configFlags() & KateDocument::cfShowTabs); for (uint z=startz; z <= endz; z++) { if ( (z >= lineRangesSize) || ((lineRanges[z].line == -1) && (!paintOnlyDirty || lineRanges[z].dirty)) ) { if (!(z >= lineRangesSize)) lineRanges[z].dirty = false; paint.fillRect( x, z * h, width, h, m_view->renderer()->config()->backgroundColor() ); } else if (!paintOnlyDirty || lineRanges[z].dirty) { lineRanges[z].dirty = false; m_view->renderer()->paintTextLine(paintDrawBuffer, &lineRanges[z], xStart, xEnd, &cursor, &bm); paint.drawPixmap (x, z * h, drawBuffer, 0, 0, width, h); } } } /** * this function ensures a certain location is visible on the screen. * if endCol is -1, ignore making the columns visible. */ void KateViewInternal::makeVisible (const KateTextCursor& c, uint endCol, bool force, bool center, bool calledExternally) { //kdDebug() << "MakeVisible start [" << startPos().line << "," << startPos().col << "] end [" << endPos().line << "," << endPos().col << "] -> request: [" << c.line << "," << c.col << "]" <foldingTree()->findNodeForLine( c.line )->visible ) // kdDebug()<<"line ("< endPos())) { KateTextCursor scroll = viewLineOffset(c, -int(linesDisplayed()) / 2); scrollPos(scroll, false, calledExternally); } else if ( c > viewLineOffset(endPos(), -m_minLinesVisible) ) { KateTextCursor scroll = viewLineOffset(c, -((int)linesDisplayed() - m_minLinesVisible - 1)); scrollPos(scroll, false, calledExternally); } else if ( c < viewLineOffset(startPos(), m_minLinesVisible) ) { KateTextCursor scroll = viewLineOffset(c, -m_minLinesVisible); scrollPos(scroll, false, calledExternally); } else { // Check to see that we're not showing blank lines KateTextCursor max = maxStartPos(); if (startPos() > max) { scrollPos(max, max.col(), calledExternally); } } if (!m_view->dynWordWrap() && endCol != (uint)-1) { int sX = (int)m_view->renderer()->textWidth (textLine( m_doc->getRealLine( c.line() ) ), c.col() ); int sXborder = sX-8; if (sXborder < 0) sXborder = 0; if (sX < m_startX) scrollColumns (sXborder); else if (sX > m_startX + width()) scrollColumns (sX - width() + 8); } m_madeVisible = !force; } void KateViewInternal::slotRegionVisibilityChangedAt(unsigned int) { kdDebug(13030) << "slotRegionVisibilityChangedAt()" << endl; m_cachedMaxStartPos.setLine(-1); KateTextCursor max = maxStartPos(); if (startPos() > max) scrollPos(max); updateView(); update(); leftBorder->update(); } void KateViewInternal::slotCodeFoldingChanged() { leftBorder->update(); } void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int) { kdDebug(13030) << "slotRegionBeginEndAddedRemoved()" << endl; // FIXME: performance problem leftBorder->update(); } void KateViewInternal::showEvent ( QShowEvent *e ) { updateView (); QWidget::showEvent (e); } uint KateViewInternal::linesDisplayed() const { int h = height(); int fh = m_view->renderer()->fontHeight(); return (h - (h % fh)) / fh; } QPoint KateViewInternal::cursorCoordinates() { int viewLine = displayViewLine(displayCursor, true); if (viewLine == -1) return QPoint(-1, -1); uint y = viewLine * m_view->renderer()->fontHeight(); uint x = cXPos - m_startX - lineRanges[viewLine].startX + leftBorder->width() + lineRanges[viewLine].xOffset(); return QPoint(x, y); } void KateViewInternal::updateMicroFocusHint() { int line = displayViewLine(displayCursor, true); /* Check for hasFocus() to avoid crashes in QXIMInputContext as in bug #131266. This is only a workaround until somebody can find the real reason of the crash (probably it's in Qt). */ if (line == -1 || !hasFocus()) return; KateRenderer *renderer = m_view->renderer(); // Cursor placement code is changed for Asian input method that // shows candidate window. This behavior is same as Qt/E 2.3.7 // which supports Asian input methods. Asian input methods need // start point of IM selection text to place candidate window as // adjacent to the selection text. uint preeditStrLen = renderer->textWidth(textLine(m_imPreeditStartLine), cursor.col()) - renderer->textWidth(textLine(m_imPreeditStartLine), m_imPreeditSelStart); uint x = cXPos - m_startX - lineRanges[line].startX + lineRanges[line].xOffset() - preeditStrLen; uint y = line * renderer->fontHeight(); setMicroFocusHint(x, y, 0, renderer->fontHeight()); } void KateViewInternal::doReturn() { KateTextCursor c = cursor; m_doc->newLine( c, this ); updateCursor( c ); updateView(); } void KateViewInternal::doDelete() { m_doc->del( m_view, cursor ); if (m_view->m_codeCompletion->codeCompletionVisible()) { m_view->m_codeCompletion->updateBox(); } } void KateViewInternal::doBackspace() { m_doc->backspace( m_view, cursor ); if (m_view->m_codeCompletion->codeCompletionVisible()) { m_view->m_codeCompletion->updateBox(); } } void KateViewInternal::doTranspose() { m_doc->transpose( cursor ); } void KateViewInternal::doDeleteWordLeft() { wordLeft( true ); m_view->removeSelectedText(); update(); } void KateViewInternal::doDeleteWordRight() { wordRight( true ); m_view->removeSelectedText(); update(); } class CalculatingCursor : public KateTextCursor { public: CalculatingCursor(KateViewInternal* vi) : KateTextCursor() , m_vi(vi) { Q_ASSERT(valid()); } CalculatingCursor(KateViewInternal* vi, const KateTextCursor& c) : KateTextCursor(c) , m_vi(vi) { Q_ASSERT(valid()); } // This one constrains its arguments to valid positions CalculatingCursor(KateViewInternal* vi, uint line, uint col) : KateTextCursor(line, col) , m_vi(vi) { makeValid(); } virtual CalculatingCursor& operator+=( int n ) = 0; virtual CalculatingCursor& operator-=( int n ) = 0; CalculatingCursor& operator++() { return operator+=( 1 ); } CalculatingCursor& operator--() { return operator-=( 1 ); } void makeValid() { m_line = kMax( 0, kMin( int( m_vi->m_doc->numLines() - 1 ), line() ) ); if (m_vi->m_view->wrapCursor()) m_col = kMax( 0, kMin( m_vi->m_doc->lineLength( line() ), col() ) ); else m_col = kMax( 0, col() ); Q_ASSERT( valid() ); } void toEdge( Bias bias ) { if( bias == left ) m_col = 0; else if( bias == right ) m_col = m_vi->m_doc->lineLength( line() ); } bool atEdge() const { return atEdge( left ) || atEdge( right ); } bool atEdge( Bias bias ) const { switch( bias ) { case left: return col() == 0; case none: return atEdge(); case right: return col() == m_vi->m_doc->lineLength( line() ); default: Q_ASSERT(false); return false; } } protected: bool valid() const { return line() >= 0 && uint( line() ) < m_vi->m_doc->numLines() && col() >= 0 && (!m_vi->m_view->wrapCursor() || col() <= m_vi->m_doc->lineLength( line() )); } KateViewInternal* m_vi; }; class BoundedCursor : public CalculatingCursor { public: BoundedCursor(KateViewInternal* vi) : CalculatingCursor( vi ) {}; BoundedCursor(KateViewInternal* vi, const KateTextCursor& c ) : CalculatingCursor( vi, c ) {}; BoundedCursor(KateViewInternal* vi, uint line, uint col ) : CalculatingCursor( vi, line, col ) {}; virtual CalculatingCursor& operator+=( int n ) { m_col += n; if (n > 0 && m_vi->m_view->dynWordWrap()) { // Need to constrain to current visible text line for dynamic wrapping mode if (m_col > m_vi->m_doc->lineLength(m_line)) { KateLineRange currentRange = m_vi->range(*this); int endX; bool crap; m_vi->m_view->renderer()->textWidth(m_vi->textLine(m_line), currentRange.startCol, m_vi->width() - currentRange.xOffset(), &crap, &endX); endX += (m_col - currentRange.endCol + 1) * m_vi->m_view->renderer()->spaceWidth(); // Constraining if applicable NOTE: some code duplication in KateViewInternal::resize() if (endX >= m_vi->width() - currentRange.xOffset()) { m_col -= n; if ( uint( line() ) < m_vi->m_doc->numLines() - 1 ) { m_line++; m_col = 0; } } } } else if (n < 0 && col() < 0 && line() > 0 ) { m_line--; m_col = m_vi->m_doc->lineLength( line() ); } m_col = kMax( 0, col() ); Q_ASSERT( valid() ); return *this; } virtual CalculatingCursor& operator-=( int n ) { return operator+=( -n ); } }; class WrappingCursor : public CalculatingCursor { public: WrappingCursor(KateViewInternal* vi) : CalculatingCursor( vi) {}; WrappingCursor(KateViewInternal* vi, const KateTextCursor& c ) : CalculatingCursor( vi, c ) {}; WrappingCursor(KateViewInternal* vi, uint line, uint col ) : CalculatingCursor( vi, line, col ) {}; virtual CalculatingCursor& operator+=( int n ) { if( n < 0 ) return operator-=( -n ); int len = m_vi->m_doc->lineLength( line() ); if( col() + n <= len ) { m_col += n; } else if( uint( line() ) < m_vi->m_doc->numLines() - 1 ) { n -= len - col() + 1; m_col = 0; m_line++; operator+=( n ); } else { m_col = len; } Q_ASSERT( valid() ); return *this; } virtual CalculatingCursor& operator-=( int n ) { if( n < 0 ) return operator+=( -n ); if( col() - n >= 0 ) { m_col -= n; } else if( line() > 0 ) { n -= col() + 1; m_line--; m_col = m_vi->m_doc->lineLength( line() ); operator-=( n ); } else { m_col = 0; } Q_ASSERT( valid() ); return *this; } }; void KateViewInternal::moveChar( Bias bias, bool sel ) { KateTextCursor c; if ( m_view->wrapCursor() ) { c = WrappingCursor( this, cursor ) += bias; } else { c = BoundedCursor( this, cursor ) += bias; } updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::cursorLeft( bool sel ) { if ( ! m_view->wrapCursor() && cursor.col() == 0 ) return; moveChar( left, sel ); if (m_view->m_codeCompletion->codeCompletionVisible()) { m_view->m_codeCompletion->updateBox(); } } void KateViewInternal::cursorRight( bool sel ) { moveChar( right, sel ); if (m_view->m_codeCompletion->codeCompletionVisible()) { m_view->m_codeCompletion->updateBox(); } } void KateViewInternal::wordLeft ( bool sel ) { WrappingCursor c( this, cursor ); // First we skip backwards all space. // Then we look up into which category the current position falls: // 1. a "word" character // 2. a "non-word" character (except space) // 3. the beginning of the line // and skip all preceding characters that fall into this class. // The code assumes that space is never part of the word character class. KateHighlighting* h = m_doc->highlight(); if( !c.atEdge( left ) ) { while( !c.atEdge( left ) && m_doc->textLine( c.line() )[ c.col() - 1 ].isSpace() ) --c; } if( c.atEdge( left ) ) { --c; } else if( h->isInWord( m_doc->textLine( c.line() )[ c.col() - 1 ] ) ) { while( !c.atEdge( left ) && h->isInWord( m_doc->textLine( c.line() )[ c.col() - 1 ] ) ) --c; } else { while( !c.atEdge( left ) && !h->isInWord( m_doc->textLine( c.line() )[ c.col() - 1 ] ) // in order to stay symmetric to wordLeft() // we must not skip space preceding a non-word sequence && !m_doc->textLine( c.line() )[ c.col() - 1 ].isSpace() ) { --c; } } updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::wordRight( bool sel ) { WrappingCursor c( this, cursor ); // We look up into which category the current position falls: // 1. a "word" character // 2. a "non-word" character (except space) // 3. the end of the line // and skip all following characters that fall into this class. // If the skipped characters are followed by space, we skip that too. // The code assumes that space is never part of the word character class. KateHighlighting* h = m_doc->highlight(); if( c.atEdge( right ) ) { ++c; } else if( h->isInWord( m_doc->textLine( c.line() )[ c.col() ] ) ) { while( !c.atEdge( right ) && h->isInWord( m_doc->textLine( c.line() )[ c.col() ] ) ) ++c; } else { while( !c.atEdge( right ) && !h->isInWord( m_doc->textLine( c.line() )[ c.col() ] ) // we must not skip space, because if that space is followed // by more non-word characters, we would skip them, too && !m_doc->textLine( c.line() )[ c.col() ].isSpace() ) { ++c; } } while( !c.atEdge( right ) && m_doc->textLine( c.line() )[ c.col() ].isSpace() ) ++c; updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::moveEdge( Bias bias, bool sel ) { BoundedCursor c( this, cursor ); c.toEdge( bias ); updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::home( bool sel ) { if (m_view->m_codeCompletion->codeCompletionVisible()) { QKeyEvent e(QEvent::KeyPress, Qt::Key_Home, 0, 0); m_view->m_codeCompletion->handleKey(&e); return; } if (m_view->dynWordWrap() && currentRange().startCol) { // Allow us to go to the real start if we're already at the start of the view line if (cursor.col() != currentRange().startCol) { KateTextCursor c(cursor.line(), currentRange().startCol); updateSelection( c, sel ); updateCursor( c ); return; } } if( !(m_doc->configFlags() & KateDocument::cfSmartHome) ) { moveEdge( left, sel ); return; } KateTextLine::Ptr l = textLine( cursor.line() ); if (!l) return; KateTextCursor c = cursor; int lc = l->firstChar(); if( lc < 0 || c.col() == lc ) { c.setCol(0); } else { c.setCol(lc); } updateSelection( c, sel ); updateCursor( c, true ); } void KateViewInternal::end( bool sel ) { if (m_view->m_codeCompletion->codeCompletionVisible()) { QKeyEvent e(QEvent::KeyPress, Qt::Key_End, 0, 0); m_view->m_codeCompletion->handleKey(&e); return; } KateLineRange range = currentRange(); if (m_view->dynWordWrap() && range.wrap) { // Allow us to go to the real end if we're already at the end of the view line if (cursor.col() < range.endCol - 1) { KateTextCursor c(cursor.line(), range.endCol - 1); updateSelection( c, sel ); updateCursor( c ); return; } } if( !(m_doc->configFlags() & KateDocument::cfSmartHome) ) { moveEdge( right, sel ); return; } KateTextLine::Ptr l = textLine( cursor.line() ); if (!l) return; // "Smart End", as requested in bugs #78258 and #106970 KateTextCursor c = cursor; // If the cursor is already the real end, jump to last non-space character. // Otherwise, go to the real end ... obviously. if (c.col() == m_doc->lineLength(c.line())) { c.setCol(l->lastChar() + 1); updateSelection(c, sel); updateCursor(c, true); } else { moveEdge(right, sel); } } KateLineRange KateViewInternal::range(int realLine, const KateLineRange* previous) { // look at the cache first if (!m_updatingView && realLine >= lineRanges[0].line && realLine <= lineRanges[lineRanges.count() - 1].line) for (uint i = 0; i < lineRanges.count(); i++) if (realLine == lineRanges[i].line) if (!m_view->dynWordWrap() || (!previous && lineRanges[i].startCol == 0) || (previous && lineRanges[i].startCol == previous->endCol)) return lineRanges[i]; // Not in the cache, we have to create it KateLineRange ret; KateTextLine::Ptr text = textLine(realLine); if (!text) { return KateLineRange(); } if (!m_view->dynWordWrap()) { Q_ASSERT(!previous); ret.line = realLine; ret.virtualLine = m_doc->getVirtualLine(realLine); ret.startCol = 0; ret.endCol = m_doc->lineLength(realLine); ret.startX = 0; ret.endX = m_view->renderer()->textWidth(text, -1); ret.viewLine = 0; ret.wrap = false; return ret; } ret.endCol = (int)m_view->renderer()->textWidth(text, previous ? previous->endCol : 0, width() - (previous ? previous->shiftX : 0), &ret.wrap, &ret.endX); Q_ASSERT(ret.endCol > ret.startCol); ret.line = realLine; if (previous) { ret.virtualLine = previous->virtualLine; ret.startCol = previous->endCol; ret.startX = previous->endX; ret.endX += previous->endX; ret.shiftX = previous->shiftX; ret.viewLine = previous->viewLine + 1; } else { // TODO worthwhile optimising this to get the data out of the initial textWidth call? if (m_view->config()->dynWordWrapAlignIndent() > 0) { int pos = text->nextNonSpaceChar(0); if (pos > 0) ret.shiftX = m_view->renderer()->textWidth(text, pos); if (ret.shiftX > ((double)width() / 100 * m_view->config()->dynWordWrapAlignIndent())) ret.shiftX = 0; } ret.virtualLine = m_doc->getVirtualLine(realLine); ret.startCol = 0; ret.startX = 0; ret.viewLine = 0; } return ret; } KateLineRange KateViewInternal::currentRange() { // Q_ASSERT(m_view->dynWordWrap()); return range(cursor); } KateLineRange KateViewInternal::previousRange() { uint currentViewLine = viewLine(cursor); if (currentViewLine) return range(cursor.line(), currentViewLine - 1); else return range(m_doc->getRealLine(displayCursor.line() - 1), -1); } KateLineRange KateViewInternal::nextRange() { uint currentViewLine = viewLine(cursor) + 1; if (currentViewLine >= viewLineCount(cursor.line())) { currentViewLine = 0; return range(cursor.line() + 1, currentViewLine); } else { return range(cursor.line(), currentViewLine); } } KateLineRange KateViewInternal::range(const KateTextCursor& realCursor) { // Q_ASSERT(m_view->dynWordWrap()); KateLineRange thisRange; bool first = true; do { thisRange = range(realCursor.line(), first ? 0L : &thisRange); first = false; } while (thisRange.wrap && !(realCursor.col() >= thisRange.startCol && realCursor.col() < thisRange.endCol) && thisRange.startCol != thisRange.endCol); return thisRange; } KateLineRange KateViewInternal::range(uint realLine, int viewLine) { // Q_ASSERT(m_view->dynWordWrap()); KateLineRange thisRange; bool first = true; do { thisRange = range(realLine, first ? 0L : &thisRange); first = false; } while (thisRange.wrap && viewLine != thisRange.viewLine && thisRange.startCol != thisRange.endCol); if (viewLine != -1 && viewLine != thisRange.viewLine) kdDebug(13030) << "WARNING: viewLine " << viewLine << " of line " << realLine << " does not exist." << endl; return thisRange; } /** * This returns the view line upon which realCursor is situated. * The view line is the number of lines in the view from the first line * The supplied cursor should be in real lines. */ uint KateViewInternal::viewLine(const KateTextCursor& realCursor) { if (!m_view->dynWordWrap()) return 0; if (realCursor.col() == 0) return 0; KateLineRange thisRange; bool first = true; do { thisRange = range(realCursor.line(), first ? 0L : &thisRange); first = false; } while (thisRange.wrap && !(realCursor.col() >= thisRange.startCol && realCursor.col() < thisRange.endCol) && thisRange.startCol != thisRange.endCol); return thisRange.viewLine; } int KateViewInternal::displayViewLine(const KateTextCursor& virtualCursor, bool limitToVisible) { KateTextCursor work = startPos(); int limit = linesDisplayed(); // Efficient non-word-wrapped path if (!m_view->dynWordWrap()) { int ret = virtualCursor.line() - startLine(); if (limitToVisible && (ret < 0 || ret > limit)) return -1; else return ret; } if (work == virtualCursor) { return 0; } int ret = -(int)viewLine(work); bool forwards = (work < virtualCursor) ? true : false; // FIXME switch to using ranges? faster? if (forwards) { while (work.line() != virtualCursor.line()) { ret += viewLineCount(m_doc->getRealLine(work.line())); work.setLine(work.line() + 1); if (limitToVisible && ret > limit) return -1; } } else { while (work.line() != virtualCursor.line()) { work.setLine(work.line() - 1); ret -= viewLineCount(m_doc->getRealLine(work.line())); if (limitToVisible && ret < 0) return -1; } } // final difference KateTextCursor realCursor = virtualCursor; realCursor.setLine(m_doc->getRealLine(realCursor.line())); if (realCursor.col() == -1) realCursor.setCol(m_doc->lineLength(realCursor.line())); ret += viewLine(realCursor); if (limitToVisible && (ret < 0 || ret > limit)) return -1; return ret; } uint KateViewInternal::lastViewLine(uint realLine) { if (!m_view->dynWordWrap()) return 0; KateLineRange thisRange; bool first = true; do { thisRange = range(realLine, first ? 0L : &thisRange); first = false; } while (thisRange.wrap && thisRange.startCol != thisRange.endCol); return thisRange.viewLine; } uint KateViewInternal::viewLineCount(uint realLine) { return lastViewLine(realLine) + 1; } /* * This returns the cursor which is offset by (offset) view lines. * This is the main function which is called by code not specifically dealing with word-wrap. * The opposite conversion (cursor to offset) can be done with displayViewLine. * * The cursors involved are virtual cursors (ie. equivalent to displayCursor) */ KateTextCursor KateViewInternal::viewLineOffset(const KateTextCursor& virtualCursor, int offset, bool keepX) { if (!m_view->dynWordWrap()) { KateTextCursor ret(kMin((int)m_doc->visibleLines() - 1, virtualCursor.line() + offset), 0); if (ret.line() < 0) ret.setLine(0); if (keepX) { int realLine = m_doc->getRealLine(ret.line()); ret.setCol(m_doc->lineLength(realLine) - 1); if (m_currentMaxX > cXPos) cXPos = m_currentMaxX; if (m_view->wrapCursor()) cXPos = kMin(cXPos, (int)m_view->renderer()->textWidth(textLine(realLine), m_doc->lineLength(realLine))); m_view->renderer()->textWidth(ret, cXPos); } return ret; } KateTextCursor realCursor = virtualCursor; realCursor.setLine(m_doc->getRealLine(virtualCursor.line())); uint cursorViewLine = viewLine(realCursor); int currentOffset = 0; int virtualLine = 0; bool forwards = (offset > 0) ? true : false; if (forwards) { currentOffset = lastViewLine(realCursor.line()) - cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateLineRange thisRange = range(realCursor.line(), cursorViewLine + offset); Q_ASSERT(thisRange.virtualLine == virtualCursor.line()); return KateTextCursor(virtualCursor.line(), thisRange.startCol); } virtualLine = virtualCursor.line() + 1; } else { offset = -offset; currentOffset = cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateLineRange thisRange = range(realCursor.line(), cursorViewLine - offset); Q_ASSERT(thisRange.virtualLine == virtualCursor.line()); return KateTextCursor(virtualCursor.line(), thisRange.startCol); } virtualLine = virtualCursor.line() - 1; } currentOffset++; while (virtualLine >= 0 && virtualLine < (int)m_doc->visibleLines()) { KateLineRange thisRange; bool first = true; int realLine = m_doc->getRealLine(virtualLine); do { thisRange = range(realLine, first ? 0L : &thisRange); first = false; if (offset == currentOffset) { if (!forwards) { // We actually want it the other way around int requiredViewLine = lastViewLine(realLine) - thisRange.viewLine; if (requiredViewLine != thisRange.viewLine) { thisRange = range(realLine, requiredViewLine); } } KateTextCursor ret(virtualLine, thisRange.startCol); // keep column position if (keepX) { ret.setCol(thisRange.endCol - 1); KateTextCursor realCursorTemp(m_doc->getRealLine(virtualCursor.line()), virtualCursor.col()); int visibleX = m_view->renderer()->textWidth(realCursorTemp) - range(realCursorTemp).startX; int xOffset = thisRange.startX; if (m_currentMaxX > visibleX) visibleX = m_currentMaxX; cXPos = xOffset + visibleX; cXPos = kMin(cXPos, lineMaxCursorX(thisRange)); m_view->renderer()->textWidth(ret, cXPos); } return ret; } currentOffset++; } while (thisRange.wrap); if (forwards) virtualLine++; else virtualLine--; } // Looks like we were asked for something a bit exotic. // Return the max/min valid position. if (forwards) return KateTextCursor(m_doc->visibleLines() - 1, m_doc->lineLength(m_doc->visibleLines() - 1)); else return KateTextCursor(0, 0); } int KateViewInternal::lineMaxCursorX(const KateLineRange& range) { if (!m_view->wrapCursor() && !range.wrap) return INT_MAX; int maxX = range.endX; if (maxX && range.wrap) { QChar lastCharInLine = textLine(range.line)->getChar(range.endCol - 1); if (lastCharInLine == QChar('\t')) { int lineSize = 0; int lastTabSize = 0; for(int i = range.startCol; i < range.endCol; i++) { if (textLine(range.line)->getChar(i) == QChar('\t')) { lastTabSize = m_view->tabWidth() - (lineSize % m_view->tabWidth()); lineSize += lastTabSize; } else { lineSize++; } } maxX -= lastTabSize * m_view->renderer()->spaceWidth(); } else { maxX -= m_view->renderer()->config()->fontMetrics()->width(lastCharInLine); } } return maxX; } int KateViewInternal::lineMaxCol(const KateLineRange& range) { int maxCol = range.endCol; if (maxCol && range.wrap) maxCol--; return maxCol; } void KateViewInternal::cursorUp(bool sel) { if (m_view->m_codeCompletion->codeCompletionVisible()) { QKeyEvent e(QEvent::KeyPress, Qt::Key_Up, 0, 0); m_view->m_codeCompletion->handleKey(&e); return; } if (displayCursor.line() == 0 && (!m_view->dynWordWrap() || viewLine(cursor) == 0)) return; int newLine = cursor.line(), newCol = 0, xOffset = 0, startCol = 0; m_preserveMaxX = true; if (m_view->dynWordWrap()) { // Dynamic word wrapping - navigate on visual lines rather than real lines KateLineRange thisRange = currentRange(); // This is not the first line because that is already simplified out above KateLineRange pRange = previousRange(); // Ensure we're in the right spot Q_ASSERT((cursor.line() == thisRange.line) && (cursor.col() >= thisRange.startCol) && (!thisRange.wrap || cursor.col() < thisRange.endCol)); // VisibleX is the distance from the start of the text to the cursor on the current line. int visibleX = m_view->renderer()->textWidth(cursor) - thisRange.startX; int currentLineVisibleX = visibleX; // Translate to new line visibleX += thisRange.xOffset(); visibleX -= pRange.xOffset(); // Limit to >= 0 visibleX = kMax(0, visibleX); startCol = pRange.startCol; xOffset = pRange.startX; newLine = pRange.line; // Take into account current max X (ie. if the current line was smaller // than the last definitely specified width) if (thisRange.xOffset() && !pRange.xOffset() && currentLineVisibleX == 0) // Special case for where xOffset may be > m_currentMaxX visibleX = m_currentMaxX; else if (visibleX < m_currentMaxX - pRange.xOffset()) visibleX = m_currentMaxX - pRange.xOffset(); cXPos = xOffset + visibleX; cXPos = kMin(cXPos, lineMaxCursorX(pRange)); newCol = kMin((int)m_view->renderer()->textPos(newLine, visibleX, startCol), lineMaxCol(pRange)); } else { newLine = m_doc->getRealLine(displayCursor.line() - 1); if ((m_view->wrapCursor()) && m_currentMaxX > cXPos) cXPos = m_currentMaxX; } KateTextCursor c(newLine, newCol); m_view->renderer()->textWidth(c, cXPos); updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::cursorDown(bool sel) { if (m_view->m_codeCompletion->codeCompletionVisible()) { QKeyEvent e(QEvent::KeyPress, Qt::Key_Down, 0, 0); m_view->m_codeCompletion->handleKey(&e); return; } if ((displayCursor.line() >= (int)m_doc->numVisLines() - 1) && (!m_view->dynWordWrap() || viewLine(cursor) == lastViewLine(cursor.line()))) return; int newLine = cursor.line(), newCol = 0, xOffset = 0, startCol = 0; m_preserveMaxX = true; if (m_view->dynWordWrap()) { // Dynamic word wrapping - navigate on visual lines rather than real lines KateLineRange thisRange = currentRange(); // This is not the last line because that is already simplified out above KateLineRange nRange = nextRange(); // Ensure we're in the right spot Q_ASSERT((cursor.line() == thisRange.line) && (cursor.col() >= thisRange.startCol) && (!thisRange.wrap || cursor.col() < thisRange.endCol)); // VisibleX is the distance from the start of the text to the cursor on the current line. int visibleX = m_view->renderer()->textWidth(cursor) - thisRange.startX; int currentLineVisibleX = visibleX; // Translate to new line visibleX += thisRange.xOffset(); visibleX -= nRange.xOffset(); // Limit to >= 0 visibleX = kMax(0, visibleX); if (!thisRange.wrap) { newLine = m_doc->getRealLine(displayCursor.line() + 1); } else { startCol = thisRange.endCol; xOffset = thisRange.endX; } // Take into account current max X (ie. if the current line was smaller // than the last definitely specified width) if (thisRange.xOffset() && !nRange.xOffset() && currentLineVisibleX == 0) // Special case for where xOffset may be > m_currentMaxX visibleX = m_currentMaxX; else if (visibleX < m_currentMaxX - nRange.xOffset()) visibleX = m_currentMaxX - nRange.xOffset(); cXPos = xOffset + visibleX; cXPos = kMin(cXPos, lineMaxCursorX(nRange)); newCol = kMin((int)m_view->renderer()->textPos(newLine, visibleX, startCol), lineMaxCol(nRange)); } else { newLine = m_doc->getRealLine(displayCursor.line() + 1); if ((m_view->wrapCursor()) && m_currentMaxX > cXPos) cXPos = m_currentMaxX; } KateTextCursor c(newLine, newCol); m_view->renderer()->textWidth(c, cXPos); updateSelection(c, sel); updateCursor(c); } void KateViewInternal::cursorToMatchingBracket( bool sel ) { KateTextCursor start( cursor ), end; if( !m_doc->findMatchingBracket( start, end ) ) return; // The cursor is now placed just to the left of the matching bracket. // If it's an ending bracket, put it to the right (so we can easily // get back to the original bracket). if( end > start ) end.setCol(end.col() + 1); updateSelection( end, sel ); updateCursor( end ); } void KateViewInternal::topOfView( bool sel ) { KateTextCursor c = viewLineOffset(startPos(), m_minLinesVisible); updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::bottomOfView( bool sel ) { // FIXME account for wordwrap KateTextCursor c = viewLineOffset(endPos(), -m_minLinesVisible); updateSelection( c, sel ); updateCursor( c ); } // lines is the offset to scroll by void KateViewInternal::scrollLines( int lines, bool sel ) { KateTextCursor c = viewLineOffset(displayCursor, lines, true); // Fix the virtual cursor -> real cursor c.setLine(m_doc->getRealLine(c.line())); updateSelection( c, sel ); updateCursor( c ); } // This is a bit misleading... it's asking for the view to be scrolled, not the cursor void KateViewInternal::scrollUp() { KateTextCursor newPos = viewLineOffset(m_startPos, -1); scrollPos(newPos); } void KateViewInternal::scrollDown() { KateTextCursor newPos = viewLineOffset(m_startPos, 1); scrollPos(newPos); } void KateViewInternal::setAutoCenterLines(int viewLines, bool updateView) { m_autoCenterLines = viewLines; m_minLinesVisible = kMin(int((linesDisplayed() - 1)/2), m_autoCenterLines); if (updateView) KateViewInternal::updateView(); } void KateViewInternal::pageUp( bool sel ) { if (m_view->m_codeCompletion->codeCompletionVisible()) { QKeyEvent e(QEvent::KeyPress, Qt::Key_PageUp, 0, 0); m_view->m_codeCompletion->handleKey(&e); return; } // remember the view line and x pos int viewLine = displayViewLine(displayCursor); bool atTop = (startPos().line() == 0 && startPos().col() == 0); // Adjust for an auto-centering cursor int lineadj = 2 * m_minLinesVisible; int cursorStart = (linesDisplayed() - 1) - viewLine; if (cursorStart < m_minLinesVisible) lineadj -= m_minLinesVisible - cursorStart; int linesToScroll = -kMax( ((int)linesDisplayed() - 1) - lineadj, 0 ); m_preserveMaxX = true; if (!m_doc->pageUpDownMovesCursor () && !atTop) { int xPos = m_view->renderer()->textWidth(cursor) - currentRange().startX; KateTextCursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1); scrollPos(newStartPos); // put the cursor back approximately where it was KateTextCursor newPos = viewLineOffset(newStartPos, viewLine, true); newPos.setLine(m_doc->getRealLine(newPos.line())); KateLineRange newLine = range(newPos); if (m_currentMaxX - newLine.xOffset() > xPos) xPos = m_currentMaxX - newLine.xOffset(); cXPos = kMin(newLine.startX + xPos, lineMaxCursorX(newLine)); m_view->renderer()->textWidth( newPos, cXPos ); m_preserveMaxX = true; updateSelection( newPos, sel ); updateCursor(newPos); } else { scrollLines( linesToScroll, sel ); } } void KateViewInternal::pageDown( bool sel ) { if (m_view->m_codeCompletion->codeCompletionVisible()) { QKeyEvent e(QEvent::KeyPress, Qt::Key_PageDown, 0, 0); m_view->m_codeCompletion->handleKey(&e); return; } // remember the view line int viewLine = displayViewLine(displayCursor); bool atEnd = startPos() >= m_cachedMaxStartPos; // Adjust for an auto-centering cursor int lineadj = 2 * m_minLinesVisible; int cursorStart = m_minLinesVisible - viewLine; if (cursorStart > 0) lineadj -= cursorStart; int linesToScroll = kMax( ((int)linesDisplayed() - 1) - lineadj, 0 ); m_preserveMaxX = true; if (!m_doc->pageUpDownMovesCursor () && !atEnd) { int xPos = m_view->renderer()->textWidth(cursor) - currentRange().startX; KateTextCursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1); scrollPos(newStartPos); // put the cursor back approximately where it was KateTextCursor newPos = viewLineOffset(newStartPos, viewLine, true); newPos.setLine(m_doc->getRealLine(newPos.line())); KateLineRange newLine = range(newPos); if (m_currentMaxX - newLine.xOffset() > xPos) xPos = m_currentMaxX - newLine.xOffset(); cXPos = kMin(newLine.startX + xPos, lineMaxCursorX(newLine)); m_view->renderer()->textWidth( newPos, cXPos ); m_preserveMaxX = true; updateSelection( newPos, sel ); updateCursor(newPos); } else { scrollLines( linesToScroll, sel ); } } int KateViewInternal::maxLen(uint startLine) { // Q_ASSERT(!m_view->dynWordWrap()); int displayLines = (m_view->height() / m_view->renderer()->fontHeight()) + 1; int maxLen = 0; for (int z = 0; z < displayLines; z++) { int virtualLine = startLine + z; if (virtualLine < 0 || virtualLine >= (int)m_doc->visibleLines()) break; KateLineRange thisRange = range((int)m_doc->getRealLine(virtualLine)); maxLen = kMax(maxLen, thisRange.endX); } return maxLen; } void KateViewInternal::top( bool sel ) { KateTextCursor c( 0, cursor.col() ); m_view->renderer()->textWidth( c, cXPos ); updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::bottom( bool sel ) { KateTextCursor c( m_doc->lastLine(), cursor.col() ); m_view->renderer()->textWidth( c, cXPos ); updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::top_home( bool sel ) { if (m_view->m_codeCompletion->codeCompletionVisible()) { QKeyEvent e(QEvent::KeyPress, Qt::Key_Home, 0, 0); m_view->m_codeCompletion->handleKey(&e); return; } KateTextCursor c( 0, 0 ); updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::bottom_end( bool sel ) { if (m_view->m_codeCompletion->codeCompletionVisible()) { QKeyEvent e(QEvent::KeyPress, Qt::Key_End, 0, 0); m_view->m_codeCompletion->handleKey(&e); return; } KateTextCursor c( m_doc->lastLine(), m_doc->lineLength( m_doc->lastLine() ) ); updateSelection( c, sel ); updateCursor( c ); } void KateViewInternal::updateSelection( const KateTextCursor& _newCursor, bool keepSel ) { KateTextCursor newCursor = _newCursor; if( keepSel ) { if ( !m_view->hasSelection() || (selectAnchor.line() == -1) || (m_view->config()->persistentSelection() && ((cursor < m_view->selectStart) || (cursor > m_view->selectEnd))) ) { selectAnchor = cursor; m_view->setSelection( cursor, newCursor ); } else { bool doSelect = true; switch (m_selectionMode) { case Word: { // Restore selStartCached if needed. It gets nuked by // viewSelectionChanged if we drag the selection into non-existence, // which can legitimately happen if a shift+DC selection is unable to // set a "proper" (i.e. non-empty) cached selection, e.g. because the // start was on something that isn't a word. Word select mode relies // on the cached selection being set properly, even if it is empty // (i.e. selStartCached == selEndCached). if ( selStartCached.line() == -1 ) selStartCached = selEndCached; int c; if ( newCursor > selEndCached ) { selectAnchor = selStartCached; KateTextLine::Ptr l = m_doc->kateTextLine( newCursor.line() ); c = newCursor.col(); if ( c > 0 && m_doc->highlight()->isInWord( l->getChar( c-1 ) ) ) { for (; c < l->length(); c++ ) if ( !m_doc->highlight()->isInWord( l->getChar( c ) ) ) break; } newCursor.setCol( c ); } else if ( newCursor < selStartCached ) { selectAnchor = selEndCached; KateTextLine::Ptr l = m_doc->kateTextLine( newCursor.line() ); c = newCursor.col(); if ( c > 0 && c < m_doc->textLine( newCursor.line() ).length() && m_doc->highlight()->isInWord( l->getChar( c ) ) && m_doc->highlight()->isInWord( l->getChar( c-1 ) ) ) { for ( c -= 2; c >= 0; c-- ) if ( !m_doc->highlight()->isInWord( l->getChar( c ) ) ) break; newCursor.setCol( c+1 ); } } else doSelect = false; } break; case Line: if ( newCursor.line() > selStartCached.line() ) { if ( newCursor.line()+1 >= m_doc->numLines() ) newCursor.setCol( m_doc->textLine( newCursor.line() ).length() ); else newCursor.setPos( newCursor.line() + 1, 0 ); // Grow to include entire line selectAnchor = selStartCached; selectAnchor.setCol( 0 ); } else if ( newCursor.line() < selStartCached.line() ) { newCursor.setCol( 0 ); // Grow to include entire line selectAnchor = selEndCached; if ( selectAnchor.col() > 0 ) { if ( selectAnchor.line()+1 >= m_doc->numLines() ) selectAnchor.setCol( m_doc->textLine( selectAnchor.line() ).length() ); else selectAnchor.setPos( selectAnchor.line() + 1, 0 ); } } else // same line, ignore doSelect = false; break; case Mouse: { if ( selStartCached.line() < 0 ) // invalid break; if ( newCursor > selEndCached ) selectAnchor = selStartCached; else if ( newCursor < selStartCached ) selectAnchor = selEndCached; else doSelect = false; } break; default: { if ( selectAnchor.line() < 0 ) // invalid break; } } if ( doSelect ) m_view->setSelection( selectAnchor, newCursor); else if ( selStartCached.line() >= 0 ) // we have a cached selection, so we restore that m_view->setSelection( selStartCached, selEndCached ); } m_selChangedByUser = true; } else if ( !m_view->config()->persistentSelection() ) { m_view->clearSelection(); selStartCached.setLine( -1 ); selectAnchor.setLine( -1 ); } } void KateViewInternal::updateCursor( const KateTextCursor& newCursor, bool force, bool center, bool calledExternally ) { if ( !force && (cursor == newCursor) ) { if ( !m_madeVisible && m_view == m_doc->activeView() ) { // unfold if required m_doc->foldingTree()->ensureVisible( newCursor.line() ); makeVisible ( displayCursor, displayCursor.col(), false, center, calledExternally ); } return; } // unfold if required m_doc->foldingTree()->ensureVisible( newCursor.line() ); KateTextCursor oldDisplayCursor = displayCursor; cursor.setPos (newCursor); displayCursor.setPos (m_doc->getVirtualLine(cursor.line()), cursor.col()); cXPos = m_view->renderer()->textWidth( cursor ); if (m_view == m_doc->activeView()) makeVisible ( displayCursor, displayCursor.col(), false, center, calledExternally ); updateBracketMarks(); // It's efficient enough to just tag them both without checking to see if they're on the same view line tagLine(oldDisplayCursor); tagLine(displayCursor); updateMicroFocusHint(); if (m_cursorTimer.isActive ()) { if ( KApplication::cursorFlashTime() > 0 ) m_cursorTimer.start( KApplication::cursorFlashTime() / 2 ); m_view->renderer()->setDrawCaret(true); } // Remember the maximum X position if requested if (m_preserveMaxX) m_preserveMaxX = false; else if (m_view->dynWordWrap()) m_currentMaxX = m_view->renderer()->textWidth(displayCursor) - currentRange().startX + currentRange().xOffset(); else m_currentMaxX = cXPos; //kdDebug() << "m_currentMaxX: " << m_currentMaxX << " (was "<< oldmaxx << "), cXPos: " << cXPos << endl; //kdDebug(13030) << "Cursor now located at real " << cursor.line << "," << cursor.col << ", virtual " << displayCursor.line << ", " << displayCursor.col << "; Top is " << startLine() << ", " << startPos().col << endl; paintText(0, 0, width(), height(), true); emit m_view->cursorPositionChanged(); } void KateViewInternal::updateBracketMarks() { if ( bm.isValid() ) { KateTextCursor bmStart(m_doc->getVirtualLine(bm.start().line()), bm.start().col()); KateTextCursor bmEnd(m_doc->getVirtualLine(bm.end().line()), bm.end().col()); if( bm.getMinIndent() != 0 ) { // @@ Do this only when cursor near start/end. if( bmStart > bmEnd ) { tagLines(bmEnd, bmStart); } else { tagLines(bmStart, bmEnd); } } else { tagLine(bmStart); tagLine(bmEnd); } } // add some limit to this, this is really endless on big files without limit int maxLines = linesDisplayed () * 3; m_doc->newBracketMark( cursor, bm, maxLines ); if ( bm.isValid() ) { KateTextCursor bmStart(m_doc->getVirtualLine(bm.start().line()), bm.start().col()); KateTextCursor bmEnd(m_doc->getVirtualLine(bm.end().line()), bm.end().col()); if( bm.getMinIndent() != 0 ) { // @@ Do this only when cursor near start/end. if( bmStart > bmEnd ) { tagLines(bmEnd, bmStart); } else { tagLines(bmStart, bmEnd); } } else { tagLine(bmStart); tagLine(bmEnd); } } } bool KateViewInternal::tagLine(const KateTextCursor& virtualCursor) { int viewLine = displayViewLine(virtualCursor, true); if (viewLine >= 0 && viewLine < (int)lineRanges.count()) { lineRanges[viewLine].dirty = true; leftBorder->update (0, lineToY(viewLine), leftBorder->width(), m_view->renderer()->fontHeight()); return true; } return false; } bool KateViewInternal::tagLines( int start, int end, bool realLines ) { return tagLines(KateTextCursor(start, 0), KateTextCursor(end, -1), realLines); } bool KateViewInternal::tagLines(KateTextCursor start, KateTextCursor end, bool realCursors) { if (realCursors) { //kdDebug()<<"realLines is true"<getVirtualLine( start.line() )); end.setLine(m_doc->getVirtualLine( end.line() )); } if (end.line() < (int)startLine()) { //kdDebug()<<"end (int)endLine()) { //kdDebug()<<"start> endLine"< start.line() || (lineRanges[z].virtualLine == start.line() && lineRanges[z].endCol >= start.col() && start.col() != -1)) && (lineRanges[z].virtualLine < end.line() || (lineRanges[z].virtualLine == end.line() && (lineRanges[z].startCol <= end.col() || end.col() == -1)))) { ret = lineRanges[z].dirty = true; //kdDebug() << "Tagged line " << lineRanges[z].line << endl; } } if (!m_view->dynWordWrap()) { int y = lineToY( start.line() ); // FIXME is this enough for when multiple lines are deleted int h = (end.line() - start.line() + 2) * m_view->renderer()->fontHeight(); if (end.line() == (int)m_doc->numVisLines() - 1) h = height(); leftBorder->update (0, y, leftBorder->width(), h); } else { // FIXME Do we get enough good info in editRemoveText to optimise this more? //bool justTagged = false; for (uint z = 0; z < lineRanges.size(); z++) { if ((lineRanges[z].virtualLine > start.line() || (lineRanges[z].virtualLine == start.line() && lineRanges[z].endCol >= start.col() && start.col() != -1)) && (lineRanges[z].virtualLine < end.line() || (lineRanges[z].virtualLine == end.line() && (lineRanges[z].startCol <= end.col() || end.col() == -1)))) { //justTagged = true; leftBorder->update (0, z * m_view->renderer()->fontHeight(), leftBorder->width(), leftBorder->height()); break; } /*else if (justTagged) { justTagged = false; leftBorder->update (0, z * m_doc->viewFont.fontHeight, leftBorder->width(), m_doc->viewFont.fontHeight); break; }*/ } } return ret; } void KateViewInternal::tagAll() { //kdDebug(13030) << "tagAll()" << endl; for (uint z = 0; z < lineRanges.size(); z++) { lineRanges[z].dirty = true; } leftBorder->updateFont(); leftBorder->update (); } void KateViewInternal::paintCursor() { if (tagLine(displayCursor)) paintText (0,0,width(), height(), true); } // Point in content coordinates void KateViewInternal::placeCursor( const QPoint& p, bool keepSelection, bool updateSelection ) { KateLineRange thisRange = yToKateLineRange(p.y()); if (thisRange.line == -1) { for (int i = (p.y() / m_view->renderer()->fontHeight()); i >= 0; i--) { thisRange = lineRanges[i]; if (thisRange.line != -1) break; } Q_ASSERT(thisRange.line != -1); } int realLine = thisRange.line; int visibleLine = thisRange.virtualLine; uint startCol = thisRange.startCol; visibleLine = kMax( 0, kMin( visibleLine, int(m_doc->numVisLines()) - 1 ) ); KateTextCursor c(realLine, 0); int x = kMin(kMax(-m_startX, p.x() - thisRange.xOffset()), lineMaxCursorX(thisRange) - thisRange.startX); m_view->renderer()->textWidth( c, startX() + x, startCol); if (updateSelection) KateViewInternal::updateSelection( c, keepSelection ); updateCursor( c ); } // Point in content coordinates bool KateViewInternal::isTargetSelected( const QPoint& p ) { KateLineRange thisRange = yToKateLineRange(p.y()); KateTextLine::Ptr l = textLine( thisRange.line ); if( !l ) return false; int col = m_view->renderer()->textPos( l, startX() + p.x() - thisRange.xOffset(), thisRange.startCol, false ); return m_view->lineColSelected( thisRange.line, col ); } //BEGIN EVENT HANDLING STUFF bool KateViewInternal::eventFilter( QObject *obj, QEvent *e ) { if (obj == m_lineScroll) { // the second condition is to make sure a scroll on the vertical bar doesn't cause a horizontal scroll ;) if (e->type() == QEvent::Wheel && m_lineScroll->minValue() != m_lineScroll->maxValue()) { wheelEvent((QWheelEvent*)e); return true; } // continue processing return QWidget::eventFilter( obj, e ); } switch( e->type() ) { case QEvent::KeyPress: { QKeyEvent *k = (QKeyEvent *)e; if (m_view->m_codeCompletion->codeCompletionVisible ()) { kdDebug (13030) << "hint around" << endl; if( k->key() == Key_Escape ) m_view->m_codeCompletion->abortCompletion(); } if ((k->key() == Qt::Key_Escape) && !m_view->config()->persistentSelection() ) { m_view->clearSelection(); return true; } else if ( !((k->state() & ControlButton) || (k->state() & AltButton)) ) { keyPressEvent( k ); return k->isAccepted(); } } break; case QEvent::DragMove: { QPoint currentPoint = ((QDragMoveEvent*) e)->pos(); QRect doNotScrollRegion( scrollMargin, scrollMargin, width() - scrollMargin * 2, height() - scrollMargin * 2 ); if ( !doNotScrollRegion.contains( currentPoint ) ) { startDragScroll(); // Keep sending move events ( (QDragMoveEvent*)e )->accept( QRect(0,0,0,0) ); } dragMoveEvent((QDragMoveEvent*)e); } break; case QEvent::DragLeave: // happens only when pressing ESC while dragging stopDragScroll(); break; case QEvent::WindowBlocked: // next focus originates from an internal dialog: // don't show the modonhd prompt m_doc->m_isasking = -1; break; default: break; } return QWidget::eventFilter( obj, e ); } void KateViewInternal::keyPressEvent( QKeyEvent* e ) { KKey key(e); bool codeComp = m_view->m_codeCompletion->codeCompletionVisible (); if (codeComp) { kdDebug (13030) << "hint around" << endl; if( e->key() == Key_Enter || e->key() == Key_Return || (key == SHIFT + Qt::Key_Return) || (key == SHIFT + Qt::Key_Enter)) { m_view->m_codeCompletion->doComplete(); e->accept(); return; } } if( !m_doc->isReadWrite() ) { e->ignore(); return; } if ((key == Qt::Key_Return) || (key == Qt::Key_Enter)) { m_view->keyReturn(); e->accept(); return; } if ((key == SHIFT + Qt::Key_Return) || (key == SHIFT + Qt::Key_Enter)) { uint ln = cursor.line(); int col = cursor.col(); KateTextLine::Ptr line = m_doc->kateTextLine( ln ); int pos = line->firstChar(); if (pos > cursor.col()) pos = cursor.col(); if (pos != -1) { while ((int)line->length() > pos && !line->getChar(pos).isLetterOrNumber() && pos < cursor.col()) ++pos; } else { pos = line->length(); // stay indented } m_doc->editStart(); m_doc->insertText( cursor.line(), line->length(), "\n" + line->string(0, pos) + line->string().right( line->length() - cursor.col() ) ); cursor.setPos(ln + 1, pos); if (col < int(line->length())) m_doc->editRemoveText(ln, col, line->length() - col); m_doc->editEnd(); updateCursor(cursor, true); updateView(); e->accept(); return; } if (key == Qt::Key_Backspace || key == SHIFT + Qt::Key_Backspace) { m_view->backspace(); e->accept(); if (codeComp) m_view->m_codeCompletion->updateBox (); return; } if (key == Qt::Key_Tab || key == SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab) { if (m_doc->invokeTabInterceptor(key)) { e->accept(); return; } else if (m_doc->configFlags() & KateDocumentConfig::cfTabIndents) { if( key == Qt::Key_Tab ) { if (m_view->hasSelection() || (m_doc->configFlags() & KateDocumentConfig::cfTabIndentsMode)) m_doc->indent( m_view, cursor.line(), 1 ); else if (m_doc->configFlags() & KateDocumentConfig::cfTabInsertsTab) m_doc->typeChars ( m_view, QString ("\t") ); else m_doc->insertIndentChars ( m_view ); e->accept(); if (codeComp) m_view->m_codeCompletion->updateBox (); return; } if (key == SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab) { m_doc->indent( m_view, cursor.line(), -1 ); e->accept(); if (codeComp) m_view->m_codeCompletion->updateBox (); return; } } } if ( !(e->state() & ControlButton) && !(e->state() & AltButton) && m_doc->typeChars ( m_view, e->text() ) ) { e->accept(); if (codeComp) m_view->m_codeCompletion->updateBox (); return; } e->ignore(); } void KateViewInternal::keyReleaseEvent( QKeyEvent* e ) { KKey key(e); if (key == SHIFT) m_shiftKeyPressed = true; else { if (m_shiftKeyPressed) { m_shiftKeyPressed = false; if (m_selChangedByUser) { QApplication::clipboard()->setSelectionMode( true ); m_view->copy(); QApplication::clipboard()->setSelectionMode( false ); m_selChangedByUser = false; } } } e->ignore(); return; } void KateViewInternal::contextMenuEvent ( QContextMenuEvent * e ) { // try to show popup menu QPoint p = e->pos(); if ( m_view->m_doc->browserView() ) { m_view->contextMenuEvent( e ); return; } if ( e->reason() == QContextMenuEvent::Keyboard ) { makeVisible( cursor, 0 ); p = cursorCoordinates(); } else if ( ! m_view->hasSelection() || m_view->config()->persistentSelection() ) placeCursor( e->pos() ); // popup is a qguardedptr now if (m_view->popup()) { m_view->popup()->popup( mapToGlobal( p ) ); e->accept (); } } void KateViewInternal::mousePressEvent( QMouseEvent* e ) { switch (e->button()) { case LeftButton: m_selChangedByUser = false; if (possibleTripleClick) { possibleTripleClick = false; m_selectionMode = Line; if ( e->state() & Qt::ShiftButton ) { updateSelection( cursor, true ); } else { m_view->selectLine( cursor ); } QApplication::clipboard()->setSelectionMode( true ); m_view->copy(); QApplication::clipboard()->setSelectionMode( false ); // Keep the line at the select anchor selected during further // mouse selection if ( selectAnchor.line() > m_view->selectStart.line() ) { // Preserve the last selected line if ( selectAnchor == m_view->selectEnd && selectAnchor.col() == 0 ) selStartCached = KateTextCursor( selectAnchor.line()-1, 0 ); else selStartCached = KateTextCursor( selectAnchor.line(), 0 ); selEndCached = m_view->selectEnd; } else { // Preserve the first selected line selStartCached = m_view->selectStart; if ( m_view->selectEnd.line() > m_view->selectStart.line() ) selEndCached = KateTextCursor( m_view->selectStart.line()+1, 0 ); else selEndCached = m_view->selectEnd; } // Set cursor to edge of selection... which edge depends on what // "direction" the selection was made in if ( m_view->selectStart < selectAnchor && selectAnchor.line() != m_view->selectStart.line() ) updateCursor( m_view->selectStart ); else updateCursor( m_view->selectEnd ); e->accept (); return; } else if (m_selectionMode == Default) { m_selectionMode = Mouse; } if ( e->state() & Qt::ShiftButton ) { if (selectAnchor.line() < 0) selectAnchor = cursor; } else { selStartCached.setLine( -1 ); // invalidate } if( !( e->state() & Qt::ShiftButton ) && isTargetSelected( e->pos() ) ) { dragInfo.state = diPending; dragInfo.start = e->pos(); } else { dragInfo.state = diNone; if ( e->state() & Qt::ShiftButton ) { placeCursor( e->pos(), true, false ); if ( selStartCached.line() >= 0 ) { if ( cursor > selEndCached ) { m_view->setSelection( selStartCached, cursor ); selectAnchor = selStartCached; } else if ( cursor < selStartCached ) { m_view->setSelection( cursor, selEndCached ); selectAnchor = selEndCached; } else { m_view->setSelection( selStartCached, cursor ); } } else { m_view->setSelection( selectAnchor, cursor ); } } else { placeCursor( e->pos() ); } scrollX = 0; scrollY = 0; m_scrollTimer.start (50); } e->accept (); break; default: e->ignore (); break; } } void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e) { switch (e->button()) { case LeftButton: m_selectionMode = Word; if ( e->state() & Qt::ShiftButton ) { KateTextCursor oldSelectStart = m_view->selectStart; KateTextCursor oldSelectEnd = m_view->selectEnd; // Now select the word under the select anchor int cs, ce; KateTextLine::Ptr l = m_doc->kateTextLine( selectAnchor.line() ); ce = selectAnchor.col(); if ( ce > 0 && m_doc->highlight()->isInWord( l->getChar( ce ) ) ) { for (; ce < l->length(); ce++ ) if ( !m_doc->highlight()->isInWord( l->getChar( ce ) ) ) break; } cs = selectAnchor.col() - 1; if ( cs < m_doc->textLine( selectAnchor.line() ).length() && m_doc->highlight()->isInWord( l->getChar( cs ) ) ) { for ( cs--; cs >= 0; cs-- ) if ( !m_doc->highlight()->isInWord( l->getChar( cs ) ) ) break; } // ...and keep it selected if (cs+1 < ce) { selStartCached = KateTextCursor( selectAnchor.line(), cs+1 ); selEndCached = KateTextCursor( selectAnchor.line(), ce ); } else { selStartCached = selectAnchor; selEndCached = selectAnchor; } // Now word select to the mouse cursor placeCursor( e->pos(), true ); } else { // first clear the selection, otherwise we run into bug #106402 // ...and set the cursor position, for the same reason (otherwise there // are *other* idiosyncrasies we can't fix without reintroducing said // bug) // Parameters: 1st false: don't redraw // 2nd false: don't emit selectionChanged signals, as // selectWord() emits this already m_view->clearSelection( false, false ); placeCursor( e->pos() ); m_view->selectWord( cursor ); if (m_view->hasSelection()) { selectAnchor = selStartCached = m_view->selectStart; selEndCached = m_view->selectEnd; } else { // if we didn't actually select anything, restore the selection mode // -- see bug #131369 (kling) m_selectionMode = Default; } } // Move cursor to end (or beginning) of selected word if (m_view->hasSelection()) { QApplication::clipboard()->setSelectionMode( true ); m_view->copy(); QApplication::clipboard()->setSelectionMode( false ); // Shift+DC before the "cached" word should move the cursor to the // beginning of the selection, not the end if (m_view->selectStart < selStartCached) updateCursor( m_view->selectStart ); else updateCursor( m_view->selectEnd ); } possibleTripleClick = true; QTimer::singleShot ( QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout()) ); scrollX = 0; scrollY = 0; m_scrollTimer.start (50); e->accept (); break; default: e->ignore (); break; } } void KateViewInternal::tripleClickTimeout() { possibleTripleClick = false; } void KateViewInternal::mouseReleaseEvent( QMouseEvent* e ) { switch (e->button()) { case LeftButton: m_selectionMode = Default; // selStartCached.setLine( -1 ); if (m_selChangedByUser) { QApplication::clipboard()->setSelectionMode( true ); m_view->copy(); QApplication::clipboard()->setSelectionMode( false ); // Set cursor to edge of selection... which edge depends on what // "direction" the selection was made in if ( m_view->selectStart < selectAnchor ) updateCursor( m_view->selectStart ); else updateCursor( m_view->selectEnd ); m_selChangedByUser = false; } if (dragInfo.state == diPending) placeCursor( e->pos(), e->state() & ShiftButton ); else if (dragInfo.state == diNone) m_scrollTimer.stop (); dragInfo.state = diNone; e->accept (); break; case MidButton: placeCursor( e->pos() ); if( m_doc->isReadWrite() ) { QApplication::clipboard()->setSelectionMode( true ); m_view->paste (); QApplication::clipboard()->setSelectionMode( false ); } e->accept (); break; default: e->ignore (); break; } } void KateViewInternal::mouseMoveEvent( QMouseEvent* e ) { if( e->state() & LeftButton ) { if (dragInfo.state == diPending) { // we had a mouse down, but haven't confirmed a drag yet // if the mouse has moved sufficiently, we will confirm QPoint p( e->pos() - dragInfo.start ); // we've left the drag square, we can start a real drag operation now if( p.manhattanLength() > KGlobalSettings::dndEventDelay() ) doDrag(); return; } else if (dragInfo.state == diDragging) { // Don't do anything after a canceled drag until the user lets go of // the mouse button! return; } mouseX = e->x(); mouseY = e->y(); scrollX = 0; scrollY = 0; int d = m_view->renderer()->fontHeight(); if (mouseX < 0) scrollX = -d; if (mouseX > width()) scrollX = d; if (mouseY < 0) { mouseY = 0; scrollY = -d; } if (mouseY > height()) { mouseY = height(); scrollY = d; } placeCursor( QPoint( mouseX, mouseY ), true ); } else { if (isTargetSelected( e->pos() ) ) { // mouse is over selected text. indicate that the text is draggable by setting // the arrow cursor as other Qt text editing widgets do if (m_mouseCursor != ArrowCursor) { setCursor( KCursor::arrowCursor() ); m_mouseCursor = ArrowCursor; } } else { // normal text cursor if (m_mouseCursor != IbeamCursor) { setCursor( KCursor::ibeamCursor() ); m_mouseCursor = IbeamCursor; } } if (m_textHintEnabled) { m_textHintTimer.start(m_textHintTimeout); m_textHintMouseX=e->x(); m_textHintMouseY=e->y(); } } } void KateViewInternal::paintEvent(QPaintEvent *e) { paintText(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height()); } void KateViewInternal::resizeEvent(QResizeEvent* e) { bool expandedHorizontally = width() > e->oldSize().width(); bool expandedVertically = height() > e->oldSize().height(); bool heightChanged = height() != e->oldSize().height(); m_madeVisible = false; if (heightChanged) { setAutoCenterLines(m_autoCenterLines, false); m_cachedMaxStartPos.setPos(-1, -1); } if (m_view->dynWordWrap()) { bool dirtied = false; for (uint i = 0; i < lineRanges.count(); i++) { // find the first dirty line // the word wrap updateView algorithm is forced to check all lines after a dirty one if (lineRanges[i].wrap || (!expandedHorizontally && (lineRanges[i].endX - lineRanges[i].startX) > width())) { dirtied = lineRanges[i].dirty = true; break; } } if (dirtied || heightChanged) { updateView(true); leftBorder->update(); } if (width() < e->oldSize().width()) { if (!m_view->wrapCursor()) { // May have to restrain cursor to new smaller width... if (cursor.col() > m_doc->lineLength(cursor.line())) { KateLineRange thisRange = currentRange(); KateTextCursor newCursor(cursor.line(), thisRange.endCol + ((width() - thisRange.xOffset() - (thisRange.endX - thisRange.startX)) / m_view->renderer()->spaceWidth()) - 1); updateCursor(newCursor); } } } } else { updateView(); if (expandedHorizontally && startX() > 0) scrollColumns(startX() - (width() - e->oldSize().width())); } if (expandedVertically) { KateTextCursor max = maxStartPos(); if (startPos() > max) scrollPos(max); } } void KateViewInternal::scrollTimeout () { if (scrollX || scrollY) { scrollLines (startPos().line() + (scrollY / (int)m_view->renderer()->fontHeight())); placeCursor( QPoint( mouseX, mouseY ), true ); } } void KateViewInternal::cursorTimeout () { m_view->renderer()->setDrawCaret(!m_view->renderer()->drawCaret()); paintCursor(); } void KateViewInternal::textHintTimeout () { m_textHintTimer.stop (); KateLineRange thisRange = yToKateLineRange(m_textHintMouseY); if (thisRange.line == -1) return; if (m_textHintMouseX> (lineMaxCursorX(thisRange) - thisRange.startX)) return; int realLine = thisRange.line; int startCol = thisRange.startCol; KateTextCursor c(realLine, 0); m_view->renderer()->textWidth( c, startX() + m_textHintMouseX, startCol); QString tmp; emit m_view->needTextHint(c.line(), c.col(), tmp); if (!tmp.isEmpty()) kdDebug(13030)<<"Hint text: "< 0) m_cursorTimer.start ( KApplication::cursorFlashTime() / 2 ); if (m_textHintEnabled) m_textHintTimer.start( m_textHintTimeout ); paintCursor(); m_doc->setActiveView( m_view ); emit m_view->gotFocus( m_view ); } void KateViewInternal::focusOutEvent (QFocusEvent *) { if( m_view->renderer() && ! m_view->m_codeCompletion->codeCompletionVisible() ) { m_cursorTimer.stop(); m_view->renderer()->setDrawCaret(true); paintCursor(); emit m_view->lostFocus( m_view ); } m_textHintTimer.stop(); } void KateViewInternal::doDrag() { dragInfo.state = diDragging; dragInfo.dragObject = new QTextDrag(m_view->selection(), this); dragInfo.dragObject->drag(); } void KateViewInternal::dragEnterEvent( QDragEnterEvent* event ) { event->accept( (QTextDrag::canDecode(event) && m_doc->isReadWrite()) || KURLDrag::canDecode(event) ); } void KateViewInternal::dragMoveEvent( QDragMoveEvent* event ) { // track the cursor to the current drop location placeCursor( event->pos(), true, false ); // important: accept action to switch between copy and move mode // without this, the text will always be copied. event->acceptAction(); } void KateViewInternal::dropEvent( QDropEvent* event ) { if ( KURLDrag::canDecode(event) ) { emit dropEventPass(event); } else if ( QTextDrag::canDecode(event) && m_doc->isReadWrite() ) { QString text; if (!QTextDrag::decode(event, text)) return; // is the source our own document? bool priv = false; if (event->source() && event->source()->inherits("KateViewInternal")) priv = m_doc->ownedView( ((KateViewInternal*)(event->source()))->m_view ); // dropped on a text selection area? bool selected = isTargetSelected( event->pos() ); if( priv && selected ) { // this is a drag that we started and dropped on our selection // ignore this case return; } // use one transaction m_doc->editStart (); // on move: remove selected text; on copy: duplicate text if ( event->action() != QDropEvent::Copy ) m_view->removeSelectedText(); m_doc->insertText( cursor.line(), cursor.col(), text ); m_doc->editEnd (); placeCursor( event->pos() ); event->acceptAction(); updateView(); } // finally finish drag and drop mode dragInfo.state = diNone; // important, because the eventFilter`s DragLeave does not occur stopDragScroll(); } //END EVENT HANDLING STUFF void KateViewInternal::clear() { cursor.setPos(0, 0); displayCursor.setPos(0, 0); } void KateViewInternal::wheelEvent(QWheelEvent* e) { if (m_lineScroll->minValue() != m_lineScroll->maxValue() && e->orientation() != Qt::Horizontal) { // React to this as a vertical event if ( ( e->state() & ControlButton ) || ( e->state() & ShiftButton ) ) { if (e->delta() > 0) scrollPrevPage(); else scrollNextPage(); } else { scrollViewLines(-((e->delta() / 120) * QApplication::wheelScrollLines())); // maybe a menu was opened or a bubbled window title is on us -> we shall erase it update(); leftBorder->update(); } } else if (columnScrollingPossible()) { QWheelEvent copy = *e; QApplication::sendEvent(m_columnScroll, ©); } else { e->ignore(); } } void KateViewInternal::startDragScroll() { if ( !m_dragScrollTimer.isActive() ) { m_dragScrollTimer.start( scrollTime ); } } void KateViewInternal::stopDragScroll() { m_dragScrollTimer.stop(); updateView(); } void KateViewInternal::doDragScroll() { QPoint p = this->mapFromGlobal( QCursor::pos() ); int dx = 0, dy = 0; if ( p.y() < scrollMargin ) { dy = p.y() - scrollMargin; } else if ( p.y() > height() - scrollMargin ) { dy = scrollMargin - (height() - p.y()); } if ( p.x() < scrollMargin ) { dx = p.x() - scrollMargin; } else if ( p.x() > width() - scrollMargin ) { dx = scrollMargin - (width() - p.x()); } dy /= 4; if (dy) scrollLines(startPos().line() + dy); if (columnScrollingPossible () && dx) scrollColumns(kMin (m_startX + dx, m_columnScroll->maxValue())); if (!dy && !dx) stopDragScroll(); } void KateViewInternal::enableTextHints(int timeout) { m_textHintTimeout=timeout; m_textHintEnabled=true; m_textHintTimer.start(timeout); } void KateViewInternal::disableTextHints() { m_textHintEnabled=false; m_textHintTimer.stop (); } bool KateViewInternal::columnScrollingPossible () { return !m_view->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maxValue() > 0); } //BEGIN EDIT STUFF void KateViewInternal::editStart() { editSessionNumber++; if (editSessionNumber > 1) return; editIsRunning = true; editOldCursor = cursor; } void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { if (editSessionNumber == 0) return; editSessionNumber--; if (editSessionNumber > 0) return; if (tagFrom && (editTagLineStart <= int(m_doc->getRealLine(startLine())))) tagAll(); else tagLines (editTagLineStart, tagFrom ? m_doc->lastLine() : editTagLineEnd, true); if (editOldCursor == cursor) updateBracketMarks(); if (m_imPreeditLength <= 0) updateView(true); if ((editOldCursor != cursor) && (m_imPreeditLength <= 0)) { m_madeVisible = false; updateCursor ( cursor, true ); } else if ( m_view == m_doc->activeView() ) { makeVisible(displayCursor, displayCursor.col()); } editIsRunning = false; } void KateViewInternal::editSetCursor (const KateTextCursor &cursor) { if (this->cursor != cursor) { this->cursor.setPos (cursor); } } //END void KateViewInternal::viewSelectionChanged () { if (!m_view->hasSelection()) { selectAnchor.setPos (-1, -1); selStartCached.setPos (-1, -1); } } //BEGIN IM INPUT STUFF void KateViewInternal::imStartEvent( QIMEvent *e ) { if ( m_doc->m_bReadOnly ) { e->ignore(); return; } if ( m_view->hasSelection() ) m_view->removeSelectedText(); m_imPreeditStartLine = cursor.line(); m_imPreeditStart = cursor.col(); m_imPreeditLength = 0; m_imPreeditSelStart = m_imPreeditStart; m_view->setIMSelectionValue( m_imPreeditStartLine, m_imPreeditStart, 0, 0, 0, true ); } void KateViewInternal::imComposeEvent( QIMEvent *e ) { if ( m_doc->m_bReadOnly ) { e->ignore(); return; } // remove old preedit if ( m_imPreeditLength > 0 ) { cursor.setPos( m_imPreeditStartLine, m_imPreeditStart ); m_doc->removeText( m_imPreeditStartLine, m_imPreeditStart, m_imPreeditStartLine, m_imPreeditStart + m_imPreeditLength ); } m_imPreeditLength = e->text().length(); m_imPreeditSelStart = m_imPreeditStart + e->cursorPos(); // update selection m_view->setIMSelectionValue( m_imPreeditStartLine, m_imPreeditStart, m_imPreeditStart + m_imPreeditLength, m_imPreeditSelStart, m_imPreeditSelStart + e->selectionLength(), true ); // insert new preedit m_doc->insertText( m_imPreeditStartLine, m_imPreeditStart, e->text() ); // update cursor cursor.setPos( m_imPreeditStartLine, m_imPreeditSelStart ); updateCursor( cursor, true ); updateView( true ); } void KateViewInternal::imEndEvent( QIMEvent *e ) { if ( m_doc->m_bReadOnly ) { e->ignore(); return; } if ( m_imPreeditLength > 0 ) { cursor.setPos( m_imPreeditStartLine, m_imPreeditStart ); m_doc->removeText( m_imPreeditStartLine, m_imPreeditStart, m_imPreeditStartLine, m_imPreeditStart + m_imPreeditLength ); } m_view->setIMSelectionValue( m_imPreeditStartLine, m_imPreeditStart, 0, 0, 0, false ); if ( e->text().length() > 0 ) { m_doc->insertText( cursor.line(), cursor.col(), e->text() ); if ( !m_cursorTimer.isActive() && KApplication::cursorFlashTime() > 0 ) m_cursorTimer.start ( KApplication::cursorFlashTime() / 2 ); updateView( true ); updateCursor( cursor, true ); } m_imPreeditStart = 0; m_imPreeditLength = 0; m_imPreeditSelStart = 0; } //END IM INPUT STUFF // kate: space-indent on; indent-width 2; replace-tabs on;