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 InlineNote InlineNoteProvider InlineNoteInterface Document MovingRange View Attribute Command DocumentCursor Message MovingRangeFeedback SessionConfigInterface CodeCompletionInterface ConfigInterface Editor diff --git a/src/include/ktexteditor/inlinenote.h b/src/include/ktexteditor/inlinenote.h new file mode 100644 --- /dev/null +++ b/src/include/ktexteditor/inlinenote.h @@ -0,0 +1,145 @@ +/* This file is part of the KDE libraries + + Copyright 2018 Sven Brauch + Copyright 2018 Michal Srb + + 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 3 of the License, or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 6 of version 3 of the license. + + 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_INLINENOTE_H +#define KTEXTEDITOR_INLINENOTE_H + +#include +#include + +class QFont; +namespace KTextEditor { class InlineNoteProvider; } + +namespace KTextEditor { + +/** + * Describes an inline note. + * + * This structure contains all the information required to deal with + * a particular inline note. It is instantiated and populated with information + * internally by KTextEditor based on the list of notes returned by + * InlineNoteProvider::inlineNotes(), and then passed back to the user of the API. + * + * Users of the InlineNoteInterface API should never need to modify instances + * of this structure. + * + * @since 5.50 + */ +class KTEXTEDITOR_EXPORT InlineNote +{ +public: + /** + * Constructs an inline note. User code usually does not need to call this, + * notes are created from the columns returned by InlineNoteProvider::inlineNotes(int line), + * and then passed around as handles grouping useful information. + */ + InlineNote(InlineNoteProvider* provider, const KTextEditor::Cursor& position, int index, + const KTextEditor::View* view, QFont font, int lineHeight, bool hasFocus); + + /** + * Constructs an invalid inline note, i.e. isValid() will return false. + */ + InlineNote(); + + /** + * Returns the column this note appears in. + */ + int column() const; + + /** + * Returns the width of this note in pixels. + */ + qreal width() const; + + /** + * Tells whether this note is valid, i.e. whether it has a valid provider and location set. + */ + bool isValid() const; + + /** + * Equality of notes. Only checks provider, index, and position. + */ + bool operator==(const InlineNote& other) const; + + /** + * Transforms the given @p pos from note coordinates to global (screen) coordinates. + * + * Useful for showing a popup; to e.g. show a popup at the bottom left corner + * of a note, show it at @c mapToGlobal({0, noteHeight}). + */ + QPoint mapToGlobal(const QPoint& pos) const; + + /** + * The provider which created this note + */ + InlineNoteProvider* provider() const; + + /** + * The view this note is shown in + */ + const KTextEditor::View* view() const; + + /** + * The position of this note + */ + KTextEditor::Cursor position() const; + + /** + * The index of this note, i.e. its index in the vector returned by + * the provider for a given line + */ + int index() const; + + /** + * Whether the mouse cursor is currently over this note; only set + * when paintInlineNote() is called + */ + bool hasFocus() const; + + /** + * The font of the text surrounding this note + */ + QFont font() const; + + /** + * The height of the line containing this note + */ + int lineHeight() const; + +private: + InlineNoteProvider* m_provider = nullptr; + const KTextEditor::View* m_view = nullptr; + KTextEditor::Cursor m_position; + int m_index = -1; + bool m_hasFocus = false; + QFont m_font; + int m_lineHeight = -1; + + // For future use, in case members need to be added. + // TODO KF6: remove if it turns out this is unneeded. + class InlineNotePrivate* d = nullptr; +}; + +} + +#endif 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,113 @@ +/* This file is part of the KDE libraries + + Copyright 2018 Sven Brauch + Copyright 2018 Michal Srb + + 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 3 of the License, or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 6 of version 3 of the license. + + 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 +#include + +class QPainter; + +namespace KTextEditor +{ + +class InlineNoteProvider; + +/** + * @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 + * @see InlineNote + * + * @author Sven Brauch, Michal Srb + * @since 5.50 + */ +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; +}; + +} + +Q_DECLARE_INTERFACE(KTextEditor::InlineNoteInterface, "org.kde.KTextEditor.InlineNoteInterface") + +#endif diff --git a/src/include/ktexteditor/inlinenoteprovider.h b/src/include/ktexteditor/inlinenoteprovider.h new file mode 100644 --- /dev/null +++ b/src/include/ktexteditor/inlinenoteprovider.h @@ -0,0 +1,159 @@ +/* This file is part of the KDE libraries + + Copyright 2018 Sven Brauch + Copyright 2018 Michal Srb + + 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 3 of the License, or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 6 of version 3 of the license. + + 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_INLINENOTEPROVIDER_H +#define KTEXTEDITOR_INLINENOTEPROVIDER_H + +#include + +#include + +namespace KTextEditor { +/** + * @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 + * @since 5.50 + */ +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. + * + * Should return a vector of columns on which the notes are 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. + * + * @param line Line number + * @returns vector of columns where inline notes appear in this line + */ + virtual QVector inlineNotes(int line) 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 + * + * @return the width of the note in pixels + */ + virtual QSize inlineNoteSize(const InlineNote& note) 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 note note to paint, containing location and index + * @param painter painter prepared for rendering the note + */ + virtual void paintInlineNote(const InlineNote& note, QPainter& painter) const = 0; + + /** + * Invoked when a note is activated by the user. + * + * This method is called when a user activates a note, i.e. clicks on it. + * Coordinates of @p pos are in note coordinates, i.e. relative to the note's + * top-left corner (same coordinate system as the painter has in paintInlineNote()). + * + * The default implementation does nothing. + * + * @param note the note which was activated + * @param buttons the button(s) the note was clicked with + * @param pos the point the note was clicked at + */ + virtual void inlineNoteActivated(const InlineNote& note, Qt::MouseButtons buttons, const QPoint& pos); + + /** + * Invoked when the mouse cursor moves into the note when it was outside before. + * + * The default implementation does nothing. + * + * @param note the note which was activated + * @param pos the location of the mouse cursor, in note coordinates + */ + virtual void inlineNoteFocusInEvent(const InlineNote& note, const QPoint& pos); + + /** + * Invoked when the mouse cursor leaves the note. + * + * The default implementation does nothing. + * + * @param note the note which was deactivated + */ + virtual void inlineNoteFocusOutEvent(const InlineNote& note); + + /** + * Invoked when the mouse cursor moves inside the note. + * + * The default implementation does nothing. + * + * @param note the note which was hovered + * @param pos the location of the mouse cursor, in note coordinates + */ + virtual void inlineNoteMouseMoveEvent(const InlineNote& note, const QPoint& pos); + +Q_SIGNALS: + /** + * The provider should emit the signal inlineNotesReset() when almost all inline notes + * changed. + */ + void inlineNotesReset(); + + /** + * The provider should emit the signal inlineNotesChanged() when any of the + * inline notes on the line changed. + */ + void inlineNotesChanged(int line); +}; + +} + +#endif diff --git a/src/render/katerenderer.h b/src/render/katerenderer.h --- a/src/render/katerenderer.h +++ b/src/render/katerenderer.h @@ -35,6 +35,7 @@ namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } +namespace KTextEditor { class InlineNote; } class KateRendererConfig; class KateRenderRange; namespace Kate 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,12 @@ #include "katetextlayout.h" #include "katebuffer.h" +#include "ktexteditor/inlinenote.h" +#include "ktexteditor/inlinenoteprovider.h" + #include "katepartdebug.h" +#include #include #include #include @@ -758,6 +762,36 @@ } } + // Draw inline notes + const auto inlineNotes = m_view->inlineNotes(range->line()); + for (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(); + } else { + // If the note is outside the text, then the X coordinate is located at the end of the line. + // Add appropriate amount of spaces to reach the required column. + x += spaceWidth() * (column - textLength); + } + + qreal y = lineHeight() * viewLine; + + // Paint the note + paint.save(); + paint.translate(x, y); + inlineNote.provider()->paintInlineNote(inlineNote, paint); + paint.restore(); + } + // draw word-wrap-honor-indent filling if ((range->viewLineCount() > 1) && range->shiftX() && (range->shiftX() > xStart)) { if (backgroundBrushSet) @@ -1008,7 +1042,30 @@ l->setTextOption(opt); // Syntax highlighting, inbuilt and arbitrary - l->setAdditionalFormats(decorationsForLine(textLine, lineLayout->line())); + QList decorations = decorationsForLine(textLine, lineLayout->line()); + + int firstLineOffset = 0; + + const auto inlineNotes = m_view->inlineNotes(lineLayout->line()); + for (const KTextEditor::InlineNote& inlineNote: inlineNotes) { + int column = inlineNote.column(); + int width = inlineNote.width(); + + // Make space for every inline note. + // If it is on column 0 (at the beginning of the line), we must offset the first line. + // If it is inside the text, we use absolute letter spacing to create space for it between the two letters. + // If it is outside of the text, we don't have to make space for it. + if (column == 0) { + firstLineOffset = width; + } else if (column < l->text().length()) { + QTextCharFormat text_char_format; + text_char_format.setFontLetterSpacing(width); + text_char_format.setFontLetterSpacingType(QFont::AbsoluteSpacing); + decorations.append(QTextLayout::FormatRange { column - 1, 1, text_char_format }); + } + } + + l->setAdditionalFormats(decorations); // Begin layouting l->beginLayout(); @@ -1034,7 +1091,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,9 @@ #include "plugin.h" #include "command.h" +#include "inlinenoteinterface.h" +#include "inlinenote.h" +#include "inlinenoteprovider.h" #include "markinterface.h" #include "modificationinterface.h" #include "sessionconfiginterface.h" @@ -210,6 +213,116 @@ TextHintProvider::~TextHintProvider() {} +InlineNoteInterface::InlineNoteInterface() +{} + +InlineNoteInterface::~InlineNoteInterface() +{} + +InlineNoteProvider::InlineNoteProvider() +{} + +InlineNoteProvider::~InlineNoteProvider() +{} + +InlineNote::InlineNote(InlineNoteProvider* provider, const KTextEditor::Cursor& position, int index, + const KTextEditor::View* view, QFont font, int lineHeight, bool hasFocus) + : m_provider(provider) + , m_view(view) + , m_position(position) + , m_index(index) + , m_hasFocus(hasFocus) + , m_font(font) + , m_lineHeight(lineHeight) +{ +} + +InlineNote::InlineNote() = default; + +int InlineNote::column() const +{ + return m_position.column(); +} + +qreal InlineNote::width() const +{ + return m_provider->inlineNoteSize(*this).width(); +} + +bool KTextEditor::InlineNote::hasFocus() const +{ + return m_hasFocus; +} + +void KTextEditor::InlineNoteProvider::inlineNoteActivated(const InlineNote& note, Qt::MouseButtons buttons, const QPoint& pos) +{ + Q_UNUSED(note); + Q_UNUSED(buttons); + Q_UNUSED(pos); +} + +void KTextEditor::InlineNoteProvider::inlineNoteFocusInEvent(const KTextEditor::InlineNote& note, const QPoint& pos) +{ + Q_UNUSED(note); + Q_UNUSED(pos); +} + +void KTextEditor::InlineNoteProvider::inlineNoteFocusOutEvent(const KTextEditor::InlineNote& note) +{ + Q_UNUSED(note); +} + +void KTextEditor::InlineNoteProvider::inlineNoteMouseMoveEvent(const KTextEditor::InlineNote& note, const QPoint& pos) +{ + Q_UNUSED(note); + Q_UNUSED(pos); +} + +bool InlineNote::isValid() const +{ + return m_provider != nullptr && m_position.isValid(); +} + +bool InlineNote::operator==(const InlineNote& other) const { + return m_provider == other.m_provider && m_position == other.m_position && m_index == other.m_index; +} + +QPoint InlineNote::mapToGlobal(const QPoint& pos) const +{ + auto localPos = static_cast(m_view)->inlineNoteRect(*this).topLeft() + pos; + return m_view->mapToGlobal(localPos); +} + +KTextEditor::InlineNoteProvider* InlineNote::provider() const +{ + return m_provider; +} + +const KTextEditor::View* InlineNote::view() const +{ + return m_view; +} + +QFont InlineNote::font() const +{ + return m_font; +} + +int InlineNote::index() const +{ + return m_index; +} + +int InlineNote::lineHeight() const +{ + return m_lineHeight; +} + +KTextEditor::Cursor InlineNote::position() const +{ + return m_position; +} + 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 @@ -46,6 +47,7 @@ { class AnnotationModel; class Message; +class InlineNoteProvider; } namespace KTextEditor { class DocumentPrivate; } @@ -84,13 +86,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 +254,23 @@ return m_hasWrap; } + // + // Inline Notes Interface + // +public: + void registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) Q_DECL_OVERRIDE; + void unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) Q_DECL_OVERRIDE; + QRect inlineNoteRect(const KTextEditor::InlineNote& note) const; + + 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 @@ -58,6 +58,7 @@ #include "katestatusbar.h" #include "kateabstractinputmode.h" +#include #include #include @@ -3644,6 +3645,74 @@ //END +//BEGIN KTextEditor::InlineNoteInterface +void KTextEditor::ViewPrivate::registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) +{ + if (! m_inlineNoteProviders.contains(provider)) { + m_inlineNoteProviders.append(provider); + + connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesReset, this, &ViewPrivate::inlineNotesReset); + connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesChanged, this, &ViewPrivate::inlineNotesLineChanged); + + inlineNotesReset(); + } +} + +void KTextEditor::ViewPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) +{ + const int index = m_inlineNoteProviders.indexOf(provider); + if (index >= 0) { + m_inlineNoteProviders.removeAt(index); + + provider->disconnect(this); + + 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)) { + KTextEditor::InlineNote note = { + provider, + {line, column}, + index, + this, + m_viewInternal->renderer()->currentFont(), + m_viewInternal->renderer()->lineHeight(), + m_viewInternal->m_activeInlineNote.hasFocus(), + }; + allInlineNotes.append(note); + index++; + } + } + return allInlineNotes; +} + +QRect KTextEditor::ViewPrivate::inlineNoteRect(const KTextEditor::InlineNote& note) const +{ + return m_viewInternal->inlineNoteRect(note); +} + +void KTextEditor::ViewPrivate::inlineNotesReset() +{ + m_viewInternal->m_activeInlineNote = {}; + tagLines(0, m_doc->lastLine(), true); +} + +void KTextEditor::ViewPrivate::inlineNotesLineChanged(int line) +{ + if ( line == m_viewInternal->m_activeInlineNote.position().line() ) { + m_viewInternal->m_activeInlineNote = {}; + } + tagLines(line, line, true); +} + +//END KTextEditor::InlineNoteInterface + KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const { KateRendererConfig * renderConfig = const_cast(this)->renderer()->config(); diff --git a/src/view/kateviewinternal.h b/src/view/kateviewinternal.h --- a/src/view/kateviewinternal.h +++ b/src/view/kateviewinternal.h @@ -27,6 +27,7 @@ #define _KATE_VIEW_INTERNAL_ #include +#include #include "katetextcursor.h" #include "katetextline.h" @@ -466,6 +467,11 @@ private: QMap m_inputModes; KateAbstractInputMode *m_currentInputMode; + + KTextEditor::InlineNote m_activeInlineNote; + KTextEditor::InlineNote inlineNoteAt(const QPoint& pos) const; + QRect inlineNoteRect(const KTextEditor::InlineNote& note) const; + QPoint toNoteCoordinates(const QPoint& pos, const KTextEditor::InlineNote& note) const; }; #endif diff --git a/src/view/kateviewinternal.cpp b/src/view/kateviewinternal.cpp --- a/src/view/kateviewinternal.cpp +++ b/src/view/kateviewinternal.cpp @@ -47,6 +47,7 @@ #include #include +#include #include #include @@ -2587,8 +2588,17 @@ void KateViewInternal::mousePressEvent(QMouseEvent *e) { + // was an inline note clicked? + auto note = inlineNoteAt(e->pos()); + if (note.isValid()) { + note.provider()->inlineNoteActivated(note, e->button(), toNoteCoordinates(e->pos(), note)); + return; + } + + // no -- continue with normal handling switch (e->button()) { case Qt::LeftButton: + m_selChangedByUser = false; if (m_possibleTripleClick) { @@ -2875,6 +2885,28 @@ mouseMoved(); } + if (e->buttons() == Qt::NoButton) { + auto note = inlineNoteAt(e->pos()); + if (note.isValid()) { + const auto notePos = toNoteCoordinates(e->pos(), note); + if (!m_activeInlineNote.isValid()) { + // no active note -- focus in + note.provider()->inlineNoteFocusInEvent(note, notePos); + m_activeInlineNote = note; + } + else { + note.provider()->inlineNoteMouseMoveEvent(note, notePos); + } + // the note might change its appearance in result to the event + tagLines(note.position(), note.position(), true); + } + else if (m_activeInlineNote.isValid()) { + m_activeInlineNote.provider()->inlineNoteFocusOutEvent(m_activeInlineNote); + tagLines(m_activeInlineNote.position(), m_activeInlineNote.position(), true); + m_activeInlineNote = {}; + } + } + if (e->buttons() & Qt::LeftButton) { if (m_dragInfo.state == diPending) { // we had a mouse down, but haven't confirmed a drag yet @@ -3857,3 +3889,45 @@ } #endif } + +QRect KateViewInternal::inlineNoteRect(const KTextEditor::InlineNote& note) const +{ + // compute note width and position + auto noteWidth = note.width(); + auto noteCursor = KTextEditor::Cursor(note.position().line(), note.column()); + + // The cursor might be outside of the text. In that case, clamp it to the text and + // later on add the missing x offset. + const auto lineLength = view()->document()->lineLength(noteCursor.line()); + int extraOffset = 0; + if (noteCursor.column() > lineLength) { + extraOffset = (noteCursor.column() - lineLength) * renderer()->spaceWidth(); + noteCursor.setColumn(lineLength); + } + auto noteStartPos = cursorToCoordinate(noteCursor, true, false); + + // compute the note's rect + auto noteRect = QRect(noteStartPos + QPoint{extraOffset, 0}, QSize(noteWidth, renderer()->lineHeight())); + return noteRect; +} + +KTextEditor::InlineNote KateViewInternal::inlineNoteAt(const QPoint& pos) const +{ + // compute the associated cursor to get the right line + auto cursor = m_view->coordinatesToCursor(pos); + auto inlineNotes = m_view->inlineNotes(cursor.line()); + // loop over all notes and check if the point is inside it + foreach (const auto& note, inlineNotes) { + auto noteRect = inlineNoteRect(note); + if (noteRect.contains(pos)) { + return note; + } + } + // none found -- return an invalid note + return {}; +} + +QPoint KateViewInternal::toNoteCoordinates(const QPoint& pos, const KTextEditor::InlineNote& note) const +{ + return pos - inlineNoteRect(note).topLeft(); +}