diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -147,6 +147,7 @@ view/kateview.cpp view/kateviewinternal.cpp view/kateviewhelpers.cpp +view/kateannotationitemdelegate.cpp view/katemessagewidget.cpp view/katefadeeffect.cpp view/kateanimation.cpp diff --git a/src/include/CMakeLists.txt b/src/include/CMakeLists.txt --- a/src/include/CMakeLists.txt +++ b/src/include/CMakeLists.txt @@ -2,7 +2,7 @@ ecm_generate_headers(KTextEditor_CamelCase_HEADERS HEADER_NAMES AnnotationInterface CodeCompletionModelControllerInterface MovingCursor Range TextHintInterface - Cursor MarkInterface MovingInterface + Cursor MarkInterface MovingInterface AbstractAnnotationItemDelegate Document MovingRange View Attribute Command DocumentCursor Message MovingRangeFeedback SessionConfigInterface CodeCompletionInterface ConfigInterface Editor diff --git a/src/include/ktexteditor/abstractannotationitemdelegate.h b/src/include/ktexteditor/abstractannotationitemdelegate.h new file mode 100644 --- /dev/null +++ b/src/include/ktexteditor/abstractannotationitemdelegate.h @@ -0,0 +1,95 @@ +/* This file is part of the KDE libraries + * Copyright 2017 Friedrich W. H. Kossebau + * + * 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 KTEXTEDITOR_ABSTRACTANNOTATIONITEMDELEGATE_H +#define KTEXTEDITOR_ABSTRACTANNOTATIONITEMDELEGATE_H + +#include + +#include +#include + +class QHelpEvent; +class QSize; +class QPoint; + + +namespace KTextEditor +{ +class AnnotationModel; +class View; + +class KTEXTEDITOR_EXPORT StyleOptionAnnotationItem : public QStyleOption +{ +public: + // TODO: not sure what SO_Default implies, but no clue how to maintain a user type registry? + enum StyleOptionType { Type = SO_Default }; + enum StyleOptionVersion { Version = 1 }; + + int wrappedLine = 0; + int wrappedLineCount = 1; + int visibleWrappedLineInGroup = 0; + bool highlight = true; + + KTextEditor::View* view = nullptr; + /** recommended size for icons or other symbols */ + QSize decorationSize; + QFontMetricsF contentFontMetrics; + + enum AnnotationItemGroupPosition { Invalid, Beginning, Middle, End, OnlyOne }; + + AnnotationItemGroupPosition annotationItemGroupingPosition; + // TODO: make delegate always care for background? + // QBrush backgroundBrush; + + StyleOptionAnnotationItem() : contentFontMetrics(QFont()) {} + StyleOptionAnnotationItem(const StyleOptionAnnotationItem& other) + : QStyleOption(Version, Type), contentFontMetrics(QFont()) { *this = other; } + +protected: + StyleOptionAnnotationItem(int version) : QStyleOption(version, Type), contentFontMetrics(QFont()) {} +}; + + +class KTEXTEDITOR_EXPORT AbstractAnnotationItemDelegate : public QObject +{ + Q_OBJECT + +protected: + explicit AbstractAnnotationItemDelegate(QObject* parent = nullptr) : QObject(parent) {} +public: + virtual ~AbstractAnnotationItemDelegate() {} + +public: + virtual void paint(QPainter *painter, const KTextEditor::StyleOptionAnnotationItem &option, + KTextEditor::AnnotationModel *model, int line) const = 0; + virtual QSize sizeHint(const KTextEditor::StyleOptionAnnotationItem &option, + KTextEditor::AnnotationModel *model, int line) const = 0; + virtual bool helpEvent(QHelpEvent *event, KTextEditor::View *view, + KTextEditor::AnnotationModel *model, int line) = 0; + virtual void contextMenuRequested(const QPoint &pos, + KTextEditor::AnnotationModel *model, int line) = 0; + +Q_SIGNALS: + void sizeHintChanged(KTextEditor::AnnotationModel *model, int line); +}; + +} + +#endif diff --git a/src/include/ktexteditor/annotationinterface.h b/src/include/ktexteditor/annotationinterface.h --- a/src/include/ktexteditor/annotationinterface.h +++ b/src/include/ktexteditor/annotationinterface.h @@ -31,6 +31,7 @@ { class View; +class AbstractAnnotationItemDelegate; /** * \brief An model for providing line annotation information @@ -60,6 +61,7 @@ enum { GroupIdentifierRole = Qt::UserRole }; + // KF6: add AnnotationModelUserRole = Qt::UserRole + 0x100 /** * data() is used to retrieve the information needed to present the @@ -81,7 +83,7 @@ * * \returns a QVariant that contains the data for the given role. */ - virtual QVariant data(int line, Qt::ItemDataRole role) const = 0; + virtual QVariant data(int line, Qt::ItemDataRole role) const = 0; //KF6: use int for role Q_SIGNALS: /** @@ -268,10 +270,23 @@ }; +class KTEXTEDITOR_EXPORT AnnotationViewInterfaceV2 : public AnnotationViewInterface +{ +public: + virtual ~AnnotationViewInterfaceV2() {} + + virtual KTextEditor::AbstractAnnotationItemDelegate* annotationItemDelegate() const = 0; + virtual void setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate) = 0; + + virtual bool uniformAnnotationItemSizes() const = 0; + virtual void setUniformItemSizes(bool enable) = 0; +}; + } Q_DECLARE_INTERFACE(KTextEditor::AnnotationInterface, "org.kde.KTextEditor.AnnotationInterface") Q_DECLARE_INTERFACE(KTextEditor::AnnotationViewInterface, "org.kde.KTextEditor.AnnotationViewInterface") +Q_DECLARE_INTERFACE(KTextEditor::AnnotationViewInterfaceV2, "org.kde.KTextEditor.AnnotationViewInterfaceV2") #endif diff --git a/src/view/kateannotationitemdelegate.h b/src/view/kateannotationitemdelegate.h new file mode 100644 --- /dev/null +++ b/src/view/kateannotationitemdelegate.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 John Firebaugh + Copyright (C) 2001 Anders Lund + Copyright (C) 2001 Christoph Cullmann + Copyright (C) 2017 Friedrich W. H. Kossebau + + 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_ANNOTATIONITEMDELEGATE_H__ +#define __KATE_ANNOTATIONITEMDELEGATE_H__ + +#include + +namespace KTextEditor { +class ViewPrivate; +} +class KateViewInternal; + +class KateAnnotationItemDelegate : public KTextEditor::AbstractAnnotationItemDelegate +{ + Q_OBJECT + +public: + explicit KateAnnotationItemDelegate(KateViewInternal *internalView, QObject *parent); + ~KateAnnotationItemDelegate() override; + +public: + void paint(QPainter *painter, const KTextEditor::StyleOptionAnnotationItem &option, + KTextEditor::AnnotationModel *model, int line) const override; + bool helpEvent(QHelpEvent *event, KTextEditor::View *view, + KTextEditor::AnnotationModel *model, int line) override; + QSize sizeHint(const KTextEditor::StyleOptionAnnotationItem &option, + KTextEditor::AnnotationModel *model, int line) const override; + void contextMenuRequested(const QPoint &pos, + KTextEditor::AnnotationModel *model, int line) override; + +private: + KateViewInternal *m_internalView; + KTextEditor::ViewPrivate *m_view; +}; + +#endif diff --git a/src/view/kateannotationitemdelegate.cpp b/src/view/kateannotationitemdelegate.cpp new file mode 100644 --- /dev/null +++ b/src/view/kateannotationitemdelegate.cpp @@ -0,0 +1,167 @@ +/* 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) + Copyright (C) 2017 Friedrich W. H. Kossebau + + 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 "kateannotationitemdelegate.h" + +#include "kateviewinternal.h" + +#include + +#include +#include +#include +#include +#include + +#include + + +KateAnnotationItemDelegate::KateAnnotationItemDelegate(KateViewInternal *internalView, QObject *parent) + : KTextEditor::AbstractAnnotationItemDelegate(parent) + , m_internalView(internalView) + , m_view(internalView->m_view) +{ +} + +KateAnnotationItemDelegate::~KateAnnotationItemDelegate() +{ +} + +void KateAnnotationItemDelegate::paint(QPainter *painter, const KTextEditor::StyleOptionAnnotationItem &option, + KTextEditor::AnnotationModel *model, int line) const +{ + Q_ASSERT(painter); + Q_ASSERT(model); + if (!painter || !model) { + return; + } + // TODO: also test line for sake of completeness + + painter->save(); + + const int margin = 3; + + const QVariant background = model->data(line, Qt::BackgroundRole); + // Fill the background + if (background.isValid()) { + painter->fillRect(option.rect, background.value()); + } + + const QVariant foreground = model->data(line, Qt::ForegroundRole); + // Set the pen for drawing the foreground + if (foreground.isValid() && foreground.canConvert()) { + painter->setPen(foreground.value()); + } + + // Draw a border around all adjacent entries that have the same text as the currently hovered one + if (option.highlight) { + // draw left and right highlight borders + painter->drawLine(option.rect.topLeft(), option.rect.bottomLeft()); + painter->drawLine(option.rect.topRight(), option.rect.bottomRight()); + + if (((option.annotationItemGroupingPosition == KTextEditor::StyleOptionAnnotationItem::Beginning) || + (option.annotationItemGroupingPosition == KTextEditor::StyleOptionAnnotationItem::OnlyOne)) && + (option.wrappedLine == 0)) { + painter->drawLine(option.rect.topLeft(), option.rect.topRight()); + } + + if (((option.annotationItemGroupingPosition == KTextEditor::StyleOptionAnnotationItem::End) || + (option.annotationItemGroupingPosition == KTextEditor::StyleOptionAnnotationItem::OnlyOne)) && + (option.wrappedLine == (option.wrappedLineCount-1))) { + painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight()); + } + } + // reset pen + if (foreground.isValid()) { + QPen pen = painter->pen(); + pen.setWidth(1); + painter->setPen(pen); + } + + // Now draw the normal text + const QVariant text = model->data(line, Qt::DisplayRole); + if ((option.wrappedLine == 0) && text.isValid() && text.canConvert()) { + painter->drawText(option.rect.x() + margin, option.rect.y(), + option.rect.width() - 2*margin, option.rect.height(), + Qt::AlignLeft | Qt::AlignVCenter, text.toString()); + } + + painter->restore(); +} + +bool KateAnnotationItemDelegate::helpEvent(QHelpEvent *event, KTextEditor::View *view, + KTextEditor::AnnotationModel *model, int line) +{ + if (!model || event->type() != QEvent::ToolTip) { + return false; + } + + const QVariant data = model->data(line, Qt::ToolTipRole); + if (!data.isValid()) { + return false; + } + + const QString toolTipText = data.toString(); + if (!toolTipText.isEmpty()) { + QToolTip::showText(event->globalPos(), toolTipText, view); + } + return true; +} + +void KateAnnotationItemDelegate::contextMenuRequested(const QPoint &pos, + KTextEditor::AnnotationModel *model, int line) +{ + Q_UNUSED(model); + + 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(m_view->mapToGlobal(pos)) == &a) { + m_view->setAnnotationBorderVisible(false); + } +} + +QSize KateAnnotationItemDelegate::sizeHint(const KTextEditor::StyleOptionAnnotationItem &option, + KTextEditor::AnnotationModel *model, int line) const +{ + Q_ASSERT(model); + if (!model) { + return QSize(0, 0); + } + + // TODO: cache maxCharWidth for given font + qreal maxCharWidth = 0.0; + // TODO: why only decimal digits? + // 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(option.contentFontMetrics.width(QChar(i))); + maxCharWidth = qMax(maxCharWidth, charWidth); + } + QVariant data = model->data(line, Qt::DisplayRole); + return QSize(data.toString().length() * maxCharWidth + 8, option.fontMetrics.height()); +} diff --git a/src/view/kateview.h b/src/view/kateview.h --- a/src/view/kateview.h +++ b/src/view/kateview.h @@ -83,13 +83,14 @@ public KTextEditor::TextHintInterface, public KTextEditor::CodeCompletionInterface, public KTextEditor::ConfigInterface, - public KTextEditor::AnnotationViewInterface + public KTextEditor::AnnotationViewInterfaceV2 { Q_OBJECT Q_INTERFACES(KTextEditor::TextHintInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::CodeCompletionInterface) Q_INTERFACES(KTextEditor::AnnotationViewInterface) + Q_INTERFACES(KTextEditor::AnnotationViewInterfaceV2) friend class KTextEditor::View; friend class ::KateViewInternal; @@ -346,6 +347,10 @@ KTextEditor::AnnotationModel *annotationModel() const Q_DECL_OVERRIDE; void setAnnotationBorderVisible(bool visible) Q_DECL_OVERRIDE; bool isAnnotationBorderVisible() const Q_DECL_OVERRIDE; + KTextEditor::AbstractAnnotationItemDelegate* annotationItemDelegate() const Q_DECL_OVERRIDE; + void setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate) Q_DECL_OVERRIDE; + bool uniformAnnotationItemSizes() const Q_DECL_OVERRIDE; + void setUniformItemSizes(bool enable) Q_DECL_OVERRIDE; Q_SIGNALS: void annotationContextMenuAboutToShow(KTextEditor::View *view, QMenu *menu, int line) Q_DECL_OVERRIDE; @@ -670,6 +675,10 @@ KateSpellCheckDialog *m_spell; KateBookmarks *const m_bookmarks; + KTextEditor::AbstractAnnotationItemDelegate *m_annotationItemDelegate; + bool m_uniformAnnotationItemSizes; + bool m_defaultAnnotationItemDelegate; + //* margins QSpacerItem *m_topSpacer; QSpacerItem *m_leftSpacer; diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -27,6 +27,7 @@ #include "kateviewinternal.h" #include "kateviewhelpers.h" +#include "kateannotationitemdelegate.h" #include "katerenderer.h" #include "katedocument.h" #include "kateundomanager.h" @@ -121,6 +122,9 @@ , m_viewInternal(new KateViewInternal(this)) , m_spell(new KateSpellCheckDialog(this)) , m_bookmarks(new KateBookmarks(this)) + , m_annotationItemDelegate(new KateAnnotationItemDelegate(m_viewInternal, this)) + , m_uniformAnnotationItemSizes(true) + , m_defaultAnnotationItemDelegate(true) , m_topSpacer(new QSpacerItem(0,0)) , m_leftSpacer(new QSpacerItem(0,0)) , m_rightSpacer(new QSpacerItem(0,0)) @@ -3251,6 +3255,47 @@ return m_viewInternal->m_leftBorder->annotationBorderOn(); } +KTextEditor::AbstractAnnotationItemDelegate* KTextEditor::ViewPrivate::annotationItemDelegate() const +{ + return m_annotationItemDelegate; +} + +void KTextEditor::ViewPrivate::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate) +{ + if (delegate == m_annotationItemDelegate) { + return; + } + + if (!delegate) { + // reset to default delegate + if (m_defaultAnnotationItemDelegate) { + return; + } + m_annotationItemDelegate = new KateAnnotationItemDelegate(m_viewInternal, this); + m_defaultAnnotationItemDelegate = true; + } else { + // drop default delegate + if (m_defaultAnnotationItemDelegate) { + delete m_annotationItemDelegate; + m_defaultAnnotationItemDelegate = false; + } + + m_annotationItemDelegate = delegate; + } + + // TODO: trigger relayout +} + +bool KTextEditor::ViewPrivate::uniformAnnotationItemSizes() const +{ + return m_uniformAnnotationItemSizes; +} + +void KTextEditor::ViewPrivate::setUniformItemSizes(bool enable) +{ + m_uniformAnnotationItemSizes = enable; +} + KTextEditor::Range KTextEditor::ViewPrivate::visibleRange() { //ensure that the view is up-to-date, otherwise 'endPos()' might fail! diff --git a/src/view/kateviewhelpers.h b/src/view/kateviewhelpers.h --- a/src/view/kateviewhelpers.h +++ b/src/view/kateviewhelpers.h @@ -41,6 +41,7 @@ namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } +namespace KTextEditor { class StyleOptionAnnotationItem; } class KateViewInternal; #define MAXFOLDINGCOLORS 16 @@ -269,11 +270,10 @@ 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); + + void initStyleOption(KTextEditor::StyleOptionAnnotationItem *styleOption) const; KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; diff --git a/src/view/kateviewhelpers.cpp b/src/view/kateviewhelpers.cpp --- a/src/view/kateviewhelpers.cpp +++ b/src/view/kateviewhelpers.cpp @@ -27,6 +27,7 @@ #include "katecmd.h" #include #include +#include #include #include "kateconfig.h" #include "katedocument.h" @@ -1622,6 +1623,194 @@ painter.setRenderHint(QPainter::Antialiasing, false); } +class KateAnnotationGroupIdentifier +{ +public: + KateAnnotationGroupIdentifier(const QVariant &identifier) + : m_isValid(identifier.isValid() && identifier.canConvert()) + , m_id(m_isValid ? identifier.toString() : QString()) + { + } + KateAnnotationGroupIdentifier() = default; + KateAnnotationGroupIdentifier(const KateAnnotationGroupIdentifier &rhs) = default; + + KateAnnotationGroupIdentifier& operator=(const KateAnnotationGroupIdentifier &rhs) + { + m_isValid = rhs.m_isValid; + m_id = rhs.m_id; + return *this; + } + KateAnnotationGroupIdentifier& operator=(const QVariant &identifier) + { + m_isValid = (identifier.isValid() && identifier.canConvert()); + if (m_isValid) { + m_id = identifier.toString(); + } else { + m_id.clear(); + } + return *this; + } + + bool operator==(const KateAnnotationGroupIdentifier &rhs) const + { + return (m_isValid == rhs.m_isValid) && (!m_isValid || (m_id == rhs.m_id)); + } + bool operator!=(const KateAnnotationGroupIdentifier &rhs) const + { + return (m_isValid != rhs.m_isValid) || (m_isValid && (m_id != rhs.m_id)); + } + + void clear() + { + m_isValid = false; + m_id.clear(); + } + bool isValid() const { return m_isValid; } + const QString& id() const { return m_id; } + +private: + bool m_isValid = false; + QString m_id; +}; + +class KateAnnotationGroupPositionState +{ +public: + KateAnnotationGroupPositionState(KateViewInternal *viewInternal, + KTextEditor::AnnotationModel *model, + uint startz, + bool isUsed); + void nextLine(uint z, int realLine, + const QString &highlightedAnnotationGroupIdentifier, + KTextEditor::StyleOptionAnnotationItem &styleOption); + +private: + KateViewInternal *m_viewInternal; + KTextEditor::AnnotationModel *m_model = nullptr; + + int m_visibleWrappedLineInAnnotationGroup = -1; + KateAnnotationGroupIdentifier m_lastAnnotationGroupIdentifier; + KateAnnotationGroupIdentifier m_nextAnnotationGroupIdentifier; + bool m_isSameAnnotationGroupsSinceLast = false; +}; + +KateAnnotationGroupPositionState::KateAnnotationGroupPositionState(KateViewInternal *viewInternal, + KTextEditor::AnnotationModel *model, + uint startz, + bool isUsed) + : m_viewInternal(viewInternal) + , m_model(model) +{ + if (!isUsed) { + return; + } + + if (!m_model || (startz >= m_viewInternal->cache()->viewCacheLineCount())) { + return; + } + + const auto realLineAtStart = m_viewInternal->cache()->viewLine(startz).line(); + m_nextAnnotationGroupIdentifier = m_model->data(realLineAtStart, + (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole); + if (m_nextAnnotationGroupIdentifier.isValid()) { + // estimate state of annotation group before first rendered line + if (startz == 0) { + if (realLineAtStart > 0) { + // TODO: here we would want to scan until the next line that would be displayed, + // to see if there are any group changes until then + // for now simply taking neighbour line into account, not a grave bug on the first displayed line + m_lastAnnotationGroupIdentifier = m_model->data(realLineAtStart - 1, + (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole); + m_isSameAnnotationGroupsSinceLast = (m_lastAnnotationGroupIdentifier == m_nextAnnotationGroupIdentifier); + } + } else { + const auto realLineBeforeStart = m_viewInternal->cache()->viewLine(startz-1).line(); + m_lastAnnotationGroupIdentifier = m_model->data(realLineBeforeStart, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole); + if (m_lastAnnotationGroupIdentifier.isValid()) { + if (m_lastAnnotationGroupIdentifier.id() == m_nextAnnotationGroupIdentifier.id()) { + m_isSameAnnotationGroupsSinceLast = true; + // estimate m_visibleWrappedLineInAnnotationGroup + m_visibleWrappedLineInAnnotationGroup = 0; + for (uint z = startz - 1; z > 0; --z) { + const auto realLine = m_viewInternal->cache()->viewLine(startz-1).line(); + const KateAnnotationGroupIdentifier identifier = m_model->data(realLine, (Qt::ItemDataRole)KTextEditor::AnnotationModel::GroupIdentifierRole); + if (identifier != m_lastAnnotationGroupIdentifier) { + break; + } + ++m_visibleWrappedLineInAnnotationGroup; + } + } + } + } + } +} + +void KateAnnotationGroupPositionState::nextLine(uint z, int realLine, + const QString &highlightedAnnotationGroupIdentifier, + KTextEditor::StyleOptionAnnotationItem &styleOption) +{ + styleOption.wrappedLine = m_viewInternal->cache()->viewLine(z).viewLine(); + styleOption.wrappedLineCount = m_viewInternal->cache()->viewLineCount(realLine); + + // Estimate position in group + const KateAnnotationGroupIdentifier annotationGroupIdentifier = m_nextAnnotationGroupIdentifier; + bool isSameAnnotationGroupsSinceThis = false; + // Calculate next line's group identifier + // shortcut: assuming wrapped lines are always displayed together, test is simple + if (styleOption.wrappedLine+1 < styleOption.wrappedLineCount) { + m_nextAnnotationGroupIdentifier = annotationGroupIdentifier; + isSameAnnotationGroupsSinceThis = true; + } else { + if (z+1 < m_viewInternal->cache()->viewCacheLineCount()) { + const int realLineAfter = m_viewInternal->cache()->viewLine(z+1).line(); + // search for any realLine with a different group id, also the non-displayed + int rl = realLine + 1; + for (; rl <= realLineAfter; ++rl) { + m_nextAnnotationGroupIdentifier = m_model->data(rl, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole); + if (!m_nextAnnotationGroupIdentifier.isValid() || + (m_nextAnnotationGroupIdentifier.id() != annotationGroupIdentifier.id())) { + break; + } + } + isSameAnnotationGroupsSinceThis = (rl > realLineAfter); + if (rl < realLineAfter) { + m_nextAnnotationGroupIdentifier = m_model->data(realLineAfter, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole); + } + } else { + // TODO: check next line after display end + m_nextAnnotationGroupIdentifier.clear(); + isSameAnnotationGroupsSinceThis = false; + } + } + + if (annotationGroupIdentifier.isValid()) { + styleOption.highlight = (highlightedAnnotationGroupIdentifier == annotationGroupIdentifier.id()); + + if (m_isSameAnnotationGroupsSinceLast) { + ++m_visibleWrappedLineInAnnotationGroup; + } else { + m_visibleWrappedLineInAnnotationGroup = 0; + } + + styleOption.annotationItemGroupingPosition = + (m_isSameAnnotationGroupsSinceLast && isSameAnnotationGroupsSinceThis) ? + StyleOptionAnnotationItem::Middle : + (m_isSameAnnotationGroupsSinceLast) ? + StyleOptionAnnotationItem::End : + (isSameAnnotationGroupsSinceThis) ? + StyleOptionAnnotationItem::Beginning : + /* else */ + StyleOptionAnnotationItem::OnlyOne; + } else { + m_visibleWrappedLineInAnnotationGroup = 0; + } + styleOption.visibleWrappedLineInGroup = m_visibleWrappedLineInAnnotationGroup; + + m_lastAnnotationGroupIdentifier = m_nextAnnotationGroupIdentifier; + m_isSameAnnotationGroupsSinceLast = isSameAnnotationGroupsSinceThis; +} + + void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height) { uint h = m_view->renderer()->lineHeight(); @@ -1660,6 +1849,8 @@ KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); + KateAnnotationGroupPositionState annotationGroupPositionState(m_viewInternal, model, + startz, m_annotationBorderOn); for (uint z = startz; z <= endz; z++) { int y = h * z; @@ -1721,60 +1912,22 @@ 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); + const int borderX = lnX + m_annotationBorderWidth; + p.drawLine(borderX, y, borderX, 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()); - } + KTextEditor::StyleOptionAnnotationItem styleOption; + initStyleOption(&styleOption); + styleOption.rect.setRect(lnX, y, m_annotationBorderWidth, h); - // 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); - } + annotationGroupPositionState.nextLine(z, realLine, m_hoveredAnnotationGroupIdentifier, + styleOption); - // 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()); - } + m_view->annotationItemDelegate()->paint(&p, styleOption, model, realLine); } - // adjust current X position and reset the pen and brush - lnX += borderWidth + 2; + // adjust current X position + lnX += m_annotationBorderWidth + /* separator line width */1; } // line number @@ -2106,7 +2259,10 @@ if (model) { m_hoveredAnnotationGroupIdentifier = model->data( t.line(), (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ).toString(); - showAnnotationTooltip(t.line(), e->globalPos()); + const QPoint viewRelativePos = m_viewInternal->mapFromGlobal(e->globalPos()); + QHelpEvent helpEvent(QEvent::ToolTip, viewRelativePos, e->globalPos()); + m_view->annotationItemDelegate()->helpEvent(&helpEvent, m_view, model, t.line()); + QTimer::singleShot(0, this, SLOT(update())); } } else { @@ -2192,7 +2348,10 @@ if (e->button() == Qt::LeftButton && singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } else if (e->button() == Qt::RightButton) { - showAnnotationMenu(cursorOnLine, e->globalPos()); + KTextEditor::AnnotationModel *model = m_view->annotationModel() ? + m_view->annotationModel() : m_doc->annotationModel(); + const QPoint pos = m_view->mapFromGlobal(e->globalPos()); + m_view->annotationItemDelegate()->contextMenuRequested(pos, model, cursorOnLine); } } } @@ -2298,70 +2457,60 @@ } } -void KateIconBorder::showAnnotationTooltip(int line, const QPoint &pos) +void KateIconBorder::initStyleOption(KTextEditor::StyleOptionAnnotationItem* styleOption) const { - 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); - } - } + styleOption->initFrom(this); + styleOption->view = m_view; + styleOption->contentFontMetrics = m_view->renderer()->config()->fontMetrics(); + // TODO have delegate paint all background or not? +// styleOption->backgroundBrush = m_view->renderer()->config()->iconBarColor(); } -int KateIconBorder::annotationLineWidth(int line) +void KateIconBorder::updateAnnotationLine(int line) { + // TODO: where is that magic number from? + int width = 8; 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; + KTextEditor::StyleOptionAnnotationItem styleOption; + initStyleOption(&styleOption); + width = m_view->annotationItemDelegate()->sizeHint(styleOption, model, line).width(); } - return 8; -} -void KateIconBorder::updateAnnotationLine(int line) -{ - if (annotationLineWidth(line) > m_annotationBorderWidth) { - m_annotationBorderWidth = annotationLineWidth(line); + if (width > m_annotationBorderWidth) { + m_annotationBorderWidth = width; 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() { + // TODO: another magic number, not matching the one in updateAnnotationLine() 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; + KTextEditor::StyleOptionAnnotationItem styleOption; + initStyleOption(&styleOption); + + const int lineCount = m_view->doc()->lines(); + if (lineCount > 0) { + const int checkedLineCount = m_view->uniformAnnotationItemSizes() ? 1 : lineCount; + for (int i = 0; i < checkedLineCount; ++i) { + const int curwidth = m_view->annotationItemDelegate()->sizeHint(styleOption, model, i).width(); + if (curwidth > m_annotationBorderWidth) { + m_annotationBorderWidth = curwidth; + } } } } diff --git a/src/view/kateviewinternal.h b/src/view/kateviewinternal.h --- a/src/view/kateviewinternal.h +++ b/src/view/kateviewinternal.h @@ -51,6 +51,8 @@ class KateIconBorder; class KateScrollBar; +class KateAnnotationItemDelegate; +class KateAnnotationGroupPositionState; class KateTextLayout; class KateTextAnimation; class KateAbstractInputMode; @@ -64,6 +66,8 @@ friend class KTextEditor::ViewPrivate; friend class KateIconBorder; friend class KateScrollBar; + friend class KateAnnotationItemDelegate; + friend class KateAnnotationGroupPositionState; friend class CalculatingCursor; friend class BoundedCursor; friend class WrappingCursor;