diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | resources/qml/TimelineDefaultMessageStyle.qml | 20 | ||||
-rw-r--r-- | resources/qml/TimelineEvent.qml | 10 | ||||
-rw-r--r-- | resources/qml/delegates/ImageMessage.qml | 30 | ||||
-rw-r--r-- | resources/qml/delegates/MessageDelegate.qml | 780 | ||||
-rw-r--r-- | resources/qml/delegates/PlayableMediaMessage.qml | 2 | ||||
-rw-r--r-- | resources/qml/delegates/Reply.qml | 50 | ||||
-rw-r--r-- | resources/qml/delegates/TextMessage.qml | 3 | ||||
-rw-r--r-- | src/timeline/EventDelegateChooser.cpp | 120 | ||||
-rw-r--r-- | src/timeline/EventDelegateChooser.h | 96 |
10 files changed, 211 insertions, 901 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e02bf34..cbe2b20e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -742,7 +742,6 @@ set(QML_SOURCES resources/qml/delegates/Encrypted.qml resources/qml/delegates/FileMessage.qml resources/qml/delegates/ImageMessage.qml - resources/qml/delegates/MessageDelegate.qml resources/qml/delegates/NoticeMessage.qml resources/qml/delegates/Pill.qml resources/qml/delegates/Placeholder.qml diff --git a/resources/qml/TimelineDefaultMessageStyle.qml b/resources/qml/TimelineDefaultMessageStyle.qml index 0866fab6..8beaa8f0 100644 --- a/resources/qml/TimelineDefaultMessageStyle.qml +++ b/resources/qml/TimelineDefaultMessageStyle.qml @@ -18,7 +18,7 @@ TimelineEvent { id: wrapper ListView.delayRemove: true width: chat.delegateMaxWidth - height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 20) + height: Math.max((section.item?.height ?? 0) + gridContainer.implicitHeight + reactionRow.implicitHeight + unreadRow.height, 10) anchors.horizontalCenter: ListView.view.contentItem.horizontalCenter //room: chatRoot.roommodel @@ -51,6 +51,9 @@ TimelineEvent { property alias hovered: messageHover.hovered property bool scrolledToThis: false + mainInset: (threadId ? (4 + Nheko.paddingSmall) : 0) + 4 + replyInset: mainInset + 4 + Nheko.paddingSmall + maxWidth: chat.delegateMaxWidth - avatarMargin - metadata.width data: [ @@ -182,16 +185,21 @@ TimelineEvent { color: TimelineManager.userColor(wrapper.threadId, palette.base) } } + + Item { + visible: wrapper.isStateEvent + width: (wrapper.maxWidth - (wrapper.main?.width ?? 0)) / 2 + height: 1 + } + Column { id: contentColumn AbstractButton { id: replyRow visible: wrapper.reply - //Layout.fillWidth: true - //Layout.maximumHeight: timelineView.height / 8 - //Layout.preferredWidth: replyRowLay.implicitWidth - //Layout.preferredHeight: replyRowLay.implicitHeight + + height: replyLine.height property color userColor: TimelineManager.userColor(wrapper.reply?.userId ?? '', palette.base) @@ -209,7 +217,7 @@ TimelineEvent { Rectangle { id: replyLine - height: replyCol.height + height: Math.min( wrapper.reply?.height, timelineView.height / 5) + Nheko.paddingSmall + replyUserButton.height color: replyRow.userColor width: 4 } diff --git a/resources/qml/TimelineEvent.qml b/resources/qml/TimelineEvent.qml index f1b6bd2a..5120fd12 100644 --- a/resources/qml/TimelineEvent.qml +++ b/resources/qml/TimelineEvent.qml @@ -62,12 +62,10 @@ EventDelegateChooser { required property string userId required property string userName - Layout.fillWidth: true - //Layout.maximumWidth: implicitWidth - body: '' color: palette.buttonText font.italic: true + font.pointSize: Settings.fontSize * 0.8 formatted: '' horizontalAlignment: Text.AlignHCenter isOnlyEmoji: false @@ -202,7 +200,6 @@ EventDelegateChooser { id: member required property string formattedStateEvent - required property bool isReply required property Room room required property string userId required property string userName @@ -212,7 +209,7 @@ EventDelegateChooser { body: formatted formatted: member.formattedStateEvent isOnlyEmoji: false - isReply: member.isReply + isReply: EventDelegateChooser.isReply isStateEvent: true keepFullText: true } @@ -233,7 +230,6 @@ EventDelegateChooser { required property string body required property string eventId - required property bool isReply required property Room room required property string userId required property string userName @@ -243,7 +239,7 @@ EventDelegateChooser { body: formatted formatted: qsTr("This room was replaced for the following reason: %1").arg(tombstone.body) isOnlyEmoji: false - isReply: tombstone.isReply + isReply: EventDelegateChooser.isReply isStateEvent: true keepFullText: true } diff --git a/resources/qml/delegates/ImageMessage.qml b/resources/qml/delegates/ImageMessage.qml index 0369d5a1..06a1a9e7 100644 --- a/resources/qml/delegates/ImageMessage.qml +++ b/resources/qml/delegates/ImageMessage.qml @@ -5,7 +5,6 @@ import QtQuick import QtQuick.Window import QtQuick.Controls -import QtQuick.Layouts import im.nheko AbstractButton { @@ -17,16 +16,17 @@ AbstractButton { required property string blurhash required property string body required property string filename - required property bool isReply required property string eventId required property int containerHeight - property double divisor: isReply ? 5 : 3 + property double divisor: EventDelegateChooser.isReply ? 5 : 3 + + EventDelegateChooser.keepAspectRatio: true + EventDelegateChooser.maxWidth: originalWidth + EventDelegateChooser.maxHeight: containerHeight / divisor + EventDelegateChooser.aspectRatio: proportionalHeight - //Layout.maximumWidth: originalWidth - Layout.maximumHeight: Math.min(originalHeight, containerHeight / divisor) - implicitWidth: height/proportionalHeight - implicitHeight: Math.min(Layout.maximumHeight, width*proportionalHeight) hoverEnabled: true + enabled: !EventDelegateChooser.isReply state: (img.status != Image.Ready || timeline.privacyScreen.active) ? "BlurhashVisible" : "ImageVisible" states: [ @@ -133,8 +133,8 @@ AbstractButton { roomm: room play: !Settings.animateImagesOnHover || parent.hovered eventId: parent.eventId - width: parent.implicitWidth - height: parent.implicitHeight + + anchors.fill: parent } Image { @@ -143,10 +143,10 @@ AbstractButton { source: blurhash ? ("image://blurhash/" + blurhash) : ("image://colorimage/:/icons/icons/ui/image-failed.svg?" + palette.buttonText) asynchronous: true fillMode: Image.PreserveAspectFit - sourceSize.width: parent.implicitWidth * Screen.devicePixelRatio - sourceSize.height: parent.implicitHeight * Screen.devicePixelRatio - width: parent.implicitWidth - height: parent.implicitHeight + sourceSize.width: parent.width * Screen.devicePixelRatio + sourceSize.height: parent.height * Screen.devicePixelRatio + + anchors.fill: parent } onClicked: Settings.openImageExternal ? room.openMedia(eventId) : TimelineManager.openImageOverlay(room, url, eventId, originalWidth, proportionalHeight); @@ -154,8 +154,8 @@ AbstractButton { Item { id: overlay - width: parent.implicitWidth - height: parent.implicitHeight + anchors.fill: parent + visible: parent.hovered Rectangle { diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml deleted file mode 100644 index cd1bdcd4..00000000 --- a/resources/qml/delegates/MessageDelegate.qml +++ /dev/null @@ -1,780 +0,0 @@ -// SPDX-FileCopyrightText: Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -import QtQuick 2.6 -import QtQuick.Controls 2.1 -import QtQuick.Layouts 1.2 -import im.nheko 1.0 - -Item { - id: d - - required property bool isReply - property bool keepFullText: !isReply - property alias child: chooser.child - //implicitWidth: chooser.child?.implicitWidth ?? 0 - required property double proportionalHeight - required property int type - required property string typeString - required property int originalWidth - required property int duration - required property string blurhash - required property string body - required property string formattedBody - required property string eventId - required property string filename - required property string filesize - required property string url - required property string thumbnailUrl - required property bool isOnlyEmoji - required property bool isStateEvent - required property string userId - required property string userName - required property string roomTopic - required property string roomName - required property string callType - required property int encryptionError - required property int relatedEventCacheBuster - property bool fitsMetadata: (chooser.child && chooser.child.fitsMetadata) ? chooser.child.fitsMetadata : false - property int metadataWidth - - implicitWidth: chooser.child?.implicitWidth - - height: chooser.child ? chooser.child.height : Nheko.paddingLarge - - DelegateChooser { - id: chooser - - //role: "type" //< not supported in our custom implementation, have to use roleValue - roleValue: type - //anchors.fill: parent - - width: parent?.width ?? 0 // this should get rid of "cannot read property 'width' of null" - - DelegateChoice { - roleValue: MtxEvent.UnknownEvent - - Placeholder { - typeString: d.typeString - text: "Unretrieved event" - } - - } - - DelegateChoice { - roleValue: MtxEvent.Tombstone - - - ColumnLayout { - width: parent.width - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - Layout.fillWidth: true - formatted: qsTr("This room was replaced for the following reason: %1").arg(d.body) - } - - Button { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Go to replacement room") - onClicked: room.joinReplacementRoom(eventId) - } - - } - } - - DelegateChoice { - roleValue: MtxEvent.TextMessage - - TextMessage { - formatted: d.formattedBody - body: d.body - isOnlyEmoji: d.isOnlyEmoji - isReply: d.isReply - keepFullText: d.keepFullText - metadataWidth: d.metadataWidth - } - - } - - DelegateChoice { - roleValue: MtxEvent.UnknownMessage - - TextMessage { - formatted: d.formattedBody - body: d.body - isOnlyEmoji: d.isOnlyEmoji - isReply: d.isReply - keepFullText: d.keepFullText - metadataWidth: d.metadataWidth - } - - } - - DelegateChoice { - roleValue: MtxEvent.ElementEffectMessage - - TextMessage { - formatted: d.formattedBody - body: d.body - isOnlyEmoji: d.isOnlyEmoji - isReply: d.isReply - keepFullText: d.keepFullText - metadataWidth: d.metadataWidth - } - - } - - DelegateChoice { - roleValue: MtxEvent.NoticeMessage - - NoticeMessage { - formatted: d.formattedBody - body: d.body - isOnlyEmoji: d.isOnlyEmoji - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - metadataWidth: d.metadataWidth - } - - } - - DelegateChoice { - roleValue: MtxEvent.EmoteMessage - - NoticeMessage { - formatted: TimelineManager.escapeEmoji(d.userName) + " " + d.formattedBody - color: TimelineManager.userColor(d.userId, palette.base) - body: d.body - isOnlyEmoji: d.isOnlyEmoji - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - metadataWidth: d.metadataWidth - } - - } - - DelegateChoice { - roleValue: MtxEvent.ImageMessage - - ImageMessage { - type: d.type - originalWidth: d.originalWidth - proportionalHeight: d.proportionalHeight - url: d.url - blurhash: d.blurhash - body: d.body - filename: d.filename - isReply: d.isReply - eventId: d.eventId - metadataWidth: d.metadataWidth - containerHeight: timelineView.height - } - - } - - DelegateChoice { - roleValue: MtxEvent.Sticker - - ImageMessage { - type: d.type - originalWidth: d.originalWidth - proportionalHeight: d.proportionalHeight - url: d.url - blurhash: d.blurhash - body: d.body - filename: d.filename - isReply: d.isReply - eventId: d.eventId - metadataWidth: d.metadataWidth - containerHeight: timelineView.height - } - - } - - DelegateChoice { - roleValue: MtxEvent.FileMessage - - FileMessage { - eventId: d.eventId - filename: d.filename - filesize: d.filesize - metadataWidth: d.metadataWidth - } - - } - - DelegateChoice { - roleValue: MtxEvent.VideoMessage - - PlayableMediaMessage { - proportionalHeight: d.proportionalHeight - type: d.type - originalWidth: d.originalWidth - thumbnailUrl: d.thumbnailUrl - eventId: d.eventId - url: d.url - body: d.body - filesize: d.filesize - duration: d.duration - metadataWidth: d.metadataWidth - } - - } - - DelegateChoice { - roleValue: MtxEvent.AudioMessage - - PlayableMediaMessage { - proportionalHeight: d.proportionalHeight - type: d.type - originalWidth: d.originalWidth - thumbnailUrl: d.thumbnailUrl - eventId: d.eventId - url: d.url - body: d.body - filesize: d.filesize - duration: d.duration - metadataWidth: d.metadataWidth - } - - } - - DelegateChoice { - roleValue: MtxEvent.Redacted - - Redacted { - metadataWidth: d.metadataWidth - } - } - - DelegateChoice { - roleValue: MtxEvent.Redaction - - Pill { - text: qsTr("%1 removed a message").arg(d.userName) - isStateEvent: d.isStateEvent - } - - } - - DelegateChoice { - roleValue: MtxEvent.Encryption - - EncryptionEnabled { - username: d.userName - } - - } - - DelegateChoice { - roleValue: MtxEvent.Encrypted - - Encrypted { - encryptionError: d.encryptionError - eventId: d.eventId - } - - } - - DelegateChoice { - roleValue: MtxEvent.ServerAcl - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed which servers are allowed in this room.").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.Name - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.roomName ? qsTr("%2 changed the room name to: %1").arg(d.roomName).arg(d.userName) : qsTr("%1 removed the room name").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.Topic - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.roomTopic ? qsTr("%2 changed the topic to: %1").arg(d.roomTopic).arg(d.userName): qsTr("%1 removed the topic").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.Avatar - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed the room avatar").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.PinnedEvents - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed the pinned messages.").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.ImagePackInRoom - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatImagePackEvent(d.eventId) - } - - } - - - DelegateChoice { - roleValue: MtxEvent.CanonicalAlias - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed the addresses for this room.").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.SpaceParent - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 changed the parent communities for this room.").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.RoomCreate - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId) - } - - } - - DelegateChoice { - roleValue: MtxEvent.CallInvite - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: { - switch (d.callType) { - case "voice": - return qsTr("%1 placed a voice call.").arg(d.userName); - case "video": - return qsTr("%1 placed a video call.").arg(d.userName); - default: - return qsTr("%1 placed a call.").arg(d.userName); - } - } - } - - } - - DelegateChoice { - roleValue: MtxEvent.CallAnswer - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 answered the call.").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.CallReject - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 rejected the call.").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.CallSelectAnswer - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 select answer").arg(d.userName) - // formatted: qsTr("Call answered elsewhere") - } - } - - DelegateChoice { - roleValue: MtxEvent.CallHangUp - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 ended the call.").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.CallCandidates - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 is negotiating the call...").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.CallNegotiate - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: qsTr("%1 is negotiating the call...").arg(d.userName) - } - - } - - DelegateChoice { - roleValue: MtxEvent.PowerLevels - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId) - } - - } - - DelegateChoice { - roleValue: MtxEvent.PolicyRuleUser - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatPolicyRule(d.eventId) - } - - } - - DelegateChoice { - roleValue: MtxEvent.PolicyRuleRoom - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatPolicyRule(d.eventId) - } - - } - - DelegateChoice { - roleValue: MtxEvent.PolicyRuleServer - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatPolicyRule(d.eventId) - } - - } - - DelegateChoice { - roleValue: MtxEvent.RoomJoinRules - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId) - } - - } - - DelegateChoice { - roleValue: MtxEvent.RoomHistoryVisibility - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId) - } - - } - - DelegateChoice { - roleValue: MtxEvent.RoomGuestAccess - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId) - } - - } - - DelegateChoice { - roleValue: MtxEvent.Member - - ColumnLayout { - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - Layout.fillWidth: true - formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId) - } - - Button { - visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId) - Layout.alignment: Qt.AlignHCenter - text: qsTr("Allow them in") - onClicked: room.acceptKnock(eventId) - } - - } - - } - - DelegateChoice { - roleValue: MtxEvent.KeyVerificationRequest - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: "KeyVerificationRequest" - } - - } - - DelegateChoice { - roleValue: MtxEvent.KeyVerificationStart - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: "KeyVerificationStart" - } - - } - - DelegateChoice { - roleValue: MtxEvent.KeyVerificationReady - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: "KeyVerificationReady" - } - - } - - DelegateChoice { - roleValue: MtxEvent.KeyVerificationCancel - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: "KeyVerificationCancel" - } - - } - - DelegateChoice { - roleValue: MtxEvent.KeyVerificationKey - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: "KeyVerificationKey" - } - - } - - DelegateChoice { - roleValue: MtxEvent.KeyVerificationMac - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: "KeyVerificationMac" - } - - } - - DelegateChoice { - roleValue: MtxEvent.KeyVerificationDone - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: "KeyVerificationDone" - } - - } - - DelegateChoice { - roleValue: MtxEvent.KeyVerificationDone - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: "KeyVerificationDone" - } - - } - - DelegateChoice { - roleValue: MtxEvent.KeyVerificationAccept - - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - keepFullText: d.keepFullText - isStateEvent: d.isStateEvent - formatted: "KeyVerificationAccept" - } - - } - - DelegateChoice { - Placeholder { - typeString: d.typeString - } - - } - - } - -} diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index fb7bf0cc..ac4a82b0 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -22,7 +22,7 @@ Item { required property string url required property string body required property string filesize - property double divisor: isReply ? 4 : 2 + property double divisor: EventDelegateChooser.isReply ? 5 : 3 property int tempWidth: originalWidth < 1? 400: originalWidth implicitWidth: type == MtxEvent.VideoMessage ? Math.round(tempWidth*Math.min((timelineView.height/divisor)/(tempWidth*proportionalHeight), 1)) : 500 width: Math.min(parent?.width ?? implicitWidth, implicitWidth) diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index 52cc982d..1598e8c0 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -48,51 +48,43 @@ AbstractButton { room: room_ eventId: r.eventId replyTo: "" + mainInset: 4 + Nheko.paddingMedium + maxWidth: r.maxWidth //height: replyContainer.implicitHeight - data: GridLayout { + data: Row { id: replyContainer - width: r.maxWidth - columns: 2 - rows: 2 - columnSpacing: Nheko.paddingMedium - rowSpacing: Nheko.paddingSmall + spacing: Nheko.paddingSmall Rectangle { id: colorline - Layout.preferredWidth: 4 - Layout.rowSpan: 2 - Layout.fillHeight: true - - Layout.row: 0 - Layout.column: 0 + width: 4 color: TimelineManager.userColor(r.userId, palette.base) } - AbstractButton { - id: usernameBtn - Layout.fillWidth: true + Column { + spacing: 0 - Layout.row: 0 - Layout.column: 1 + AbstractButton { + id: usernameBtn - contentItem: ElidedLabel { - id: userName_ - fullText: r.userName - color: r.userColor - textFormat: Text.RichText - width: parent.width - elideWidth: width + contentItem: Label { + id: userName_ + text: r.userName + color: r.userColor + textFormat: Text.RichText + width: timelineEvent.main?.width + } + onClicked: room.openUserProfile(r.userId) } - onClicked: room.openUserProfile(r.userId) - } - data: [ - colorline, usernameBtn, timelineEvent.main, - ] + data: [ + usernameBtn, timelineEvent.main, + ] + } } } diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index 03623924..e9254eed 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -10,7 +10,7 @@ import im.nheko MatrixText { required property string body required property bool isOnlyEmoji - required property bool isReply + property bool isReply: EventDelegateChooser.isReply required property bool keepFullText required property string formatted @@ -45,7 +45,6 @@ MatrixText { //EventDelegateChooser.fillWidth: true - clip: !keepFullText selectByMouse: !Settings.mobileMode && !isReply enabled: !Settings.mobileMode && !isReply hoverEnabled: !Settings.mobileMode diff --git a/src/timeline/EventDelegateChooser.cpp b/src/timeline/EventDelegateChooser.cpp index 0060907d..a8629b3e 100644 --- a/src/timeline/EventDelegateChooser.cpp +++ b/src/timeline/EventDelegateChooser.cpp @@ -97,6 +97,7 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) return; item->setParentItem(&chooser); + item->setParent(&chooser); auto roleNames = chooser.room_->roleNames(); QHash<QByteArray, int> nameToRole; @@ -106,8 +107,6 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) QHash<int, int> roleToPropIdx; std::vector<QModelRoleData> roles; - bool isReplyNeeded = false; - // Workaround for https://bugreports.qt.io/browse/QTBUG-98846 QHash<QString, RequiredPropertyKey> requiredProperties; for (const auto &[propKey, prop] : @@ -124,10 +123,7 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) if (!prop.isRequired() && !requiredProperties.contains(prop.name())) continue; - if (prop.name() == std::string_view("isReply")) { - isReplyNeeded = true; - roleToPropIdx.insert(-1, i); - } else if (auto role = nameToRole.find(prop.name()); role != nameToRole.end()) { + if (auto role = nameToRole.find(prop.name()); role != nameToRole.end()) { roleToPropIdx.insert(*role, i); roles.emplace_back(*role); @@ -141,6 +137,11 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) chooser.room_->multiData(currentId, forReply ? chooser.eventId_ : QString(), roles); Qt::beginPropertyUpdateGroup(); + auto attached = qobject_cast<EventDelegateChooserAttachedType *>( + qmlAttachedPropertiesObject<EventDelegateChooser>(obj)); + Q_ASSERT(attached != nullptr); + attached->setIsReply(this->forReply); + for (const auto &role : roles) { const auto &roleName = roleNames[role.role()]; // nhlog::ui()->critical("Setting role {}, {} to {}", @@ -155,22 +156,25 @@ EventDelegateChooser::DelegateIncubator::setInitialState(QObject *obj) QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req); } - if (isReplyNeeded) { - const auto roleName = QByteArray("isReply"); - // nhlog::ui()->critical("Setting role {} to {}", roleName.toStdString(), forReply); - - // nhlog::ui()->critical("Setting {}", mo->property(roleToPropIdx[-1]).name()); - mo->property(roleToPropIdx[-1]).write(obj, forReply); - - if (const auto &req = requiredProperties.find(roleName); req != requiredProperties.end()) - QQmlIncubatorPrivate::get(this)->requiredProperties()->remove(*req); - } Qt::endPropertyUpdateGroup(); // setInitialProperties(rolesToSet); auto update = [this, obj, roleToPropIdx = std::move(roleToPropIdx)](const QList<int> &changedRoles) { + if (changedRoles.empty() || changedRoles.contains(TimelineModel::Roles::Type)) { + int type = chooser.room_ + ->dataById(currentId, + TimelineModel::Roles::Type, + forReply ? chooser.eventId_ : QString()) + .toInt(); + if (type != oldType) { + nhlog::ui()->debug("Type changed!"); + reset(currentId); + return; + } + } + std::vector<QModelRoleData> rolesToRequest; if (changedRoles.empty()) { @@ -233,6 +237,7 @@ EventDelegateChooser::DelegateIncubator::reset(QString id) chooser.room_ ->dataById(id, TimelineModel::Roles::Type, forReply ? chooser.eventId_ : QString()) .toInt(); + this->oldType = role; for (const auto choice : qAsConst(chooser.choices_)) { const auto &choiceValue = choice->roleValues(); @@ -293,41 +298,58 @@ EventDelegateChooser::updatePolish() nhlog::ui()->critical("POLISHING {}", (void *)this); - if (mainChild) { - auto attached = qobject_cast<EventDelegateChooserAttachedType *>( - qmlAttachedPropertiesObject<EventDelegateChooser>(mainChild)); - Q_ASSERT(attached != nullptr); - - // in theory we could also reset the width, but that doesn't seem to work nicely for text - // areas because of how they cache it. - mainChild->setWidth(maxWidth_); - mainChild->ensurePolished(); - auto width = mainChild->implicitWidth(); - - if (width > maxWidth_ || attached->fillWidth()) - width = maxWidth_; - - nhlog::ui()->debug( - "Made event delegate width: {}, {}", width, mainChild->metaObject()->className()); - mainChild->setWidth(width); - mainChild->ensurePolished(); - } - - if (replyChild) { - auto attached = qobject_cast<EventDelegateChooserAttachedType *>( - qmlAttachedPropertiesObject<EventDelegateChooser>(replyChild)); - Q_ASSERT(attached != nullptr); + auto layoutItem = [this](QQuickItem *item, int inset) { + if (item) { + auto attached = qobject_cast<EventDelegateChooserAttachedType *>( + qmlAttachedPropertiesObject<EventDelegateChooser>(item)); + Q_ASSERT(attached != nullptr); + + int maxWidth = maxWidth_ - inset; + + // in theory we could also reset the width, but that doesn't seem to work nicely for + // text areas because of how they cache it. + if (attached->maxWidth() > 0) + item->setWidth(attached->maxWidth()); + else + item->setWidth(maxWidth); + item->ensurePolished(); + auto width = item->implicitWidth(); + + if (width < 1 || width > maxWidth) + width = maxWidth; + + if (attached->maxWidth() > 0 && width > attached->maxWidth()) + width = attached->maxWidth(); + + if (attached->keepAspectRatio()) { + auto height = width * attached->aspectRatio(); + if (attached->maxHeight() && height > attached->maxHeight()) { + height = attached->maxHeight(); + width = height / attached->aspectRatio(); + } + + item->setHeight(height); + } - // in theory we could also reset the width, but that doesn't seem to work nicely for text - // areas because of how they cache it. - replyChild->setWidth(maxWidth_); - replyChild->ensurePolished(); - auto width = replyChild->implicitWidth(); + nhlog::ui()->debug( + "Made event delegate width: {}, {}", width, item->metaObject()->className()); + item->setWidth(width); + item->ensurePolished(); + } + }; - if (width > maxWidth_ || attached->fillWidth()) - width = maxWidth_; + layoutItem(mainChild, mainInset_); + layoutItem(replyChild, replyInset_); +} - replyChild->setWidth(width); - replyChild->ensurePolished(); +void +EventDelegateChooserAttachedType::polishChooser() +{ + auto p = parent(); + if (p) { + auto chooser = qobject_cast<EventDelegateChooser *>(p->parent()); + if (chooser) { + chooser->polish(); + } } } diff --git a/src/timeline/EventDelegateChooser.h b/src/timeline/EventDelegateChooser.h index ff67ccd8..ce79444a 100644 --- a/src/timeline/EventDelegateChooser.h +++ b/src/timeline/EventDelegateChooser.h @@ -17,27 +17,78 @@ class EventDelegateChooserAttachedType : public QObject { Q_OBJECT - Q_PROPERTY(bool fillWidth READ fillWidth WRITE setFillWidth NOTIFY fillWidthChanged) + Q_PROPERTY(bool keepAspectRatio READ keepAspectRatio WRITE setKeepAspectRatio NOTIFY + keepAspectRatioChanged) + Q_PROPERTY(double aspectRatio READ aspectRatio WRITE setAspectRatio NOTIFY aspectRatioChanged) + Q_PROPERTY(int maxWidth READ maxWidth WRITE setMaxWidth NOTIFY maxWidthChanged) + Q_PROPERTY(int maxHeight READ maxHeight WRITE setMaxHeight NOTIFY maxHeightChanged) + Q_PROPERTY(bool isReply READ isReply WRITE setIsReply NOTIFY isReplyChanged) + QML_ANONYMOUS public: EventDelegateChooserAttachedType(QObject *parent) : QObject(parent) { } - bool fillWidth() const { return fillWidth_; } - void setFillWidth(bool fill) + + bool keepAspectRatio() const { return keepAspectRatio_; } + void setKeepAspectRatio(bool fill) + { + if (fill != keepAspectRatio_) { + keepAspectRatio_ = fill; + emit keepAspectRatioChanged(); + polishChooser(); + } + } + + double aspectRatio() const { return aspectRatio_; } + void setAspectRatio(double fill) { - fillWidth_ = fill; - emit fillWidthChanged(); + aspectRatio_ = fill; + emit aspectRatioChanged(); + polishChooser(); } + + int maxWidth() const { return maxWidth_; } + void setMaxWidth(int fill) + { + maxWidth_ = fill; + emit maxWidthChanged(); + polishChooser(); + } + + int maxHeight() const { return maxHeight_; } + void setMaxHeight(int fill) + { + maxHeight_ = fill; + emit maxHeightChanged(); + } + + bool isReply() const { return isReply_; } + void setIsReply(bool fill) + { + if (fill != isReply_) { + isReply_ = fill; + emit isReplyChanged(); + polishChooser(); + } + } + signals: - void fillWidthChanged(); + void keepAspectRatioChanged(); + void aspectRatioChanged(); + void maxWidthChanged(); + void maxHeightChanged(); + void isReplyChanged(); private: - bool fillWidth_ = false, keepAspectRatio = false; - double aspectRatio = 1.; - int maxWidth = -1; - int maxHeight = -1; + void polishChooser(); + + double aspectRatio_ = 1.; + int maxWidth_ = -1; + int maxHeight_ = -1; + bool keepAspectRatio_ = false; + bool isReply_ = false; }; class EventDelegateChoice : public QObject @@ -84,6 +135,8 @@ class EventDelegateChooser : public QQuickItem Q_PROPERTY(TimelineModel *room READ room WRITE setRoom NOTIFY roomChanged REQUIRED FINAL) Q_PROPERTY(bool sameWidth READ sameWidth WRITE setSameWidth NOTIFY sameWidthChanged) Q_PROPERTY(int maxWidth READ maxWidth WRITE setMaxWidth NOTIFY maxWidthChanged) + Q_PROPERTY(int replyInset READ replyInset WRITE setReplyInset NOTIFY replyInsetChanged) + Q_PROPERTY(int mainInset READ mainInset WRITE setMainInset NOTIFY mainInsetChanged) public: QQmlListProperty<EventDelegateChoice> choices(); @@ -103,7 +156,7 @@ public: sameWidth_ = width; emit sameWidthChanged(); } - bool maxWidth() const { return maxWidth_; } + int maxWidth() const { return maxWidth_; } void setMaxWidth(int width) { maxWidth_ = width; @@ -111,6 +164,22 @@ public: polish(); } + int replyInset() const { return replyInset_; } + void setReplyInset(int width) + { + replyInset_ = width; + emit replyInsetChanged(); + polish(); + } + + int mainInset() const { return mainInset_; } + void setMainInset(int width) + { + mainInset_ = width; + emit mainInsetChanged(); + polish(); + } + void setRoom(TimelineModel *m) { if (m != room_) { @@ -161,6 +230,8 @@ signals: void replyToChanged(); void sameWidthChanged(); void maxWidthChanged(); + void replyInsetChanged(); + void mainInsetChanged(); private: struct DelegateIncubator final : public QQmlIncubator @@ -183,6 +254,7 @@ private: QString instantiatedId; int instantiatedRole = -1; QAbstractItemModel *instantiatedModel = nullptr; + int oldType = -1; }; QVariant roleValue_; @@ -194,6 +266,8 @@ private: QString replyId; bool sameWidth_ = false; int maxWidth_ = 400; + int replyInset_ = 0; + int mainInset_ = 0; static void appendChoice(QQmlListProperty<EventDelegateChoice> *, EventDelegateChoice *); static qsizetype choiceCount(QQmlListProperty<EventDelegateChoice> *); |