From b9076c5c4d1beb7ff4cb4a2db8e6eb4e7f5b0dcd Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 8 Oct 2019 20:55:09 +0200 Subject: [PATCH] Try out DelegateChooser requires Qt5.12+ --- resources/qml/TimelineView.qml | 176 ++++-------------- resources/qml/delegates/FileMessage.qml | 6 +- resources/qml/delegates/ImageMessage.qml | 8 +- resources/qml/delegates/NoticeMessage.qml | 2 +- .../qml/delegates/PlayableMediaMessage.qml | 10 +- resources/qml/delegates/TextMessage.qml | 2 +- resources/qml/delegates/TimelineRow.qml | 139 ++++++++++++++ resources/qml/delegates/placeholder.qml | 2 +- resources/res.qrc | 1 + 9 files changed, 189 insertions(+), 157 deletions(-) create mode 100644 resources/qml/delegates/TimelineRow.qml diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 4782c1f1..0642b13a 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -3,9 +3,12 @@ import QtQuick.Controls 2.1 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import QtQuick.Window 2.2 +import Qt.labs.qmlmodels 1.0 import com.github.nheko 1.0 +import "./delegates" + Rectangle { anchors.fill: parent @@ -77,158 +80,47 @@ Rectangle { onMovementEnded: updatePosition() spacing: 4 - delegate: RowLayout { - anchors.leftMargin: avatarSize + 4 - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: scrollbar.width - - function isFullyVisible() { - return (y - chat.contentY - 1) + height < chat.height + delegate: DelegateChooser { + role: "type" + DelegateChoice { + roleValue: MtxEvent.TextMessage + TimelineRow { view: chat; TextMessage { id: kid } } } - function getIndex() { - return index; + DelegateChoice { + roleValue: MtxEvent.NoticeMessage + TimelineRow { view: chat; NoticeMessage { id: kid } } } - - Loader { - id: loader - Layout.fillWidth: true - Layout.alignment: Qt.AlignTop - height: item.height - - source: switch(model.type) { - //case MtxEvent.Aliases: return "delegates/Aliases.qml" - //case MtxEvent.Avatar: return "delegates/Avatar.qml" - //case MtxEvent.CanonicalAlias: return "delegates/CanonicalAlias.qml" - //case MtxEvent.Create: return "delegates/Create.qml" - //case MtxEvent.GuestAccess: return "delegates/GuestAccess.qml" - //case MtxEvent.HistoryVisibility: return "delegates/HistoryVisibility.qml" - //case MtxEvent.JoinRules: return "delegates/JoinRules.qml" - //case MtxEvent.Member: return "delegates/Member.qml" - //case MtxEvent.Name: return "delegates/Name.qml" - //case MtxEvent.PowerLevels: return "delegates/PowerLevels.qml" - //case MtxEvent.Topic: return "delegates/Topic.qml" - case MtxEvent.NoticeMessage: return "delegates/NoticeMessage.qml" - case MtxEvent.TextMessage: return "delegates/TextMessage.qml" - case MtxEvent.EmoteMessage: return "delegates/TextMessage.qml" - case MtxEvent.ImageMessage: return "delegates/ImageMessage.qml" - case MtxEvent.Sticker: return "delegates/ImageMessage.qml" - case MtxEvent.FileMessage: return "delegates/FileMessage.qml" - case MtxEvent.VideoMessage: return "delegates/PlayableMediaMessage.qml" - case MtxEvent.AudioMessage: return "delegates/PlayableMediaMessage.qml" - case MtxEvent.Redacted: return "delegates/Redacted.qml" - default: return "delegates/placeholder.qml" - } - property variant eventData: model + DelegateChoice { + roleValue: MtxEvent.EmoteMessage + TimelineRow { view: chat; TextMessage { id: kid } } } - - StatusIndicator { - state: model.state - Layout.alignment: Qt.AlignRight | Qt.AlignTop - Layout.preferredHeight: 16 + DelegateChoice { + roleValue: MtxEvent.ImageMessage + TimelineRow { view: chat; ImageMessage { id: kid } } } - - EncryptionIndicator { - visible: model.isEncrypted - Layout.alignment: Qt.AlignRight | Qt.AlignTop - Layout.preferredHeight: 16 + DelegateChoice { + roleValue: MtxEvent.Sticker + TimelineRow { view: chat; ImageMessage { id: kid } } } - - Button { - Layout.alignment: Qt.AlignRight | Qt.AlignTop - id: replyButton - flat: true - Layout.preferredHeight: 16 - ToolTip.visible: hovered - ToolTip.text: qsTr("Reply") - - // disable background, because we don't want a border on hover - background: Item { - } - - Image { - id: replyButtonImg - // Workaround, can't get icon.source working for now... - anchors.fill: parent - source: "qrc:/icons/icons/ui/mail-reply.png" - } - ColorOverlay { - anchors.fill: replyButtonImg - source: replyButtonImg - color: replyButton.hovered ? colors.highlight : colors.buttonText - } - - onClicked: chat.model.replyAction(model.id) + DelegateChoice { + roleValue: MtxEvent.FileMessage + TimelineRow { view: chat; FileMessage { id: kid } } } - Button { - Layout.alignment: Qt.AlignRight | Qt.AlignTop - id: optionsButton - flat: true - Layout.preferredHeight: 16 - ToolTip.visible: hovered - ToolTip.text: qsTr("Options") - - // disable background, because we don't want a border on hover - background: Item { - } - - Image { - id: optionsButtonImg - // Workaround, can't get icon.source working for now... - anchors.fill: parent - source: "qrc:/icons/icons/ui/vertical-ellipsis.png" - } - ColorOverlay { - anchors.fill: optionsButtonImg - source: optionsButtonImg - color: optionsButton.hovered ? colors.highlight : colors.buttonText - } - - onClicked: contextMenu.open() - - Menu { - y: optionsButton.height - id: contextMenu - - MenuItem { - text: qsTr("Read receipts") - onTriggered: chat.model.readReceiptsAction(model.id) - } - MenuItem { - text: qsTr("Mark as read") - } - MenuItem { - text: qsTr("View raw message") - onTriggered: chat.model.viewRawMessage(model.id) - } - MenuItem { - text: qsTr("Redact message") - onTriggered: chat.model.redactEvent(model.id) - } - MenuItem { - visible: model.type == MtxEvent.ImageMessage || model.type == MtxEvent.VideoMessage || model.type == MtxEvent.AudioMessage || model.type == MtxEvent.FileMessage || model.type == MtxEvent.Sticker - text: qsTr("Save as") - onTriggered: timelineManager.saveMedia(model.url, model.filename, model.mimetype, model.type) - } - } + DelegateChoice { + roleValue: MtxEvent.VideoMessage + TimelineRow { view: chat; PlayableMediaMessage { id: kid } } } - - Text { - Layout.alignment: Qt.AlignRight | Qt.AlignTop - text: model.timestamp.toLocaleTimeString("HH:mm") - color: inactiveColors.text - - ToolTip.visible: ma.containsMouse - ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate) - - MouseArea{ - id: ma - anchors.fill: parent - hoverEnabled: true - } + DelegateChoice { + roleValue: MtxEvent.AudioMessage + TimelineRow { view: chat; PlayableMediaMessage { id: kid } } + } + DelegateChoice { + roleValue: MtxEvent.Redacted + TimelineRow { view: chat; Redacted { id: kid } } } } + section { property: "section" delegate: Column { diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml index 6dd552ab..ad2c695d 100644 --- a/resources/qml/delegates/FileMessage.qml +++ b/resources/qml/delegates/FileMessage.qml @@ -31,7 +31,7 @@ Rectangle { } MouseArea { anchors.fill: parent - onClicked: timelineManager.saveMedia(eventData.url, eventData.filename, eventData.mimetype, eventData.type) + onClicked: timelineManager.saveMedia(model.url, model.filename, model.mimetype, model.type) cursorShape: Qt.PointingHandCursor } } @@ -40,14 +40,14 @@ Rectangle { Text { Layout.fillWidth: true - text: eventData.body + text: model.body textFormat: Text.PlainText elide: Text.ElideRight color: colors.text } Text { Layout.fillWidth: true - text: eventData.filesize + text: model.filesize textFormat: Text.PlainText elide: Text.ElideRight color: colors.text diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 2ed41a17..70d2debe 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -4,20 +4,20 @@ import com.github.nheko 1.0 Item { width: 300 - height: 300 * eventData.proportionalHeight + height: 300 * model.proportionalHeight Image { id: img anchors.fill: parent - source: eventData.url.replace("mxc://", "image://MxcImage/") + source: model.url.replace("mxc://", "image://MxcImage/") asynchronous: true fillMode: Image.PreserveAspectFit MouseArea { - enabled: eventData.type == MtxEvent.ImageMessage + enabled: model.type == MtxEvent.ImageMessage anchors.fill: parent - onClicked: timelineManager.openImageOverlay(eventData.url, eventData.filename, eventData.mimetype, eventData.type) + onClicked: timelineManager.openImageOverlay(model.url, model.filename, model.mimetype, model.type) } } } diff --git a/resources/qml/delegates/NoticeMessage.qml b/resources/qml/delegates/NoticeMessage.qml index 5f04d235..b916d65a 100644 --- a/resources/qml/delegates/NoticeMessage.qml +++ b/resources/qml/delegates/NoticeMessage.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 TextEdit { - text: eventData.formattedBody + text: model.formattedBody textFormat: TextEdit.RichText readOnly: true wrapMode: Text.Wrap diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index 2385c750..c716d21d 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -17,7 +17,7 @@ Rectangle { anchors.centerIn: parent VideoOutput { - visible: eventData.type == MtxEvent.VideoMessage + visible: model.type == MtxEvent.VideoMessage Layout.maximumHeight: 300 Layout.minimumHeight: 300 Layout.maximumWidth: 500 @@ -85,7 +85,7 @@ Rectangle { anchors.fill: parent onClicked: { switch (button.state) { - case "": timelineManager.cacheMedia(eventData.url, eventData.mimetype); break; + case "": timelineManager.cacheMedia(model.url, model.mimetype); break; case "stopped": media.play(); console.log("play"); button.state = "playing" @@ -107,7 +107,7 @@ Rectangle { Connections { target: timelineManager onMediaCached: { - if (mxcUrl == eventData.url) { + if (mxcUrl == model.url) { media.source = "file://" + cacheUrl button.state = "stopped" console.log("media loaded: " + mxcUrl + " at " + cacheUrl) @@ -132,14 +132,14 @@ Rectangle { Text { Layout.fillWidth: true - text: eventData.body + text: model.body textFormat: Text.PlainText elide: Text.ElideRight color: colors.text } Text { Layout.fillWidth: true - text: eventData.filesize + text: model.filesize textFormat: Text.PlainText elide: Text.ElideRight color: colors.text diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index f7dba618..3a3492ed 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 TextEdit { - text: eventData.formattedBody + text: model.formattedBody textFormat: TextEdit.RichText readOnly: true wrapMode: Text.Wrap diff --git a/resources/qml/delegates/TimelineRow.qml b/resources/qml/delegates/TimelineRow.qml new file mode 100644 index 00000000..28a2ec8c --- /dev/null +++ b/resources/qml/delegates/TimelineRow.qml @@ -0,0 +1,139 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import QtGraphicalEffects 1.0 +import QtQuick.Window 2.2 + +import com.github.nheko 1.0 + +import ".." + +RowLayout { + property var view: undefined + default property alias data: contentItem.data + + height: kid.height // TODO: fix this, we shouldn't need to give the child of contentItem this id! + anchors.leftMargin: avatarSize + 4 + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: scrollbar.width + + function isFullyVisible() { + return (y - view.contentY - 1) + height < view.height + } + function getIndex() { + return index; + } + + Item { + id: contentItem + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + } + + StatusIndicator { + state: model.state + Layout.alignment: Qt.AlignRight | Qt.AlignTop + Layout.preferredHeight: 16 + } + + EncryptionIndicator { + visible: model.isEncrypted + Layout.alignment: Qt.AlignRight | Qt.AlignTop + Layout.preferredHeight: 16 + } + + Button { + Layout.alignment: Qt.AlignRight | Qt.AlignTop + id: replyButton + flat: true + Layout.preferredHeight: 16 + ToolTip.visible: hovered + ToolTip.text: qsTr("Reply") + + // disable background, because we don't want a border on hover + background: Item { + } + + Image { + id: replyButtonImg + // Workaround, can't get icon.source working for now... + anchors.fill: parent + source: "qrc:/icons/icons/ui/mail-reply.png" + } + ColorOverlay { + anchors.fill: replyButtonImg + source: replyButtonImg + color: replyButton.hovered ? colors.highlight : colors.buttonText + } + + onClicked: view.model.replyAction(model.id) + } + Button { + Layout.alignment: Qt.AlignRight | Qt.AlignTop + id: optionsButton + flat: true + Layout.preferredHeight: 16 + ToolTip.visible: hovered + ToolTip.text: qsTr("Options") + + // disable background, because we don't want a border on hover + background: Item { + } + + Image { + id: optionsButtonImg + // Workaround, can't get icon.source working for now... + anchors.fill: parent + source: "qrc:/icons/icons/ui/vertical-ellipsis.png" + } + ColorOverlay { + anchors.fill: optionsButtonImg + source: optionsButtonImg + color: optionsButton.hovered ? colors.highlight : colors.buttonText + } + + onClicked: contextMenu.open() + + Menu { + y: optionsButton.height + id: contextMenu + + MenuItem { + text: qsTr("Read receipts") + onTriggered: view.model.readReceiptsAction(model.id) + } + MenuItem { + text: qsTr("Mark as read") + } + MenuItem { + text: qsTr("View raw message") + onTriggered: view.model.viewRawMessage(model.id) + } + MenuItem { + text: qsTr("Redact message") + onTriggered: view.model.redactEvent(model.id) + } + MenuItem { + visible: model.type == MtxEvent.ImageMessage || model.type == MtxEvent.VideoMessage || model.type == MtxEvent.AudioMessage || model.type == MtxEvent.FileMessage || model.type == MtxEvent.Sticker + text: qsTr("Save as") + onTriggered: timelineManager.saveMedia(model.url, model.filename, model.mimetype, model.type) + } + } + } + + Text { + Layout.alignment: Qt.AlignRight | Qt.AlignTop + text: model.timestamp.toLocaleTimeString("HH:mm") + color: inactiveColors.text + + ToolTip.visible: ma.containsMouse + ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate) + + MouseArea{ + id: ma + anchors.fill: parent + hoverEnabled: true + } + } +} diff --git a/resources/qml/delegates/placeholder.qml b/resources/qml/delegates/placeholder.qml index e64bc368..462af2db 100644 --- a/resources/qml/delegates/placeholder.qml +++ b/resources/qml/delegates/placeholder.qml @@ -2,7 +2,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.1 Label { - text: qsTr("unimplemented event: ") + eventData.type + text: qsTr("unimplemented event: ") + model.type textFormat: Text.PlainText wrapMode: Text.Wrap width: parent.width diff --git a/resources/res.qrc b/resources/res.qrc index 16bab4e4..2e0f89ce 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -119,6 +119,7 @@ qml/Avatar.qml qml/StatusIndicator.qml qml/EncryptionIndicator.qml + qml/delegates/TimelineRow.qml qml/delegates/TextMessage.qml qml/delegates/NoticeMessage.qml qml/delegates/ImageMessage.qml