// SPDX-FileCopyrightText: 2025 James Graham // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL #pragma once #include #include #include "enums/chatbartype.h" #include "enums/richformat.h" #include "nestedlisthelper_p.h" class QTextDocument; class ChatBarSyntaxHighlighter; class NeoChatRoom; /** * @class ChatTextItemHelper * * A class to wrap around a QQuickItem that is a QML TextEdit (or inherited from it). * * This class has 2 key functions: * - Provide easy read/write access to the properties of the TextEdit. This is required * because Qt does not give us access to the cpp headers of most QML items. * - Provide standard functions to edit the underlying QTextDocument. * * @sa QQuickItem, TextEdit, QTextDocument */ class ChatTextItemHelper : public QObject { Q_OBJECT QML_ELEMENT /** * @brief The QML text Item the ChatTextItemHelper is handling. */ Q_PROPERTY(QQuickItem *textItem READ textItem WRITE setTextItem NOTIFY textItemChanged) /** * @brief The QML text Item the ChatTextItemHelper is handling. */ Q_PROPERTY(QRect cursorRectangle READ cursorRectangle NOTIFY cursorPositionChanged) public: enum InsertPosition { Cursor, Start, End, }; explicit ChatTextItemHelper(QObject *parent = nullptr); /** * @brief Get the NeoChatRoom used by the syntax highlighter. * * @sa NeoChatRoom */ NeoChatRoom *room() const; /** * @brief Set the NeoChatRoom required by the syntax highlighter. * * @sa NeoChatRoom */ void setRoom(NeoChatRoom *room); /** * @brief Get the ChatBarType::Type used by the syntax highlighter. * * @sa ChatBarType::Type */ ChatBarType::Type type() const; /** * @brief Set the ChatBarType::Type required by the syntax highlighter. * * @sa ChatBarType::Type */ void setType(ChatBarType::Type type); QQuickItem *textItem() const; void setTextItem(QQuickItem *textItem); /** * @brief The text format of the wrapped item. */ std::optional textFormat() const; /** * @brief Whether a completion has started based on recent text entry. */ bool isCompleting = false; /** * @brief The fixed characters that will always be at the beginning of the text item. */ QString fixedStartChars() const; /** * @brief The fixed characters that will always be at the end of the text item. */ QString fixedEndChars() const; /** * @brief Set the fixed characters that will always be at the beginning and end of the text item. */ void setFixedChars(const QString &startChars, const QString &endChars); /** * @brief Any text to initialise the text item with when set. */ QString initialText() const; /** * @brief Set the text to initialise the text item with when set. * * This text will only be set if the text item is empty when set. */ void setInitialText(const QString &text); /** * @brief The underlying QTextDocument. * * @sa QTextDocument */ QTextDocument *document() const; /** * @brief Whetehr the underlying QTextDocument is empty. * * @sa QTextDocument */ bool isEmpty() const; /** * @brief The line count of the text item. */ int lineCount() const; /** * @brief Remove the first QTextBlock from the QTextDocument and return as a QTextDocumentFragment. * * @sa QTextBlock, QTextDocument, QTextDocumentFragment */ QTextDocumentFragment takeFirstBlock(); /** * @brief Fill the given QTextDocumentFragment with the text item contents. * * The idea is to split the QTextDocument into 3. There is the QTextBlock that the * cursor is currently in, the midFragment. Then if there are any blocks after * this they are put into the afterFragment. The if there is any block before * the midFragment these are left and hasBefore is set to true. * * This is used when inserting a new block type at the cursor. The midFragement will be * given the new style and then the before and after are put back as the same * block type. * * @sa QTextBlock, QTextDocument, QTextDocumentFragment */ void fillFragments(bool &hasBefore, QTextDocumentFragment &midFragment, std::optional &afterFragment); /** * @brief Insert the given QTextDocumentFragment as the given position. */ void insertFragment(const QTextDocumentFragment fragment, InsertPosition position = Cursor, bool keepPosition = false); /** * @brief Return a QTextCursor pointing to the current cursor position. */ QTextCursor textCursor() const; /** * @brief Return the current cursor position of the underlying text item. */ std::optional cursorPosition() const; /** * @brief Return the rectangle where the cursor of the underlying text item is rendered. */ QRect cursorRectangle() const; /** * @brief Set the cursor position of the underlying text item to the given value. */ void setCursorPosition(int pos); /** * @brief Set the cursor visibility of the underlying text item to the given value. */ void setCursorVisible(bool visible); /** * @brief Set the cursor position to the same as the given text item. * * This will either be the first or last line dependent upon the infront value. */ void setCursorFromTextItem(ChatTextItemHelper *textItem, bool infront); /** * @brief Merge the given format on the given QTextCursor. */ void mergeFormatOnCursor(RichFormat::Format format, QTextCursor cursor = {}); /** * @brief Whether the list can be indented more at the given cursor. */ bool canIndentListMoreAtCursor(QTextCursor cursor = {}) const; /** * @brief Whether the list can be indented less at the given cursor. */ bool canIndentListLessAtCursor(QTextCursor cursor = {}) const; /** * @brief Indented the list more at the given cursor. */ void indentListMoreAtCursor(QTextCursor cursor = {}); /** * @brief Indented the list less at the given cursor. */ void indentListLessAtCursor(QTextCursor cursor = {}); /** * @brief Force active focus on the underlying text item. */ void forceActiveFocus() const; /** * @brief Rehightlight the text in the text item. */ void rehighlight() const; /** * @brief Whether there is any rich formatting in the item text. */ bool hasRichFormatting() const; /** * @brief Output the text in the text item in markdown format. */ QString markdownText() const; /** * @brief Output the text in the text item in plain text format. */ QString plainText() const; Q_SIGNALS: /** * @brief Emitted when the NeoChatRoom used by the syntax highlighter is changed. */ void roomChanged(); /** * @brief Emitted when the ChatBarType::Type used by the syntax highlighter is changed. */ void typeChanged(); void textItemChanged(); /** * @brief Emitted when the textFormat property of the underlying text item is changed. */ void textFormatChanged(); /** * @brief Emitted when the char format at the current cursor is changed. */ void charFormatChanged(); /** * @brief Emitted when the style at the current cursor is changed. */ void styleChanged(); /** * @brief Emitted when the list format at the current cursor is changed. */ void listChanged(); /** * @brief Emitted when the contents of the underlying text item are changed. */ void contentsChange(int position, int charsRemoved, int charsAdded); /** * @brief Emitted when the contents of the underlying text item are changed. */ void contentsChanged(); /** * @brief Emitted when the contents of the underlying text item are cleared. */ void cleared(ChatTextItemHelper *self); /** * @brief Emitted when the cursor position of the underlying text item is changed. */ void cursorPositionChanged(bool fromContentsChange); private: QPointer m_textItem; QPointer m_highlighter; bool m_contentsJustChanged = false; QString m_fixedStartChars = {}; QString m_fixedEndChars = {}; QString m_initialText = {}; void initializeChars(); bool m_initializingChars = false; std::optional lineLength(int lineNumber) const; int selectionStart() const; int selectionEnd() const; void mergeTextFormatOnCursor(RichFormat::Format format, QTextCursor cursor); void mergeStyleFormatOnCursor(RichFormat::Format format, QTextCursor cursor); void mergeListFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor); NestedListHelper m_nestedListHelper; QString trim(QString string) const; private Q_SLOTS: void itemTextFormatChanged(); void itemCursorPositionChanged(); };