From 33c0cae64c9810cb7abd66cb8f0bb1d1a7db7cc3 Mon Sep 17 00:00:00 2001 From: James Graham Date: Fri, 15 Sep 2023 13:57:40 +0000 Subject: [PATCH] Message menu rework Rework the file menu so that it no longer relies on having a reference to the media delegate to manage a download for either opening externally or copying to clipboard. This allows the menus to be moved out of the delegates and maximize components and have them accessed through RoomManager. This reduces duplication and reduces the number of components in an already heavy delegate. --- src/neochatroom.cpp | 58 +++++++++++ src/neochatroom.h | 14 +++ .../Component/NeochatMaximizeComponent.qml | 32 +++---- src/qml/Component/Timeline/AudioDelegate.qml | 2 +- src/qml/Component/Timeline/FileDelegate.qml | 2 +- src/qml/Component/Timeline/ImageDelegate.qml | 2 +- .../Component/Timeline/MessageDelegate.qml | 2 +- .../Component/Timeline/TimelineContainer.qml | 38 -------- src/qml/Component/Timeline/VideoDelegate.qml | 2 +- .../Menu/Timeline/FileDelegateContextMenu.qml | 55 ++++++----- .../Timeline/MessageDelegateContextMenu.qml | 95 +++++++++++++++---- src/qml/Page/RoomPage.qml | 38 ++++++++ src/roommanager.cpp | 24 +++++ src/roommanager.h | 44 +++++++++ 14 files changed, 305 insertions(+), 103 deletions(-) diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index 8077f677d..ae785f92b 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -39,6 +39,7 @@ #include #include +#include "clipboard.h" #include "controller.h" #include "eventhandler.h" #include "events/joinrulesevent.h" @@ -47,6 +48,7 @@ #include "neochatconfig.h" #include "notificationsmanager.h" #include "texthandler.h" +#include "urlhelper.h" #include "utils.h" #include @@ -1344,6 +1346,62 @@ QByteArray NeoChatRoom::getEventJsonSource(const QString &eventId) return {}; } +void NeoChatRoom::openEventMediaExternally(const QString &eventId) +{ + const auto evtIt = findInTimeline(eventId); + if (evtIt != messageEvents().rend() && is(**evtIt)) { + const auto event = evtIt->viewAs(); + if (event->hasFileContent()) { + const auto transferInfo = fileTransferInfo(eventId); + if (transferInfo.completed()) { + UrlHelper helper; + helper.openUrl(transferInfo.localPath); + } else { + downloadFile(eventId, + QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/' + + event->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId))); + connect(this, &Room::fileTransferCompleted, this, [this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) { + Q_UNUSED(localFile); + Q_UNUSED(fileMetadata); + if (id == eventId) { + auto transferInfo = fileTransferInfo(eventId); + UrlHelper helper; + helper.openUrl(transferInfo.localPath); + } + }); + } + } + } +} + +void NeoChatRoom::copyEventMedia(const QString &eventId) +{ + const auto evtIt = findInTimeline(eventId); + if (evtIt != messageEvents().rend() && is(**evtIt)) { + const auto event = evtIt->viewAs(); + if (event->hasFileContent()) { + const auto transferInfo = fileTransferInfo(eventId); + if (transferInfo.completed()) { + Clipboard clipboard; + clipboard.setImage(transferInfo.localPath); + } else { + downloadFile(eventId, + QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/' + + event->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId))); + connect(this, &Room::fileTransferCompleted, this, [this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) { + Q_UNUSED(localFile); + Q_UNUSED(fileMetadata); + if (id == eventId) { + auto transferInfo = fileTransferInfo(eventId); + Clipboard clipboard; + clipboard.setImage(transferInfo.localPath); + } + }); + } + } + } +} + QString NeoChatRoom::chatBoxText() const { return m_chatBoxText; diff --git a/src/neochatroom.h b/src/neochatroom.h index ca36f9d8b..39a3e838d 100644 --- a/src/neochatroom.h +++ b/src/neochatroom.h @@ -553,6 +553,20 @@ public: Q_INVOKABLE QByteArray getEventJsonSource(const QString &eventId); + /** + * @brief Open the media for the given event in an appropriate external app. + * + * Will do nothing if the event has no media. + */ + Q_INVOKABLE void openEventMediaExternally(const QString &eventId); + + /** + * @brief Copy the media for the given event to the clipboard. + * + * Will do nothing if the event has no media. + */ + Q_INVOKABLE void copyEventMedia(const QString &eventId); + [[nodiscard]] bool readMarkerLoaded() const; /** diff --git a/src/qml/Component/NeochatMaximizeComponent.qml b/src/qml/Component/NeochatMaximizeComponent.qml index fcec2c2b7..c923bed69 100644 --- a/src/qml/Component/NeochatMaximizeComponent.qml +++ b/src/qml/Component/NeochatMaximizeComponent.qml @@ -25,6 +25,8 @@ Components.AlbumMaximizeComponent { readonly property var currentTime: model.data(model.index(content.currentIndex, 0), MessageEventModel.TimeRole) + readonly property var currentDelegateType: model.data(model.index(content.currentIndex, 0), MessageEventModel.DelegateTypeRole) + readonly property string currentPlainText: model.data(model.index(content.currentIndex, 0), MessageEventModel.PlainText) readonly property var currentMimeType: model.data(model.index(content.currentIndex, 0), MessageEventModel.MimeTypeRole) @@ -84,28 +86,26 @@ Components.AlbumMaximizeComponent { } } } - onItemRightClicked: { - const contextMenu = fileDelegateContextMenu.createObject(parent, { - author: root.currentAuthor, - eventId: root.currentEventId, - file: parent, - mimeType: root.currentMimeType, - progressInfo: root.currentProgressInfo, - plainText: root.currentPlainText, - connection: root.currentRoom.connection - }); - contextMenu.closeFullscreen.connect(root.close) - contextMenu.open(); - } + onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, + root.currentAuthor, + root.currentDelegateType, + root.currentPlainText, + "", + "", + root.currentMimeType, + root.currentProgressInfo) + onSaveItem: { var dialog = saveAsDialog.createObject(QQC2.ApplicationWindow.overlay) dialog.open() dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(root.currentEventId) } - Component { - id: fileDelegateContextMenu - FileDelegateContextMenu {} + Connections { + target: RoomManager + function onCloseFullScreen() { + root.close() + } } Component { diff --git a/src/qml/Component/Timeline/AudioDelegate.qml b/src/qml/Component/Timeline/AudioDelegate.qml index c0549e3f0..44120bbe5 100644 --- a/src/qml/Component/Timeline/AudioDelegate.qml +++ b/src/qml/Component/Timeline/AudioDelegate.qml @@ -36,7 +36,7 @@ TimelineContainer { readonly property bool downloaded: root.progressInfo && root.progressInfo.completed onDownloadedChanged: audio.play() - onOpenContextMenu: openFileContext(root) + onOpenContextMenu: RoomManager.viewEventMenu(eventId, author, delegateType, plainText, "", "", mediaInfo.mimeType, progressInfo) innerObject: ColumnLayout { Layout.fillWidth: true diff --git a/src/qml/Component/Timeline/FileDelegate.qml b/src/qml/Component/Timeline/FileDelegate.qml index 55a7f81e1..73a24cebe 100644 --- a/src/qml/Component/Timeline/FileDelegate.qml +++ b/src/qml/Component/Timeline/FileDelegate.qml @@ -43,7 +43,7 @@ TimelineContainer { openSavedFile(); } - onOpenContextMenu: openFileContext(root) + onOpenContextMenu: RoomManager.viewEventMenu(eventId, author, delegateType, plainText, "", "", mediaInfo.mimeType, progressInfo) function saveFileAs() { const dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay) diff --git a/src/qml/Component/Timeline/ImageDelegate.qml b/src/qml/Component/Timeline/ImageDelegate.qml index d2f4ec187..80caf7f4e 100644 --- a/src/qml/Component/Timeline/ImageDelegate.qml +++ b/src/qml/Component/Timeline/ImageDelegate.qml @@ -53,7 +53,7 @@ TimelineContainer { */ readonly property var maxHeight: Kirigami.Units.gridUnit * 30 - onOpenContextMenu: openFileContext(root) + onOpenContextMenu: RoomManager.viewEventMenu(eventId, author, delegateType, plainText, "", "", mediaInfo.mimeType, progressInfo) innerObject: Item { id: imageContainer diff --git a/src/qml/Component/Timeline/MessageDelegate.qml b/src/qml/Component/Timeline/MessageDelegate.qml index 79c1d2b27..9191396d7 100644 --- a/src/qml/Component/Timeline/MessageDelegate.qml +++ b/src/qml/Component/Timeline/MessageDelegate.qml @@ -36,7 +36,7 @@ TimelineContainer { */ required property bool showLinkPreview - onOpenContextMenu: openMessageContext(label.selectedText) + onOpenContextMenu: RoomManager.viewEventMenu(eventId, author, delegateType, plainText, display, label.selectedText) innerObject: ColumnLayout { Layout.maximumWidth: root.contentMaxWidth diff --git a/src/qml/Component/Timeline/TimelineContainer.qml b/src/qml/Component/Timeline/TimelineContainer.qml index f01081240..faf37b460 100644 --- a/src/qml/Component/Timeline/TimelineContainer.qml +++ b/src/qml/Component/Timeline/TimelineContainer.qml @@ -589,44 +589,6 @@ ColumnLayout { return (yoff + height > 0 && yoff < ListView.view.height) } - Component { - id: messageDelegateContextMenu - MessageDelegateContextMenu {} - } - - Component { - id: fileDelegateContextMenu - FileDelegateContextMenu {} - } - - /// Open message context dialog for file and videos - function openFileContext(file) { - const contextMenu = fileDelegateContextMenu.createObject(root, { - author: root.author, - eventId: root.eventId, - file: file, - progressInfo: root.progressInfo, - plainText: root.plainText, - htmlText: root.display, - connection: root.connection, - }); - contextMenu.open(); - } - - /// Open context menu for normal message - function openMessageContext(selectedText) { - const contextMenu = messageDelegateContextMenu.createObject(root, { - selectedText: selectedText, - author: root.author, - eventId: root.eventId, - eventType: root.delegateType, - plainText: root.plainText, - htmlText: root.display, - connection: root.connection, - }); - contextMenu.open(); - } - function setHoverActionsToDelegate() { if (ListView.view.setHoverActionsToDelegate) { ListView.view.setHoverActionsToDelegate(root) diff --git a/src/qml/Component/Timeline/VideoDelegate.qml b/src/qml/Component/Timeline/VideoDelegate.qml index 9ee8dd20b..758db5d3d 100644 --- a/src/qml/Component/Timeline/VideoDelegate.qml +++ b/src/qml/Component/Timeline/VideoDelegate.qml @@ -54,7 +54,7 @@ TimelineContainer { */ readonly property var maxHeight: Kirigami.Units.gridUnit * 30 - onOpenContextMenu: openFileContext(vid) + onOpenContextMenu: RoomManager.viewEventMenu(eventId, author, delegateType, plainText, "", "", mediaInfo.mimeType, progressInfo) onDownloadedChanged: { if (downloaded) { diff --git a/src/qml/Menu/Timeline/FileDelegateContextMenu.qml b/src/qml/Menu/Timeline/FileDelegateContextMenu.qml index caa0331e8..8235f04ab 100644 --- a/src/qml/Menu/Timeline/FileDelegateContextMenu.qml +++ b/src/qml/Menu/Timeline/FileDelegateContextMenu.qml @@ -9,36 +9,40 @@ import org.kde.kirigami 2.15 as Kirigami import org.kde.neochat 1.0 +/** + * @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 MessageDelegateContextMenu + */ MessageDelegateContextMenu { id: root - signal closeFullscreen - /** * @brief The MIME type of the media. */ property string mimeType - required property var file + /** + * @brief Progress info when downloading files. + * + * @sa Quotient::FileTransferInfo + */ required property var progressInfo + /** + * @brief The main list of menu item actions. + * + * Each action will be instantiated as a single line in the menu. + */ property list actions: [ Kirigami.Action { text: i18n("Open Externally") icon.name: "document-open" onTriggered: { - if (file.downloaded) { - if (!UrlHelper.openUrl(progressInfo.localPath)) { - UrlHelper.openUrl(progressInfo.localDir); - } - } else { - file.onDownloadedChanged.connect(function() { - if (!UrlHelper.openUrl(progressInfo.localPath)) { - UrlHelper.openUrl(progressInfo.localDir); - } - }); - currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId)) - } + currentRoom.openEventMediaExternally(root.eventId) } }, Kirigami.Action { @@ -56,21 +60,14 @@ MessageDelegateContextMenu { onTriggered: { currentRoom.chatBoxReplyId = eventId currentRoom.chatBoxEditId = "" - root.closeFullscreen() + RoomManager.requestFullScreenClose() } }, Kirigami.Action { text: i18n("Copy") icon.name: "edit-copy" onTriggered: { - if(file.downloaded) { - Clipboard.setImage(progressInfo.localPath) - } else { - file.onDownloadedChanged.connect(function() { - Clipboard.setImage(progressInfo.localPath) - }); - currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId)) - } + currentRoom.copyEventMedia(root.eventId) } }, Kirigami.Action { @@ -99,12 +96,17 @@ MessageDelegateContextMenu { } ] + /** + * @brief The list of menu item actions that have sub-actions. + * + * Each action will be instantiated as a single line that opens a sub menu. + */ property list nestedActions: [ ShareAction { id: shareAction inputData: { 'urls': [], - 'mimeType': [root.mimeType ? root.mimeType : root.file.mediaINfo.mimeType] + 'mimeType': [root.mimeType] } property string filename: StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId); @@ -114,11 +116,12 @@ MessageDelegateContextMenu { Component.onCompleted: { shareAction.inputData = { urls: [filename], - mimeType: [root.mimeType ? root.mimeType : root.file.mediaINfo.mimeType] + mimeType: [root.mimeType] }; } } ] + Component { id: saveAsDialog FileDialog { diff --git a/src/qml/Menu/Timeline/MessageDelegateContextMenu.qml b/src/qml/Menu/Timeline/MessageDelegateContextMenu.qml index fa11fb141..0a1be1d93 100644 --- a/src/qml/Menu/Timeline/MessageDelegateContextMenu.qml +++ b/src/qml/Menu/Timeline/MessageDelegateContextMenu.qml @@ -10,20 +10,82 @@ import org.kde.kirigamiaddons.labs.components 1.0 as KirigamiComponents import org.kde.neochat 1.0 +/** + * @brief The base 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 + * with different visuals appropriate to the platform. + * + * 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. + */ Loader { id: root - required property var author - required property string eventId - property var eventType - property string selectedText: "" - required property string plainText - property string htmlText: undefined - + /** + * @brief The curent connection for the account accessing the event. + */ required property NeoChatConnection connection + /** + * @brief The matrix ID of the message event. + */ + required property string eventId + + /** + * @brief The message author. + * + * This should consist of the following: + * - id - The matrix ID of the author. + * - isLocalUser - Whether the author is the local user. + * - avatarSource - The mxc URL for the author's avatar in the current room. + * - avatarMediaId - The media ID of the author's avatar. + * - avatarUrl - The mxc URL for the author's avatar. + * - displayName - The display name of the author. + * - display - The name of the author. + * - color - The color for the author. + * - object - The Quotient::User object for the author. + * + * @sa Quotient::User + */ + required property var author + + /** + * @brief The delegate type of the message. + */ + required property int delegateType + + /** + * @brief The display text of the message as plain text. + */ + required property string plainText + + /** + * @brief The display text of the message as rich text. + */ + property string htmlText: "" + + /** + * @brief The text the user currently has selected. + */ + property string selectedText: "" + + /** + * @brief The list of menu item actions that have sub-actions. + * + * Each action will be instantiated as a single line that open a sub menu. + */ property list nestedActions + /** + * @brief The main list of menu item actions. + * + * Each action will be instantiated as a single line in the menu. + */ property list actions: [ Kirigami.Action { text: i18n("Edit") @@ -32,7 +94,7 @@ Loader { currentRoom.chatBoxEditId = eventId; currentRoom.chatBoxReplyId = ""; } - visible: author.id === root.connection.localUserId && (root.eventType === DelegateType.Emote || root.eventType === DelegateType.Message) + visible: author.isLocalUser && (root.delegateType === DelegateType.Emote || root.delegateType === DelegateType.Message) }, Kirigami.Action { text: i18n("Reply") @@ -53,13 +115,13 @@ Loader { width: Kirigami.Units.gridUnit * 25 }) page.chosen.connect(function(targetRoomId) { - root.connection.room(targetRoomId).postHtmlMessage(root.plainText, root.htmlText ? root.htmlText : root.plainText) + root.connection.room(targetRoomId).postHtmlMessage(root.plainText, root.htmlText.length > 0 ? root.htmlText : root.plainText) page.closeDialog() }) } }, Kirigami.Action { - visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact") + visible: author.isLocalUser || currentRoom.canSendState("redact") text: i18n("Remove") icon.name: "edit-delete-remove" icon.color: "red" @@ -71,12 +133,12 @@ Loader { Kirigami.Action { text: i18n("Copy") icon.name: "edit-copy" - onTriggered: Clipboard.saveText(root.selectedText === "" ? root.plainText : root.selectedText) + onTriggered: Clipboard.saveText(root.selectedText.length > 0 ? root.plainText : root.selectedText) }, Kirigami.Action { text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report") icon.name: "dialog-warning-symbolic" - visible: author.id !== currentRoom.localUser.id + visible: author.isLocalUser onTriggered: applicationWindow().pageStack.pushDialogLayer("qrc:/ReportSheet.qml", {room: currentRoom, eventId: eventId}, { title: i18nc("@title", "Report Message"), width: Kirigami.Units.gridUnit * 25 @@ -116,12 +178,10 @@ Loader { icon.name: modelData.icon.name onTriggered: modelData.trigger() } - onObjectAdded: { - menuItem.insertItem(0, object) - } + onObjectAdded: (index, object) => {menuItem.insertItem(0, object)} } } - onObjectAdded: { + onObjectAdded: (index, object) => { object.visible = false; menu.addMenu(object) } @@ -130,7 +190,6 @@ Loader { Repeater { model: root.actions QQC2.MenuItem { - id: menuItem visible: modelData.visible action: modelData onClicked: root.item.close(); @@ -147,7 +206,7 @@ Loader { Instantiator { model: WebShortcutModel { id: webshortcutmodel - selectedText: root.selectedText ? root.selectedText : root.plainText + selectedText: root.selectedText.length > 0 ? root.selectedText : root.plainText onOpenUrl: RoomManager.visitNonMatrix(url) } delegate: QQC2.MenuItem { diff --git a/src/qml/Page/RoomPage.qml b/src/qml/Page/RoomPage.qml index b7aad0107..7ca3997e8 100644 --- a/src/qml/Page/RoomPage.qml +++ b/src/qml/Page/RoomPage.qml @@ -208,6 +208,30 @@ Kirigami.Page { width: Kirigami.Units.gridUnit * 25 }); } + + function onShowMessageMenu(eventId, author, delegateType, plainText, htmlText, selectedText) { + const contextMenu = messageDelegateContextMenu.createObject(root, { + selectedText: selectedText, + author: author, + eventId: eventId, + delegateType: delegateType, + plainText: plainText, + htmlText: htmlText + }); + contextMenu.open(); + } + + function onShowFileMenu(eventId, author, delegateType, plainText, mimeType, progressInfo) { + const contextMenu = fileDelegateContextMenu.createObject(root, { + author: author, + eventId: eventId, + delegateType: delegateType, + plainText: plainText, + mimeType: mimeType, + progressInfo: progressInfo + }); + contextMenu.open(); + } } function showUserDetail(user) { @@ -221,4 +245,18 @@ Kirigami.Page { id: userDetailDialog UserDetailDialog {} } + + Component { + id: messageDelegateContextMenu + MessageDelegateContextMenu { + connection: root.connection + } + } + + Component { + id: fileDelegateContextMenu + FileDelegateContextMenu { + connection: root.connection + } + } } diff --git a/src/roommanager.cpp b/src/roommanager.cpp index cc6f05317..c07d876b3 100644 --- a/src/roommanager.cpp +++ b/src/roommanager.cpp @@ -5,6 +5,7 @@ #include "roommanager.h" #include "controller.h" +#include "enums/delegatetype.h" #include "models/messageeventmodel.h" #include "neochatconfig.h" #include "neochatroom.h" @@ -103,11 +104,34 @@ void RoomManager::maximizeMedia(int index) Q_EMIT showMaximizedMedia(index); } +void RoomManager::requestFullScreenClose() +{ + Q_EMIT closeFullScreen(); +} + void RoomManager::viewEventSource(const QString &eventId) { Q_EMIT showEventSource(eventId); } +void RoomManager::viewEventMenu(const QString &eventId, + const QVariantMap &author, + DelegateType::Type delegateType, + const QString &plainText, + const QString &htmlText, + const QString &selectedText, + const QString &mimeType, + const FileTransferInfo &progressInfo) +{ + if (delegateType == DelegateType::Image || delegateType == DelegateType::Video || delegateType == DelegateType::Audio + || delegateType == DelegateType::File) { + Q_EMIT showFileMenu(eventId, author, delegateType, plainText, mimeType, progressInfo); + return; + } + + Q_EMIT showMessageMenu(eventId, author, delegateType, plainText, htmlText, selectedText); +} + bool RoomManager::hasOpenRoom() const { return m_currentRoom != nullptr; diff --git a/src/roommanager.h b/src/roommanager.h index d13716546..44169fd22 100644 --- a/src/roommanager.h +++ b/src/roommanager.h @@ -6,9 +6,11 @@ #include #include #include +#include #include #include "chatdocumenthandler.h" +#include "enums/delegatetype.h" #include "models/mediamessagefiltermodel.h" #include "models/messageeventmodel.h" #include "models/messagefiltermodel.h" @@ -195,11 +197,28 @@ public: */ Q_INVOKABLE void maximizeMedia(int index); + /** + * @brief Request that any full screen overlay currently open closes. + */ + Q_INVOKABLE void requestFullScreenClose(); + /** * @brief Show the JSON source for the given event Matrix ID */ Q_INVOKABLE void viewEventSource(const QString &eventId); + /** + * @brief Show a conterxt menu for the given event. + */ + Q_INVOKABLE void viewEventMenu(const QString &eventId, + const QVariantMap &author, + DelegateType::Type delegateType, + const QString &plainText, + const QString &htmlText = {}, + const QString &selectedText = {}, + const QString &mimeType = {}, + const FileTransferInfo &progressInfo = {}); + /** * @brief Call this when the current used connection is dropped. */ @@ -266,11 +285,36 @@ Q_SIGNALS: */ void showMaximizedMedia(int index); + /** + * @brief Request that any full screen overlay closes. + */ + void closeFullScreen(); + /** * @brief Request the JSON source for the given event ID is shown. */ void showEventSource(const QString &eventId); + /** + * @brief Request to show a menu for the given event. + */ + void showMessageMenu(const QString &eventId, + const QVariantMap &author, + DelegateType::Type delegateType, + const QString &plainText, + const QString &htmlText, + const QString &selectedText); + + /** + * @brief Request to show a menu for the given media event. + */ + void showFileMenu(const QString &eventId, + const QVariantMap &author, + DelegateType::Type delegateType, + const QString &plainText, + const QString &mimeType, + const FileTransferInfo &progressInfo); + /** * @brief Show the direct chat confirmation dialog. *