# HG changeset patch
# Node ID f8d57c8b2f7c
# Parent  cc9e9b7c040a
diff --git a/pidgin/gtkconv.c b/pidgin/gtkconv.c
--- a/pidgin/gtkconv.c
+++ b/pidgin/gtkconv.c
@@ -39,7 +39,6 @@
 #include "pidgincore.h"
 #include "pidgindisplaywindow.h"
 #include "pidgininfopane.h"
-#include "pidginmessage.h"
 
 /* Prototypes. <-- because Paco-Paco hates this comment. */
 gboolean pidgin_conv_has_focus(PurpleConversation *conv);
@@ -400,26 +399,6 @@
 	pidgin_display_window_add(PIDGIN_DISPLAY_WINDOW(window), conv);
 }
 
-static void
-conversation_write(PurpleConversation *conv, PurpleMessage *pmsg) {
-	GtkWidget *conversation = NULL;
-
-	conversation = pidgin_conversation_from_purple_conversation(conv);
-	if(PIDGIN_IS_CONVERSATION(conversation)) {
-		pidgin_conversation_write_message(PIDGIN_CONVERSATION(conversation),
-		                                  pmsg);
-	}
-}
-
-static void
-pidgin_conv_new(PurpleConversation *conv)
-{
-	private_gtkconv_new(conv, FALSE);
-	if (PIDGIN_IS_PIDGIN_CONVERSATION_OLD(conv))
-		purple_signal_emit(pidgin_conversations_get_handle(),
-				"conversation-displayed", PIDGIN_CONVERSATION_OLD(conv));
-}
-
 void
 pidgin_conversation_detach(PurpleConversation *conv) {
 	if(PIDGIN_IS_PIDGIN_CONVERSATION_OLD(conv)) {
@@ -508,46 +487,6 @@
 	return TRUE;
 }
 
-static void
-pidgin_conv_write_conv(PurpleConversation *conv, PurpleMessage *pmsg)
-{
-	PidginMessage *pidgin_msg = NULL;
-	PurpleMessageFlags flags;
-	PidginConversationOld *gtkconv;
-	PurpleConnection *gc;
-	PurpleAccount *account;
-	gboolean plugin_return;
-
-	g_return_if_fail(conv != NULL);
-	gtkconv = PIDGIN_CONVERSATION_OLD(conv);
-	g_return_if_fail(gtkconv != NULL);
-	flags = purple_message_get_flags(pmsg);
-
-	account = purple_conversation_get_account(conv);
-	g_return_if_fail(account != NULL);
-	gc = purple_account_get_connection(account);
-	g_return_if_fail(gc != NULL || !(flags & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)));
-
-	plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1(
-		pidgin_conversations_get_handle(),
-		(PURPLE_IS_IM_CONVERSATION(conv) ? "displaying-im-msg" : "displaying-chat-msg"),
-		conv, pmsg));
-	if (plugin_return)
-	{
-		return;
-	}
-
-	pidgin_msg = pidgin_message_new(pmsg);
-	talkatu_history_write_message(
-		TALKATU_HISTORY(gtkconv->history),
-		TALKATU_MESSAGE(pidgin_msg)
-	);
-
-	purple_signal_emit(pidgin_conversations_get_handle(),
-		(PURPLE_IS_IM_CONVERSATION(conv) ? "displayed-im-msg" : "displayed-chat-msg"),
-		conv, pmsg);
-}
-
 gboolean
 pidgin_conv_has_focus(PurpleConversation *conv)
 {
@@ -569,7 +508,6 @@
 static PurpleConversationUiOps conversation_ui_ops =
 {
 	.create_conversation = conversation_create,
-	.write_conv = conversation_write,
 
 	.destroy_conversation = pidgin_conv_destroy,
 	.has_focus = pidgin_conv_has_focus,
diff --git a/pidgin/meson.build b/pidgin/meson.build
--- a/pidgin/meson.build
+++ b/pidgin/meson.build
@@ -30,7 +30,6 @@
 	'pidginaddbuddydialog.c',
 	'pidginaddchatdialog.c',
 	'pidginapplication.c',
-	'pidginattachment.c',
 	'pidginavatar.c',
 	'pidgincolor.c',
 	'pidgincommands.c',
@@ -45,7 +44,6 @@
 	'pidgininfopane.c',
 	'pidgininvitedialog.c',
 	'pidginkeypad.c',
-	'pidginmessage.c',
 	'pidginnotificationaddcontact.c',
 	'pidginnotificationauthorizationrequest.c',
 	'pidginnotificationconnectionerror.c',
@@ -99,7 +97,6 @@
 	'pidginaddbuddydialog.h',
 	'pidginaddchatdialog.h',
 	'pidginapplication.h',
-	'pidginattachment.h',
 	'pidginavatar.h',
 	'pidgincolor.h',
 	'pidgincontactinfomenu.h',
@@ -114,7 +111,6 @@
 	'pidgininfopane.h',
 	'pidgininvitedialog.h',
 	'pidginkeypad.h',
-	'pidginmessage.h',
 	'pidginnotificationaddcontact.h',
 	'pidginnotificationauthorizationrequest.h',
 	'pidginnotificationconnectionerror.h',
diff --git a/pidgin/pidginattachment.h b/pidgin/pidginattachment.h
deleted file mode 100644
--- a/pidgin/pidginattachment.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * pidgin
- *
- * Pidgin is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
-# error "only <pidgin.h> may be included directly"
-#endif
-
-#ifndef PIDGIN_ATTACHMENT_H
-#define PIDGIN_ATTACHMENT_H
-
-#include <purple.h>
-
-#include <talkatu.h>
-
-G_BEGIN_DECLS
-
-#define PIDGIN_TYPE_ATTACHMENT (pidgin_attachment_get_type())
-G_DECLARE_FINAL_TYPE(PidginAttachment, pidgin_attachment, PIDGIN, ATTACHMENT, GObject)
-
-/**
- * pidgin_attachment_new:
- * @attachment: The #PurpleAttachment to wrap.
- *
- * Wraps @attachment so that it can be used as a #TalkatuAttachment.
- *
- * Returns: (transfer full): The new #PidginAttachment instance.
- */
-PidginAttachment *pidgin_attachment_new(PurpleAttachment *attachment);
-
-/**
- * pidgin_attachment_get_attachment:
- * @attachment: The #PidginAttachment instance.
- *
- * Gets the #PurpleAttachment that @attachment is wrapping.
- *
- * Returns: (transfer none): The #PurpleAttachment that @attachment is wrapping.
- */
-PurpleAttachment *pidgin_attachment_get_attachment(PidginAttachment *attachment);
-
-G_END_DECLS
-
-#endif /* PIDGIN_ATTACHMENT_H */
diff --git a/pidgin/pidginattachment.c b/pidgin/pidginattachment.c
deleted file mode 100644
--- a/pidgin/pidginattachment.c
+++ /dev/null
@@ -1,198 +0,0 @@
-/* pidgin
- *
- * Pidgin is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
- */
-
-#include "pidginattachment.h"
-
-struct _PidginAttachment {
-	GObject parent;
-
-	PurpleAttachment *attachment;
-};
-
-enum {
-	PROP_0,
-	PROP_ATTACHMENT,
-	N_PROPERTIES,
-	/* overrides */
-	PROP_ID = N_PROPERTIES,
-	PROP_CONTENT_TYPE,
-	PROP_LOCAL_URI,
-	PROP_REMOTE_URI,
-	PROP_SIZE,
-};
-static GParamSpec *properties[N_PROPERTIES] = {NULL, };
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static void
-pidgin_attachment_set_attachment(PidginAttachment *attachment,
-                                 PurpleAttachment *purple_attachment)
-{
-	if(g_set_object(&attachment->attachment, purple_attachment)) {
-		g_object_notify_by_pspec(G_OBJECT(attachment),
-		                         properties[PROP_ATTACHMENT]);
-	}
-}
-
-/******************************************************************************
- * TalkatuAttachment Implementation
- *****************************************************************************/
-static guint64 *
-pidgin_attachment_get_hash_key(TalkatuAttachment *attachment) {
-	PidginAttachment *wrapper = PIDGIN_ATTACHMENT(attachment);
-
-	return purple_attachment_get_hash_key(wrapper->attachment);
-}
-
-static void
-pidgin_attachment_talkatu_attachment_init(TalkatuAttachmentInterface *iface) {
-	iface->get_hash_key = pidgin_attachment_get_hash_key;
-}
-
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_TYPE_EXTENDED(
-	PidginAttachment,
-	pidgin_attachment,
-	G_TYPE_OBJECT,
-	0,
-	G_IMPLEMENT_INTERFACE(
-		TALKATU_TYPE_ATTACHMENT,
-	    pidgin_attachment_talkatu_attachment_init
-	)
-);
-
-static void
-pidgin_attachment_get_property(GObject *obj, guint param_id, GValue *value, GParamSpec *pspec) {
-	PidginAttachment *wrapper = PIDGIN_ATTACHMENT(obj);
-	PurpleAttachment *attachment = wrapper->attachment;
-
-	switch(param_id) {
-		case PROP_ATTACHMENT:
-			g_value_set_object(value, attachment);
-			break;
-		case PROP_ID:
-			g_value_set_uint(value, purple_attachment_get_id(attachment));
-			break;
-		case PROP_CONTENT_TYPE:
-			g_value_set_string(value,
-			                   purple_attachment_get_content_type(attachment));
-			break;
-		case PROP_LOCAL_URI:
-			g_value_set_string(value,
-			                   purple_attachment_get_local_uri(attachment));
-			break;
-		case PROP_REMOTE_URI:
-			g_value_set_string(value,
-			                   purple_attachment_get_remote_uri(attachment));
-			break;
-		case PROP_SIZE:
-			g_value_set_uint64(value,
-			                   purple_attachment_get_size(attachment));
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
-}
-
-static void
-pidgin_attachment_set_property(GObject *obj, guint param_id, const GValue *value, GParamSpec *pspec) {
-	PidginAttachment *attachment = PIDGIN_ATTACHMENT(obj);
-
-	switch(param_id) {
-		case PROP_ATTACHMENT:
-			pidgin_attachment_set_attachment(attachment,
-			                                 g_value_get_object(value));
-			break;
-		case PROP_ID:
-		case PROP_CONTENT_TYPE:
-		case PROP_LOCAL_URI:
-		case PROP_REMOTE_URI:
-		case PROP_SIZE:
-			/* we don't allow setting these, if you need to change them, use
-			 * the underlying PurpleAttachment.
-			 */
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
-}
-
-static void
-pidgin_attachment_init(G_GNUC_UNUSED PidginAttachment *attachment) {
-}
-
-static void
-pidgin_attachment_finalize(GObject *obj) {
-	PidginAttachment *attachment = PIDGIN_ATTACHMENT(obj);
-
-	g_clear_object(&attachment->attachment);
-
-	G_OBJECT_CLASS(pidgin_attachment_parent_class)->finalize(obj);
-}
-
-static void
-pidgin_attachment_class_init(PidginAttachmentClass *klass) {
-	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
-
-	obj_class->get_property = pidgin_attachment_get_property;
-	obj_class->set_property = pidgin_attachment_set_property;
-	obj_class->finalize = pidgin_attachment_finalize;
-
-	/* add our custom properties */
-	properties[PROP_ATTACHMENT] = g_param_spec_object(
-		"attachment", "attachment", "The purple attachment",
-		PURPLE_TYPE_MESSAGE,
-		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
-	);
-
-	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
-
-	/* add our overridden properties */
-	g_object_class_override_property(obj_class, PROP_ID, "id");
-	g_object_class_override_property(obj_class, PROP_CONTENT_TYPE, "content-type");
-	g_object_class_override_property(obj_class, PROP_LOCAL_URI, "local-uri");
-	g_object_class_override_property(obj_class, PROP_REMOTE_URI, "remote-uri");
-	g_object_class_override_property(obj_class, PROP_SIZE, "size");
-}
-
-/******************************************************************************
- * API
- *****************************************************************************/
-PidginAttachment *
-pidgin_attachment_new(PurpleAttachment *attachment) {
-	return g_object_new(
-		PIDGIN_TYPE_ATTACHMENT,
-		"attachment", attachment,
-		NULL
-	);
-}
-
-PurpleAttachment *
-pidgin_attachment_get_attachment(PidginAttachment *attachment) {
-	g_return_val_if_fail(PIDGIN_IS_ATTACHMENT(attachment), NULL);
-
-	return attachment->attachment;
-}
diff --git a/pidgin/pidginconversation.h b/pidgin/pidginconversation.h
--- a/pidgin/pidginconversation.h
+++ b/pidgin/pidginconversation.h
@@ -84,21 +84,6 @@
 PurpleConversation *pidgin_conversation_get_conversation(PidginConversation *conversation);
 
 /**
- * pidgin_conversation_write_message:
- * @conversation: The instance.
- * @message: The message to write.
- *
- * This is temporary until [class@Purple.Conversation] keeps track of its own
- * messages.
- *
- * Write @message to @conversation. This is only for display; if you need to
- * send a message you should use [method@Purple.Conversation.send].
- *
- * Since: 3.0.0
- */
-void pidgin_conversation_write_message(PidginConversation *conversation, PurpleMessage *message);
-
-/**
  * pidgin_conversation_close:
  * @conversation: The instance.
  *
diff --git a/pidgin/pidginconversation.c b/pidgin/pidginconversation.c
--- a/pidgin/pidginconversation.c
+++ b/pidgin/pidginconversation.c
@@ -24,9 +24,11 @@
 
 #include <purple.h>
 
-#include "pidgin/pidginconversation.h"
-#include "pidgin/pidgininfopane.h"
-#include "pidgin/pidginmessage.h"
+#include <talkatu.h>
+
+#include "pidgincolor.h"
+#include "pidginconversation.h"
+#include "pidgininfopane.h"
 
 #define PIDGIN_CONVERSATION_DATA ("pidgin-conversation")
 
@@ -57,11 +59,23 @@
                                      PurpleConversation *purple_conversation)
 {
 	if(g_set_object(&conversation->conversation, purple_conversation)) {
+		GtkNoSelection *selection = NULL;
+		GListModel *model = NULL;
+
 		if(PURPLE_IS_CONVERSATION(purple_conversation)) {
 			g_object_set_data(G_OBJECT(purple_conversation),
 			                  PIDGIN_CONVERSATION_DATA, conversation);
+			model = purple_conversation_get_messages(purple_conversation);
+		} else {
+			model = G_LIST_MODEL(g_list_store_new(PURPLE_TYPE_MESSAGE));
 		}
 
+		selection = gtk_no_selection_new(model);
+
+		gtk_list_view_set_model(GTK_LIST_VIEW(conversation->history),
+		                        GTK_SELECTION_MODEL(selection));
+		g_clear_object(&selection);
+
 		pidgin_info_pane_set_conversation(PIDGIN_INFO_PANE(conversation->info_pane),
 		                                  purple_conversation);
 
@@ -100,6 +114,69 @@
 	}
 }
 
+static PangoAttrList *
+pidgin_conversation_get_author_attributes(G_GNUC_UNUSED GObject *self,
+                                          PurpleMessage *message,
+                                          G_GNUC_UNUSED gpointer data)
+{
+	const char *author = NULL;
+	const char *custom_color = NULL;
+	GdkRGBA rgba;
+	PangoAttrList *attrs = NULL;
+	gboolean color_valid = FALSE;
+
+	if(!PURPLE_IS_MESSAGE(message)) {
+		return NULL;
+	}
+
+	author = purple_message_get_author_alias(message);
+	if(purple_strempty(author)) {
+		author = purple_message_get_author(message);
+	}
+
+	custom_color = purple_message_get_author_name_color(message);
+	if(!purple_strempty(custom_color)) {
+		color_valid = gdk_rgba_parse(&rgba, custom_color);
+	}
+
+	if(!color_valid) {
+		pidgin_color_calculate_for_text(author, &rgba);
+		color_valid = TRUE;
+	}
+
+	attrs = pango_attr_list_new();
+
+	if(color_valid) {
+		PangoAttribute *attr = NULL;
+
+		attr = pango_attr_foreground_new(0xFFFF * rgba.red,
+		                                 0xFFFF * rgba.green,
+		                                 0xFFFF * rgba.blue);
+		pango_attr_list_insert(attrs, attr);
+	}
+
+	return attrs;
+}
+
+static char *
+pidgin_converation_get_timestamp_string(G_GNUC_UNUSED GObject *self,
+                                        PurpleMessage *message,
+                                        G_GNUC_UNUSED gpointer data)
+{
+	GDateTime *timestamp = NULL;
+
+	if(!PURPLE_IS_MESSAGE(message)) {
+		return NULL;
+	}
+
+	timestamp = purple_message_get_timestamp(message);
+	if(timestamp != NULL) {
+		return g_date_time_format(timestamp, "%I:%M %p");
+	}
+
+	return NULL;
+}
+
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
@@ -192,6 +269,10 @@
 
 	gtk_widget_class_bind_template_callback(widget_class,
 	                                        pidgin_conversation_send_message_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_conversation_get_author_attributes);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_converation_get_timestamp_string);
 }
 
 /******************************************************************************
@@ -223,23 +304,6 @@
 }
 
 void
-pidgin_conversation_write_message(PidginConversation *conversation,
-                                  PurpleMessage *purple_message)
-{
-	PidginMessage *message = NULL;
-
-	g_return_if_fail(PIDGIN_IS_CONVERSATION(conversation));
-	g_return_if_fail(PURPLE_IS_MESSAGE(purple_message));
-
-	message = pidgin_message_new(purple_message);
-
-	talkatu_history_write_message(TALKATU_HISTORY(conversation->history),
-	                              TALKATU_MESSAGE(message));
-
-	g_clear_object(&message);
-}
-
-void
 pidgin_conversation_close(PidginConversation *conversation) {
 	g_return_if_fail(PIDGIN_IS_CONVERSATION(conversation));
 
diff --git a/pidgin/pidginmessage.h b/pidgin/pidginmessage.h
deleted file mode 100644
--- a/pidgin/pidginmessage.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Pidgin - Internet Messenger
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * Pidgin is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
-# error "only <pidgin.h> may be included directly"
-#endif
-
-#ifndef PIDGIN_MESSAGE_H
-#define PIDGIN_MESSAGE_H
-
-#include <purple.h>
-
-#include <talkatu.h>
-
-G_BEGIN_DECLS
-
-/**
- * PidginMessage:
- *
- * #PidginMessage maps a #PurpleMessage to a #TalkatuMessage.
- *
- * Since: 3.0.0
- */
-
-#define PIDGIN_TYPE_MESSAGE (pidgin_message_get_type())
-G_DECLARE_FINAL_TYPE(PidginMessage, pidgin_message, PIDGIN, MESSAGE, GObject)
-
-/**
- * pidgin_message_new:
- * @msg: The #PurpleMessage to wrap.
- *
- * Wraps @msg so that it can be used as a #TalkatuMessage.
- *
- * Returns: (transfer full): The new #PidginMessage instance.
- */
-PidginMessage *pidgin_message_new(PurpleMessage *msg);
-
-/**
- * pidgin_message_get_message:
- * @msg: The #PidginMessage instance.
- *
- * Gets the #PurpleMessage that @msg is wrapping.
- *
- * Returns: (transfer none): The #PurpleMessage that @msg is wrapping.
- */
-PurpleMessage *pidgin_message_get_message(PidginMessage *msg);
-
-G_END_DECLS
-
-#endif /* PIDGIN_MESSAGE_H */
diff --git a/pidgin/pidginmessage.c b/pidgin/pidginmessage.c
deleted file mode 100644
--- a/pidgin/pidginmessage.c
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Pidgin - Internet Messenger
- * Copyright (C) Pidgin Developers <devel@pidgin.im>
- *
- * Pidgin is the legal property of its developers, whose names are too numerous
- * to list here.  Please refer to the COPYRIGHT file distributed with this
- * source distribution.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <https://www.gnu.org/licenses/>.
- */
-
-#include "pidginmessage.h"
-
-#include "pidginattachment.h"
-#include "pidgincolor.h"
-
-struct _PidginMessage {
-	GObject parent;
-
-	PurpleMessage *message;
-};
-
-typedef struct {
-	TalkatuAttachmentForeachFunc func;
-	gpointer data;
-} PidginMessageAttachmentForeachData;
-
-enum {
-	PROP_0,
-	PROP_MESSAGE,
-	N_PROPERTIES,
-	/* overrides */
-	PROP_ID = N_PROPERTIES,
-	PROP_CONTENT_TYPE,
-	PROP_AUTHOR,
-	PROP_AUTHOR_NAME_COLOR,
-	PROP_CONTENTS,
-	PROP_TIMESTAMP,
-	PROP_EDITED,
-};
-static GParamSpec *properties[N_PROPERTIES] = {NULL, };
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static void
-pidgin_message_set_message(PidginMessage *message, PurpleMessage *purple_msg) {
-	if(g_set_object(&message->message, purple_msg)) {
-		g_object_notify_by_pspec(G_OBJECT(message), properties[PROP_MESSAGE]);
-	}
-}
-
-static GdkRGBA *
-pidgin_message_parse_author_name_color(PidginMessage *message) {
-	GdkRGBA *ret = NULL;
-	const gchar *color = NULL;
-	gboolean set = FALSE;
-
-	ret = g_new0(GdkRGBA, 1);
-
-	color = purple_message_get_author_name_color(message->message);
-	if(color != NULL && gdk_rgba_parse(ret, color)) {
-		set = TRUE;
-	}
-
-	if(!set) {
-		const gchar *author = purple_message_get_author_alias(message->message);
-
-		if(author == NULL) {
-			author = purple_message_get_author(message->message);
-		}
-
-		pidgin_color_calculate_for_text(author, ret);
-	}
-
-	return ret;
-}
-
-/******************************************************************************
- * TalkatuMessage Implementation
- *****************************************************************************/
-static gboolean
-pidgin_message_add_attachment(TalkatuMessage *tmessage,
-                              TalkatuAttachment *tattachment)
-{
-	PidginMessage *pmessage = PIDGIN_MESSAGE(tmessage);
-	PurpleAttachment *pattachment = NULL;
-	gboolean ret = FALSE;
-
-	pattachment = purple_attachment_new(
-		talkatu_attachment_get_id(tattachment),
-		talkatu_attachment_get_content_type(tattachment)
-	);
-
-	ret = purple_message_add_attachment(pmessage->message, pattachment);
-
-	g_object_unref(G_OBJECT(pattachment));
-
-	return ret;
-}
-
-static gboolean
-pidgin_message_remove_attachment(TalkatuMessage *tmessage, guint64 id) {
-	PidginMessage *pmessage = PIDGIN_MESSAGE(tmessage);
-
-	return purple_message_remove_attachment(pmessage->message, id);
-}
-
-static TalkatuAttachment *
-pidgin_message_get_attachment(TalkatuMessage *tmessage, guint64 id) {
-	PidginMessage *pmessage = PIDGIN_MESSAGE(tmessage);
-	PidginAttachment *pidgin_attachment = NULL;
-	PurpleAttachment *purple_attachment = NULL;
-
-	purple_attachment = purple_message_get_attachment(pmessage->message, id);
-	pidgin_attachment = pidgin_attachment_new(purple_attachment);
-	g_object_unref(G_OBJECT(purple_attachment));
-
-	return TALKATU_ATTACHMENT(pidgin_attachment);
-}
-
-static void
-pidgin_message_foreach_attachment_helper(PurpleAttachment *attachment,
-                                         gpointer data)
-{
-	PidginAttachment *pidgin_attachment = NULL;
-	PidginMessageAttachmentForeachData *d = NULL;
-
-	d = (PidginMessageAttachmentForeachData *)data;
-	pidgin_attachment = pidgin_attachment_new(attachment);
-
-	d->func(TALKATU_ATTACHMENT(pidgin_attachment), d->data);
-
-	g_object_unref(G_OBJECT(pidgin_attachment));
-}
-
-static void
-pidgin_message_foreach_attachment(TalkatuMessage *tmessage,
-                                  TalkatuAttachmentForeachFunc func,
-                                  gpointer data)
-{
-	PidginMessage *pmessage = PIDGIN_MESSAGE(tmessage);
-	PidginMessageAttachmentForeachData *d = NULL;
-
-	/* PurpleAttachmentForeachFunc and TalkatuAttachmentForeachFunc may not
-	 * always have the same signature.  So to work around that, we use a helper
-	 * function that has the signature of PurpleAttachmentForeachFunc but will
-	 * call the TalkatuAttachmentForeachFunc while also wrapping the
-	 * PurpleAttachments.
-	 */
-
-	d = g_new(PidginMessageAttachmentForeachData, 1);
-	d->func = func;
-	d->data = data;
-
-	purple_message_foreach_attachment(
-		pmessage->message,
-		pidgin_message_foreach_attachment_helper,
-		d
-	);
-
-	g_free(d);
-}
-
-static void
-pidgin_message_clear_attachments(TalkatuMessage *tmessage) {
-	PidginMessage *pmessage = PIDGIN_MESSAGE(tmessage);
-
-	purple_message_clear_attachments(pmessage->message);
-}
-
-static void
-pidgin_message_talkatu_message_init(TalkatuMessageInterface *iface) {
-	iface->add_attachment = pidgin_message_add_attachment;
-	iface->remove_attachment = pidgin_message_remove_attachment;
-	iface->get_attachment = pidgin_message_get_attachment;
-	iface->foreach_attachment = pidgin_message_foreach_attachment;
-	iface->clear_attachments = pidgin_message_clear_attachments;
-}
-
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_TYPE_EXTENDED(
-	PidginMessage,
-	pidgin_message,
-	G_TYPE_OBJECT,
-	0,
-	G_IMPLEMENT_INTERFACE(TALKATU_TYPE_MESSAGE,
-	                      pidgin_message_talkatu_message_init)
-);
-
-static void
-pidgin_message_get_property(GObject *obj, guint param_id, GValue *value,
-                            GParamSpec *pspec)
-{
-	PidginMessage *message = PIDGIN_MESSAGE(obj);
-
-	switch(param_id) {
-		case PROP_MESSAGE:
-			g_value_set_object(value, message->message);
-			break;
-		case PROP_ID:
-			g_value_set_string(value, purple_message_get_id(message->message));
-			break;
-		case PROP_CONTENT_TYPE:
-			g_value_set_enum(value, TALKATU_CONTENT_TYPE_PLAIN);
-			break;
-		case PROP_AUTHOR:
-			g_value_set_string(value,
-			                   purple_message_get_author(message->message));
-			break;
-		case PROP_AUTHOR_NAME_COLOR:
-			g_value_take_boxed(value,
-			                   pidgin_message_parse_author_name_color(message));
-			break;
-		case PROP_CONTENTS:
-			g_value_set_string(value,
-			                   purple_message_get_contents(message->message));
-			break;
-		case PROP_TIMESTAMP:
-			g_value_set_boxed(value,
-			                  purple_message_get_timestamp(message->message));
-			break;
-		case PROP_EDITED:
-			g_value_set_boolean(value, FALSE);
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
-}
-
-static void
-pidgin_message_set_property(GObject *obj, guint param_id, const GValue *value,
-                            GParamSpec *pspec)
-{
-	PidginMessage *message = PIDGIN_MESSAGE(obj);
-
-	switch(param_id) {
-		case PROP_MESSAGE:
-			pidgin_message_set_message(message, g_value_get_object(value));
-			break;
-		case PROP_ID:
-		case PROP_CONTENT_TYPE:
-		case PROP_TIMESTAMP:
-		case PROP_AUTHOR:
-		case PROP_AUTHOR_NAME_COLOR:
-		case PROP_CONTENTS:
-		case PROP_EDITED:
-			/* we don't allow settings these */
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
-}
-
-static void
-pidgin_message_init(G_GNUC_UNUSED PidginMessage *message) {
-}
-
-static void
-pidgin_message_finalize(GObject *obj) {
-	PidginMessage *message = PIDGIN_MESSAGE(obj);
-
-	g_clear_object(&message->message);
-
-	G_OBJECT_CLASS(pidgin_message_parent_class)->finalize(obj);
-}
-
-static void
-pidgin_message_class_init(PidginMessageClass *klass) {
-	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
-
-	obj_class->get_property = pidgin_message_get_property;
-	obj_class->set_property = pidgin_message_set_property;
-	obj_class->finalize = pidgin_message_finalize;
-
-	/* add our custom properties */
-	properties[PROP_MESSAGE] = g_param_spec_object(
-		"message", "message", "The purple message",
-		PURPLE_TYPE_MESSAGE,
-		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
-	);
-
-	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
-
-	/* add our overridden properties */
-	g_object_class_override_property(obj_class, PROP_ID, "id");
-	g_object_class_override_property(obj_class, PROP_TIMESTAMP, "timestamp");
-	g_object_class_override_property(obj_class, PROP_CONTENT_TYPE,
-	                                 "content-type");
-	g_object_class_override_property(obj_class, PROP_AUTHOR, "author");
-	g_object_class_override_property(obj_class, PROP_AUTHOR_NAME_COLOR,
-	                                 "author-name-color");
-	g_object_class_override_property(obj_class, PROP_CONTENTS, "contents");
-	g_object_class_override_property(obj_class, PROP_EDITED, "edited");
-}
-
-/******************************************************************************
- * API
- *****************************************************************************/
-PidginMessage *
-pidgin_message_new(PurpleMessage *message) {
-	return g_object_new(
-		PIDGIN_TYPE_MESSAGE,
-		"message", message,
-		NULL
-	);
-}
-
-PurpleMessage *
-pidgin_message_get_message(PidginMessage *message) {
-	g_return_val_if_fail(PIDGIN_IS_MESSAGE(message), NULL);
-
-	return message->message;
-}
diff --git a/pidgin/resources/Conversations/conversation.ui b/pidgin/resources/Conversations/conversation.ui
--- a/pidgin/resources/Conversations/conversation.ui
+++ b/pidgin/resources/Conversations/conversation.ui
@@ -58,7 +58,15 @@
             </property>
             <property name="vexpand">1</property>
             <child>
-              <object class="TalkatuHistory" id="history"/>
+              <object class="GtkListView" id="history">
+                <property name="hexpand">1</property>
+                <property name="vexpand">1</property>
+                <property name="factory">
+                  <object class="GtkBuilderListItemFactory">
+                    <property name="resource">/im/pidgin/Pidgin3/Conversations/message.ui</property>
+                  </object>
+                </property>
+              </object>
             </child>
           </object>
         </child>
diff --git a/pidgin/resources/Conversations/message.ui b/pidgin/resources/Conversations/message.ui
new file mode 100644
--- /dev/null
+++ b/pidgin/resources/Conversations/message.ui
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Pidgin - Internet Messenger
+Copyright (C) Pidgin Developers <devel@pidgin.im>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this library; if not, see <https://www.gnu.org/licenses/>.
+-->
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <template class="GtkListItem">
+    <property name="child">
+      <object class="GtkBox">
+        <child>
+          <object class="GtkImage" id="avatar">
+            <property name="icon-name">image-missing</property>
+            <property name="visible">0</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="hexpand">1</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkBox">
+                <property name="spacing">8</property>
+                <child>
+                  <object class="GtkLabel" id="author">
+                    <binding name="label">
+                      <lookup name="author" type="PurpleMessage">
+                        <lookup name="item">GtkListItem</lookup>
+                      </lookup>
+                    </binding>
+                    <binding name="attributes">
+                      <closure type="PangoAttrList" function="pidgin_conversation_get_author_attributes">
+                        <lookup name="item">GtkListItem</lookup>
+                      </closure>
+                    </binding>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="timestamp">
+                    <binding name="label">
+                      <closure type="gchararray" function="pidgin_converation_get_timestamp_string">
+                        <lookup name="item">GtkListItem</lookup>
+                      </closure>
+                    </binding>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="edited">
+                    <property name="visible">0</property>
+                    <property name="label" translatable="yes">(edited)</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="content">
+                <property name="wrap">1</property>
+                <property name="wrap-mode">word-char</property>
+                <property name="xalign">0</property>
+                <property name="yalign">0</property>
+                <property name="use-markup">1</property>
+                <binding name="label">
+                  <lookup name="contents" type="PurpleMessage">
+                    <lookup name="item">GtkListItem</lookup>
+                  </lookup>
+                </binding>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </property>
+  </template>
+</interface>
diff --git a/pidgin/resources/pidgin.gresource.xml b/pidgin/resources/pidgin.gresource.xml
--- a/pidgin/resources/pidgin.gresource.xml
+++ b/pidgin/resources/pidgin.gresource.xml
@@ -15,6 +15,7 @@
     <file compressed="true" preprocess="xml-stripblanks">Conversations/conversation.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">Conversations/infopane.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">Conversations/invite_dialog.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">Conversations/message.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ContactList/widget.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">Debug/debug.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">Dialogs/addbuddy.ui</file>
diff --git a/po/POTFILES.in b/po/POTFILES.in
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -261,7 +261,6 @@
 pidgin/pidginaddbuddydialog.c
 pidgin/pidginaddchatdialog.c
 pidgin/pidginapplication.c
-pidgin/pidginattachment.c
 pidgin/pidginavatar.c
 pidgin/pidgin.c
 pidgin/pidgincolor.c
@@ -274,7 +273,6 @@
 pidgin/pidgininfopane.c
 pidgin/pidgininvitedialog.c
 pidgin/pidginkeypad.c
-pidgin/pidginmessage.c
 pidgin/pidginplugininfo.c
 pidgin/pidginpluginsdialog.c
 pidgin/pidginpluginsmenu.c
