diff --git a/src/completion/expandingtree/expandingdelegate.cpp b/src/completion/expandingtree/expandingdelegate.cpp index 81abf06c..9c16a4cb 100644 --- a/src/completion/expandingtree/expandingdelegate.cpp +++ b/src/completion/expandingtree/expandingdelegate.cpp @@ -1,354 +1,350 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * * 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 "expandingdelegate.h" #include #include #include #include #include #include #include "katepartdebug.h" #include "expandingwidgetmodel.h" ExpandingDelegate::ExpandingDelegate(ExpandingWidgetModel *model, QObject *parent) : QItemDelegate(parent) , m_currentColumnStart(0) , m_model(model) { } //Gets the background-color in the way QItemDelegate does it static QColor getUsedBackgroundColor(const QStyleOptionViewItem &option, const QModelIndex &index) { if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } return option.palette.brush(cg, QPalette::Highlight).color(); } else { QVariant value = index.data(Qt::BackgroundRole); if (value.canConvert()) { return qvariant_cast(value).color(); } } return QApplication::palette().base().color(); } static void dampColors(QColor &col) { //Reduce the colors that are less visible to the eye, because they are closer to black when it comes to contrast //The most significant color to the eye is green. Then comes red, and then blue, with blue _much_ less significant. col.setBlue(0); col.setRed(col.red() / 2); } //A hack to compute more eye-focused contrast values static double readabilityContrast(QColor foreground, QColor background) { dampColors(foreground); dampColors(background); return abs(foreground.green() - background.green()) + abs(foreground.red() - background.red()) + abs(foreground.blue() - background.blue()); } void ExpandingDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optionOld, const QModelIndex &index) const { QStyleOptionViewItem option(optionOld); m_currentIndex = index; adjustStyle(index, option); if (index.column() == 0) { model()->placeExpandingWidget(index); } //Make sure the decorations are painted at the top, because the center of expanded items will be filled with the embedded widget. if (model()->isPartiallyExpanded(index) == ExpandingWidgetModel::ExpandUpwards) { m_cachedAlignment = Qt::AlignBottom; } else { m_cachedAlignment = Qt::AlignTop; } option.decorationAlignment = m_cachedAlignment; option.displayAlignment = m_cachedAlignment; //qCDebug(LOG_KTE) << "Painting row " << index.row() << ", column " << index.column() << ", internal " << index.internalPointer() << ", drawselected " << option.showDecorationSelected << ", selected " << (option.state & QStyle::State_Selected); m_cachedHighlights.clear(); m_backgroundColor = getUsedBackgroundColor(option, index); if (model()->indexIsItem(index)) { m_currentColumnStart = 0; m_cachedHighlights = createHighlighting(index, option); } /*qCDebug(LOG_KTE) << "Highlights for line:"; foreach (const QTextLayout::FormatRange& fr, m_cachedHighlights) qCDebug(LOG_KTE) << fr.start << " len " << fr.length << " format ";*/ QItemDelegate::paint(painter, option, index); ///This is a bug workaround for the Qt raster paint engine: It paints over widgets embedded into the viewport when updating due to mouse events ///@todo report to Qt Software if (model()->isExpanded(index) && model()->expandingWidget(index)) { model()->expandingWidget(index)->update(); } } -QList ExpandingDelegate::createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const +QVector ExpandingDelegate::createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const { Q_UNUSED(index); Q_UNUSED(option); - return QList(); + return QVector(); } QSize ExpandingDelegate::basicSizeHint(const QModelIndex &index) const { return QItemDelegate::sizeHint(QStyleOptionViewItem(), index); } QSize ExpandingDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize s = QItemDelegate::sizeHint(option, index); if (model()->isExpanded(index) && model()->expandingWidget(index)) { QWidget *widget = model()->expandingWidget(index); QSize widgetSize = widget->size(); s.setHeight(widgetSize.height() + s.height() + 10); //10 is the sum that must match exactly the offsets used in ExpandingWidgetModel::placeExpandingWidgets } else if (model()->isPartiallyExpanded(index)) { s.setHeight(s.height() + 30 + 10); } return s; } void ExpandingDelegate::adjustStyle(const QModelIndex &index, QStyleOptionViewItem &option) const { Q_UNUSED(index) Q_UNUSED(option) } void ExpandingDelegate::adjustRect(QRect &rect) const { if (!model()->indexIsItem(m_currentIndex) /*&& m_currentIndex.column() == 0*/) { rect.setLeft(model()->treeView()->columnViewportPosition(0)); int columnCount = model()->columnCount(m_currentIndex.parent()); if (!columnCount) { return; } rect.setRight(model()->treeView()->columnViewportPosition(columnCount - 1) + model()->treeView()->columnWidth(columnCount - 1)); } } void ExpandingDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &_rect, const QString &text) const { QRect rect(_rect); adjustRect(rect); QTextLayout layout(text, option.font, painter->device()); - QList additionalFormats; + QVector additionalFormats; int missingFormats = text.length(); for (int i = 0; i < m_cachedHighlights.count(); ++i) { if (m_cachedHighlights[i].start + m_cachedHighlights[i].length <= m_currentColumnStart) { continue; } if (additionalFormats.isEmpty()) if (i != 0 && m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length > m_currentColumnStart) { QTextLayout::FormatRange before; before.start = 0; before.length = m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length - m_currentColumnStart; before.format = m_cachedHighlights[i - 1].format; additionalFormats.append(before); } QTextLayout::FormatRange format; format.start = m_cachedHighlights[i].start - m_currentColumnStart; format.length = m_cachedHighlights[i].length; format.format = m_cachedHighlights[i].format; additionalFormats.append(format); } if (!additionalFormats.isEmpty()) { missingFormats = text.length() - (additionalFormats.back().length + additionalFormats.back().start); } if (missingFormats > 0) { QTextLayout::FormatRange format; format.start = text.length() - missingFormats; format.length = missingFormats; QTextCharFormat fm; fm.setForeground(option.palette.text()); format.format = fm; additionalFormats.append(format); } if (m_backgroundColor.isValid()) { QColor background = m_backgroundColor; // qCDebug(LOG_KTE) << text << "background:" << background.name(); //Now go through the formats, and make sure the contrast background/foreground is readable for (int a = 0; a < additionalFormats.size(); ++a) { QColor currentBackground = background; if (additionalFormats[a].format.hasProperty(QTextFormat::BackgroundBrush)) { currentBackground = additionalFormats[a].format.background().color(); } QColor currentColor = additionalFormats[a].format.foreground().color(); double currentContrast = readabilityContrast(currentColor, currentBackground); QColor invertedColor(0xffffffff - additionalFormats[a].format.foreground().color().rgb()); double invertedContrast = readabilityContrast(invertedColor, currentBackground); // qCDebug(LOG_KTE) << "values:" << invertedContrast << currentContrast << invertedColor.name() << currentColor.name(); if (invertedContrast > currentContrast) { // qCDebug(LOG_KTE) << text << additionalFormats[a].length << "switching from" << currentColor.name() << "to" << invertedColor.name(); QBrush b(additionalFormats[a].format.foreground()); b.setColor(invertedColor); additionalFormats[a].format.setForeground(b); } } } for (int a = additionalFormats.size() - 1; a >= 0; --a) { if (additionalFormats[a].length == 0) { additionalFormats.removeAt(a); } else { ///For some reason the text-formats seem to be invalid in some way, sometimes ///@todo Fix this properly, it sucks not copying everything over QTextCharFormat fm; fm.setForeground(QBrush(additionalFormats[a].format.foreground().color())); fm.setBackground(additionalFormats[a].format.background()); fm.setUnderlineStyle(additionalFormats[a].format.underlineStyle()); fm.setUnderlineColor(additionalFormats[a].format.underlineColor()); fm.setFontWeight(additionalFormats[a].format.fontWeight()); additionalFormats[a].format = fm; } } -// qCDebug(LOG_KTE) << "Highlights for text [" << text << "] col start " << m_currentColumnStart << ":"; -// foreach (const QTextLayout::FormatRange& fr, additionalFormats) -// qCDebug(LOG_KTE) << fr.start << " len " << fr.length << "foreground" << fr.format.foreground() << "background" << fr.format.background(); - - layout.setAdditionalFormats(additionalFormats); + layout.setFormats(additionalFormats); QTextOption to; to.setAlignment( static_cast(m_cachedAlignment | option.displayAlignment) ); to.setWrapMode(QTextOption::WrapAnywhere); layout.setTextOption(to); layout.beginLayout(); QTextLine line = layout.createLine(); // Leave some extra space when the text is right-aligned line.setLineWidth(rect.width() - (option.displayAlignment == Qt::AlignRight ? 8 : 0)); layout.endLayout(); //We need to do some hand layouting here if (to.alignment() & Qt::AlignBottom) { layout.draw(painter, QPoint(rect.left(), rect.bottom() - (int)line.height())); } else { layout.draw(painter, rect.topLeft()); } return; //if (painter->fontMetrics().width(text) > textRect.width() && !text.contains(QLatin1Char('\n'))) //str = elidedText(option.fontMetrics, textRect.width(), option.textElideMode, text); //qt_format_text(option.font, textRect, option.displayAlignment, str, 0, 0, 0, 0, painter); } void ExpandingDelegate::drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QPixmap &pixmap) const { if (model()->indexIsItem(m_currentIndex)) { QItemDelegate::drawDecoration(painter, option, rect, pixmap); } } void ExpandingDelegate::drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(index) //Problem: This isn't called at all, because drawBrackground is not virtual :-/ QStyle *style = model()->treeView()->style() ? model()->treeView()->style() : QApplication::style(); style->drawControl(QStyle::CE_ItemViewItem, &option, painter); } ExpandingWidgetModel *ExpandingDelegate::model() const { return m_model; } void ExpandingDelegate::heightChanged() const { } bool ExpandingDelegate::editorEvent(QEvent *event, QAbstractItemModel * /*model*/, const QStyleOptionViewItem & /*option*/, const QModelIndex &index) { if (event->type() == QEvent::MouseButtonRelease) { event->accept(); model()->setExpanded(index, !model()->isExpanded(index)); heightChanged(); return true; } else { event->ignore(); } return false; } -QList ExpandingDelegate::highlightingFromVariantList(const QList &customHighlights) const +QVector ExpandingDelegate::highlightingFromVariantList(const QList &customHighlights) const { - QList ret; + QVector ret; for (int i = 0; i + 2 < customHighlights.count(); i += 3) { if (!customHighlights[i].canConvert(QVariant::Int) || !customHighlights[i + 1].canConvert(QVariant::Int) || !customHighlights[i + 2].canConvert()) { qCWarning(LOG_KTE) << "Unable to convert triple to custom formatting."; continue; } QTextLayout::FormatRange format; format.start = customHighlights[i].toInt(); format.length = customHighlights[i + 1].toInt(); format.format = customHighlights[i + 2].value().toCharFormat(); if (!format.format.isValid()) { qCWarning(LOG_KTE) << "Format is not valid"; } ret << format; } return ret; } diff --git a/src/completion/expandingtree/expandingdelegate.h b/src/completion/expandingtree/expandingdelegate.h index 3a72bf63..656bf51d 100644 --- a/src/completion/expandingtree/expandingdelegate.h +++ b/src/completion/expandingtree/expandingdelegate.h @@ -1,86 +1,86 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * * 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 ExpandingDelegate_H #define ExpandingDelegate_H #include #include #include class ExpandingWidgetModel; class QVariant; class QStyleOptionViewItem; /** * This is a delegate that cares, together with ExpandingWidgetModel, about embedded widgets in tree-view. * */ class ExpandingDelegate : public QItemDelegate { Q_OBJECT public: explicit ExpandingDelegate(ExpandingWidgetModel *model, QObject *parent = nullptr); // Overridden to create highlighting for current index void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; // Returns the basic size-hint as reported by QItemDelegate QSize basicSizeHint(const QModelIndex &index) const; ExpandingWidgetModel *model() const; protected: //Called right before paint to allow last-minute changes to the style virtual void adjustStyle(const QModelIndex &index, QStyleOptionViewItem &option) const; void drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; virtual void drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QPixmap &pixmap) const override; //option can be changed - virtual QList createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const; + virtual QVector createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const; void adjustRect(QRect &rect) const; /** * Creates a list of FormatRanges as should be returned by createHighlighting from a list of QVariants as described in the kde header ktexteditor/codecompletionmodel.h * */ - QList highlightingFromVariantList(const QList &customHighlights) const; + QVector highlightingFromVariantList(const QList &customHighlights) const; //Called when an item was expanded/unexpanded and the height changed virtual void heightChanged() const; //Initializes the style options from the index void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const; mutable int m_currentColumnStart; //Text-offset for custom highlighting, will be applied to m_cachedHighlights(Only highlights starting after this will be used). Shoult be zero of the highlighting is not taken from kate. mutable QList m_currentColumnStarts; - mutable QList m_cachedHighlights; + mutable QVector m_cachedHighlights; mutable Qt::Alignment m_cachedAlignment; mutable QColor m_backgroundColor; mutable QModelIndex m_currentIndex; private: ExpandingWidgetModel *m_model; }; #endif diff --git a/src/completion/katecompletiondelegate.cpp b/src/completion/katecompletiondelegate.cpp index 5ab7eb6a..ef8f3ab3 100644 --- a/src/completion/katecompletiondelegate.cpp +++ b/src/completion/katecompletiondelegate.cpp @@ -1,168 +1,168 @@ /* 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(); } } -QList KateCompletionDelegate::createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const +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 QList(); + 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; - QList ret = renderer()->decorationsForLine(thisLine, 0, false, &rr, option.state & QStyle::State_Selected); + QVector ret = renderer()->decorationsForLine(thisLine, 0, false, &rr, option.state & QStyle::State_Selected); //Remove background-colors - for (QList::iterator it = ret.begin(); it != ret.end(); ++it) { + for (QVector::iterator it = ret.begin(); it != ret.end(); ++it) { (*it).format.clearBackground(); } return ret; } diff --git a/src/completion/katecompletiondelegate.h b/src/completion/katecompletiondelegate.h index 0124eb6b..3ca0fc45 100644 --- a/src/completion/katecompletiondelegate.h +++ b/src/completion/katecompletiondelegate.h @@ -1,47 +1,47 @@ /* 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. */ #ifndef KATECOMPLETIONDELEGATE_H #define KATECOMPLETIONDELEGATE_H #include "expandingtree/expandingdelegate.h" class KateRenderer; namespace KTextEditor { class DocumentPrivate; } class KateCompletionWidget; class KateCompletionDelegate : public ExpandingDelegate { public: explicit KateCompletionDelegate(ExpandingWidgetModel *model, KateCompletionWidget *parent); KateRenderer *renderer() const; KateCompletionWidget *widget() const; KTextEditor::DocumentPrivate *document() const; protected: void adjustStyle(const QModelIndex &index, QStyleOptionViewItem &option) const override; mutable int m_cachedRow; mutable QList m_cachedColumnStarts; void heightChanged() const override; - QList createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const override; + QVector createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const override; }; #endif diff --git a/src/render/katerenderer.cpp b/src/render/katerenderer.cpp index fdb29840..597c29d4 100644 --- a/src/render/katerenderer.cpp +++ b/src/render/katerenderer.cpp @@ -1,1152 +1,1151 @@ /* 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 "katepartdebug.h" #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(true) , 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::setShowTrailingSpaces(bool 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::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); setShowTrailingSpaces(false); 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::paintTrailingSpace(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; } -QList 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, KateRenderRange *completionHighlight, bool completionSelected) const { - QList newHighlight; + QVector newHighlight; // 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; // Add the inbuilt highlighting to the list NormalRenderRange *inbuiltHighlight = new NormalRenderRange(); const QVector &al = textLine->attributesList(); 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)); } 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 ;) qSort(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; } } } // span range NormalRenderRange *additionaHl = new NormalRenderRange(); additionaHl->addRange(new KTextEditor::Range(*kateRange), attribute); renderRanges.append(additionaHl); } } else { // Add the code completion arbitrary highlight to the list renderRanges.append(completionHighlight); } // 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(); // 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()); // 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 } 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); 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); 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(); // 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(); } 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; if (selectionsOnly) { assignSelectionBrushesFromAttribute(fr, *a); } } newHighlight.append(fr); currentPosition = nextPosition; } if (completionHighlight) // Don't delete external completion render range { renderRanges.removeAll(completionHighlight); } qDeleteAll(renderRanges); } 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()); } } /* 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, 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); if (range->layout()) { bool drawSelection = m_view && 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 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) { - // FIXME toVector() may be a performance issue - additionalFormats = decorationsForLine(range->textLine(), range->line(), true).toVector(); + 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 (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) != QLatin1Char('\t') || !showTabs()) { if (range->layout()->textOption().alignment() == Qt::AlignRight) { // Draw on left for RTL lines paintTrailingSpace(paint, line.lineLayout().cursorToX(spaceIndex) - xStart - spaceWidth() / 2.0, y); } else { paintTrailingSpace(paint, line.lineLayout().cursorToX(spaceIndex) - xStart + spaceWidth() / 2.0, y); } } --spaceIndex; } } } 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 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(); } } // 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()->wordWrapMarkerColor()); pen.setCosmetic(true); pen.setStyle(Qt::DashLine); pen.setDashOffset(xStart); paint.setPen(pen); paint.drawLine(0, (lineHeight() * range->viewLineCount()) - 1, xEnd - xStart, (lineHeight() * range->viewLineCount()) - 1); paint.setRenderHints(backupRenderHints); } // show word wrap marker if desirable if ((!isPrinterFriendly()) && config()->wordWrapMarker() && QFontInfo(config()->font()).fixedPitch()) { 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 attibute 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 font line spacing, this includes the leading m_fontHeight = qMax(1, qCeil(config()->fontMetrics().lineSpacing())); } 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 - l->setAdditionalFormats(decorationsForLine(textLine, lineLayout->line())); + l->setFormats(decorationsForLine(textLine, lineLayout->line())); // 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 : 0, 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 15679060..0633afa5 100644 --- a/src/render/katerenderer.h +++ b/src/render/katerenderer.h @@ -1,433 +1,433 @@ /* 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 #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(); /** * 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); /** * @returns whether trailing spaces should be shown. */ inline bool showTrailingSpaces() const { return m_showSpaces; } /** * Set whether a mark should be painted for trailing spaces. */ void setShowTrailingSpaces(bool 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); 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. */ - QList 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, KateRenderRange *completionHighlight = nullptr, 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 paintTrailingSpace(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; bool m_showSpaces; float m_markerSize; bool m_showNonPrintableSpaces; bool m_printerFriendly; QColor m_caretOverrideColor; QList m_attributes; /** * Configuration */ public: inline KateRendererConfig *config() const { return m_config; } void updateConfig(); private: KateRendererConfig *const m_config; }; #endif diff --git a/src/view/kateviewhelpers.cpp b/src/view/kateviewhelpers.cpp index 25f4395c..0308242b 100644 --- a/src/view/kateviewhelpers.cpp +++ b/src/view/kateviewhelpers.cpp @@ -1,2892 +1,2892 @@ /* This file is part of the KDE libraries Copyright (C) 2008, 2009 Matthew Woehlke Copyright (C) 2007 Mirko Stocker Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 Christoph Cullmann Copyright (C) 2011 Svyatoslav Kuzmich Copyright (C) 2012 KÃ¥re Särs (Minimap) 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 "kateviewhelpers.h" #include "katecmd.h" #include #include #include #include "kateconfig.h" #include "katedocument.h" #include #include "katerenderer.h" #include "kateview.h" #include "kateviewinternal.h" #include "katelayoutcache.h" #include "katetextlayout.h" #include "kateglobal.h" #include "katepartdebug.h" #include "katecommandrangeexpressionparser.h" #include "kateabstractinputmode.h" #include "katetextpreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //BEGIN KateMessageLayout KateMessageLayout::KateMessageLayout(QWidget *parent) : QLayout(parent) { } KateMessageLayout::~KateMessageLayout() { while (QLayoutItem *item = takeAt(0)) delete item; } void KateMessageLayout::addItem(QLayoutItem *item) { Q_ASSERT(false); add(item, KTextEditor::Message::CenterInView); } void KateMessageLayout::addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos) { add(new QWidgetItem(widget), pos); } int KateMessageLayout::count() const { return m_items.size(); } QLayoutItem *KateMessageLayout::itemAt(int index) const { if (index < 0 || index >= m_items.size()) return nullptr; return m_items[index]->item; } void KateMessageLayout::setGeometry(const QRect &rect) { QLayout::setGeometry(rect); const int s = spacing(); const QRect adjustedRect = rect.adjusted(s, s, -s, -s); for (auto wrapper : m_items) { QLayoutItem *item = wrapper->item; auto position = wrapper->position; if (position == KTextEditor::Message::TopInView) { const QRect r(adjustedRect.width() - item->sizeHint().width(), s, item->sizeHint().width(), item->sizeHint().height()); item->setGeometry(r); } else if (position == KTextEditor::Message::BottomInView) { const QRect r(adjustedRect.width() - item->sizeHint().width(), adjustedRect.height() - item->sizeHint().height(), item->sizeHint().width(), item->sizeHint().height()); item->setGeometry(r); } else if (position == KTextEditor::Message::CenterInView) { QRect r(0, 0, item->sizeHint().width(), item->sizeHint().height()); r.moveCenter(adjustedRect.center()); item->setGeometry(r); } else { Q_ASSERT_X(false, "setGeometry", "Only TopInView, CenterInView, and BottomInView are supported."); } } } QSize KateMessageLayout::sizeHint() const { return QSize(); } QLayoutItem *KateMessageLayout::takeAt(int index) { if (index >= 0 && index < m_items.size()) { ItemWrapper *layoutStruct = m_items.takeAt(index); return layoutStruct->item; } return nullptr; } void KateMessageLayout::add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos) { m_items.push_back(new ItemWrapper(item, pos)); } //END KateMessageLayout //BEGIN KateScrollBar static const int s_lineWidth = 100; static const int s_pixelMargin = 8; static const int s_linePixelIncLimit = 6; const unsigned char KateScrollBar::characterOpacity[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 15 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, // <- 31 0, 125, 41, 221, 138, 195, 218, 21, 142, 142, 137, 137, 97, 87, 87, 140, // <- 47 223, 164, 183, 190, 191, 193, 214, 158, 227, 216, 103, 113, 146, 140, 146, 149, // <- 63 248, 204, 240, 174, 217, 197, 178, 205, 209, 176, 168, 211, 160, 246, 238, 218, // <- 79 195, 229, 227, 196, 167, 212, 188, 238, 197, 169, 189, 158, 21, 151, 115, 90, // <- 95 15, 192, 209, 153, 208, 187, 162, 221, 183, 149, 161, 191, 146, 203, 167, 182, // <- 111 208, 203, 139, 166, 158, 167, 157, 189, 164, 179, 156, 167, 145, 166, 109, 0, // <- 127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 159 0, 125, 184, 187, 146, 201, 127, 203, 89, 194, 156, 141, 117, 87, 202, 88, // <- 175 115, 165, 118, 121, 85, 190, 236, 87, 88, 111, 151, 140, 194, 191, 203, 148, // <- 191 215, 215, 222, 224, 223, 234, 230, 192, 208, 208, 216, 217, 187, 187, 194, 195, // <- 207 228, 255, 228, 228, 235, 239, 237, 150, 255, 222, 222, 229, 232, 180, 197, 225, // <- 223 208, 208, 216, 217, 212, 230, 218, 170, 202, 202, 211, 204, 156, 156, 165, 159, // <- 239 214, 194, 197, 197, 206, 206, 201, 132, 214, 183, 183, 192, 187, 195, 227, 198 }; KateScrollBar::KateScrollBar(Qt::Orientation orientation, KateViewInternal *parent) : QScrollBar(orientation, parent->m_view) , m_middleMouseDown(false) , m_leftMouseDown(false) , m_view(parent->m_view) , m_doc(parent->doc()) , m_viewInternal(parent) , m_textPreview(nullptr) , m_showMarks(false) , m_showMiniMap(false) , m_miniMapAll(true) , m_miniMapWidth(40) , m_grooveHeight(height()) , m_linesModified(0) { connect(this, SIGNAL(valueChanged(int)), this, SLOT(sliderMaybeMoved(int))); connect(m_doc, SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(marksChanged())); m_updateTimer.setInterval(300); m_updateTimer.setSingleShot(true); QTimer::singleShot(10, this, SLOT(updatePixmap())); // track mouse for text preview widget setMouseTracking(orientation == Qt::Vertical); // setup text preview timer m_delayTextPreviewTimer.setSingleShot(true); m_delayTextPreviewTimer.setInterval(250); connect(&m_delayTextPreviewTimer, SIGNAL(timeout()), this, SLOT(showTextPreview())); } KateScrollBar::~KateScrollBar() { delete m_textPreview; } void KateScrollBar::setShowMiniMap(bool b) { if (b && !m_showMiniMap) { connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_doc, SIGNAL(textChanged(KTextEditor::Document*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_view, SIGNAL(delayedUpdateOfView()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updatePixmap()), Qt::UniqueConnection); connect(&(m_view->textFolding()), SIGNAL(foldingRangesChanged()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); } else if (!b) { disconnect(&m_updateTimer); } m_showMiniMap = b; updateGeometry(); update(); } QSize KateScrollBar::sizeHint() const { if (m_showMiniMap) { return QSize(m_miniMapWidth, QScrollBar::sizeHint().height()); } return QScrollBar::sizeHint(); } int KateScrollBar::minimapYToStdY(int y) { // Check if the minimap fills the whole scrollbar if (m_stdGroveRect.height() == m_mapGroveRect.height()) { return y; } // check if y is on the step up/down if ((y < m_stdGroveRect.top()) || (y > m_stdGroveRect.bottom())) { return y; } if (y < m_mapGroveRect.top()) { return m_stdGroveRect.top() + 1; } if (y > m_mapGroveRect.bottom()) { return m_stdGroveRect.bottom() - 1; } // check for div/0 if (m_mapGroveRect.height() == 0) { return y; } int newY = (y - m_mapGroveRect.top()) * m_stdGroveRect.height() / m_mapGroveRect.height(); newY += m_stdGroveRect.top(); return newY; } void KateScrollBar::mousePressEvent(QMouseEvent *e) { // delete text preview hideTextPreview(); if (e->button() == Qt::MidButton) { m_middleMouseDown = true; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = true; } if (m_showMiniMap) { if (m_leftMouseDown) { // if we show the minimap left-click jumps directly to the selected position int newVal = (e->pos().y()-m_mapGroveRect.top()) / (double)m_mapGroveRect.height() * (double)(maximum()+pageStep()) - pageStep()/2; newVal = qBound(0, newVal, maximum()); setSliderPosition(newVal); } QMouseEvent eMod(QEvent::MouseButtonPress, QPoint(6, minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mousePressEvent(&eMod); } else { QScrollBar::mousePressEvent(e); } m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); redrawMarks(); } void KateScrollBar::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::MidButton) { m_middleMouseDown = false; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = false; } redrawMarks(); if (m_leftMouseDown || m_middleMouseDown) { QToolTip::hideText(); } if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseButtonRelease, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseReleaseEvent(&eMod); } else { QScrollBar::mouseReleaseEvent(e); } } void KateScrollBar::mouseMoveEvent(QMouseEvent *e) { if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseMove, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseMoveEvent(&eMod); } else { QScrollBar::mouseMoveEvent(e); } if (e->buttons() & (Qt::LeftButton | Qt::MidButton)) { redrawMarks(); // current line tool tip m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } showTextPreviewDelayed(); } void KateScrollBar::leaveEvent(QEvent *event) { hideTextPreview(); QAbstractSlider::leaveEvent(event); } bool KateScrollBar::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object) if (m_textPreview && event->type() == QEvent::WindowDeactivate) { // We need hide the scrollbar TextPreview widget hideTextPreview(); } return false; } void KateScrollBar::paintEvent(QPaintEvent *e) { if (m_doc->marks().size() != m_lines.size()) { recomputeMarksPositions(); } if (m_showMiniMap) { miniMapPaintEvent(e); } else { normalPaintEvent(e); } } void KateScrollBar::showTextPreviewDelayed() { if (!m_textPreview) { if (!m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.start(); } } else { showTextPreview(); } } void KateScrollBar::showTextPreview() { if (orientation() != Qt::Vertical || isSliderDown() || (minimum() == maximum()) || !m_view->config()->scrollBarPreview()) { return; } QRect grooveRect; if (m_showMiniMap) { // If mini-map is shown, the height of the map might not be the whole height grooveRect = m_mapGroveRect; } else { QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); } if (m_view->config()->scrollPastEnd()) { // Adjust the grove size to accommodate the added pageStep at the bottom int adjust = pageStep()*grooveRect.height() / (maximum() + pageStep() - minimum()); grooveRect.adjust(0,0,0, -adjust); } const QPoint cursorPos = mapFromGlobal(QCursor::pos()); if (grooveRect.contains(cursorPos)) { if (!m_textPreview) { m_textPreview = new KateTextPreview(m_view); m_textPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_textPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window qApp->installEventFilter(this); } const qreal posInPercent = static_cast(cursorPos.y() - grooveRect.top()) / grooveRect.height(); const qreal startLine = posInPercent * m_view->textFolding().visibleLines(); m_textPreview->resize(m_view->width() / 2, m_view->height() / 5); const int xGlobal = mapToGlobal(QPoint(0, 0)).x(); const int yGlobal = qMin(mapToGlobal(QPoint(0, height())).y() - m_textPreview->height(), qMax(mapToGlobal(QPoint(0, 0)).y(), mapToGlobal(cursorPos).y() - m_textPreview->height() / 2)); m_textPreview->move(xGlobal - m_textPreview->width(), yGlobal); m_textPreview->setLine(startLine); m_textPreview->setCenterView(true); m_textPreview->setScaleFactor(0.8); m_textPreview->raise(); m_textPreview->show(); } else { hideTextPreview(); } } void KateScrollBar::hideTextPreview() { if (m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.stop(); } qApp->removeEventFilter(this); delete m_textPreview; } // This function is optimized for bing called in sequence. const QColor KateScrollBar::charColor(const QVector &attributes, int &attributeIndex, - const QList &decorations, + const QVector &decorations, const QColor &defaultColor, int x, QChar ch) { QColor color = defaultColor; bool styleFound = false; // Query the decorations, that is, things like search highlighting, or the // KDevelop DUChain highlighting, for a color to use foreach (const QTextLayout::FormatRange &range, decorations) { if (range.start <= x && range.start + range.length > x) { // If there's a different background color set (search markers, ...) // use that, otherwise use the foreground color. if (range.format.hasProperty(QTextFormat::BackgroundBrush)) { color = range.format.background().color(); } else { color = range.format.foreground().color(); } styleFound = true; break; } } // If there's no decoration set for the current character (this will mostly be the case for // plain Kate), query the styles, that is, the default kate syntax highlighting. if (!styleFound) { // go to the block containing x while ((attributeIndex < attributes.size()) && ((attributes[attributeIndex].offset + attributes[attributeIndex].length) < x)) { ++attributeIndex; } if ((attributeIndex < attributes.size()) && (x < attributes[attributeIndex].offset + attributes[attributeIndex].length)) { color = m_view->renderer()->attribute(attributes[attributeIndex].attributeValue)->foreground().color(); } } // Query how much "blackness" the character has. // This causes for example a dot or a dash to appear less intense // than an A or similar. // This gives the pixels created a bit of structure, which makes it look more // like real text. color.setAlpha((ch.unicode() < 256) ? characterOpacity[ch.unicode()] : 222); return color; } void KateScrollBar::updatePixmap() { //QTime time; //time.start(); if (!m_showMiniMap) { // make sure no time is wasted if the option is disabled return; } // For performance reason, only every n-th line will be drawn if the widget is // sufficiently small compared to the amount of lines in the document. int docLineCount = m_view->textFolding().visibleLines(); int pixmapLineCount = docLineCount; if (m_view->config()->scrollPastEnd()) { pixmapLineCount += pageStep(); } int pixmapLinesUnscaled = pixmapLineCount; if (m_grooveHeight < 5) { m_grooveHeight = 5; } int lineDivisor = pixmapLinesUnscaled / m_grooveHeight; if (lineDivisor < 1) { lineDivisor = 1; } int charIncrement = 1; int lineIncrement = 1; if ((m_grooveHeight > 10) && (pixmapLineCount >= m_grooveHeight * 2)) { charIncrement = pixmapLineCount / m_grooveHeight; while (charIncrement > s_linePixelIncLimit) { lineIncrement++; pixmapLineCount = pixmapLinesUnscaled / lineIncrement; charIncrement = pixmapLineCount / m_grooveHeight; } pixmapLineCount /= charIncrement; } int pixmapLineWidth = s_pixelMargin + s_lineWidth / charIncrement; //qCDebug(LOG_KTE) << "l" << lineIncrement << "c" << charIncrement << "d" << lineDivisor; //qCDebug(LOG_KTE) << "pixmap" << pixmapLineCount << pixmapLineWidth << "docLines" << m_view->textFolding().visibleLines() << "height" << m_grooveHeight; const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor defaultTextColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor selectionBgColor = m_view->renderer()->config()->selectionColor(); QColor modifiedLineColor = m_view->renderer()->config()->modifiedLineColor(); QColor savedLineColor = m_view->renderer()->config()->savedLineColor(); // move the modified line color away from the background color modifiedLineColor.setHsv(modifiedLineColor.hue(), 255, 255 - backgroundColor.value() / 3); savedLineColor.setHsv(savedLineColor.hue(), 100, 255 - backgroundColor.value() / 3); // increase dimensions by ratio m_pixmap = QPixmap(pixmapLineWidth * m_view->devicePixelRatioF(), pixmapLineCount * m_view->devicePixelRatioF()); m_pixmap.fill(QColor("transparent")); // The text currently selected in the document, to be drawn later. const KTextEditor::Range &selection = m_view->selectionRange(); QPainter painter; if (painter.begin(&m_pixmap)) { // init pen once, afterwards, only change it if color changes to avoid a lot of allocation for setPen painter.setPen(selectionBgColor); // Do not force updates of the highlighting if the document is very large bool simpleMode = m_doc->lines() > 7500; int pixelY = 0; int drawnLines = 0; // Iterate over all visible lines, drawing them. for (int virtualLine = 0; virtualLine < docLineCount; virtualLine += lineIncrement) { int realLineNumber = m_view->textFolding().visibleLineToLine(virtualLine); QString lineText = m_doc->line(realLineNumber); if (!simpleMode) { m_doc->buffer().ensureHighlighted(realLineNumber); } const Kate::TextLine &kateline = m_doc->plainKateTextLine(realLineNumber); const QVector &attributes = kateline->attributesList(); - QList< QTextLayout::FormatRange > decorations = m_view->renderer()->decorationsForLine(kateline, realLineNumber); + QVector decorations = m_view->renderer()->decorationsForLine(kateline, realLineNumber); int attributeIndex = 0; // Draw selection if it is on an empty line if (selection.contains(KTextEditor::Cursor(realLineNumber, 0)) && lineText.size() == 0) { if (selectionBgColor != painter.pen().color()) { painter.setPen(selectionBgColor); } painter.drawLine(s_pixelMargin, pixelY, s_pixelMargin + s_lineWidth - 1, pixelY); } // Iterate over the line to draw the background int selStartX = -1; int selEndX = -1; int pixelX = s_pixelMargin; // use this to control the offset of the text from the left for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // Query the selection and draw it behind the character if (selection.contains(KTextEditor::Cursor(realLineNumber, x))) { if (selStartX == -1) selStartX = pixelX; selEndX = pixelX; if (lineText.size() - 1 == x) { selEndX = s_lineWidth + s_pixelMargin-1; } } if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { pixelX++; } } if (selStartX != -1) { if (selectionBgColor != painter.pen().color()) { painter.setPen(selectionBgColor); } painter.drawLine(selStartX, pixelY, selEndX, pixelY); } // Iterate over all the characters in the current line pixelX = s_pixelMargin; for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // draw the pixels if (lineText[x] == QLatin1Char(' ')) { pixelX++; } else if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { const QColor newPenColor(charColor(attributes, attributeIndex, decorations, defaultTextColor, x, lineText[x])); if (newPenColor != painter.pen().color()) { painter.setPen(newPenColor); } // Actually draw the pixel with the color queried from the renderer. painter.drawPoint(pixelX, pixelY); pixelX++; } } drawnLines++; if (((drawnLines) % charIncrement) == 0) { pixelY++; } } //qCDebug(LOG_KTE) << drawnLines; // Draw line modification marker map. // Disable this if the document is really huge, // since it requires querying every line. if (m_doc->lines() < 50000) { for (int lineno = 0; lineno < docLineCount; lineno++) { int realLineNo = m_view->textFolding().visibleLineToLine(lineno); const Kate::TextLine &line = m_doc->plainKateTextLine(realLineNo); const QColor & col = line->markedAsModified() ? modifiedLineColor : savedLineColor; if (line->markedAsModified() || line->markedAsSavedOnDisk()) { painter.fillRect(2, lineno / lineDivisor, 3, 1, col); } } } // end painting painter.end(); } // set right ratio m_pixmap.setDevicePixelRatio(m_view->devicePixelRatioF()); //qCDebug(LOG_KTE) << time.elapsed(); // Redraw the scrollbar widget with the updated pixmap. update(); } void KateScrollBar::miniMapPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); m_stdGroveRect = grooveRect; if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.moveTop(alignMargin); grooveRect.setHeight(grooveRect.height() - alignMargin); } if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.setHeight(grooveRect.height() - alignMargin); } m_grooveHeight = grooveRect.height(); const int docXMargin = 1; QRect sliderRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); sliderRect.adjust(docXMargin, 0, 0, 0); //style()->drawControl(QStyle::CE_ScrollBarAddLine, &opt, &painter, this); //style()->drawControl(QStyle::CE_ScrollBarSubLine, &opt, &painter, this); // calculate the document size and position const int docHeight = qMin(grooveRect.height(), int(m_pixmap.height() / m_pixmap.devicePixelRatio() * 2)) - 2 * docXMargin; const int yoffset = 1; // top-aligned in stead of center-aligned (grooveRect.height() - docHeight) / 2; const QRect docRect(QPoint(grooveRect.left() + docXMargin, yoffset + grooveRect.top()), QSize(grooveRect.width() - docXMargin, docHeight)); m_mapGroveRect = docRect; // calculate the visible area int max = qMax(maximum() + 1, 1); int visibleStart = value() * docHeight / (max + pageStep()) + docRect.top() + 0.5; int visibleEnd = (value() + pageStep()) * docHeight / (max + pageStep()) + docRect.top(); QRect visibleRect = docRect; visibleRect.moveTop(visibleStart); visibleRect.setHeight(visibleEnd - visibleStart); // calculate colors const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor highlightColor = palette().link().color(); const int backgroundLightness = backgroundColor.lightness(); const int foregroundLightness = foregroundColor.lightness(); const int lighnessDiff = (foregroundLightness - backgroundLightness); // get a color suited for the color theme QColor darkShieldColor = palette().color(QPalette::Mid); int hue, sat, light; darkShieldColor.getHsl(&hue, &sat, &light); // apply suitable lightness darkShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.35); // gradient for nicer results QLinearGradient gradient(0, 0, width(), 0); gradient.setColorAt(0, darkShieldColor); gradient.setColorAt(0.3, darkShieldColor.lighter(115)); gradient.setColorAt(1, darkShieldColor); QColor lightShieldColor; lightShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.15); QColor outlineColor; outlineColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.5); // draw the grove background in case the document is small painter.setPen(Qt::NoPen); painter.setBrush(backgroundColor); painter.drawRect(grooveRect); // adjust the rectangles if ((docHeight + 2 * docXMargin >= grooveRect.height()) && (sliderRect.height() > visibleRect.height() + 2)) { visibleRect.adjust(2, 0, -3, 0); } else { visibleRect.adjust(1, 0, -1, 2); sliderRect.setTop(visibleRect.top() - 1); sliderRect.setBottom(visibleRect.bottom() + 1); } // Smooth transform only when squeezing if (grooveRect.height() < m_pixmap.height() / m_pixmap.devicePixelRatio()) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } // draw the modified lines margin QRect pixmapMarginRect(QPoint(0, 0), QSize(s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapMarginRect(QPoint(0, docRect.top()), QSize(s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapMarginRect, m_pixmap, pixmapMarginRect); // calculate the stretch and draw the stretched lines (scrollbar marks) QRect pixmapRect(QPoint(s_pixelMargin, 0), QSize(m_pixmap.width() / m_pixmap.devicePixelRatio() - s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapRect(QPoint(s_pixelMargin, docRect.top()), QSize(docRect.width() - s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapRect, m_pixmap, pixmapRect); // delimit the end of the document const int y = docPixmapRect.height() + grooveRect.y(); if (y+2 < grooveRect.y() + grooveRect.height()) { QColor fg(foregroundColor); fg.setAlpha(30); painter.setBrush(Qt::NoBrush); painter.setPen(QPen(fg, 1)); painter.drawLine(grooveRect.x()+1,y+2,width()-1,y+2); } // fade the invisible sections const QRect top( grooveRect.x(), grooveRect.y(), grooveRect.width(), visibleRect.y()-grooveRect.y() //Pen width ); const QRect bottom( grooveRect.x(), grooveRect.y()+visibleRect.y()+visibleRect.height()-grooveRect.y(), //Pen width grooveRect.width(), grooveRect.height() - (visibleRect.y()-grooveRect.y())-visibleRect.height() ); QColor faded(backgroundColor); faded.setAlpha(110); painter.fillRect(top, faded); painter.fillRect(bottom, faded); // add a thin line to limit the scrollbar QColor c(foregroundColor); c.setAlpha(10); painter.setPen(QPen(c,1)); painter.drawLine(0, 0, 0, height()); if (m_showMarks) { QHashIterator it = m_lines; QPen penBg; penBg.setWidth(4); lightShieldColor.setAlpha(180); penBg.setColor(lightShieldColor); painter.setPen(penBg); while (it.hasNext()) { it.next(); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();; painter.drawLine(6, y, width() - 6, y); } it = m_lines; QPen pen; pen.setWidth(2); while (it.hasNext()) { it.next(); pen.setColor(it.value()); painter.setPen(pen); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top(); painter.drawLine(6, y, width() - 6, y); } } // slider outline QColor sliderColor(highlightColor); sliderColor.setAlpha(50); painter.fillRect(sliderRect, sliderColor); painter.setPen(QPen(highlightColor, 0)); // rounded rect looks ugly for some reason, so we draw 4 lines. painter.drawLine(sliderRect.left(), sliderRect.top() + 1, sliderRect.left(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.right(), sliderRect.top() + 1, sliderRect.right(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.left() + 1, sliderRect.top(), sliderRect.right() - 1, sliderRect.top()); painter.drawLine(sliderRect.left() + 1, sliderRect.bottom(), sliderRect.right() - 1, sliderRect.bottom()); } void KateScrollBar::normalPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); if (!m_showMarks) { return; } QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect rect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); int sideMargin = width() - rect.width(); if (sideMargin < 4) { sideMargin = 4; } sideMargin /= 2; QHashIterator it = m_lines; while (it.hasNext()) { it.next(); painter.setPen(it.value()); if (it.key() < rect.top() || it.key() > rect.bottom()) { painter.drawLine(0, it.key(), width(), it.key()); } else { painter.drawLine(0, it.key(), sideMargin, it.key()); painter.drawLine(width() - sideMargin, it.key(), width(), it.key()); } } } void KateScrollBar::resizeEvent(QResizeEvent *e) { QScrollBar::resizeEvent(e); m_updateTimer.start(); m_lines.clear(); update(); } void KateScrollBar::sliderChange(SliderChange change) { // call parents implementation QScrollBar::sliderChange(change); if (change == QAbstractSlider::SliderValueChange) { redrawMarks(); } else if (change == QAbstractSlider::SliderRangeChange) { marksChanged(); } if (m_leftMouseDown || m_middleMouseDown) { const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } } void KateScrollBar::marksChanged() { m_lines.clear(); update(); } void KateScrollBar::redrawMarks() { if (!m_showMarks) { return; } update(); } void KateScrollBar::recomputeMarksPositions() { // get the style options to compute the scrollbar pixels QStyleOptionSlider opt; initStyleOption(&opt); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); // cache top margin and groove height const int top = grooveRect.top(); const int h = grooveRect.height() - 1; // make sure we have a sane height if (h <= 0) { return; } // get total visible (=without folded) lines in the document int visibleLines = m_view->textFolding().visibleLines() - 1; if (m_view->config()->scrollPastEnd()) { visibleLines += m_viewInternal->linesDisplayed() - 1; visibleLines -= m_view->config()->autoCenterLines(); } // now repopulate the scrollbar lines list m_lines.clear(); const QHash &marks = m_doc->marks(); for (QHash::const_iterator i = marks.constBegin(); i != marks.constEnd(); ++i) { KTextEditor::Mark *mark = i.value(); const int line = m_view->textFolding().lineToVisibleLine(mark->line); const double ratio = static_cast(line) / visibleLines; m_lines.insert(top + (int)(h * ratio), KateRendererConfig::global()->lineMarkerColor((KTextEditor::MarkInterface::MarkTypes)mark->type)); } } void KateScrollBar::sliderMaybeMoved(int value) { if (m_middleMouseDown) { // we only need to emit this signal once, as for the following slider // movements the signal sliderMoved() is already emitted. // Thus, set m_middleMouseDown to false right away. m_middleMouseDown = false; emit sliderMMBMoved(value); } } //END //BEGIN KateCmdLineEditFlagCompletion /** * This class provide completion of flags. It shows a short description of * each flag, and flags are appended. */ class KateCmdLineEditFlagCompletion : public KCompletion { public: KateCmdLineEditFlagCompletion() { ; } QString makeCompletion(const QString & /*s*/) override { return QString(); } }; //END KateCmdLineEditFlagCompletion //BEGIN KateCmdLineEdit KateCommandLineBar::KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent) : KateViewBarWidget(true, parent) { QHBoxLayout *topLayout = new QHBoxLayout(); centralWidget()->setLayout(topLayout); topLayout->setMargin(0); m_lineEdit = new KateCmdLineEdit(this, view); connect(m_lineEdit, SIGNAL(hideRequested()), SIGNAL(hideMe())); topLayout->addWidget(m_lineEdit); QToolButton *helpButton = new QToolButton(this); helpButton->setAutoRaise(true); helpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); topLayout->addWidget(helpButton); connect(helpButton, SIGNAL(clicked()), this, SLOT(showHelpPage())); setFocusProxy(m_lineEdit); } void KateCommandLineBar::showHelpPage() { KHelpClient::invokeHelp(QStringLiteral("advanced-editing-tools-commandline"), QStringLiteral("kate")); } KateCommandLineBar::~KateCommandLineBar() { } // inserts the given string in the command line edit and (if selected = true) selects it so the user // can type over it if they want to void KateCommandLineBar::setText(const QString &text, bool selected) { m_lineEdit->setText(text); if (selected) { m_lineEdit->selectAll(); } } void KateCommandLineBar::execute(const QString &text) { m_lineEdit->slotReturnPressed(text); } KateCmdLineEdit::KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view) : KLineEdit() , m_view(view) , m_bar(bar) , m_msgMode(false) , m_histpos(0) , m_cmdend(0) , m_command(nullptr) { connect(this, SIGNAL(returnPressed(QString)), this, SLOT(slotReturnPressed(QString))); setCompletionObject(KateCmd::self()->commandCompletionObject()); setAutoDeleteCompletionObject(false); m_hideTimer = new QTimer(this); m_hideTimer->setSingleShot(true); connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hideLineEdit())); // make sure the timer is stopped when the user switches views. if not, focus will be given to the // wrong view when KateViewBar::hideCurrentBarWidget() is called after 4 seconds. (the timer is // used for showing things like "Success" for four seconds after the user has used the kate // command line) connect(m_view, SIGNAL(focusOut(KTextEditor::View*)), m_hideTimer, SLOT(stop())); } void KateCmdLineEdit::hideEvent(QHideEvent *e) { Q_UNUSED(e); } QString KateCmdLineEdit::helptext(const QPoint &) const { QString beg = QStringLiteral("
Help: "); QString mid = QStringLiteral("
"); QString end = QStringLiteral("
"); QString t = text(); QRegExp re(QLatin1String("\\s*help\\s+(.*)")); if (re.indexIn(t) > -1) { QString s; // get help for command QString name = re.cap(1); if (name == QLatin1String("list")) { return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(QLatin1Char(' ')) + i18n("

For help on individual commands, do 'help <command>'

") + end; } else if (! name.isEmpty()) { KTextEditor::Command *cmd = KateCmd::self()->queryCommand(name); if (cmd) { if (cmd->help(m_view, name, s)) { return beg + name + mid + s + end; } else { return beg + name + mid + i18n("No help for '%1'", name) + end; } } else { return beg + mid + i18n("No such command %1", name) + end; } } } return beg + mid + i18n( "

This is the Katepart command line.
" "Syntax: command [ arguments ]
" "For a list of available commands, enter help list
" "For help for individual commands, enter help <command>

") + end; } bool KateCmdLineEdit::event(QEvent *e) { if (e->type() == QEvent::QueryWhatsThis) { setWhatsThis(helptext(QPoint())); e->accept(); return true; } return KLineEdit::event(e); } /** * Parse the text as a command. * * The following is a simple PEG grammar for the syntax of the command. * (A PEG grammar is like a BNF grammar, except that "/" stands for * ordered choice: only the first matching rule is used. In other words, * the parsing is short-circuited in the manner of the "or" operator in * programming languages, and so the grammar is unambiguous.) * * Text <- Range? Command * / Position * Range <- Position ("," Position)? * / "%" * Position <- Base Offset? * Base <- Line * / LastLine * / ThisLine * / Mark * Offset <- [+-] Base * Line <- [0-9]+ * LastLine <- "$" * ThisLine <- "." * Mark <- "'" [a-z] */ void KateCmdLineEdit::slotReturnPressed(const QString &text) { if (text.isEmpty()) { return; } // silently ignore leading space characters uint n = 0; const uint textlen = text.length(); while ((n < textlen) && (text[n].isSpace())) { n++; } if (n >= textlen) { return; } QString cmd = text.mid(n); // Parse any leading range expression, and strip it (and maybe do some other transforms on the command). QString leadingRangeExpression; KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd); // Built in help: if the command starts with "help", [try to] show some help if (cmd.startsWith(QLatin1String("help"))) { QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint())); clear(); KateCmd::self()->appendHistory(cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); return; } if (cmd.length() > 0) { KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd); m_oldText = leadingRangeExpression + cmd; m_msgMode = true; // the following commands changes the focus themselves, so bar should be hidden before execution. if (QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { emit hideRequested(); } if (!p) { setText(i18n("No such command: \"%1\"", cmd)); } else if (range.isValid() && !p->supportsRange(cmd)) { // Raise message, when the command does not support ranges. setText(i18n("Error: No range allowed for command \"%1\".", cmd)); } else { QString msg; if (p->exec(m_view, cmd, msg, range)) { // append command along with range (will be empty if none given) to history KateCmd::self()->appendHistory(leadingRangeExpression + cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); if (msg.length() > 0) { setText(i18n("Success: ") + msg); } else if (isVisible()) { // always hide on success without message emit hideRequested(); } } else { if (msg.length() > 0) { if (msg.contains(QLatin1Char('\n'))) { // multiline error, use widget with more space QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), msg); } else { setText(msg); } } else { setText(i18n("Command \"%1\" failed.", cmd)); } } } } // clean up if (completionObject() != KateCmd::self()->commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_command = nullptr; m_cmdend = 0; // the following commands change the focus themselves if (!QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { m_view->setFocus(); } if (isVisible()) { m_hideTimer->start(4000); } } void KateCmdLineEdit::hideLineEdit() // unless i have focus ;) { if (! hasFocus()) { emit hideRequested(); } } void KateCmdLineEdit::focusInEvent(QFocusEvent *ev) { if (m_msgMode) { m_msgMode = false; setText(m_oldText); selectAll(); } KLineEdit::focusInEvent(ev); } void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev) { if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) { m_view->setFocus(); hideLineEdit(); clear(); } else if (ev->key() == Qt::Key_Up) { fromHistory(true); } else if (ev->key() == Qt::Key_Down) { fromHistory(false); } uint cursorpos = cursorPosition(); KLineEdit::keyPressEvent(ev); // during typing, let us see if we have a valid command if (! m_cmdend || cursorpos <= m_cmdend) { QChar c; if (! ev->text().isEmpty()) { c = ev->text().at(0); } if (! m_cmdend && ! c.isNull()) { // we have no command, so lets see if we got one if (! c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) { m_command = KateCmd::self()->queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_cmdend = 0; } } // if we got a command, check if it wants to do something. if (m_command) { KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed()); if (cmpl) { // We need to prepend the current command name + flag string // when completion is done //qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!"; setCompletionObject(cmpl); } } } else if (m_command && !ev->text().isEmpty()) { // check if we should call the commands processText() if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) { m_command->processText(m_view, text()); } } } void KateCmdLineEdit::fromHistory(bool up) { if (! KateCmd::self()->historyLength()) { return; } QString s; if (up) { if (m_histpos > 0) { m_histpos--; s = KateCmd::self()->fromHistory(m_histpos); } } else { if (m_histpos < (KateCmd::self()->historyLength() - 1)) { m_histpos++; s = KateCmd::self()->fromHistory(m_histpos); } else { m_histpos = KateCmd::self()->historyLength(); setText(m_oldText); } } if (! s.isEmpty()) { // Select the argument part of the command, so that it is easy to overwrite setText(s); static QRegExp reCmd = QRegExp(QLatin1String(".*[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)")); if (reCmd.indexIn(text()) == 0) { setSelection(text().length() - reCmd.cap(1).length(), reCmd.cap(1).length()); } } } //END KateCmdLineEdit //BEGIN KateIconBorder using namespace KTextEditor; KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent) : QWidget(parent) , m_view(internalView->m_view) , m_doc(internalView->doc()) , m_viewInternal(internalView) , m_iconBorderOn(false) , m_lineNumbersOn(false) , m_relLineNumbersOn(false) , m_updateRelLineNumbers(false) , m_foldingMarkersOn(false) , m_dynWrapIndicatorsOn(false) , m_annotationBorderOn(false) , m_dynWrapIndicators(0) , m_lastClickedLine(-1) , m_cachedLNWidth(0) , m_maxCharWidth(0.0) , iconPaneWidth(16) , m_annotationBorderWidth(6) , m_foldingPreview(nullptr) , m_foldingRange(nullptr) , m_nextHighlightBlock(-2) , m_currentBlockLine(-1) { setAttribute(Qt::WA_StaticContents); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); setMouseTracking(true); m_doc->setMarkDescription(MarkInterface::markType01, i18n("Bookmark")); m_doc->setMarkPixmap(MarkInterface::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")).pixmap(16, 16)); updateFont(); m_delayFoldingHlTimer.setSingleShot(true); m_delayFoldingHlTimer.setInterval(150); connect(&m_delayFoldingHlTimer, SIGNAL(timeout()), this, SLOT(showBlock())); // user interaction (scrolling) hides e.g. preview connect(m_view, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(displayRangeChanged())); } KateIconBorder::~KateIconBorder() { delete m_foldingPreview; delete m_foldingRange; } void KateIconBorder::setIconBorderOn(bool enable) { if (enable == m_iconBorderOn) { return; } m_iconBorderOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setAnnotationBorderOn(bool enable) { if (enable == m_annotationBorderOn) { return; } m_annotationBorderOn = enable; emit m_view->annotationBorderVisibilityChanged(m_view, enable); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::removeAnnotationHovering() { // remove hovering if it's still there if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) { m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::setLineNumbersOn(bool enable) { if (enable == m_lineNumbersOn) { return; } m_lineNumbersOn = enable; m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setRelLineNumbersOn(bool enable) { if (enable == m_relLineNumbersOn) { return; } m_relLineNumbersOn = enable; /* * We don't have to touch the m_dynWrapIndicatorsOn because * we already got it right from the m_lineNumbersOn */ updateGeometry(); QTimer::singleShot( 0, this, SLOT(update()) ); } void KateIconBorder::updateForCursorLineChange() { if (m_relLineNumbersOn) { m_updateRelLineNumbers = true; } // always do normal update, e.g. for different current line color! update(); } void KateIconBorder::setDynWrapIndicators(int state) { if (state == m_dynWrapIndicators) { return; } m_dynWrapIndicators = state; m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setFoldingMarkersOn(bool enable) { if (enable == m_foldingMarkersOn) { return; } m_foldingMarkersOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } QSize KateIconBorder::sizeHint() const { int w = 0; if (m_iconBorderOn) { w += iconPaneWidth + 2; } if (m_annotationBorderOn) { w += m_annotationBorderWidth + 2; } if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { w += lineNumberWidth() + 2; } if (m_foldingMarkersOn) { w += iconPaneWidth; } // space for the line modification system border if (m_view->config()->lineModification()) { w += 3; } // two pixel space w += 2; return QSize(w, 0); } // This function (re)calculates the maximum width of any of the digit characters (0 -> 9) // for graceful handling of variable-width fonts as the linenumber font. void KateIconBorder::updateFont() { const QFontMetricsF &fm = m_view->renderer()->config()->fontMetrics(); m_maxCharWidth = 0.0; // Loop to determine the widest numeric character in the current font. // 48 is ascii '0' for (int i = 48; i < 58; i++) { const qreal charWidth = ceil(fm.width(QChar(i))); m_maxCharWidth = qMax(m_maxCharWidth, charWidth); } // the icon pane scales with the font... iconPaneWidth = fm.height(); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } int KateIconBorder::lineNumberWidth() const { // width = (number of digits + 1) * char width const int digits = (int) ceil(log10((double)(m_view->doc()->lines() + 1))); int width = m_lineNumbersOn ? (int)ceil((digits + 1) * m_maxCharWidth) : 0; if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { // HACK: 16 == style().scrollBarExtent().width() width = qMax(16 + 4, width); if (m_cachedLNWidth != width || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { int w = 16;// HACK: 16 == style().scrollBarExtent().width() style().scrollBarExtent().width(); int h = m_view->renderer()->lineHeight(); QSize newSize(w * devicePixelRatio(), h * devicePixelRatio()); if ((m_arrow.size() != newSize || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) && !newSize.isEmpty()) { m_arrow = QPixmap(newSize); m_arrow.setDevicePixelRatio(devicePixelRatio()); QPainter p(&m_arrow); p.fillRect(0, 0, w, h, m_view->renderer()->config()->iconBarColor()); h = m_view->renderer()->config()->fontMetrics().ascent(); p.setPen(m_view->renderer()->config()->lineNumberColor()); QPainterPath path; path.moveTo(w / 2, h / 2); path.lineTo(w / 2, 0); path.lineTo(w / 4, h / 4); path.lineTo(0, 0); path.lineTo(0, h / 2); path.lineTo(w / 2, h - 1); path.lineTo(w * 3 / 4, h - 1); path.lineTo(w - 1, h * 3 / 4); path.lineTo(w * 3 / 4, h / 2); path.lineTo(0, h / 2); p.drawPath(path); } } } return width; } void KateIconBorder::paintEvent(QPaintEvent *e) { paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height()); } static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open) { painter.setRenderHint(QPainter::Antialiasing); qreal size = qMin(width, height); if (KColorUtils::luma(c) > 0.25) { c = KColorUtils::darken(c); } else { c = KColorUtils::shade(c, 0.1); } QPen pen; pen.setJoinStyle(Qt::RoundJoin); pen.setColor(c); pen.setWidthF(1.5); painter.setPen(pen); painter.setBrush(c); // let some border, if possible size *= 0.6; qreal halfSize = size / 2; qreal halfSizeP = halfSize * 0.6; QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2); if (open) { QPointF points[3] = { middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP) }; painter.drawConvexPolygon(points, 3); } else { QPointF points[3] = { middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0) }; painter.drawConvexPolygon(points, 3); } painter.setRenderHint(QPainter::Antialiasing, false); } void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height) { uint h = m_view->renderer()->lineHeight(); uint startz = (y / h); uint endz = startz + 1 + (height / h); uint lineRangesSize = m_viewInternal->cache()->viewCacheLineCount(); uint currentLine = m_view->cursorPosition().line(); // center the folding boxes int m_px = (h - 11) / 2; if (m_px < 0) { m_px = 0; } int lnWidth(0); if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { // avoid calculating unless needed ;-) lnWidth = lineNumberWidth(); if (lnWidth != m_cachedLNWidth || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { // we went from n0 ->n9 lines or vice verca // this causes an extra updateGeometry() first time the line numbers // are displayed, but sizeHint() is supposed to be const so we can't set // the cached value there. m_cachedLNWidth = lnWidth; m_oldBackgroundColor = m_view->renderer()->config()->iconBarColor(); updateGeometry(); update(); return; } } int w(this->width()); // sane value/calc only once QPainter p(this); p.setRenderHints(QPainter::TextAntialiasing); p.setFont(m_view->renderer()->config()->font()); // for line numbers KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); for (uint z = startz; z <= endz; z++) { int y = h * z; int realLine = -1; if (z < lineRangesSize) { realLine = m_viewInternal->cache()->viewLine(z).line(); } int lnX = 0; p.fillRect(0, y, w - 5, h, m_view->renderer()->config()->iconBarColor()); p.fillRect(w - 5, y, 5, h, m_view->renderer()->config()->backgroundColor()); // icon pane if (m_iconBorderOn) { p.setPen(m_view->renderer()->config()->separatorColor()); p.setBrush(m_view->renderer()->config()->separatorColor()); p.drawLine(lnX + iconPaneWidth + 1, y, lnX + iconPaneWidth + 1, y + h); if ((realLine > -1) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { uint mrk(m_doc->mark(realLine)); // call only once if (mrk) { for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (mrk & markType) { QPixmap px_mark(m_doc->markPixmap(markType)); if (!px_mark.isNull() && h > 0 && iconPaneWidth > 0) { if (iconPaneWidth < px_mark.width() || h < (uint)px_mark.height()) { px_mark = px_mark.scaled(iconPaneWidth, h, Qt::KeepAspectRatio); } // center the mark pixmap int x_px = (iconPaneWidth - px_mark.width()) / 2; if (x_px < 0) { x_px = 0; } int y_px = (h - px_mark.height()) / 2; if (y_px < 0) { y_px = 0; } p.drawPixmap(lnX + x_px, y + y_px, px_mark); } } } } } lnX += iconPaneWidth + 2; } // annotation information if (m_annotationBorderOn) { // Draw a border line between annotations and the line numbers p.setPen(m_view->renderer()->config()->lineNumberColor()); p.setBrush(m_view->renderer()->config()->lineNumberColor()); int borderWidth = m_annotationBorderWidth; p.drawLine(lnX + borderWidth + 1, y, lnX + borderWidth + 1, y + h); if ((realLine > -1) && model) { // Fetch data from the model QVariant text = model->data(realLine, Qt::DisplayRole); QVariant foreground = model->data(realLine, Qt::ForegroundRole); QVariant background = model->data(realLine, Qt::BackgroundRole); // Fill the background if (background.isValid()) { p.fillRect(lnX, y, borderWidth + 1, h, background.value()); } // Set the pen for drawing the foreground if (foreground.isValid()) { p.setPen(foreground.value()); } // Draw a border around all adjacent entries that have the same text as the currently hovered one const QVariant identifier = model->data( realLine, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ); if( m_hoveredAnnotationGroupIdentifier == identifier.toString() ) { p.drawLine(lnX, y, lnX, y + h); p.drawLine(lnX + borderWidth, y, lnX + borderWidth, y + h); QVariant beforeText = model->data(realLine - 1, Qt::DisplayRole); QVariant afterText = model->data(realLine + 1, Qt::DisplayRole); if (((beforeText.isValid() && beforeText.canConvert() && text.isValid() && text.canConvert() && beforeText.toString() != text.toString()) || realLine == 0) && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawLine(lnX + 1, y, lnX + borderWidth, y); } if (((afterText.isValid() && afterText.canConvert() && text.isValid() && text.canConvert() && afterText.toString() != text.toString()) || realLine == m_view->doc()->lines() - 1) && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawLine(lnX + 1, y + h - 1, lnX + borderWidth, y + h - 1); } } if (foreground.isValid()) { QPen pen = p.pen(); pen.setWidth(1); p.setPen(pen); } // Now draw the normal text if (text.isValid() && text.canConvert() && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { p.drawText(lnX + 3, y, borderWidth - 3, h, Qt::AlignLeft | Qt::AlignVCenter, text.toString()); } } // adjust current X position and reset the pen and brush lnX += borderWidth + 2; } // line number if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { if (realLine > -1) { int distanceToCurrent = abs(realLine - static_cast(currentLine)); QColor color; if (distanceToCurrent == 0) { color = m_view->renderer()->config()->currentLineNumberColor(); } else { color = m_view->renderer()->config()->lineNumberColor(); } p.setPen(color); p.setBrush(color); if (m_viewInternal->cache()->viewLine(z).startCol() == 0) { if (m_relLineNumbersOn) { if (distanceToCurrent == 0) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignLeft|Qt::AlignVCenter, QString::number(realLine + 1)); } else { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignRight|Qt::AlignVCenter, QString::number(distanceToCurrent)); } if (m_updateRelLineNumbers) { m_updateRelLineNumbers = false; update(); } } else if (m_lineNumbersOn) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, QString::number(realLine + 1)); } } else if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { p.drawPixmap(lnX + lnWidth - (m_arrow.width() / m_arrow.devicePixelRatio()) - 2, y, m_arrow); } } lnX += lnWidth + 2; } // folding markers if (m_foldingMarkersOn) { // first icon border background p.fillRect(lnX, y, iconPaneWidth, h, m_view->renderer()->config()->iconBarColor()); // possible additional folding highlighting if ((realLine >= 0) && m_foldingRange && m_foldingRange->overlapsLine(realLine)) { p.save(); // use linear gradient as brush QLinearGradient g(lnX, y, lnX + iconPaneWidth, y); const QColor foldingColor(m_view->renderer()->config()->foldingColor()); g.setColorAt(0, foldingColor); g.setColorAt(0.3, foldingColor.lighter(110)); g.setColorAt(1, foldingColor); p.setBrush(g); p.setPen(foldingColor); p.setClipRect(lnX, y, iconPaneWidth, h); p.setRenderHint(QPainter::Antialiasing); const qreal r = 4.0; if (m_foldingRange->start().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawRect(lnX, y, iconPaneWidth, h + r); } else if (m_foldingRange->end().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawRect(lnX, y - r, iconPaneWidth, h + r); } else { p.drawRect(lnX, y - r, iconPaneWidth, h + 2 * r); } p.restore(); } if ((realLine >= 0) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } Kate::TextLine tl = m_doc->kateTextLine(realLine); if (!startingRanges.isEmpty() || tl->markedAsFoldingStart()) { if (anyFolded) { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, false); } else { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, true); } } } lnX += iconPaneWidth; } // modified line system if (m_view->config()->lineModification() && realLine > -1 && !m_doc->url().isEmpty()) { // one pixel space ++lnX; Kate::TextLine tl = m_doc->plainKateTextLine(realLine); if (tl->markedAsModified()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->modifiedLineColor()); } if (tl->markedAsSavedOnDisk()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->savedLineColor()); } } } } KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const { // see KateIconBorder::sizeHint() for pixel spacings int x = 0; if (m_iconBorderOn) { x += iconPaneWidth; if (p.x() <= x) { return IconBorder; } x += 2; } if (this->m_annotationBorderOn) { x += m_annotationBorderWidth; if (p.x() <= x) { return AnnotationBorder; } x += 2; } if (m_lineNumbersOn || m_dynWrapIndicators) { x += lineNumberWidth(); if (p.x() <= x) { return LineNumbers; } x += 2; } if (m_foldingMarkersOn) { x += iconPaneWidth; if (p.x() <= x) { return FoldingMarkers; } } if (m_view->config()->lineModification()) { x += 3 + 2; if (p.x() <= x) { return ModificationBorder; } } return None; } void KateIconBorder::mousePressEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { m_lastClickedLine = t.line(); const auto area = positionToArea(e->pos()); // IconBorder and AnnotationBorder have their own behavior; don't forward to view if (area != IconBorder && area != AnnotationBorder) { const auto pos = QPoint(0, e->y()); if (area == LineNumbers && e->button() == Qt::LeftButton && !(e->modifiers() & Qt::ShiftModifier)) { // setup view so the following mousePressEvent will select the line m_viewInternal->beginSelectLine(pos); } QMouseEvent forward(QEvent::MouseButtonPress, pos, e->button(), e->buttons(), e->modifiers()); m_viewInternal->mousePressEvent(&forward); } return e->accept(); } QWidget::mousePressEvent(e); } void KateIconBorder::showDelayedBlock(int line) { // save the line over which the mouse hovers // either we start the timer for delay, or we show the block immediately // if the moving range already exists m_nextHighlightBlock = line; if (!m_foldingRange) { if (!m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.start(); } } else { showBlock(); } } void KateIconBorder::showBlock() { if (m_nextHighlightBlock == m_currentBlockLine) { return; } m_currentBlockLine = m_nextHighlightBlock; if (m_currentBlockLine >= m_doc->buffer().lines()) { return; } /** * compute to which folding range we belong * FIXME: optimize + perhaps have some better threshold or use timers! */ KTextEditor::Range newRange = KTextEditor::Range::invalid(); for (int line = m_currentBlockLine; line >= qMax(0, m_currentBlockLine - 1024); --line) { /** * try if we have folding range from that line, should be fast per call */ KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { continue; } /** * does the range reach us? */ if (foldingRange.overlapsLine(m_currentBlockLine)) { newRange = foldingRange; break; } } if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) { // new range equals the old one, nothing to do. return; } else { // the ranges differ, delete the old, if it exists delete m_foldingRange; m_foldingRange = nullptr; } if (newRange.isValid()) { //qCDebug(LOG_KTE) << "new folding hl-range:" << newRange; m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); /** * create highlighting color with alpha for the range! */ QColor result = m_view->renderer()->config()->foldingColor(); result.setAlphaF(0.5); attr->setBackground(QBrush(result)); m_foldingRange->setView(m_view); // use z depth defined in moving ranges interface m_foldingRange->setZDepth(-100.0); m_foldingRange->setAttribute(attr); } // show text preview, if a folded region starts here bool foldUnderMouse = false; if (m_foldingRange && m_view->config()->foldingPreview()) { const QPoint globalPos = QCursor::pos(); const QPoint pos = mapFromGlobal(globalPos); const KateTextLayout &t = m_view->m_viewInternal->yToKateTextLayout(pos.y()); if (t.isValid() && positionToArea(pos) == FoldingMarkers) { const int realLine = t.line(); foldUnderMouse = !m_view->textFolding().isLineVisible(realLine + 1); if (foldUnderMouse) { if (!m_foldingPreview) { m_foldingPreview = new KateTextPreview(m_view); m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_foldingPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window // qApp->installEventFilter(this); } // TODO: use KateViewInternal::maxLen() somehow to compute proper width for amount of lines to display // try using the end line of the range for proper popup height const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, (height() - pos.y()) / m_view->renderer()->lineHeight()); m_foldingPreview->resize(m_view->width() / 2, lineCount * m_view->renderer()->lineHeight() + 2 * m_foldingPreview->frameWidth()); const int xGlobal = mapToGlobal(QPoint(width(), 0)).x(); const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(realLine, 0))).y(); m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft()); m_foldingPreview->setLine(realLine); m_foldingPreview->setCenterView(false); m_foldingPreview->setShowFoldedLines(true); m_foldingPreview->raise(); m_foldingPreview->show(); } } } if (!foldUnderMouse) { delete m_foldingPreview; } /** * repaint */ repaint(); } void KateIconBorder::hideBlock() { if (m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.stop(); } m_nextHighlightBlock = -2; m_currentBlockLine = -1; delete m_foldingRange; m_foldingRange = nullptr; delete m_foldingPreview; } void KateIconBorder::leaveEvent(QEvent *event) { hideBlock(); removeAnnotationHovering(); QWidget::leaveEvent(event); } void KateIconBorder::mouseMoveEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { if (positionToArea(e->pos()) == FoldingMarkers) { showDelayedBlock(t.line()); } else { hideBlock(); } if (positionToArea(e->pos()) == AnnotationBorder) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { m_hoveredAnnotationGroupIdentifier = model->data( t.line(), (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ).toString(); showAnnotationTooltip(t.line(), e->globalPos()); QTimer::singleShot(0, this, SLOT(update())); } } else { if (positionToArea(e->pos()) == IconBorder) { m_doc->requestMarkTooltip(t.line(), e->globalPos()); } m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } if (positionToArea(e->pos()) != IconBorder) { QPoint p = m_viewInternal->mapFromGlobal(e->globalPos()); QMouseEvent forward(QEvent::MouseMove, p, e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseMoveEvent(&forward); } } else { // remove hovering if it's still there removeAnnotationHovering(); } QWidget::mouseMoveEvent(e); } void KateIconBorder::mouseReleaseEvent(QMouseEvent *e) { const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); if (area == IconBorder) { if (e->button() == Qt::LeftButton) { if (!m_doc->handleMarkClick(cursorOnLine)) { KateViewConfig *config = m_view->config(); const uint editBits = m_doc->editableMarks(); // is the default or the only editable mark const uint singleMark = qPopulationCount(editBits) > 1 ? editBits & config->defaultMarkType() : editBits; if (singleMark) { if (m_doc->mark(cursorOnLine) & singleMark) { m_doc->removeMark(cursorOnLine, singleMark); } else { m_doc->addMark(cursorOnLine, singleMark); } } else if (config->allowMarkMenu()) { showMarkMenu(cursorOnLine, QCursor::pos()); } } } else if (e->button() == Qt::RightButton) { showMarkMenu(cursorOnLine, QCursor::pos()); } } if (area == FoldingMarkers) { // ask the folding info for this line, if any folds are around! QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(cursorOnLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } // fold or unfold all ranges, remember if any action happened! bool actionDone = false; for (int i = 0; i < startingRanges.size(); ++i) { actionDone = (anyFolded ? m_view->textFolding().unfoldRange(startingRanges[i].first) : m_view->textFolding().foldRange(startingRanges[i].first)) || actionDone; } // if no action done, try to fold it, create non-persistent folded range, if possible! if (!actionDone) { // either use the fold for this line or the range that is highlighted ATM if any! KTextEditor::Range foldingRange = m_view->doc()->buffer().computeFoldingRangeForStartLine(cursorOnLine); if (!foldingRange.isValid() && m_foldingRange) { foldingRange = m_foldingRange->toRange(); } m_view->textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); } delete m_foldingPreview; } if (area == AnnotationBorder) { const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (e->button() == Qt::LeftButton && singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } else if (e->button() == Qt::RightButton) { showAnnotationMenu(cursorOnLine, e->globalPos()); } } } QMouseEvent forward(QEvent::MouseButtonRelease, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseReleaseEvent(&forward); } void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e) { int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (area == AnnotationBorder && !singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } } QMouseEvent forward(QEvent::MouseButtonDblClick, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseDoubleClickEvent(&forward); } void KateIconBorder::wheelEvent(QWheelEvent *e) { QCoreApplication::sendEvent(m_viewInternal, e); } void KateIconBorder::showMarkMenu(uint line, const QPoint &pos) { if (m_doc->handleMarkContextMenu(line, pos)) { return; } if (!m_view->config()->allowMarkMenu()) { return; } QMenu markMenu; QMenu selectDefaultMark; auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark); QVector vec(33); int i = 1; for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (!(m_doc->editableMarks() & markType)) { continue; } QAction *mA; QAction *dMA; const QPixmap icon = m_doc->markPixmap(markType); if (!m_doc->markDescription(markType).isEmpty()) { mA = markMenu.addAction(icon, m_doc->markDescription(markType)); dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType)); } else { mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1)); dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1)); } selectDefaultMarkActionGroup->addAction(dMA); mA->setData(i); mA->setCheckable(true); dMA->setData(i + 100); dMA->setCheckable(true); if (m_doc->mark(line) & markType) { mA->setChecked(true); } if (markType & KateViewConfig::global()->defaultMarkType()) { dMA->setChecked(true); } vec[i++] = markType; } if (markMenu.actions().count() == 0) { return; } if (markMenu.actions().count() > 1) { markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark); } QAction *rA = markMenu.exec(pos); if (!rA) { return; } int result = rA->data().toInt(); if (result > 100) { KateViewConfig::global()->setDefaultMarkType(vec[result - 100]); } else { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes) vec[result]; if (m_doc->mark(line) & markType) { m_doc->removeMark(line, markType); } else { m_doc->addMark(line, markType); } } } void KateIconBorder::showAnnotationTooltip(int line, const QPoint &pos) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::ToolTipRole); QString tip = data.toString(); if (!tip.isEmpty()) { QToolTip::showText(pos, data.toString(), this); } } } int KateIconBorder::annotationLineWidth(int line) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::DisplayRole); return data.toString().length() * m_maxCharWidth + 8; } return 8; } void KateIconBorder::updateAnnotationLine(int line) { if (annotationLineWidth(line) > m_annotationBorderWidth) { m_annotationBorderWidth = annotationLineWidth(line); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos) { QMenu menu; QAction a(i18n("Disable Annotation Bar"), &menu); a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); menu.addAction(&a); emit m_view->annotationContextMenuAboutToShow(m_view, &menu, line); if (menu.exec(pos) == &a) { m_view->setAnnotationBorderVisible(false); } } void KateIconBorder::hideAnnotationTooltip() { QToolTip::hideText(); } void KateIconBorder::updateAnnotationBorderWidth() { m_annotationBorderWidth = 6; KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { for (int i = 0; i < m_view->doc()->lines(); i++) { int curwidth = annotationLineWidth(i); if (curwidth > m_annotationBorderWidth) { m_annotationBorderWidth = curwidth; } } } updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel) { if (oldmodel) { oldmodel->disconnect(this); } if (newmodel) { connect(newmodel, SIGNAL(reset()), this, SLOT(updateAnnotationBorderWidth())); connect(newmodel, SIGNAL(lineChanged(int)), this, SLOT(updateAnnotationLine(int))); } updateAnnotationBorderWidth(); } void KateIconBorder::displayRangeChanged() { hideBlock(); removeAnnotationHovering(); } //END KateIconBorder //BEGIN KateViewEncodingAction // Acording to http://www.iana.org/assignments/ianacharset-mib // the default/unknown mib value is 2. #define MIB_DEFAULT 2 bool lessThanAction(KSelectAction *a, KSelectAction *b) { return a->text() < b->text(); } void KateViewEncodingAction::Private::init() { QList actions; q->setToolBarMode(MenuMode); int i; foreach (const QStringList &encodingsForScript, KCharsets::charsets()->encodingsByScript()) { KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), q); for (i = 1; i < encodingsForScript.size(); ++i) { tmp->addAction(encodingsForScript.at(i)); } q->connect(tmp, SIGNAL(triggered(QAction*)), q, SLOT(_k_subActionTriggered(QAction*))); //tmp->setCheckable(true); actions << tmp; } qSort(actions.begin(), actions.end(), lessThanAction); foreach (KSelectAction *action, actions) { q->addAction(action); } } void KateViewEncodingAction::Private::_k_subActionTriggered(QAction *action) { if (currentSubAction == action) { return; } currentSubAction = action; bool ok = false; int mib = q->mibForName(action->text(), &ok); if (ok) { emit q->KSelectAction::triggered(action->text()); emit q->triggered(q->codecForMib(mib)); } } KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode) : KSelectAction(text, parent), doc(_doc), view(_view), d(new Private(this)) , m_saveAsMode(saveAsMode) { d->init(); connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); } KateViewEncodingAction::~KateViewEncodingAction() { delete d; } void KateViewEncodingAction::slotAboutToShow() { setCurrentCodec(doc->config()->encoding()); } void KateViewEncodingAction::setEncoding(const QString &e) { /** * in save as mode => trigger saveAs */ if (m_saveAsMode) { doc->documentSaveAsWithEncoding(e); return; } /** * else switch encoding */ doc->userSetEncodingForNextReload(); doc->setEncoding(e); view->reloadFile(); } int KateViewEncodingAction::mibForName(const QString &codecName, bool *ok) const { // FIXME logic is good but code is ugly bool success = false; int mib = MIB_DEFAULT; KCharsets *charsets = KCharsets::charsets(); QTextCodec *codec = charsets->codecForName(codecName, success); if (!success) { // Maybe we got a description name instead codec = charsets->codecForName(charsets->encodingForName(codecName), success); } if (codec) { mib = codec->mibEnum(); } if (ok) { *ok = success; } if (success) { return mib; } qCWarning(LOG_KTE) << "Invalid codec name: " << codecName; return MIB_DEFAULT; } QTextCodec *KateViewEncodingAction::codecForMib(int mib) const { if (mib == MIB_DEFAULT) { // FIXME offer to change the default codec return QTextCodec::codecForLocale(); } else { return QTextCodec::codecForMib(mib); } } QTextCodec *KateViewEncodingAction::currentCodec() const { return codecForMib(currentCodecMib()); } bool KateViewEncodingAction::setCurrentCodec(QTextCodec *codec) { disconnect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); int i, j; for (i = 0; i < actions().size(); ++i) { if (actions().at(i)->menu()) { for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) { if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) { continue; } if (actions().at(i)->menu()->actions().at(j)->isSeparator()) { continue; } if (codec == KCharsets::charsets()->codecForName(actions().at(i)->menu()->actions().at(j)->text())) { d->currentSubAction = actions().at(i)->menu()->actions().at(j); d->currentSubAction->setChecked(true); } else { actions().at(i)->menu()->actions().at(j)->setChecked(false); } } } } connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); return true; } QString KateViewEncodingAction::currentCodecName() const { return d->currentSubAction->text(); } bool KateViewEncodingAction::setCurrentCodec(const QString &codecName) { return setCurrentCodec(KCharsets::charsets()->codecForName(codecName)); } int KateViewEncodingAction::currentCodecMib() const { return mibForName(currentCodecName()); } bool KateViewEncodingAction::setCurrentCodec(int mib) { return setCurrentCodec(codecForMib(mib)); } //END KateViewEncodingAction //BEGIN KateViewBar related classes KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent) : QWidget(parent) , m_viewBar(nullptr) { QHBoxLayout *layout = new QHBoxLayout(this); // NOTE: Here be cosmetics. layout->setMargin(0); // hide button if (addCloseButton) { QToolButton *hideButton = new QToolButton(this); hideButton->setAutoRaise(true); hideButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); connect(hideButton, SIGNAL(clicked()), SIGNAL(hideMe())); layout->addWidget(hideButton); layout->setAlignment(hideButton, Qt::AlignLeft | Qt::AlignTop); } // widget to be used as parent for the real content m_centralWidget = new QWidget(this); layout->addWidget(m_centralWidget); setLayout(layout); setFocusProxy(m_centralWidget); } KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view) : QWidget(parent), m_external(external), m_view(view), m_permanentBarWidget(nullptr) { m_layout = new QVBoxLayout(this); m_stack = new QStackedWidget(this); m_layout->addWidget(m_stack); m_layout->setMargin(0); m_stack->hide(); hide(); } void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget) { // just ignore additional adds for already existing widgets if (hasBarWidget(newBarWidget)) { return; } // add new widget, invisible... newBarWidget->hide(); m_stack->addWidget(newBarWidget); newBarWidget->setAssociatedViewBar(this); connect(newBarWidget, SIGNAL(hideMe()), SLOT(hideCurrentBarWidget())); } void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget) { // remove only if there if (!hasBarWidget(barWidget)) { return; } m_stack->removeWidget(barWidget); barWidget->setAssociatedViewBar(nullptr); barWidget->hide(); disconnect(barWidget, nullptr, this, nullptr); } void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget); Q_ASSERT(!m_permanentBarWidget); m_stack->addWidget(barWidget); m_stack->setCurrentWidget(barWidget); m_stack->show(); m_permanentBarWidget = barWidget; m_permanentBarWidget->show(); setViewBarVisible(true); } void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(m_permanentBarWidget == barWidget); Q_UNUSED(barWidget); const bool hideBar = m_stack->currentWidget() == m_permanentBarWidget; m_permanentBarWidget->hide(); m_stack->removeWidget(m_permanentBarWidget); m_permanentBarWidget = nullptr; if (hideBar) { m_stack->hide(); setViewBarVisible(false); } } bool KateViewBar::hasPermanentWidget(KateViewBarWidget *barWidget) const { return (m_permanentBarWidget == barWidget); } void KateViewBar::showBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget != nullptr); if (barWidget != qobject_cast(m_stack->currentWidget())) { hideCurrentBarWidget(); } // raise correct widget m_stack->setCurrentWidget(barWidget); barWidget->show(); barWidget->setFocus(Qt::ShortcutFocusReason); m_stack->show(); setViewBarVisible(true); } bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const { return m_stack->indexOf(barWidget) != -1; } void KateViewBar::hideCurrentBarWidget() { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (current) { current->closed(); } // if we have any permanent widget, make it visible again if (m_permanentBarWidget) { m_stack->setCurrentWidget (m_permanentBarWidget); } else { // else: hide the bar m_stack->hide(); setViewBarVisible(false); } m_view->setFocus(); } void KateViewBar::setViewBarVisible(bool visible) { if (m_external) { if (visible) { m_view->mainWindow()->showViewBar(m_view); } else { m_view->mainWindow()->hideViewBar(m_view); } } else { setVisible(visible); } } bool KateViewBar::hiddenOrPermanent() const { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (!isVisible() || (m_permanentBarWidget && m_permanentBarWidget == current)) { return true; } return false; } void KateViewBar::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { hideCurrentBarWidget(); return; } QWidget::keyPressEvent(event); } void KateViewBar::hideEvent(QHideEvent *event) { Q_UNUSED(event); // if (!event->spontaneous()) // m_view->setFocus(); } //END KateViewBar related classes KatePasteMenu::KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view) : KActionMenu(text, view) , m_view(view) { connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); } void KatePasteMenu::slotAboutToShow() { menu()->clear(); /** * insert complete paste history */ int i = 0; Q_FOREACH (const QString &text, KTextEditor::EditorPrivate::self()->clipboardHistory()) { /** * get text for the menu ;) */ QString leftPart = (text.size() > 48) ? (text.left(48) + QLatin1String("...")) : text; QAction *a = menu()->addAction(leftPart.replace(QLatin1String("\n"), QLatin1String(" ")), this, SLOT(paste())); a->setData(i++); } } void KatePasteMenu::paste() { if (!sender()) { return; } QAction *action = qobject_cast(sender()); if (!action) { return; } // get index int i = action->data().toInt(); if (i >= KTextEditor::EditorPrivate::self()->clipboardHistory().size()) { return; } // paste m_view->paste(&KTextEditor::EditorPrivate::self()->clipboardHistory()[i]); } diff --git a/src/view/kateviewhelpers.h b/src/view/kateviewhelpers.h index 66fba0fc..75c37046 100644 --- a/src/view/kateviewhelpers.h +++ b/src/view/kateviewhelpers.h @@ -1,634 +1,634 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 Christoph Cullmann 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_VIEW_HELPERS_H__ #define __KATE_VIEW_HELPERS_H__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextline.h" namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } class KateViewInternal; #define MAXFOLDINGCOLORS 16 class KateLineInfo; class KateTextPreview; namespace KTextEditor { class Command; class AnnotationModel; class MovingRange; } class QTimer; class QVBoxLayout; /** * Class to layout KTextEditor::Message%s in KateView. Only the floating * positions TopInView, CenterInView, and BottomInView are supported. * AboveView and BelowView are not supported and ASSERT. */ class KateMessageLayout : public QLayout { public: explicit KateMessageLayout (QWidget *parent); ~KateMessageLayout() override; void addWidget(QWidget *widget, KTextEditor::Message::MessagePosition pos); int count() const override; QLayoutItem *itemAt(int index) const override; void setGeometry(const QRect &rect) override; QSize sizeHint() const override; QLayoutItem *takeAt(int index) override; void add(QLayoutItem *item, KTextEditor::Message::MessagePosition pos); private: void addItem(QLayoutItem *item) override; // never called publically struct ItemWrapper { ItemWrapper(QLayoutItem *i, KTextEditor::Message::MessagePosition pos) : item(i) , position(pos) {} QLayoutItem * item = nullptr; KTextEditor::Message::MessagePosition position; }; QVector m_items; }; /** * This class is required because QScrollBar's sliderMoved() signal is * really supposed to be a sliderDragged() signal... so this way we can capture * MMB slider moves as well * * Also, it adds some useful indicators on the scrollbar. */ class KateScrollBar : public QScrollBar { Q_OBJECT public: KateScrollBar(Qt::Orientation orientation, class KateViewInternal *parent); ~KateScrollBar() override; QSize sizeHint() const override; inline bool showMarks() const { return m_showMarks; } inline void setShowMarks(bool b) { m_showMarks = b; update(); } inline bool showMiniMap() const { return m_showMiniMap; } void setShowMiniMap(bool b); inline bool miniMapAll() const { return m_miniMapAll; } inline void setMiniMapAll(bool b) { m_miniMapAll = b; updateGeometry(); update(); } inline bool miniMapWidth() const { return m_miniMapWidth; } inline void setMiniMapWidth(int width) { m_miniMapWidth = width; updateGeometry(); update(); } inline void queuePixmapUpdate() { m_updateTimer.start(); } Q_SIGNALS: void sliderMMBMoved(int value); protected: void mousePressEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override; void leaveEvent(QEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override; void paintEvent(QPaintEvent *e) override; void resizeEvent(QResizeEvent *) override; void sliderChange(SliderChange change) override; protected Q_SLOTS: void sliderMaybeMoved(int value); void marksChanged(); public Q_SLOTS: void updatePixmap(); private Q_SLOTS: void showTextPreview(); private: void showTextPreviewDelayed(); void hideTextPreview(); void redrawMarks(); void recomputeMarksPositions(); void miniMapPaintEvent(QPaintEvent *e); void normalPaintEvent(QPaintEvent *e); int minimapYToStdY(int y); const QColor charColor(const QVector &attributes, int &attributeIndex, - const QList &decorations, + const QVector &decorations, const QColor &defaultColor, int x, QChar ch); bool m_middleMouseDown; bool m_leftMouseDown; KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; class KateViewInternal *m_viewInternal; QPointer m_textPreview; QTimer m_delayTextPreviewTimer; QHash m_lines; bool m_showMarks; bool m_showMiniMap; bool m_miniMapAll; int m_miniMapWidth; QPixmap m_pixmap; int m_grooveHeight; QRect m_stdGroveRect; QRect m_mapGroveRect; QTimer m_updateTimer; QPoint m_toolTipPos; // lists of lines added/removed recently to avoid scrollbar flickering QHash m_linesAdded; int m_linesModified; static const unsigned char characterOpacity[256]; }; class KateIconBorder : public QWidget { Q_OBJECT public: KateIconBorder(KateViewInternal *internalView, QWidget *parent); ~KateIconBorder() override; // VERY IMPORTANT ;) QSize sizeHint() const override; void updateFont(); int lineNumberWidth() const; void setIconBorderOn(bool enable); void setLineNumbersOn(bool enable); void setRelLineNumbersOn(bool enable); void setAnnotationBorderOn(bool enable); void setDynWrapIndicators(int state); int dynWrapIndicators() const { return m_dynWrapIndicators; } bool dynWrapIndicatorsOn() const { return m_dynWrapIndicatorsOn; } void setFoldingMarkersOn(bool enable); void toggleIconBorder() { setIconBorderOn(!iconBorderOn()); } void toggleLineNumbers() { setLineNumbersOn(!lineNumbersOn()); } void toggleFoldingMarkers() { setFoldingMarkersOn(!foldingMarkersOn()); } inline bool iconBorderOn() const { return m_iconBorderOn; } inline bool lineNumbersOn() const { return m_lineNumbersOn; } inline bool viRelNumbersOn() const { return m_relLineNumbersOn; } inline bool foldingMarkersOn() const { return m_foldingMarkersOn; } inline bool annotationBorderOn() const { return m_annotationBorderOn; } void updateForCursorLineChange(); enum BorderArea { None, LineNumbers, IconBorder, FoldingMarkers, AnnotationBorder, ModificationBorder }; BorderArea positionToArea(const QPoint &) const; public Q_SLOTS: void updateAnnotationBorderWidth(); void updateAnnotationLine(int line); void annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel); void displayRangeChanged(); private: void paintEvent(QPaintEvent *) override; void paintBorder(int x, int y, int width, int height); void mousePressEvent(QMouseEvent *) override; void mouseMoveEvent(QMouseEvent *) override; void mouseReleaseEvent(QMouseEvent *) override; void mouseDoubleClickEvent(QMouseEvent *) override; void leaveEvent(QEvent *event) override; void wheelEvent(QWheelEvent *e) override; void showMarkMenu(uint line, const QPoint &pos); void showAnnotationTooltip(int line, const QPoint &pos); void hideAnnotationTooltip(); void removeAnnotationHovering(); void showAnnotationMenu(int line, const QPoint &pos); int annotationLineWidth(int line); KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; KateViewInternal *m_viewInternal; bool m_iconBorderOn: 1; bool m_lineNumbersOn: 1; bool m_relLineNumbersOn:1; bool m_updateRelLineNumbers:1; bool m_foldingMarkersOn: 1; bool m_dynWrapIndicatorsOn: 1; bool m_annotationBorderOn: 1; int m_dynWrapIndicators; int m_lastClickedLine; int m_cachedLNWidth; qreal m_maxCharWidth; int iconPaneWidth; int m_annotationBorderWidth; mutable QPixmap m_arrow; mutable QColor m_oldBackgroundColor; QPointer m_foldingPreview; KTextEditor::MovingRange *m_foldingRange; int m_nextHighlightBlock; int m_currentBlockLine; QTimer m_delayFoldingHlTimer; void showDelayedBlock(int line); void hideBlock(); private Q_SLOTS: void showBlock(); private: QString m_hoveredAnnotationGroupIdentifier; void initializeFoldingColors(); }; class KateViewEncodingAction: public KSelectAction { Q_OBJECT Q_PROPERTY(QString codecName READ currentCodecName WRITE setCurrentCodec) Q_PROPERTY(int codecMib READ currentCodecMib) public: KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode = false); ~KateViewEncodingAction(); int mibForName(const QString &codecName, bool *ok = nullptr) const; QTextCodec *codecForMib(int mib) const; QTextCodec *currentCodec() const; bool setCurrentCodec(QTextCodec *codec); QString currentCodecName() const; bool setCurrentCodec(const QString &codecName); int currentCodecMib() const; bool setCurrentCodec(int mib); Q_SIGNALS: /** * Specific (proper) codec was selected */ void triggered(QTextCodec *codec); private: KTextEditor::DocumentPrivate *doc; KTextEditor::ViewPrivate *view; class Private { public: Private(KateViewEncodingAction *parent) : q(parent), currentSubAction(nullptr) { } void init(); void _k_subActionTriggered(QAction *); KateViewEncodingAction *q; QAction *currentSubAction; }; Private *const d; Q_PRIVATE_SLOT(d, void _k_subActionTriggered(QAction *)) const bool m_saveAsMode; private Q_SLOTS: void setEncoding(const QString &e); void slotAboutToShow(); }; class KateViewBar; class KateViewBarWidget : public QWidget { Q_OBJECT friend class KateViewBar; public: explicit KateViewBarWidget(bool addCloseButton, QWidget *parent = nullptr); virtual void closed() {} /// returns the currently associated KateViewBar and 0, if it is not associated KateViewBar *viewBar() { return m_viewBar; } protected: /** * @return widget that should be used to add controls to bar widget */ QWidget *centralWidget() { return m_centralWidget; } Q_SIGNALS: void hideMe(); // for friend class KateViewBar private: void setAssociatedViewBar(KateViewBar *bar) { m_viewBar = bar; } private: QWidget *m_centralWidget; KateViewBar *m_viewBar; // 0-pointer, if not added to a view bar }; class KateViewBar : public QWidget { Q_OBJECT public: KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view); /** * Adds a widget to this viewbar. * Widget is initially invisible, you should call showBarWidget, to show it. * Several widgets can be added to the bar, but only one can be visible */ void addBarWidget(KateViewBarWidget *newBarWidget); /** * Removes a widget from this viewbar. * Removing a widget makes sense if it takes a lot of space vertically, * because we use a QStackedWidget to maintain the same height for all * widgets in the viewbar. */ void removeBarWidget(KateViewBarWidget *barWidget); /** * @return if viewbar has widget @p barWidget */ bool hasBarWidget(KateViewBarWidget *barWidget) const; /** * Shows barWidget that was previously added with addBarWidget. * @see hideCurrentBarWidget */ void showBarWidget(KateViewBarWidget *barWidget); /** * Adds widget that will be always shown in the viewbar. * After adding permanent widget viewbar is immediately shown. * ViewBar with permanent widget won't hide itself * until permanent widget is removed. * OTOH showing/hiding regular barWidgets will work as usual * (they will be shown above permanent widget) * * If permanent widget already exists, asserts! */ void addPermanentBarWidget(KateViewBarWidget *barWidget); /** * Removes permanent bar widget from viewbar. * If no other viewbar widgets are shown, viewbar gets hidden. * * barWidget is not deleted, caller must do it if it wishes */ void removePermanentBarWidget(KateViewBarWidget *barWidget); /** * @return if viewbar has permanent widget @p barWidget */ bool hasPermanentWidget(KateViewBarWidget *barWidget) const; /** * @return true if the KateViewBar is hidden or displays a permanentBarWidget */ bool hiddenOrPermanent() const; public Q_SLOTS: /** * Hides currently shown bar widget */ void hideCurrentBarWidget(); protected: void keyPressEvent(QKeyEvent *event) override; void hideEvent(QHideEvent *event) override; private: /** * Shows or hides whole viewbar */ void setViewBarVisible(bool visible); bool m_external; private: KTextEditor::ViewPrivate *m_view; QStackedWidget *m_stack; KateViewBarWidget *m_permanentBarWidget; QVBoxLayout *m_layout; }; class KTEXTEDITOR_EXPORT KateCommandLineBar : public KateViewBarWidget { Q_OBJECT public: explicit KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent = nullptr); ~KateCommandLineBar(); void setText(const QString &text, bool selected = true); void execute(const QString &text); public Q_SLOTS: void showHelpPage(); private: class KateCmdLineEdit *m_lineEdit; }; class KateCmdLineEdit : public KLineEdit { Q_OBJECT public: KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view); bool event(QEvent *e) override; void hideEvent(QHideEvent *e) override; Q_SIGNALS: void hideRequested(); public Q_SLOTS: void slotReturnPressed(const QString &cmd); private Q_SLOTS: void hideLineEdit(); protected: void focusInEvent(QFocusEvent *ev) override; void keyPressEvent(QKeyEvent *ev) override; private: /** * Parse an expression denoting a position in the document. * Return the position as an integer. * Examples of expressions are "10" (the 10th line), * "$" (the last line), "." (the current line), * "'a" (the mark 'a), "/foo/" (a forwards search for "foo"), * and "?bar?" (a backwards search for "bar"). * @param string the expression to parse * @return the position, an integer */ int calculatePosition(QString string); void fromHistory(bool up); QString helptext(const QPoint &) const; KTextEditor::ViewPrivate *m_view; KateCommandLineBar *m_bar; bool m_msgMode; QString m_oldText; uint m_histpos; ///< position in the history uint m_cmdend; ///< the point where a command ends in the text, if we have a valid one. KTextEditor::Command *m_command; ///< For completing flags/args and interactiveness class KateCmdLnWhatsThis *m_help; QTimer *m_hideTimer; }; class KatePasteMenu : public KActionMenu { Q_OBJECT public: KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view); private: KTextEditor::ViewPrivate *m_view; private Q_SLOTS: void slotAboutToShow(); void paste(); }; #endif