pidgin/pidgin

Parents 26dc4a668ef1
Children 90276efac79d
Convert PidginAccountsEnabledMenu from a menu controller to a GMenuModel

Testing Done:
Enabled and disabled a bunch of accounts from the menus while running under valgrind.

Reviewed at https://reviews.imfreedom.org/r/1527/
--- 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 @@
}
}
+static void
+purple_menu_copy_helper(GMenuModel *source, GMenu *destination) {
+ gint index = 0;
+
+ 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);
+
+ if(value != NULL) {
+ 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);
+ g_clear_object(&item);
+ }
+}
+
/******************************************************************************
* Public API
*****************************************************************************/
@@ -118,3 +161,16 @@
g_hash_table_unref(table);
}
+
+GMenu *
+purple_menu_copy(GMenuModel *model) {
+ GMenu *menu = NULL;
+
+ g_return_val_if_fail(G_IS_MENU_MODEL(model), NULL);
+
+ menu = g_menu_new();
+
+ purple_menu_copy_helper(model, menu);
+
+ return 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;
+/**
+ * purple_menu_copy:
+ * @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.
+ *
+ * Since: 3.0.0
+ */
+GMenu *purple_menu_copy(GMenuModel *model);
+
G_END_DECLS
#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"
-#include "pidgincore.h"
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static void
-pidgin_accounts_enabled_menu_refresh_helper(PurpleAccount *account,
- gpointer data)
-{
- GApplication *application = g_application_get_default();
- GMenu *menu = data;
-
- if(purple_account_get_enabled(account)) {
- PurpleConnection *connection = purple_account_get_connection(account);
- GMenu *submenu = NULL;
- gchar *label = NULL;
- 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),
- "enabled-account");
-
- if(PURPLE_IS_CONNECTION(connection)) {
- connection_id = purple_connection_get_id(connection);
- }
+struct _PidginAccountsEnabledMenu {
+ GMenuModel parent;
- purple_menu_populate_dynamic_targets(submenu,
- "account", account_id,
- "connection", connection_id,
- NULL);
-
- /* translators: This format string is intended to contain the account
- * name followed by the protocol name to uniquely identify a specific
- * account.
- */
- label = g_strdup_printf(_("%s (%s)"), account_name, protocol_name);
-
- g_menu_append_submenu(menu, label, G_MENU_MODEL(submenu));
+ GQueue *accounts;
+};
- g_free(label);
- }
-}
-
-static void
-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,
- menu);
-}
+G_DEFINE_TYPE(PidginAccountsEnabledMenu, pidgin_accounts_enabled_menu,
+ G_TYPE_MENU_MODEL)
/******************************************************************************
* Callbacks
*****************************************************************************/
static void
-pidgin_accounts_enabled_menu_enabled_cb(G_GNUC_UNUSED PurpleAccount *account,
- gpointer data)
+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);
+}
+
+static void
+pidgin_accounts_enabled_menu_disabled_cb(PurpleAccount *account, gpointer data)
{
- pidgin_accounts_enabled_menu_refresh(data);
+ PidginAccountsEnabledMenu *menu = data;
+ gint index = -1;
+
+ index = g_queue_index(menu->accounts, account);
+ if(index >= 0) {
+ 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);
+ }
}
static void
-pidgin_accounts_enabled_menu_disabled_cb(G_GNUC_UNUSED PurpleAccount *account,
- gpointer data)
+pidgin_accounts_enabled_menu_connected_cb(PurpleAccount *account, gpointer data)
{
- pidgin_accounts_enabled_menu_refresh(data);
+ PidginAccountsEnabledMenu *menu = data;
+ gint index = -1;
+
+ index = g_queue_index(menu->accounts, account);
+ if(index >= 0) {
+ /* Tell the model that the account needs to be updated. */
+ g_menu_model_items_changed(G_MENU_MODEL(menu), index, 0, 0);
+ }
}
static void
-pidgin_accounts_enabled_menu_connected_cb(G_GNUC_UNUSED PurpleAccount *account,
- gpointer data)
+pidgin_accounts_enabled_menu_disconnected_cb(PurpleAccount *account,
+ gpointer data)
{
- pidgin_accounts_enabled_menu_refresh(data);
+ PidginAccountsEnabledMenu *menu = data;
+ gint index = -1;
+
+ index = g_queue_index(menu->accounts, account);
+ if(index >= 0) {
+ /* Tell the model that the account needs to be updated. */
+ g_menu_model_items_changed(G_MENU_MODEL(menu), index, 0, 0);
+ }
+}
+
+/******************************************************************************
+ * GMenuModel Implementation
+ *****************************************************************************/
+static gboolean
+pidgin_accounts_enabled_menu_is_mutable(GMenuModel *model) {
+ return TRUE;
+}
+
+static gint
+pidgin_accounts_enabled_menu_get_n_items(GMenuModel *model) {
+ PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(model);
+
+ return g_queue_get_length(menu->accounts);
}
static void
-pidgin_accounts_enabled_menu_disconnected_cb(G_GNUC_UNUSED PurpleAccount *account,
- gpointer data)
+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;
+ gchar *label = NULL;
+ const gchar *account_name = NULL, *protocol_name = NULL, *icon_name = NULL;
+
+ /* Create our hash table of attributes to return. This must always be
+ * populated.
+ */
+ *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)) {
+ return;
+ }
+
+ 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);
+ }
+
+ /* Add the label. */
+
+ /* translators: This format string is intended to contain the account
+ * name followed by the protocol name to uniquely identify a specific
+ * account.
+ */
+ label = g_strdup_printf(_("%s (%s)"), account_name, protocol_name);
+ value = g_variant_new_string(label);
+ g_free(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));
+ }
}
static void
-pidgin_accounts_enabled_menu_weak_notify_cb(G_GNUC_UNUSED gpointer data,
- GObject *obj)
+pidgin_accounts_enabled_menu_get_item_links(GMenuModel *model, gint index,
+ GHashTable **links)
{
- 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,
+ g_object_unref);
+
+ account = g_queue_peek_nth(menu->accounts, index);
+ if(!PURPLE_IS_ACCOUNT(account)) {
+ return;
+ }
+
+ 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),
+ "enabled-account");
+ submenu = purple_menu_copy(G_MENU_MODEL(template));
+
+ purple_menu_populate_dynamic_targets(submenu,
+ "account", account_id,
+ "connection", connection_id,
+ NULL);
+
+ g_hash_table_insert(*links, G_MENU_LINK_SUBMENU, submenu);
}
/******************************************************************************
- * Public API
+ * GObject Implementation
*****************************************************************************/
-GMenu *
-pidgin_accounts_enabled_menu_new(void) {
- GMenu *menu = NULL;
+static 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);
+ menu->accounts = NULL;
+ }
+
+ G_OBJECT_CLASS(pidgin_accounts_enabled_menu_parent_class)->dispose(obj);
+
+}
+
+static void
+pidgin_accounts_enabled_menu_constructed(GObject *obj) {
+ PidginAccountsEnabledMenu *menu = PIDGIN_ACCOUNTS_ENABLED_MENU(obj);
+ PurpleAccountManager *manager = NULL;
+ GList *enabled = NULL, *l = NULL;
+ gint count = 0;
+
+ 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));
+ count++;
+ }
+ g_list_free(enabled);
+
+ g_menu_model_items_changed(G_MENU_MODEL(obj), 0, 0, count);
+}
+
+static void
+pidgin_accounts_enabled_menu_init(PidginAccountsEnabledMenu *menu) {
gpointer handle = NULL;
- /* Create the menu and set our instance as data on it so it'll be freed
- * when the menu is destroyed.
- */
- menu = g_menu_new();
- 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),
menu);
+}
- return menu;
+static void
+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;
}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+GMenuModel *
+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 @@
G_BEGIN_DECLS
/**
+ * PidginAccountsEnabledMenu:
+ *
+ * A [class@Gio.MenuModel] that automatically updates itself based on what
+ * accounts are enabled.
+ *
+ * Since: 3.0.0
+ */
+
+#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.
*
* Since: 3.0.0
*/
-GMenu *pidgin_accounts_enabled_menu_new(void);
+GMenuModel *pidgin_accounts_enabled_menu_new(void);
G_END_DECLS
--- 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 @@
static void
pidgin_application_populate_dynamic_menus(PidginApplication *application) {
- GMenu *source = NULL, *target = NULL;
+ GMenu *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),
"enabled-accounts");
- 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();