--- a/pidgin/plugins/disco/gtkdisco.c Thu Feb 23 23:44:43 2023 -0600
+++ b/pidgin/plugins/disco/gtkdisco.c Fri Feb 24 00:45:22 2023 -0600
@@ -51,7 +51,6 @@
pidgin_disco_list_destroy(PidginDiscoList *list)
- g_hash_table_destroy(list->services);
if (list->dialog && list->dialog->discolist == list) {
list->dialog->discolist = NULL;
@@ -181,11 +180,9 @@
g_simple_action_set_enabled(dialog->register_action, FALSE);
g_clear_pointer(&dialog->discolist, pidgin_disco_list_unref);
- gtk_tree_store_clear(dialog->model);
+ g_list_store_remove_all(dialog->root); pdl = dialog->discolist = pidgin_disco_list_new();
- pdl->services = g_hash_table_new_full(NULL, NULL, NULL,
- (GDestroyNotify)gtk_tree_row_reference_free);
pidgin_disco_list_ref(pdl);
@@ -245,64 +242,30 @@
-service_click_cb(G_GNUC_UNUSED GtkGestureClick *click,
- G_GNUC_UNUSED gint n_press,
- gdouble x, gdouble y, gpointer data)
+selection_changed_cb(GtkSelectionModel *self, G_GNUC_UNUSED guint position, + G_GNUC_UNUSED guint n_items, gpointer data) PidginDiscoDialog *dialog = data;
- XmppDiscoService *service;
- /* Figure out what was clicked */
- if (!gtk_tree_view_get_path_at_pos(dialog->tree, (gint)x, (gint)y, &path,
- gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
- gtk_tree_path_free(path);
- gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
- service = g_value_get_pointer(&val);
- gtk_tree_view_convert_bin_window_to_widget_coords(dialog->tree,
- rect.width = rect.height = 1;
- gtk_popover_set_pointing_to(GTK_POPOVER(dialog->popover), &rect);
- gtk_popover_popup(GTK_POPOVER(dialog->popover));
-selection_changed_cb(GtkTreeSelection *selection, PidginDiscoDialog *dialog)
+ GtkTreeListRow *row = NULL; gboolean allow_add = FALSE, allow_register = FALSE;
- if (gtk_tree_selection_get_selected(selection, NULL, &iter)) {
- gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
- dialog->selected = g_value_get_pointer(&val);
- if (dialog->selected != NULL) {
- XmppDiscoServiceFlags flags = xmpp_disco_service_get_flags(dialog->selected);
+ /* The passed in position and n_items gives the *range* of selections that + * have changed, so just re-query it since GtkSingleSelection has exactly + row = gtk_single_selection_get_selected_item(GTK_SINGLE_SELECTION(self)); + dialog->selected = gtk_tree_list_row_get_item(row); + if(XMPP_DISCO_IS_SERVICE(dialog->selected)) { + XmppDiscoServiceFlags flags; + flags = xmpp_disco_service_get_flags(dialog->selected); allow_add = (flags & XMPP_DISCO_ADD) != 0;
allow_register = (flags & XMPP_DISCO_REGISTER) != 0;
+ /* gtk_tree_list_row_get_item returns a ref, but this struct isn't + * supposed to hold one, as the model holds on to it. */ + g_object_unref(dialog->selected); @@ -310,54 +273,73 @@
g_simple_action_set_enabled(dialog->register_action, allow_register);
-row_expanded_cb(G_GNUC_UNUSED GtkTreeView *tree, GtkTreeIter *arg1,
- G_GNUC_UNUSED GtkTreePath *rg2, gpointer user_data)
- PidginDiscoDialog *dialog = user_data;
- XmppDiscoService *service;
+service_create_child_model_cb(GObject *item, G_GNUC_UNUSED gpointer data) { + XmppDiscoService *service = XMPP_DISCO_SERVICE(item); + GListModel *model = NULL;
- gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), arg1,
- service = g_value_get_pointer(&val);
- xmpp_disco_service_expand(service);
+ model = xmpp_disco_service_get_child_model(service); + if(G_IS_LIST_MODEL(model)) { + /* Always return a new ref, as the caller takes ownership. */ -row_activated_cb(G_GNUC_UNUSED GtkTreeView *tree_view,
- G_GNUC_UNUSED GtkTreeViewColumn *column,
+row_expanded_cb(GObject *obj, G_GNUC_UNUSED GParamSpec *pspec, + G_GNUC_UNUSED gpointer data) + GtkTreeListRow *row = GTK_TREE_LIST_ROW(obj); + if(gtk_tree_list_row_get_expanded(row)) { + XmppDiscoService *service = gtk_tree_list_row_get_item(row); + if(XMPP_DISCO_IS_SERVICE(service)) { + xmpp_disco_service_expand(service); + g_clear_object(&service); +list_row_notify_cb(GObject *obj, GParamSpec *pspec, gpointer data) { + GtkTreeListRow *row = NULL; + row = gtk_tree_expander_get_list_row(GTK_TREE_EXPANDER(obj)); + if(GTK_IS_TREE_LIST_ROW(row)) { + g_signal_connect(row, "notify::expanded", G_CALLBACK(row_expanded_cb), +row_activated_cb(GtkColumnView *self, guint position, gpointer user_data) { PidginDiscoDialog *dialog = user_data;
- XmppDiscoService *service;
+ GtkSelectionModel *model = NULL; + GtkTreeListRow *row = NULL; + XmppDiscoService *service = NULL; XmppDiscoServiceFlags flags;
- if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path)) {
- gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
- service = g_value_get_pointer(&val);
+ model = gtk_column_view_get_model(self); + row = g_list_model_get_item(G_LIST_MODEL(model), position); + service = gtk_tree_list_row_get_item(row); flags = xmpp_disco_service_get_flags(service);
if((flags & XMPP_DISCO_BROWSE) != 0) {
- if (gtk_tree_view_row_expanded(GTK_TREE_VIEW(dialog->tree), path)) {
- gtk_tree_view_collapse_row(GTK_TREE_VIEW(dialog->tree), path);
+ if(gtk_tree_list_row_get_expanded(row)) { + gtk_tree_list_row_set_expanded(row, FALSE); - gtk_tree_view_expand_row(GTK_TREE_VIEW(dialog->tree), path, FALSE);
+ gtk_tree_list_row_set_expanded(row, TRUE); } else if((flags & XMPP_DISCO_REGISTER) != 0) {
g_action_activate(G_ACTION(dialog->register_action), NULL);
} else if((flags & XMPP_DISCO_ADD) != 0) {
g_action_activate(G_ACTION(dialog->add_action), NULL);
+ g_clear_object(&service); @@ -393,104 +375,6 @@
pidgin_disco_list_set_in_progress(dialog->discolist, FALSE);
-disco_query_tooltip(GtkWidget *widget, int x, int y, gboolean keyboard_mode,
- GtkTooltip *tooltip, gpointer data)
- PidginDiscoDialog *dialog = data;
- GtkTreePath *path = NULL;
- XmppDiscoService *service;
- const char *type = NULL;
- char *markup, *jid, *name, *desc = NULL;
- GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
- if (!gtk_tree_selection_get_selected(selection, NULL, &iter)) {
- path = gtk_tree_model_get_path(GTK_TREE_MODEL(dialog->model), &iter);
- gtk_tree_view_convert_widget_to_bin_window_coords(GTK_TREE_VIEW(widget),
- gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bx, by, &path,
- if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path)) {
- gtk_tree_path_free(path);
- gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
- service = g_value_get_pointer(&val);
- gtk_tree_path_free(path);
- switch(xmpp_disco_service_get_service_type(service)) {
- case XMPP_DISCO_SERVICE_TYPE_UNSET:
- case XMPP_DISCO_SERVICE_TYPE_GATEWAY:
- case XMPP_DISCO_SERVICE_TYPE_DIRECTORY:
- case XMPP_DISCO_SERVICE_TYPE_CHAT:
- case XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION:
- type = _("PubSub Collection");
- case XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF:
- type = _("PubSub Leaf");
- case XMPP_DISCO_SERVICE_TYPE_OTHER:
- name = g_markup_escape_text(xmpp_disco_service_get_name(service), -1);
- jid = g_markup_escape_text(xmpp_disco_service_get_jid(service), -1);
- if(xmpp_disco_service_get_description(service) != NULL) {
- desc = g_markup_escape_text(xmpp_disco_service_get_description(service),
- markup = g_strdup_printf("<span size='x-large' weight='bold'>%s</span>\n<b>%s:</b> %s%s%s",
- desc != NULL ? _("\n<b>Description:</b> ") : "",
- desc != NULL ? desc : "");
- gtk_tooltip_set_markup(tooltip, markup);
- gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, path);
- gtk_tree_path_free(path);
void pidgin_disco_signed_off_cb(PurpleConnection *pc)
@@ -506,7 +390,7 @@
pidgin_disco_list_set_in_progress(list, FALSE);
- gtk_tree_store_clear(dialog->model);
+ g_list_store_remove_all(dialog->root); pidgin_disco_list_unref(list);
dialog->discolist = NULL;
@@ -550,18 +434,13 @@
gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
- gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
- gtk_widget_class_bind_template_child(widget_class, PidginDiscoDialog,
gtk_widget_class_bind_template_callback(widget_class, destroy_win_cb);
gtk_widget_class_bind_template_callback(widget_class,
dialog_select_account_cb);
gtk_widget_class_bind_template_callback(widget_class, row_activated_cb);
- gtk_widget_class_bind_template_callback(widget_class, row_expanded_cb);
- gtk_widget_class_bind_template_callback(widget_class, service_click_cb);
+ gtk_widget_class_bind_template_callback(widget_class, list_row_notify_cb); gtk_widget_class_bind_template_callback(widget_class,
@@ -574,6 +453,7 @@
pidgin_disco_dialog_init(PidginDiscoDialog *dialog)
+ GtkTreeListModel *model = NULL; GActionEntry entries[] = {
{ .name = "stop", .activate = activate_stop },
{ .name = "browse", .activate = activate_browse },
@@ -591,10 +471,6 @@
dialog->account = pidgin_account_chooser_get_selected(
PIDGIN_ACCOUNT_CHOOSER(dialog->account_chooser));
- gtk_widget_set_has_tooltip(GTK_WIDGET(dialog->tree), TRUE);
- g_signal_connect(G_OBJECT(dialog->tree), "query-tooltip",
- G_CALLBACK(disco_query_tooltip), dialog);
action_group = g_simple_action_group_new();
action_map = G_ACTION_MAP(action_group);
g_action_map_add_action_entries(action_map, entries, G_N_ELEMENTS(entries),
@@ -618,6 +494,13 @@
gtk_widget_insert_action_group(GTK_WIDGET(dialog), "disco",
G_ACTION_GROUP(action_group));
+ dialog->root = g_list_store_new(XMPP_DISCO_TYPE_SERVICE); + model = gtk_tree_list_model_new(G_LIST_MODEL(dialog->root), FALSE, FALSE, + (GtkTreeListModelCreateModelFunc)service_create_child_model_cb, + gtk_sort_list_model_set_model(dialog->sorter, G_LIST_MODEL(model)); /******************************************************************************
@@ -638,86 +521,23 @@
-void pidgin_disco_add_service(PidginDiscoList *pdl, XmppDiscoService *service, XmppDiscoService *parent)
+pidgin_disco_add_service(PidginDiscoList *pdl, XmppDiscoService *service, + XmppDiscoService *parent) PidginDiscoDialog *dialog;
- GtkTreeIter iter, parent_iter, child;
- char *icon_name = NULL;
- gboolean append = TRUE;
g_return_if_fail(dialog != NULL);
- purple_debug_info("xmppdisco", "Adding service \"%s\"",
- xmpp_disco_service_get_name(service));
- purple_debug_info("xmppdisco", "Service \"%s\" has no children",
- xmpp_disco_service_get_name(parent));
+ purple_debug_info("xmppdisco", "Adding service \"%s\" to %p", + xmpp_disco_service_get_name(service), parent); gtk_progress_bar_pulse(GTK_PROGRESS_BAR(dialog->progress));
- GtkTreeRowReference *rr;
- rr = g_hash_table_lookup(pdl->services, parent);
- path = gtk_tree_row_reference_get_path(rr);
- gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model),
- gtk_tree_path_free(path);
- if (gtk_tree_model_iter_children(
- GTK_TREE_MODEL(dialog->model), &child,
- GTK_TREE_MODEL(dialog->model), &child,
- SERVICE_COLUMN, &tmp, -1);
- if (parent != NULL && !append) {
- gtk_tree_store_remove(dialog->model, &child);
+ xmpp_disco_service_add_child(parent, service); + g_list_store_append(dialog->root, service);
- gtk_tree_store_append(dialog->model, &iter,
- (parent ? &parent_iter : NULL));
- if((xmpp_disco_service_get_flags(service) & XMPP_DISCO_BROWSE) != 0) {
- GtkTreeRowReference *rr;
- gtk_tree_store_append(dialog->model, &child, &iter);
- path = gtk_tree_model_get_path(GTK_TREE_MODEL(dialog->model),
- rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(dialog->model),
- g_hash_table_insert(pdl->services, service, rr);
- gtk_tree_path_free(path);
- icon_name = xmpp_disco_service_get_icon_name(service);
- gtk_tree_store_set(dialog->model, &iter, ICON_NAME_COLUMN, icon_name,
- NAME_COLUMN, xmpp_disco_service_get_name(service),
- DESCRIPTION_COLUMN, xmpp_disco_service_get_description(service),
- SERVICE_COLUMN, service, -1);
--- a/pidgin/plugins/disco/resources/disco.ui Thu Feb 23 23:44:43 2023 -0600
+++ b/pidgin/plugins/disco/resources/disco.ui Fri Feb 24 00:45:22 2023 -0600
@@ -25,30 +25,6 @@
<!-- interface-name Pidgin -->
<!-- interface-description Internet Messenger -->
<!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
- <menu id="popover_menu">
- <attribute name="action">disco.add</attribute>
- <attribute name="label" translatable="yes">Add to Buddy List</attribute>
- <attribute name="action">disco.register</attribute>
- <attribute name="label" translatable="yes">Register</attribute>
- <object class="GtkTreeStore" id="model">
- <!-- column-name icon-name -->
- <column type="gchararray"/>
- <!-- column-name name -->
- <column type="gchararray"/>
- <!-- column-name description -->
- <column type="gchararray"/>
- <!-- column-name service -->
- <column type="gpointer"/>
<template class="PidginDiscoDialog" parent="GtkDialog">
<property name="title" translatable="1">Service Discovery</property>
<signal name="destroy" handler="destroy_win_cb" swapped="no"/>
@@ -93,59 +69,126 @@
<property name="vscrollbar_policy">always</property>
<property name="min_content_height">250</property>
- <object class="GtkTreeView" id="tree">
+ <object class="GtkColumnView" id="columnview"> <property name="focusable">1</property>
- <property name="model">model</property>
- <signal name="row-activated" handler="row_activated_cb" object="PidginDiscoDialog" swapped="no"/>
- <signal name="row-expanded" handler="row_expanded_cb" object="PidginDiscoDialog" swapped="no"/>
- <child internal-child="selection">
- <object class="GtkTreeSelection">
- <signal name="changed" handler="selection_changed_cb" swapped="no"/>
+ <property name="model"> + <object class="GtkSingleSelection"> + <property name="model"> + <object class="GtkSortListModel" id="sorter"> + <property name="sorter"> + <object class="GtkTreeListRowSorter"> + <binding name="sorter"> + <lookup name="sorter">columnview</lookup> + <signal name="selection-changed" handler="selection_changed_cb"/>
+ <signal name="activate" handler="row_activated_cb"/> - <object class="GtkGestureClick">
- <property name="button">3</property>
- <signal name="pressed" handler="service_click_cb" object="PidginDiscoDialog" swapped="no"/>
- <object class="GtkPopoverMenu" id="popover">
- <property name="menu-model">popover_menu</property>
+ <object class="GtkColumnViewColumn"> + <property name="resizable">1</property> + <property name="title" translatable="1">Name</property> + <property name="factory"> + <object class="GtkBuilderListItemFactory"> + <property name="bytes"> +<?xml version="1.0" encoding="UTF-8"?> + <template class="GtkListItem"> + <property name="child"> + <object class="GtkTreeExpander"> + <property name="child"> + <object class="GtkBox"> + <property name="spacing">6</property> + <object class="GtkImage"> + <binding name="icon-name"> + <lookup name="icon-name" type="XmppDiscoService"> + <lookup name="item" type="GtkTreeListRow"> + <lookup name="item">GtkListItem</lookup> + <object class="GtkLabel"> + <property name="ellipsize">end</property> + <property name="xalign">0</property> + <lookup name="name" type="XmppDiscoService"> + <lookup name="item" type="GtkTreeListRow"> + <lookup name="item">GtkListItem</lookup> + <binding name="list-row"> + <lookup name="item">GtkListItem</lookup> + <signal name="notify::list-row" handler="list_row_notify_cb"/> + <property name="sorter"> + <object class="GtkStringSorter"> + <property name="expression"> + <lookup name="name" type="XmppDiscoService"></lookup> - <object class="GtkTreeViewColumn">
- <property name="resizable">1</property>
- <property name="title" translatable="1">Name</property>
- <property name="reorderable">1</property>
- <property name="sort_column_id">1</property>
- <object class="GtkCellRendererPixbuf"/>
- <attribute name="icon-name">0</attribute>
- <object class="GtkCellRendererText"/>
- <attribute name="text">1</attribute>
- <object class="GtkTreeViewColumn">
+ <object class="GtkColumnViewColumn"> + <property name="expand">1</property> <property name="resizable">1</property>
<property name="title" translatable="1">Description</property>
- <property name="reorderable">1</property>
- <property name="sort_column_id">2</property>
- <object class="GtkCellRendererText"/>
- <attribute name="text">2</attribute>
+ <property name="factory"> + <object class="GtkBuilderListItemFactory"> + <property name="bytes"> +<?xml version="1.0" encoding="UTF-8"?> + <template class="GtkListItem"> + <property name="child"> + <object class="GtkLabel"> + <property name="ellipsize">end</property> + <property name="xalign">0</property> + <lookup name="description" type="XmppDiscoService"> + <lookup name="item" type="GtkTreeListRow"> + <lookup name="item">GtkListItem</lookup> + <property name="sorter"> + <object class="GtkStringSorter"> + <property name="expression"> + <lookup name="description" type="XmppDiscoService"></lookup> --- a/pidgin/plugins/disco/xmppdiscoservice.c Thu Feb 23 23:44:43 2023 -0600
+++ b/pidgin/plugins/disco/xmppdiscoservice.c Fri Feb 24 00:45:22 2023 -0600
@@ -29,6 +29,7 @@
@@ -53,6 +54,7 @@
@@ -68,6 +70,29 @@
g_object_notify_by_pspec(G_OBJECT(service), properties[PROP_LIST]);
+xmpp_disco_service_refresh_child_model(GObject *obj, + G_GNUC_UNUSED GParamSpec *pspec, + G_GNUC_UNUSED gpointer data) + XmppDiscoService *service = XMPP_DISCO_SERVICE(obj); + gboolean changed = FALSE; + if((service->flags & XMPP_DISCO_BROWSE) != 0) { + if(service->children == NULL) { + service->children = g_list_store_new(XMPP_DISCO_TYPE_SERVICE); + changed = service->children != NULL; + g_clear_object(&service->children); + g_object_notify_by_pspec(G_OBJECT(service), properties[PROP_CHILD_MODEL]); /******************************************************************************
*****************************************************************************/
@@ -115,6 +140,10 @@
g_value_take_string(value,
xmpp_disco_service_get_icon_name(service));
+ g_value_set_object(value, + xmpp_disco_service_get_child_model(service)); G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -174,6 +203,7 @@
g_clear_pointer(&service->gateway_type, g_free);
g_clear_pointer(&service->jid, g_free);
g_clear_pointer(&service->node, g_free);
+ g_clear_object(&service->children); G_OBJECT_CLASS(xmpp_disco_service_parent_class)->finalize(obj);
@@ -237,6 +267,12 @@
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+ properties[PROP_CHILD_MODEL] = g_param_spec_object( + "child-model", "child-model", + "The model containing children of this service.", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties(obj_class, PROP_LAST, properties);
@@ -245,7 +281,9 @@
-xmpp_disco_service_init(G_GNUC_UNUSED XmppDiscoService *service) {
+xmpp_disco_service_init(XmppDiscoService *service) { + g_signal_connect(service, "notify::flags", + G_CALLBACK(xmpp_disco_service_refresh_child_model), NULL); /******************************************************************************
@@ -465,3 +503,21 @@
+xmpp_disco_service_get_child_model(XmppDiscoService *service) { + g_return_val_if_fail(XMPP_DISCO_IS_SERVICE(service), NULL); + return G_LIST_MODEL(service->children); +xmpp_disco_service_add_child(XmppDiscoService *service, + XmppDiscoService *child) + g_return_if_fail(XMPP_DISCO_IS_SERVICE(service)); + g_return_if_fail(XMPP_DISCO_IS_SERVICE(child)); + g_return_if_fail((service->flags & XMPP_DISCO_BROWSE) != 0); + g_list_store_append(service->children, child);