Start implementing new message selection system
This commit is contained in:
parent
710dba09ec
commit
3852357614
6
TODO.md
6
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
3
src/icons/thin/toggle-select-message.svg
Normal file
3
src/icons/thin/toggle-select-message.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg clip-rule="evenodd" fill-rule="evenodd" height="24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m24 6.278-11.16 12.722-6.84-6 1.319-1.49 5.341 4.686 9.865-11.196zm-22.681 5.232 6.835 6.01-1.314 1.48-6.84-6zm9.278.218 5.921-6.728 1.482 1.285-5.921 6.756z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 285 B |
3
src/icons/thin/unselect-all-messages.svg
Normal file
3
src/icons/thin/unselect-all-messages.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m11 24v-2h-4v2zm8-22h3v3h2v-5h-5zm-19 15h2v-4h-2zm0-6h2v-4h-2zm2-6v-3h3v-2h-5v5zm22 2h-2v4h2zm0 6h-2v4h2zm-2 6v3h-3v2h5v-5zm-17 3h-3v-3h-2v5h5zm12 2v-2h-4v2zm-6-22v-2h-4v2zm6 0v-2h-4v2zm0 11h-10v-2h10z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 309 B |
Loading…
Reference in New Issue
Block a user