Implement selection across multiple MessageDelegates
BUG: 457669
This commit is contained in:
@@ -13,6 +13,13 @@ import org.kde.neochat 1.0
|
|||||||
TimelineContainer {
|
TimelineContainer {
|
||||||
id: messageDelegate
|
id: messageDelegate
|
||||||
|
|
||||||
|
function positionAt(x, y) {
|
||||||
|
let point = label.mapFromItem(messageDelegate, x, y)
|
||||||
|
return label.positionAt(point.x, point.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
property alias selectedText: label.selectedText
|
||||||
|
|
||||||
property bool isEmote: false
|
property bool isEmote: false
|
||||||
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
|
onOpenContextMenu: openMessageContext(model, label.selectedText, Controller.plainText(label.textDocument))
|
||||||
|
|
||||||
@@ -22,6 +29,7 @@ TimelineContainer {
|
|||||||
id: label
|
id: label
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
isEmote: messageDelegate.isEmote
|
isEmote: messageDelegate.isEmote
|
||||||
|
isDelegate: true
|
||||||
}
|
}
|
||||||
Loader {
|
Loader {
|
||||||
id: linkPreviewLoader
|
id: linkPreviewLoader
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ TextEdit {
|
|||||||
|
|
||||||
property bool isEmote: false
|
property bool isEmote: false
|
||||||
|
|
||||||
|
property bool isDelegate: false
|
||||||
|
|
||||||
readonly property var linkRegex: /(href=["'])?(\b(https?):\/\/[^\s\<\>\"\'\\]+)/g
|
readonly property var linkRegex: /(href=["'])?(\b(https?):\/\/[^\s\<\>\"\'\\]+)/g
|
||||||
property string textMessage: model.display.includes("http")
|
property string textMessage: model.display.includes("http")
|
||||||
? model.display.replace(linkRegex, function() {
|
? model.display.replace(linkRegex, function() {
|
||||||
@@ -41,8 +43,11 @@ TextEdit {
|
|||||||
persistentSelection: true
|
persistentSelection: true
|
||||||
|
|
||||||
// Work around QTBUG 93281
|
// Work around QTBUG 93281
|
||||||
Component.onCompleted: if (text.includes("<img")) {
|
Component.onCompleted: {
|
||||||
Controller.forceRefreshTextDocument(contentLabel.textDocument, contentLabel)
|
updateSelection();
|
||||||
|
if (text.includes("<img")) {
|
||||||
|
Controller.forceRefreshTextDocument(contentLabel.textDocument, contentLabel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
text: "<style>
|
text: "<style>
|
||||||
@@ -103,4 +108,26 @@ a{
|
|||||||
enabled: !parent.hoveredLink && !spoilerRevealed
|
enabled: !parent.hoveredLink && !spoilerRevealed
|
||||||
onTapped: spoilerRevealed = true
|
onTapped: spoilerRevealed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: selectionArea
|
||||||
|
enabled: contentLabel.isDelegate
|
||||||
|
function onSelectionChanged() {
|
||||||
|
updateSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelection() {
|
||||||
|
if (index < selectionArea.lowerIndex || index > selectionArea.upperIndex) {
|
||||||
|
contentLabel.select(0, 0);
|
||||||
|
} else if (index > selectionArea.lowerIndex && index < selectionArea.upperIndex) {
|
||||||
|
contentLabel.selectAll();
|
||||||
|
} else if (index === selectionArea.selectionStartIndex && index === selectionArea.selectionEndIndex) {
|
||||||
|
contentLabel.select(selectionArea.upperPos, selectionArea.lowerPos);
|
||||||
|
} else if (index === selectionArea.upperIndex) {
|
||||||
|
contentLabel.select(selectionArea.upperPos, contentLabel.length);
|
||||||
|
} else if (index === selectionArea.lowerIndex) {
|
||||||
|
contentLabel.select(0, selectionArea.lowerPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -549,8 +549,66 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
function indexAtRelative(x, y) {
|
||||||
|
return indexAt(x + contentX, y + contentY)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: selectionArea
|
||||||
|
property int selectionStartIndex
|
||||||
|
property int selectionEndIndex
|
||||||
|
property int selectionStartPos
|
||||||
|
property int selectionEndPos
|
||||||
|
|
||||||
|
property int upperIndex: selectionStartIndex > selectionEndIndex ? selectionStartIndex : selectionEndIndex
|
||||||
|
property int upperPos: selectionStartIndex > selectionEndIndex ? selectionStartPos : (selectionStartIndex == selectionEndIndex ? (selectionStartPos > selectionEndPos ? selectionEndPos : selectionStartPos) : selectionEndPos)
|
||||||
|
property int lowerIndex: selectionStartIndex > selectionEndIndex ? selectionEndIndex : selectionStartIndex
|
||||||
|
property int lowerPos: selectionStartIndex > selectionEndIndex ? selectionEndPos : (selectionStartIndex == selectionEndIndex ? (selectionStartPos > selectionEndPos ? selectionStartPos : selectionEndPos) : selectionStartPos)
|
||||||
|
|
||||||
|
signal selectionChanged
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
function indexAndPos(x, y) {
|
||||||
|
const index = messageListView.indexAtRelative(x, y);
|
||||||
|
if (index == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const item = messageListView.itemAtIndex(index);
|
||||||
|
const relItemY = item.y - messageListView.contentY;
|
||||||
|
const pos = item.positionAt(x, y - relItemY);
|
||||||
|
|
||||||
|
return [index, pos]
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressed: {
|
||||||
|
[selectionEndIndex, selectionEndPos] = indexAndPos(mouse.x, mouse.y);
|
||||||
|
[selectionStartIndex, selectionStartPos] = indexAndPos(mouse.x, mouse.y);
|
||||||
|
selectionChanged();
|
||||||
|
}
|
||||||
|
onPositionChanged: {
|
||||||
|
if (!pressed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
[selectionEndIndex, selectionEndPos] = indexAndPos(mouse.x, mouse.y);
|
||||||
|
selectionChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.Action {
|
||||||
|
onTriggered: {
|
||||||
|
var text = ""
|
||||||
|
for (let i = selectionArea.upperIndex; i >= selectionArea.lowerIndex; i--) {
|
||||||
|
text += messageListView.itemAtIndex(i).selectedText
|
||||||
|
if (i > selectionArea.lowerIndex) {
|
||||||
|
text += " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Clipboard.saveText(text)
|
||||||
|
}
|
||||||
|
shortcut: "Ctrl+C"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
footer: ChatBox {
|
footer: ChatBox {
|
||||||
id: chatBox
|
id: chatBox
|
||||||
|
|||||||
Reference in New Issue
Block a user