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" 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/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_view->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(), currentFont()); + } 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(), currentFont(), paint); + paint.restore(); + } + // draw word-wrap-honor-indent filling if ((range->viewLineCount() > 1) && range->shiftX() && (range->shiftX() > xStart)) { if (backgroundBrushSet) @@ -1008,7 +1041,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_view->inlineNotes(lineLayout->line()); + foreach (const KTextEditor::InlineNote& inlineNote, inlineNotes) { + int column = inlineNote.column(); + int width = inlineNote.width(lineHeight(), currentFont()); + + // 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(); @@ -1034,7 +1090,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" @@ -210,6 +211,18 @@ TextHintProvider::~TextHintProvider() {} +InlineNoteInterface::InlineNoteInterface() +{} + +InlineNoteInterface::~InlineNoteInterface() +{} + +InlineNoteProvider::InlineNoteProvider() +{} + +InlineNoteProvider::~InlineNoteProvider() +{} + 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 @@ -84,13 +85,15 @@ public KTextEditor::TextHintInterface, public KTextEditor::CodeCompletionInterface, public KTextEditor::ConfigInterface, + public KTextEditor::InlineNoteInterface, public KTextEditor::AnnotationViewInterface { Q_OBJECT Q_INTERFACES(KTextEditor::TextHintInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::CodeCompletionInterface) Q_INTERFACES(KTextEditor::AnnotationViewInterface) + Q_INTERFACES(KTextEditor::InlineNoteInterface) friend class KTextEditor::View; friend class ::KateViewInternal; @@ -250,6 +253,23 @@ return m_hasWrap; } + // + // Inline Notes Interface + // +public: + void registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) Q_DECL_OVERRIDE; + void unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) Q_DECL_OVERRIDE; + + QVarLengthArray inlineNotes(int line) const; + +private: + QVector m_inlineNoteProviders; + +private Q_SLOTS: + void inlineNotesReset(); + void inlineNotesLineChanged(int line); + + // // KTextEditor::SelectionInterface stuff // diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -3644,6 +3644,61 @@ //END +//BEGIN KTextEditor::InlineNoteInterface +void KTextEditor::ViewPrivate::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::ViewPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) +{ + const int index = m_inlineNoteProviders.indexOf(provider); + if (index >= 0) { + m_inlineNoteProviders.removeAt(index); + + disconnect(provider, nullptr, this, nullptr); + + inlineNotesReset(); + } +} + +QVarLengthArray KTextEditor::ViewPrivate::inlineNotes(int line) const +{ + QVarLengthArray allInlineNotes; + for (KTextEditor::InlineNoteProvider *provider: m_inlineNoteProviders) { + int index = 0; + for (auto column: provider->inlineNotes(line)) { + allInlineNotes.append({ + provider, + {line, column}, + index + }); + index++; + } + } + return allInlineNotes; +} + +void KTextEditor::ViewPrivate::inlineNotesReset() +{ + repaintText(false); +} + +void KTextEditor::ViewPrivate::inlineNotesLineChanged(int line) +{ + tagLines(line, line); + repaintText(false); +} + +//END KTextEditor::InlineNoteInterface + KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const { KateRendererConfig * renderConfig = const_cast(this)->renderer()->config();