summary refs log tree commit diff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--resources/qml/TimelineRow.qml61
-rw-r--r--resources/qml/delegates/FileMessage.qml6
-rw-r--r--resources/qml/delegates/ImageMessage.qml12
-rw-r--r--resources/qml/delegates/MessageDelegate.qml128
-rw-r--r--resources/qml/delegates/NoticeMessage.qml2
-rw-r--r--resources/qml/delegates/Placeholder.qml2
-rw-r--r--resources/qml/delegates/PlayableMediaMessage.qml16
-rw-r--r--resources/qml/delegates/TextMessage.qml2
-rw-r--r--src/timeline/TimelineModel.cpp16
-rw-r--r--src/timeline/TimelineModel.h1
10 files changed, 159 insertions, 87 deletions
diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml
index 2c2ed02a..86780413 100644
--- a/resources/qml/TimelineRow.qml
+++ b/resources/qml/TimelineRow.qml
@@ -14,23 +14,70 @@ RowLayout {
 	anchors.left: parent.left
 	anchors.right: parent.right
 
-	height: Math.max(contentItem.height, 16)
+	//height: Math.max(model.replyTo ? reply.height + contentItem.height + 4 : contentItem.height, 16)
 
 	Column {
 		Layout.fillWidth: true
 		Layout.alignment: Qt.AlignTop
+		spacing: 4
 
-		//property var replyTo: model.replyTo
+		// fancy reply, if this is a reply
+		Rectangle {
+			visible: model.replyTo
+			width: parent.width
+			height: replyContainer.height
+
+			Rectangle {
+				id: colorLine
+				height: replyContainer.height
+				width: 4
+				color: chat.model.userColor(reply.modelData.userId, colors.window)
+			}
+
+			Column {
+				id: replyContainer
+				anchors.left: colorLine.right
+				anchors.leftMargin: 4
+				width: parent.width - 8
+
+
+				Text { 
+					id: userName
+					text: chat.model.escapeEmoji(reply.modelData.userName)
+					color: chat.model.userColor(reply.modelData.userId, colors.window)
+					textFormat: Text.RichText
+
+					MouseArea {
+						anchors.fill: parent
+						onClicked: chat.model.openUserProfile(reply.modelData.userId)
+						cursorShape: Qt.PointingHandCursor
+					}
+				}
+
+				MessageDelegate {
+					id: reply
+					width: parent.width
 
-		//Text {
-		//	property int idx: timelineManager.timeline.idToIndex(replyTo)
-		//	text: "" + (idx != -1 ? timelineManager.timeline.data(timelineManager.timeline.index(idx, 0), 2) : "nothing")
-		//}
+					modelData: chat.model.getDump(model.replyTo)
+				}
+			}
+
+			color: { var col = chat.model.userColor(reply.modelData.userId, colors.window); col.a = 0.2; return col }
+
+			MouseArea {
+				anchors.fill: parent
+				onClicked: chat.positionViewAtIndex(chat.model.idToIndex(model.replyTo), ListView.Contain)
+				cursorShape: Qt.PointingHandCursor
+			}
+		}
+
+		// actual message content
 		MessageDelegate {
 			id: contentItem
 
 			width: parent.width
-			height: childrenRect.height
+
+			modelData: model
 		}
 	}
 
diff --git a/resources/qml/delegates/FileMessage.qml b/resources/qml/delegates/FileMessage.qml
index 2c911c5e..9a5300bb 100644
--- a/resources/qml/delegates/FileMessage.qml
+++ b/resources/qml/delegates/FileMessage.qml
@@ -31,7 +31,7 @@ Rectangle {
 			}
 			MouseArea {
 				anchors.fill: parent
-				onClicked: timelineManager.timeline.saveMedia(model.id)
+				onClicked: timelineManager.timeline.saveMedia(model.data.id)
 				cursorShape: Qt.PointingHandCursor
 			}
 		}
@@ -40,14 +40,14 @@ Rectangle {
 
 			Text {
 				Layout.fillWidth: true
-				text: model.body
+				text: model.data.body
 				textFormat: Text.PlainText
 				elide: Text.ElideRight
 				color: colors.text
 			}
 			Text {
 				Layout.fillWidth: true
-				text: model.filesize
+				text: model.data.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 15ce29b7..3393f043 100644
--- a/resources/qml/delegates/ImageMessage.qml
+++ b/resources/qml/delegates/ImageMessage.qml
@@ -3,26 +3,26 @@ import QtQuick 2.6
 import im.nheko 1.0
 
 Item {
-	property double tempWidth: Math.min(parent ? parent.width : undefined, model.width)
-	property double tempHeight: tempWidth * model.proportionalHeight
+	property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width)
+	property double tempHeight: tempWidth * model.data.proportionalHeight
 
 	property bool tooHigh: tempHeight > chat.height - 40
 
 	height: tooHigh ? chat.height - 40 : tempHeight
-	width: tooHigh ? (chat.height - 40) / model.proportionalHeight : tempWidth
+	width: tooHigh ? (chat.height - 40) / model.data.proportionalHeight : tempWidth
 
 	Image {
 		id: img
 		anchors.fill: parent
 
-		source: model.url.replace("mxc://", "image://MxcImage/")
+		source: model.data.url.replace("mxc://", "image://MxcImage/")
 		asynchronous: true
 		fillMode: Image.PreserveAspectFit
 
 		MouseArea {
-			enabled: model.type == MtxEvent.ImageMessage
+			enabled: model.data.type == MtxEvent.ImageMessage
 			anchors.fill: parent
-			onClicked: timelineManager.openImageOverlay(model.url, model.id)
+			onClicked: timelineManager.openImageOverlay(model.data.url, model.data.id)
 		}
 	}
 }
diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml
index 20ec71e5..1716d2d4 100644
--- a/resources/qml/delegates/MessageDelegate.qml
+++ b/resources/qml/delegates/MessageDelegate.qml
@@ -1,67 +1,81 @@
 import QtQuick 2.6
 import im.nheko 1.0
 
-DelegateChooser {
-	//role: "type" //< not supported in our custom implementation, have to use roleValue
-	roleValue: model.type
-
-	DelegateChoice {
-		roleValue: MtxEvent.TextMessage
-		TextMessage {}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.NoticeMessage
-		NoticeMessage {}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.EmoteMessage
-		TextMessage {}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.ImageMessage
-		ImageMessage {}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.Sticker
-		ImageMessage {}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.FileMessage
-		FileMessage {}
+Item {
+	// Workaround to have an assignable global property
+	Item {
+		id: model
+		property var data;
 	}
-	DelegateChoice {
-		roleValue: MtxEvent.VideoMessage
-		PlayableMediaMessage {}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.AudioMessage
-		PlayableMediaMessage {}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.Redacted
-		Pill {
-			text: qsTr("redacted")
+	
+	property alias modelData: model.data
+
+	height: chooser.childrenRect.height
+
+	DelegateChooser {
+		id: chooser
+		//role: "type" //< not supported in our custom implementation, have to use roleValue
+		roleValue: model.data.type
+		anchors.fill: parent
+
+		DelegateChoice {
+			roleValue: MtxEvent.TextMessage
+			TextMessage {}
 		}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.Encryption
-		Pill {
-			text: qsTr("Encryption enabled")
+		DelegateChoice {
+			roleValue: MtxEvent.NoticeMessage
+			NoticeMessage {}
 		}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.Name
-		NoticeMessage {
-			notice: model.roomName ? qsTr("room name changed to: %1").arg(model.roomName) : qsTr("removed room name")
+		DelegateChoice {
+			roleValue: MtxEvent.EmoteMessage
+			TextMessage {}
 		}
-	}
-	DelegateChoice {
-		roleValue: MtxEvent.Topic
-		NoticeMessage {
-			notice: model.roomTopic ? qsTr("topic changed to: %1").arg(model.roomTopic) : qsTr("removed topic")
+		DelegateChoice {
+			roleValue: MtxEvent.ImageMessage
+			ImageMessage {}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.Sticker
+			ImageMessage {}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.FileMessage
+			FileMessage {}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.VideoMessage
+			PlayableMediaMessage {}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.AudioMessage
+			PlayableMediaMessage {}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.Redacted
+			Pill {
+				text: qsTr("redacted")
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.Encryption
+			Pill {
+				text: qsTr("Encryption enabled")
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.Name
+			NoticeMessage {
+				notice: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name")
+			}
+		}
+		DelegateChoice {
+			roleValue: MtxEvent.Topic
+			NoticeMessage {
+				notice: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
+			}
+		}
+		DelegateChoice {
+			Placeholder {}
 		}
-	}
-	DelegateChoice {
-		Placeholder {}
 	}
 }
diff --git a/resources/qml/delegates/NoticeMessage.qml b/resources/qml/delegates/NoticeMessage.qml
index f7467eca..34132bcf 100644
--- a/resources/qml/delegates/NoticeMessage.qml
+++ b/resources/qml/delegates/NoticeMessage.qml
@@ -1,7 +1,7 @@
 import ".."
 
 MatrixText {
-	property string notice: model.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
+	property string notice: model.data.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
 	text: notice
 	width: parent ? parent.width : undefined
 	font.italic: true
diff --git a/resources/qml/delegates/Placeholder.qml b/resources/qml/delegates/Placeholder.qml
index 4c0e68c3..36d7b2bc 100644
--- a/resources/qml/delegates/Placeholder.qml
+++ b/resources/qml/delegates/Placeholder.qml
@@ -1,7 +1,7 @@
 import ".."
 
 MatrixText {
-	text: qsTr("unimplemented event: ") + model.type
+	text: qsTr("unimplemented event: ") + model.data.type
 	width: parent ? parent.width : undefined
 	color: inactiveColors.text
 }
diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml
index b3275462..ebf7487c 100644
--- a/resources/qml/delegates/PlayableMediaMessage.qml
+++ b/resources/qml/delegates/PlayableMediaMessage.qml
@@ -19,12 +19,12 @@ Rectangle {
 
 		Rectangle {
 			id: videoContainer
-			visible: model.type == MtxEvent.VideoMessage
-			width: Math.min(parent.width, model.width ? model.width : 400) // some media has 0 as size...
-			height: width*model.proportionalHeight
+			visible: model.data.type == MtxEvent.VideoMessage
+			width: Math.min(parent.width, model.data.width ? model.data.width : 400) // some media has 0 as size...
+			height: width*model.data.proportionalHeight
 			Image {
 				anchors.fill: parent
-				source: model.thumbnailUrl.replace("mxc://", "image://MxcImage/")
+				source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/")
 				asynchronous: true
 				fillMode: Image.PreserveAspectFit
 
@@ -97,7 +97,7 @@ Rectangle {
 					anchors.fill: parent
 					onClicked: {
 						switch (button.state) {
-							case "": timelineManager.timeline.cacheMedia(model.id); break;
+							case "": timelineManager.timeline.cacheMedia(model.data.id); break;
 							case "stopped":
 							media.play(); console.log("play");
 							button.state = "playing"
@@ -120,7 +120,7 @@ Rectangle {
 				Connections {
 					target: timelineManager.timeline
 					onMediaCached: {
-						if (mxcUrl == model.url) {
+						if (mxcUrl == model.data.url) {
 							media.source = "file://" + cacheUrl
 							button.state = "stopped"
 							console.log("media loaded: " + mxcUrl + " at " + cacheUrl)
@@ -145,14 +145,14 @@ Rectangle {
 
 				Text {
 					Layout.fillWidth: true
-					text: model.body
+					text: model.data.body
 					textFormat: Text.PlainText
 					elide: Text.ElideRight
 					color: colors.text
 				}
 				Text {
 					Layout.fillWidth: true
-					text: model.filesize
+					text: model.data.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 f984b32f..92ba560b 100644
--- a/resources/qml/delegates/TextMessage.qml
+++ b/resources/qml/delegates/TextMessage.qml
@@ -1,6 +1,6 @@
 import ".."
 
 MatrixText {
-	text: model.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
+	text: model.data.formattedBody.replace("<pre>", "<pre style='white-space: pre-wrap'>")
 	width: parent ? parent.width : undefined
 }
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 0e7f5259..41d864bd 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -212,6 +212,14 @@ TimelineModel::rowCount(const QModelIndex &parent) const
         return (int)this->eventOrder.size();
 }
 
+QVariantMap
+TimelineModel::getDump(QString eventId) const
+{
+        if (events.contains(eventId))
+                return data(index(idToIndex(eventId), 0), Dump).toMap();
+        return {};
+}
+
 QVariant
 TimelineModel::data(const QModelIndex &index, int role) const
 {
@@ -263,11 +271,13 @@ TimelineModel::data(const QModelIndex &index, int role) const
                 return QVariant(toRoomEventType(event));
         case Body:
                 return QVariant(utils::replaceEmoji(QString::fromStdString(body(event))));
-        case FormattedBody:
+        case FormattedBody: {
+                const static QRegularExpression replyFallback(
+                  "<mx-reply>.*</mx-reply>", QRegularExpression::DotMatchesEverythingOption);
                 return QVariant(
                   utils::replaceEmoji(utils::linkifyMessage(formattedBodyWithFallback(event)))
-                    .remove("<mx-reply>")
-                    .remove("</mx-reply>"));
+                    .remove(replyFallback));
+        }
         case Url:
                 return QVariant(QString::fromStdString(url(event)));
         case ThumbnailUrl:
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 0f18f7ef..61dd6b69 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -181,6 +181,7 @@ public slots:
         void setCurrentIndex(int index);
         int currentIndex() const { return idToIndex(currentId); }
         void markEventsAsRead(const std::vector<QString> &event_ids);
+        QVariantMap getDump(QString eventId) const;
 
 private slots:
         // Add old events at the top of the timeline.