Refactor event context menu into its separate file
Have only one menu component attached to the EventList, instead of every delegate carrying its own.
This commit is contained in:
parent
b5b968db3a
commit
648c37f413
2
TODO.md
2
TODO.md
|
@ -1,5 +1,7 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
- improve debug
|
||||||
|
|
||||||
- handle invalid access token
|
- handle invalid access token
|
||||||
- If an account is gone from the user's config, discard UI state last page
|
- If an account is gone from the user's config, discard UI state last page
|
||||||
- filter > enter > room list is always scrolled to top
|
- filter > enter > room list is always scrolled to top
|
||||||
|
|
175
src/gui/Pages/Chat/Timeline/EventContextMenu.qml
Normal file
175
src/gui/Pages/Chat/Timeline/EventContextMenu.qml
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import Clipboard 0.1
|
||||||
|
import "../../.."
|
||||||
|
import "../../../Base"
|
||||||
|
import "../../../PythonBridge"
|
||||||
|
|
||||||
|
HMenu {
|
||||||
|
id: menu
|
||||||
|
|
||||||
|
property HListView eventList
|
||||||
|
property int eventIndex: 0
|
||||||
|
property Item eventDelegate: null // TODO: Qt 5.13: just use itemAtIndex()
|
||||||
|
property var hoveredMedia: [] // [Utils.Media.<Type>, url, title]
|
||||||
|
property string hoveredLink: ""
|
||||||
|
|
||||||
|
readonly property QtObject event: eventList.model.get(eventIndex)
|
||||||
|
|
||||||
|
readonly property bool isEncryptedMedia:
|
||||||
|
Object.keys(JSON.parse(event.media_crypt_dict)).length > 0
|
||||||
|
|
||||||
|
function spawn(eventIndex, eventDelegate, hoveredMedia=[], hoveredUrl="") {
|
||||||
|
menu.eventIndex = eventIndex
|
||||||
|
menu.eventDelegate = eventDelegate
|
||||||
|
menu.hoveredMedia = hoveredMedia
|
||||||
|
menu.hoveredLink = hoveredUrl
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
hoveredMedia = []
|
||||||
|
hoveredLink = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
icon.name: "toggle-select-message"
|
||||||
|
text: event.id in eventList.checked ? qsTr("Deselect") : qsTr("Select")
|
||||||
|
onTriggered: eventList.toggleCheck(eventIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
visible: eventList.selectedCount >= 2
|
||||||
|
icon.name: "deselect-all-messages"
|
||||||
|
text: qsTr("Deselect all")
|
||||||
|
onTriggered: eventList.checked = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
visible: eventIndex !== 0
|
||||||
|
icon.name: "select-until-here"
|
||||||
|
text: qsTr("Select until here")
|
||||||
|
onTriggered: eventList.checkFromLastToHere(eventIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
icon.name: "open-externally"
|
||||||
|
text: qsTr("Open externally")
|
||||||
|
visible: Boolean(event.media_url)
|
||||||
|
onTriggered: eventList.openMediaExternally(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
icon.name: "copy-local-path"
|
||||||
|
text: qsTr("Copy local path")
|
||||||
|
visible: Boolean(event.media_local_path)
|
||||||
|
onTriggered:
|
||||||
|
Clipboard.text =
|
||||||
|
event.media_local_path.replace(/^file:\/\//, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
id: copyMedia
|
||||||
|
icon.name: "copy-link"
|
||||||
|
text:
|
||||||
|
menu.hoveredMedia.length === 0 ||
|
||||||
|
menu.isEncryptedMedia ?
|
||||||
|
"" :
|
||||||
|
|
||||||
|
menu.hoveredMedia[0] === Utils.Media.File ?
|
||||||
|
qsTr("Copy file address") :
|
||||||
|
|
||||||
|
menu.hoveredMedia[0] === Utils.Media.Image ?
|
||||||
|
qsTr("Copy image address") :
|
||||||
|
|
||||||
|
menu.hoveredMedia[0] === Utils.Media.Video ?
|
||||||
|
qsTr("Copy video address") :
|
||||||
|
|
||||||
|
qsTr("Copy audio address")
|
||||||
|
|
||||||
|
visible: Boolean(text)
|
||||||
|
onTriggered: Clipboard.text = event.media_http_url // FIXME
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
icon.name: "copy-link"
|
||||||
|
text: qsTr("Copy link address")
|
||||||
|
visible: Boolean(menu.hoveredLink)
|
||||||
|
onTriggered: Clipboard.text = menu.hoveredLink
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
icon.name: "copy-text"
|
||||||
|
text:
|
||||||
|
eventList.selectedCount ? qsTr("Copy selection") :
|
||||||
|
menu.hoveredMedia.length > 0 ? qsTr("Copy filename") :
|
||||||
|
qsTr("Copy text")
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
if (! eventList.selectedCount){
|
||||||
|
Clipboard.text = JSON.parse(event.source).body
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eventList.copySelectedDelegates()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
icon.name: "reply-to"
|
||||||
|
text: qsTr("Reply")
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
chat.replyToEventId = event.id
|
||||||
|
chat.replyToUserId = event.sender_id
|
||||||
|
chat.replyToDisplayName = event.sender_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItemPopupSpawner {
|
||||||
|
readonly property var events:
|
||||||
|
eventList.selectedCount ?
|
||||||
|
eventList.redactableCheckedEvents :
|
||||||
|
|
||||||
|
eventList.canRedact(event) ?
|
||||||
|
[event] :
|
||||||
|
|
||||||
|
[]
|
||||||
|
|
||||||
|
icon.name: "remove-message"
|
||||||
|
text: qsTr("Remove")
|
||||||
|
enabled: properties.eventSenderAndIds.length
|
||||||
|
|
||||||
|
popup: "Popups/RedactPopup.qml"
|
||||||
|
properties: ({
|
||||||
|
preferUserId: chat.userId,
|
||||||
|
roomId: chat.roomId,
|
||||||
|
eventSenderAndIds: events.map(ev => [ev.sender_id, ev.id]),
|
||||||
|
|
||||||
|
onlyOwnMessageWarning:
|
||||||
|
! chat.roomInfo.can_redact_all &&
|
||||||
|
events.length < eventList.selectedCount
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItem {
|
||||||
|
icon.name: "debug"
|
||||||
|
text: qsTr("Debug this event")
|
||||||
|
onTriggered: mainUI.debugConsole.toggle(eventDelegate, "t.json()")
|
||||||
|
}
|
||||||
|
|
||||||
|
HMenuItemPopupSpawner {
|
||||||
|
icon.name: "clear-messages"
|
||||||
|
text: qsTr("Clear messages")
|
||||||
|
|
||||||
|
popup: "Popups/ClearMessagesPopup.qml"
|
||||||
|
properties: ({
|
||||||
|
userId: chat.userId,
|
||||||
|
roomId: chat.roomId,
|
||||||
|
preClearCallback: eventList.uncheckAll,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,9 +56,12 @@ HColumnLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
function openContextMenu() {
|
function openContextMenu() {
|
||||||
contextMenu.media = eventDelegate.hoveredMediaTypeUrl
|
eventList.contextMenu.spawn(
|
||||||
contextMenu.link = eventContent.hoveredLink
|
model.index,
|
||||||
contextMenu.popup()
|
eventDelegate,
|
||||||
|
hoveredMediaTypeUrl,
|
||||||
|
eventContent.hoveredLink,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleChecked() {
|
function toggleChecked() {
|
||||||
|
@ -129,159 +132,4 @@ HColumnLayout {
|
||||||
acceptedPointerTypes: PointerDevice.Finger | PointerDevice.Pen
|
acceptedPointerTypes: PointerDevice.Finger | PointerDevice.Pen
|
||||||
onLongPressed: openContextMenu()
|
onLongPressed: openContextMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
HMenu {
|
|
||||||
id: contextMenu
|
|
||||||
|
|
||||||
property var media: []
|
|
||||||
property string link: ""
|
|
||||||
|
|
||||||
readonly property bool isEncryptedMedia:
|
|
||||||
Object.keys(JSON.parse(model.media_crypt_dict)).length > 0
|
|
||||||
|
|
||||||
onClosed: {
|
|
||||||
media = []
|
|
||||||
link = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
icon.name: "toggle-select-message"
|
|
||||||
text: eventDelegate.checked ? qsTr("Deselect") : qsTr("Select")
|
|
||||||
onTriggered: eventDelegate.toggleChecked()
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
visible: eventList.selectedCount >= 2
|
|
||||||
icon.name: "deselect-all-messages"
|
|
||||||
text: qsTr("Deselect all")
|
|
||||||
onTriggered: eventList.checked = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
visible: model.index !== 0
|
|
||||||
icon.name: "select-until-here"
|
|
||||||
text: qsTr("Select until here")
|
|
||||||
onTriggered: eventList.checkFromLastToHere(model.index)
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
icon.name: "open-externally"
|
|
||||||
text: qsTr("Open externally")
|
|
||||||
visible: Boolean(model.media_url)
|
|
||||||
onTriggered: eventList.openMediaExternally(model)
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
icon.name: "copy-local-path"
|
|
||||||
text: qsTr("Copy local path")
|
|
||||||
visible: Boolean(model.media_local_path)
|
|
||||||
onTriggered:
|
|
||||||
Clipboard.text =
|
|
||||||
model.media_local_path.replace(/^file:\/\//, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
id: copyMedia
|
|
||||||
icon.name: "copy-link"
|
|
||||||
text:
|
|
||||||
contextMenu.media.length === 0 ||
|
|
||||||
contextMenu.isEncryptedMedia ?
|
|
||||||
"" :
|
|
||||||
|
|
||||||
contextMenu.media[0] === Utils.Media.File ?
|
|
||||||
qsTr("Copy file address") :
|
|
||||||
|
|
||||||
contextMenu.media[0] === Utils.Media.Image ?
|
|
||||||
qsTr("Copy image address") :
|
|
||||||
|
|
||||||
contextMenu.media[0] === Utils.Media.Video ?
|
|
||||||
qsTr("Copy video address") :
|
|
||||||
|
|
||||||
qsTr("Copy audio address")
|
|
||||||
|
|
||||||
visible: Boolean(text)
|
|
||||||
onTriggered: Clipboard.text = model.media_http_url
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
icon.name: "copy-link"
|
|
||||||
text: qsTr("Copy link address")
|
|
||||||
visible: Boolean(contextMenu.link)
|
|
||||||
onTriggered: Clipboard.text = contextMenu.link
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
icon.name: "copy-text"
|
|
||||||
text:
|
|
||||||
eventList.selectedCount ? qsTr("Copy selection") :
|
|
||||||
contextMenu.media.length > 0 ? qsTr("Copy filename") :
|
|
||||||
qsTr("Copy text")
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
if (! eventList.selectedCount){
|
|
||||||
Clipboard.text = JSON.parse(model.source).body
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
eventList.copySelectedDelegates()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
icon.name: "reply-to"
|
|
||||||
text: qsTr("Reply")
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
chat.replyToEventId = model.id
|
|
||||||
chat.replyToUserId = model.sender_id
|
|
||||||
chat.replyToDisplayName = model.sender_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItemPopupSpawner {
|
|
||||||
readonly property var events: {
|
|
||||||
eventList.selectedCount ?
|
|
||||||
eventList.redactableCheckedEvents :
|
|
||||||
|
|
||||||
eventList.canRedact(currentModel) ?
|
|
||||||
[model] :
|
|
||||||
|
|
||||||
[]
|
|
||||||
}
|
|
||||||
|
|
||||||
icon.name: "remove-message"
|
|
||||||
text: qsTr("Remove")
|
|
||||||
enabled: properties.eventSenderAndIds.length
|
|
||||||
|
|
||||||
popup: "Popups/RedactPopup.qml"
|
|
||||||
properties: ({
|
|
||||||
preferUserId: chat.userId,
|
|
||||||
roomId: chat.roomId,
|
|
||||||
eventSenderAndIds: events.map(ev => [ev.sender_id, ev.id]),
|
|
||||||
|
|
||||||
onlyOwnMessageWarning:
|
|
||||||
! chat.roomInfo.can_redact_all &&
|
|
||||||
events.length < eventList.selectedCount
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItem {
|
|
||||||
icon.name: "debug"
|
|
||||||
text: qsTr("Debug this event")
|
|
||||||
onTriggered:
|
|
||||||
mainUI.debugConsole.toggle(eventContent, "t.parent.json()")
|
|
||||||
}
|
|
||||||
|
|
||||||
HMenuItemPopupSpawner {
|
|
||||||
icon.name: "clear-messages"
|
|
||||||
text: qsTr("Clear messages")
|
|
||||||
|
|
||||||
popup: "Popups/ClearMessagesPopup.qml"
|
|
||||||
properties: ({
|
|
||||||
userId: chat.userId,
|
|
||||||
roomId: chat.roomId,
|
|
||||||
preClearCallback: eventList.uncheckAll,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import "../../../PythonBridge"
|
||||||
import "../../../ShortcutBundles"
|
import "../../../ShortcutBundles"
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
property alias eventList: eventList
|
readonly property alias eventList: eventList
|
||||||
|
readonly property alias contextMenu: contextMenu
|
||||||
|
|
||||||
|
|
||||||
color: theme.chat.eventList.background
|
color: theme.chat.eventList.background
|
||||||
|
@ -241,6 +242,8 @@ Rectangle {
|
||||||
readonly property var redactableCheckedEvents:
|
readonly property var redactableCheckedEvents:
|
||||||
getSortedChecked().filter(ev => eventList.canRedact(ev))
|
getSortedChecked().filter(ev => eventList.canRedact(ev))
|
||||||
|
|
||||||
|
readonly property alias contextMenu: contextMenu
|
||||||
|
|
||||||
function focusCenterMessage() {
|
function focusCenterMessage() {
|
||||||
const previous = highlightRangeMode
|
const previous = highlightRangeMode
|
||||||
highlightRangeMode = HListView.NoHighlightRange
|
highlightRangeMode = HListView.NoHighlightRange
|
||||||
|
@ -487,6 +490,7 @@ Rectangle {
|
||||||
eventList.getLocalOrDownloadMedia(event, Qt.openUrlExternally)
|
eventList.getLocalOrDownloadMedia(event, Qt.openUrlExternally)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: ! window.anyPopup
|
enabled: ! window.anyPopup
|
||||||
clip: true
|
clip: true
|
||||||
|
@ -563,6 +567,11 @@ Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EventContextMenu {
|
||||||
|
id: contextMenu
|
||||||
|
eventList: eventList
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user