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) { function onShowDelegateMenu(eventId: string, author, messageComponentType, plainText: string, richText: string, mimeType: string, progressInfo, isThread: bool, selectedText: string, hoveredLink: string) {
const contextMenu = messageDelegateContextMenu.createObject(root, { const contextMenu = delegateContextMenu.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, {
author: author, author: author,
eventId: eventId, eventId: eventId,
plainText: plainText, plainText: plainText,
@@ -262,16 +249,8 @@ Kirigami.Page {
} }
Component { Component {
id: messageDelegateContextMenu id: delegateContextMenu
MessageDelegateContextMenu { DelegateContextMenu {
room: root.currentRoom
connection: root.currentRoom.connection
}
}
Component {
id: fileDelegateContextMenu
FileDelegateContextMenu {
room: root.currentRoom room: root.currentRoom
connection: root.currentRoom.connection connection: root.currentRoom.connection
} }

View File

@@ -279,23 +279,15 @@ void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, Neoch
return; return;
} }
const auto &event = **it; const auto &event = **it;
if (EventHandler::mediaInfo(room, &event).contains("mimeType"_L1)) { Q_EMIT showDelegateMenu(eventId,
Q_EMIT showFileMenu(eventId,
sender, sender,
MessageComponentType::typeForEvent(event), MessageComponentType::typeForEvent(event),
EventHandler::plainBody(room, &event), EventHandler::plainBody(room, &event),
EventHandler::richBody(room, &event),
EventHandler::mediaInfo(room, &event)["mimeType"_L1].toString(), EventHandler::mediaInfo(room, &event)["mimeType"_L1].toString(),
room->fileTransferInfo(eventId)); room->fileTransferInfo(eventId),
return; selectedText,
} hoveredLink);
Q_EMIT showMessageMenu(eventId,
sender,
MessageComponentType::typeForEvent(event),
EventHandler::plainBody(room, &event),
EventHandler::richBody(room, &event),
selectedText,
hoveredLink);
} }
bool RoomManager::hasOpenRoom() const bool RoomManager::hasOpenRoom() const

View File

@@ -296,23 +296,15 @@ Q_SIGNALS:
/** /**
* @brief Request to show a menu for the given event. * @brief Request to show a menu for the given event.
*/ */
void showMessageMenu(const QString &eventId, void showDelegateMenu(const QString &eventId,
const NeochatRoomMember *author, const NeochatRoomMember *author,
MessageComponentType::Type messageComponentType, MessageComponentType::Type messageComponentType,
const QString &plainText, const QString &plainText,
const QString &htmlText, const QString &richtText,
const QString &selectedText, const QString &mimeType,
const QString &hoveredLink); const FileTransferInfo &progressInfo,
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);
/** /**
* @brief Show the direct chat confirmation dialog. * @brief Show the direct chat confirmation dialog.

View File

@@ -22,8 +22,6 @@ ecm_add_qml_module(Timeline GENERATE_PLUGIN_SOURCE
QuickActions.qml QuickActions.qml
TypingPane.qml TypingPane.qml
DelegateContextMenu.qml DelegateContextMenu.qml
FileDelegateContextMenu.qml
MessageDelegateContextMenu.qml
SOURCES SOURCES
messageattached.cpp messageattached.cpp
messagedelegate.cpp messagedelegate.cpp

View File

@@ -2,9 +2,12 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu> // SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
import QtCore as Core
import QtQuick import QtQuick
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Dialogs as Dialogs
import Qt.labs.qmlmodels import Qt.labs.qmlmodels
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents import org.kde.kirigamiaddons.components as KirigamiComponents
@@ -14,7 +17,7 @@ import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat 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 * 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 * 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 * The menu supports both a list of main actions and the ability to define sub menus
* using the nested action parameter. * 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 { KirigamiComponents.ConvergentContextMenu {
id: root id: root
@@ -74,122 +74,26 @@ KirigamiComponents.ConvergentContextMenu {
property string hoveredLink: "" 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 { property string htmlText: ""
visible: NeoChatConfig.developerTools
text: i18n("View Source")
icon.name: "code-context"
onTriggered: RoomManager.viewEventSource(root.eventId)
}
component RemoveMessageAction: Kirigami.Action { /**
visible: (author.isLocalMember || currentRoom.canSendState("redact")) && root.messageComponentType !== MessageComponentType.Other * @brief Progress info when downloading files.
text: i18nc("@action:button", "Remove…") *
icon.name: "edit-delete-remove" * @sa Quotient::FileTransferInfo
icon.color: "red" */
onTriggered: { required property var progressInfo
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);
});
}
}
component ReplyMessageAction: Kirigami.Action { /**
visible: root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent * @brief The MIME type of the media, or an empty string if the event does not have file content
text: i18n("Reply") */
icon.name: "mail-replied-symbolic" property string mimeType
onTriggered: {
currentRoom.mainCache.replyId = eventId;
currentRoom.editCache.editId = "";
RoomManager.requestFullScreenClose();
}
}
component ReplyThreadMessageAction: Kirigami.Action { /**
visible: root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent * @brief Whether the event has file-based content. This includes images, videos, and other files
text: i18nc("@action:button", "Reply in Thread") */
icon.name: "dialog-messages" readonly property bool hasFileContent: mimeType.length > 0
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
}
}
}
Kirigami.Action { Kirigami.Action {
id: emojiAction 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 { Kirigami.Action {
id: drawer visible: root.messageComponentType !== MessageComponentType.Other || NeoChatConfig.relateAnyEvent
height: stackView.implicitHeight text: i18nc("@action:inmenu", "Reply in Thread")
edge: Qt.BottomEdge icon.name: "dialog-messages"
padding: 0 onTriggered: {
leftPadding: 0 currentRoom.threadCache.replyId = "";
rightPadding: 0 currentRoom.threadCache.threadId = currentRoom.eventIsThreaded(root.eventId) ? currentRoom.rootIdForThread(root.eventId) : root.eventId;
bottomPadding: 0 currentRoom.mainCache.clearRelations();
topPadding: 0 currentRoom.editCache.clearRelations();
RoomManager.requestFullScreenClose();
}
}
QQC2.StackView { Kirigami.Action {
id: stackView visible: !root.hasFileContent && root.author.isLocalMember && root.messageComponentType === MessageComponentType.Text
width: parent.width text: i18n("Edit")
implicitHeight: currentItem.implicitHeight icon.name: "document-edit"
onTriggered: {
currentRoom.editCache.editId = eventId;
currentRoom.mainCache.replyId = "";
currentRoom.mainCache.threadId = "";
}
}
Component { Kirigami.Action {
id: nestedActionsComponent visible: (author.isLocalMember || currentRoom.canSendState("redact")) && root.messageComponentType !== MessageComponentType.Other
ColumnLayout { text: i18nc("@action:button", "Remove…")
id: actionLayout icon.name: "edit-delete-remove"
property string title: "" icon.color: "red"
property list<Kirigami.Action> actions onTriggered: {
width: parent.width let dialog = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
spacing: 0 title: i18nc("@title:dialog", "Remove Message"),
RowLayout { placeholder: i18nc("@info:placeholder", "Reason for removing this message"),
QQC2.ToolButton { actionText: i18nc("@action:button 'Remove' as in 'Remove this message'", "Remove"),
icon.name: 'draw-arrow-back' icon: "delete"
onClicked: stackView.pop() }, {
} title: i18nc("@title:dialog", "Remove Message"),
Kirigami.Heading { width: Kirigami.Units.gridUnit * 25
level: 3 });
Layout.fillWidth: true dialog.accepted.connect(reason => {
text: actionLayout.title currentRoom.redactEvent(root.eventId, reason);
wrapMode: Text.WordWrap });
} }
} }
Repeater {
id: listViewAction
model: actionLayout.actions
FormCard.FormButtonDelegate { Kirigami.Action {
icon.name: modelData.icon.name separator: true
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
onLinkActivated: RoomManager.resolveResource(link, "join") Kirigami.Action {
} visible: root.messageComponentType !== MessageComponentType.Other
} text: i18nc("@action:inmenu As in 'Forward this message'", "Forward…")
} icon.name: "mail-forward-symbolic"
Kirigami.Separator { onTriggered: {
Layout.fillWidth: true let page = (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
} connection: root.connection
Kirigami.Separator { }, {
Layout.fillWidth: true title: i18nc("@title", "Forward Message"),
} width: Kirigami.Units.gridUnit * 25
Repeater { });
id: listViewAction page.chosen.connect(function (targetRoomId) {
model: root.actions root.connection.room(targetRoomId).postHtmlMessage(root.plainText, root.htmlText.length > 0 ? root.htmlText : root.plainText);
page.closeDialog();
});
}
}
DelegateChooser { Kirigami.Action {
role: "separator" visible: root.hasFileContent
DelegateChoice { text: {
roleValue: true 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 { Kirigami.Action {
visible: modelData.visible 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 { Kirigami.Action {
roleValue: false 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 { Kirigami.Action {
icon.name: modelData.icon.name text: i18nc("@action:inmenu", "Copy Link Address")
icon.color: modelData.icon.color ?? undefined icon.name: "edit-copy"
enabled: modelData.enabled visible: root.hoveredLink.length > 0
visible: modelData.visible onTriggered: Clipboard.saveText(root.hoveredLink)
text: modelData.text }
onClicked: {
modelData.triggered();
root.item.close();
}
}
}
}
}
Repeater { Kirigami.Action {
model: root.nestedActions 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 { Kirigami.Action {
action: modelData text: i18nc("@action:inmenu", "Copy Message Link")
visible: modelData.visible icon.name: "link-symbolic"
onClicked: { onTriggered: {
stackView.push(nestedActionsComponent, { Clipboard.saveText("https://matrix.to/#/" + currentRoom.id + "/" + root.eventId);
title: modelData.text, }
actions: modelData.children }
});
} 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()
}
}