# HG changeset patch
# Node ID b4ff7cf2ba0d72c264e03e79a8164315f7f93686
# Parent  7e425e201771b82c6f282609dd56fb0df5c44533
diff --git a/pidgin/meson.build b/pidgin/meson.build
--- a/pidgin/meson.build
+++ b/pidgin/meson.build
@@ -26,6 +26,7 @@
 	'pidgincontactinfomenu.c',
 	'pidgincontactlist.c',
 	'pidginconversation.c',
+	'pidginconversationmember.c',
 	'pidgindebug.c',
 	'pidgindisplayitem.c',
 	'pidgindisplaywindow.c',
@@ -77,6 +78,7 @@
 	'pidgincontactinfomenu.h',
 	'pidgincontactlist.h',
 	'pidginconversation.h',
+	'pidginconversationmember.h',
 	'pidgincore.h',
 	'pidgindisplayitem.h',
 	'pidgindisplaywindow.h',
diff --git a/pidgin/pidginconversation.c b/pidgin/pidginconversation.c
--- a/pidgin/pidginconversation.c
+++ b/pidgin/pidginconversation.c
@@ -269,37 +269,6 @@
 	}
 }
 
-static void
-pidgin_conversation_member_list_context_cb(GtkGestureSingle *self,
-                                           G_GNUC_UNUSED gint n_press,
-                                           gdouble x,
-                                           gdouble y,
-                                           gpointer data)
-{
-	PurpleAccount *account = NULL;
-	PurpleContactInfo *info = NULL;
-	PurpleConversationMember *member = NULL;
-	GtkWidget *parent = NULL;
-	GtkListItem *item = data;
-
-	parent = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(self));
-
-	member = gtk_list_item_get_item(item);
-	info = purple_conversation_member_get_contact_info(member);
-
-	/* ConversationMembers are a PurpleAccount for the libpurple user, or in
-	 * most cases are PurpleContact for all the other users. Because of this,
-	 * we have to do a runtime check to determine which one they are.
-	 */
-	if(PURPLE_IS_ACCOUNT(info)) {
-		account = PURPLE_ACCOUNT(info);
-	} else if(PURPLE_IS_CONTACT(info)) {
-		account = purple_contact_get_account(PURPLE_CONTACT(info));
-	}
-
-	pidgin_contact_info_menu_popup(info, account, parent, x, y);
-}
-
 static int
 pidgin_conversation_member_list_sort(gconstpointer a, gconstpointer b,
                                      G_GNUC_UNUSED gpointer data)
@@ -470,8 +439,6 @@
 	gtk_widget_class_bind_template_callback(widget_class,
 	                                        pidgin_conversation_input_key_pressed_cb);
 	gtk_widget_class_bind_template_callback(widget_class,
-	                                        pidgin_conversation_member_list_context_cb);
-	gtk_widget_class_bind_template_callback(widget_class,
 	                                        pidgin_conversation_message_setup);
 	gtk_widget_class_bind_template_callback(widget_class,
 	                                        pidgin_conversation_message_bind);
diff --git a/pidgin/pidginconversationmember.c b/pidgin/pidginconversationmember.c
new file mode 100644
--- /dev/null
+++ b/pidgin/pidginconversationmember.c
@@ -0,0 +1,276 @@
+/*
+ * 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 "pidginconversationmember.h"
+
+#include "pidgincolor.h"
+#include "pidgincontactinfomenu.h"
+
+struct _PidginConversationMember {
+	GtkBox parent;
+
+	PurpleConversationMember *conversation_member;
+};
+
+enum {
+	PROP_0,
+	PROP_CONVERSATION_MEMBER,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+pidgin_conversation_member_context_cb(G_GNUC_UNUSED GtkGestureSingle *self,
+                                      G_GNUC_UNUSED gint n_press,
+                                      gdouble x,
+                                      gdouble y,
+                                      gpointer data)
+{
+	PidginConversationMember *member = data;
+	PurpleAccount *account = NULL;
+	PurpleContactInfo *info = NULL;
+
+	/* So this doesn't actually work at all right now and the code this is
+	 * replacing didn't work either. PurpleConversationMember has a
+	 * PurpleContactInfo, so no account, and the libpurple user is just a
+	 * PurpleContactInfo as well so no account. The conversation member doesn't
+	 * have the account and therefore neither does the widget.
+	 *
+	 * We're probably going to have to make PurpleConversationMember take a
+	 * PurpleContact, but that doesn't work for the libpurple user right now
+	 * either.
+	 *
+	 * So TL;DR, this is all broke until we sort out that mess.
+	 */
+	info = purple_conversation_member_get_contact_info(member->conversation_member);
+
+	if(PURPLE_IS_CONTACT(info)) {
+		account = purple_contact_get_account(PURPLE_CONTACT(info));
+	}
+
+	pidgin_contact_info_menu_popup(info, account, GTK_WIDGET(member), x, y);
+}
+
+static gboolean
+pidgin_conversation_member_show_badges_cb(G_GNUC_UNUSED PidginConversationMember *self,
+                                          PurpleConversationMember *conversation_member,
+                                          G_GNUC_UNUSED gpointer data)
+{
+	if(PURPLE_IS_CONVERSATION_MEMBER(conversation_member)) {
+		PurpleBadges *badges = NULL;
+		guint n_badges = 0;
+
+		badges = purple_conversation_member_get_badges(conversation_member);
+		n_badges = g_list_model_get_n_items(G_LIST_MODEL(badges));
+
+		if(n_badges > 0) {
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+static char *
+pidgin_conversation_member_get_name_for_display(G_GNUC_UNUSED PidginConversationMember *self,
+                                                PurpleConversationMember *conversation_member,
+                                                G_GNUC_UNUSED gpointer data)
+{
+	const char *name_for_display = "";
+
+	if(PURPLE_IS_CONVERSATION_MEMBER(conversation_member)) {
+		name_for_display = purple_conversation_member_get_name_for_display(self->conversation_member);
+	}
+
+	return g_strdup(name_for_display);
+}
+
+static PangoAttrList *
+pidgin_conversation_member_attributes(G_GNUC_UNUSED PidginConversationMember *self,
+                                      PurpleConversationMember *conversation_member,
+                                      G_GNUC_UNUSED gpointer data)
+{
+	GdkRGBA rgba;
+	PangoAttrList *attrs = NULL;
+	gboolean color_valid = FALSE;
+	const char *custom_color = NULL;
+
+	if(!PURPLE_IS_CONVERSATION_MEMBER(conversation_member)) {
+		return NULL;
+	}
+
+	if(PURPLE_IS_CONVERSATION_MEMBER(conversation_member)) {
+		custom_color = purple_conversation_member_get_color(conversation_member);
+	}
+
+	if(!purple_strempty(custom_color)) {
+		color_valid = gdk_rgba_parse(&rgba, custom_color);
+	}
+
+	if(!color_valid) {
+		const char *name_for_display = NULL;
+
+		if(PURPLE_IS_CONVERSATION_MEMBER(conversation_member)) {
+			name_for_display = purple_conversation_member_get_name_for_display(conversation_member);
+		}
+
+		pidgin_color_calculate_for_text(name_for_display, &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;
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_FINAL_TYPE(PidginConversationMember, pidgin_conversation_member,
+                    GTK_TYPE_BOX)
+
+static void
+pidgin_conversation_member_finalize(GObject *obj) {
+	PidginConversationMember *member = PIDGIN_CONVERSATION_MEMBER(obj);
+
+	g_clear_object(&member->conversation_member);
+
+	G_OBJECT_CLASS(pidgin_conversation_member_parent_class)->finalize(obj);
+}
+
+static void
+pidgin_conversation_member_get_property(GObject *obj, guint param_id,
+                                        GValue *value, GParamSpec *pspec)
+{
+	PidginConversationMember *member = PIDGIN_CONVERSATION_MEMBER(obj);
+
+	switch(param_id) {
+	case PROP_CONVERSATION_MEMBER:
+		g_value_set_object(value,
+		                   pidgin_conversation_member_get_conversation_member(member));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+pidgin_conversation_member_set_property(GObject *obj, guint param_id,
+                                        const GValue *value, GParamSpec *pspec)
+{
+	PidginConversationMember *member = PIDGIN_CONVERSATION_MEMBER(obj);
+
+	switch(param_id) {
+	case PROP_CONVERSATION_MEMBER:
+		pidgin_conversation_member_set_conversation_member(member,
+		                                                   g_value_get_object(value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+pidgin_conversation_member_init(PidginConversationMember *member) {
+	gtk_widget_init_template(GTK_WIDGET(member));
+}
+
+static void
+pidgin_conversation_member_class_init(PidginConversationMemberClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+	obj_class->finalize = pidgin_conversation_member_finalize;
+	obj_class->get_property = pidgin_conversation_member_get_property;
+	obj_class->set_property = pidgin_conversation_member_set_property;
+
+	/**
+	 * PidginConversationMember:conversation-member:
+	 *
+	 * The conversation member which is being displayed.
+	 *
+	 * Since: 3.0
+	 */
+	properties[PROP_CONVERSATION_MEMBER] = g_param_spec_object(
+		"conversation-member", NULL, NULL,
+		PURPLE_TYPE_CONVERSATION_MEMBER,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+	gtk_widget_class_set_template_from_resource(widget_class,
+	                                            "/im/pidgin/Pidgin3/conversationmember.ui");
+
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_conversation_member_context_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_conversation_member_show_badges_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_conversation_member_get_name_for_display);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_conversation_member_attributes);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+GtkWidget *
+pidgin_conversation_member_new(PurpleConversationMember *conversation_member) {
+	return g_object_new(
+		PIDGIN_TYPE_CONVERSATION_MEMBER,
+		"conversation-member", conversation_member,
+		NULL);
+}
+
+PurpleConversationMember *
+pidgin_conversation_member_get_conversation_member(PidginConversationMember *member)
+{
+	g_return_val_if_fail(PIDGIN_IS_CONVERSATION_MEMBER(member), NULL);
+
+	return member->conversation_member;
+}
+
+void
+pidgin_conversation_member_set_conversation_member(PidginConversationMember *member,
+                                                   PurpleConversationMember *conversation_member)
+{
+	g_return_if_fail(PIDGIN_IS_CONVERSATION_MEMBER(member));
+
+	if(g_set_object(&member->conversation_member, conversation_member)) {
+		g_object_notify_by_pspec(G_OBJECT(member),
+		                         properties[PROP_CONVERSATION_MEMBER]);
+	}
+}
diff --git a/pidgin/pidginconversationmember.h b/pidgin/pidginconversationmember.h
new file mode 100644
--- /dev/null
+++ b/pidgin/pidginconversationmember.h
@@ -0,0 +1,91 @@
+/*
+ * 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_CONVERSATION_MEMBER_H
+#define PIDGIN_CONVERSATION_MEMBER_H
+
+#include <gtk/gtk.h>
+
+#include <purple.h>
+
+#include "pidginversion.h"
+
+G_BEGIN_DECLS
+
+/**
+ * PidginConversationMember:
+ *
+ * A widget that is used to display a conversation member.
+ *
+ * Since: 3.0
+ */
+
+#define PIDGIN_TYPE_CONVERSATION_MEMBER (pidgin_conversation_member_get_type())
+
+PIDGIN_AVAILABLE_IN_3_0
+G_DECLARE_FINAL_TYPE(PidginConversationMember, pidgin_conversation_member,
+                     PIDGIN, CONVERSATION_MEMBER, GtkBox)
+
+/**
+ * pidgin_conversation_member_new:
+ * @conversation_member: the conversation member to display
+ *
+ * Creates a display for a conversation member.
+ *
+ * Returns: (transfer full): The conversation member display.
+ *
+ * Since: 3.0
+ */
+PIDGIN_AVAILABLE_IN_3_0
+GtkWidget *pidgin_conversation_member_new(PurpleConversationMember *conversation_member);
+
+/**
+ * pidgin_conversation_member_get_conversation_member:
+ *
+ * Gets the currently displayed conversation member.
+ *
+ * Returns: (transfer none): The [type@Purple.ConversationMember] that is
+ *          currently displayed.
+ *
+ * Since: 3.0
+ */
+PIDGIN_AVAILABLE_IN_3_0
+PurpleConversationMember *pidgin_conversation_member_get_conversation_member(PidginConversationMember *member);
+
+/**
+ * pidgin_conversation_member_set_conversation_member:
+ * @conversation_member: (nullable): the new conversation member to display
+ *
+ * Sets the currently displayed conversation member.
+ *
+ * Since: 3.0
+ */
+PIDGIN_AVAILABLE_IN_3_0
+void pidgin_conversation_member_set_conversation_member(PidginConversationMember *member, PurpleConversationMember *conversation_member);
+
+G_END_DECLS
+
+#endif /* PIDGIN_CONVERSATION_MEMBER_H */
diff --git a/pidgin/pidginmessage.c b/pidgin/pidginmessage.c
--- a/pidgin/pidginmessage.c
+++ b/pidgin/pidginmessage.c
@@ -22,8 +22,6 @@
 
 #include "pidginmessage.h"
 
-#include "pidgincolor.h"
-
 struct _PidginMessage {
 	GtkBox parent;
 
@@ -78,72 +76,6 @@
 /******************************************************************************
  * Callbacks
  *****************************************************************************/
-static char *
-pidgin_message_get_author(G_GNUC_UNUSED GObject *self, PurpleMessage *message,
-                          G_GNUC_UNUSED gpointer data)
-{
-	if(PURPLE_IS_MESSAGE(message)) {
-		PurpleConversationMember *author = NULL;
-
-		author = purple_message_get_author(message);
-
-		if(PURPLE_IS_CONVERSATION_MEMBER(author)) {
-			return g_strdup(purple_conversation_member_get_name_for_display(author));
-		}
-	}
-
-	return g_strdup("");
-}
-
-static PangoAttrList *
-pidgin_message_get_author_attributes(G_GNUC_UNUSED GObject *self,
-                                     PurpleMessage *message,
-                                     G_GNUC_UNUSED gpointer data)
-{
-	PurpleConversationMember *author = NULL;
-	GdkRGBA rgba;
-	PangoAttrList *attrs = NULL;
-	gboolean color_valid = FALSE;
-	const char *custom_color = NULL;
-
-	if(!PURPLE_IS_MESSAGE(message)) {
-		return NULL;
-	}
-
-	author = purple_message_get_author(message);
-	if(PURPLE_IS_CONVERSATION_MEMBER(author)) {
-		custom_color = purple_conversation_member_get_color(author);
-	}
-
-	if(!purple_strempty(custom_color)) {
-		color_valid = gdk_rgba_parse(&rgba, custom_color);
-	}
-
-	if(!color_valid) {
-		const char *name_for_display = NULL;
-
-		if(PURPLE_IS_CONVERSATION_MEMBER(author)) {
-			name_for_display = purple_conversation_member_get_name_for_display(author);
-		}
-
-		pidgin_color_calculate_for_text(name_for_display, &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 PangoAttrList *
 pidgin_message_get_message_attributes(G_GNUC_UNUSED GObject *self,
                                       PurpleMessage *message,
@@ -276,31 +208,6 @@
 	return pidgin_message_set_tooltip_for_timestamp(tooltip, timestamp);
 }
 
-static gboolean
-pidgin_message_show_badges_cb(G_GNUC_UNUSED GObject *self,
-                              PurpleMessage *message,
-                              G_GNUC_UNUSED gpointer data)
-{
-	if(PURPLE_IS_MESSAGE(message)) {
-		PurpleConversationMember *member = NULL;
-
-		member = purple_message_get_author(message);
-		if(PURPLE_IS_CONVERSATION_MEMBER(member)) {
-			PurpleBadges *badges = NULL;
-			guint n_badges = 0;
-
-			badges = purple_conversation_member_get_badges(member);
-			n_badges = g_list_model_get_n_items(G_LIST_MODEL(badges));
-
-			if(n_badges > 0) {
-				return TRUE;
-			}
-		}
-	}
-
-	return FALSE;
-}
-
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
@@ -381,10 +288,6 @@
 	);
 
 	gtk_widget_class_bind_template_callback(widget_class,
-	                                        pidgin_message_get_author);
-	gtk_widget_class_bind_template_callback(widget_class,
-	                                        pidgin_message_get_author_attributes);
-	gtk_widget_class_bind_template_callback(widget_class,
 	                                        pidgin_message_get_message_attributes);
 	gtk_widget_class_bind_template_callback(widget_class,
 	                                        pidgin_message_get_timestamp_string);
@@ -396,8 +299,6 @@
 	                                        pidgin_message_query_tooltip_edited_cb);
 	gtk_widget_class_bind_template_callback(widget_class,
 	                                        pidgin_message_query_tooltip_timestamp_cb);
-	gtk_widget_class_bind_template_callback(widget_class,
-	                                        pidgin_message_show_badges_cb);
 }
 
 /******************************************************************************
diff --git a/pidgin/resources/Conversations/message.ui b/pidgin/resources/Conversations/message.ui
--- a/pidgin/resources/Conversations/message.ui
+++ b/pidgin/resources/Conversations/message.ui
@@ -24,12 +24,6 @@
       <object class="GtkEventControllerMotion" id="motion_controller"/>
     </child>
     <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>
@@ -37,36 +31,11 @@
           <object class="GtkBox">
             <property name="spacing">8</property>
             <child>
-              <object class="PidginBadges">
-                <binding name="badges">
-                  <lookup name="badges" type="PurpleConversationMember">
-                    <lookup name="author" type="PurpleMessage">
-                      <lookup name="message">PidginMessage</lookup>
-                    </lookup>
-                  </lookup>
-                </binding>
-                <binding name="visible">
-                  <closure type="gboolean" function="pidgin_message_show_badges_cb">
+              <object class="PidginConversationMember">
+                <binding name="conversation-member">
+                  <lookup name="author" type="PurpleMessage">
                     <lookup name="message">PidginMessage</lookup>
-                  </closure>
-                </binding>
-              </object>
-            </child>
-            <child>
-              <object class="GtkLabel" id="author">
-                <binding name="label">
-                  <!-- The first argument to the closure is automatically a
-                       PidginMessage, but if we don't do the lookup the message
-                       property on it isn't set yet for some reason.
-                    -->
-                  <closure type="gchararray" function="pidgin_message_get_author">
-                    <lookup name="message">PidginMessage</lookup>
-                  </closure>
-                </binding>
-                <binding name="attributes">
-                  <closure type="PangoAttrList" function="pidgin_message_get_author_attributes">
-                    <lookup name="message">PidginMessage</lookup>
-                  </closure>
+                  </lookup>
                 </binding>
               </object>
             </child>
diff --git a/pidgin/resources/conversationmember.ui b/pidgin/resources/conversationmember.ui
new file mode 100644
--- /dev/null
+++ b/pidgin/resources/conversationmember.ui
@@ -0,0 +1,66 @@
+<?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="PidginConversationMember">
+    <property name="orientation">horizontal</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkGestureClick">
+        <property name="button">3</property>
+        <signal name="released" handler="pidgin_conversation_member_context_cb"/>
+      </object>
+    </child>
+    <child>
+      <object class="GtkImage" id="avatar">
+        <property name="icon-name">image-missing</property>
+        <property name="icon-size">normal</property>
+        <property name="visible">0</property>
+      </object>
+    </child>
+    <child>
+      <object class="PidginBadges">
+        <binding name="badges">
+          <lookup name="badges" type="PurpleConversationMember">
+            <lookup name="conversation-member">PidginConversationMember</lookup>
+          </lookup>
+        </binding>
+        <binding name="visible">
+          <closure type="gboolean" function="pidgin_conversation_member_show_badges_cb">
+            <lookup name="conversation-member">PidginConversationMember</lookup>
+          </closure>
+        </binding>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <binding name="label">
+          <closure type="gchararray" function="pidgin_conversation_member_get_name_for_display">
+            <lookup name="conversation-member">PidginConversationMember</lookup>
+          </closure>
+        </binding>
+        <binding name="attributes">
+          <closure type="PangoAttrList" function="pidgin_conversation_member_attributes">
+            <lookup name="conversation-member">PidginConversationMember</lookup>
+          </closure>
+        </binding>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/pidgin/resources/conversationmemberlistitem.ui b/pidgin/resources/conversationmemberlistitem.ui
--- a/pidgin/resources/conversationmemberlistitem.ui
+++ b/pidgin/resources/conversationmemberlistitem.ui
@@ -19,40 +19,10 @@
 <interface>
   <template class="GtkListItem">
     <property name="child">
-      <object class="GtkBox">
-        <property name="orientation">horizontal</property>
-        <property name="spacing">6</property>
-        <child>
-          <object class="GtkGestureClick">
-            <property name="button">3</property>
-            <signal name="released" handler="pidgin_conversation_member_list_context_cb"/>
-          </object>
-        </child>
-        <child>
-          <object class="GtkBox">
-            <property name="orientation">horizontal</property>
-            <child>
-              <object class="PidginBadges">
-                <binding name="badges">
-                  <lookup name="badges" type="PurpleConversationMember">
-                    <lookup name="item">GtkListItem</lookup>
-                  </lookup>
-                </binding>
-              </object>
-            </child>
-            <child>
-              <object class="GtkLabel">
-                <property name="xalign">0</property>
-                <property name="use-markup">1</property>
-                <binding name="label">
-                  <lookup name="name-for-display" type="PurpleConversationMember">
-                    <lookup name="item">GtkListItem</lookup>
-                  </lookup>
-                </binding>
-              </object>
-            </child>
-          </object>
-        </child>
+      <object class="PidginConversationMember">
+        <binding name="conversation-member">
+          <lookup name="item">GtkListItem</lookup>
+        </binding>
       </object>
     </property>
   </template>
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
@@ -33,6 +33,7 @@
     <file compressed="true" preprocess="xml-stripblanks">channeljoindialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">contactlist.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">contactlistitem.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">conversationmember.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">conversationmemberlistitem.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">display-item.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">display-window.ui</file>
