// SPDX-FileCopyrightText: 2020 Carl Schwan // SPDX-FileCopyrightText: 2025 James Graham // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include #include "chatbarcache.h" #include "chatmarkdownhelper.h" #include "enums/chatbartype.h" #include "enums/richformat.h" #include "neochatroom.h" #include "nestedlisthelper_p.h" class QTextDocument; class QmlTextItemWrapper; class NeoChatRoom; class SyntaxHighlighter; /** * @class ChatDocumentHandler * * Handle the QQuickTextDocument of a qml text item. * * The class provides functionality to highlight text in the text document as well * as providing completion functionality via a CompletionModel. * * The ChatDocumentHandler is also linked to a NeoChatRoom to provide functionality * to save the chat document text when switching between rooms. * * To get the full functionality the cursor position and text selection information * need to be passed in. For example: * * @code{.qml} * import QtQuick 2.0 * import QtQuick.Controls 2.15 as QQC2 * * import org.kde.kirigami 2.12 as Kirigami * import org.kde.neochat 1.0 * * QQC2.TextArea { * id: textField * * // Set this to a NeoChatRoom object. * property var room * * ChatDocumentHandler { * id: documentHandler * document: textField.textDocument * cursorPosition: textField.cursorPosition * selectionStart: textField.selectionStart * selectionEnd: textField.selectionEnd * mentionColor: Kirigami.Theme.linkColor * errorColor: Kirigami.Theme.negativeTextColor * room: textField.room * } * } * @endcode * * @sa QQuickTextDocument, CompletionModel, NeoChatRoom */ class ChatDocumentHandler : public QObject { Q_OBJECT QML_ELEMENT /** * @brief The QQuickTextDocument that is being handled. */ Q_PROPERTY(ChatBarType::Type type READ type WRITE setType NOTIFY typeChanged) /** * @brief The current room that the text document is being handled for. */ Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged) /** * @brief The QML text Item the ChatDocumentHandler is handling. */ Q_PROPERTY(QQuickItem *textItem READ textItem WRITE setTextItem NOTIFY textItemChanged) /** * @brief Whether the cursor is currently on the first line. */ Q_PROPERTY(bool atFirstLine READ atFirstLine NOTIFY atFirstLineChanged) /** * @brief Whether the cursor is cuurently on the last line. */ Q_PROPERTY(bool atLastLine READ atLastLine NOTIFY atLastLineChanged) Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor NOTIFY textColorChanged) Q_PROPERTY(bool bold READ bold NOTIFY textFormatChanged) Q_PROPERTY(bool italic READ italic NOTIFY textFormatChanged) Q_PROPERTY(bool underline READ underline NOTIFY textFormatChanged) Q_PROPERTY(bool strikethrough READ strikethrough NOTIFY textFormatChanged) Q_PROPERTY(RichFormat::Format style READ style NOTIFY styleChanged) Q_PROPERTY(int currentListStyle READ currentListStyle NOTIFY listChanged) public: enum InsertPosition { Cursor, Start, End, }; explicit ChatDocumentHandler(QObject *parent = nullptr); ChatBarType::Type type() const; void setType(ChatBarType::Type type); [[nodiscard]] NeoChatRoom *room() const; void setRoom(NeoChatRoom *room); QQuickItem *textItem() const; void setTextItem(QQuickItem *textItem); ChatDocumentHandler *previousDocumentHandler() const; void setPreviousDocumentHandler(ChatDocumentHandler *previousDocumentHandler); ChatDocumentHandler *nextDocumentHandler() const; void setNextDocumentHandler(ChatDocumentHandler *nextDocumentHandler); QString fixedStartChars() const; void setFixedStartChars(const QString &chars); QString fixedEndChars() const; void setFixedEndChars(const QString &chars); QString initialText() const; void setInitialText(const QString &text); bool isEmpty() const; bool atFirstLine() const; bool atLastLine() const; void setCursorFromDocumentHandler(ChatDocumentHandler *previousDocumentHandler, bool infront, int defaultPosition = 0); int lineCount() const; std::optional lineLength(int lineNumber) const; int cursorPositionInLine() const; QTextDocumentFragment takeFirstBlock(); void fillFragments(bool &hasBefore, QTextDocumentFragment &midFragment, std::optional &afterFragment); /** * @brief Update the mentions in @p document when editing a message. */ Q_INVOKABLE void updateMentions(const QString &editId); QColor textColor() const; void setTextColor(const QColor &color); bool bold() const; bool italic() const; bool underline() const; bool strikethrough() const; Q_INVOKABLE void setFormat(RichFormat::Format format); int currentListStyle() const; bool canIndentListMore() const; bool canIndentListLess() const; Q_INVOKABLE void indentListLess(); Q_INVOKABLE void indentListMore(); RichFormat::Format style() const; Q_INVOKABLE void tab(); Q_INVOKABLE void deleteChar(); Q_INVOKABLE void backspace(); Q_INVOKABLE void insertReturn(); Q_INVOKABLE void insertText(const QString &text); void insertFragment(const QTextDocumentFragment fragment, InsertPosition position = Cursor, bool keepPosition = false); Q_INVOKABLE QString currentLinkUrl() const; Q_INVOKABLE QString currentLinkText() const; Q_INVOKABLE void updateLink(const QString &linkUrl, const QString &linkText); Q_INVOKABLE void insertCompletion(const QString &text, const QUrl &link); Q_INVOKABLE void dumpHtml(); Q_INVOKABLE QString htmlText() const; Q_SIGNALS: void typeChanged(); void textItemChanged(); void roomChanged(); void atFirstLineChanged(); void atLastLineChanged(); void textColorChanged(); void currentListStyleChanged(); void formatChanged(); void textFormatChanged(); void styleChanged(); void listChanged(); void contentsChanged(); void unhandledBackspaceAtBeginning(ChatDocumentHandler *self); void removeMe(ChatDocumentHandler *self); private: ChatBarType::Type m_type = ChatBarType::None; QPointer m_room; QPointer m_textItem; void connectTextItem(); QPointer m_previousDocumentHandler; QPointer m_nextDocumentHandler; QString m_fixedStartChars = {}; QString m_fixedEndChars = {}; QString m_initialText = {}; void initializeChars(); QPointer m_markdownHelper; std::optional m_pendingFormat = std::nullopt; std::optional m_pendingOverrideFormat = std::nullopt; SyntaxHighlighter *m_highlighter = nullptr; QString getText() const; void pushMention(const Mention mention) const; std::optional textFormat() const; void mergeFormatOnWordOrSelection(const QTextCharFormat &format); void selectLinkText(QTextCursor *cursor) const; QColor linkColor(); QColor mLinkColor; void regenerateColorScheme(); QString trim(QString string) const; };