From ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kate/part/kateviewinternal.cpp | 3496 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3496 insertions(+) create mode 100644 kate/part/kateviewinternal.cpp (limited to 'kate/part/kateviewinternal.cpp') diff --git a/kate/part/kateviewinternal.cpp b/kate/part/kateviewinternal.cpp new file mode 100644 index 000000000..96edc1a9c --- /dev/null +++ b/kate/part/kateviewinternal.cpp @@ -0,0 +1,3496 @@ +/* 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; -- cgit v1.2.1