diff --git a/src/document/katedocument.h b/src/document/katedocument.h --- a/src/document/katedocument.h +++ b/src/document/katedocument.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "katetextline.h" @@ -81,14 +82,16 @@ public KTextEditor::ConfigInterface, public KTextEditor::AnnotationInterface, public KTextEditor::MovingInterface, + public KTextEditor::InlineNoteInterface, private KTextEditor::MovingRangeFeedback { Q_OBJECT Q_INTERFACES(KTextEditor::MarkInterface) Q_INTERFACES(KTextEditor::ModificationInterface) Q_INTERFACES(KTextEditor::AnnotationInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::MovingInterface) + Q_INTERFACES(KTextEditor::InlineNoteInterface) friend class KTextEditor::Document; friend class ::KateDocumentTest; @@ -721,6 +724,22 @@ private: KTextEditor::AnnotationModel *m_annotationModel; + // + // Inline Notes Interface + // +public: + void registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) Q_DECL_OVERRIDE; + void unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) Q_DECL_OVERRIDE; + + QVector inlineNotes(int line) const; + +private: + QVector m_inlineNoteProviders; + +private Q_SLOTS: + void inlineNotesReset(); + void inlineNotesLineChanged(int line); + // // KParts::ReadWrite stuff // diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -5286,6 +5286,59 @@ } //END KTextEditor::AnnotationInterface +//BEGIN KTextEditor::InlineNoteInterface +void KTextEditor::DocumentPrivate::registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) +{ + if (! m_inlineNoteProviders.contains(provider)) { + m_inlineNoteProviders.append(provider); + + connect(provider, SIGNAL(reset()), this, SLOT(inlineNotesReset())); + connect(provider, SIGNAL(lineChanged(int)), this, SLOT(inlineNotesLineChanged(int))); + + inlineNotesReset(); + } +} + +void KTextEditor::DocumentPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) +{ + const int index = m_inlineNoteProviders.indexOf(provider); + if (index >= 0) { + m_inlineNoteProviders.removeAt(index); + + disconnect(provider, 0, this, 0); + + inlineNotesReset(); + } +} + +QVector KTextEditor::DocumentPrivate::inlineNotes(int line) const +{ + // Common cases first + switch (m_inlineNoteProviders.size()) { + case 0: return {}; + case 1: return m_inlineNoteProviders.first()->inlineNotes(line); + } + + QVector allInlineNotes; + foreach (KTextEditor::InlineNoteProvider *provider, m_inlineNoteProviders) { + allInlineNotes.append(provider->inlineNotes(line)); + } + return allInlineNotes; +} + +void KTextEditor::DocumentPrivate::inlineNotesReset() +{ + repaintViews(false); +} + +void KTextEditor::DocumentPrivate::inlineNotesLineChanged(int line) +{ + tagLines(line, line); + repaintViews(false); +} + +//END KTextEditor::InlineNoteInterface + //TAKEN FROM kparts.h bool KTextEditor::DocumentPrivate::queryClose() { 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 InlineNoteInterface Document MovingRange View Attribute Command DocumentCursor Message MovingRangeFeedback SessionConfigInterface CodeCompletionInterface ConfigInterface Editor diff --git a/src/include/ktexteditor/inlinenoteinterface.h b/src/include/ktexteditor/inlinenoteinterface.h new file mode 100755 --- /dev/null +++ b/src/include/ktexteditor/inlinenoteinterface.h @@ -0,0 +1,201 @@ +/* This file is part of the KDE libraries + + 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_INLINENOTEINTERFACE_H +#define KTEXTEDITOR_INLINENOTEINTERFACE_H + +#include + +#include + +#include + +class QFont; +class QFontMetricsF; +class QPainter; + +namespace KTextEditor +{ + +class InlineNoteProvider; +class View; + +/** + * \brief Inline notes interface for rendering notes in the text. + * + * \ingroup kte_group_view_extensions + * + * \section inlinenote_introduction Introduction + * + * The inline notes interface provides a way to render arbitrary things in + * the text. The layout of the line is adapted to create space for the note. + * Possible applications include showing a name of a function parameter on + * call side or rendering square with color preview next to CSS color + * property. + * + * To register as inline note provider, call registerInlineNoteProvider() with + * an instance that inherits InlineNoteProvider. Finally, make sure you remove + * your inline note provider by calling unregisterInlineNoteProvider(). + * + * \section inlinenote_access Accessing the InlineNoteInterface + * + * The InlineNoteInterface is an extension interface for a + * View, i.e. the View inherits the interface. Use qobject_cast to access the + * interface: + * \code + * // view is of type KTextEditor::View* + * KTextEditor::InlineNoteInterface *iface = + * qobject_cast(view); + * + * if (iface) { + * // the implementation supports the interface + * // myProvider inherits KTextEditor::InlineNoteProvider + * iface->registerInlineNoteProvider(myProvider); + * } + * \endcode + * + * \see InlineNoteProvider + */ +class KTEXTEDITOR_EXPORT InlineNoteInterface +{ +public: + InlineNoteInterface(); + virtual ~InlineNoteInterface(); + + /** + * Register the inline note provider \p provider. + * + * Whenever a line is painted, the \p provider will be queried for notes + * that should be painted in it. When the provider is about to be + * destroyed, make sure to call unregisterTextHintProvider() to avoid a + * dangling pointer. + * + * \param provider inline note provider + * \see unregisterInlineNoteProvider(), InlineNoteProvider + */ + virtual void registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) = 0; + + /** + * Unregister the inline note provider \p provider. + * + * \param provider inline note provider to unregister + * \see registerInlineNoteProvider(), InlineNoteProvider + */ + virtual void unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) = 0; +}; + +class KTEXTEDITOR_EXPORT InlineNote +{ +public: + /** + * Virtual destructor to allow inheritance. + */ + virtual ~InlineNote(); + + /** + * Column on which the note is located. + * + * 0 means the note is located before the first character of the line. + * 1 means the note is located after the first character, etc. If the + * returned number is bigger than the length of the line, the note will be + * placed behind the text as if there were additional spaces. + */ + virtual int column() const = 0; + + /** + * Width to be reserved for the note in the text. + * + * The method is given the height of the line and the metrics of current + * font which it may use for calculating the width. + * + * \param height the height of the line in pixels + * \param fontMetrics the QFontMetricsF of the font used by the editor + * + * \return the width of the note in pixels + */ + virtual qreal width(qreal height, const QFontMetricsF &fontMetrics) const = 0; + + /** + * Paint the note into the line. + * + * The method should use the given painter to render the note into the + * line. The painter is translated such that coordinates 0x0 mark the top + * left corner of the note. The method should not paint outside rectangle + * given by the height parameter and the width previously returned by the + * width method. + * + * The method is given the height of the line, the metrics of current font + * and the font which it may use during painting. + * + * \param height the height of the line in pixels + * \param fontMetrics the QFontMetricsF of the font used by the editor + * \param font the QFont used in the editor + * \param painter painter prepared for rendering the note + */ + virtual void paint(qreal height, const QFontMetricsF &fontMetrics, const QFont &font, QPainter &painter) const = 0; +}; + +/** + * \brief A source of inline notes for a document. + * + * InlineNoteProvider is object that can be queried for inline notes in the + * document. It emits signals when the notes change and should be queried again. + * + * \see InlineNoteInterface + */ +class KTEXTEDITOR_EXPORT InlineNoteProvider : public QObject +{ + Q_OBJECT + +public: + /** + * Default constructor. + */ + InlineNoteProvider(); + + /** + * Virtual destructor to allow inheritance. + */ + virtual ~InlineNoteProvider(); + + /** + * Get list of inline notes for given line. + * + * \param line Line number + * \returns vector of pointers to InlineNote objects belonging to the line + */ + virtual QVector inlineNotes(int line) = 0; + +Q_SIGNALS: + /** + * The provider should emit the signal reset() when almost all inline notes + * changed. + */ + void reset(); + + /** + * The provider should emit the signal lineChanged() when any of the + * inline notes on the line changed. + */ + void lineChanged(int line); +}; + +} + +Q_DECLARE_INTERFACE(KTextEditor::InlineNoteInterface, "org.kde.KTextEditor.InlineNoteInterface") + +#endif diff --git a/src/render/katerenderer.cpp b/src/render/katerenderer.cpp --- a/src/render/katerenderer.cpp +++ b/src/render/katerenderer.cpp @@ -31,8 +31,11 @@ #include "katetextlayout.h" #include "katebuffer.h" +#include "ktexteditor/inlinenoteinterface.h" + #include "katepartdebug.h" +#include #include #include #include @@ -758,6 +761,36 @@ } } + // Draw inline notes + auto inlineNotes = m_doc->inlineNotes(range->line()); + foreach (const KTextEditor::InlineNote *inlineNote, inlineNotes) { + int column = inlineNote->column(); + int viewLine = range->viewLineForColumn(column); + + // Determine the position where to paint the note. + // We start by getting the x coordinate of cursor placed to the column. + qreal x = range->viewLine(viewLine).lineLayout().cursorToX(column) - xStart; + int textLength = range->length(); + if (column == 0 || column < textLength) { + // If the note is inside text or at the beginning, then there is a hole in the text where the + // note should be painted and the cursor gets placed at the right side of it. So we have to + // subtract the width of the note to get to left side of the hole. + x -= inlineNote->width(lineHeight(), currentFontMetrics()); + } else { + // If the note is outside the text, then the X coordinate is located at the end of the line. + // Add appropriate amount of spaces to reach the required column. + x += spaceWidth() * (column - textLength); + } + + qreal y = lineHeight() * viewLine; + + // Paint the note + paint.save(); + paint.translate(x, y); + inlineNote->paint(lineHeight(), currentFontMetrics(), currentFont(), paint); + paint.restore(); + } + // draw word-wrap-honor-indent filling if ((range->viewLineCount() > 1) && range->shiftX() && (range->shiftX() > xStart)) { if (backgroundBrushSet) @@ -1007,7 +1040,30 @@ l->setTextOption(opt); // Syntax highlighting, inbuilt and arbitrary - l->setAdditionalFormats(decorationsForLine(textLine, lineLayout->line())); + QList decorations = decorationsForLine(textLine, lineLayout->line()); + + int firstLineOffset = 0; + + auto inlineNotes = m_doc->inlineNotes(lineLayout->line()); + foreach (const KTextEditor::InlineNote *inlineNote, inlineNotes) { + int column = inlineNote->column(); + int width = inlineNote->width(lineHeight(), currentFontMetrics()); + + // Make space for every inline note. + // If it is on column 0 (at the beginning of the line), we must offset the first line. + // If it is inside the text, we use absolute letter spacing to create space for it between the two letters. + // If it is outside of the text, we don't have to make space for it. + if (column == 0) { + firstLineOffset = width; + } else if (column < l->text().length()) { + QTextCharFormat text_char_format; + text_char_format.setFontLetterSpacing(width); + text_char_format.setFontLetterSpacingType(QFont::AbsoluteSpacing); + decorations.append(QTextLayout::FormatRange { column - 1, 1, text_char_format }); + } + } + + l->setAdditionalFormats(decorations); // Begin layouting l->beginLayout(); @@ -1033,7 +1089,7 @@ // we include the leading, this must match the ::updateFontHeight code! line.setLeadingIncluded(true); - line.setPosition(QPoint(line.lineNumber() ? shiftX : 0, height)); + line.setPosition(QPoint(line.lineNumber() ? shiftX : firstLineOffset, height)); if (needShiftX && line.width() > 0) { diff --git a/src/utils/ktexteditor.cpp b/src/utils/ktexteditor.cpp --- a/src/utils/ktexteditor.cpp +++ b/src/utils/ktexteditor.cpp @@ -32,6 +32,7 @@ #include "plugin.h" #include "command.h" +#include "inlinenoteinterface.h" #include "markinterface.h" #include "modificationinterface.h" #include "sessionconfiginterface.h" @@ -215,6 +216,21 @@ TextHintProvider::~TextHintProvider() {} +InlineNoteInterface::InlineNoteInterface() +{} + +InlineNoteInterface::~InlineNoteInterface() +{} + +InlineNoteProvider::InlineNoteProvider() +{} + +InlineNoteProvider::~InlineNoteProvider() +{} + +InlineNote::~InlineNote() +{} + Command::Command(const QStringList &cmds, QObject *parent) : QObject(parent) , m_cmds (cmds) diff --git a/src/view/kateview.h b/src/view/kateview.h --- a/src/view/kateview.h +++ b/src/view/kateview.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include