From 385235761419c37b20de8f5037a6a5346e2cd37e Mon Sep 17 00:00:00 2001 From: miruka Date: Wed, 25 Mar 2020 23:06:51 -0400 Subject: [PATCH] Start implementing new message selection system --- TODO.md | 6 +++ src/gui/Base/HImage.qml | 3 ++ src/gui/Base/HListView.qml | 25 ++++++++++ src/gui/Base/HSelectableLabel.qml | 6 ++- src/gui/Base/HTile.qml | 2 + src/gui/Pages/Chat/Timeline/EventContent.qml | 14 +++++- src/gui/Pages/Chat/Timeline/EventDelegate.qml | 46 +++++++++++++++++-- src/gui/Pages/Chat/Timeline/EventFile.qml | 10 +++- src/gui/Pages/Chat/Timeline/EventImage.qml | 16 ++++++- src/gui/Pages/Chat/Timeline/EventList.qml | 9 +++- src/icons/thin/toggle-select-message.svg | 3 ++ src/icons/thin/unselect-all-messages.svg | 3 ++ 12 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 src/icons/thin/toggle-select-message.svg create mode 100644 src/icons/thin/unselect-all-messages.svg diff --git a/TODO.md b/TODO.md index bf402a2b..640ca9c1 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,11 @@ # TODO +- precise text selection for one message +- long-press-drag to select multiple messages on touch +- drag to select on non-touch +- shift+click to select everything in between + +- remove radius on invite/left banner - side pane back/forward buttons hard to use on touch - is it still slow on sway with wayland-egl? - room pane drag-scroll a tiny bit activates the delegates diff --git a/src/gui/Base/HImage.qml b/src/gui/Base/HImage.qml index aac80469..6895d4bf 100644 --- a/src/gui/Base/HImage.qml +++ b/src/gui/Base/HImage.qml @@ -21,6 +21,7 @@ Image { property bool animate: true property bool animated: utils.urlExtension(image.source).toLowerCase() === "gif" + property bool enabledAnimatedPausing: true property alias radius: roundMask.radius property alias showProgressBar: progressBarLoader.active @@ -74,7 +75,9 @@ Image { property bool userPaused: ! window.settings.media.autoPlayGIF TapHandler { + enabled: image.enabledAnimatedPausing onTapped: parent.userPaused = ! parent.userPaused + gesturePolicy: TapHandler.ReleaseWithinBounds } HIcon { diff --git a/src/gui/Base/HListView.qml b/src/gui/Base/HListView.qml index 0f0475d7..a8699156 100644 --- a/src/gui/Base/HListView.qml +++ b/src/gui/Base/HListView.qml @@ -68,6 +68,31 @@ ListView { property bool allowDragging: true property alias cursorShape: mouseArea.cursorShape property int currentItemHeight: currentItem ? currentItem.height : 0 + property var checkedDelegates: ({}) + property int selectedCount: Object.keys(checkedDelegates).length + + + function delegatesChecked(...indices) { + for (const i of indices) { + const model = listView.model.get(i) + checkedDelegates[model.id] = model + } + checkedDelegatesChanged() + } + + function delegatesUnchecked(...indices) { + for (const i of indices) { + const model = listView.model.get(i) + delete checkedDelegates[model.id] + } + checkedDelegatesChanged() + } + + function getSortedCheckedDelegates() { + return Object.values(checkedDelegates).sort( + (a, b) => a.date > b.date ? 1 : -1 + ) + } Connections { diff --git a/src/gui/Base/HSelectableLabel.qml b/src/gui/Base/HSelectableLabel.qml index 5df02ac6..f3aeb110 100644 --- a/src/gui/Base/HSelectableLabel.qml +++ b/src/gui/Base/HSelectableLabel.qml @@ -13,11 +13,13 @@ TextEdit { tabStopDistance: 4 * 4 // 4 spaces readOnly: true - persistentSelection: true activeFocusOnPress: false focus: false - onLinkActivated: Qt.openUrlExternally(link) + onLinkActivated: if (enableLinkActivation) Qt.openUrlExternally(link) + + + property bool enableLinkActivation: true function selectWordAt(position) { diff --git a/src/gui/Base/HTile.qml b/src/gui/Base/HTile.qml index 677498f6..2efaf9b9 100644 --- a/src/gui/Base/HTile.qml +++ b/src/gui/Base/HTile.qml @@ -9,6 +9,7 @@ HButton { signal leftClicked() signal rightClicked() + signal longPressed() default property alias additionalData: contentItem.data @@ -106,6 +107,7 @@ HButton { TapHandler { acceptedButtons: Qt.LeftButton onTapped: leftClicked() + onLongPressed: tile.longPressed() } TapHandler { diff --git a/src/gui/Pages/Chat/Timeline/EventContent.qml b/src/gui/Pages/Chat/Timeline/EventContent.qml index 9060b3b7..ee892c71 100644 --- a/src/gui/Pages/Chat/Timeline/EventContent.qml +++ b/src/gui/Pages/Chat/Timeline/EventContent.qml @@ -110,6 +110,9 @@ HRowLayout { HSelectableLabel { id: contentLabel visible: ! pureMedia + enableLinkActivation: ! eventList.selectedCount + + // selectByMouse: eventDelegate.checked XXX topPadding: theme.chat.message.verticalSpacing bottomPadding: topPadding @@ -165,6 +168,13 @@ HRowLayout { HoverHandler { id: contentHover } + TapHandler { + acceptedButtons: Qt.LeftButton + onTapped: + if (! parent.hoveredLink || ! parent.enableLinkActivation) + eventDelegate.toggleChecked() + } + Rectangle { width: Math.max( parent.paintedWidth + @@ -176,7 +186,9 @@ HRowLayout { height: contentColumn.height radius: theme.chat.message.radius z: -100 - color: isOwn? + color: eventDelegate.checked ? + "blue" : // XXX + isOwn? theme.chat.message.ownBackground : theme.chat.message.background diff --git a/src/gui/Pages/Chat/Timeline/EventDelegate.qml b/src/gui/Pages/Chat/Timeline/EventDelegate.qml index 7def3fdd..4cc57781 100644 --- a/src/gui/Pages/Chat/Timeline/EventDelegate.qml +++ b/src/gui/Pages/Chat/Timeline/EventDelegate.qml @@ -10,6 +10,8 @@ HColumnLayout { id: eventDelegate width: eventList.width + ListView.onRemove: eventList.delegatesUnchecked(model.id) + enum Media { Page, File, Image, Video, Audio } @@ -20,6 +22,7 @@ HColumnLayout { readonly property var nextModel: eventList.model.get(model.index - 1) readonly property QtObject currentModel: model + property bool checked: model.id in eventList.checkedDelegates property bool compact: window.settings.compactMode property bool isOwn: chat.userId === model.sender_id property bool onRight: eventList.ownEventsOnRight && isOwn @@ -73,9 +76,14 @@ HColumnLayout { contextMenu.popup() } + function toggleChecked() { + eventDelegate.checked ? + eventList.delegatesUnchecked(model.index) : + eventList.delegatesChecked(model.index) + } + Item { - Layout.fillWidth: true Layout.preferredHeight: model.event_type === "RoomCreateEvent" ? 0 : separationSpacing @@ -103,6 +111,10 @@ HColumnLayout { Behavior on x { HNumberAnimation {} } } + TapHandler { + acceptedButtons: Qt.LeftButton + onTapped: toggleChecked() + } TapHandler { acceptedButtons: Qt.RightButton @@ -123,6 +135,19 @@ HColumnLayout { onClosed: { media = []; link = "" } + HMenuItem { + icon.name: "toggle-select-message" + text: eventDelegate.checked ? qsTr("Unselect") : qsTr("Select") + onTriggered: eventDelegate.toggleChecked() + } + + HMenuItem { + visible: eventList.selectedCount >= 2 + icon.name: "unselect-all-messages" + text: qsTr("Unselect all") + onTriggered: eventList.checkedDelegates = {} + } + HMenuItem { id: copyMedia icon.name: "copy-link" @@ -161,10 +186,21 @@ HColumnLayout { HMenuItem { icon.name: "copy-text" text: qsTr("Copy text") - visible: enabled || (! copyLink.visible && ! copyMedia.visible) - enabled: Boolean(selectableLabelContainer.joinedSelection) - onTriggered: - Clipboard.text = selectableLabelContainer.joinedSelection + visible: ! copyLink.visible && ! copyMedia.visible + onTriggered: { + if (! eventList.selectedCount) { + Clipboard.text = JSON.parse(model.source).body + return + } + + const contents = [] + + for (const model of eventList.getSortedCheckedDelegates()) { + contents.push(JSON.parse(model.source).body) + } + + Clipboard.text = contents.join("\n\n") + } } HMenuItem { diff --git a/src/gui/Pages/Chat/Timeline/EventFile.qml b/src/gui/Pages/Chat/Timeline/EventFile.qml index b45eeffe..cda0f42b 100644 --- a/src/gui/Pages/Chat/Timeline/EventFile.qml +++ b/src/gui/Pages/Chat/Timeline/EventFile.qml @@ -22,8 +22,10 @@ HTile { svgName: "download" } - onLeftClicked: download(Qt.openUrlExternally) onRightClicked: eventDelegate.openContextMenu() + onLeftClicked: + eventList.selectedCount ? + eventDelegate.toggleChecked() : download(Qt.openUrlExternally) onHoveredChanged: { if (! hovered) { @@ -44,4 +46,10 @@ HTile { JSON.parse(loader.singleMediaInfo.media_crypt_dict) readonly property bool isEncrypted: ! utils.isEmptyObject(cryptDict) + + + Binding on backgroundColor { + value: "blue" // XXX + when: eventDelegate.checked + } } diff --git a/src/gui/Pages/Chat/Timeline/EventImage.qml b/src/gui/Pages/Chat/Timeline/EventImage.qml index b220d49a..2c13119b 100644 --- a/src/gui/Pages/Chat/Timeline/EventImage.qml +++ b/src/gui/Pages/Chat/Timeline/EventImage.qml @@ -8,6 +8,7 @@ HMxcImage { width: fitSize.width height: fitSize.height horizontalAlignment: Image.AlignLeft + enabledAnimatedPausing: ! eventList.selectedCount title: thumbnail ? loader.thumbnailTitle : loader.title animated: loader.singleMediaInfo.media_mime === "image/gif" || @@ -82,8 +83,11 @@ HMxcImage { TapHandler { - onTapped: if (! image.animated) getOpenUrl(Qt.openUrlExternally) - onDoubleTapped: getOpenUrl(Qt.openUrlExternally) + onTapped: + eventList.selectedCount ? + eventDelegate.toggleChecked() : getOpenUrl(Qt.openUrlExternally) + + gesturePolicy: TapHandler.ReleaseWithinBounds } HoverHandler { @@ -123,4 +127,12 @@ HMxcImage { Behavior on opacity { HNumberAnimation {} } } + + Rectangle { + anchors.fill: parent + visible: eventDelegate.checked + // XXX + color: "blue" + opacity: 0.2 + } } diff --git a/src/gui/Pages/Chat/Timeline/EventList.qml b/src/gui/Pages/Chat/Timeline/EventList.qml index 7d69dd20..56b5d4a0 100644 --- a/src/gui/Pages/Chat/Timeline/EventList.qml +++ b/src/gui/Pages/Chat/Timeline/EventList.qml @@ -10,10 +10,14 @@ Rectangle { color: theme.chat.eventList.background - property Item selectableLabelContainer: Item {} property alias eventList: eventList + HShortcut { + sequence: "Escape" + onActivated: eventList.checkedDelegates = {} + } + HListView { id: eventList clip: true @@ -136,6 +140,7 @@ Rectangle { } } + HNoticePage { text: qsTr("No messages to show yet") @@ -150,7 +155,7 @@ Rectangle { const left = centroid.pressedButtons & Qt.LeftButton const vel = centroid.velocity.y const pos = centroid.position.y - const dist = Math.min(selectableLabelContainer.height / 4, 50) + const dist = Math.min(eventList.height / 4, 50) const boost = 20 * (pos < dist ? -pos : -(height - pos)) dragFlicker.speed = diff --git a/src/icons/thin/toggle-select-message.svg b/src/icons/thin/toggle-select-message.svg new file mode 100644 index 00000000..0ec9bc77 --- /dev/null +++ b/src/icons/thin/toggle-select-message.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/unselect-all-messages.svg b/src/icons/thin/unselect-all-messages.svg new file mode 100644 index 00000000..5e6a322f --- /dev/null +++ b/src/icons/thin/unselect-all-messages.svg @@ -0,0 +1,3 @@ + + +