diff --git a/autotests/chatkeyhelpertest.qml b/autotests/chatkeyhelpertest.qml index 1f6531128..76a8a48ce 100644 --- a/autotests/chatkeyhelpertest.qml +++ b/autotests/chatkeyhelpertest.qml @@ -9,19 +9,13 @@ import org.kde.neochat.libneochat import NeoChatTestUtils TestCase { - name: "ChatTextItemHelperTest" + name: "ChatKeyHelperTest" TextEdit { id: textEdit - Keys.onUpPressed: (event) => { - event.accepted = true; - testHelper.keyHelper.up(); - } - - Keys.onDownPressed: (event) => { - event.accepted = true; - testHelper.keyHelper.down(); + Keys.onPressed: (event) => { + event.accepted = testHelper.keyHelper.handleKey(event.key, event.modifiers); } } diff --git a/autotests/modeltest.cpp b/autotests/modeltest.cpp index c42c5988e..f542d43f7 100644 --- a/autotests/modeltest.cpp +++ b/autotests/modeltest.cpp @@ -29,6 +29,7 @@ #include "models/livelocationsmodel.h" #include "models/locationsmodel.h" #include "models/messagecontentfiltermodel.h" +#include "models/messagecontentmodel.h" #include "models/notificationsmodel.h" #include "models/permissionsmodel.h" #include "models/pinnedmessagemodel.h" @@ -179,8 +180,8 @@ void ModelTest::testRoomTreeModel() void ModelTest::testMessageContentModel() { - auto contentModel = new MessageContentModel(room, eventId); - auto tester = new QAbstractItemModelTester(contentModel); + auto contentModel = std::make_unique(room, eventId); + auto tester = new QAbstractItemModelTester(contentModel.get()); tester->setUseFetchMore(true); } diff --git a/src/chatbar/CMakeLists.txt b/src/chatbar/CMakeLists.txt index 1a4a85b6f..3738f90e5 100644 --- a/src/chatbar/CMakeLists.txt +++ b/src/chatbar/CMakeLists.txt @@ -8,6 +8,7 @@ ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE QML_FILES AttachDialog.qml ChatBar.qml + ChatBarCore.qml RichEditBar.qml SendBar.qml CompletionMenu.qml diff --git a/src/chatbar/ChatBar.qml b/src/chatbar/ChatBar.qml index 11f31f8e3..bab100818 100644 --- a/src/chatbar/ChatBar.qml +++ b/src/chatbar/ChatBar.qml @@ -42,9 +42,9 @@ Item { } } - property int chatBarType: LibNeoChat.ChatBarType.Room - - onActiveFocusChanged: chatContentView.itemAt(contentModel.index(contentModel.focusRow, 0)).forceActiveFocus() + onActiveFocusChanged: if (activeFocus) { + core.forceActiveFocus(); + } Connections { target: ShareHandler @@ -67,97 +67,16 @@ Item { } } - Message.room: root.currentRoom - Message.contentModel: contentModel - - implicitHeight: chatBar.implicitHeight + Kirigami.Units.largeSpacing - - QQC2.Control { - id: chatBar + implicitHeight: core.implicitHeight + Kirigami.Units.largeSpacing + ChatBarCore { + id: core anchors.top: root.top anchors.horizontalCenter: root.horizontalCenter - spacing: 0 - - width: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2 - topPadding: Kirigami.Units.smallSpacing - bottomPadding: Kirigami.Units.smallSpacing - - contentItem: ColumnLayout { - RichEditBar { - id: richEditBar - visible: NeoChatConfig.sendMessageWith === 1 - maxAvailableWidth: chatBarSizeHelper.availableWidth - Kirigami.Units.largeSpacing * 2 - - room: root.currentRoom - contentModel: chatContentView.model - - onClicked: contentModel.refocusCurrentComponent() - } - Kirigami.Separator { - Layout.fillWidth: true - visible: NeoChatConfig.sendMessageWith === 1 - } - RowLayout { - spacing: 0 - QQC2.ScrollView { - id: chatScrollView - Layout.fillWidth: true - Layout.maximumHeight: Kirigami.Units.gridUnit * 8 - - clip: true - - ColumnLayout { - readonly property real visibleTop: chatScrollView.QQC2.ScrollBar.vertical.position * chatScrollView.contentHeight - readonly property real visibleBottom: chatScrollView.QQC2.ScrollBar.vertical.position * chatScrollView.contentHeight + chatScrollView.QQC2.ScrollBar.vertical.size * chatScrollView.contentHeight - readonly property rect cursorRectInColumn: mapFromItem(contentModel.focusedTextItem.textItem, contentModel.focusedTextItem.cursorRectangle); - onCursorRectInColumnChanged: { - if (chatScrollView.QQC2.ScrollBar.vertical.visible) { - if (cursorRectInColumn.y < visibleTop) { - chatScrollView.QQC2.ScrollBar.vertical.position = cursorRectInColumn.y / chatScrollView.contentHeight - } else if (cursorRectInColumn.y + cursorRectInColumn.height > visibleBottom) { - chatScrollView.QQC2.ScrollBar.vertical.position = (cursorRectInColumn.y + cursorRectInColumn.height - (chatScrollView.QQC2.ScrollBar.vertical.size * chatScrollView.contentHeight)) / chatScrollView.contentHeight - } - } - } - - width: chatScrollView.width - spacing: Kirigami.Units.smallSpacing - - Repeater { - id: chatContentView - model: ChatBarMessageContentModel { - id: contentModel - type: root.chatBarType - room: root.currentRoom - sendMessageWithEnter: NeoChatConfig.sendMessageWith === 0 - } - - delegate: MessageComponentChooser { - rightAnchorMargin: chatScrollView.QQC2.ScrollBar.vertical.visible ? chatScrollView.QQC2.ScrollBar.vertical.width : 0 - } - } - } - } - SendBar { - room: root.currentRoom - contentModel: chatContentView.model - } - } - } - - background: Kirigami.ShadowedRectangle { - Kirigami.Theme.colorSet: Kirigami.Theme.View - Kirigami.Theme.inherit: false - - radius: Kirigami.Units.cornerRadius - color: Kirigami.Theme.backgroundColor - border { - color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast) - width: 1 - } - } + Message.room: root.currentRoom + room: root.currentRoom + maxAvailableWidth: chatBarSizeHelper.availableWidth } MouseArea { id: hoverArea @@ -165,7 +84,7 @@ Item { top: chatModeButton.top left: root.left right: root.right - bottom: chatBar.top + bottom: core.top } propagateComposedEvents: true hoverEnabled: true @@ -174,12 +93,12 @@ Item { QQC2.Button { id: chatModeButton anchors { - bottom: chatBar.top + bottom: core.top bottomMargin: Kirigami.Units.smallSpacing horizontalCenter: root.horizontalCenter } - visible: hoverArea.containsMouse || hovered || chatBar.hovered + visible: hoverArea.containsMouse || hovered || core.hovered width: Kirigami.Units.iconSizes.enormous height: Kirigami.Units.iconSizes.smallMedium @@ -197,27 +116,4 @@ Item { endPercentWidth: NeoChatConfig.compactLayout ? 100 : 85 maxWidth: NeoChatConfig.compactLayout ? root.width - Kirigami.Units.largeSpacing * 2 : Kirigami.Units.gridUnit * 60 } - - QtObject { - id: _private - - property LibNeoChat.CompletionModel completionModel: LibNeoChat.CompletionModel { - room: root.currentRoom - type: root.chatBarType - textItem: contentModel.focusedTextItem - roomListModel: RoomManager.roomListModel - userListModel: RoomManager.userListModel - - onIsCompletingChanged: { - if (!isCompleting) { - return; - } - - let dialog = Qt.createComponent('org.kde.neochat.chatbar', 'CompletionMenu').createObject(contentModel.focusedTextItem.textItem, { - model: _private.completionModel, - keyHelper: contentModel.keyHelper - }).open(); - } - } - } } diff --git a/src/chatbar/ChatBarCore.qml b/src/chatbar/ChatBarCore.qml index e7d9eee03..7e7115e58 100644 --- a/src/chatbar/ChatBarCore.qml +++ b/src/chatbar/ChatBarCore.qml @@ -8,14 +8,26 @@ import QtQuick.Layouts import org.kde.kirigami as Kirigami +import org.kde.neochat import org.kde.neochat.libneochat as LibNeoChat QQC2.Control { id: root - required property real availableWidth + /** + * @brief The current room that user is viewing. + */ + required property LibNeoChat.NeoChatRoom room - width: root.availableWidth - Kirigami.Units.largeSpacing * 2 + property int chatBarType: LibNeoChat.ChatBarType.Room + + required property real maxAvailableWidth + + Message.contentModel: contentModel + + onActiveFocusChanged: contentModel.refocusCurrentComponent() + + implicitWidth: root.maxAvailableWidth - (root.maxAvailableWidth >= (parent?.width ?? 0) ? Kirigami.Units.largeSpacing * 2 : 0) topPadding: Kirigami.Units.smallSpacing bottomPadding: Kirigami.Units.smallSpacing @@ -23,9 +35,9 @@ QQC2.Control { RichEditBar { id: richEditBar visible: NeoChatConfig.sendMessageWith === 1 - maxAvailableWidth: root.availableWidth - Kirigami.Units.largeSpacing * 2 + maxAvailableWidth: root.maxAvailableWidth - Kirigami.Units.largeSpacing * 2 - room: root.currentRoom + room: root.room contentModel: chatContentView.model onClicked: contentModel.refocusCurrentComponent() @@ -65,18 +77,18 @@ QQC2.Control { model: ChatBarMessageContentModel { id: contentModel type: root.chatBarType - room: root.currentRoom + room: root.room sendMessageWithEnter: NeoChatConfig.sendMessageWith === 0 } - delegate: MessageComponentChooser { + delegate: BaseMessageComponentChooser { rightAnchorMargin: chatScrollView.QQC2.ScrollBar.vertical.visible ? chatScrollView.QQC2.ScrollBar.vertical.width : 0 } } } } SendBar { - room: root.currentRoom + room: root.room contentModel: chatContentView.model } } @@ -93,4 +105,27 @@ QQC2.Control { width: 1 } } + + QtObject { + id: _private + + property LibNeoChat.CompletionModel completionModel: LibNeoChat.CompletionModel { + room: root.room + type: root.chatBarType + textItem: contentModel.focusedTextItem + roomListModel: RoomManager.roomListModel + userListModel: RoomManager.userListModel + + onIsCompletingChanged: { + if (!isCompleting) { + return; + } + + let dialog = Qt.createComponent('org.kde.neochat.chatbar', 'CompletionMenu').createObject(contentModel.focusedTextItem.textItem, { + model: _private.completionModel, + keyHelper: contentModel.keyHelper + }).open(); + } + } + } } diff --git a/src/chatbar/SendBar.qml b/src/chatbar/SendBar.qml index b19359f6f..d3ff28f6a 100644 --- a/src/chatbar/SendBar.qml +++ b/src/chatbar/SendBar.qml @@ -31,7 +31,7 @@ RowLayout { property bool isBusy: root.room && root.room.hasFileUploading - visible: root.chatBarCache.attachmentPath.length === 0 + visible: !root.contentModel.hasAttachment && (root.contentModel?.type ?? true) === LibNeoChat.ChatBarType.Room icon.name: "mail-attachment" text: i18n("Attach an image or file") display: QQC2.AbstractButton.IconOnly @@ -84,6 +84,7 @@ RowLayout { } QQC2.ToolButton { id: mapButton + visible: (root.contentModel?.type ?? true) === LibNeoChat.ChatBarType.Room icon.name: "globe" property bool isBusy: false text: i18n("Send a Location") @@ -100,6 +101,7 @@ RowLayout { } QQC2.ToolButton { id: pollButton + visible: (root.contentModel?.type ?? true) === LibNeoChat.ChatBarType.Room icon.name: "amarok_playcount" property bool isBusy: false text: i18nc("@action:button", "Create a Poll") @@ -128,4 +130,21 @@ RowLayout { QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay QQC2.ToolTip.text: text } + QQC2.ToolButton { + id: cancelButton + visible: (root.contentModel?.type ?? true) === LibNeoChat.ChatBarType.Edit + display: QQC2.AbstractButton.IconOnly + text: i18nc("@action:button", "Cancel") + icon.name: "dialog-close" + onClicked: root.room.cacheForType(contentModel.type).clearRelations() + + Kirigami.Action { + shortcut: "Escape" + onTriggered: cancelButton.clicked() + } + + QQC2.ToolTip.text: text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } } diff --git a/src/libneochat/chatbarcache.h b/src/libneochat/chatbarcache.h index 67a3d1dde..22cd014dd 100644 --- a/src/libneochat/chatbarcache.h +++ b/src/libneochat/chatbarcache.h @@ -30,7 +30,7 @@ struct Mention { * A class to cache data from a chat bar. * * A chat bar can be anything that allows users to compose or edit message, it doesn't - * necessarily have to use the ChatBar component, e.g. ChatBarComponent. + * necessarily have to use the ChatBar component. * * This object is intended to allow the current contents of a chat bar to be cached * between different rooms, i.e. there is an expectation that each NeoChatRoom could @@ -40,7 +40,7 @@ struct Mention { * as it's parent. This is necessary for certain functions which need to get * relevant room information. * - * @sa ChatBar, ChatBarComponent, NeoChatRoom + * @sa ChatBar, NeoChatRoom */ class ChatBarCache : public QObject { diff --git a/src/libneochat/neochatroom.h b/src/libneochat/neochatroom.h index 5015a9c14..becc3bce6 100644 --- a/src/libneochat/neochatroom.h +++ b/src/libneochat/neochatroom.h @@ -512,7 +512,7 @@ public: ChatBarCache *threadCache() const; - ChatBarCache *cacheForType(ChatBarType::Type type) const; + Q_INVOKABLE ChatBarCache *cacheForType(ChatBarType::Type type) const; /** * @brief Reply to the last message sent in the timeline. diff --git a/src/libneochat/texthandler.cpp b/src/libneochat/texthandler.cpp index bde1aaec3..0a3267a64 100644 --- a/src/libneochat/texthandler.cpp +++ b/src/libneochat/texthandler.cpp @@ -52,8 +52,8 @@ void TextHandler::setData(const QString &string) QString TextHandler::handleSendText() { m_pos = 0; - m_dataBuffer = customMarkdownToHtml(m_data); - m_dataBuffer = markdownToHTML(m_dataBuffer); + m_dataBuffer = markdownToHTML(m_data); + m_dataBuffer = customMarkdownToHtml(m_dataBuffer); m_nextTokenType = nextTokenType(m_dataBuffer, m_pos, m_nextToken, m_nextTokenType); @@ -805,6 +805,8 @@ QString TextHandler::customMarkdownToHtml(const QString &stringIn) // underline processSyntax(u"_"_s, u""_s, u""_s); + qWarning() << buffer; + return buffer; } diff --git a/src/messagecontent/BaseMessageComponentChooser.qml b/src/messagecontent/BaseMessageComponentChooser.qml index 6bbe88335..634918d60 100644 --- a/src/messagecontent/BaseMessageComponentChooser.qml +++ b/src/messagecontent/BaseMessageComponentChooser.qml @@ -151,11 +151,6 @@ DelegateChooser { } } - DelegateChoice { - roleValue: MessageComponentType.ChatBar - delegate: ChatBarComponent {} - } - DelegateChoice { roleValue: MessageComponentType.ReplyButton delegate: ReplyButtonComponent {} diff --git a/src/messagecontent/CMakeLists.txt b/src/messagecontent/CMakeLists.txt index 1e94e5d84..c747801aa 100644 --- a/src/messagecontent/CMakeLists.txt +++ b/src/messagecontent/CMakeLists.txt @@ -11,7 +11,6 @@ ecm_add_qml_module(MessageContent GENERATE_PLUGIN_SOURCE ReplyMessageComponentChooser.qml AuthorComponent.qml AudioComponent.qml - ChatBarComponent.qml CodeComponent.qml EncryptedComponent.qml FetchButtonComponent.qml diff --git a/src/messagecontent/ChatBarComponent.qml b/src/messagecontent/ChatBarComponent.qml deleted file mode 100644 index c0ce9de4d..000000000 --- a/src/messagecontent/ChatBarComponent.qml +++ /dev/null @@ -1,269 +0,0 @@ -// SPDX-FileCopyrightText: 2023 James Graham -// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL - -pragma ComponentBehavior: Bound - -import QtCore -import QtQuick -import QtQuick.Controls as QQC2 -import QtQuick.Layouts - -import org.kde.kirigami as Kirigami - -import org.kde.neochat -import org.kde.neochat.chatbar - -/** - * @brief A component to show a chat bar in a message bubble. - */ -QQC2.Control { - id: root - - /** - * @brief The ChatBarCache to use. - */ - required property ChatBarCache chatBarCache - - readonly property bool isBusy: root.Message.room && root.Message.room.hasFileUploading - - Layout.fillWidth: true - Layout.maximumWidth: Message.maxContentWidth - implicitWidth: Message.maxContentWidth - - contentItem: ColumnLayout { - Loader { - id: paneLoader - - Layout.fillWidth: true - Layout.margins: Kirigami.Units.largeSpacing - Layout.preferredHeight: active ? (item as Item).implicitHeight : 0 - - active: visible - visible: root.chatBarCache.replyId.length > 0 || root.chatBarCache.attachmentPath.length > 0 - sourceComponent: root.chatBarCache.replyId.length > 0 ? replyPane : attachmentPane - } - RowLayout { - Layout.fillWidth: true - spacing: 0 - - QQC2.ScrollView { - id: chatBarScrollView - Layout.topMargin: Kirigami.Units.smallSpacing - Layout.bottomMargin: Kirigami.Units.smallSpacing - Layout.leftMargin: Kirigami.Units.largeSpacing - Layout.rightMargin: Kirigami.Units.largeSpacing - - Layout.fillWidth: true - Layout.maximumHeight: Kirigami.Units.gridUnit * 8 - - QQC2.TextArea { - id: textArea - - // Work around for BUG: 503846 - // Seems to crash when we try to access textArea's text here. Even though we add a slight delay it's still instantaneous in the UI. - Component.onCompleted: Qt.callLater(() => _private.updateText()) - - Layout.fillWidth: true - - color: Kirigami.Theme.textColor - verticalAlignment: TextEdit.AlignVCenter - wrapMode: TextEdit.Wrap - - onTextChanged: { - root.chatBarCache.text = text; - } - - Keys.onEnterPressed: event => { - if (completionMenu.visible) { - completionMenu.completeCurrent(); - } else if (event.modifiers & Qt.ShiftModifier) { - textArea.insert(cursorPosition, "\n"); - } else { - _private.post(); - } - } - Keys.onReturnPressed: event => { - if (completionMenu.visible) { - completionMenu.completeCurrent(); - } else if (event.modifiers & Qt.ShiftModifier) { - textArea.insert(cursorPosition, "\n"); - } else { - _private.post(); - } - } - Keys.onTabPressed: { - if (completionMenu.visible) { - completionMenu.completeCurrent(); - } - } - Keys.onPressed: event => { - if (event.key === Qt.Key_Up && completionMenu.visible) { - completionMenu.decrementIndex(); - } else if (event.key === Qt.Key_Down && completionMenu.visible) { - completionMenu.incrementIndex(); - } - } - - // CompletionMenu { - // id: completionMenu - // width: Math.max(350, root.width - 1) - // height: implicitHeight - // y: -height - 5 - // z: 10 - // margins: 0 - // Behavior on height { - // NumberAnimation { - // property: "height" - // duration: Kirigami.Units.shortDuration - // easing.type: Easing.OutCubic - // } - // } - // } - - // opt-out of whatever spell checker a styled TextArea might come with - Kirigami.SpellCheck.enabled: false - - TextMetrics { - id: textMetrics - text: textArea.text - } - - Component { - id: openFileDialog - - OpenFileDialog { - parentWindow: Window.window - currentFolder: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0] - } - } - - Component { - id: attachDialog - - AttachDialog { - anchors.centerIn: parent - } - } - - background: null - } - } - PieProgressBar { - visible: root.isBusy - progress: root.Message.room.fileUploadingProgress - } - QQC2.ToolButton { - visible: !root.isBusy - display: QQC2.AbstractButton.IconOnly - text: i18nc("@action:button", "Attach an image or file") - icon.name: "mail-attachment" - onClicked: { - let dialog = (Clipboard.hasImage ? attachDialog : openFileDialog).createObject(QQC2.Overlay.overlay) as QQC2.Dialog; - dialog.chosen.connect(path => root.chatBarCache.attachmentPath = path); - dialog.open(); - } - - QQC2.ToolTip.text: text - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - } - QQC2.ToolButton { - display: QQC2.AbstractButton.IconOnly - text: root.chatBarCache.isEditing ? i18nc("@action:button", "Confirm edit") : i18nc("@action:button", "Post message in thread") - icon.name: "document-send" - onClicked: _private.post() - - QQC2.ToolTip.text: text - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - } - QQC2.ToolButton { - id: cancelButton - display: QQC2.AbstractButton.IconOnly - text: i18nc("@action:button", "Cancel") - icon.name: "dialog-close" - onClicked: { - root.chatBarCache.clearRelations(); - } - - Kirigami.Action { - shortcut: "Escape" - onTriggered: cancelButton.clicked() - } - - QQC2.ToolTip.text: text - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - } - } - } - - background: Rectangle { - color: Kirigami.Theme.backgroundColor - radius: Kirigami.Units.cornerRadius - border { - width: 1 - color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast) - } - } - - Component { - id: replyPane - Item { - implicitWidth: replyComponent.implicitWidth - implicitHeight: replyComponent.implicitHeight - ReplyComponent { - id: replyComponent - replyContentModel: ContentProvider.contentModelForEvent(root.Message.room, root.chatBarCache.replyId, true) - Message.maxContentWidth: paneLoader.item.width - } - QQC2.Button { - anchors.top: parent.top - anchors.right: parent.right - - display: QQC2.AbstractButton.IconOnly - text: i18nc("@action:button", "Cancel reply") - icon.name: "dialog-close" - onClicked: { - root.chatBarCache.replyId = ""; - root.chatBarCache.attachmentPath = ""; - } - QQC2.ToolTip.text: text - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - } - } - } - Component { - id: attachmentPane - AttachmentPane { - attachmentPath: root.chatBarCache.attachmentPath - - onAttachmentCancelled: { - root.chatBarCache.attachmentPath = ""; - root.forceActiveFocus(); - } - } - } - - QtObject { - id: _private - - function updateText() { - // This could possibly be undefined due to some esoteric QtQuick issue. Referencing it somewhere in JS is enough. - documentHandler.document; - if (root.chatBarCache?.isEditing && root.chatBarCache.relationMessage.length > 0) { - textArea.text = root.chatBarCache.relationMessage; - documentHandler.updateMentions(root.chatBarCache.editId); - textArea.forceActiveFocus(); - textArea.cursorPosition = textArea.text.length; - } - } - - function post() { - root.chatBarCache.postMessage(); - textArea.clear(); - root.chatBarCache.clearRelations(); - } - } -} diff --git a/src/messagecontent/MessageComponentChooser.qml b/src/messagecontent/MessageComponentChooser.qml index 3af15f5a1..7d5d2c1e3 100644 --- a/src/messagecontent/MessageComponentChooser.qml +++ b/src/messagecontent/MessageComponentChooser.qml @@ -2,9 +2,11 @@ // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL import QtQuick +import QtQuick.Layouts import Qt.labs.qmlmodels import org.kde.neochat +import org.kde.neochat.libneochat as LibNeoChat /** * @brief Select a message component based on a MessageComponentType. @@ -23,4 +25,15 @@ BaseMessageComponentChooser { } } } + + DelegateChoice { + roleValue: MessageComponentType.ChatBar + delegate: ChatBarCore { + Layout.fillWidth: true + Layout.maximumWidth: Message.maxContentWidth + room: Message.room + chatBarType: LibNeoChat.ChatBarType.Edit + maxAvailableWidth: Message.maxContentWidth + } + } } diff --git a/src/messagecontent/models/chatbarmessagecontentmodel.cpp b/src/messagecontent/models/chatbarmessagecontentmodel.cpp index 7cc34464b..bbbf438ff 100644 --- a/src/messagecontent/models/chatbarmessagecontentmodel.cpp +++ b/src/messagecontent/models/chatbarmessagecontentmodel.cpp @@ -77,7 +77,17 @@ void ChatBarMessageContentModel::connectCache(ChatBarCache *oldCache) oldCache->disconnect(this); } - connect(m_room->cacheForType(m_type), &ChatBarCache::relationIdChanged, this, &ChatBarMessageContentModel::updateReplyModel); + connect(m_room->cacheForType(m_type), &ChatBarCache::relationIdChanged, this, [this]() { + if (!m_room || m_type == ChatBarType::None) { + return; + } + const auto currentCache = m_room->cacheForType(m_type); + if (currentCache->isReplying()) { + updateReplyModel(); + } else if (currentCache->isEditing()) { + initializeFromCache(); + } + }); connect(m_room->cacheForType(m_type), &ChatBarCache::attachmentPathChanged, this, [this]() { if (m_room->cacheForType(m_type)->attachmentPath().length() > 0) { addAttachment(QUrl(m_room->cacheForType(m_type)->attachmentPath())); @@ -114,7 +124,8 @@ void ChatBarMessageContentModel::initializeFromCache() clearModel(); - const auto textSections = m_room->cacheForType(m_type)->text().split(u"\n\n"_s); + const auto currentCache = m_room->cacheForType(m_type); + const auto textSections = (m_type == ChatBarType::Room ? currentCache->text() : currentCache->relationMessage()).split(u"\n\n"_s); if (textSections.length() == 1 && textSections[0].isEmpty()) { initializeModel(); return; @@ -334,6 +345,11 @@ bool ChatBarMessageContentModel::hasRichFormatting() const return false; } +bool ChatBarMessageContentModel::hasAttachment() const +{ + return hasComponentType({MessageComponentType::File, MessageComponentType::Audio, MessageComponentType::Image, MessageComponentType::Video}); +} + void ChatBarMessageContentModel::addAttachment(const QUrl &path) { if (m_type == ChatBarType::None || !m_room) { @@ -360,6 +376,7 @@ void ChatBarMessageContentModel::addAttachment(const QUrl &path) it->display = path.fileName(); Q_EMIT dataChanged(index(std::distance(m_components.begin(), it)), index(std::distance(m_components.begin(), it)), {DisplayRole}); m_room->cacheForType(m_type)->setAttachmentPath(path.toString()); + Q_EMIT hasAttachmentChanged(); } ChatBarMessageContentModel::ComponentIt @@ -477,6 +494,7 @@ void ChatBarMessageContentModel::removeAttachment() if (m_room) { m_room->cacheForType(m_type)->setAttachmentPath({}); } + Q_EMIT hasAttachmentChanged(); } bool ChatBarMessageContentModel::sendMessageWithEnter() const diff --git a/src/messagecontent/models/chatbarmessagecontentmodel.h b/src/messagecontent/models/chatbarmessagecontentmodel.h index f43d97d7b..0f6bb0288 100644 --- a/src/messagecontent/models/chatbarmessagecontentmodel.h +++ b/src/messagecontent/models/chatbarmessagecontentmodel.h @@ -67,6 +67,11 @@ class ChatBarMessageContentModel : public MessageContentModel */ Q_PROPERTY(bool hasRichFormatting READ hasRichFormatting NOTIFY hasRichFormattingChanged) + /** + * @brief Whether the model has an attachment.. + */ + Q_PROPERTY(bool hasAttachment READ hasAttachment NOTIFY hasAttachmentChanged) + /** * @brief The UserListModel to be used for room completions. */ @@ -89,6 +94,7 @@ public: Q_INVOKABLE void insertComponentAtCursor(MessageComponentType::Type type); bool hasRichFormatting() const; + bool hasAttachment() const; Q_INVOKABLE void addAttachment(const QUrl &path); Q_INVOKABLE void removeComponent(int row, bool removeLast = false); @@ -104,6 +110,7 @@ Q_SIGNALS: void typeChanged(ChatBarType::Type oldType, ChatBarType::Type newType); void focusRowChanged(); void hasRichFormattingChanged(); + void hasAttachmentChanged(); void sendMessageWithEnterChanged(); private: diff --git a/src/messagecontent/models/messagecontentmodel.cpp b/src/messagecontent/models/messagecontentmodel.cpp index 984394adf..c87eacad7 100644 --- a/src/messagecontent/models/messagecontentmodel.cpp +++ b/src/messagecontent/models/messagecontentmodel.cpp @@ -244,7 +244,7 @@ QHash MessageContentModel::roleNamesStatic() return roles; } -bool MessageContentModel::hasComponentType(MessageComponentType::Type type) +bool MessageContentModel::hasComponentType(MessageComponentType::Type type) const { return std::find_if(m_components.cbegin(), m_components.cend(), @@ -254,7 +254,7 @@ bool MessageContentModel::hasComponentType(MessageComponentType::Type type) != m_components.cend(); } -bool MessageContentModel::hasComponentType(QList types) +bool MessageContentModel::hasComponentType(QList types) const { for (const auto &type : types) { if (hasComponentType(type)) { diff --git a/src/messagecontent/models/messagecontentmodel.h b/src/messagecontent/models/messagecontentmodel.h index 3602a512e..4169cda75 100644 --- a/src/messagecontent/models/messagecontentmodel.h +++ b/src/messagecontent/models/messagecontentmodel.h @@ -164,8 +164,8 @@ protected: using ComponentIt = QList::iterator; QList m_components; - bool hasComponentType(MessageComponentType::Type type); - bool hasComponentType(QList types); + bool hasComponentType(MessageComponentType::Type type) const; + bool hasComponentType(QList types) const; void forEachComponentOfType(MessageComponentType::Type type, std::function function); void forEachComponentOfType(QList types, std::function function);