# HG changeset patch
# Node ID 1c836f51daa7
# Parent  b9ef2851fdc0
diff --git a/pidgin/meson.build b/pidgin/meson.build
--- a/pidgin/meson.build
+++ b/pidgin/meson.build
@@ -38,6 +38,7 @@
 	'pidgincontactlist.c',
 	'pidgincontactlistwindow.c',
 	'pidgindebug.c',
+	'pidgindisplayitem.c',
 	'pidgindisplaywindow.c',
 	'pidginiconname.c',
 	'pidgininfopane.c',
@@ -104,6 +105,7 @@
 	'pidgincontactlist.h',
 	'pidgincontactlistwindow.h',
 	'pidgincore.h',
+	'pidgindisplayitem.h',
 	'pidgindisplaywindow.h',
 	'pidgindebug.h',
 	'pidginiconname.h',
diff --git a/pidgin/pidgindisplayitem.h b/pidgin/pidgindisplayitem.h
new file mode 100644
--- /dev/null
+++ b/pidgin/pidgindisplayitem.h
@@ -0,0 +1,203 @@
+/*
+ * 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_DISPLAY_ITEM_H
+#define PIDGIN_DISPLAY_ITEM_H
+
+#include <glib.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/**
+ * PidginDisplayItem:
+ *
+ * A class that represents used by [class@Pidgin.DisplayWindow] to represent
+ * all of the displays items.
+ *
+ * Since: 3.0.0
+ */
+
+#define PIDGIN_TYPE_DISPLAY_ITEM (pidgin_display_item_get_type())
+G_DECLARE_FINAL_TYPE(PidginDisplayItem, pidgin_display_item,
+                     PIDGIN, DISPLAY_ITEM, GObject)
+
+/**
+ * pidgin_display_item_new:
+ * @child: The [class@Gtk.Widget] that this item represents.
+ * @id: A unique identifier that will be used internally.
+ *
+ * Creates a new #PidginDisplayItem instance.
+ *
+ * Returns: (transfer full): The new #PidginDisplayItem instance.
+ */
+PidginDisplayItem *pidgin_display_item_new(GtkWidget *child, const char *id);
+
+/**
+ * pidgin_display_item_get_widget:
+ * @item: The instance.
+ *
+ * Gets the widget that @item was created for.
+ *
+ * Returns: (transfer none): The widget widget.
+ *
+ * Since: 3.0.0
+ */
+GtkWidget *pidgin_display_item_get_widget(PidginDisplayItem *item);
+
+/**
+ * pidgin_display_item_get_id:
+ * @item: The instance.
+ *
+ * Gets the unique identifier of @item.
+ *
+ * Returns: The unique identifier of @item.
+ *
+ * Since: 3.0.0
+ */
+const char *pidgin_display_item_get_id(PidginDisplayItem *item);
+
+/**
+ * pidgin_display_item_get_title:
+ * @item: The instance.
+ *
+ * Gets the title of @item.
+ *
+ * Returns: (nullable): The title of @item.
+ *
+ * Since: 3.0.0
+ */
+const char *pidgin_display_item_get_title(PidginDisplayItem *item);
+
+/**
+ * pidgin_display_item_set_title:
+ * @item: The instance.
+ * @title: (nullable): The new title.
+ *
+ * Sets the title for @item to @title.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_display_item_set_title(PidginDisplayItem *item, const char *title);
+
+/**
+ * pidgin_display_item_get_icon_name:
+ * @item: The instance.
+ *
+ * Gets the icon name if any that should be used when displaying @item.
+ *
+ * Returns: (nullable): The icon name to use.
+ *
+ * Since: 3.0.0
+ */
+const char *pidgin_display_item_get_icon_name(PidginDisplayItem *item);
+
+/**
+ * pidgin_display_item_set_icon_name:
+ * @item: The instance.
+ * @icon_name: (nullable): The icon-name to use.
+ *
+ * Sets the icon name that should be used when displaying @item.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_display_item_set_icon_name(PidginDisplayItem *item, const char *icon_name);
+
+/**
+ * pidgin_display_item_get_needs_attention:
+ * @item: The instance.
+ *
+ * Gets whether or not @item needs attention.
+ *
+ * Returns: %TRUE if @item needs attention otherwise %FALSE.
+ *
+ * Since: 3.0.0
+ */
+gboolean pidgin_display_item_get_needs_attention(PidginDisplayItem *item);
+
+/**
+ * pidgin_display_item_set_needs_attention:
+ * @item: The instance.
+ * @needs_attention: Whether or not attention is needed.
+ *
+ * Sets whether or not @item needs attention.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_display_item_set_needs_attention(PidginDisplayItem *item, gboolean needs_attention);
+
+/**
+ * pidgin_display_item_get_badge_number:
+ * @item: The instance.
+ *
+ * Gets the number that should be displayed in the badge for @item.
+ *
+ * Returns: The value to display or %0 to display nothing.
+ *
+ * Since: 3.0.0
+ */
+guint pidgin_display_item_get_badge_number(PidginDisplayItem *item);
+
+/**
+ * pidgin_display_item_set_badge_number:
+ * @item: The instance.
+ * @badge_number: The new value.
+ *
+ * Sets the values to be displayed in the badge for @item to @badge_number. A
+ * value of %0 indicates that the badge should not be displayed.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_display_item_set_badge_number(PidginDisplayItem *item, guint badge_number);
+
+/**
+ * pidgin_display_item_get_children:
+ * @item: The instance.
+ *
+ * Gets the children for @item if there are any.
+ *
+ * Returns: (transfer none) (nullable): The children of @item if the are any.
+ *
+ * Since: 3.0.0
+ */
+GListModel *pidgin_display_item_get_children(PidginDisplayItem *item);
+
+/**
+ * pidgin_display_item_set_children:
+ * @item: The instance:
+ * @children: (nullable): The new children.
+ *
+ * Sets the children of @item to @children. If @children is not %NULL then the
+ * model must have an item type of [class@Pidgin.DisplayItem].
+ *
+ * Since: 3.0.0
+ */
+void pidgin_display_item_set_children(PidginDisplayItem *item, GListModel *children);
+
+G_END_DECLS
+
+#endif /* PIDGIN_DISPLAY_ITEM_H */
diff --git a/pidgin/pidgindisplayitem.c b/pidgin/pidgindisplayitem.c
new file mode 100644
--- /dev/null
+++ b/pidgin/pidgindisplayitem.c
@@ -0,0 +1,421 @@
+/*
+ * 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 "pidgindisplayitem.h"
+
+struct _PidginDisplayItem {
+	GObject parent;
+
+	GtkWidget *widget;
+
+	char *id;
+	char *title;
+
+	char *icon_name;
+
+	gboolean needs_attention;
+
+	guint badge_number;
+
+	GListModel *children;
+};
+
+enum {
+	PROP_0,
+	PROP_WIDGET,
+	PROP_ID,
+	PROP_TITLE,
+	PROP_ICON_NAME,
+	PROP_NEEDS_ATTENTION,
+	PROP_BADGE_NUMBER,
+	PROP_CHILDREN,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL,};
+
+G_DEFINE_TYPE(PidginDisplayItem, pidgin_display_item, G_TYPE_OBJECT)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+pidgin_display_item_set_widget(PidginDisplayItem *item, GtkWidget *widget) {
+	g_return_if_fail(PIDGIN_IS_DISPLAY_ITEM(item));
+
+	if(g_set_object(&item->widget, widget)) {
+		g_object_notify_by_pspec(G_OBJECT(item), properties[PROP_WIDGET]);
+	}
+}
+
+static void
+pidgin_display_item_set_id(PidginDisplayItem *item, const char *id) {
+	g_return_if_fail(PIDGIN_IS_DISPLAY_ITEM(item));
+	g_return_if_fail(id != NULL);
+
+	if(item->id == id) {
+		return;
+	}
+
+	g_free(item->id);
+	item->id = g_strdup(id);
+
+	g_object_notify_by_pspec(G_OBJECT(item), properties[PROP_ID]);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+pidgin_display_item_dispose(GObject *obj) {
+	PidginDisplayItem *item = PIDGIN_DISPLAY_ITEM(obj);
+
+	g_clear_object(&item->widget);
+	g_clear_object(&item->children);
+
+	G_OBJECT_CLASS(pidgin_display_item_parent_class)->dispose(obj);
+}
+
+static void
+pidgin_display_item_finalize(GObject *obj) {
+	PidginDisplayItem *item = PIDGIN_DISPLAY_ITEM(obj);
+
+	g_clear_pointer(&item->id, g_free);
+	g_clear_pointer(&item->title, g_free);
+	g_clear_pointer(&item->icon_name, g_free);
+
+	G_OBJECT_CLASS(pidgin_display_item_parent_class)->finalize(obj);
+}
+
+static void
+pidgin_display_item_get_property(GObject *obj, guint param_id, GValue *value,
+                                 GParamSpec *pspec)
+{
+	PidginDisplayItem *item = PIDGIN_DISPLAY_ITEM(obj);
+
+	switch(param_id) {
+		case PROP_WIDGET:
+			g_value_set_object(value, pidgin_display_item_get_widget(item));
+			break;
+		case PROP_ID:
+			g_value_set_string(value, pidgin_display_item_get_id(item));
+			break;
+		case PROP_TITLE:
+			g_value_set_string(value, pidgin_display_item_get_title(item));
+			break;
+		case PROP_ICON_NAME:
+			g_value_set_string(value, pidgin_display_item_get_icon_name(item));
+			break;
+		case PROP_NEEDS_ATTENTION:
+			g_value_set_boolean(value,
+			                    pidgin_display_item_get_needs_attention(item));
+			break;
+		case PROP_BADGE_NUMBER:
+			g_value_set_uint(value,
+			                 pidgin_display_item_get_badge_number(item));
+			break;
+		case PROP_CHILDREN:
+			g_value_set_object(value, pidgin_display_item_get_children(item));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_display_item_set_property(GObject *obj, guint param_id,
+                                 const GValue *value, GParamSpec *pspec)
+{
+	PidginDisplayItem *item = PIDGIN_DISPLAY_ITEM(obj);
+
+	switch(param_id) {
+		case PROP_WIDGET:
+			pidgin_display_item_set_widget(item, g_value_get_object(value));
+			break;
+		case PROP_ID:
+			pidgin_display_item_set_id(item, g_value_get_string(value));
+			break;
+		case PROP_TITLE:
+			pidgin_display_item_set_title(item, g_value_get_string(value));
+			break;
+		case PROP_ICON_NAME:
+			pidgin_display_item_set_icon_name(item, g_value_get_string(value));
+			break;
+		case PROP_NEEDS_ATTENTION:
+			pidgin_display_item_set_needs_attention(item,
+			                                        g_value_get_boolean(value));
+			break;
+		case PROP_BADGE_NUMBER:
+			pidgin_display_item_set_badge_number(item,
+			                                     g_value_get_uint(value));
+			break;
+		case PROP_CHILDREN:
+			pidgin_display_item_set_children(item, g_value_get_object(value));
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+			break;
+	}
+}
+
+static void
+pidgin_display_item_init(G_GNUC_UNUSED PidginDisplayItem *window) {
+}
+
+static void
+pidgin_display_item_class_init(PidginDisplayItemClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+	obj_class->dispose = pidgin_display_item_dispose;
+	obj_class->finalize = pidgin_display_item_finalize;
+	obj_class->get_property = pidgin_display_item_get_property;
+	obj_class->set_property = pidgin_display_item_set_property;
+
+	/**
+	 * PidginDisplayItem:widget:
+	 *
+	 * The [class@Gtk.Widget] that this item is for.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_WIDGET] = g_param_spec_object(
+		"widget", "widget",
+		"The widget for this item",
+		GTK_TYPE_WIDGET,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PidginDisplayItem:id:
+	 *
+	 * A unique identifier for this item. This is used for things like
+	 * remembering positions and selections.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ID] = g_param_spec_string(
+		"id", "id",
+		"A unique identifier for the item",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PidginDisplayItem:title:
+	 *
+	 * The title that should be displayed for this item.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_TITLE] = g_param_spec_string(
+		"title", "title",
+		"The title for the item",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PidginDisplayItem:icon-name:
+	 *
+	 * The icon name to use for this item.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_ICON_NAME] = g_param_spec_string(
+		"icon-name", "icon-name",
+		"The icon-name to use for this item",
+		NULL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PidginDisplayItem:needs-attention:
+	 *
+	 * Determines whether the item should show that it needs attention or not.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_NEEDS_ATTENTION] = g_param_spec_boolean(
+		"needs-attention", "needs-attention",
+		"Whether or not the item needs attention",
+		FALSE,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PidginDisplayItem:badge-number:
+	 *
+	 * The number that should be shown in the badge. Typically this is an
+	 * unread count. If this is 0 it should not be displayed.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_BADGE_NUMBER] = g_param_spec_uint(
+		"badge-number", "badge-number",
+		"The number to show in the badge",
+		0, G_MAXUINT, 0,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	/**
+	 * PidginDisplayItem:children:
+	 *
+	 * A [iface@Gio.ListModel] of child items. The type of the model needs to
+	 * be [class@Pidgin.DisplayItem].
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_CHILDREN] = g_param_spec_object(
+		"children", "children",
+		"A GListModel of child items",
+		G_TYPE_LIST_MODEL,
+		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * API
+ *****************************************************************************/
+PidginDisplayItem *
+pidgin_display_item_new(GtkWidget *widget, const char *id) {
+	g_return_val_if_fail(GTK_IS_WIDGET(widget), NULL);
+	g_return_val_if_fail(id != NULL, NULL);
+
+	return g_object_new(
+		PIDGIN_TYPE_DISPLAY_ITEM,
+		"widget", widget,
+		"id", id,
+		NULL);
+}
+
+GtkWidget *
+pidgin_display_item_get_widget(PidginDisplayItem *item) {
+	g_return_val_if_fail(PIDGIN_IS_DISPLAY_ITEM(item), NULL);
+
+	return item->widget;
+}
+
+const char *
+pidgin_display_item_get_id(PidginDisplayItem *item) {
+	g_return_val_if_fail(PIDGIN_IS_DISPLAY_ITEM(item), NULL);
+
+	return item->id;
+}
+
+const char *
+pidgin_display_item_get_title(PidginDisplayItem *item) {
+	g_return_val_if_fail(PIDGIN_IS_DISPLAY_ITEM(item), NULL);
+
+	return item->title;
+}
+
+void
+pidgin_display_item_set_title(PidginDisplayItem *item, const char *title) {
+	g_return_if_fail(PIDGIN_IS_DISPLAY_ITEM(item));
+
+	if(item->title == title) {
+		return;
+	}
+
+	g_free(item->title);
+	item->title = g_strdup(title);
+
+	g_object_notify_by_pspec(G_OBJECT(item), properties[PROP_TITLE]);
+}
+
+const char *
+pidgin_display_item_get_icon_name(PidginDisplayItem *item) {
+	g_return_val_if_fail(PIDGIN_IS_DISPLAY_ITEM(item), NULL);
+
+	return item->icon_name;
+}
+
+void
+pidgin_display_item_set_icon_name(PidginDisplayItem *item,
+                                  const char *icon_name)
+{
+	g_return_if_fail(PIDGIN_IS_DISPLAY_ITEM(item));
+
+	if(item->icon_name == icon_name) {
+		return;
+	}
+
+	g_free(item->icon_name);
+	item->icon_name = g_strdup(icon_name);
+
+	g_object_notify_by_pspec(G_OBJECT(item), properties[PROP_ICON_NAME]);
+}
+
+gboolean
+pidgin_display_item_get_needs_attention(PidginDisplayItem *item) {
+	g_return_val_if_fail(PIDGIN_IS_DISPLAY_ITEM(item), FALSE);
+
+	return item->needs_attention;
+}
+
+void
+pidgin_display_item_set_needs_attention(PidginDisplayItem *item,
+                                        gboolean needs_attention)
+{
+	g_return_if_fail(PIDGIN_IS_DISPLAY_ITEM(item));
+
+	if(item->needs_attention != needs_attention) {
+		item->needs_attention = needs_attention;
+
+		g_object_notify_by_pspec(G_OBJECT(item),
+		                         properties[PROP_NEEDS_ATTENTION]);
+	}
+}
+
+guint
+pidgin_display_item_get_badge_number(PidginDisplayItem *item) {
+	g_return_val_if_fail(PIDGIN_IS_DISPLAY_ITEM(item), 0);
+
+	return item->badge_number;
+}
+
+void
+pidgin_display_item_set_badge_number(PidginDisplayItem *item,
+                                     guint badge_number)
+{
+	g_return_if_fail(PIDGIN_IS_DISPLAY_ITEM(item));
+
+	if(item->badge_number != badge_number) {
+		item->badge_number = badge_number;
+
+		g_object_notify_by_pspec(G_OBJECT(item),
+		                         properties[PROP_BADGE_NUMBER]);
+	}
+}
+
+GListModel *
+pidgin_display_item_get_children(PidginDisplayItem *item) {
+	g_return_val_if_fail(PIDGIN_IS_DISPLAY_ITEM(item), NULL);
+
+	return item->children;
+}
+
+void
+pidgin_display_item_set_children(PidginDisplayItem *item,
+                                 GListModel *children)
+{
+	g_return_if_fail(PIDGIN_IS_DISPLAY_ITEM(item));
+
+	if(g_set_object(&item->children, children)) {
+		g_object_notify_by_pspec(G_OBJECT(item), properties[PROP_CHILDREN]);
+	}
+}
diff --git a/pidgin/pidgindisplaywindow.c b/pidgin/pidgindisplaywindow.c
--- a/pidgin/pidgindisplaywindow.c
+++ b/pidgin/pidgindisplaywindow.c
@@ -29,16 +29,10 @@
 #include "gtkconv.h"
 #include "gtkdialogs.h"
 #include "gtkutils.h"
+#include "pidgindisplayitem.h"
 #include "pidgininvitedialog.h"
 
 enum {
-	PIDGIN_DISPLAY_WINDOW_COLUMN_OBJECT,
-	PIDGIN_DISPLAY_WINDOW_COLUMN_NAME,
-	PIDGIN_DISPLAY_WINDOW_COLUMN_ICON,
-	PIDGIN_DISPLAY_WINDOW_COLUMN_MARKUP,
-};
-
-enum {
 	SIG_CONVERSATION_SWITCHED,
 	N_SIGNALS,
 };
@@ -47,18 +41,13 @@
 struct _PidginDisplayWindow {
 	GtkApplicationWindow parent;
 
-	GtkWidget *vbox;
-
 	GtkWidget *view;
-	GtkTreeSelection *selection;
-	GtkTreeStore *model;
+	GtkWidget *bin;
 
-	GtkWidget *stack;
+	GListModel *base_model;
+	GListModel *selection_model;
 
-	GtkWidget *contact_list;
-	GtkWidget *notification_list;
-
-	GtkTreePath *conversation_path;
+	GListStore *conversation_model;
 };
 
 G_DEFINE_TYPE(PidginDisplayWindow, pidgin_display_window,
@@ -74,21 +63,44 @@
                                           const gchar **actions,
                                           gboolean enabled)
 {
-	gint i = 0;
-
-	for(i = 0; actions[i] != NULL; i++) {
+	for(int i = 0; actions[i] != NULL; i++) {
 		GAction *action = NULL;
 		const gchar *name = actions[i];
 
 		action = g_action_map_lookup_action(map, name);
 		if(action != NULL) {
 			g_simple_action_set_enabled(G_SIMPLE_ACTION(action), enabled);
-		} else {
-			g_critical("Failed to find action named %s", name);
 		}
 	}
 }
 
+static GListModel *
+pidgin_display_window_create_model(GObject *item,
+                                   G_GNUC_UNUSED gpointer data)
+{
+	GListModel *model = NULL;
+
+	model = pidgin_display_item_get_children(PIDGIN_DISPLAY_ITEM(item));
+	if(model != NULL) {
+		return g_object_ref(model);
+	}
+
+	return NULL;
+}
+
+static gboolean
+pidgin_display_window_find_conversation(gconstpointer a, gconstpointer b) {
+	PidginDisplayItem *item_a = PIDGIN_DISPLAY_ITEM((gpointer)a);
+	PidginDisplayItem *item_b = PIDGIN_DISPLAY_ITEM((gpointer)b);
+	PurpleConversation *conversation_a = NULL;
+	PurpleConversation *conversation_b = NULL;
+
+	conversation_a = g_object_get_data(G_OBJECT(item_a), "conversation");
+	conversation_b = g_object_get_data(G_OBJECT(item_b), "conversation");
+
+	return (conversation_a == conversation_b);
+}
+
 /******************************************************************************
  * Callbacks
  *****************************************************************************/
@@ -288,65 +300,6 @@
 /******************************************************************************
  * Callbacks
  *****************************************************************************/
-static void
-pidgin_display_window_selection_changed(GtkTreeSelection *selection,
-                                        gpointer data)
-{
-	PidginDisplayWindow *window = PIDGIN_DISPLAY_WINDOW(data);
-	GtkTreeModel *model = NULL;
-	GtkTreeIter iter;
-	gboolean changed = FALSE;
-
-	if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
-		GObject *obj;
-		gboolean is_conversation = FALSE;
-		gboolean im_selected = FALSE, chat_selected = FALSE;
-		gchar *name = NULL;
-
-		gtk_tree_model_get(model, &iter,
-		                   PIDGIN_DISPLAY_WINDOW_COLUMN_NAME, &name,
-		                   PIDGIN_DISPLAY_WINDOW_COLUMN_OBJECT, &obj,
-		                   -1);
-
-		adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(window->stack),
-		                                      name);
-		g_free(name);
-
-		changed = TRUE;
-
-		/* If a conversation is selected, enable the generic conversation
-		 * actions.
-		 */
-		is_conversation = PURPLE_IS_CONVERSATION(obj);
-		pidgin_display_window_actions_set_enabled(G_ACTION_MAP(window),
-							       pidgin_display_window_conversation_actions,
-							       is_conversation);
-
-		/* If an IM is selected, enable the IM-specific actions otherwise
-		 * disable them.
-		 */
-		im_selected = PURPLE_IS_IM_CONVERSATION(obj);
-		pidgin_display_window_actions_set_enabled(G_ACTION_MAP(window),
-							       pidgin_display_window_im_conversation_actions,
-							       im_selected);
-
-		/* If a chat is selected, enable the chat-specific actions otherwise
-		 * disable them.
-		 */
-		chat_selected = PURPLE_IS_CHAT_CONVERSATION(obj);
-		pidgin_display_window_actions_set_enabled(G_ACTION_MAP(window),
-							       pidgin_display_window_chat_conversation_actions,
-							       chat_selected);
-
-		g_clear_object(&obj);
-	}
-
-	if(!changed) {
-		adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(window->stack),
-		                                      "__conversations__");
-	}
-}
-
 static gboolean
 pidgin_display_window_key_pressed_cb(G_GNUC_UNUSED GtkEventControllerKey *controller,
                                      guint keyval,
@@ -356,7 +309,6 @@
 {
 	PidginDisplayWindow *window = data;
 
-	/* If CTRL was held down... */
 	if (state & GDK_CONTROL_MASK) {
 		switch (keyval) {
 			case GDK_KEY_Page_Down:
@@ -365,18 +317,22 @@
 				pidgin_display_window_select_next(window);
 				return TRUE;
 				break;
-
+			case GDK_KEY_Home:
+				pidgin_display_window_select_first(window);
+				return TRUE;
+				break;
+			case GDK_KEY_End:
+				pidgin_display_window_select_last(window);
+				return TRUE;
+				break;
 			case GDK_KEY_Page_Up:
 			case GDK_KEY_KP_Page_Up:
 			case '[':
 				pidgin_display_window_select_previous(window);
 				return TRUE;
 				break;
-		} /* End of switch */
-	}
-
-	/* If ALT (or whatever) was held down... */
-	else if (state & GDK_ALT_MASK) {
+		}
+	} else if (state & GDK_ALT_MASK) {
 		if ('1' <= keyval && keyval <= '9') {
 			guint switchto = keyval - '1';
 			pidgin_display_window_select_nth(window, switchto);
@@ -388,41 +344,57 @@
 	return FALSE;
 }
 
+static void
+pidgin_display_window_selected_item_changed_cb(GObject *self,
+                                               G_GNUC_UNUSED GParamSpec *pspec,
+                                               gpointer data)
+{
+	PidginDisplayItem *item = NULL;
+	PidginDisplayWindow *window = data;
+	PurpleConversation *conversation = NULL;
+	GtkSingleSelection *selection = GTK_SINGLE_SELECTION(self);
+	GtkTreeListRow *row = NULL;
+	GtkWidget *widget = NULL;
+	gboolean is_conversation = FALSE;
+	gboolean is_im_conversation = FALSE;
+	gboolean is_chat_conversation = FALSE;
+
+	row = gtk_single_selection_get_selected_item(selection);
+
+	item = gtk_tree_list_row_get_item(row);
+
+	/* Toggle whether actions should be enabled or disabled. */
+	conversation = g_object_get_data(G_OBJECT(item), "conversation");
+	if(PURPLE_IS_CONVERSATION(conversation)) {
+		is_conversation = PURPLE_IS_CONVERSATION(conversation);
+		is_im_conversation = PURPLE_IS_IM_CONVERSATION(conversation);
+		is_chat_conversation = PURPLE_IS_CHAT_CONVERSATION(conversation);
+	}
+
+	pidgin_display_window_actions_set_enabled(G_ACTION_MAP(window),
+	                                          pidgin_display_window_conversation_actions,
+	                                          is_conversation);
+	pidgin_display_window_actions_set_enabled(G_ACTION_MAP(window),
+	                                          pidgin_display_window_im_conversation_actions,
+	                                          is_im_conversation);
+	pidgin_display_window_actions_set_enabled(G_ACTION_MAP(window),
+	                                          pidgin_display_window_chat_conversation_actions,
+	                                          is_chat_conversation);
+
+	widget = pidgin_display_item_get_widget(item);
+	if(GTK_IS_WIDGET(widget)) {
+		adw_bin_set_child(ADW_BIN(window->bin), widget);
+	}
+}
+
 /******************************************************************************
- * GObjectImplementation
+ * GObject Implementation
  *****************************************************************************/
 static void
 pidgin_display_window_dispose(GObject *obj) {
 	PidginDisplayWindow *window = PIDGIN_DISPLAY_WINDOW(obj);
 
-	if(GTK_IS_TREE_MODEL(window->model)) {
-		GtkTreeModel *model = GTK_TREE_MODEL(window->model);
-		GtkTreeIter parent, iter;
-
-		gtk_tree_model_get_iter(model, &parent, window->conversation_path);
-		if(gtk_tree_model_iter_children(model, &iter, &parent)) {
-			gboolean valid = FALSE;
-
-			/* gtk_tree_store_remove moves the iter to the next item at the
-			 * same level, so we abuse that to do our iteration.
-			 */
-			do {
-				PurpleConversation *conversation = NULL;
-
-				gtk_tree_model_get(model, &iter,
-				                   PIDGIN_DISPLAY_WINDOW_COLUMN_OBJECT, &conversation,
-				                   -1);
-
-				if(PURPLE_IS_CONVERSATION(conversation)) {
-					pidgin_conversation_detach(conversation);
-
-					valid = gtk_tree_store_remove(window->model, &iter);
-				}
-			} while(valid);
-		}
-
-		g_clear_pointer(&window->conversation_path, gtk_tree_path_free);
-	}
+	g_clear_object(&window->conversation_model);
 
 	G_OBJECT_CLASS(pidgin_display_window_parent_class)->dispose(obj);
 }
@@ -430,46 +402,33 @@
 static void
 pidgin_display_window_init(PidginDisplayWindow *window) {
 	GtkEventController *key = NULL;
-	GtkTreeIter iter;
+	GtkTreeListModel *tree_model = NULL;
 
 	gtk_widget_init_template(GTK_WIDGET(window));
 
+	/* Setup the tree list model. */
+	tree_model = gtk_tree_list_model_new(window->base_model, FALSE, TRUE,
+	                                     (GtkTreeListModelCreateModelFunc)pidgin_display_window_create_model,
+	                                     window, NULL);
+
+	/* Set the model of the selection to the tree model. */
+	gtk_single_selection_set_model(GTK_SINGLE_SELECTION(window->selection_model),
+	                               G_LIST_MODEL(tree_model));
+
+	/* Set the application and add all of our actions. */
 	gtk_window_set_application(GTK_WINDOW(window),
 	                           GTK_APPLICATION(g_application_get_default()));
 
 	g_action_map_add_action_entries(G_ACTION_MAP(window), win_entries,
 	                                G_N_ELEMENTS(win_entries), window);
 
+	/* Add a key controller. */
 	key = gtk_event_controller_key_new();
 	gtk_event_controller_set_propagation_phase(key, GTK_PHASE_CAPTURE);
 	g_signal_connect(G_OBJECT(key), "key-pressed",
 	                 G_CALLBACK(pidgin_display_window_key_pressed_cb),
 	                 window);
 	gtk_widget_add_controller(GTK_WIDGET(window), key);
-
-	/* Add our toplevels to the tree store. */
-	gtk_tree_store_append(window->model, &iter, NULL);
-	gtk_tree_store_set(window->model, &iter,
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_OBJECT, window->contact_list,
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_NAME, "__contacts__",
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_MARKUP, _("Contacts"),
-	                   -1);
-
-	gtk_tree_store_append(window->model, &iter, NULL);
-	gtk_tree_store_set(window->model, &iter,
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_OBJECT, window->notification_list,
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_NAME, "__notifications__",
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_MARKUP, _("Notifications"),
-	                   -1);
-
-	gtk_tree_store_append(window->model, &iter, NULL);
-	gtk_tree_store_set(window->model, &iter,
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_MARKUP, _("Conversations"),
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_NAME, "__conversations__",
-	                   -1);
-	gtk_tree_selection_select_iter(window->selection, &iter);
-	window->conversation_path = gtk_tree_model_get_path(GTK_TREE_MODEL(window->model),
-	                                                    &iter);
 }
 
 static void
@@ -505,27 +464,20 @@
 	);
 
 	gtk_widget_class_bind_template_child(widget_class, PidginDisplayWindow,
-	                                     vbox);
-
-	gtk_widget_class_bind_template_child(widget_class, PidginDisplayWindow,
-	                                     model);
-	gtk_widget_class_bind_template_child(widget_class, PidginDisplayWindow,
 	                                     view);
 	gtk_widget_class_bind_template_child(widget_class, PidginDisplayWindow,
-	                                     selection);
-
+	                                     bin);
 	gtk_widget_class_bind_template_child(widget_class, PidginDisplayWindow,
-	                                     stack);
+	                                     base_model);
 	gtk_widget_class_bind_template_child(widget_class, PidginDisplayWindow,
-	                                     contact_list);
+	                                     selection_model);
 	gtk_widget_class_bind_template_child(widget_class, PidginDisplayWindow,
-	                                     notification_list);
-
-	gtk_widget_class_bind_template_callback(widget_class,
-	                                        pidgin_display_window_selection_changed);
+	                                     conversation_model);
 
 	gtk_widget_class_bind_template_callback(widget_class,
 	                                        pidgin_display_window_key_pressed_cb);
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_display_window_selected_item_changed_cb);
 }
 
 /******************************************************************************
@@ -555,30 +507,15 @@
                           PurpleConversation *conversation)
 {
 	PidginConversation *gtkconv = NULL;
-	GtkTreeIter parent, iter;
-	GtkTreeModel *model = NULL;
-	const gchar *markup = NULL;
-	gboolean expand = FALSE;
 
 	g_return_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window));
 	g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
 
-	model = GTK_TREE_MODEL(window->model);
-	if(!gtk_tree_model_get_iter(model, &parent, window->conversation_path)) {
-		/* If we can't find the conversation_path we have to bail. */
-		g_warning("couldn't get an iterator to conversation_path");
-
-		return;
-	}
-
-	if(!gtk_tree_model_iter_has_child(model, &parent)) {
-		expand = TRUE;
-	}
-
-	markup = purple_conversation_get_name(conversation);
-
 	gtkconv = PIDGIN_CONVERSATION(conversation);
 	if(gtkconv != NULL) {
+		PidginDisplayItem *item = NULL;
+		const char *value = NULL;
+
 		GtkWidget *parent = gtk_widget_get_parent(gtkconv->tab_cont);
 
 		if(GTK_IS_WIDGET(parent)) {
@@ -586,233 +523,177 @@
 			gtk_widget_unparent(gtkconv->tab_cont);
 		}
 
-		adw_view_stack_add_named(ADW_VIEW_STACK(window->stack),
-		                         gtkconv->tab_cont, markup);
-		gtk_widget_show(gtkconv->tab_cont);
+		value = purple_conversation_get_name(conversation);
+		item = pidgin_display_item_new(gtkconv->tab_cont, value);
+		g_object_set_data(G_OBJECT(item), "conversation", conversation);
+
+		g_object_bind_property(conversation, "title",
+		                       item, "title",
+		                       G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+		g_list_store_append(window->conversation_model, item);
 
 		if(GTK_IS_WIDGET(parent)) {
 			g_object_unref(gtkconv->tab_cont);
 		}
 	}
-
-	gtk_tree_store_prepend(window->model, &iter, &parent);
-	gtk_tree_store_set(window->model, &iter,
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_OBJECT, conversation,
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_NAME, markup,
-	                   PIDGIN_DISPLAY_WINDOW_COLUMN_MARKUP, markup,
-	                   -1);
-
-	/* If we just added the first child, expand the parent. */
-	if(expand) {
-		gtk_tree_view_expand_row(GTK_TREE_VIEW(window->view),
-		                         window->conversation_path, FALSE);
-	}
-
-
-	if(!gtk_widget_is_visible(GTK_WIDGET(window))) {
-		gtk_widget_show(GTK_WIDGET(window));
-	}
 }
 
 void
 pidgin_display_window_remove(PidginDisplayWindow *window,
                              PurpleConversation *conversation)
 {
-	GtkTreeIter parent, iter;
-	GtkTreeModel *model = NULL;
-	GObject *obj = NULL;
+	PidginDisplayItem *item = NULL;
+	guint position = 0;
+	gboolean found = FALSE;
+	gchar *id = NULL;
 
 	g_return_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window));
 	g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
 
-	model = GTK_TREE_MODEL(window->model);
-
-	if(!gtk_tree_model_get_iter(model, &parent, window->conversation_path)) {
-		/* The path is somehow invalid, so bail... */
-		return;
-	}
-
-	if(!gtk_tree_model_iter_children(model, &iter, &parent)) {
-		/* The conversations iter has no children. */
-		return;
-	}
-
-	do {
-		gtk_tree_model_get(model, &iter,
-		                   PIDGIN_DISPLAY_WINDOW_COLUMN_OBJECT, &obj,
-		                   -1);
+	/* Create a wrapper item for our find function. */
+	id = g_uuid_string_random();
+	item = g_object_new(PIDGIN_TYPE_DISPLAY_ITEM, "id", id, NULL);
+	g_free(id);
+	g_object_set_data(G_OBJECT(item), "conversation", conversation);
 
-		if(PURPLE_CONVERSATION(obj) == conversation) {
-			GtkWidget *child = NULL;
-			const gchar *name = NULL;
+	found = g_list_store_find_with_equal_func(window->conversation_model,
+	                                          item,
+	                                          pidgin_display_window_find_conversation,
+	                                          &position);
 
-			name = purple_conversation_get_name(conversation);
-			child = adw_view_stack_get_child_by_name(ADW_VIEW_STACK(window->stack),
-			                                         name);
-			if(GTK_IS_WIDGET(child)) {
-				gtk_widget_unparent(child);
-			}
+	g_clear_object(&item);
 
-			gtk_tree_store_remove(window->model, &iter);
-
-			g_clear_object(&obj);
-
-			break;
-		}
-
-		g_clear_object(&obj);
-	} while(gtk_tree_model_iter_next(model, &iter));
+	if(found) {
+		g_list_store_remove(window->conversation_model, position);
+	}
 }
 
 guint
-pidgin_display_window_get_count(PidginDisplayWindow *window) {
-	GtkSelectionModel *model = NULL;
-	guint count = 0;
-
-	g_return_val_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window), 0);
+pidgin_display_window_get_count(G_GNUC_UNUSED PidginDisplayWindow *window) {
+	/* TODO: This is only used by the gestures plugin and that will probably
+	 * need some rewriting and different api for a mixed content window list
+	 * this is now.
+	 */
 
-	model = adw_view_stack_get_pages(ADW_VIEW_STACK(window->stack));
-
-	count = g_list_model_get_n_items(G_LIST_MODEL(model));
-
-	g_object_unref(model);
-
-	return count;
+	return 0;
 }
 
 PurpleConversation *
 pidgin_display_window_get_selected(PidginDisplayWindow *window) {
-	PurpleConversation *conversation = NULL;
-	GtkTreeSelection *selection = NULL;
-	GtkTreeIter iter;
+	GtkSingleSelection *selection = NULL;
+	GtkTreeListRow *tree_row = NULL;
+	GObject *selected = NULL;
 
 	g_return_val_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window), NULL);
 
-	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
-	if(gtk_tree_selection_get_selected(selection, NULL, &iter)) {
+	selection = GTK_SINGLE_SELECTION(window->selection_model);
+	tree_row = gtk_single_selection_get_selected_item(selection);
+	selected = gtk_tree_list_row_get_item(tree_row);
 
-		gtk_tree_model_get(GTK_TREE_MODEL(window->model), &iter,
-		                   PIDGIN_DISPLAY_WINDOW_COLUMN_OBJECT, &conversation,
-		                   -1);
+	if(PIDGIN_IS_DISPLAY_ITEM(selected)) {
+		return g_object_get_data(selected, "conversation");
 	}
 
-	return conversation;
+	return NULL;
 }
 
 void
 pidgin_display_window_select(PidginDisplayWindow *window,
                              PurpleConversation *conversation)
 {
-	const gchar *name = NULL;
+	/* TODO: This is used by the unity and gestures plugins, but I'm really not
+	 * sure how to make this work yet without some hard-coding or something, so
+	 * I'm opting to stub it out for now.
+	 */
 
 	g_return_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window));
 	g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
-
-	name = purple_conversation_get_name(conversation);
-	adw_view_stack_set_visible_child_name(ADW_VIEW_STACK(window->stack), name);
 }
 
 void
 pidgin_display_window_select_previous(PidginDisplayWindow *window) {
-	GtkTreeIter iter;
-	GtkTreeModel *model = NULL;
-	GtkTreeSelection *selection = NULL;
-	gboolean set = FALSE;
+	GtkSingleSelection *selection = NULL;
+	guint position = 0;
 
 	g_return_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window));
 
-	model = GTK_TREE_MODEL(window->model);
-
-	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
-	if(gtk_tree_selection_get_selected(selection, NULL, &iter)) {
-		if(gtk_tree_model_iter_previous(model, &iter)) {
-			gtk_tree_selection_select_iter(selection, &iter);
-			set = TRUE;
-		}
+	selection = GTK_SINGLE_SELECTION(window->selection_model);
+	position = gtk_single_selection_get_selected(selection);
+	if(position == 0) {
+		position = g_list_model_get_n_items(G_LIST_MODEL(selection)) - 1;
+	} else {
+		position = position - 1;
 	}
 
-	if(!set) {
-		pidgin_display_window_select_last(window);
-	}
+	gtk_single_selection_set_selected(selection, position);
 }
 
-
 void
 pidgin_display_window_select_next(PidginDisplayWindow *window) {
-	GtkTreeIter iter;
-	GtkTreeModel *model = NULL;
-	GtkTreeSelection *selection = NULL;
-	gboolean set = FALSE;
+	GtkSingleSelection *selection = NULL;
+	guint position = 0;
 
 	g_return_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window));
 
-	model = GTK_TREE_MODEL(window->model);
-
-	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
-	if(gtk_tree_selection_get_selected(selection, NULL, &iter)) {
-		if(gtk_tree_model_iter_next(model, &iter)) {
-			gtk_tree_selection_select_iter(selection, &iter);
-			set = TRUE;
-		}
+	selection = GTK_SINGLE_SELECTION(window->selection_model);
+	position = gtk_single_selection_get_selected(selection);
+	if(position + 1 >= g_list_model_get_n_items(G_LIST_MODEL(selection))) {
+		position = 0;
+	} else {
+		position = position + 1;
 	}
 
-	if(!set) {
-		pidgin_display_window_select_first(window);
-	}
+	gtk_single_selection_set_selected(selection, position);
 }
 
 void
 pidgin_display_window_select_first(PidginDisplayWindow *window) {
-	GtkTreeIter iter;
-	GtkTreeModel *model = NULL;
+	GtkSingleSelection *selection = NULL;
 
 	g_return_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window));
 
-	model = GTK_TREE_MODEL(window->model);
+	selection = GTK_SINGLE_SELECTION(window->selection_model);
 
-	if(gtk_tree_model_get_iter_first(model, &iter)) {
-		GtkTreeSelection *selection = NULL;
-
-		selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
-		gtk_tree_selection_select_iter(selection, &iter);
-	}
+	/* The selection has autoselect set to true, which won't do anything if
+	 * this is an invalid value.
+	 */
+	gtk_single_selection_set_selected(selection, 0);
 }
 
 void
 pidgin_display_window_select_last(PidginDisplayWindow *window) {
-	GtkTreeIter iter;
-	GtkTreeModel *model = NULL;
-	gint count = 0;
+	GtkSingleSelection *selection = NULL;
+	guint n_items = 0;
 
 	g_return_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window));
 
-	model = GTK_TREE_MODEL(window->model);
-	count = gtk_tree_model_iter_n_children(model, NULL);
+	selection = GTK_SINGLE_SELECTION(window->selection_model);
+	n_items = g_list_model_get_n_items(G_LIST_MODEL(selection));
 
-	if(gtk_tree_model_iter_nth_child(model, &iter, NULL, count - 1)) {
-		GtkTreeSelection *selection = NULL;
-
-		selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
-		gtk_tree_selection_select_iter(selection, &iter);
-	}
+	/* The selection has autoselect set to true, which won't do anything if
+	 * this is an invalid value.
+	 */
+	gtk_single_selection_set_selected(selection, n_items - 1);
 }
 
 void
 pidgin_display_window_select_nth(PidginDisplayWindow *window,
                                  guint nth)
 {
-	GtkTreeIter iter;
-	GtkTreeModel *model = NULL;
+	GtkSingleSelection *selection = NULL;
+	guint n_items = 0;
 
 	g_return_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window));
 
-	model = GTK_TREE_MODEL(window->model);
+	selection = GTK_SINGLE_SELECTION(window->selection_model);
 
-	if(gtk_tree_model_iter_nth_child(model, &iter, NULL, nth)) {
-		GtkTreeSelection *selection = NULL;
-
-		selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
-		gtk_tree_selection_select_iter(selection, &iter);
+	/* The selection has autoselect set to true, but this isn't bound checking
+	 * or something on the children models, so we verify before setting.
+	 */
+	n_items = g_list_model_get_n_items(G_LIST_MODEL(selection));
+	if(nth < n_items) {
+		gtk_single_selection_set_selected(selection, nth);
 	}
 }
 
@@ -820,13 +701,24 @@
 pidgin_display_window_conversation_is_selected(PidginDisplayWindow *window,
                                                PurpleConversation *conversation)
 {
-	const gchar *name = NULL, *visible = NULL;
+	GtkSingleSelection *selection = NULL;
+	GObject *selected = NULL;
 
 	g_return_val_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window), FALSE);
 	g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE);
 
-	name = purple_conversation_get_name(conversation);
-	visible = adw_view_stack_get_visible_child_name(ADW_VIEW_STACK(window->stack));
+	selection = GTK_SINGLE_SELECTION(window->selection_model);
+	selected = gtk_single_selection_get_selected_item(selection);
+
+	if(PIDGIN_IS_DISPLAY_ITEM(selected)) {
+		PurpleConversation *selected_conversation = NULL;
 
-	return purple_strequal(name, visible);
+		selected_conversation = g_object_get_data(G_OBJECT(selected),
+		                                          "conversation");
+		if(selected_conversation != NULL) {
+			return (selected_conversation == conversation);
+		}
+	}
+
+	return FALSE;
 }
diff --git a/pidgin/resources/Display/window.ui b/pidgin/resources/Display/window.ui
--- a/pidgin/resources/Display/window.ui
+++ b/pidgin/resources/Display/window.ui
@@ -24,17 +24,54 @@
   <!-- interface-name Pidgin -->
   <!-- interface-description Internet Messenger -->
   <!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
-  <object class="GtkTreeStore" id="model">
-    <columns>
-      <!-- column-name conversation -->
-      <column type="GObject"/>
-      <!-- column-name name -->
-      <column type="gchararray"/>
-      <!-- column-name icon -->
-      <column type="GdkPixbuf"/>
-      <!-- column-name markup -->
-      <column type="gchararray"/>
-    </columns>
+  <object class="GListStore" id="base_model">
+    <property name="item-type">PidginDisplayItem</property>
+    <child>
+      <object class="PidginDisplayItem">
+        <property name="id">__contacts__</property>
+        <property name="title" translatable="1">Contacts</property>
+        <property name="widget">
+          <object class="GtkScrolledWindow">
+            <child>
+              <object class="PidginContactList" id="contact_list"/>
+            </child>
+          </object>
+        </property>
+      </object>
+    </child>
+    <child>
+      <object class="PidginDisplayItem">
+        <property name="id">__notifications__</property>
+        <property name="title" translatable="1">Notifications</property>
+        <property name="widget">
+          <object class="GtkScrolledWindow">
+            <child>
+              <object class="PidginNotificationList" id="notification_list">
+                <property name="orientation">vertical</property>
+              </object>
+            </child>
+          </object>
+        </property>
+      </object>
+    </child>
+    <child>
+      <object class="PidginDisplayItem">
+        <property name="id">__conversations__</property>
+        <property name="title" translatable="1">Conversations</property>
+        <property name="widget">
+          <object class="AdwStatusPage">
+            <property name="icon-name">mail-send-symbolic</property>
+            <property name="title" translatable="1">Conversations</property>
+            <property name="description" translatable="1">When you send a message to a friend or join a chat it will show up here!</property>
+          </object>
+        </property>
+        <property name="children">
+          <object class="GListStore" id="conversation_model">
+            <property name="item-type">PidginDisplayItem</property>
+          </object>
+        </property>
+      </object>
+    </child>
   </object>
   <template class="PidginDisplayWindow" parent="GtkApplicationWindow">
     <property name="show-menubar">1</property>
@@ -70,78 +107,84 @@
                     <property name="vexpand">1</property>
                     <property name="focusable">1</property>
                     <property name="propagate-natural-width">1</property>
-                    <property name="child">
-                      <object class="GtkTreeView" id="view">
-                        <property name="focusable">1</property>
-                        <property name="model">model</property>
-                        <property name="headers-visible">0</property>
-                        <property name="search-column">3</property>
-                        <child internal-child="selection">
-                          <object class="GtkTreeSelection" id="selection">
-                            <property name="mode">browse</property>
-                            <signal name="changed" handler="pidgin_display_window_selection_changed" object="PidginDisplayWindow" swapped="no"/>
+                    <style>
+                      <class name="sidebar"/>
+                    </style>
+                    <child>
+                      <object class="GtkListView" id="view">
+                        <property name="model">
+                          <object class="GtkSingleSelection" id="selection_model">
+                            <property name="autoselect">1</property>
+                            <property name="can-unselect">0</property>
+                            <signal name="notify::selected-item" handler="pidgin_display_window_selected_item_changed_cb"/>
                           </object>
-                        </child>
-                        <child>
-                          <object class="GtkTreeViewColumn" id="markup">
-                            <child>
-                              <object class="GtkCellRendererPixbuf" id="icon"/>
-                              <attributes>
-                                <attribute name="pixbuf">2</attribute>
-                              </attributes>
-                            </child>
-                            <child>
-                              <object class="GtkCellRendererText" id="name"/>
-                              <attributes>
-                                <attribute name="markup">3</attribute>
-                              </attributes>
-                            </child>
+                        </property>
+                        <property name="factory">
+                          <object class="GtkBuilderListItemFactory">
+                            <property name="bytes">
+<![CDATA[
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GtkListItem">
+    <property name="child">
+      <object class="GtkTreeExpander" id="expander">
+        <binding name="list-row">
+          <lookup name="item">GtkListItem</lookup>
+        </binding>
+        <property name="child">
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="xalign">0</property>
+                <property name="hexpand">0</property>
+                <binding name="label">
+                  <lookup name="title" type="PidginDisplayItem">
+                    <lookup name="item">expander</lookup>
+                  </lookup>
+                </binding>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel">
+                <style>
+                  <class name="accent"/>
+                  <class name="circular"/>
+                </style>
+                <property name="halign">end</property>
+                <binding name="visible">
+                  <lookup name="needs-attention" type="PidginDisplayItem">
+                    <lookup name="item">expander</lookup>
+                  </lookup>
+                </binding>
+                <binding name="label">
+                  <lookup name="badge-number" type="PidginDisplayItem">
+                    <lookup name="item">expander</lookup>
+                  </lookup>
+                </binding>
+              </object>
+            </child>
+          </object>
+        </property>
+      </object>
+    </property>
+  </template>
+</interface>
+]]>
+                            </property>
                           </object>
-                        </child>
+                        </property>
                       </object>
-                    </property>
+                    </child>
                   </object>
                 </child>
               </object>
             </child>
             <child>
-              <object class="AdwViewStack" id="stack">
-                <child>
-                  <object class="AdwViewStackPage">
-                    <property name="name">__contacts__</property>
-                    <property name="child">
-                      <object class="PidginContactList" id="contact_list">
-                      </object>
-                    </property>
-                  </object>
-                </child>
-
-                <child>
-                  <object class="AdwViewStackPage">
-                    <property name="name">__notifications__</property>
-                    <property name="child">
-                      <object class="GtkScrolledWindow">
-                        <child>
-                          <object class="PidginNotificationList" id="notification_list">
-                            <property name="orientation">vertical</property>
-                          </object>
-                        </child>
-                      </object>
-                    </property>
-                  </object>
-                </child>
-                <child>
-                  <object class="AdwViewStackPage">
-                    <property name="name">__conversations__</property>
-                    <property name="child">
-                      <object class="AdwStatusPage">
-                        <property name="icon-name">mail-send-symbolic</property>
-                        <property name="title" translatable="1">Conversations</property>
-                        <property name="description" translatable="1">When you send a message to a friend or join a chat it will show up here!</property>
-                      </object>
-                    </property>
-                  </object>
-                </child>
+              <object class="AdwBin" id="bin">
+                <property name="vexpand">1</property>
+                <property name="hexpand">1</property>
               </object>
             </child>
           </object>
