Remove message text selection hack
This commit is contained in:
		
							
								
								
									
										6
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								TODO.md
									
									
									
									
									
								
							@@ -1,5 +1,9 @@
 | 
			
		||||
# TODO
 | 
			
		||||
 | 
			
		||||
- 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
 | 
			
		||||
 | 
			
		||||
## Goals before 0.5.0
 | 
			
		||||
 | 
			
		||||
- Redacting messages
 | 
			
		||||
@@ -13,8 +17,6 @@
 | 
			
		||||
 | 
			
		||||
## Refactoring
 | 
			
		||||
 | 
			
		||||
- Rewrite the message text selection buggy mess 
 | 
			
		||||
 | 
			
		||||
- Put keybindings in the components they belong to instead of shoving them
 | 
			
		||||
  all in one central file
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,120 +19,26 @@ TextEdit {
 | 
			
		||||
 | 
			
		||||
    onLinkActivated: Qt.openUrlExternally(link)
 | 
			
		||||
 | 
			
		||||
    Component.onCompleted: updateSelection()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // If index is a whole number, the label will get two \n before itself
 | 
			
		||||
    // in container.joinedSelection. If it's a decimal number, if gets one \n.
 | 
			
		||||
    property real index
 | 
			
		||||
    property HSelectableLabelContainer container
 | 
			
		||||
    property bool selectable: true
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function updateSelection() {
 | 
			
		||||
        if (! selectable && label.selectedText) {
 | 
			
		||||
            label.deselect()
 | 
			
		||||
            updateContainerSelectedTexts()
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (! selectable) return
 | 
			
		||||
 | 
			
		||||
        if (! container.reversed &&
 | 
			
		||||
            container.selectionStart <= container.selectionEnd ||
 | 
			
		||||
 | 
			
		||||
            container.reversed &&
 | 
			
		||||
            container.selectionStart > container.selectionEnd)
 | 
			
		||||
        {
 | 
			
		||||
            var first    = container.selectionStart
 | 
			
		||||
            var firstPos = container.selectionStartPosition
 | 
			
		||||
            var last     = container.selectionEnd
 | 
			
		||||
            var lastPos  = container.selectionEndPosition
 | 
			
		||||
        } else {
 | 
			
		||||
            var first    = container.selectionEnd
 | 
			
		||||
            var firstPos = container.selectionEndPosition
 | 
			
		||||
            var last     = container.selectionStart
 | 
			
		||||
            var lastPos  = container.selectionStartPosition
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (first === index && last === index) {
 | 
			
		||||
            select(
 | 
			
		||||
                label.positionAt(firstPos.x, firstPos.y),
 | 
			
		||||
                label.positionAt(lastPos.x, lastPos.y),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        } else if ((! container.reversed && first < index && index < last) ||
 | 
			
		||||
                   (container.reversed && first > index && index > last))
 | 
			
		||||
        {
 | 
			
		||||
            label.selectAll()
 | 
			
		||||
 | 
			
		||||
        } else if (first === index) {
 | 
			
		||||
            label.select(positionAt(firstPos.x, firstPos.y), length)
 | 
			
		||||
 | 
			
		||||
        } else if (last === index) {
 | 
			
		||||
            label.select(0, positionAt(lastPos.x, lastPos.y))
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
            label.deselect()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updateContainerSelectedTexts()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function updateContainerSelectedTexts() {
 | 
			
		||||
        container.selectedTexts[index] = selectedText
 | 
			
		||||
        container.selectedTextsChanged()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function selectWordAt(position) {
 | 
			
		||||
        container.clearSelection()
 | 
			
		||||
        label.cursorPosition = positionAt(position.x, position.y)
 | 
			
		||||
        label.selectWord()
 | 
			
		||||
        updateContainerSelectedTexts()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function selectAllText() {
 | 
			
		||||
        container.clearSelection()
 | 
			
		||||
        label.selectAll()
 | 
			
		||||
        updateContainerSelectedTexts()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    Connections {
 | 
			
		||||
        target: container
 | 
			
		||||
        onSelectionInfoChanged: updateSelection()
 | 
			
		||||
        onDeselectAll: deselect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DropArea {
 | 
			
		||||
        anchors.fill: parent
 | 
			
		||||
        onPositionChanged: {
 | 
			
		||||
            if (! container.selecting) {
 | 
			
		||||
                container.clearSelection()
 | 
			
		||||
                container.selectionStart         = index
 | 
			
		||||
                container.selectionStartPosition = Qt.point(drag.x, drag.y)
 | 
			
		||||
                container.selecting              = true
 | 
			
		||||
            } else {
 | 
			
		||||
                container.selectionEnd         = index
 | 
			
		||||
                container.selectionEndPosition = Qt.point(drag.x, drag.y)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TapHandler {
 | 
			
		||||
        acceptedButtons: Qt.LeftButton
 | 
			
		||||
        onTapped: {
 | 
			
		||||
            tapCount === 2 ? selectWordAt(eventPoint.position) :
 | 
			
		||||
            tapCount === 3 ? selectAllText() :
 | 
			
		||||
            container.clearSelection()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PointHandler {
 | 
			
		||||
        onActiveChanged:
 | 
			
		||||
            active ? container.dragStarted() : container.dragStopped()
 | 
			
		||||
        onPointChanged: container.dragPointChanged(point)
 | 
			
		||||
    }
 | 
			
		||||
    // XXX
 | 
			
		||||
    // TapHandler {
 | 
			
		||||
    //     acceptedButtons: Qt.LeftButton
 | 
			
		||||
    //     onTapped: {
 | 
			
		||||
    //         tapCount === 2 ? selectWordAt(eventPoint.position) :
 | 
			
		||||
    //         tapCount === 3 ? selectAllText() :
 | 
			
		||||
    //         null
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    MouseArea {
 | 
			
		||||
        anchors.fill: label
 | 
			
		||||
 
 | 
			
		||||
@@ -1,92 +0,0 @@
 | 
			
		||||
// SPDX-License-Identifier: LGPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
import QtQuick 2.12
 | 
			
		||||
import Clipboard 0.1
 | 
			
		||||
 | 
			
		||||
FocusScope {
 | 
			
		||||
    signal deselectAll()
 | 
			
		||||
    signal dragStarted()
 | 
			
		||||
    signal dragStopped()
 | 
			
		||||
    signal dragPointChanged(var eventPoint)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    property bool reversed: false
 | 
			
		||||
 | 
			
		||||
    property bool selecting: false
 | 
			
		||||
    property real selectionStart: -1
 | 
			
		||||
    property real selectionEnd: -1
 | 
			
		||||
    property point selectionStartPosition: Qt.point(-1, -1)
 | 
			
		||||
    property point selectionEndPosition: Qt.point(-1, -1)
 | 
			
		||||
    property var selectedTexts: ({})
 | 
			
		||||
 | 
			
		||||
    readonly property var selectionInfo: [
 | 
			
		||||
        selectionStart, selectionStartPosition,
 | 
			
		||||
        selectionEnd, selectionEndPosition,
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    readonly property string joinedSelection: {
 | 
			
		||||
        const toCopy = []
 | 
			
		||||
 | 
			
		||||
        for (const key of Object.keys(selectedTexts).sort()) {
 | 
			
		||||
            if (! selectedTexts[key]) continue
 | 
			
		||||
 | 
			
		||||
            // For some dumb reason, Object.keys convert the floats to strings
 | 
			
		||||
            toCopy.push(Number.isInteger(parseFloat(key)) ? "\n\n" : "\n")
 | 
			
		||||
            toCopy.push(selectedTexts[key])
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (reversed) toCopy.reverse()
 | 
			
		||||
 | 
			
		||||
        return toCopy.join("").trim()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    onJoinedSelectionChanged:
 | 
			
		||||
        if (joinedSelection) Clipboard.selection = joinedSelection
 | 
			
		||||
 | 
			
		||||
    onDragStarted: {
 | 
			
		||||
        draggedItem.Drag.active = true
 | 
			
		||||
    }
 | 
			
		||||
    onDragStopped: {
 | 
			
		||||
        draggedItem.Drag.drop()
 | 
			
		||||
        draggedItem.Drag.active = false
 | 
			
		||||
        selecting               = false
 | 
			
		||||
    }
 | 
			
		||||
    onDragPointChanged: {
 | 
			
		||||
        const pos = mapFromItem(
 | 
			
		||||
            mainUI, eventPoint.scenePosition.x, eventPoint.scenePosition.y,
 | 
			
		||||
        )
 | 
			
		||||
        draggedItem.x = pos.x
 | 
			
		||||
        draggedItem.y = pos.y
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function clearSelection() {
 | 
			
		||||
        selecting              = false
 | 
			
		||||
        selectionStart         = -1
 | 
			
		||||
        selectionEnd           = -1
 | 
			
		||||
        selectionStartPosition = Qt.point(-1, -1)
 | 
			
		||||
        selectionEndPosition   = Qt.point(-1, -1)
 | 
			
		||||
        deselectAll()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // PointHandler and TapHandler won't activate if the press occurs inside
 | 
			
		||||
    // a label child, so we need a Point/TapHandler inside them too.
 | 
			
		||||
 | 
			
		||||
    PointHandler {
 | 
			
		||||
        // We don't use a DragHandler because they have an unchangable minimum
 | 
			
		||||
        // drag distance before they activate.
 | 
			
		||||
        id: pointHandler
 | 
			
		||||
        onActiveChanged: active ? dragStarted() : dragStopped()
 | 
			
		||||
        onPointChanged: dragPointChanged(point)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TapHandler {
 | 
			
		||||
        acceptedButtons: Qt.LeftButton
 | 
			
		||||
        onTapped: clearSelection()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // This item will trigger the children labels's DropAreas
 | 
			
		||||
    Item { id: draggedItem }
 | 
			
		||||
}
 | 
			
		||||
@@ -165,9 +165,10 @@ Rectangle {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            area.onSelectedTextChanged: if (area.selectedText && eventList) {
 | 
			
		||||
                eventList.selectableLabelContainer.clearSelection()
 | 
			
		||||
            }
 | 
			
		||||
            // XXX
 | 
			
		||||
            // area.onSelectedTextChanged: if (area.selectedText && eventList) {
 | 
			
		||||
                // eventList.selectableLabelContainer.clearSelection()
 | 
			
		||||
            // }
 | 
			
		||||
 | 
			
		||||
            Component.onCompleted: {
 | 
			
		||||
                area.Keys.onReturnPressed.connect(ev => {
 | 
			
		||||
@@ -206,15 +207,16 @@ Rectangle {
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
                area.Keys.onPressed.connect(ev => {
 | 
			
		||||
                    if (ev.matches(StandardKey.Copy) &&
 | 
			
		||||
                        eventList &&
 | 
			
		||||
                        eventList.selectableLabelContainer.joinedSelection
 | 
			
		||||
                    ) {
 | 
			
		||||
                        ev.accepted = true
 | 
			
		||||
                        Clipboard.text =
 | 
			
		||||
                            eventList.selectableLabelContainer.joinedSelection
 | 
			
		||||
                        return
 | 
			
		||||
                    }
 | 
			
		||||
                    // XXX
 | 
			
		||||
                    // if (ev.matches(StandardKey.Copy) &&
 | 
			
		||||
                    //     eventList &&
 | 
			
		||||
                    //     eventList.selectableLabelContainer.joinedSelection
 | 
			
		||||
                    // ) {
 | 
			
		||||
                    //     ev.accepted = true
 | 
			
		||||
                    //     Clipboard.text =
 | 
			
		||||
                    //         eventList.selectableLabelContainer.joinedSelection
 | 
			
		||||
                    //     return
 | 
			
		||||
                    // }
 | 
			
		||||
 | 
			
		||||
                    // FIXME: buggy
 | 
			
		||||
                    // if (ev.modifiers === Qt.NoModifier &&
 | 
			
		||||
 
 | 
			
		||||
@@ -109,8 +109,6 @@ HRowLayout {
 | 
			
		||||
 | 
			
		||||
        HSelectableLabel {
 | 
			
		||||
            id: contentLabel
 | 
			
		||||
            container: selectableLabelContainer
 | 
			
		||||
            index: model.index
 | 
			
		||||
            visible: ! pureMedia
 | 
			
		||||
 | 
			
		||||
            topPadding: theme.chat.message.verticalSpacing
 | 
			
		||||
@@ -158,13 +156,11 @@ HRowLayout {
 | 
			
		||||
 | 
			
		||||
            function selectAllText() {
 | 
			
		||||
                // Select the message body without the date or name
 | 
			
		||||
                container.clearSelection()
 | 
			
		||||
                contentLabel.select(
 | 
			
		||||
                    0,
 | 
			
		||||
                    contentLabel.length -
 | 
			
		||||
                    timeText.length - 1  // - 1: separating space
 | 
			
		||||
                )
 | 
			
		||||
                contentLabel.updateContainerSelectedTexts()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            HoverHandler { id: contentHover }
 | 
			
		||||
 
 | 
			
		||||
@@ -7,65 +7,16 @@ import "../../.."
 | 
			
		||||
import "../../../Base"
 | 
			
		||||
 | 
			
		||||
Rectangle {
 | 
			
		||||
    property alias selectableLabelContainer: selectableLabelContainer
 | 
			
		||||
    property alias eventList: eventList
 | 
			
		||||
 | 
			
		||||
    color: theme.chat.eventList.background
 | 
			
		||||
 | 
			
		||||
    HSelectableLabelContainer {
 | 
			
		||||
        id: selectableLabelContainer
 | 
			
		||||
        anchors.fill: parent
 | 
			
		||||
        reversed: eventList.verticalLayoutDirection === ListView.BottomToTop
 | 
			
		||||
 | 
			
		||||
        DragHandler {
 | 
			
		||||
            target: null
 | 
			
		||||
            onActiveChanged: if (! active) dragFlicker.speed = 0
 | 
			
		||||
            onCentroidChanged: {
 | 
			
		||||
                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 boost = 20 * (pos < dist ?  -pos : -(height - pos))
 | 
			
		||||
    property Item selectableLabelContainer: Item {}
 | 
			
		||||
    property alias eventList: eventList
 | 
			
		||||
 | 
			
		||||
                dragFlicker.speed =
 | 
			
		||||
                    left && vel && pos < dist          ? 1000 + boost :
 | 
			
		||||
                    left && vel && pos > height - dist ? -1000 + -boost :
 | 
			
		||||
                    0
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Timer {
 | 
			
		||||
            id: dragFlicker
 | 
			
		||||
            interval: 100
 | 
			
		||||
            running: speed !== 0
 | 
			
		||||
            repeat: true
 | 
			
		||||
 | 
			
		||||
            onTriggered: {
 | 
			
		||||
                if (eventList.verticalOvershoot !== 0) return
 | 
			
		||||
                if (speed < 0 && eventList.atYEnd) return
 | 
			
		||||
                if (eventList.atYBeggining) {
 | 
			
		||||
                    if (bouncedStart) { return } else { bouncedStart = true }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                eventList.flick(0, speed * acceleration)
 | 
			
		||||
                acceleration = Math.min(8, acceleration * 1.05)
 | 
			
		||||
            }
 | 
			
		||||
            onRunningChanged: if (! running) {
 | 
			
		||||
                acceleration = 1.0
 | 
			
		||||
                bouncedStart = false
 | 
			
		||||
                eventList.cancelFlick()
 | 
			
		||||
                eventList.returnToBounds()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            property real speed: 0.0
 | 
			
		||||
            property real acceleration: 1.0
 | 
			
		||||
            property bool bouncedStart: false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    HListView {
 | 
			
		||||
        id: eventList
 | 
			
		||||
        clip: true
 | 
			
		||||
            allowDragging: false
 | 
			
		||||
 | 
			
		||||
        anchors.fill: parent
 | 
			
		||||
        anchors.leftMargin: theme.spacing
 | 
			
		||||
@@ -79,12 +30,8 @@ Rectangle {
 | 
			
		||||
        // reloaded from network.
 | 
			
		||||
        cacheBuffer: Screen.desktopAvailableHeight * 2
 | 
			
		||||
 | 
			
		||||
            onYPosChanged:
 | 
			
		||||
                if (canLoad && yPos < 0.1) Qt.callLater(loadPastEvents)
 | 
			
		||||
 | 
			
		||||
            // When an invited room becomes joined, we should now be able to
 | 
			
		||||
            // fetch past events.
 | 
			
		||||
            onInviterChanged: canLoad = true
 | 
			
		||||
        model: ModelStore.get(chat.userId, chat.roomId, "events")
 | 
			
		||||
        delegate: EventDelegate {}
 | 
			
		||||
 | 
			
		||||
        // Since the list is BottomToTop, this is actually a header
 | 
			
		||||
        footer: Item {
 | 
			
		||||
@@ -107,6 +54,13 @@ Rectangle {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        onYPosChanged:
 | 
			
		||||
            if (canLoad && yPos < 0.1) Qt.callLater(loadPastEvents)
 | 
			
		||||
 | 
			
		||||
        // When an invited room becomes joined, we should now be able to
 | 
			
		||||
        // fetch past events.
 | 
			
		||||
        onInviterChanged: canLoad = true
 | 
			
		||||
 | 
			
		||||
        Component.onCompleted: shortcuts.flickTarget = eventList
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -180,11 +134,6 @@ Rectangle {
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            model: ModelStore.get(chat.userId, chat.roomId, "events")
 | 
			
		||||
            delegate: EventDelegate {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    HNoticePage {
 | 
			
		||||
@@ -193,4 +142,49 @@ Rectangle {
 | 
			
		||||
        visible: eventList.model.count < 1
 | 
			
		||||
        anchors.fill: parent
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DragHandler {
 | 
			
		||||
        target: null
 | 
			
		||||
        onActiveChanged: if (! active) dragFlicker.speed = 0
 | 
			
		||||
        onCentroidChanged: {
 | 
			
		||||
            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 boost = 20 * (pos < dist ?  -pos : -(height - pos))
 | 
			
		||||
 | 
			
		||||
            dragFlicker.speed =
 | 
			
		||||
                left && vel && pos < dist          ? 1000 + boost :
 | 
			
		||||
                left && vel && pos > height - dist ? -1000 + -boost :
 | 
			
		||||
                0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Timer {
 | 
			
		||||
        id: dragFlicker
 | 
			
		||||
        interval: 100
 | 
			
		||||
        running: speed !== 0
 | 
			
		||||
        repeat: true
 | 
			
		||||
 | 
			
		||||
        onTriggered: {
 | 
			
		||||
            if (eventList.verticalOvershoot !== 0) return
 | 
			
		||||
            if (speed < 0 && eventList.atYEnd) return
 | 
			
		||||
            if (eventList.atYBeggining) {
 | 
			
		||||
                if (bouncedStart) { return } else { bouncedStart = true }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            eventList.flick(0, speed * acceleration)
 | 
			
		||||
            acceleration = Math.min(8, acceleration * 1.05)
 | 
			
		||||
        }
 | 
			
		||||
        onRunningChanged: if (! running) {
 | 
			
		||||
            acceleration = 1.0
 | 
			
		||||
            bouncedStart = false
 | 
			
		||||
            eventList.cancelFlick()
 | 
			
		||||
            eventList.returnToBounds()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        property real speed: 0.0
 | 
			
		||||
        property real acceleration: 1.0
 | 
			
		||||
        property bool bouncedStart: false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user