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.
This commit is contained in:
Tobias Fella
2025-08-11 21:51:43 +02:00
parent 5c5dcd555b
commit a6b9759a31
7 changed files with 321 additions and 603 deletions

View File

@@ -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
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -2,9 +2,12 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// 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<Kirigami.Action> 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();
}
}*/
}

View File

@@ -1,140 +0,0 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// 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);
}
}
}

View File

@@ -1,143 +0,0 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// 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()
}
}