Files
neochat/src/libneochat/chatdocumenthandler.h

241 lines
7.3 KiB
C++

// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QTextCursor>
#include <qnamespace.h>
#include <qtextdocumentfragment.h>
#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<int> lineLength(int lineNumber) const;
int cursorPositionInLine() const;
QTextDocumentFragment takeFirstBlock();
void fillFragments(bool &hasBefore, QTextDocumentFragment &midFragment, std::optional<QTextDocumentFragment> &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<NeoChatRoom> m_room;
QPointer<QmlTextItemWrapper> m_textItem;
void connectTextItem();
QPointer<ChatDocumentHandler> m_previousDocumentHandler;
QPointer<ChatDocumentHandler> m_nextDocumentHandler;
QString m_fixedStartChars = {};
QString m_fixedEndChars = {};
QString m_initialText = {};
void initializeChars();
QPointer<ChatMarkdownHelper> m_markdownHelper;
std::optional<QTextCharFormat> m_pendingFormat = std::nullopt;
std::optional<QTextCharFormat> m_pendingOverrideFormat = std::nullopt;
SyntaxHighlighter *m_highlighter = nullptr;
QString getText() const;
void pushMention(const Mention mention) const;
std::optional<Qt::TextFormat> textFormat() const;
void mergeFormatOnWordOrSelection(const QTextCharFormat &format);
void selectLinkText(QTextCursor *cursor) const;
QColor linkColor();
QColor mLinkColor;
void regenerateColorScheme();
QString trim(QString string) const;
};