From a6b9759a3183fae797a633ae696abdc4bd764d96 Mon Sep 17 00:00:00 2001 From: Tobias Fella Date: Mon, 11 Aug 2025 21:51:43 +0200 Subject: [PATCH] Unify Delegate context menus The menus have always been split into a menu for file-like content and text-like content This split makes some things a bit more complicated then necessary. --- src/app/qml/RoomPage.qml | 29 +- src/app/roommanager.cpp | 18 +- src/app/roommanager.h | 26 +- src/timeline/CMakeLists.txt | 2 - src/timeline/DelegateContextMenu.qml | 566 +++++++++++--------- src/timeline/FileDelegateContextMenu.qml | 140 ----- src/timeline/MessageDelegateContextMenu.qml | 143 ----- 7 files changed, 321 insertions(+), 603 deletions(-) delete mode 100644 src/timeline/FileDelegateContextMenu.qml delete mode 100644 src/timeline/MessageDelegateContextMenu.qml diff --git a/src/app/qml/RoomPage.qml b/src/app/qml/RoomPage.qml index ae934c0e6..255fef20d 100644 --- a/src/app/qml/RoomPage.qml +++ b/src/app/qml/RoomPage.qml @@ -215,21 +215,8 @@ Kirigami.Page { }); } - function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText, hoveredLink, isThread) { - const contextMenu = messageDelegateContextMenu.createObject(root, { - selectedText: selectedText, - hoveredLink: hoveredLink, - author: author, - eventId: eventId, - messageComponentType: messageComponentType, - plainText: plainText, - htmlText: htmlText, - }); - contextMenu.popup(); - } - - function onShowFileMenu(eventId, author, messageComponentType, plainText, mimeType, progressInfo, isThread) { - const contextMenu = fileDelegateContextMenu.createObject(root, { + function onShowDelegateMenu(eventId: string, author, messageComponentType, plainText: string, richText: string, mimeType: string, progressInfo, isThread: bool, selectedText: string, hoveredLink: string) { + const contextMenu = delegateContextMenu.createObject(root, { author: author, eventId: eventId, plainText: plainText, @@ -262,16 +249,8 @@ Kirigami.Page { } Component { - id: messageDelegateContextMenu - MessageDelegateContextMenu { - room: root.currentRoom - connection: root.currentRoom.connection - } - } - - Component { - id: fileDelegateContextMenu - FileDelegateContextMenu { + id: delegateContextMenu + DelegateContextMenu { room: root.currentRoom connection: root.currentRoom.connection } diff --git a/src/app/roommanager.cpp b/src/app/roommanager.cpp index ae9f70a29..f748a64c5 100644 --- a/src/app/roommanager.cpp +++ b/src/app/roommanager.cpp @@ -279,23 +279,15 @@ void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, Neoch return; } const auto &event = **it; - if (EventHandler::mediaInfo(room, &event).contains("mimeType"_L1)) { - Q_EMIT showFileMenu(eventId, + Q_EMIT showDelegateMenu(eventId, sender, MessageComponentType::typeForEvent(event), EventHandler::plainBody(room, &event), + EventHandler::richBody(room, &event), EventHandler::mediaInfo(room, &event)["mimeType"_L1].toString(), - room->fileTransferInfo(eventId)); - return; - } - - Q_EMIT showMessageMenu(eventId, - sender, - MessageComponentType::typeForEvent(event), - EventHandler::plainBody(room, &event), - EventHandler::richBody(room, &event), - selectedText, - hoveredLink); + room->fileTransferInfo(eventId), + selectedText, + hoveredLink); } bool RoomManager::hasOpenRoom() const diff --git a/src/app/roommanager.h b/src/app/roommanager.h index f3619f04d..29e61c2d2 100644 --- a/src/app/roommanager.h +++ b/src/app/roommanager.h @@ -296,23 +296,15 @@ Q_SIGNALS: /** * @brief Request to show a menu for the given event. */ - void showMessageMenu(const QString &eventId, - const NeochatRoomMember *author, - MessageComponentType::Type messageComponentType, - const QString &plainText, - const QString &htmlText, - const QString &selectedText, - const QString &hoveredLink); - - /** - * @brief Request to show a menu for the given media event. - */ - void showFileMenu(const QString &eventId, - const NeochatRoomMember *author, - MessageComponentType::Type messageComponentType, - const QString &plainText, - const QString &mimeType, - const FileTransferInfo &progressInfo); + void showDelegateMenu(const QString &eventId, + const NeochatRoomMember *author, + MessageComponentType::Type messageComponentType, + const QString &plainText, + const QString &richtText, + const QString &mimeType, + const FileTransferInfo &progressInfo, + const QString &selectedText, + const QString &hoveredLink); /** * @brief Show the direct chat confirmation dialog. diff --git a/src/timeline/CMakeLists.txt b/src/timeline/CMakeLists.txt index 47cbf4b48..beaf084b0 100644 --- a/src/timeline/CMakeLists.txt +++ b/src/timeline/CMakeLists.txt @@ -22,8 +22,6 @@ ecm_add_qml_module(Timeline GENERATE_PLUGIN_SOURCE QuickActions.qml TypingPane.qml DelegateContextMenu.qml - FileDelegateContextMenu.qml - MessageDelegateContextMenu.qml SOURCES messageattached.cpp messagedelegate.cpp diff --git a/src/timeline/DelegateContextMenu.qml b/src/timeline/DelegateContextMenu.qml index be1556925..a18d00c25 100644 --- a/src/timeline/DelegateContextMenu.qml +++ b/src/timeline/DelegateContextMenu.qml @@ -2,9 +2,12 @@ // SPDX-FileCopyrightText: 2020 Carl Schwan // SPDX-License-Identifier: GPL-3.0-only +import QtCore as Core import QtQuick import QtQuick.Controls as QQC2 import QtQuick.Layouts +import QtQuick.Dialogs as Dialogs + import Qt.labs.qmlmodels import org.kde.kirigami as Kirigami import org.kde.kirigamiaddons.components as KirigamiComponents @@ -14,7 +17,7 @@ import org.kde.kirigamiaddons.delegates as Delegates import org.kde.neochat /** - * @brief The base menu for most message types. + * @brief The menu for most message types. * * This menu supports showing a list of actions to be shown for a particular event * delegate in a message timeline. The menu supports both desktop and mobile menus @@ -22,9 +25,6 @@ import org.kde.neochat * * The menu supports both a list of main actions and the ability to define sub menus * using the nested action parameter. - * - * For event types that need alternate actions this class can be used as a base and - * the actions and nested actions can be overwritten to show the alternate items. */ KirigamiComponents.ConvergentContextMenu { id: root @@ -74,122 +74,26 @@ KirigamiComponents.ConvergentContextMenu { property string hoveredLink: "" /** - * Some common actions shared between menus + * @brief The HTML text of the event, if it is has one. */ - component ViewSourceAction: Kirigami.Action { - visible: NeoChatConfig.developerTools - text: i18n("View Source") - icon.name: "code-context" - onTriggered: RoomManager.viewEventSource(root.eventId) - } + property string htmlText: "" - component RemoveMessageAction: Kirigami.Action { - visible: (author.isLocalMember || currentRoom.canSendState("redact")) && root.messageComponentType !== MessageComponentType.Other - text: i18nc("@action:button", "Remove…") - icon.name: "edit-delete-remove" - icon.color: "red" - onTriggered: { - let dialog = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { - title: i18nc("@title:dialog", "Remove Message"), - placeholder: i18nc("@info:placeholder", "Reason for removing this message"), - actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"), - icon: "delete" - }, { - title: i18nc("@title:dialog", "Remove Message"), - width: Kirigami.Units.gridUnit * 25 - }); - dialog.accepted.connect(reason => { - currentRoom.redactEvent(root.eventId, reason); - }); - } - } + /** + * @brief Progress info when downloading files. + * + * @sa Quotient::FileTransferInfo + */ + required property var progressInfo - component ReplyMessageAction: Kirigami.Action { - visible: root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent - text: i18n("Reply") - icon.name: "mail-replied-symbolic" - onTriggered: { - currentRoom.mainCache.replyId = eventId; - currentRoom.editCache.editId = ""; - RoomManager.requestFullScreenClose(); - } - } + /** + * @brief The MIME type of the media, or an empty string if the event does not have file content + */ + property string mimeType - component ReplyThreadMessageAction: Kirigami.Action { - visible: root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent - text: i18nc("@action:button", "Reply in Thread") - icon.name: "dialog-messages" - onTriggered: { - currentRoom.threadCache.replyId = ""; - currentRoom.threadCache.threadId = currentRoom.eventIsThreaded(root.eventId) ? currentRoom.rootIdForThread(root.eventId) : root.eventId; - currentRoom.mainCache.clearRelations(); - currentRoom.editCache.clearRelations(); - RoomManager.requestFullScreenClose(); - } - } - - - component ReportMessageAction: Kirigami.Action { - text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report…") - icon.name: "dialog-warning-symbolic" - visible: !author.isLocalMember - onTriggered: { - let dialog = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { - title: i18nc("@title:dialog", "Report Message"), - placeholder: i18nc("@info:placeholder", "Reason for reporting this message"), - icon: "dialog-warning-symbolic", - actionText: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report") - }, { - title: i18nc("@title", "Report Message"), - width: Kirigami.Units.gridUnit * 25 - }); - dialog.accepted.connect(reason => { - currentRoom.reportEvent(root.eventId, reason); - }); - } - } - - component PinMessageAction: Kirigami.Action { - readonly property bool pinned: currentRoom.isEventPinned(root.eventId) - - visible: currentRoom.canSendState("m.room.pinned_events") && root.messageComponentType !== MessageComponentType.Other - text: pinned ? i18nc("@action:button 'Unpin' as in 'Unpin this message'", "Unpin") : i18nc("@action:button 'Pin' as in 'Pin the message in the room'", "Pin") - icon.name: pinned ? "window-unpin-symbolic" : "pin-symbolic" - onTriggered: pinned ? currentRoom.unpinEvent(root.eventId) : currentRoom.pinEvent(root.eventId) - } - - headerContentItem: RowLayout { - spacing: Kirigami.Units.largeSpacing - - KirigamiComponents.Avatar { - source: root.author.avatarUrl - - Layout.preferredWidth: Kirigami.Units.gridUnit * 2 - Layout.preferredHeight: Kirigami.Units.gridUnit * 2 - Layout.alignment: Qt.AlignTop - } - - ColumnLayout { - spacing: 0 - - Layout.fillWidth: true - - Kirigami.Heading { - level: 4 - text: root.author.htmlSafeDisplayName - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - QQC2.Label { - text: root.plainText - textFormat: Text.PlainText - elide: Text.ElideRight - onLinkActivated: RoomManager.resolveResource(link, "join") - Layout.fillWidth: true - } - } - } + /** + * @brief Whether the event has file-based content. This includes images, videos, and other files + */ + readonly property bool hasFileContent: mimeType.length > 0 Kirigami.Action { id: emojiAction @@ -247,167 +151,303 @@ KirigamiComponents.ConvergentContextMenu { } } - /* + Kirigami.Action { + separator: true + } + + Kirigami.Action { + visible: root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent + text: i18nc("@action:inmenu", "Reply") + icon.name: "mail-replied-symbolic" + onTriggered: { + currentRoom.mainCache.replyId = eventId; + currentRoom.editCache.editId = ""; + RoomManager.requestFullScreenClose(); } } - Component { - id: mobileMenu - Kirigami.OverlayDrawer { - id: drawer - height: stackView.implicitHeight - edge: Qt.BottomEdge - padding: 0 - leftPadding: 0 - rightPadding: 0 - bottomPadding: 0 - topPadding: 0 + Kirigami.Action { + visible: root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent + text: i18nc("@action:inmenu", "Reply in Thread") + icon.name: "dialog-messages" + onTriggered: { + currentRoom.threadCache.replyId = ""; + currentRoom.threadCache.threadId = currentRoom.eventIsThreaded(root.eventId) ? currentRoom.rootIdForThread(root.eventId) : root.eventId; + currentRoom.mainCache.clearRelations(); + currentRoom.editCache.clearRelations(); + RoomManager.requestFullScreenClose(); + } + } - QQC2.StackView { - id: stackView - width: parent.width - implicitHeight: currentItem.implicitHeight + Kirigami.Action { + visible: !root.hasFileContent && root.author.isLocalMember && root.messageComponentType === MessageComponentType.Text + text: i18n("Edit") + icon.name: "document-edit" + onTriggered: { + currentRoom.editCache.editId = eventId; + currentRoom.mainCache.replyId = ""; + currentRoom.mainCache.threadId = ""; + } + } - Component { - id: nestedActionsComponent - ColumnLayout { - id: actionLayout - property string title: "" - property list actions - width: parent.width - spacing: 0 - RowLayout { - QQC2.ToolButton { - icon.name: 'draw-arrow-back' - onClicked: stackView.pop() - } - Kirigami.Heading { - level: 3 - Layout.fillWidth: true - text: actionLayout.title - wrapMode: Text.WordWrap - } - } - Repeater { - id: listViewAction - model: actionLayout.actions + Kirigami.Action { + visible: (author.isLocalMember || currentRoom.canSendState("redact")) && root.messageComponentType !== MessageComponentType.Other + text: i18nc("@action:button", "Remove…") + icon.name: "edit-delete-remove" + icon.color: "red" + onTriggered: { + let dialog = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { + title: i18nc("@title:dialog", "Remove Message"), + placeholder: i18nc("@info:placeholder", "Reason for removing this message"), + actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"), + icon: "delete" + }, { + title: i18nc("@title:dialog", "Remove Message"), + width: Kirigami.Units.gridUnit * 25 + }); + dialog.accepted.connect(reason => { + currentRoom.redactEvent(root.eventId, reason); + }); + } + } - FormCard.FormButtonDelegate { - icon.name: modelData.icon.name - icon.color: modelData.icon.color ?? undefined - enabled: modelData.enabled - visible: modelData.visible - text: modelData.text - onClicked: { - modelData.triggered(); - root.item.close(); - } - } - } - } - } - initialItem: ColumnLayout { - id: popupContent - width: parent.width - spacing: 0 - RowLayout { - id: headerLayout - Layout.fillWidth: true - Layout.margins: Kirigami.Units.largeSpacing - spacing: Kirigami.Units.largeSpacing - KirigamiComponents.Avatar { - id: avatar - source: author.avatarUrl - Layout.preferredWidth: Kirigami.Units.gridUnit * 2 - Layout.preferredHeight: Kirigami.Units.gridUnit * 2 - Layout.alignment: Qt.AlignTop - } - ColumnLayout { - Layout.fillWidth: true - Kirigami.Heading { - level: 3 - Layout.fillWidth: true - text: author.htmlSafeDisplayName - wrapMode: Text.WordWrap - } - QQC2.Label { - text: plainText - Layout.fillWidth: true - wrapMode: Text.WordWrap + Kirigami.Action { + separator: true + } - onLinkActivated: RoomManager.resolveResource(link, "join") - } - } - } - Kirigami.Separator { - Layout.fillWidth: true - } - Kirigami.Separator { - Layout.fillWidth: true - } - Repeater { - id: listViewAction - model: root.actions + Kirigami.Action { + visible: root.messageComponentType !== MessageComponentType.Other + text: i18nc("@action:inmenu As in 'Forward this message'", "Forward…") + icon.name: "mail-forward-symbolic" + onTriggered: { + let page = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), { + connection: root.connection + }, { + title: i18nc("@title", "Forward Message"), + width: Kirigami.Units.gridUnit * 25 + }); + page.chosen.connect(function (targetRoomId) { + root.connection.room(targetRoomId).postHtmlMessage(root.plainText, root.htmlText.length > 0 ? root.htmlText : root.plainText); + page.closeDialog(); + }); + } + } - DelegateChooser { - role: "separator" - DelegateChoice { - roleValue: true + Kirigami.Action { + visible: root.hasFileContent + text: { + if (root.messageComponentType === MessageComponentType.Image) { + return i18nc("@action:inmenu", "Open Image"); + } else if (root.messageComponentType === MessageComponentType.Audio) { + return i18nc("@action:inmenu", "Open Audio"); + } else if (root.messageComponentType === MessageComponentType.Video) { + return i18nc("@action:inmenu", "Open Video"); + } else { + return i18nc("@action:inmenu", "Open File"); + } + } + icon.name: "document-open" + onTriggered: { + currentRoom.openEventMediaExternally(root.eventId); + } + } - FormCard.FormDelegateSeparator { - visible: modelData.visible - } - } + Kirigami.Action { + visible: root.hasFileContent + text: { + if (root.messageComponentType === MessageComponentType.Image) { + return i18nc("@action:inmenu", "Save Image…"); + } else if (root.messageComponentType === MessageComponentType.Audio) { + return i18nc("@action:inmenu", "Save Audio…"); + } else if (root.messageComponentType === MessageComponentType.Video) { + return i18nc("@action:inmenu", "Save Video…"); + } else { + return i18nc("@action:inmenu", "Save File…"); + } + } + icon.name: "document-save" + onTriggered: { + var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay); + dialog.selectedFile = currentRoom.fileNameToDownload(eventId); + dialog.open(); + } + } - DelegateChoice { - roleValue: false + Kirigami.Action { + visible: root.hasFileContent + text: { + if (root.messageComponentType === MessageComponentType.Image) { + return i18nc("@action:inmenu", "Copy Image"); + } else if (root.messageComponentType === MessageComponentType.Audio) { + return i18nc("@action:inmenu", "Copy Audio"); + } else if (root.messageComponentType === MessageComponentType.Video) { + return i18nc("@action:inmenu", "Copy Video"); + } else { + return i18nc("@action:inmenu", "Copy File"); + } + } + icon.name: "edit-copy" + onTriggered: { + currentRoom.copyEventMedia(root.eventId); + } + } - FormCard.FormButtonDelegate { - icon.name: modelData.icon.name - icon.color: modelData.icon.color ?? undefined - enabled: modelData.enabled - visible: modelData.visible - text: modelData.text - onClicked: { - modelData.triggered(); - root.item.close(); - } - } - } - } - } + Kirigami.Action { + text: i18nc("@action:inmenu", "Copy Link Address") + icon.name: "edit-copy" + visible: root.hoveredLink.length > 0 + onTriggered: Clipboard.saveText(root.hoveredLink) + } - Repeater { - model: root.nestedActions + Kirigami.Action { + visible: !root.hasFileContent + text: i18nc("@action:inmenu", "Copy Text") + icon.name: "edit-copy" + onTriggered: Clipboard.saveText(root.selectedText.length > 0 ? root.selectedText : root.plainText) + } - FormCard.FormButtonDelegate { - action: modelData - visible: modelData.visible - onClicked: { - stackView.push(nestedActionsComponent, { - title: modelData.text, - actions: modelData.children - }); - } - } - } - } + Kirigami.Action { + text: i18nc("@action:inmenu", "Copy Message Link") + icon.name: "link-symbolic" + onTriggered: { + Clipboard.saveText("https://matrix.to/#/" + currentRoom.id + "/" + root.eventId); + } + } + + Kirigami.Action { + text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report…") + icon.name: "dialog-warning-symbolic" + visible: !author.isLocalMember + onTriggered: { + let dialog = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), { + title: i18nc("@title:dialog", "Report Message"), + placeholder: i18nc("@info:placeholder", "Reason for reporting this message"), + icon: "dialog-warning-symbolic", + actionText: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report") + }, { + title: i18nc("@title", "Report Message"), + width: Kirigami.Units.gridUnit * 25 + }); + dialog.accepted.connect(reason => { + currentRoom.reportEvent(root.eventId, reason); + }); + } + } + + Kirigami.Action { + id: webShortcutModelAction + + text: i18nc("@action:inmenu", "Search for '%1'", webShortcutModel.trunkatedSearchText) + icon.name: "search-symbolic" + visible: !root.hasFileContent && webShortcutModel.enabled + + readonly property Instantiator instantiator: Instantiator { + model: WebShortcutModel { + id: webShortcutModel + selectedText: root.selectedText.length > 0 ? root.selectedText : root.plainText + onOpenUrl: url => RoomManager.resolveResource(url.toString()) + } + delegate: Kirigami.Action { + text: model.display + icon.name: model.decoration + onTriggered: webShortcutModel.trigger(model.edit) + } + onObjectAdded: (index, object) => webShortcutModelAction.children.push(object) + } + } + + Kirigami.Action { + text: i18nc("@action:inmenu", "Configure Web Shortcuts…") + icon.name: "configure" + visible: !Controller.isFlatpak && webShortcutModel.enabled + onTriggered: webShortcutModel.configureWebShortcuts() + } + + Kirigami.Action { + visible: !root.hasFileContent + text: i18nc("@action:inmenu", "Read Text Aloud") + icon.name: "audio-speakers-symbolic" + onTriggered: { + TextToSpeechHelper.speak(i18nc("@info text-to-speech %1 is author %2 is message text", "%1 said %2", root.author.displayName, root.plainText)) + } + } + + ShareAction { + id: shareAction + inputData: { + "urls": [filename], + "mimeType": [root.mimeType] + } + room: currentRoom + eventId: root.eventId + property string filename: Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId) + } + + Kirigami.Action { + readonly property bool pinned: currentRoom.isEventPinned(root.eventId) + + visible: currentRoom.canSendState("m.room.pinned_events") && root.messageComponentType !== MessageComponentType.Other + text: pinned ? i18nc("@action:button 'Unpin' as in 'Unpin this message'", "Unpin") : i18nc("@action:button 'Pin' as in 'Pin the message in the room'", "Pin") + icon.name: pinned ? "window-unpin-symbolic" : "pin-symbolic" + onTriggered: pinned ? currentRoom.unpinEvent(root.eventId) : currentRoom.pinEvent(root.eventId) + } + + Kirigami.Action { + separator: true + visible: viewSourceAction.visible + } + + Kirigami.Action { + id: viewSourceAction + visible: NeoChatConfig.developerTools + text: i18nc("@action:inmenu", "View Source") + icon.name: "code-context" + onTriggered: RoomManager.viewEventSource(root.eventId) + } + + readonly property Component saveAsDialog: Dialogs.FileDialog { + fileMode: Dialogs.FileDialog.SaveFile + currentFolder: NeoChatConfig.lastSaveDirectory.length > 0 ? NeoChatConfig.lastSaveDirectory : Core.StandardPaths.writableLocation(Core.StandardPaths.DownloadLocation) + onAccepted: { + if (!selectedFile) { + return; + } + NeoChatConfig.lastSaveDirectory = currentFolder; + NeoChatConfig.save(); + currentRoom.downloadFile(eventId, selectedFile); + } + } + + headerContentItem: RowLayout { + spacing: Kirigami.Units.largeSpacing + + KirigamiComponents.Avatar { + source: root.author.avatarUrl + + Layout.preferredWidth: Kirigami.Units.gridUnit * 2 + Layout.preferredHeight: Kirigami.Units.gridUnit * 2 + Layout.alignment: Qt.AlignTop + } + + ColumnLayout { + spacing: 0 + + Layout.fillWidth: true + + Kirigami.Heading { + level: 4 + text: root.author.htmlSafeDisplayName + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + QQC2.Label { + text: root.plainText + textFormat: Text.PlainText + elide: Text.ElideRight + onLinkActivated: RoomManager.resolveResource(link, "join") + Layout.fillWidth: true } } } - - asynchronous: true - sourceComponent: Kirigami.Settings.isMobile ? mobileMenu : regularMenu - - function open() { - active = true; - } - - onStatusChanged: if (status == Loader.Ready) { - if (Kirigami.Settings.isMobile) { - item.open(); - } else { - item.popup(); - } - }*/ } diff --git a/src/timeline/FileDelegateContextMenu.qml b/src/timeline/FileDelegateContextMenu.qml deleted file mode 100644 index 9affc349c..000000000 --- a/src/timeline/FileDelegateContextMenu.qml +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Black Hat -// SPDX-License-Identifier: GPL-3.0-only - -import QtCore as Core -import QtQuick -import QtQuick.Controls as QQC2 -import QtQuick.Dialogs as Dialogs - -import org.kde.kirigami as Kirigami - -import org.kde.neochat - -/** - * @brief The menu for media messages. - * - * This component just overloads the actions and nested actions of the base menu - * to what is required for a media item. - * - * @sa DelegateContextMenu - */ -DelegateContextMenu { - id: root - - /** - * @brief The MIME type of the media. - */ - property string mimeType - - /** - * @brief Progress info when downloading files. - * - * @sa Quotient::FileTransferInfo - */ - required property var progressInfo - - DelegateContextMenu.ReplyMessageAction {} - - Kirigami.Action { - separator: true - } - - QQC2.Action { - text: { - if (root.messageComponentType === MessageComponentType.Image) { - return i18nc("@action:inmenu", "Open Image"); - } else if (root.messageComponentType === MessageComponentType.Audio) { - return i18nc("@action:inmenu", "Open Audio"); - } else if (root.messageComponentType === MessageComponentType.Video) { - return i18nc("@action:inmenu", "Open Video"); - } else { - return i18nc("@action:inmenu", "Open File"); - } - } - icon.name: "document-open" - onTriggered: { - currentRoom.openEventMediaExternally(root.eventId); - } - } - - QQC2.Action { - text: { - if (root.messageComponentType === MessageComponentType.Image) { - return i18nc("@action:inmenu", "Save Image…"); - } else if (root.messageComponentType === MessageComponentType.Audio) { - return i18nc("@action:inmenu", "Save Audio…"); - } else if (root.messageComponentType === MessageComponentType.Video) { - return i18nc("@action:inmenu", "Save Video…"); - } else { - return i18nc("@action:inmenu", "Save File…"); - } - } - icon.name: "document-save" - onTriggered: { - var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay); - dialog.selectedFile = currentRoom.fileNameToDownload(eventId); - dialog.open(); - } - } - - QQC2.Action { - text: { - if (root.messageComponentType === MessageComponentType.Image) { - return i18nc("@action:inmenu", "Copy Image"); - } else if (root.messageComponentType === MessageComponentType.Audio) { - return i18nc("@action:inmenu", "Copy Audio"); - } else if (root.messageComponentType === MessageComponentType.Video) { - return i18nc("@action:inmenu", "Copy Video"); - } else { - return i18nc("@action:inmenu", "Copy File"); - } - } - icon.name: "edit-copy" - onTriggered: { - currentRoom.copyEventMedia(root.eventId); - } - } - - Kirigami.Action { - separator: true - } - - DelegateContextMenu.RemoveMessageAction {} - - DelegateContextMenu.PinMessageAction {} - - DelegateContextMenu.ReportMessageAction {} - - Kirigami.Action { - separator: true - visible: viewSourceAction.visible - } - - DelegateContextMenu.ViewSourceAction { - id: viewSourceAction - } - - ShareAction { - id: shareAction - inputData: { - "urls": [filename], - "mimeType": [root.mimeType] - } - room: currentRoom - eventId: root.eventId - property string filename: Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId) - } - - readonly property Component saveAsDialog: Dialogs.FileDialog { - fileMode: Dialogs.FileDialog.SaveFile - currentFolder: NeoChatConfig.lastSaveDirectory.length > 0 ? NeoChatConfig.lastSaveDirectory : Core.StandardPaths.writableLocation(Core.StandardPaths.DownloadLocation) - onAccepted: { - if (!selectedFile) { - return; - } - NeoChatConfig.lastSaveDirectory = currentFolder; - NeoChatConfig.save(); - currentRoom.downloadFile(eventId, selectedFile); - } - } -} diff --git a/src/timeline/MessageDelegateContextMenu.qml b/src/timeline/MessageDelegateContextMenu.qml deleted file mode 100644 index 145813a64..000000000 --- a/src/timeline/MessageDelegateContextMenu.qml +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Black Hat -// SPDX-FileCopyrightText: 2020 Carl Schwan -// SPDX-License-Identifier: GPL-3.0-only - -import QtQuick -import QtQuick.Controls as QQC2 -import QtTextToSpeech -import QtQuick.Layouts -import org.kde.kirigami as Kirigami -import org.kde.kirigamiaddons.components as KirigamiComponents -import org.kde.kirigamiaddons.formcard as FormCard -import org.kde.neochat - -import org.kde.kirigamiaddons.components as KirigamiComponents -import org.kde.kirigamiaddons.formcard as FormCard - -import org.kde.neochat - -/** - * @brief The menu for normal messages. - * - * This component just overloads the actions and nested actions of the base menu - * to what is required for a message item. - * - * @sa DelegateContextMenu - */ -DelegateContextMenu { - id: root - - /** - * @brief The display text of the message as rich text. - */ - required property string htmlText - - Kirigami.Action { - text: i18n("Edit") - icon.name: "document-edit" - onTriggered: { - currentRoom.editCache.editId = eventId; - currentRoom.mainCache.replyId = ""; - currentRoom.mainCache.threadId = ""; - } - visible: root.author.isLocalMember && root.messageComponentType === MessageComponentType.Text - } - - DelegateContextMenu.ReplyMessageAction {} - - DelegateContextMenu.ReplyThreadMessageAction {} - - Kirigami.Action { - visible: root.messageComponentType !== MessageComponentType.Other - text: i18nc("@action:inmenu As in 'Forward this message'", "Forward…") - icon.name: "mail-forward-symbolic" - onTriggered: { - let page = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), { - connection: root.connection - }, { - title: i18nc("@title", "Forward Message"), - width: Kirigami.Units.gridUnit * 25 - }); - page.chosen.connect(function (targetRoomId) { - root.connection.room(targetRoomId).postHtmlMessage(root.plainText, root.htmlText.length > 0 ? root.htmlText : root.plainText); - page.closeDialog(); - }); - } - } - Kirigami.Action { - visible: root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent - separator: true - } - DelegateContextMenu.RemoveMessageAction {} - Kirigami.Action { - text: i18nc("@action:inmenu", "Copy Link Address") - icon.name: "edit-copy" - visible: root.hoveredLink.length > 0 - onTriggered: Clipboard.saveText(root.hoveredLink) - } - QQC2.Action { - text: i18nc("@action:inmenu", "Copy Text") - icon.name: "edit-copy" - onTriggered: Clipboard.saveText(root.selectedText.length > 0 ? root.selectedText : root.plainText) - } - QQC2.Action { - text: i18nc("@action:inmenu", "Copy Message Link") - icon.name: "link-symbolic" - onTriggered: { - Clipboard.saveText("https://matrix.to/#/" + currentRoom.id + "/" + root.eventId); - } - } - QQC2.Action { - text: i18nc("@action:inmenu", "Read Text Aloud") - icon.name: "audio-speakers-symbolic" - onTriggered: { - TextToSpeechHelper.speak(i18nc("@info text-to-speech %1 is author %2 is message text", "%1 said %2", root.author.displayName, root.plainText)) - } - } - Kirigami.Action { - separator: true - } - DelegateContextMenu.PinMessageAction {} - DelegateContextMenu.ReportMessageAction {} - Kirigami.Action { - separator: true - visible: viewSourceAction.visible - } - DelegateContextMenu.ViewSourceAction { - id: viewSourceAction - } - - Kirigami.Action { - separator: true - visible: webShortcutModel.enabled - } - - Kirigami.Action { - id: webShortcutModelAction - - text: i18n("Search for '%1'", webShortcutModel.trunkatedSearchText) - icon.name: "search-symbolic" - visible: webShortcutModel.enabled - - readonly property Instantiator instantiator: Instantiator { - model: WebShortcutModel { - id: webShortcutModel - selectedText: root.selectedText.length > 0 ? root.selectedText : root.plainText - onOpenUrl: url => RoomManager.resolveResource(url.toString()) - } - delegate: QQC2.Action { - text: model.display - icon.name: model.decoration - onTriggered: webShortcutModel.trigger(model.edit) - } - onObjectAdded: (index, object) => webShortcutModelAction.children.push(object) - } - } - - Kirigami.Action { - text: i18n("Configure Web Shortcuts…") - icon.name: "configure" - visible: !Controller.isFlatpak && webShortcutModel.enabled - onTriggered: webShortcutModel.configureWebShortcuts() - } -}