Disallow formatting when there is an attachment and when adding one if there is rich formatting warn the user it will be removed and remove if they accept.
This commit is contained in:
@@ -73,7 +73,7 @@ QQC2.ToolBar {
|
|||||||
onActivated: boldButton.clicked()
|
onActivated: boldButton.clicked()
|
||||||
}
|
}
|
||||||
icon.name: "format-text-bold"
|
icon.name: "format-text-bold"
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
|
enabled: chatButtonHelper.richFormatEnabled
|
||||||
text: i18nc("@action:button", "Bold")
|
text: i18nc("@action:button", "Bold")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
@@ -94,7 +94,7 @@ QQC2.ToolBar {
|
|||||||
onActivated: italicButton.clicked()
|
onActivated: italicButton.clicked()
|
||||||
}
|
}
|
||||||
icon.name: "format-text-italic"
|
icon.name: "format-text-italic"
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
|
enabled: chatButtonHelper.richFormatEnabled
|
||||||
text: i18nc("@action:button", "Italic")
|
text: i18nc("@action:button", "Italic")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
@@ -115,7 +115,7 @@ QQC2.ToolBar {
|
|||||||
onActivated: underlineButton.clicked()
|
onActivated: underlineButton.clicked()
|
||||||
}
|
}
|
||||||
icon.name: "format-text-underline"
|
icon.name: "format-text-underline"
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
|
enabled: chatButtonHelper.richFormatEnabled
|
||||||
text: i18nc("@action:button", "Underline")
|
text: i18nc("@action:button", "Underline")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
@@ -131,7 +131,7 @@ QQC2.ToolBar {
|
|||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
icon.name: "format-text-strikethrough"
|
icon.name: "format-text-strikethrough"
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
|
enabled: chatButtonHelper.richFormatEnabled
|
||||||
text: i18nc("@action:button", "Strikethrough")
|
text: i18nc("@action:button", "Strikethrough")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
@@ -150,7 +150,7 @@ QQC2.ToolBar {
|
|||||||
id: compressedTextFormatButton
|
id: compressedTextFormatButton
|
||||||
visible: root.maxAvailableWidth < root.listCompressedImplicitWidth
|
visible: root.maxAvailableWidth < root.listCompressedImplicitWidth
|
||||||
icon.name: "dialog-text-and-font"
|
icon.name: "dialog-text-and-font"
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
|
enabled: chatButtonHelper.richFormatEnabled
|
||||||
text: i18nc("@action:button", "Format Text")
|
text: i18nc("@action:button", "Format Text")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
@@ -218,7 +218,7 @@ QQC2.ToolBar {
|
|||||||
visible: root.maxAvailableWidth > root.uncompressedImplicitWidth
|
visible: root.maxAvailableWidth > root.uncompressedImplicitWidth
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
icon.name: "format-list-unordered"
|
icon.name: "format-list-unordered"
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
|
enabled: chatButtonHelper.richFormatEnabled
|
||||||
text: i18nc("@action:button", "Unordered List")
|
text: i18nc("@action:button", "Unordered List")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
@@ -234,7 +234,7 @@ QQC2.ToolBar {
|
|||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
icon.name: "format-list-ordered"
|
icon.name: "format-list-ordered"
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
|
enabled: chatButtonHelper.richFormatEnabled
|
||||||
text: i18nc("@action:button", "Ordered List")
|
text: i18nc("@action:button", "Ordered List")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
@@ -251,7 +251,7 @@ QQC2.ToolBar {
|
|||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
id: indentAction
|
id: indentAction
|
||||||
icon.name: "format-indent-more"
|
icon.name: "format-indent-more"
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code && root.chatButtonHelper.canIndentListMore
|
enabled: chatButtonHelper.richFormatEnabled && root.chatButtonHelper.canIndentListMore
|
||||||
text: i18nc("@action:button", "Increase List Level")
|
text: i18nc("@action:button", "Increase List Level")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -266,7 +266,7 @@ QQC2.ToolBar {
|
|||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
id: dedentAction
|
id: dedentAction
|
||||||
icon.name: "format-indent-less"
|
icon.name: "format-indent-less"
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code && root.chatButtonHelper.canIndentListLess
|
enabled: chatButtonHelper.richFormatEnabled && root.chatButtonHelper.canIndentListLess
|
||||||
text: i18nc("@action:button", "Decrease List Level")
|
text: i18nc("@action:button", "Decrease List Level")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
onClicked: {
|
onClicked: {
|
||||||
@@ -281,7 +281,7 @@ QQC2.ToolBar {
|
|||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
id: compressedListButton
|
id: compressedListButton
|
||||||
enabled: root.contentModel.focusType !== LibNeoChat.MessageComponentType.Code
|
enabled: chatButtonHelper.richFormatEnabled
|
||||||
visible: root.maxAvailableWidth < root.uncompressedImplicitWidth
|
visible: root.maxAvailableWidth < root.uncompressedImplicitWidth
|
||||||
icon.name: "format-list-unordered"
|
icon.name: "format-list-unordered"
|
||||||
text: i18nc("@action:button", "List Style")
|
text: i18nc("@action:button", "List Style")
|
||||||
@@ -315,6 +315,7 @@ QQC2.ToolBar {
|
|||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
icon.name: "format-indent-more"
|
icon.name: "format-indent-more"
|
||||||
text: i18nc("@action:button", "Increase List Level")
|
text: i18nc("@action:button", "Increase List Level")
|
||||||
|
enabled: root.chatButtonHelper.canIndentListMore
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
root.chatButtonHelper.indentListMore();
|
root.chatButtonHelper.indentListMore();
|
||||||
root.clicked();
|
root.clicked();
|
||||||
@@ -323,6 +324,7 @@ QQC2.ToolBar {
|
|||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
icon.name: "format-indent-less"
|
icon.name: "format-indent-less"
|
||||||
text: i18nc("@action:button", "Decrease List Level")
|
text: i18nc("@action:button", "Decrease List Level")
|
||||||
|
enabled: root.chatButtonHelper.canIndentListLess
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
root.chatButtonHelper.indentListLess();
|
root.chatButtonHelper.indentListLess();
|
||||||
root.clicked();
|
root.clicked();
|
||||||
@@ -338,6 +340,7 @@ QQC2.ToolBar {
|
|||||||
id: styleButton
|
id: styleButton
|
||||||
icon.name: "typewriter"
|
icon.name: "typewriter"
|
||||||
text: i18nc("@action:button", "Text Style")
|
text: i18nc("@action:button", "Text Style")
|
||||||
|
enabled: root.chatButtonHelper.styleFormatEnabled
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: styleMenu.visible
|
checked: styleMenu.visible
|
||||||
@@ -422,10 +425,29 @@ QQC2.ToolBar {
|
|||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
if (!root.contentModel.hasRichFormatting) {
|
||||||
|
fileDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let warningDialog = Qt.createComponent('org.kde.kirigami', 'PromptDialog').createObject(QQC2.Overlay.overlay, {
|
||||||
|
dialogType: Kirigami.PromptDialog.Warning,
|
||||||
|
title: i18n("Attach an image or file?"),
|
||||||
|
subtitle: i18n("Attachments can only have plain text captions, all rich formatting will be removed"),
|
||||||
|
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||||
|
});
|
||||||
|
warningDialog.onAccepted.connect(() => {
|
||||||
|
attachmentButton.fileDialog();
|
||||||
|
});
|
||||||
|
warningDialog.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileDialog(): void {
|
||||||
let dialog = (LibNeoChat.Clipboard.hasImage ? attachDialog : openFileDialog).createObject(QQC2.Overlay.overlay);
|
let dialog = (LibNeoChat.Clipboard.hasImage ? attachDialog : openFileDialog).createObject(QQC2.Overlay.overlay);
|
||||||
dialog.chosen.connect(path => root.contentModel.addAttachment(path));
|
dialog.chosen.connect(path => root.contentModel.addAttachment(path));
|
||||||
dialog.open();
|
dialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
QQC2.ToolTip.text: text
|
QQC2.ToolTip.text: text
|
||||||
|
|||||||
@@ -5,7 +5,11 @@
|
|||||||
|
|
||||||
#include <Kirigami/Platform/PlatformTheme>
|
#include <Kirigami/Platform/PlatformTheme>
|
||||||
|
|
||||||
|
#include "chatbarcache.h"
|
||||||
|
#include "chattextitemhelper.h"
|
||||||
|
#include "enums/chatbartype.h"
|
||||||
#include "enums/richformat.h"
|
#include "enums/richformat.h"
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
ChatButtonHelper::ChatButtonHelper(QObject *parent)
|
ChatButtonHelper::ChatButtonHelper(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
@@ -30,13 +34,51 @@ void ChatButtonHelper::setTextItem(ChatTextItemHelper *textItem)
|
|||||||
m_textItem = textItem;
|
m_textItem = textItem;
|
||||||
|
|
||||||
if (m_textItem) {
|
if (m_textItem) {
|
||||||
connect(m_textItem, &ChatTextItemHelper::formatChanged, this, &ChatButtonHelper::linkChanged);
|
connect(m_textItem, &ChatTextItemHelper::roomChanged, this, [this]() {
|
||||||
connect(m_textItem, &ChatTextItemHelper::textFormatChanged, this, &ChatButtonHelper::textFormatChanged);
|
if (m_textItem->room() && m_textItem->type() != ChatBarType::None) {
|
||||||
|
const auto cache = m_textItem->room()->cacheForType(m_textItem->type());
|
||||||
|
connect(cache, &ChatBarCache::attachmentPathChanged, this, &ChatButtonHelper::richFormatEnabledChanged);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_textItem, &ChatTextItemHelper::textFormatChanged, this, &ChatButtonHelper::richFormatEnabledChanged);
|
||||||
|
connect(m_textItem, &ChatTextItemHelper::charFormatChanged, this, &ChatButtonHelper::charFormatChanged);
|
||||||
connect(m_textItem, &ChatTextItemHelper::styleChanged, this, &ChatButtonHelper::styleChanged);
|
connect(m_textItem, &ChatTextItemHelper::styleChanged, this, &ChatButtonHelper::styleChanged);
|
||||||
connect(m_textItem, &ChatTextItemHelper::listChanged, this, &ChatButtonHelper::listChanged);
|
connect(m_textItem, &ChatTextItemHelper::listChanged, this, &ChatButtonHelper::listChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT textItemChanged();
|
Q_EMIT textItemChanged();
|
||||||
|
Q_EMIT richFormatEnabledChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatButtonHelper::richFormatEnabled() const
|
||||||
|
{
|
||||||
|
if (!m_textItem) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto styleAvailable = styleFormatEnabled();
|
||||||
|
if (!styleAvailable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto format = m_textItem->textFormat();
|
||||||
|
if (format) {
|
||||||
|
return format != Qt::PlainText;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatButtonHelper::styleFormatEnabled() const
|
||||||
|
{
|
||||||
|
if (!m_textItem) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto room = m_textItem->room();
|
||||||
|
if (!room) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (const auto cache = room->cacheForType(m_textItem->type())) {
|
||||||
|
return cache->attachmentPath().isEmpty();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatButtonHelper::bold() const
|
bool ChatButtonHelper::bold() const
|
||||||
|
|||||||
@@ -26,22 +26,32 @@ class ChatButtonHelper : public QObject
|
|||||||
/**
|
/**
|
||||||
* @brief Whether the text format at the current cursor is bold.
|
* @brief Whether the text format at the current cursor is bold.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool bold READ bold NOTIFY textFormatChanged)
|
Q_PROPERTY(bool richFormatEnabled READ richFormatEnabled NOTIFY richFormatEnabledChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the text format at the current cursor is bold.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool styleFormatEnabled READ styleFormatEnabled NOTIFY richFormatEnabledChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the text format at the current cursor is bold.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool bold READ bold NOTIFY charFormatChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the text format at the current cursor is italic.
|
* @brief Whether the text format at the current cursor is italic.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool italic READ italic NOTIFY textFormatChanged)
|
Q_PROPERTY(bool italic READ italic NOTIFY charFormatChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the text format at the current cursor is underlined.
|
* @brief Whether the text format at the current cursor is underlined.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool underline READ underline NOTIFY textFormatChanged)
|
Q_PROPERTY(bool underline READ underline NOTIFY charFormatChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the text format at the current cursor is struckthrough.
|
* @brief Whether the text format at the current cursor is struckthrough.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool strikethrough READ strikethrough NOTIFY textFormatChanged)
|
Q_PROPERTY(bool strikethrough READ strikethrough NOTIFY charFormatChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the format at the current cursor includes RichFormat::UnorderedList.
|
* @brief Whether the format at the current cursor includes RichFormat::UnorderedList.
|
||||||
@@ -71,12 +81,12 @@ class ChatButtonHelper : public QObject
|
|||||||
/**
|
/**
|
||||||
* @brief The link url at the current cursor position.
|
* @brief The link url at the current cursor position.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(QString currentLinkUrl READ currentLinkUrl NOTIFY linkChanged)
|
Q_PROPERTY(QString currentLinkUrl READ currentLinkUrl NOTIFY charFormatChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The link url at the current cursor position.
|
* @brief The link url at the current cursor position.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(QString currentLinkText READ currentLinkText NOTIFY linkChanged)
|
Q_PROPERTY(QString currentLinkText READ currentLinkText NOTIFY charFormatChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChatButtonHelper(QObject *parent = nullptr);
|
explicit ChatButtonHelper(QObject *parent = nullptr);
|
||||||
@@ -84,6 +94,8 @@ public:
|
|||||||
ChatTextItemHelper *textItem() const;
|
ChatTextItemHelper *textItem() const;
|
||||||
void setTextItem(ChatTextItemHelper *textItem);
|
void setTextItem(ChatTextItemHelper *textItem);
|
||||||
|
|
||||||
|
bool richFormatEnabled() const;
|
||||||
|
bool styleFormatEnabled() const;
|
||||||
bool bold() const;
|
bool bold() const;
|
||||||
bool italic() const;
|
bool italic() const;
|
||||||
bool underline() const;
|
bool underline() const;
|
||||||
@@ -128,11 +140,10 @@ public:
|
|||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void textItemChanged();
|
void textItemChanged();
|
||||||
void formatChanged();
|
void richFormatEnabledChanged();
|
||||||
void textFormatChanged();
|
void charFormatChanged();
|
||||||
void styleChanged();
|
void styleChanged();
|
||||||
void listChanged();
|
void listChanged();
|
||||||
void linkChanged();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<ChatTextItemHelper> m_textItem;
|
QPointer<ChatTextItemHelper> m_textItem;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
#include "chattextitemhelper.h"
|
#include "chattextitemhelper.h"
|
||||||
|
#include "chatbartype.h"
|
||||||
#include "richformat.h"
|
#include "richformat.h"
|
||||||
|
|
||||||
#include <QQuickTextDocument>
|
#include <QQuickTextDocument>
|
||||||
@@ -13,20 +14,50 @@
|
|||||||
#include "chatbarsyntaxhighlighter.h"
|
#include "chatbarsyntaxhighlighter.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
|
// Because we can't get access to the private header we foward declare this so the SIGNAL() macro works in setTextItem.
|
||||||
|
namespace QQuickTextEdit
|
||||||
|
{
|
||||||
|
enum TextFormat : unsigned int;
|
||||||
|
}
|
||||||
|
|
||||||
ChatTextItemHelper::ChatTextItemHelper(QObject *parent)
|
ChatTextItemHelper::ChatTextItemHelper(QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_highlighter(new ChatBarSyntaxHighlighter(this))
|
, m_highlighter(new ChatBarSyntaxHighlighter(this))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NeoChatRoom *ChatTextItemHelper::room() const
|
||||||
|
{
|
||||||
|
if (!m_highlighter) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return m_highlighter->room;
|
||||||
|
}
|
||||||
|
|
||||||
void ChatTextItemHelper::setRoom(NeoChatRoom *room)
|
void ChatTextItemHelper::setRoom(NeoChatRoom *room)
|
||||||
{
|
{
|
||||||
|
if (!m_highlighter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_highlighter->room = room;
|
m_highlighter->room = room;
|
||||||
|
Q_EMIT roomChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatBarType::Type ChatTextItemHelper::type() const
|
||||||
|
{
|
||||||
|
if (!m_highlighter) {
|
||||||
|
return ChatBarType::None;
|
||||||
|
}
|
||||||
|
return m_highlighter->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatTextItemHelper::setType(ChatBarType::Type type)
|
void ChatTextItemHelper::setType(ChatBarType::Type type)
|
||||||
{
|
{
|
||||||
|
if (!m_highlighter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
m_highlighter->type = type;
|
m_highlighter->type = type;
|
||||||
|
Q_EMIT typeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickItem *ChatTextItemHelper::textItem() const
|
QQuickItem *ChatTextItemHelper::textItem() const
|
||||||
@@ -51,6 +82,7 @@ void ChatTextItemHelper::setTextItem(QQuickItem *textItem)
|
|||||||
|
|
||||||
if (m_textItem) {
|
if (m_textItem) {
|
||||||
connect(m_textItem, SIGNAL(cursorPositionChanged()), this, SLOT(itemCursorPositionChanged()));
|
connect(m_textItem, SIGNAL(cursorPositionChanged()), this, SLOT(itemCursorPositionChanged()));
|
||||||
|
connect(m_textItem, SIGNAL(textFormatChanged(QQuickTextEdit::TextFormat)), this, SLOT(itemTextFormatChanged()));
|
||||||
if (const auto doc = document()) {
|
if (const auto doc = document()) {
|
||||||
connect(doc, &QTextDocument::contentsChanged, this, &ChatTextItemHelper::contentsChanged);
|
connect(doc, &QTextDocument::contentsChanged, this, &ChatTextItemHelper::contentsChanged);
|
||||||
connect(doc, &QTextDocument::contentsChange, this, [this]() {
|
connect(doc, &QTextDocument::contentsChange, this, [this]() {
|
||||||
@@ -63,8 +95,8 @@ void ChatTextItemHelper::setTextItem(QQuickItem *textItem)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT textItemChanged();
|
Q_EMIT textItemChanged();
|
||||||
Q_EMIT formatChanged();
|
|
||||||
Q_EMIT textFormatChanged();
|
Q_EMIT textFormatChanged();
|
||||||
|
Q_EMIT charFormatChanged();
|
||||||
Q_EMIT styleChanged();
|
Q_EMIT styleChanged();
|
||||||
Q_EMIT listChanged();
|
Q_EMIT listChanged();
|
||||||
}
|
}
|
||||||
@@ -74,10 +106,14 @@ std::optional<Qt::TextFormat> ChatTextItemHelper::textFormat() const
|
|||||||
if (!m_textItem) {
|
if (!m_textItem) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<Qt::TextFormat>(m_textItem->property("textFormat").toInt());
|
return static_cast<Qt::TextFormat>(m_textItem->property("textFormat").toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatTextItemHelper::itemTextFormatChanged()
|
||||||
|
{
|
||||||
|
Q_EMIT textFormatChanged();
|
||||||
|
}
|
||||||
|
|
||||||
QString ChatTextItemHelper::fixedStartChars() const
|
QString ChatTextItemHelper::fixedStartChars() const
|
||||||
{
|
{
|
||||||
return m_fixedStartChars;
|
return m_fixedStartChars;
|
||||||
@@ -393,8 +429,8 @@ void ChatTextItemHelper::itemCursorPositionChanged()
|
|||||||
|
|
||||||
Q_EMIT cursorPositionChanged(m_contentsJustChanged);
|
Q_EMIT cursorPositionChanged(m_contentsJustChanged);
|
||||||
m_contentsJustChanged = false;
|
m_contentsJustChanged = false;
|
||||||
Q_EMIT formatChanged();
|
|
||||||
Q_EMIT textFormatChanged();
|
Q_EMIT textFormatChanged();
|
||||||
|
Q_EMIT charFormatChanged();
|
||||||
Q_EMIT styleChanged();
|
Q_EMIT styleChanged();
|
||||||
Q_EMIT listChanged();
|
Q_EMIT listChanged();
|
||||||
}
|
}
|
||||||
@@ -438,8 +474,7 @@ void ChatTextItemHelper::mergeTextFormatOnCursor(RichFormat::Format format, QTex
|
|||||||
cursor.select(QTextCursor::WordUnderCursor);
|
cursor.select(QTextCursor::WordUnderCursor);
|
||||||
}
|
}
|
||||||
cursor.mergeCharFormat(charFormat);
|
cursor.mergeCharFormat(charFormat);
|
||||||
Q_EMIT formatChanged();
|
Q_EMIT charFormatChanged();
|
||||||
Q_EMIT textFormatChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatTextItemHelper::mergeStyleFormatOnCursor(RichFormat::Format format, QTextCursor cursor)
|
void ChatTextItemHelper::mergeStyleFormatOnCursor(RichFormat::Format format, QTextCursor cursor)
|
||||||
@@ -475,14 +510,12 @@ void ChatTextItemHelper::mergeStyleFormatOnCursor(RichFormat::Format format, QTe
|
|||||||
cursor.mergeBlockCharFormat(chrfmt);
|
cursor.mergeBlockCharFormat(chrfmt);
|
||||||
cursor.endEditBlock();
|
cursor.endEditBlock();
|
||||||
|
|
||||||
Q_EMIT formatChanged();
|
|
||||||
Q_EMIT styleChanged();
|
Q_EMIT styleChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatTextItemHelper::mergeListFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor)
|
void ChatTextItemHelper::mergeListFormatOnCursor(RichFormat::Format format, const QTextCursor &cursor)
|
||||||
{
|
{
|
||||||
m_nestedListHelper.handleOnBulletType(RichFormat::listStyleForFormat(format), cursor);
|
m_nestedListHelper.handleOnBulletType(RichFormat::listStyleForFormat(format), cursor);
|
||||||
Q_EMIT formatChanged();
|
|
||||||
Q_EMIT listChanged();
|
Q_EMIT listChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,6 +578,11 @@ void ChatTextItemHelper::rehighlight() const
|
|||||||
m_highlighter->rehighlight();
|
m_highlighter->rehighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatTextItemHelper::hasRichFormatting() const
|
||||||
|
{
|
||||||
|
return markdownText() != plainText();
|
||||||
|
}
|
||||||
|
|
||||||
QString ChatTextItemHelper::markdownText() const
|
QString ChatTextItemHelper::markdownText() const
|
||||||
{
|
{
|
||||||
const auto doc = document();
|
const auto doc = document();
|
||||||
@@ -554,6 +592,15 @@ QString ChatTextItemHelper::markdownText() const
|
|||||||
return trim(doc->toMarkdown());
|
return trim(doc->toMarkdown());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ChatTextItemHelper::plainText() const
|
||||||
|
{
|
||||||
|
const auto doc = document();
|
||||||
|
if (!doc) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return trim(doc->toPlainText());
|
||||||
|
}
|
||||||
|
|
||||||
QString ChatTextItemHelper::trim(QString string) const
|
QString ChatTextItemHelper::trim(QString string) const
|
||||||
{
|
{
|
||||||
while (string.startsWith(u"\n"_s)) {
|
while (string.startsWith(u"\n"_s)) {
|
||||||
|
|||||||
@@ -47,6 +47,13 @@ public:
|
|||||||
|
|
||||||
explicit ChatTextItemHelper(QObject *parent = nullptr);
|
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.
|
* @brief Set the NeoChatRoom required by the syntax highlighter.
|
||||||
*
|
*
|
||||||
@@ -54,6 +61,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
void setRoom(NeoChatRoom *room);
|
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.
|
* @brief Set the ChatBarType::Type required by the syntax highlighter.
|
||||||
*
|
*
|
||||||
@@ -64,6 +78,11 @@ public:
|
|||||||
QQuickItem *textItem() const;
|
QQuickItem *textItem() const;
|
||||||
void setTextItem(QQuickItem *textItem);
|
void setTextItem(QQuickItem *textItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The text format of the wrapped item.
|
||||||
|
*/
|
||||||
|
std::optional<Qt::TextFormat> textFormat() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether a completion has started based on recent text entry.
|
* @brief Whether a completion has started based on recent text entry.
|
||||||
*/
|
*/
|
||||||
@@ -205,16 +224,52 @@ public:
|
|||||||
*/
|
*/
|
||||||
void rehighlight() const;
|
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.
|
* @brief Output the text in the text item in markdown format.
|
||||||
*/
|
*/
|
||||||
QString markdownText() const;
|
QString markdownText() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Output the text in the text item in plain text format.
|
||||||
|
*/
|
||||||
|
QString plainText() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
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();
|
void textItemChanged();
|
||||||
void formatChanged();
|
|
||||||
|
/**
|
||||||
|
* @brief Emitted when the textFormat property of the underlying text item is changed.
|
||||||
|
*/
|
||||||
void textFormatChanged();
|
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();
|
void styleChanged();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Emitted when the list format at the current cursor is changed.
|
||||||
|
*/
|
||||||
void listChanged();
|
void listChanged();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -242,7 +297,6 @@ private:
|
|||||||
QPointer<ChatBarSyntaxHighlighter> m_highlighter;
|
QPointer<ChatBarSyntaxHighlighter> m_highlighter;
|
||||||
|
|
||||||
bool m_contentsJustChanged = false;
|
bool m_contentsJustChanged = false;
|
||||||
std::optional<Qt::TextFormat> textFormat() const;
|
|
||||||
|
|
||||||
QString m_fixedStartChars = {};
|
QString m_fixedStartChars = {};
|
||||||
QString m_fixedEndChars = {};
|
QString m_fixedEndChars = {};
|
||||||
@@ -263,5 +317,6 @@ private:
|
|||||||
QString trim(QString string) const;
|
QString trim(QString string) const;
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
|
void itemTextFormatChanged();
|
||||||
void itemCursorPositionChanged();
|
void itemCursorPositionChanged();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,12 +91,15 @@ ChatBarMessageContentModel::ChatBarMessageContentModel(QObject *parent)
|
|||||||
initializeModel();
|
initializeModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatBarMessageContentModel::initializeModel()
|
void ChatBarMessageContentModel::initializeModel(const QString &initialText)
|
||||||
{
|
{
|
||||||
|
updateReplyModel();
|
||||||
|
|
||||||
beginInsertRows({}, rowCount(), rowCount());
|
beginInsertRows({}, rowCount(), rowCount());
|
||||||
const auto textItem = new ChatTextItemHelper(this);
|
const auto textItem = new ChatTextItemHelper(this);
|
||||||
textItem->setRoom(m_room);
|
textItem->setRoom(m_room);
|
||||||
textItem->setType(m_type);
|
textItem->setType(m_type);
|
||||||
|
textItem->setInitialText(initialText);
|
||||||
connectTextItem(textItem);
|
connectTextItem(textItem);
|
||||||
m_components += MessageComponent{
|
m_components += MessageComponent{
|
||||||
.type = MessageComponentType::Text,
|
.type = MessageComponentType::Text,
|
||||||
@@ -248,6 +251,10 @@ ChatTextItemHelper *ChatBarMessageContentModel::focusedTextItem() const
|
|||||||
void ChatBarMessageContentModel::connectTextItem(ChatTextItemHelper *chattextitemhelper)
|
void ChatBarMessageContentModel::connectTextItem(ChatTextItemHelper *chattextitemhelper)
|
||||||
{
|
{
|
||||||
connect(chattextitemhelper, &ChatTextItemHelper::contentsChanged, this, &ChatBarMessageContentModel::updateCache);
|
connect(chattextitemhelper, &ChatTextItemHelper::contentsChanged, this, &ChatBarMessageContentModel::updateCache);
|
||||||
|
connect(chattextitemhelper, &ChatTextItemHelper::contentsChanged, this, &ChatBarMessageContentModel::hasRichFormattingChanged);
|
||||||
|
connect(chattextitemhelper, &ChatTextItemHelper::charFormatChanged, this, &ChatBarMessageContentModel::hasRichFormattingChanged);
|
||||||
|
connect(chattextitemhelper, &ChatTextItemHelper::styleChanged, this, &ChatBarMessageContentModel::hasRichFormattingChanged);
|
||||||
|
connect(chattextitemhelper, &ChatTextItemHelper::listChanged, this, &ChatBarMessageContentModel::hasRichFormattingChanged);
|
||||||
connect(chattextitemhelper, &ChatTextItemHelper::cleared, this, [this](ChatTextItemHelper *helper) {
|
connect(chattextitemhelper, &ChatTextItemHelper::cleared, this, [this](ChatTextItemHelper *helper) {
|
||||||
removeComponent(helper);
|
removeComponent(helper);
|
||||||
});
|
});
|
||||||
@@ -281,12 +288,37 @@ QModelIndex ChatBarMessageContentModel::indexForTextItem(ChatTextItemHelper *tex
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ChatBarMessageContentModel::hasRichFormatting() const
|
||||||
|
{
|
||||||
|
for (const auto &component : m_components) {
|
||||||
|
if (component.type != MessageComponentType::Text) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (const auto textItem = textItemForComponent(component)) {
|
||||||
|
if (textItem->hasRichFormatting()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void ChatBarMessageContentModel::addAttachment(const QUrl &path)
|
void ChatBarMessageContentModel::addAttachment(const QUrl &path)
|
||||||
{
|
{
|
||||||
if (m_type == ChatBarType::None || !m_room) {
|
if (m_type == ChatBarType::None || !m_room) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString plainText;
|
||||||
|
for (const auto &component : m_components) {
|
||||||
|
if (const auto textItem = textItemForComponent(component)) {
|
||||||
|
plainText += u"%1%2"_s.arg(plainText.isEmpty() ? u""_s : u"\n"_s, textItem->plainText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearModel();
|
||||||
|
initializeModel(plainText);
|
||||||
|
|
||||||
auto it = insertComponent(m_components.first().type == MessageComponentType::Reply ? 1 : 0,
|
auto it = insertComponent(m_components.first().type == MessageComponentType::Reply ? 1 : 0,
|
||||||
MessageComponentType::typeForPath(path),
|
MessageComponentType::typeForPath(path),
|
||||||
{
|
{
|
||||||
@@ -295,19 +327,7 @@ void ChatBarMessageContentModel::addAttachment(const QUrl &path)
|
|||||||
{"animated"_L1, false},
|
{"animated"_L1, false},
|
||||||
});
|
});
|
||||||
it->display = path.fileName();
|
it->display = path.fileName();
|
||||||
++it;
|
|
||||||
Q_EMIT dataChanged(index(std::distance(m_components.begin(), it)), index(std::distance(m_components.begin(), it)), {DisplayRole});
|
Q_EMIT dataChanged(index(std::distance(m_components.begin(), it)), index(std::distance(m_components.begin(), it)), {DisplayRole});
|
||||||
|
|
||||||
bool textKept = false;
|
|
||||||
while (it != m_components.end()) {
|
|
||||||
if (it->type != MessageComponentType::Text || textKept) {
|
|
||||||
it = removeComponent(it);
|
|
||||||
} else {
|
|
||||||
textKept = true;
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_room->cacheForType(m_type)->setAttachmentPath(path.toString());
|
m_room->cacheForType(m_type)->setAttachmentPath(path.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,6 +358,8 @@ ChatBarMessageContentModel::insertComponent(int row, MessageComponentType::Type
|
|||||||
.attributes = attributes,
|
.attributes = attributes,
|
||||||
});
|
});
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
|
Q_EMIT hasRichFormattingChanged();
|
||||||
|
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,6 +476,8 @@ ChatBarMessageContentModel::ComponentIt ChatBarMessageContentModel::removeCompon
|
|||||||
it = m_components.erase(it);
|
it = m_components.erase(it);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
|
|
||||||
|
Q_EMIT hasRichFormattingChanged();
|
||||||
|
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,6 +590,9 @@ void ChatBarMessageContentModel::postMessage()
|
|||||||
|
|
||||||
std::optional<QString> ChatBarMessageContentModel::getReplyEventId()
|
std::optional<QString> ChatBarMessageContentModel::getReplyEventId()
|
||||||
{
|
{
|
||||||
|
if (!m_room) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
return m_room->mainCache()->isReplying() ? std::make_optional(m_room->mainCache()->replyId()) : std::nullopt;
|
return m_room->mainCache()->isReplying() ? std::make_optional(m_room->mainCache()->replyId()) : std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,13 @@ class ChatBarMessageContentModel : public MessageContentModel
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(ChatTextItemHelper *focusedTextItem READ focusedTextItem NOTIFY focusRowChanged)
|
Q_PROPERTY(ChatTextItemHelper *focusedTextItem READ focusedTextItem NOTIFY focusRowChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether there is any rich formatting in any of the model components.
|
||||||
|
*
|
||||||
|
* If true the contents of the model will change if an attachment is added.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool hasRichFormatting READ hasRichFormatting NOTIFY hasRichFormattingChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ChatBarMessageContentModel(QObject *parent = nullptr);
|
explicit ChatBarMessageContentModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
@@ -75,6 +82,7 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void insertComponentAtCursor(MessageComponentType::Type type);
|
Q_INVOKABLE void insertComponentAtCursor(MessageComponentType::Type type);
|
||||||
|
|
||||||
|
bool hasRichFormatting() const;
|
||||||
Q_INVOKABLE void addAttachment(const QUrl &path);
|
Q_INVOKABLE void addAttachment(const QUrl &path);
|
||||||
|
|
||||||
Q_INVOKABLE void removeComponent(int row, bool removeLast = false);
|
Q_INVOKABLE void removeComponent(int row, bool removeLast = false);
|
||||||
@@ -86,11 +94,12 @@ public:
|
|||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void typeChanged();
|
void typeChanged();
|
||||||
void focusRowChanged();
|
void focusRowChanged();
|
||||||
|
void hasRichFormattingChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ChatBarType::Type m_type = ChatBarType::None;
|
ChatBarType::Type m_type = ChatBarType::None;
|
||||||
|
|
||||||
void initializeModel();
|
void initializeModel(const QString &initialText = {});
|
||||||
|
|
||||||
std::optional<QString> getReplyEventId() override;
|
std::optional<QString> getReplyEventId() override;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user