--- a/libpurple/purplemenu.c Thu Jul 21 01:18:36 2022 -0500
+++ b/libpurple/purplemenu.c Thu Jul 28 21:17:04 2022 -0500
@@ -57,6 +57,49 @@
+purple_menu_copy_helper(GMenuModel *source, GMenu *destination) { + for(index = 0; index < g_menu_model_get_n_items(source); index++) { + GMenuItem *item = NULL; + GMenuAttributeIter *attr_iter = NULL; + GMenuLinkIter *link_iter = NULL; + item = g_menu_item_new(NULL, NULL); + attr_iter = g_menu_model_iterate_item_attributes(source, index); + while(g_menu_attribute_iter_next(attr_iter)) { + const gchar *name = g_menu_attribute_iter_get_name(attr_iter); + GVariant *value = g_menu_attribute_iter_get_value(attr_iter); + g_menu_item_set_attribute_value(item, name, value); + g_variant_unref(value); + g_clear_object(&attr_iter); + link_iter = g_menu_model_iterate_item_links(source, index); + while(g_menu_link_iter_next(link_iter)) { + GMenuModel *link_source = g_menu_link_iter_get_value(link_iter); + GMenu *link_destination = g_menu_new(); + purple_menu_copy_helper(link_source, link_destination); + g_menu_item_set_link(item, g_menu_link_iter_get_name(link_iter), + G_MENU_MODEL(link_destination)); + g_clear_object(&link_source); + g_clear_object(&link_destination); + g_clear_object(&link_iter); + g_menu_append_item(destination, item); /******************************************************************************
*****************************************************************************/
@@ -118,3 +161,16 @@
g_hash_table_unref(table);
+purple_menu_copy(GMenuModel *model) { + g_return_val_if_fail(G_IS_MENU_MODEL(model), NULL); + purple_menu_copy_helper(model, menu); --- a/libpurple/purplemenu.h Thu Jul 21 01:18:36 2022 -0500
+++ b/libpurple/purplemenu.h Thu Jul 28 21:17:04 2022 -0500
@@ -84,6 +84,19 @@
void purple_menu_populate_dynamic_targets(GMenu *menu, const gchar *first_property, ...) G_GNUC_NULL_TERMINATED;
+ * @model: The [class@Gio.MenuModel] instance to copy. + * Creates a full copy of @model as a new [class@Gio.Menu]. If @model was not + * a [class@Gio.Menu] instance, any additional functionality will be lost. + * Returns: (transfer full): The new menu. +GMenu *purple_menu_copy(GMenuModel *model); #endif /* PURPLE_MENU_H */
--- a/pidgin/pidginaccountsenabledmenu.c Thu Jul 21 01:18:36 2022 -0500
+++ b/pidgin/pidginaccountsenabledmenu.c Thu Jul 28 21:17:04 2022 -0500
@@ -26,118 +26,222 @@
#include "pidginaccountsenabledmenu.h"
-/******************************************************************************
- *****************************************************************************/
-pidgin_accounts_enabled_menu_refresh_helper(PurpleAccount *account,
- GApplication *application = g_application_get_default();
- if(purple_account_get_enabled(account)) {
- PurpleConnection *connection = purple_account_get_connection(account);
- const gchar *account_name = purple_account_get_username(account);
- const gchar *protocol_name = purple_account_get_protocol_name(account);
- const gchar *account_id = purple_account_get_id(account);
- const gchar *connection_id = NULL;
- submenu = gtk_application_get_menu_by_id(GTK_APPLICATION(application),
- if(PURPLE_IS_CONNECTION(connection)) {
- connection_id = purple_connection_get_id(connection);
+struct _PidginAccountsEnabledMenu { - purple_menu_populate_dynamic_targets(submenu,
- "connection", connection_id,
- /* translators: This format string is intended to contain the account
- * name followed by the protocol name to uniquely identify a specific
- label = g_strdup_printf(_("%s (%s)"), account_name, protocol_name);
- g_menu_append_submenu(menu, label, G_MENU_MODEL(submenu));
-pidgin_accounts_enabled_menu_refresh(GMenu *menu) {
- PurpleAccountManager *manager = NULL;
- g_menu_remove_all(menu);
- manager = purple_account_manager_get_default();
- purple_account_manager_foreach(manager,
- pidgin_accounts_enabled_menu_refresh_helper,
+G_DEFINE_TYPE(PidginAccountsEnabledMenu, pidgin_accounts_enabled_menu, /******************************************************************************
*****************************************************************************/
-pidgin_accounts_enabled_menu_enabled_cb(G_GNUC_UNUSED PurpleAccount *account,
+pidgin_accounts_enabled_menu_enabled_cb(PurpleAccount *account, gpointer data) { + PidginAccountsEnabledMenu *menu = data; + /* Add the account to the start of the list. */ + g_queue_push_head(menu->accounts, g_object_ref(account)); + /* Tell everyone our model added a new item at position 0. */ + g_menu_model_items_changed(G_MENU_MODEL(menu), 0, 0, 1); +pidgin_accounts_enabled_menu_disabled_cb(PurpleAccount *account, gpointer data) - pidgin_accounts_enabled_menu_refresh(data);
+ PidginAccountsEnabledMenu *menu = data; + index = g_queue_index(menu->accounts, account); + g_queue_pop_nth(menu->accounts, index); + /* Tell the model that we removed one item at the given index. */ + g_menu_model_items_changed(G_MENU_MODEL(menu), index, 1, 0); + g_object_unref(account); -pidgin_accounts_enabled_menu_disabled_cb(G_GNUC_UNUSED PurpleAccount *account,
+pidgin_accounts_enabled_menu_connected_cb(PurpleAccount *account, gpointer data) - pidgin_accounts_enabled_menu_refresh(data);
+ PidginAccountsEnabledMenu *menu = data; + index = g_queue_index(menu->accounts, account); + /* Tell the model that the account needs to be updated. */ + g_menu_model_items_changed(G_MENU_MODEL(menu), index, 0, 0); -pidgin_accounts_enabled_menu_connected_cb(G_GNUC_UNUSED PurpleAccount *account,
+pidgin_accounts_enabled_menu_disconnected_cb(PurpleAccount *account, - pidgin_accounts_enabled_menu_refresh(data);
+ PidginAccountsEnabledMenu *menu = data; + index = g_queue_index(menu->accounts, account); + /* Tell the model that the account needs to be updated. */ + g_menu_model_items_changed(G_MENU_MODEL(menu), index, 0, 0); +/****************************************************************************** + * GMenuModel Implementation + *****************************************************************************/ +pidgin_accounts_enabled_menu_is_mutable(GMenuModel *model) { +pidgin_accounts_enabled_menu_get_n_items(GMenuModel *model) { + PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model); + return g_queue_get_length(menu->accounts); -pidgin_accounts_enabled_menu_disconnected_cb(G_GNUC_UNUSED PurpleAccount *account,
+pidgin_accounts_enabled_menu_get_item_attributes(GMenuModel *model, gint index, + GHashTable **attributes) - pidgin_accounts_enabled_menu_refresh(data);
+ PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model); + PurpleAccount *account = NULL; + PurpleProtocol *protocol = NULL; + GVariant *value = NULL; + const gchar *account_name = NULL, *protocol_name = NULL, *icon_name = NULL; + /* Create our hash table of attributes to return. This must always be + *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + (GDestroyNotify)g_variant_unref); + /* Get the account the caller is interested in. */ + account = g_queue_peek_nth(menu->accounts, index); + if(!PURPLE_IS_ACCOUNT(account)) { + account_name = purple_account_get_username(account); + /* Get the protocol from the account. */ + protocol = purple_account_get_protocol(account); + if(PURPLE_IS_PROTOCOL(protocol)) { + protocol_name = purple_protocol_get_name(protocol); + icon_name = purple_protocol_get_icon_name(protocol); + /* translators: This format string is intended to contain the account + * name followed by the protocol name to uniquely identify a specific + label = g_strdup_printf(_("%s (%s)"), account_name, protocol_name); + value = g_variant_new_string(label); + g_hash_table_insert(*attributes, G_MENU_ATTRIBUTE_LABEL, + g_variant_ref_sink(value)); + /* Add the icon if we have one. */ + if(icon_name != NULL) { + value = g_variant_new_string(icon_name); + g_hash_table_insert(*attributes, G_MENU_ATTRIBUTE_ICON, + g_variant_ref_sink(value)); -pidgin_accounts_enabled_menu_weak_notify_cb(G_GNUC_UNUSED gpointer data,
+pidgin_accounts_enabled_menu_get_item_links(GMenuModel *model, gint index, - purple_signals_disconnect_by_handle(obj);
+ PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model); + PurpleAccount *account = NULL; + PurpleConnection *connection = NULL; + GApplication *application = g_application_get_default(); + GMenu *submenu = NULL, *template = NULL; + const gchar *account_id = NULL, *connection_id = NULL; + /* Create our hash table for links, this must always be populated. */ + *links = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + account = g_queue_peek_nth(menu->accounts, index); + if(!PURPLE_IS_ACCOUNT(account)) { + account_id = purple_account_get_id(account); + connection = purple_account_get_connection(account); + if(PURPLE_IS_CONNECTION(connection)) { + connection_id = purple_connection_get_id(connection); + /* Create a copy of our template menu. */ + template = gtk_application_get_menu_by_id(GTK_APPLICATION(application), + submenu = purple_menu_copy(G_MENU_MODEL(template)); + purple_menu_populate_dynamic_targets(submenu, + "connection", connection_id, + g_hash_table_insert(*links, G_MENU_LINK_SUBMENU, submenu); /******************************************************************************
+ * GObject Implementation *****************************************************************************/
-pidgin_accounts_enabled_menu_new(void) {
+pidgin_accounts_enabled_menu_dispose(GObject *obj) { + PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(obj); + if(menu->accounts != NULL) { + g_queue_free_full(menu->accounts, g_object_unref); + G_OBJECT_CLASS(pidgin_accounts_enabled_menu_parent_class)->dispose(obj); +pidgin_accounts_enabled_menu_constructed(GObject *obj) { + PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(obj); + PurpleAccountManager *manager = NULL; + GList *enabled = NULL, *l = NULL; + G_OBJECT_CLASS(pidgin_accounts_enabled_menu_parent_class)->constructed(obj); + manager = purple_account_manager_get_default(); + enabled = purple_account_manager_get_enabled(manager); + for(l = enabled; l != NULL; l = l->next) { + g_queue_push_head(menu->accounts, g_object_ref(l->data)); + g_menu_model_items_changed(G_MENU_MODEL(obj), 0, 0, count); +pidgin_accounts_enabled_menu_init(PidginAccountsEnabledMenu *menu) { - /* Create the menu and set our instance as data on it so it'll be freed
- * when the menu is destroyed.
- g_object_weak_ref(G_OBJECT(menu),
- pidgin_accounts_enabled_menu_weak_notify_cb, NULL);
- /* Populate ourselves with any accounts that are already enabled. */
- pidgin_accounts_enabled_menu_refresh(menu);
+ menu->accounts = g_queue_new(); /* Wire up the purple signals we care about. */
handle = purple_accounts_get_handle();
@@ -157,6 +261,26 @@
purple_signal_connect(handle, "account-signed-off", menu,
G_CALLBACK(pidgin_accounts_enabled_menu_disconnected_cb),
+pidgin_accounts_enabled_menu_class_init(PidginAccountsEnabledMenuClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + GMenuModelClass *model_class = G_MENU_MODEL_CLASS(klass); + obj_class->constructed = pidgin_accounts_enabled_menu_constructed; + obj_class->dispose = pidgin_accounts_enabled_menu_dispose; + model_class->is_mutable = pidgin_accounts_enabled_menu_is_mutable; + model_class->get_n_items = pidgin_accounts_enabled_menu_get_n_items; + model_class->get_item_attributes = pidgin_accounts_enabled_menu_get_item_attributes; + model_class->get_item_links = pidgin_accounts_enabled_menu_get_item_links; +/****************************************************************************** + *****************************************************************************/ +pidgin_accounts_enabled_menu_new(void) { + return g_object_new(PIDGIN_TYPE_ACCOUNTS_ENABLED_MENU, NULL); --- a/pidgin/pidginaccountsenabledmenu.h Thu Jul 21 01:18:36 2022 -0500
+++ b/pidgin/pidginaccountsenabledmenu.h Thu Jul 28 21:17:04 2022 -0500
@@ -35,16 +35,29 @@
+ * PidginAccountsEnabledMenu: + * A [class@Gio.MenuModel] that automatically updates itself based on what + * accounts are enabled. +#define PIDGIN_TYPE_ACCOUNTS_ENABLED_MENU (pidgin_accounts_enabled_menu_get_type()) +G_DECLARE_FINAL_TYPE(PidginAccountsEnabledMenu, pidgin_accounts_enabled_menu, + PIDGIN, ACCOUNTS_ENABLED_MENU, GMenuModel) * pidgin_accounts_enabled_menu_new:
- * Creates a [class@Gio.Menu] that will automatically update itself to include
- * accounts that are enabled in libpurple.
+ * Creates a [class@Gio.MenuModel] that will automatically update itself to + * include accounts that are enabled in libpurple. * Returns: (transfer full): The new menu instance.
-GMenu *pidgin_accounts_enabled_menu_new(void);
+GMenuModel *pidgin_accounts_enabled_menu_new(void); --- a/pidgin/pidginapplication.c Thu Jul 21 01:18:36 2022 -0500
+++ b/pidgin/pidginapplication.c Thu Jul 28 21:17:04 2022 -0500
@@ -121,7 +121,7 @@
pidgin_application_populate_dynamic_menus(PidginApplication *application) {
- GMenu *source = NULL, *target = NULL;
GMenuModel *model = NULL;
/* Link the AccountsDisabledMenu into its proper location. */
@@ -131,10 +131,10 @@
g_menu_append_section(target, NULL, model);
/* Link the AccountsEnabledMenu into its proper location. */
- source = pidgin_accounts_enabled_menu_new();
+ model = pidgin_accounts_enabled_menu_new(); target = gtk_application_get_menu_by_id(GTK_APPLICATION(application),
- g_menu_append_section(target, NULL, G_MENU_MODEL(source));
+ g_menu_append_section(target, NULL, model); /* Link the PluginsMenu into its proper location. */
model = pidgin_plugins_menu_new();