void KateLayoutCache::updateViewCache(const KTextEditor::Cursor& startPos, int newViewLineCount, int viewLinesScrolled) { //kDebug( 13033 ) << startPos << " nvlc " << newViewLineCount << " vls " << viewLinesScrolled; int oldViewLineCount = m_textLayouts.count(); if (newViewLineCount == -1) newViewLineCount = oldViewLineCount; enableLayoutCache = true; int realLine; if (newViewLineCount == -1) realLine = m_renderer->folding().visibleLineToLine(m_renderer->folding().lineToVisibleLine(startPos.line())); else realLine = m_renderer->folding().visibleLineToLine(startPos.line()); int _viewLine = 0; if (wrap()) { // TODO check these assumptions are ok... probably they don't give much speedup anyway? if (startPos == m_startPos && m_textLayouts.count()) { _viewLine = m_textLayouts.first().viewLine(); } else if (viewLinesScrolled > 0 && viewLinesScrolled < m_textLayouts.count()) { _viewLine = m_textLayouts[viewLinesScrolled].viewLine(); } else { KateLineLayoutPtr l = line(realLine); if (l) { Q_ASSERT(l->isValid()); Q_ASSERT(l->length() >= startPos.column() || m_renderer->view()->wrapCursor()); for (; _viewLine < l->viewLineCount(); ++_viewLine) { const KateTextLayout& t = l->viewLine(_viewLine); if (t.startCol() >= startPos.column() || _viewLine == l->viewLineCount() - 1) goto foundViewLine; } // FIXME FIXME need to calculate past-end-of-line position here... Q_ASSERT(false); foundViewLine: Q_ASSERT(true); } } } m_startPos = startPos; // Move the text layouts if we've just scrolled... if (viewLinesScrolled != 0) { // loop backwards if we've just scrolled up... bool forwards = viewLinesScrolled >= 0 ? true : false; for (int z = forwards ? 0 : m_textLayouts.count() - 1; forwards ? (z < m_textLayouts.count()) : (z >= 0); forwards ? z++ : z--) { int oldZ = z + viewLinesScrolled; if (oldZ >= 0 && oldZ < m_textLayouts.count()) m_textLayouts[z] = m_textLayouts[oldZ]; } } // Resize functionality if (newViewLineCount > oldViewLineCount) { m_textLayouts.reserve(newViewLineCount); } else if (newViewLineCount < oldViewLineCount) { /* FIXME reintroduce... check we're not missing any int lastLine = m_textLayouts[newSize - 1].line(); for (int i = oldSize; i < newSize; i++) { const KateTextLayout& layout = m_textLayouts[i]; if (layout.line() > lastLine && !layout.viewLine()) layout.kateLineLayout()->layout()->setCacheEnabled(false); }*/ m_textLayouts.resize(newViewLineCount); } KateLineLayoutPtr l = line(realLine); for (int i = 0; i < newViewLineCount; ++i) { if (!l) { if (i < m_textLayouts.count()) { if (m_textLayouts[i].isValid()) m_textLayouts[i] = KateTextLayout::invalid(); } else { m_textLayouts.append(KateTextLayout::invalid()); } continue; } Q_ASSERT(l->isValid()); Q_ASSERT(_viewLine < l->viewLineCount()); if (i < m_textLayouts.count()) { bool dirty = false; if (m_textLayouts[i].line() != realLine || m_textLayouts[i].viewLine() != _viewLine || (!m_textLayouts[i].isValid() && l->viewLine(_viewLine).isValid())) dirty = true; m_textLayouts[i] = l->viewLine(_viewLine); if (dirty) m_textLayouts[i].setDirty(true); } else { m_textLayouts.append(l->viewLine(_viewLine)); } //kDebug( 13033 ) << "Laid out line " << realLine << " (" << l << "), viewLine " << _viewLine << " (" << m_textLayouts[i].kateLineLayout().data() << ")"; //m_textLayouts[i].debugOutput(); _viewLine++; if (_viewLine > l->viewLineCount() - 1) { int virtualLine = l->virtualLine() + 1; realLine = m_renderer->folding().visibleLineToLine(virtualLine); _viewLine = 0; if (realLine < m_renderer->doc()->lines()) { l = line(realLine, virtualLine); } else { l = 0; } } } enableLayoutCache = false; }
/* The ultimate line painting function. Currently missing features: - draw indent lines */ void KateRenderer::paintTextLine(QPainter& paint, KateLineLayoutPtr range, int xStart, int xEnd, const KTextEditor::Cursor* cursor) { Q_ASSERT(range->isValid()); // kDebug( 13033 )<<"KateRenderer::paintTextLine"; // font data const QFontMetricsF &fm = config()->fontMetrics(); int currentViewLine = -1; if (cursor && cursor->line() == range->line()) currentViewLine = range->viewLineForColumn(cursor->column()); paintTextLineBackground(paint, range, currentViewLine, xStart, xEnd); if (range->layout()) { bool drawSelection = m_view->selection() && showSelections() && m_view->selectionRange().overlapsLine(range->line()); // Draw selection in block selecton mode. We need 2 kinds of selections that QTextLayout::draw can't render: // - past-end-of-line selection and // - 0-column-wide selection (used to indicate where text will be typed) if (drawSelection && m_view->blockSelection()) { int selectionStartColumn = m_doc->fromVirtualColumn(range->line(), m_doc->toVirtualColumn(m_view->selectionRange().start())); int selectionEndColumn = m_doc->fromVirtualColumn(range->line(), m_doc->toVirtualColumn(m_view->selectionRange().end())); QBrush selectionBrush = config()->selectionColor(); if (selectionStartColumn != selectionEndColumn) { KateTextLayout lastLine = range->viewLine(range->viewLineCount() - 1); if (selectionEndColumn > lastLine.startCol()) { int selectionStartX = (selectionStartColumn > lastLine.startCol()) ? cursorToX(lastLine, selectionStartColumn, true) : 0; int selectionEndX = cursorToX(lastLine, selectionEndColumn, true); paint.fillRect(QRect(selectionStartX - xStart, (int)lastLine.lineLayout().y(), selectionEndX - selectionStartX, lineHeight()), selectionBrush); } } else { const int selectStickWidth = 2; KateTextLayout selectionLine = range->viewLine(range->viewLineForColumn(selectionStartColumn)); int selectionX = cursorToX(selectionLine, selectionStartColumn, true); paint.fillRect(QRect(selectionX - xStart, (int)selectionLine.lineLayout().y(), selectStickWidth, lineHeight()), selectionBrush); } } QVector<QTextLayout::FormatRange> additionalFormats; if (range->length() > 0) { // We may have changed the pen, be absolutely sure it gets set back to // normal foreground color before drawing text for text that does not // set the pen color paint.setPen(attribute(KTextEditor::HighlightInterface::dsNormal)->foreground().color()); // Draw the text :) if (drawSelection) { // FIXME toVector() may be a performance issue additionalFormats = decorationsForLine(range->textLine(), range->line(), true).toVector(); range->layout()->draw(&paint, QPoint(-xStart,0), additionalFormats); } else { range->layout()->draw(&paint, QPoint(-xStart,0)); } } QBrush backgroundBrush; bool backgroundBrushSet = false; // Loop each individual line for additional text decoration etc. QListIterator<QTextLayout::FormatRange> it = range->layout()->additionalFormats(); QVectorIterator<QTextLayout::FormatRange> it2 = additionalFormats; for (int i = 0; i < range->viewLineCount(); ++i) { KateTextLayout line = range->viewLine(i); // Determine the background to use, if any, for the end of this view line backgroundBrushSet = false; while (it2.hasNext()) { const QTextLayout::FormatRange& fr = it2.peekNext(); if (fr.start > line.endCol()) break; if (fr.start + fr.length > line.endCol()) { if (fr.format.hasProperty(QTextFormat::BackgroundBrush)) { backgroundBrushSet = true; backgroundBrush = fr.format.background(); } goto backgroundDetermined; } it2.next(); } while (it.hasNext()) { const QTextLayout::FormatRange& fr = it.peekNext(); if (fr.start > line.endCol()) break; if (fr.start + fr.length > line.endCol()) { if (fr.format.hasProperty(QTextFormat::BackgroundBrush)) { backgroundBrushSet = true; backgroundBrush = fr.format.background(); } break; } it.next(); } backgroundDetermined: // Draw selection or background color outside of areas where text is rendered if (!m_printerFriendly ) { bool draw = false; QBrush drawBrush; if (m_view->selection() && !m_view->blockSelection() && m_view->lineEndSelected(line.end(true))) { draw = true; drawBrush = config()->selectionColor(); } else if (backgroundBrushSet && !m_view->blockSelection()) { draw = true; drawBrush = backgroundBrush; } if (draw) { int fillStartX = line.endX() - line.startX() + line.xOffset() - xStart; int fillStartY = lineHeight() * i; int width= xEnd - xStart - fillStartX; int height= lineHeight(); // reverse X for right-aligned lines if (range->layout()->textOption().alignment() == Qt::AlignRight) fillStartX = 0; if (width > 0) { QRect area(fillStartX, fillStartY, width, height); paint.fillRect(area, drawBrush); } } } // Draw indent lines if (showIndentLines() && i == 0) { const qreal w = spaceWidth(); const int lastIndentColumn = range->textLine()->indentDepth(m_tabWidth); for (int x = m_indentWidth; x < lastIndentColumn; x += m_indentWidth) { paintIndentMarker(paint, x * w + 1 - xStart, range->line()); } } // draw an open box to mark non-breaking spaces const QString& text = range->textLine()->string(); int y = lineHeight() * i + fm.ascent() - fm.strikeOutPos(); int nbSpaceIndex = text.indexOf(nbSpaceChar, line.lineLayout().xToCursor(xStart)); while (nbSpaceIndex != -1 && nbSpaceIndex < line.endCol()) { int x = line.lineLayout().cursorToX(nbSpaceIndex); if (x > xEnd) break; paintNonBreakSpace(paint, x - xStart, y); nbSpaceIndex = text.indexOf(nbSpaceChar, nbSpaceIndex + 1); } // draw tab stop indicators if (showTabs()) { int tabIndex = text.indexOf(tabChar, line.lineLayout().xToCursor(xStart)); while (tabIndex != -1 && tabIndex < line.endCol()) { int x = line.lineLayout().cursorToX(tabIndex); if (x > xEnd) break; paintTabstop(paint, x - xStart + spaceWidth()/2.0, y); tabIndex = text.indexOf(tabChar, tabIndex + 1); } } // draw trailing spaces if (showTrailingSpaces()) { int spaceIndex = line.endCol() - 1; int trailingPos = range->textLine()->lastChar(); if (trailingPos < 0) trailingPos = 0; if (spaceIndex >= trailingPos) { while (spaceIndex >= line.startCol() && text.at(spaceIndex).isSpace()) { if (text.at(spaceIndex) != '\t' || !showTabs()) paintTrailingSpace(paint, line.lineLayout().cursorToX(spaceIndex) - xStart + spaceWidth()/2.0, y); --spaceIndex; } } } } // draw word-wrap-honor-indent filling if ( (range->viewLineCount() > 1) && range->shiftX() && (range->shiftX() > xStart) ) { if (backgroundBrushSet) paint.fillRect(0, lineHeight(), range->shiftX() - xStart, lineHeight() * (range->viewLineCount() - 1), backgroundBrush); paint.fillRect(0, lineHeight(), range->shiftX() - xStart, lineHeight() * (range->viewLineCount() - 1), QBrush(config()->wordWrapMarkerColor(), Qt::Dense4Pattern)); } // Draw caret if (drawCaret() && cursor && range->includesCursor(*cursor)) { int caretWidth, lineWidth = 2; QColor color; QTextLine line = range->layout()->lineForTextPosition(qMin(cursor->column(), range->length())); // Determine the caret's style caretStyles style = caretStyle(); // Make the caret the desired width if (style == Line) { caretWidth = lineWidth; } else if (line.isValid() && cursor->column() < range->length()) { caretWidth = int(line.cursorToX(cursor->column() + 1) - line.cursorToX(cursor->column())); if (caretWidth < 0) { caretWidth = -caretWidth; } } else { caretWidth = spaceWidth(); } // Determine the color if (m_caretOverrideColor.isValid()) { // Could actually use the real highlighting system for this... // would be slower, but more accurate for corner cases color = m_caretOverrideColor; } else { // search for the FormatRange that includes the cursor foreach (const QTextLayout::FormatRange &r, range->layout()->additionalFormats()) { if ((r.start <= cursor->column() ) && ( (r.start + r.length) > cursor->column())) { // check for Qt::NoBrush, as the returned color is black() and no invalid QColor QBrush foregroundBrush = r.format.foreground(); if (foregroundBrush != Qt::NoBrush) { color = r.format.foreground().color(); } break; } } // still no color found, fall back to default style if (!color.isValid()) color = attribute(KTextEditor::HighlightInterface::dsNormal)->foreground().color(); } // Clip the caret - Qt's caret has a habit of intruding onto other lines. paint.save(); paint.setClipRect(0, line.lineNumber() * lineHeight(), xEnd - xStart, lineHeight()); switch(style) { case Line : paint.setPen(QPen(color, caretWidth)); break; case Block : // use a gray caret so it's possible to see the character color.setAlpha(128); paint.setPen(QPen(color, caretWidth)); break; case Underline : paint.setClipRect(0, lineHeight() - lineWidth, xEnd - xStart, lineWidth); break; case Half : color.setAlpha(128); paint.setPen(QPen(color, caretWidth)); paint.setClipRect(0, lineHeight() / 2, xEnd - xStart, lineHeight() / 2); break; } if (cursor->column() <= range->length()) { range->layout()->drawCursor(&paint, QPoint(-xStart,0), cursor->column(), caretWidth); } else { // Off the end of the line... must be block mode. Draw the caret ourselves. const KateTextLayout& lastLine = range->viewLine(range->viewLineCount() - 1); int x = cursorToX(lastLine, KTextEditor::Cursor(range->line(), cursor->column()), true); if ((x >= xStart) && (x <= xEnd)) { paint.fillRect(x - xStart, (int)lastLine.lineLayout().y(), caretWidth, lineHeight(), color); } } paint.restore(); }