diff --git a/src/completion/katecompletiondelegate.cpp b/src/completion/katecompletiondelegate.cpp index ef8f3ab3..3bb7b9b0 100644 --- a/src/completion/katecompletiondelegate.cpp +++ b/src/completion/katecompletiondelegate.cpp @@ -1,168 +1,167 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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 "katecompletiondelegate.h" #include #include "katerenderer.h" #include "katetextline.h" #include "katedocument.h" #include "kateview.h" #include "katehighlight.h" #include "katerenderrange.h" #include "katepartdebug.h" #include "katecompletionwidget.h" #include "katecompletionmodel.h" #include "katecompletiontree.h" //Currently disable because it doesn't work #define DISABLE_INTERNAL_HIGHLIGHTING KateCompletionDelegate::KateCompletionDelegate(ExpandingWidgetModel *model, KateCompletionWidget *parent) : ExpandingDelegate(model, parent), m_cachedRow(-1) { } void KateCompletionDelegate::adjustStyle(const QModelIndex &index, QStyleOptionViewItem &option) const { if (index.column() == 0) { //We always want to use the match-color if available, also for highlighted items. ///@todo Only do this for the "current" item, for others the model is asked for the match color. uint color = model()->matchColor(index); if (color != 0) { QColor match(color); for (int a = 0; a <= 2; a++) { option.palette.setColor((QPalette::ColorGroup)a, QPalette::Highlight, match); } } } } KateRenderer *KateCompletionDelegate::renderer() const { return widget()->view()->renderer(); } KateCompletionWidget *KateCompletionDelegate::widget() const { return static_cast(const_cast(parent())); } KTextEditor::DocumentPrivate *KateCompletionDelegate::document() const { return widget()->view()->doc(); } void KateCompletionDelegate::heightChanged() const { if (parent()) { widget()->updateHeight(); } } QVector KateCompletionDelegate::createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const { QVariant highlight = model()->data(index, KTextEditor::CodeCompletionModel::HighlightingMethod); // TODO: config enable specifying no highlight as default int highlightMethod = KTextEditor::CodeCompletionModel::InternalHighlighting; if (highlight.canConvert(QVariant::Int)) { highlightMethod = highlight.toInt(); } if (highlightMethod & KTextEditor::CodeCompletionModel::CustomHighlighting) { m_currentColumnStart = 0; return highlightingFromVariantList(model()->data(index, KTextEditor::CodeCompletionModel::CustomHighlight).toList()); } #ifdef DISABLE_INTERNAL_HIGHLIGHTING return QVector(); #endif if (index.row() == m_cachedRow && highlightMethod & KTextEditor::CodeCompletionModel::InternalHighlighting) { if (index.column() < m_cachedColumnStarts.size()) { m_currentColumnStart = m_cachedColumnStarts[index.column()]; } else { qCWarning(LOG_KTE) << "Column-count does not match"; } return m_cachedHighlights; } ///@todo reset the cache when the model changed m_cachedRow = index.row(); KTextEditor::Cursor completionStart = widget()->completionRange()->start(); QString startText = document()->text(KTextEditor::Range(completionStart.line(), 0, completionStart.line(), completionStart.column())); QString lineContent = startText; int len = completionStart.column(); m_cachedColumnStarts.clear(); for (int i = 0; i < KTextEditor::CodeCompletionModel::ColumnCount; ++i) { m_cachedColumnStarts.append(len); QString text = model()->data(model()->index(index.row(), i, index.parent()), Qt::DisplayRole).toString(); lineContent += text; len += text.length(); } Kate::TextLine thisLine = Kate::TextLine(new Kate::TextLineData(lineContent)); //qCDebug(LOG_KTE) << "About to highlight with mode " << highlightMethod << " text [" << thisLine->string() << "]"; if (highlightMethod & KTextEditor::CodeCompletionModel::InternalHighlighting) { Kate::TextLine previousLine; if (completionStart.line()) { previousLine = document()->kateTextLine(completionStart.line() - 1); } else { previousLine = Kate::TextLine(new Kate::TextLineData()); } Kate::TextLine nextLine; if ((completionStart.line() + 1) < document()->lines()) { nextLine = document()->kateTextLine(completionStart.line() + 1); } else { nextLine = Kate::TextLine(new Kate::TextLineData()); } bool ctxChanged = false; document()->highlight()->doHighlight(previousLine.data(), thisLine.data(), nextLine.data(), ctxChanged); } m_currentColumnStart = m_cachedColumnStarts[index.column()]; - NormalRenderRange rr; - QVector ret = renderer()->decorationsForLine(thisLine, 0, false, &rr, option.state & QStyle::State_Selected); + QVector ret = renderer()->decorationsForLine(thisLine, 0, false, true, option.state & QStyle::State_Selected); //Remove background-colors for (QVector::iterator it = ret.begin(); it != ret.end(); ++it) { (*it).format.clearBackground(); } return ret; } diff --git a/src/render/katerenderer.cpp b/src/render/katerenderer.cpp index 4d97356a..2d9258eb 100644 --- a/src/render/katerenderer.cpp +++ b/src/render/katerenderer.cpp @@ -1,1232 +1,1227 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Mirko Stocker Copyright (C) 2003-2005 Hamish Rodda Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy Copyright (C) 2013 Andrey Matveyakin 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 "katerenderer.h" #include "katedocument.h" #include "kateconfig.h" #include "katehighlight.h" #include "kateview.h" #include "katerenderrange.h" #include "katetextlayout.h" #include "katebuffer.h" #include "inlinenotedata.h" #include "ktexteditor/inlinenote.h" #include "ktexteditor/inlinenoteprovider.h" #include "katepartdebug.h" #include #include #include #include #include #include #include // qCeil static const QChar tabChar(QLatin1Char('\t')); static const QChar spaceChar(QLatin1Char(' ')); static const QChar nbSpaceChar(0xa0); // non-breaking space KateRenderer::KateRenderer(KTextEditor::DocumentPrivate *doc, Kate::TextFolding &folding, KTextEditor::ViewPrivate *view) : m_doc(doc) , m_folding(folding) , m_view(view) , m_tabWidth(m_doc->config()->tabWidth()) , m_indentWidth(m_doc->config()->indentationWidth()) , m_caretStyle(KateRenderer::Line) , m_drawCaret(true) , m_showSelections(true) , m_showTabs(true) , m_showSpaces(KateDocumentConfig::Trailing) , m_showNonPrintableSpaces(false) , m_printerFriendly(false) , m_config(new KateRendererConfig(this)) { updateAttributes(); // initialize with a sane font height updateFontHeight(); // make the proper calculation for markerSize updateMarkerSize(); } KateRenderer::~KateRenderer() { delete m_config; } void KateRenderer::updateAttributes() { m_attributes = m_doc->highlight()->attributes(config()->schema()); } KTextEditor::Attribute::Ptr KateRenderer::attribute(uint pos) const { if (pos < (uint)m_attributes.count()) { return m_attributes[pos]; } return m_attributes[0]; } KTextEditor::Attribute::Ptr KateRenderer::specificAttribute(int context) const { if (context >= 0 && context < m_attributes.count()) { return m_attributes[context]; } return m_attributes[0]; } void KateRenderer::setDrawCaret(bool drawCaret) { m_drawCaret = drawCaret; } void KateRenderer::setCaretStyle(KateRenderer::caretStyles style) { m_caretStyle = style; } void KateRenderer::setShowTabs(bool showTabs) { m_showTabs = showTabs; } void KateRenderer::setShowSpaces(KateDocumentConfig::WhitespaceRendering showSpaces) { m_showSpaces = showSpaces; } void KateRenderer::setShowNonPrintableSpaces(const bool on) { m_showNonPrintableSpaces = on; } void KateRenderer::setTabWidth(int tabWidth) { m_tabWidth = tabWidth; } bool KateRenderer::showIndentLines() const { return m_config->showIndentationLines(); } void KateRenderer::setShowIndentLines(bool showIndentLines) { m_config->setShowIndentationLines(showIndentLines); } void KateRenderer::setIndentWidth(int indentWidth) { m_indentWidth = indentWidth; } void KateRenderer::setShowSelections(bool showSelections) { m_showSelections = showSelections; } void KateRenderer::increaseFontSizes(qreal step) { QFont f(config()->font()); f.setPointSizeF(f.pointSizeF() + step); config()->setFont(f); } void KateRenderer::resetFontSizes() { QFont f(KateRendererConfig::global()->font()); config()->setFont(f); } void KateRenderer::decreaseFontSizes(qreal step) { QFont f(config()->font()); if ((f.pointSizeF() - step) > 0) { f.setPointSizeF(f.pointSizeF() - step); } config()->setFont(f); } bool KateRenderer::isPrinterFriendly() const { return m_printerFriendly; } void KateRenderer::setPrinterFriendly(bool printerFriendly) { m_printerFriendly = printerFriendly; setShowTabs(false); setShowSpaces(KateDocumentConfig::None); setShowSelections(false); setDrawCaret(false); } void KateRenderer::paintTextLineBackground(QPainter &paint, KateLineLayoutPtr layout, int currentViewLine, int xStart, int xEnd) { if (isPrinterFriendly()) { return; } // Normal background color QColor backgroundColor(config()->backgroundColor()); // paint the current line background if we're on the current line QColor currentLineColor = config()->highlightedLineColor(); // Check for mark background int markRed = 0, markGreen = 0, markBlue = 0, markCount = 0; // Retrieve marks for this line uint mrk = m_doc->mark(layout->line()); if (mrk) { for (uint bit = 0; bit < 32; bit++) { KTextEditor::MarkInterface::MarkTypes markType = (KTextEditor::MarkInterface::MarkTypes)(1 << bit); if (mrk & markType) { QColor markColor = config()->lineMarkerColor(markType); if (markColor.isValid()) { markCount++; markRed += markColor.red(); markGreen += markColor.green(); markBlue += markColor.blue(); } } } // for } // Marks if (markCount) { markRed /= markCount; markGreen /= markCount; markBlue /= markCount; backgroundColor.setRgb( int((backgroundColor.red() * 0.9) + (markRed * 0.1)), int((backgroundColor.green() * 0.9) + (markGreen * 0.1)), int((backgroundColor.blue() * 0.9) + (markBlue * 0.1)) ); } // Draw line background paint.fillRect(0, 0, xEnd - xStart, lineHeight() * layout->viewLineCount(), backgroundColor); // paint the current line background if we're on the current line if (currentViewLine != -1) { if (markCount) { markRed /= markCount; markGreen /= markCount; markBlue /= markCount; currentLineColor.setRgb( int((currentLineColor.red() * 0.9) + (markRed * 0.1)), int((currentLineColor.green() * 0.9) + (markGreen * 0.1)), int((currentLineColor.blue() * 0.9) + (markBlue * 0.1)) ); } paint.fillRect(0, lineHeight() * currentViewLine, xEnd - xStart, lineHeight(), currentLineColor); } } void KateRenderer::paintTabstop(QPainter &paint, qreal x, qreal y) { QPen penBackup(paint.pen()); QPen pen(config()->tabMarkerColor()); pen.setWidthF(qMax(1.0, spaceWidth() / 10.0)); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, false); int dist = spaceWidth() * 0.3; QPoint points[8]; points[0] = QPoint(x - dist, y - dist); points[1] = QPoint(x, y); points[2] = QPoint(x, y); points[3] = QPoint(x - dist, y + dist); x += spaceWidth() / 3.0; points[4] = QPoint(x - dist, y - dist); points[5] = QPoint(x, y); points[6] = QPoint(x, y); points[7] = QPoint(x - dist, y + dist); paint.drawLines(points, 4); paint.setPen(penBackup); } void KateRenderer::paintSpace(QPainter &paint, qreal x, qreal y) { QPen penBackup(paint.pen()); QPen pen(config()->tabMarkerColor()); pen.setWidthF(m_markerSize); pen.setCapStyle(Qt::RoundCap); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, true); paint.drawPoint(QPointF(x, y)); paint.setPen(penBackup); } void KateRenderer::paintNonBreakSpace(QPainter &paint, qreal x, qreal y) { QPen penBackup(paint.pen()); QPen pen(config()->tabMarkerColor()); pen.setWidthF(qMax(1.0, spaceWidth() / 10.0)); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, false); const int height = fontHeight(); const int width = spaceWidth(); QPoint points[6]; points[0] = QPoint(x + width / 10, y + height / 4); points[1] = QPoint(x + width / 10, y + height / 3); points[2] = QPoint(x + width / 10, y + height / 3); points[3] = QPoint(x + width - width / 10, y + height / 3); points[4] = QPoint(x + width - width / 10, y + height / 3); points[5] = QPoint(x + width - width / 10, y + height / 4); paint.drawLines(points, 3); paint.setPen(penBackup); } void KateRenderer::paintNonPrintableSpaces(QPainter &paint, qreal x, qreal y, const QChar &chr) { paint.save(); QPen pen(config()->spellingMistakeLineColor()); pen.setWidthF(qMax(1.0, spaceWidth() * 0.1)); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, false); const int height = fontHeight(); const int width = config()->fontMetrics().width(chr); const int offset = spaceWidth() * 0.1; QPoint points[8]; points[0] = QPoint(x - offset, y + offset); points[1] = QPoint(x + width + offset, y + offset); points[2] = QPoint(x + width + offset, y + offset); points[3] = QPoint(x + width + offset, y - height - offset); points[4] = QPoint(x + width + offset, y - height - offset); points[5] = QPoint(x - offset, y - height - offset); points[6] = QPoint(x - offset, y - height - offset); points[7] = QPoint(x - offset, y + offset); paint.drawLines(points, 4); paint.restore(); } void KateRenderer::paintIndentMarker(QPainter &paint, uint x, uint y /*row*/) { QPen penBackup(paint.pen()); QPen myPen(config()->indentationLineColor()); static const QVector dashPattern = QVector() << 1 << 1; myPen.setDashPattern(dashPattern); if (y % 2) { myPen.setDashOffset(1); } paint.setPen(myPen); const int height = fontHeight(); const int top = 0; const int bottom = height - 1; QPainter::RenderHints renderHints = paint.renderHints(); paint.setRenderHints(renderHints, false); paint.drawLine(x + 2, top, x + 2, bottom); paint.setRenderHints(renderHints, true); paint.setPen(penBackup); } static bool rangeLessThanForRenderer(const Kate::TextRange *a, const Kate::TextRange *b) { // compare Z-Depth first // smaller Z-Depths should win! if (a->zDepth() > b->zDepth()) { return true; } else if (a->zDepth() < b->zDepth()) { return false; } // end of a > end of b? if (a->end().toCursor() > b->end().toCursor()) { return true; } // if ends are equal, start of a < start of b? if (a->end().toCursor() == b->end().toCursor()) { return a->start().toCursor() < b->start().toCursor(); } return false; } -QVector KateRenderer::decorationsForLine(const Kate::TextLine &textLine, int line, bool selectionsOnly, KateRenderRange *completionHighlight, bool completionSelected) const +QVector KateRenderer::decorationsForLine(const Kate::TextLine &textLine, int line, bool selectionsOnly, bool completionHighlight, bool completionSelected) const { - QVector newHighlight; + // limit number of attributes we can highlight in reasonable time + const int limitOfRanges = 1024; + auto rangesWithAttributes = m_doc->buffer().rangesForLine(line, m_printerFriendly ? nullptr : m_view, true); + if (rangesWithAttributes.size() > limitOfRanges) { + rangesWithAttributes.clear(); + } + const auto &al = (textLine->attributesList().size() > limitOfRanges) ? QVector() : textLine->attributesList(); // Don't compute the highlighting if there isn't going to be any highlighting - QList rangesWithAttributes = m_doc->buffer().rangesForLine(line, m_printerFriendly ? nullptr : m_view, true); - if (selectionsOnly || !textLine->attributesList().isEmpty() || !rangesWithAttributes.isEmpty()) { - RenderRangeList renderRanges; + if (!(selectionsOnly || !al.isEmpty() || !rangesWithAttributes.isEmpty())) { + return QVector(); + } - // Add the inbuilt highlighting to the list - NormalRenderRange *inbuiltHighlight = new NormalRenderRange(); - const QVector &al = textLine->attributesList(); + // Add the inbuilt highlighting to the list + RenderRangeVector renderRanges; + if (!al.isEmpty()) { + auto ¤tRange = renderRanges.pushNewRange(); for (int i = 0; i < al.count(); ++i) if (al[i].length > 0 && al[i].attributeValue > 0) { - inbuiltHighlight->addRange(new KTextEditor::Range(KTextEditor::Cursor(line, al[i].offset), al[i].length), specificAttribute(al[i].attributeValue)); + currentRange.addRange(KTextEditor::Range(KTextEditor::Cursor(line, al[i].offset), al[i].length), specificAttribute(al[i].attributeValue)); } - renderRanges.append(inbuiltHighlight); - - if (!completionHighlight) { - // check for dynamic hl stuff - const QSet *rangesMouseIn = m_view ? m_view->rangesMouseIn() : nullptr; - const QSet *rangesCaretIn = m_view ? m_view->rangesCaretIn() : nullptr; - bool anyDynamicHlsActive = m_view && (!rangesMouseIn->empty() || !rangesCaretIn->empty()); - - // sort all ranges, we want that the most specific ranges win during rendering, multiple equal ranges are kind of random, still better than old smart rangs behavior ;) - std::sort(rangesWithAttributes.begin(), rangesWithAttributes.end(), rangeLessThanForRenderer); - - // loop over all ranges - for (int i = 0; i < rangesWithAttributes.size(); ++i) { - // real range - Kate::TextRange *kateRange = rangesWithAttributes[i]; - - // calculate attribute, default: normal attribute - KTextEditor::Attribute::Ptr attribute = kateRange->attribute(); - if (anyDynamicHlsActive) { - // check mouse in - if (KTextEditor::Attribute::Ptr attributeMouseIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)) { - if (rangesMouseIn->contains(kateRange)) { - attribute = attributeMouseIn; - } - } + } - // check caret in - if (KTextEditor::Attribute::Ptr attributeCaretIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateCaretIn)) { - if (rangesCaretIn->contains(kateRange)) { - attribute = attributeCaretIn; - } + if (!completionHighlight) { + // check for dynamic hl stuff + const QSet *rangesMouseIn = m_view ? m_view->rangesMouseIn() : nullptr; + const QSet *rangesCaretIn = m_view ? m_view->rangesCaretIn() : nullptr; + bool anyDynamicHlsActive = m_view && (!rangesMouseIn->empty() || !rangesCaretIn->empty()); + + // sort all ranges, we want that the most specific ranges win during rendering, multiple equal ranges are kind of random, still better than old smart rangs behavior ;) + std::sort(rangesWithAttributes.begin(), rangesWithAttributes.end(), rangeLessThanForRenderer); + + // loop over all ranges + for (int i = 0; i < rangesWithAttributes.size(); ++i) { + // real range + Kate::TextRange *kateRange = rangesWithAttributes[i]; + + // calculate attribute, default: normal attribute + KTextEditor::Attribute::Ptr attribute = kateRange->attribute(); + if (anyDynamicHlsActive) { + // check mouse in + if (KTextEditor::Attribute::Ptr attributeMouseIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)) { + if (rangesMouseIn->contains(kateRange)) { + attribute = attributeMouseIn; } } - // span range - NormalRenderRange *additionaHl = new NormalRenderRange(); - additionaHl->addRange(new KTextEditor::Range(*kateRange), attribute); - renderRanges.append(additionaHl); + // check caret in + if (KTextEditor::Attribute::Ptr attributeCaretIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateCaretIn)) { + if (rangesCaretIn->contains(kateRange)) { + attribute = attributeCaretIn; + } + } } - } else { - // Add the code completion arbitrary highlight to the list - renderRanges.append(completionHighlight); + + // span range + renderRanges.pushNewRange().addRange(*kateRange, attribute); } + } - // Add selection highlighting if we're creating the selection decorations - if ((m_view && selectionsOnly && showSelections() && m_view->selection()) || (completionHighlight && completionSelected) || (m_view && m_view->blockSelection())) { - NormalRenderRange *selectionHighlight = new NormalRenderRange(); + // Add selection highlighting if we're creating the selection decorations + if ((m_view && selectionsOnly && showSelections() && m_view->selection()) || (completionHighlight && completionSelected) || (m_view && m_view->blockSelection())) { + auto ¤tRange = renderRanges.pushNewRange(); - // Set up the selection background attribute TODO: move this elsewhere, eg. into the config? - static KTextEditor::Attribute::Ptr backgroundAttribute; - if (!backgroundAttribute) { - backgroundAttribute = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); - } + // Set up the selection background attribute TODO: move this elsewhere, eg. into the config? + static KTextEditor::Attribute::Ptr backgroundAttribute; + if (!backgroundAttribute) { + backgroundAttribute = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); + } - backgroundAttribute->setBackground(config()->selectionColor()); - backgroundAttribute->setForeground(attribute(KTextEditor::dsNormal)->selectedForeground().color()); + backgroundAttribute->setBackground(config()->selectionColor()); + backgroundAttribute->setForeground(attribute(KTextEditor::dsNormal)->selectedForeground().color()); - // Create a range for the current selection - if (completionHighlight && completionSelected) { - selectionHighlight->addRange(new KTextEditor::Range(line, 0, line + 1, 0), backgroundAttribute); - } else if (m_view->blockSelection() && m_view->selectionRange().overlapsLine(line)) { - selectionHighlight->addRange(new KTextEditor::Range(m_doc->rangeOnLine(m_view->selectionRange(), line)), backgroundAttribute); - } else { - selectionHighlight->addRange(new KTextEditor::Range(m_view->selectionRange()), backgroundAttribute); - } - - renderRanges.append(selectionHighlight); - // highlighting for the vi visual modes + // Create a range for the current selection + if (completionHighlight && completionSelected) { + currentRange.addRange(KTextEditor::Range(line, 0, line + 1, 0), backgroundAttribute); + } else if (m_view->blockSelection() && m_view->selectionRange().overlapsLine(line)) { + currentRange.addRange(m_doc->rangeOnLine(m_view->selectionRange(), line), backgroundAttribute); + } else { + currentRange.addRange(m_view->selectionRange(), backgroundAttribute); } + } - KTextEditor::Cursor currentPosition, endPosition; - - // Calculate the range which we need to iterate in order to get the highlighting for just this line - if (m_view && selectionsOnly) { - if (m_view->blockSelection()) { - KTextEditor::Range subRange = m_doc->rangeOnLine(m_view->selectionRange(), line); - currentPosition = subRange.start(); - endPosition = subRange.end(); - } else { - KTextEditor::Range rangeNeeded = m_view->selectionRange() & KTextEditor::Range(line, 0, line + 1, 0); + // no render ranges, nothing to do, else we loop below endless! + if (renderRanges.isEmpty()) { + return QVector(); + } - currentPosition = qMax(KTextEditor::Cursor(line, 0), rangeNeeded.start()); - endPosition = qMin(KTextEditor::Cursor(line + 1, 0), rangeNeeded.end()); - } + // Calculate the range which we need to iterate in order to get the highlighting for just this line + KTextEditor::Cursor currentPosition, endPosition; + if (m_view && selectionsOnly) { + if (m_view->blockSelection()) { + KTextEditor::Range subRange = m_doc->rangeOnLine(m_view->selectionRange(), line); + currentPosition = subRange.start(); + endPosition = subRange.end(); } else { - currentPosition = KTextEditor::Cursor(line, 0); - endPosition = KTextEditor::Cursor(line + 1, 0); + KTextEditor::Range rangeNeeded = m_view->selectionRange() & KTextEditor::Range(line, 0, line + 1, 0); + + currentPosition = qMax(KTextEditor::Cursor(line, 0), rangeNeeded.start()); + endPosition = qMin(KTextEditor::Cursor(line + 1, 0), rangeNeeded.end()); } + } else { + currentPosition = KTextEditor::Cursor(line, 0); + endPosition = KTextEditor::Cursor(line + 1, 0); + } - // Main iterative loop. This walks through each set of highlighting ranges, and stops each - // time the highlighting changes. It then creates the corresponding QTextLayout::FormatRanges. - while (currentPosition < endPosition) { - renderRanges.advanceTo(currentPosition); + // Main iterative loop. This walks through each set of highlighting ranges, and stops each + // time the highlighting changes. It then creates the corresponding QTextLayout::FormatRanges. + QVector newHighlight; + while (currentPosition < endPosition) { + renderRanges.advanceTo(currentPosition); - if (!renderRanges.hasAttribute()) { - // No attribute, don't need to create a FormatRange for this text range - currentPosition = renderRanges.nextBoundary(); - continue; - } + if (!renderRanges.hasAttribute()) { + // No attribute, don't need to create a FormatRange for this text range + currentPosition = renderRanges.nextBoundary(); + continue; + } - KTextEditor::Cursor nextPosition = renderRanges.nextBoundary(); + KTextEditor::Cursor nextPosition = renderRanges.nextBoundary(); - // Create the format range and populate with the correct start, length and format info - QTextLayout::FormatRange fr; - fr.start = currentPosition.column(); + // Create the format range and populate with the correct start, length and format info + QTextLayout::FormatRange fr; + fr.start = currentPosition.column(); - if (nextPosition < endPosition || endPosition.line() <= line) { - fr.length = nextPosition.column() - currentPosition.column(); + if (nextPosition < endPosition || endPosition.line() <= line) { + fr.length = nextPosition.column() - currentPosition.column(); - } else { - // +1 to force background drawing at the end of the line when it's warranted - fr.length = textLine->length() - currentPosition.column() + 1; - } + } else { + // +1 to force background drawing at the end of the line when it's warranted + fr.length = textLine->length() - currentPosition.column() + 1; + } - KTextEditor::Attribute::Ptr a = renderRanges.generateAttribute(); - if (a) { - fr.format = *a; + KTextEditor::Attribute::Ptr a = renderRanges.generateAttribute(); + if (a) { + fr.format = *a; - if (selectionsOnly) { - assignSelectionBrushesFromAttribute(fr, *a); - } + if (selectionsOnly) { + assignSelectionBrushesFromAttribute(fr, *a); } - - newHighlight.append(fr); - - currentPosition = nextPosition; } - if (completionHighlight) - // Don't delete external completion render range - { - renderRanges.removeAll(completionHighlight); - } + newHighlight.append(fr); - qDeleteAll(renderRanges); + currentPosition = nextPosition; } return newHighlight; } void KateRenderer::assignSelectionBrushesFromAttribute(QTextLayout::FormatRange &target, const KTextEditor::Attribute &attribute) const { if (attribute.hasProperty(SelectedForeground)) { target.format.setForeground(attribute.selectedForeground()); } if (attribute.hasProperty(SelectedBackground)) { target.format.setBackground(attribute.selectedBackground()); } } void KateRenderer::paintTextLine(QPainter &paint, KateLineLayoutPtr range, int xStart, int xEnd, const KTextEditor::Cursor *cursor, PaintTextLineFlags flags) { Q_ASSERT(range->isValid()); // qCDebug(LOG_KTE)<<"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); // Draws the dashed underline at the start of a folded block of text. if (!(flags & SkipDrawFirstInvisibleLineUnderlined) && range->startsInvisibleBlock()) { const QPainter::RenderHints backupRenderHints = paint.renderHints(); paint.setRenderHint(QPainter::Antialiasing, false); QPen pen(config()->foldingColor()); pen.setCosmetic(true); pen.setStyle(Qt::DashLine); pen.setDashOffset(xStart); pen.setWidth(2); paint.setPen(pen); paint.drawLine(0, (lineHeight() * range->viewLineCount()) - 2, xEnd - xStart, (lineHeight() * range->viewLineCount()) - 2); paint.setRenderHints(backupRenderHints); } if (range->layout()) { bool drawSelection = m_view && m_view->selection() && showSelections() && m_view->selectionRange().overlapsLine(range->line()); // Draw selection in block selection 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 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::dsNormal)->foreground().color()); // Draw the text :) if (drawSelection) { additionalFormats = decorationsForLine(range->textLine(), range->line(), true); 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 it = range->layout()->additionalFormats(); QVectorIterator it2 = additionalFormats; for (int i = 0; i < range->viewLineCount(); ++i) { KateTextLayout line = range->viewLine(i); bool haveBackground = false; // 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(); } haveBackground = true; break; } it2.next(); } while (!haveBackground && 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(); } // Draw selection or background color outside of areas where text is rendered if (!m_printerFriendly) { bool draw = false; QBrush drawBrush; if (m_view && 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 (showSpaces() != KateDocumentConfig::None) { int spaceIndex = line.endCol() - 1; const int trailingPos = showSpaces() == KateDocumentConfig::All ? 0 : qMax(range->textLine()->lastChar(), 0); if (spaceIndex >= trailingPos) { for (; spaceIndex >= line.startCol(); --spaceIndex) { if (!text.at(spaceIndex).isSpace()) { if (showSpaces() == KateDocumentConfig::Trailing) break; else continue; } if (text.at(spaceIndex) != QLatin1Char('\t') || !showTabs()) { if (range->layout()->textOption().alignment() == Qt::AlignRight) { // Draw on left for RTL lines paintSpace(paint, line.lineLayout().cursorToX(spaceIndex) - xStart - spaceWidth() / 2.0, y); } else { paintSpace(paint, line.lineLayout().cursorToX(spaceIndex) - xStart + spaceWidth() / 2.0, y); } } } } } if (showNonPrintableSpaces()) { const int y = lineHeight() * i + fm.ascent(); static const QRegularExpression nonPrintableSpacesRegExp(QStringLiteral("[\\x{2000}-\\x{200F}\\x{2028}-\\x{202F}\\x{205F}-\\x{2064}\\x{206A}-\\x{206F}]")); QRegularExpressionMatchIterator i = nonPrintableSpacesRegExp.globalMatch(text, line.lineLayout().xToCursor(xStart)); while (i.hasNext()) { const int charIndex = i.next().capturedStart(); const int x = line.lineLayout().cursorToX(charIndex); if (x > xEnd) { break; } paintNonPrintableSpaces(paint, x - xStart, y, text[charIndex]); } } } // Draw inline notes if (!isPrinterFriendly()) { const auto inlineNotes = m_view->inlineNotes(range->line()); for (const auto& inlineNoteData: inlineNotes) { KTextEditor::InlineNote inlineNote(inlineNoteData); const int column = inlineNote.position().column(); int viewLine = range->viewLineForColumn(column); // Determine the position where to paint the note. // We start by getting the x coordinate of cursor placed to the column. qreal x = range->viewLine(viewLine).lineLayout().cursorToX(column) - xStart; int textLength = range->length(); if (column == 0 || column < textLength) { // If the note is inside text or at the beginning, then there is a hole in the text where the // note should be painted and the cursor gets placed at the right side of it. So we have to // subtract the width of the note to get to left side of the hole. x -= inlineNote.width(); } else { // If the note is outside the text, then the X coordinate is located at the end of the line. // Add appropriate amount of spaces to reach the required column. x += spaceWidth() * (column - textLength); } qreal y = lineHeight() * viewLine; // Paint the note paint.save(); paint.translate(x, y); inlineNote.provider()->paintInlineNote(inlineNote, paint); paint.restore(); } } // 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::dsNormal)->foreground().color(); } } paint.save(); paint.setRenderHint(QPainter::Antialiasing, false); 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 : break; case Half : color.setAlpha(128); paint.setPen(QPen(color, caretWidth)); 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(); } } // show word wrap marker if desirable if ((!isPrinterFriendly()) && config()->wordWrapMarker()) { const QPainter::RenderHints backupRenderHints = paint.renderHints(); paint.setRenderHint(QPainter::Antialiasing, false); paint.setPen(config()->wordWrapMarkerColor()); int _x = qreal(m_doc->config()->wordWrapAt()) * fm.width(QLatin1Char('x')) - xStart; paint.drawLine(_x, 0, _x, lineHeight()); paint.setRenderHints(backupRenderHints); } } const QFont &KateRenderer::currentFont() const { return config()->font(); } const QFontMetricsF &KateRenderer::currentFontMetrics() const { return config()->fontMetrics(); } uint KateRenderer::fontHeight() const { return m_fontHeight; } uint KateRenderer::documentHeight() const { return m_doc->lines() * lineHeight(); } int KateRenderer::lineHeight() const { return fontHeight(); } bool KateRenderer::getSelectionBounds(int line, int lineLength, int &start, int &end) const { bool hasSel = false; if (m_view->selection() && !m_view->blockSelection()) { if (m_view->lineIsSelection(line)) { start = m_view->selectionRange().start().column(); end = m_view->selectionRange().end().column(); hasSel = true; } else if (line == m_view->selectionRange().start().line()) { start = m_view->selectionRange().start().column(); end = lineLength; hasSel = true; } else if (m_view->selectionRange().containsLine(line)) { start = 0; end = lineLength; hasSel = true; } else if (line == m_view->selectionRange().end().line()) { start = 0; end = m_view->selectionRange().end().column(); hasSel = true; } } else if (m_view->lineHasSelected(line)) { start = m_view->selectionRange().start().column(); end = m_view->selectionRange().end().column(); hasSel = true; } if (start > end) { int temp = end; end = start; start = temp; } return hasSel; } void KateRenderer::updateConfig() { // update the attribute list pointer updateAttributes(); // update font height, do this before we update the view! updateFontHeight(); // trigger view update, if any! if (m_view) { m_view->updateRendererConfig(); } } void KateRenderer::updateFontHeight() { /** * ensure minimal height of one pixel to not fall in the div by 0 trap somewhere * * use a line spacing that matches the code in qt to layout/paint text * * see bug 403868 * https://github.com/qt/qtbase/blob/5.12/src/gui/text/qtextlayout.cpp (line 2270 at the moment) where the text height is set as: * * qreal height = maxY + fontHeight - minY; * * with fontHeight: * * qreal fontHeight = font.ascent() + font.descent(); */ m_fontHeight = qMax(1, qCeil(config()->fontMetrics().ascent() + config()->fontMetrics().descent())); } void KateRenderer::updateMarkerSize() { // marker size will be calculated over the value defined // on dialog m_markerSize = spaceWidth() / (3.5 - (m_doc->config()->markerSize() * 0.5)); } qreal KateRenderer::spaceWidth() const { return config()->fontMetrics().width(spaceChar); } void KateRenderer::layoutLine(KateLineLayoutPtr lineLayout, int maxwidth, bool cacheLayout) const { // if maxwidth == -1 we have no wrap Kate::TextLine textLine = lineLayout->textLine(); Q_ASSERT(textLine); QTextLayout *l = lineLayout->layout(); if (!l) { l = new QTextLayout(textLine->string(), config()->font()); } else { l->setText(textLine->string()); l->setFont(config()->font()); } l->setCacheEnabled(cacheLayout); // Initial setup of the QTextLayout. // Tab width QTextOption opt; opt.setFlags(QTextOption::IncludeTrailingSpaces); opt.setTabStop(m_tabWidth * config()->fontMetrics().width(spaceChar)); opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); // Find the first strong character in the string. // If it is an RTL character, set the base layout direction of the string to RTL. // // See http://www.unicode.org/reports/tr9/#The_Paragraph_Level (Sections P2 & P3). // Qt's text renderer ("scribe") version 4.2 assumes a "higher-level protocol" // (such as KatePart) will specify the paragraph level, so it does not apply P2 & P3 // by itself. If this ever change in Qt, the next code block could be removed. if (isLineRightToLeft(lineLayout)) { opt.setAlignment(Qt::AlignRight); opt.setTextDirection(Qt::RightToLeft); } else { opt.setAlignment(Qt::AlignLeft); opt.setTextDirection(Qt::LeftToRight); } l->setTextOption(opt); // Syntax highlighting, inbuilt and arbitrary QVector decorations = decorationsForLine(textLine, lineLayout->line()); int firstLineOffset = 0; if (!isPrinterFriendly()) { const auto inlineNotes = m_view->inlineNotes(lineLayout->line()); for (const KTextEditor::InlineNote& inlineNote: inlineNotes) { const int column = inlineNote.position().column(); int width = inlineNote.width(); // Make space for every inline note. // If it is on column 0 (at the beginning of the line), we must offset the first line. // If it is inside the text, we use absolute letter spacing to create space for it between the two letters. // If it is outside of the text, we don't have to make space for it. if (column == 0) { firstLineOffset = width; } else if (column < l->text().length()) { QTextCharFormat text_char_format; text_char_format.setFontLetterSpacing(width); text_char_format.setFontLetterSpacingType(QFont::AbsoluteSpacing); decorations.append(QTextLayout::FormatRange { column - 1, 1, text_char_format }); } } } l->setFormats(decorations); // Begin layouting l->beginLayout(); int height = 0; int shiftX = 0; bool needShiftX = (maxwidth != -1) && m_view && (m_view->config()->dynWordWrapAlignIndent() > 0); forever { QTextLine line = l->createLine(); if (!line.isValid()) { break; } if (maxwidth > 0) { line.setLineWidth(maxwidth); } // we include the leading, this must match the ::updateFontHeight code! line.setLeadingIncluded(true); line.setPosition(QPoint(line.lineNumber() ? shiftX : firstLineOffset, height)); if (needShiftX && line.width() > 0) { needShiftX = false; // Determine x offset for subsequent-lines-of-paragraph indenting int pos = textLine->nextNonSpaceChar(0); if (pos > 0) { shiftX = (int)line.cursorToX(pos); } // check for too deep shift value and limit if necessary if (shiftX > ((double)maxwidth / 100 * m_view->config()->dynWordWrapAlignIndent())) { shiftX = 0; } // if shiftX > 0, the maxwidth has to adapted maxwidth -= shiftX; lineLayout->setShiftX(shiftX); } height += lineHeight(); } l->endLayout(); lineLayout->setLayout(l); } // 1) QString::isRightToLeft() sux // 2) QString::isRightToLeft() is marked as internal (WTF?) // 3) QString::isRightToLeft() does not seem to work on my setup // 4) isStringRightToLeft() should behave much better than QString::isRightToLeft() therefore: // 5) isStringRightToLeft() kicks ass bool KateRenderer::isLineRightToLeft(KateLineLayoutPtr lineLayout) const { QString s = lineLayout->textLine()->string(); int i = 0; // borrowed from QString::updateProperties() while (i != s.length()) { QChar c = s.at(i); switch (c.direction()) { case QChar::DirL: case QChar::DirLRO: case QChar::DirLRE: return false; case QChar::DirR: case QChar::DirAL: case QChar::DirRLO: case QChar::DirRLE: return true; default: break; } i ++; } return false; #if 0 // or should we use the direction of the widget? QWidget *display = qobject_cast(view()->parent()); if (!display) { return false; } return display->layoutDirection() == Qt::RightToLeft; #endif } int KateRenderer::cursorToX(const KateTextLayout &range, int col, bool returnPastLine) const { return cursorToX(range, KTextEditor::Cursor(range.line(), col), returnPastLine); } int KateRenderer::cursorToX(const KateTextLayout &range, const KTextEditor::Cursor &pos, bool returnPastLine) const { Q_ASSERT(range.isValid()); int x; if (range.lineLayout().width() > 0) { x = (int)range.lineLayout().cursorToX(pos.column()); } else { x = 0; } int over = pos.column() - range.endCol(); if (returnPastLine && over > 0) { x += over * spaceWidth(); } return x; } KTextEditor::Cursor KateRenderer::xToCursor(const KateTextLayout &range, int x, bool returnPastLine) const { Q_ASSERT(range.isValid()); KTextEditor::Cursor ret(range.line(), range.lineLayout().xToCursor(x)); // TODO wrong for RTL lines? if (returnPastLine && range.endCol(true) == -1 && x > range.width() + range.xOffset()) { ret.setColumn(ret.column() + ((x - (range.width() + range.xOffset())) / spaceWidth())); } return ret; } void KateRenderer::setCaretOverrideColor(const QColor &color) { m_caretOverrideColor = color; } diff --git a/src/render/katerenderer.h b/src/render/katerenderer.h index 60232f27..cdac7ce6 100644 --- a/src/render/katerenderer.h +++ b/src/render/katerenderer.h @@ -1,438 +1,438 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Mirko Stocker Copyright (C) 2003-2005 Hamish Rodda Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger 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. */ #ifndef KATE_RENDERER_H #define KATE_RENDERER_H #include #include "katetextline.h" #include "katelinelayout.h" #include "kateconfig.h" #include #include #include #include #include namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } class KateRendererConfig; class KateRenderRange; namespace Kate { class TextFolding; } /** * Handles all of the work of rendering the text * (used for the views and printing) * **/ class KateRenderer { public: /** * Style of Caret * * The caret is displayed as a vertical bar (Line), a filled box * (Block), a horizontal bar (Underline), or a half-height filled * box (Half). The default is Line. * * Line Block Underline Half * * ## _ ######### _ _ * ## __| | #####| |# __| | __| | * ## / _' | ##/ _' |# / _' | / _' | * ##| (_| | #| (#| |# | (_| | #| (#| |# * ## \__,_| ##\__,_|# \__,_| ##\__,_|# * ## ######### ######### ######### */ enum caretStyles { Line, Block, Underline, Half }; /** * Constructor * @param doc document to render * @param folding folding information * @param view view which is output (0 for example for rendering to print) */ explicit KateRenderer(KTextEditor::DocumentPrivate *doc, Kate::TextFolding &folding, KTextEditor::ViewPrivate *view = nullptr); /** * Destructor */ ~KateRenderer(); KateRenderer(const KateRenderer &) = delete; KateRenderer& operator=(const KateRenderer &) = delete; /** * Returns the document to which this renderer is bound */ KTextEditor::DocumentPrivate *doc() const { return m_doc; } /** * Returns the folding info to which this renderer is bound * @return folding info */ Kate::TextFolding &folding() const { return m_folding; } /** * Returns the view to which this renderer is bound */ KTextEditor::ViewPrivate *view() const { return m_view; } /** * update the highlighting attributes * (for example after an hl change or after hl config changed) */ void updateAttributes(); /** * Determine whether the caret (text cursor) will be drawn. * @return should it be drawn? */ inline bool drawCaret() const { return m_drawCaret; } /** * Set whether the caret (text cursor) will be drawn. * @param drawCaret should caret be drawn? */ void setDrawCaret(bool drawCaret); /** * The style of the caret (text cursor) to be painted. * @return caretStyle */ inline KateRenderer::caretStyles caretStyle() const { return m_caretStyle; } /** * Set the style of caret to be painted. * @param style style to set */ void setCaretStyle(KateRenderer::caretStyles style); /** * Set a \a brush with which to override drawing of the caret. Set to QColor() to clear. */ void setCaretOverrideColor(const QColor &color); /** * @returns whether tabs should be shown (ie. a small mark * drawn to identify a tab) * @return tabs should be shown */ inline bool showTabs() const { return m_showTabs; } /** * Set whether a mark should be painted to help identifying tabs. * @param showTabs show the tabs? */ void setShowTabs(bool showTabs); /** * Set which spaces should be rendered */ void setShowSpaces(KateDocumentConfig::WhitespaceRendering showSpaces); /** * @returns whether which spaces should be rendered */ inline KateDocumentConfig::WhitespaceRendering showSpaces() const { return m_showSpaces; } /** * Update marker size shown. */ void updateMarkerSize(); /** * @returns whether non-printable spaces should be shown */ inline bool showNonPrintableSpaces() const { return m_showNonPrintableSpaces; } /** * Set whether box should be drawn around non-printable spaces */ void setShowNonPrintableSpaces(const bool showNonPrintableSpaces); /** * Sets the width of the tab. Helps performance. * @param tabWidth new tab width */ void setTabWidth(int tabWidth); /** * @returns whether indent lines should be shown * @return indent lines should be shown */ bool showIndentLines() const; /** * Set whether a guide should be painted to help identifying indent lines. * @param showLines show the indent lines? */ void setShowIndentLines(bool showLines); /** * Sets the width of the tab. Helps performance. * @param indentWidth new indent width */ void setIndentWidth(int indentWidth); /** * Show the view's selection? * @return show sels? */ inline bool showSelections() const { return m_showSelections; } /** * Set whether the view's selections should be shown. * The default is true. * @param showSelections show the selections? */ void setShowSelections(bool showSelections); /** * Change to a different font (soon to be font set?) */ void increaseFontSizes(qreal step = 1.0); void decreaseFontSizes(qreal step = 1.0); void resetFontSizes(); const QFont ¤tFont() const; const QFontMetricsF ¤tFontMetrics() const; /** * @return whether the renderer is configured to paint in a * printer-friendly fashion. */ bool isPrinterFriendly() const; /** * Configure this renderer to paint in a printer-friendly fashion. * * Sets the other options appropriately if true. */ void setPrinterFriendly(bool printerFriendly); /** * Text width & height calculation functions... */ void layoutLine(KateLineLayoutPtr line, int maxwidth = -1, bool cacheLayout = false) const; /** * This is a smaller QString::isRightToLeft(). It's also marked as internal to kate * instead of internal to Qt, so we can modify. This method searches for the first * strong character in the paragraph and then returns its direction. In case of a * line without any strong characters, the direction is forced to be LTR. * * Back in KDE 4.1 this method counted chars, which lead to unwanted side effects. * (see https://bugs.kde.org/show_bug.cgi?id=178594). As this function is internal * the way it work will probably change between releases. Be warned! */ bool isLineRightToLeft(KateLineLayoutPtr lineLayout) const; /** * The ultimate decoration creation function. * * \param selectionsOnly return decorations for selections and/or dynamic highlighting. */ - QVector decorationsForLine(const Kate::TextLine &textLine, int line, bool selectionsOnly = false, KateRenderRange *completionHighlight = nullptr, bool completionSelected = false) const; + QVector decorationsForLine(const Kate::TextLine &textLine, int line, bool selectionsOnly = false, bool completionHighlight = false, bool completionSelected = false) const; // Width calculators qreal spaceWidth() const; /** * Returns the x position of cursor \p col on the line \p range. */ int cursorToX(const KateTextLayout &range, int col, bool returnPastLine = false) const; /// \overload int cursorToX(const KateTextLayout &range, const KTextEditor::Cursor &pos, bool returnPastLine = false) const; /** * Returns the real cursor which is occupied by the specified x value, or that closest to it. * If \p returnPastLine is true, the column will be extrapolated out with the assumption * that the extra characters are spaces. */ KTextEditor::Cursor xToCursor(const KateTextLayout &range, int x, bool returnPastLine = false) const; // Font height uint fontHeight() const; // Line height int lineHeight() const; // Document height uint documentHeight() const; // Selection boundaries bool getSelectionBounds(int line, int lineLength, int &start, int &end) const; /** * Flags to customize the paintTextLine function behavior */ enum PaintTextLineFlag { /** * Skip drawing the dashed underline at the start of a folded block of text? */ SkipDrawFirstInvisibleLineUnderlined = 0x1, }; Q_DECLARE_FLAGS(PaintTextLineFlags, PaintTextLineFlag) /** * This is the ultimate function to perform painting of a text line. * * The text line is painted from the upper limit of (0,0). To move that, * apply a transform to your painter. * * @param paint painter to use * @param range layout to use in painting this line * @param xStart starting width in pixels. * @param xEnd ending width in pixels. * @param cursor position of the caret, if placed on the current line. * @param flags flags for customizing the drawing of the line */ void paintTextLine(QPainter &paint, KateLineLayoutPtr range, int xStart, int xEnd, const KTextEditor::Cursor *cursor = nullptr, PaintTextLineFlags flags = PaintTextLineFlags()); /** * Paint the background of a line * * Split off from the main @ref paintTextLine method to make it smaller. As it's being * called only once per line it shouldn't noticably affect performance and it * helps readability a LOT. * * @param paint painter to use * @param layout layout to use in painting this line * @param currentViewLine if one of the view lines is the current line, set * this to the index; otherwise -1. * @param xStart starting width in pixels. * @param xEnd ending width in pixels. */ void paintTextLineBackground(QPainter &paint, KateLineLayoutPtr layout, int currentViewLine, int xStart, int xEnd); /** * This takes an in index, and returns all the attributes for it. * For example, if you have a ktextline, and want the KTextEditor::Attribute * for a given position, do: * * attribute(myktextline->attribute(position)); */ KTextEditor::Attribute::Ptr attribute(uint pos) const; KTextEditor::Attribute::Ptr specificAttribute(int context) const; private: /** * Paint a trailing space on position (x, y). */ void paintSpace(QPainter &paint, qreal x, qreal y); /** * Paint a tab stop marker on position (x, y). */ void paintTabstop(QPainter &paint, qreal x, qreal y); /** * Paint a non-breaking space marker on position (x, y). */ void paintNonBreakSpace(QPainter &paint, qreal x, qreal y); /** * Paint non printable spaces bounding box */ void paintNonPrintableSpaces(QPainter &paint, qreal x, qreal y, const QChar &chr); /** Paint a SciTE-like indent marker. */ void paintIndentMarker(QPainter &paint, uint x, uint y); void assignSelectionBrushesFromAttribute(QTextLayout::FormatRange &target, const KTextEditor::Attribute &attribute) const; // update font height void updateFontHeight(); KTextEditor::DocumentPrivate *const m_doc; Kate::TextFolding &m_folding; KTextEditor::ViewPrivate *const m_view; // cache of config values int m_tabWidth; int m_indentWidth; int m_fontHeight; // some internal flags KateRenderer::caretStyles m_caretStyle; bool m_drawCaret; bool m_showSelections; bool m_showTabs; KateDocumentConfig::WhitespaceRendering m_showSpaces = KateDocumentConfig::None; float m_markerSize; bool m_showNonPrintableSpaces; bool m_printerFriendly; QColor m_caretOverrideColor; QVector m_attributes; /** * Configuration */ public: inline KateRendererConfig *config() const { return m_config; } void updateConfig(); private: KateRendererConfig *const m_config; }; #endif diff --git a/src/render/katerenderrange.cpp b/src/render/katerenderrange.cpp index 61be93d2..1770db90 100644 --- a/src/render/katerenderrange.cpp +++ b/src/render/katerenderrange.cpp @@ -1,214 +1,195 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Hamish Rodda * Copyright (C) 2007 Mirko Stocker * Copyright (C) 2008 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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 "katerenderrange.h" #include void mergeAttributes(KTextEditor::Attribute::Ptr base, KTextEditor::Attribute::Ptr add) { if (!add) { return; } bool hadBg = base->hasProperty(KTextEditor::Attribute::BackgroundBrush); bool hasBg = add->hasProperty(KTextEditor::Attribute::BackgroundBrush); bool hadFg = base->hasProperty(KTextEditor::Attribute::ForegroundBrush); bool hasFg = add->hasProperty(KTextEditor::Attribute::ForegroundBrush); if (((!hadBg || !hasBg) && (!hadFg || !hasFg))) { //Nothing to blend *base += *add; return; } //We eventually have to blend QBrush baseBgBrush, baseFgBrush; if (hadBg) { baseBgBrush = base->background(); } if (hadFg) { baseFgBrush = base->foreground(); } *base += *add; if (hadBg && hasBg) { QBrush bg = add->background(); if (!bg.isOpaque()) { QColor mixWithColor = bg.color(); mixWithColor.setAlpha(255); bg.setColor(KColorUtils::mix(baseBgBrush.color(), mixWithColor, bg.color().alphaF())); base->setBackground(bg); } } if (hadFg && hasFg) { QBrush fg = add->foreground(); if (!fg.isOpaque()) { QColor mixWithColor = fg.color(); mixWithColor.setAlpha(255); fg.setColor(KColorUtils::mix(baseFgBrush.color(), mixWithColor, fg.color().alphaF())); base->setForeground(fg); } } } -bool KateRenderRange::isReady() const -{ - return false; -} - NormalRenderRange::NormalRenderRange() { } -NormalRenderRange::~NormalRenderRange() -{ - QVectorIterator it = m_ranges; - while (it.hasNext()) { - delete it.next().first; - } -} - -void NormalRenderRange::addRange(KTextEditor::Range *range, KTextEditor::Attribute::Ptr attribute) +void NormalRenderRange::addRange(const KTextEditor::Range &range, KTextEditor::Attribute::Ptr attribute) { - m_ranges.append(pairRA(range, attribute)); + m_ranges.push_back(std::make_pair(range, attribute)); } KTextEditor::Cursor NormalRenderRange::nextBoundary() const { return m_nextBoundary; } bool NormalRenderRange::advanceTo(const KTextEditor::Cursor &pos) { - int index = m_currentRange; - while (index < m_ranges.count()) { - const pairRA &p = m_ranges.at(index); - KTextEditor::Range *r = p.first; - if (r->end() <= pos) { + size_t index = m_currentRange; + while (index < m_ranges.size()) { + const auto &p = m_ranges[index]; + const auto &r = p.first; + if (r.end() <= pos) { ++index; } else { bool ret = index != m_currentRange; m_currentRange = index; - if (r->start() > pos) { - m_nextBoundary = r->start(); + if (r.start() > pos) { + m_nextBoundary = r.start(); } else { - m_nextBoundary = r->end(); + m_nextBoundary = r.end(); } - if (r->contains(pos)) { + if (r.contains(pos)) { m_currentAttribute = p.second; } else { m_currentAttribute.reset(); } return ret; } } m_nextBoundary = KTextEditor::Cursor(INT_MAX, INT_MAX); m_currentAttribute.reset(); return false; } KTextEditor::Attribute::Ptr NormalRenderRange::currentAttribute() const { return m_currentAttribute; } -KTextEditor::Cursor RenderRangeList::nextBoundary() const +KTextEditor::Cursor RenderRangeVector::nextBoundary() const { KTextEditor::Cursor ret = m_currentPos; bool first = true; - foreach (KateRenderRange *r, *this) { + for (auto &r : m_ranges) { if (first) { - ret = r->nextBoundary(); + ret = r.nextBoundary(); first = false; } else { - KTextEditor::Cursor nb = r->nextBoundary(); + KTextEditor::Cursor nb = r.nextBoundary(); if (ret > nb) { ret = nb; } } } return ret; } -RenderRangeList::~RenderRangeList() +NormalRenderRange &RenderRangeVector::pushNewRange() { + m_ranges.push_back(NormalRenderRange()); + return m_ranges.back(); } -void RenderRangeList::advanceTo(const KTextEditor::Cursor &pos) +void RenderRangeVector::advanceTo(const KTextEditor::Cursor &pos) { - foreach (KateRenderRange *r, *this) { - r->advanceTo(pos); - } - - //Delete lists that are ready, else the list may get too large due to temporaries - for (int a = size() - 1; a >= 0; --a) { - KateRenderRange *r = at(a); - if (r->isReady()) { - delete r; - removeAt(a); - } + for (auto &r : m_ranges) { + r.advanceTo(pos); } } -bool RenderRangeList::hasAttribute() const +bool RenderRangeVector::hasAttribute() const { - foreach (KateRenderRange *r, *this) - if (r->currentAttribute()) { + for (auto &r : m_ranges) { + if (r.currentAttribute()) { return true; } + } return false; } -KTextEditor::Attribute::Ptr RenderRangeList::generateAttribute() const +KTextEditor::Attribute::Ptr RenderRangeVector::generateAttribute() const { KTextEditor::Attribute::Ptr a; bool ownsAttribute = false; - foreach (KateRenderRange *r, *this) { - if (KTextEditor::Attribute::Ptr a2 = r->currentAttribute()) { + for (auto &r : m_ranges) { + if (KTextEditor::Attribute::Ptr a2 = r.currentAttribute()) { if (!a) { a = a2; } else { if (!ownsAttribute) { //Make an own copy of the attribute.. ownsAttribute = true; a = new KTextEditor::Attribute(*a); } mergeAttributes(a, a2); } } } return a; } diff --git a/src/render/katerenderrange.h b/src/render/katerenderrange.h index 3fece937..0d97e616 100644 --- a/src/render/katerenderrange.h +++ b/src/render/katerenderrange.h @@ -1,75 +1,67 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Hamish Rodda * Copyright (C) 2008 David Nolden * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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. */ #ifndef KATERENDERRANGE_H #define KATERENDERRANGE_H #include #include -#include -#include +#include +#include -class KateRenderRange -{ -public: - virtual ~KateRenderRange() {} - virtual KTextEditor::Cursor nextBoundary() const = 0; - virtual bool advanceTo(const KTextEditor::Cursor &pos) = 0; - virtual KTextEditor::Attribute::Ptr currentAttribute() const = 0; - virtual bool isReady() const; -}; - -typedef QPair pairRA; - -class NormalRenderRange : public KateRenderRange +class NormalRenderRange { public: NormalRenderRange(); - ~NormalRenderRange() override; - void addRange(KTextEditor::Range *range, KTextEditor::Attribute::Ptr attribute); + void addRange(const KTextEditor::Range &range, KTextEditor::Attribute::Ptr attribute); - KTextEditor::Cursor nextBoundary() const override; - bool advanceTo(const KTextEditor::Cursor &pos) override; - KTextEditor::Attribute::Ptr currentAttribute() const override; + KTextEditor::Cursor nextBoundary() const; + bool advanceTo(const KTextEditor::Cursor &pos); + KTextEditor::Attribute::Ptr currentAttribute() const; private: - QVector m_ranges; + std::vector> m_ranges; KTextEditor::Cursor m_nextBoundary; KTextEditor::Attribute::Ptr m_currentAttribute; - int m_currentRange = 0; + size_t m_currentRange = 0; }; -class RenderRangeList : public QVector +class RenderRangeVector { public: - ~RenderRangeList(); KTextEditor::Cursor nextBoundary() const; void advanceTo(const KTextEditor::Cursor &pos); bool hasAttribute() const; KTextEditor::Attribute::Ptr generateAttribute() const; + NormalRenderRange &pushNewRange(); + bool isEmpty() const + { + return m_ranges.empty(); + } private: + std::vector m_ranges; KTextEditor::Cursor m_currentPos; }; #endif