pidgin/pidgin

Make the DisplayWindow use a GtkListView

16 months ago, Gary Kramlich
5ac6db2b8c2d
Parents 16d2e16bd24f
Children 18adb1710a54
Make the DisplayWindow use a GtkListView

The traditional pattern of using a TreeView and Notebook/stack has been
removed. This now use a ListView with a model that contains the child widgets,
and an AdwBin where we swap the children in and out.

I didn't implement everything, as now that this window is a bit more mixed
content, things aren't as clear cut. I left some TODO's in the code describing
these scenarios.

Testing Done:
Tested opening and closing chats in the ui. Verified the keybinds work for navigating everything thing.

Bugs closed: PIDGIN-17752

Reviewed at https://reviews.imfreedom.org/r/2227/
--- a/pidgin/meson.build Wed Feb 08 08:59:47 2023 -0600
+++ b/pidgin/meson.build Thu Feb 09 22:51:59 2023 -0600
@@ -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',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgindisplayitem.c Thu Feb 09 22:51:59 2023 -0600
@@ -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]);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/pidgindisplayitem.h Thu Feb 09 22:51:59 2023 -0600
@@ -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's 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.
+ *
+ * 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 there 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 */
--- a/pidgin/pidgindisplaywindow.c Wed Feb 08 08:59:47 2023 -0600
+++ b/pidgin/pidgindisplaywindow.c Thu Feb 09 22:51:59 2023 -0600
@@ -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,34 @@
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));
+ g_clear_object(&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 +465,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 +508,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 +524,178 @@
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);
+ g_clear_object(&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 +703,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;
}
--- a/pidgin/resources/Display/window.ui Wed Feb 08 08:59:47 2023 -0600
+++ b/pidgin/resources/Display/window.ui Thu Feb 09 22:51:59 2023 -0600
@@ -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>