Fri, 27 May 2022 14:52:34 -0500
Convert plugin actions to GMenu and GAction
Also ported the idle maker plugin to the new api.
Testing Done:
Made sure all of the actions for the idle maker plugin worked.
Reviewed at https://reviews.imfreedom.org/r/1408/
--- a/libpurple/plugins/idle.c Fri May 27 04:11:45 2022 -0500 +++ b/libpurple/plugins/idle.c Fri May 27 14:52:34 2022 -0500 @@ -132,9 +132,22 @@ idled_accts = g_list_remove(idled_accts, acct); } - static void -idle_action(PurplePluginAction *action) +signing_off_cb(PurpleConnection *gc, void *data) +{ + PurpleAccount *account; + + account = purple_connection_get_account(gc); + idled_accts = g_list_remove(idled_accts, account); +} + +/****************************************************************************** + * Actions + *****************************************************************************/ +static void +purple_idle_set_account_idle_time(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED GVariant *parameter, + gpointer data) { /* Use the super fancy request API */ @@ -155,7 +168,7 @@ request = purple_request_fields_new(); purple_request_fields_add_group(request, group); - purple_request_fields(action->plugin, + purple_request_fields(data, N_("I'dle Mak'er"), _("Set Account Idle Time"), NULL, @@ -166,7 +179,9 @@ } static void -unidle_action(PurplePluginAction *action) +purple_idle_unset_account_idle_time(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED GVariant *parameter, + gpointer data) { PurpleRequestFields *request; PurpleRequestFieldGroup *group; @@ -188,7 +203,7 @@ request = purple_request_fields_new(); purple_request_fields_add_group(request, group); - purple_request_fields(action->plugin, + purple_request_fields(data, N_("I'dle Mak'er"), _("Unset Account Idle Time"), NULL, @@ -199,7 +214,9 @@ } static void -idle_all_action(PurplePluginAction *action) +purple_idle_set_all_accounts_idle_time(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED GVariant *parameter, + gpointer data) { PurpleRequestFields *request; PurpleRequestFieldGroup *group; @@ -213,7 +230,7 @@ request = purple_request_fields_new(); purple_request_fields_add_group(request, group); - purple_request_fields(action->plugin, + purple_request_fields(data, N_("I'dle Mak'er"), _("Set Idle Time for All Accounts"), NULL, @@ -224,7 +241,9 @@ } static void -unidle_all_action(PurplePluginAction *action) +purple_idle_unset_all_accounts_idle_time(G_GNUC_UNUSED GSimpleAction *action, + G_GNUC_UNUSED GVariant *parameter, + G_GNUC_UNUSED gpointer data) { /* freeing the list here will cause segfaults if the user idles an account * after the list is freed */ @@ -233,48 +252,47 @@ idled_accts = NULL; } -static GList * -actions(PurplePlugin *plugin) -{ - GList *l = NULL; - PurplePluginAction *act = NULL; - - act = purple_plugin_action_new(_("Set Account Idle Time"), - idle_action); - l = g_list_append(l, act); - - act = purple_plugin_action_new(_("Unset Account Idle Time"), - unidle_action); - l = g_list_append(l, act); - - act = purple_plugin_action_new(_("Set Idle Time for All Accounts"), - idle_all_action); - l = g_list_append(l, act); - - act = purple_plugin_action_new( - _("Unset Idle Time for All Idled Accounts"), unidle_all_action); - l = g_list_append(l, act); - - return l; -} - -static void -signing_off_cb(PurpleConnection *gc, void *data) -{ - PurpleAccount *account; - - account = purple_connection_get_account(gc); - idled_accts = g_list_remove(idled_accts, account); -} - +/****************************************************************************** + * GPlugin Exports + *****************************************************************************/ static GPluginPluginInfo * idle_query(GError **error) { + GSimpleActionGroup *group = NULL; + GActionEntry entries[] = { + { + .name = "set-account-idle-time", + .activate = purple_idle_set_account_idle_time, + }, { + .name = "unset-account-idle-time", + .activate = purple_idle_unset_account_idle_time, + }, { + .name = "set-all-accounts-idle-time", + .activate = purple_idle_set_all_accounts_idle_time, + }, { + .name = "unset-all-accounts-idle-time", + .activate = purple_idle_unset_all_accounts_idle_time, + } + }; + GMenu *menu = NULL; const gchar * const authors[] = { "Eric Warmenhoven <eric@warmenhoven.org>", NULL }; + group = g_simple_action_group_new(); + g_action_map_add_action_entries(G_ACTION_MAP(group), entries, + G_N_ELEMENTS(entries), NULL); + + menu = g_menu_new(); + g_menu_append(menu, _("Set Account Idle Time"), "set-account-idle-time"); + g_menu_append(menu, _("Unset Account Idle Time"), + "unset-account-idle-time"); + g_menu_append(menu, _("Set Idle Time for All Accounts"), + "set-all-accounts-idle-time"); + g_menu_append(menu, _("Unset Idle Time for All Idled Accounts"), + "unset-all-accounts-idle-time"); + return purple_plugin_info_new( "id", IDLE_PLUGIN_ID, /* This is a cultural reference. Dy'er Mak'er is a song by Led Zeppelin. @@ -287,7 +305,8 @@ "authors", authors, "website", PURPLE_WEBSITE, "abi-version", PURPLE_ABI_VERSION, - "actions-cb", actions, + "action-group", group, + "action-menu", menu, NULL ); } @@ -305,7 +324,7 @@ static gboolean idle_unload(GPluginPlugin *plugin, gboolean shutdown, GError **error) { - unidle_all_action(NULL); + purple_idle_unset_all_accounts_idle_time(NULL, NULL, NULL); return TRUE; }
--- a/libpurple/purpleplugininfo.c Fri May 27 04:11:45 2022 -0500 +++ b/libpurple/purpleplugininfo.c Fri May 27 14:52:34 2022 -0500 @@ -46,6 +46,9 @@ /* TRUE if a plugin has been unloaded at least once. Auto-load * plugins that have been unloaded once will not be auto-loaded again. */ gboolean unloaded; + + GActionGroup *action_group; + GMenuModel *menu_model; } PurplePluginInfoPrivate; enum { @@ -55,6 +58,8 @@ PROP_PREF_FRAME_CB, PROP_PREF_REQUEST_CB, PROP_FLAGS, + PROP_ACTION_GROUP, + PROP_ACTION_MENU, N_PROPERTIES, }; static GParamSpec *properties[N_PROPERTIES] = { NULL, }; @@ -63,6 +68,39 @@ GPLUGIN_TYPE_PLUGIN_INFO); /************************************************************************** + * Helpers + **************************************************************************/ +static void +purple_plugin_info_set_action_group(PurplePluginInfo *info, + GActionGroup *group) +{ + PurplePluginInfoPrivate *priv = NULL; + + g_return_if_fail(PURPLE_IS_PLUGIN_INFO(info)); + + priv = purple_plugin_info_get_instance_private(info); + + if(g_set_object(&priv->action_group, group)) { + g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_ACTION_GROUP]); + } +} + +static void +purple_plugin_info_set_action_menu(PurplePluginInfo *info, + GMenuModel *menu_model) +{ + PurplePluginInfoPrivate *priv = NULL; + + g_return_if_fail(PURPLE_IS_PLUGIN_INFO(info)); + + priv = purple_plugin_info_get_instance_private(info); + + if(g_set_object(&priv->menu_model, menu_model)) { + g_object_notify_by_pspec(G_OBJECT(info), properties[PROP_ACTION_MENU]); + } +} + +/************************************************************************** * GObject Implementation **************************************************************************/ static void @@ -94,6 +132,14 @@ case PROP_FLAGS: priv->flags = g_value_get_flags(value); break; + case PROP_ACTION_GROUP: + purple_plugin_info_set_action_group(info, + g_value_get_object(value)); + break; + case PROP_ACTION_MENU: + purple_plugin_info_set_action_menu(info, + g_value_get_object(value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); break; @@ -126,6 +172,14 @@ case PROP_FLAGS: g_value_set_flags(value, purple_plugin_info_get_flags(info)); break; + case PROP_ACTION_GROUP: + g_value_take_object(value, + purple_plugin_info_get_action_group(info)); + break; + case PROP_ACTION_MENU: + g_value_take_object(value, + purple_plugin_info_get_action_menu(info)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); break; @@ -215,6 +269,32 @@ 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + /** + * PurplePluginInfo::action-group: + * + * A [class@Gio.ActionGroup] of actions that this plugin provides. + * + * Since: 3.0.0 + */ + properties[PROP_ACTION_GROUP] = g_param_spec_object( + "action-group", "action-group", + "The action group for this plugin", + G_TYPE_ACTION_GROUP, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + /** + * PurplePluginInfo::action-menu: + * + * A [class@Gio.MenuModel] for activating actions. + * + * Since: 3.0.0 + */ + properties[PROP_ACTION_MENU] = g_param_spec_object( + "action-menu", "action-menu", + "The menu model for this plugin", + G_TYPE_MENU_MODEL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); } @@ -326,3 +406,33 @@ priv->unloaded = unloaded; } + +GActionGroup * +purple_plugin_info_get_action_group(PurplePluginInfo *info) { + PurplePluginInfoPrivate *priv = NULL; + + g_return_val_if_fail(PURPLE_IS_PLUGIN_INFO(info), NULL); + + priv = purple_plugin_info_get_instance_private(info); + + if(G_IS_ACTION_GROUP(priv->action_group)) { + return g_object_ref(priv->action_group); + } + + return NULL; +} + +GMenuModel * +purple_plugin_info_get_action_menu(PurplePluginInfo *info) { + PurplePluginInfoPrivate *priv = NULL; + + g_return_val_if_fail(PURPLE_IS_PLUGIN_INFO(info), NULL); + + priv = purple_plugin_info_get_instance_private(info); + + if(G_IS_MENU_MODEL(priv->menu_model)) { + return g_object_ref(priv->menu_model); + } + + return NULL; +}
--- a/libpurple/purpleplugininfo.h Fri May 27 04:11:45 2022 -0500 +++ b/libpurple/purpleplugininfo.h Fri May 27 14:52:34 2022 -0500 @@ -25,6 +25,8 @@ #include <glib.h> +#include <gio/gio.h> + #include <gplugin.h> #include <gplugin-native.h> @@ -381,6 +383,30 @@ */ void purple_plugin_info_set_unloaded(PurplePluginInfo *info, gboolean unloaded); +/** + * purple_plugin_info_get_action_group: + * @info: The instance. + * + * Gets the [class:Gio.ActionGroup] from @info if one is set. + * + * Returns: (transfer full): The action group. + * + * Since: 3.0.0 + */ +GActionGroup *purple_plugin_info_get_action_group(PurplePluginInfo *info); + +/** + * purple_plugin_info_get_action_menu: + * @info: The instance. + * + * Gets the [class:Gio.MenuModel] from @info if one is set. + * + * Returns: (transfer full): The menu model. + * + * Since: 3.0.0 + */ +GMenuModel *purple_plugin_info_get_action_menu(PurplePluginInfo *info); + G_END_DECLS #endif /* PURPLE_PLUGIN_INFO_H */
--- a/pidgin/glade/pidgin3.xml.in Fri May 27 04:11:45 2022 -0500 +++ b/pidgin/glade/pidgin3.xml.in Fri May 27 14:52:34 2022 -0500 @@ -13,7 +13,6 @@ <glade-widget-class name="PidginCredentialsPage" generic-name="credentials_page" title="CredentialsPage"/> <glade-widget-class name="PidginDialog" generic-name="dialog" title="Dialog"/> <glade-widget-class name="PidginInviteDialog" generic-name="invite_dialog" title="InviteDialog"/> - <glade-widget-class name="PidginPluginsMenu" generic-name="plugins_menu" title="PluginsMenu"/> <glade-widget-class name="PidginPresenceIcon" generic-name="presence_icon" title="PresenceIcon"/> <glade-widget-class name="PidginProtocolChooser" generic-name="protocol_chooser" title="ProtocolChooser"/> <glade-widget-class name="PidginProtocolStore" generic-name="protocol_store" title="ProtocolStore"/> @@ -35,7 +34,6 @@ <glade-widget-class-ref name="PidginCredentialsPage"/> <glade-widget-class-ref name="PidginDialog"/> <glade-widget-class-ref name="PidginInviteDialog"/> - <glade-widget-class-ref name="PidginPluginsMenu"/> <glade-widget-class-ref name="PidginPresenceIcon"/> <glade-widget-class-ref name="PidginProtocolChooser"/> <glade-widget-class-ref name="PidginProtocolStore"/>
--- a/pidgin/pidginapplication.c Fri May 27 04:11:45 2022 -0500 +++ b/pidgin/pidginapplication.c Fri May 27 14:52:34 2022 -0500 @@ -51,6 +51,7 @@ #include "pidgininactiveaccountsmenu.h" #include "pidginmooddialog.h" #include "pidginpluginsdialog.h" +#include "pidginpluginsmenu.h" #include "pidginstatusmanager.h" #include "pidginprefs.h" @@ -133,6 +134,12 @@ target = gtk_application_get_menu_by_id(GTK_APPLICATION(application), "enabled-accounts"); g_menu_append_section(target, NULL, G_MENU_MODEL(source)); + + /* Link the PluginsMenu into its proper location. */ + model = pidgin_plugins_menu_new(); + target = gtk_application_get_menu_by_id(GTK_APPLICATION(application), + "plugins-menu"); + g_menu_append_section(target, NULL, model); } /******************************************************************************
--- a/pidgin/pidgincontactlist.c Fri May 27 04:11:45 2022 -0500 +++ b/pidgin/pidgincontactlist.c Fri May 27 14:52:34 2022 -0500 @@ -34,9 +34,6 @@ GtkWidget *accounts; GtkWidget *accounts_menu; - - GtkWidget *plugins; - GtkWidget *plugins_menu; }; G_DEFINE_TYPE(PidginContactList, pidgin_contact_list, @@ -60,8 +57,6 @@ gtk_menu_item_set_submenu(GTK_MENU_ITEM(contact_list->accounts), contact_list->accounts_menu); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(contact_list->plugins), - contact_list->plugins_menu); } static void @@ -83,10 +78,6 @@ accounts); gtk_widget_class_bind_template_child(widget_class, PidginContactList, accounts_menu); - gtk_widget_class_bind_template_child(widget_class, PidginContactList, - plugins); - gtk_widget_class_bind_template_child(widget_class, PidginContactList, - plugins_menu); } /******************************************************************************
--- a/pidgin/pidginpluginsmenu.c Fri May 27 04:11:45 2022 -0500 +++ b/pidgin/pidginpluginsmenu.c Fri May 27 14:52:34 2022 -0500 @@ -1,5 +1,6 @@ /* - * pidgin + * 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 @@ -16,243 +17,214 @@ * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include "pidginpluginsmenu.h" +#include "pidginapplication.h" + #include <gplugin.h> #include <purple.h> struct _PidginPluginsMenu { - GtkMenu parent; - - GtkWidget *separator; + GMenuModel parent; - GSimpleActionGroup *action_group; - - GHashTable *plugin_items; + GQueue *plugins; }; -#define PIDGIN_PLUGINS_MENU_ACTION_PREFIX "plugins-menu" +G_DEFINE_TYPE(PidginPluginsMenu, pidgin_plugins_menu, G_TYPE_MENU_MODEL) /****************************************************************************** * Helpers *****************************************************************************/ static void -pidgin_plugins_menu_action_activated(GSimpleAction *simple, GVariant *parameter, - gpointer data) -{ - PurplePluginAction *action = (PurplePluginAction *)data; +pidgin_plugins_menu_add_item(gpointer data, gpointer user_data) { + PidginPluginsMenu *menu = user_data; + GPluginPlugin *plugin = data; + GPluginPluginInfo *info = NULL; + + info = gplugin_plugin_get_info(plugin); + if(PURPLE_IS_PLUGIN_INFO(info)) { + GActionGroup *group = NULL; - if(action != NULL && action->callback != NULL) { - action->callback(action); + group = purple_plugin_info_get_action_group(PURPLE_PLUGIN_INFO(info)); + if(G_IS_ACTION_GROUP(group)) { + GApplication *application = g_application_get_default(); + const gchar *prefix; + + prefix = gplugin_plugin_info_get_id(info); + pidgin_application_add_action_group(PIDGIN_APPLICATION(application), + prefix, group); + g_object_unref(group); + + g_queue_push_tail(menu->plugins, g_object_ref(plugin)); + } } + + g_clear_object(&info); } static void -pidgin_plugins_menu_add_plugin_actions(PidginPluginsMenu *menu, - PurplePlugin *plugin) -{ - GPluginPluginInfo *info = NULL; - PurplePluginActionsCb actions_cb = NULL; - GList *actions = NULL; - GtkWidget *submenu = NULL, *item = NULL; - gint i = 0; - - info = gplugin_plugin_get_info(GPLUGIN_PLUGIN(plugin)); - - actions_cb = purple_plugin_info_get_actions_cb(PURPLE_PLUGIN_INFO(info)); - if(actions_cb == NULL) { - g_object_unref(G_OBJECT(info)); - - return; - } - - actions = actions_cb(plugin); - if(actions == NULL) { - g_object_unref(G_OBJECT(info)); - - return; - } - - submenu = gtk_menu_new(); - - for(i = 0; actions != NULL; i++) { - PurplePluginAction *action = NULL; - GSimpleAction *gaction = NULL; - GtkWidget *action_item = NULL; - gchar *action_base_name = NULL; - gchar *action_full_name = NULL; +pidgin_plugins_menu_refresh(PidginPluginsMenu *menu) { + GPluginManager *manager = NULL; + GSList *loaded = NULL; + gint removed = 0, added = 0; - action = (PurplePluginAction *)actions->data; - if(action == NULL) { - action_item = gtk_separator_menu_item_new(); - gtk_widget_show(action_item); - gtk_menu_shell_append(GTK_MENU_SHELL(submenu), action_item); - - actions = g_list_delete_link(actions, actions); - - continue; - } - - if(action->label == NULL) { - actions = g_list_delete_link(actions, actions); - - g_warn_if_reached(); - - continue; - } - - action_base_name = g_strdup_printf("%s-%d", - gplugin_plugin_info_get_id(info), - i); - action_full_name = g_strdup_printf("%s.%s", - PIDGIN_PLUGINS_MENU_ACTION_PREFIX, - action_base_name); - - /* create the menu item with the full action name */ - action_item = gtk_menu_item_new_with_label(action->label); - gtk_actionable_set_action_name(GTK_ACTIONABLE(action_item), - action_full_name); - gtk_widget_show(action_item); - g_free(action_full_name); - - /* add our action item to the menu */ - gtk_menu_shell_append(GTK_MENU_SHELL(submenu), action_item); - - /* now create the gaction with the base name */ - gaction = g_simple_action_new(action_base_name, NULL); - g_free(action_base_name); + removed = g_queue_get_length(menu->plugins); + g_queue_clear_full(menu->plugins, g_object_unref); - /* now connect to the activate signal of the action using - * g_signal_connect_data with a destroy notify to free the plugin action - * when the signal handler is removed. - */ - g_signal_connect_data(G_OBJECT(gaction), "activate", - G_CALLBACK(pidgin_plugins_menu_action_activated), - action, - (GClosureNotify)purple_plugin_action_free, - 0); - - /* finally add the action to the action group and remove our ref */ - g_action_map_add_action(G_ACTION_MAP(menu->action_group), - G_ACTION(gaction)); - g_object_unref(G_OBJECT(gaction)); - - actions = g_list_delete_link(actions, actions); - } - - item = gtk_menu_item_new_with_label(gplugin_plugin_info_get_name(info)); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu); - gtk_widget_show(item); - - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - - g_hash_table_insert(menu->plugin_items, - g_object_ref(G_OBJECT(plugin)), - item); - - g_object_unref(G_OBJECT(info)); - - /* make sure that our separator is visible */ - gtk_widget_show(menu->separator); -} + manager = gplugin_manager_get_default(); + loaded = gplugin_manager_find_plugins_with_state(manager, + GPLUGIN_PLUGIN_STATE_LOADED); -static void -pidgin_plugins_menu_remove_plugin_actions(PidginPluginsMenu *menu, - PurplePlugin *plugin) -{ - GPluginPluginInfo *info = NULL; - PurplePluginActionsCb actions_cb = NULL; - GList *actions = NULL; - gint i = 0; - - /* try remove the menu item from plugin from the hash table. If we didn't - * remove anything, we have nothing to do so bail. - */ - if(!g_hash_table_remove(menu->plugin_items, plugin)) { - return; - } - - info = gplugin_plugin_get_info(GPLUGIN_PLUGIN(plugin)); - - actions_cb = purple_plugin_info_get_actions_cb(PURPLE_PLUGIN_INFO(info)); - if(actions_cb == NULL) { - g_object_unref(G_OBJECT(info)); - - return; - } + g_slist_foreach(loaded, pidgin_plugins_menu_add_item, menu); - actions = actions_cb(plugin); - if(actions == NULL) { - g_object_unref(G_OBJECT(info)); - - return; - } - - /* now walk through the actions and remove them from the action group. */ - for(i = 0; actions != NULL; i++) { - gchar *name = NULL; - - name = g_strdup_printf("%s-%d", gplugin_plugin_info_get_id(info), i); + added = g_queue_get_length(menu->plugins); - g_action_map_remove_action(G_ACTION_MAP(menu->action_group), name); - g_free(name); - - actions = g_list_delete_link(actions, actions); - } - - g_object_unref(G_OBJECT(info)); - - /* finally, if this was the last item in the list, hide the separator. */ - if(g_hash_table_size(menu->plugin_items) == 0) { - gtk_widget_hide(menu->separator); - } + /* Tell our listeners that our menu has changed. */ + g_menu_model_items_changed(G_MENU_MODEL(menu), 0, removed, added); } /****************************************************************************** * Callbacks *****************************************************************************/ static void -pidgin_plugins_menu_plugin_loaded_cb(GObject *manager, GPluginPlugin *plugin, +pidgin_plugins_menu_plugin_loaded_cb(G_GNUC_UNUSED GObject *manager, + G_GNUC_UNUSED GPluginPlugin *plugin, gpointer data) { - pidgin_plugins_menu_add_plugin_actions(PIDGIN_PLUGINS_MENU(data), plugin); + pidgin_plugins_menu_refresh(PIDGIN_PLUGINS_MENU(data)); +} + +static void +pidgin_plugins_menu_plugin_unloaded_cb(G_GNUC_UNUSED GObject *manager, + GPluginPlugin *plugin, + gpointer data) +{ + GApplication *application = NULL; + GPluginPluginInfo *info = NULL; + const gchar *prefix; + + /* Remove the action group that the plugin added. */ + info = gplugin_plugin_get_info(plugin); + if(GPLUGIN_IS_PLUGIN_INFO(info)) { + prefix = gplugin_plugin_info_get_id(info); + + if(prefix != NULL) { + application = g_application_get_default(); + pidgin_application_add_action_group(PIDGIN_APPLICATION(application), + prefix, NULL); + } + } + + /* Refresh the list */ + pidgin_plugins_menu_refresh(PIDGIN_PLUGINS_MENU(data)); +} + +/****************************************************************************** + * GMenuModel Implementation + *****************************************************************************/ +static gboolean +pidgin_plugins_menu_is_mutable(GMenuModel *model) { + return TRUE; +} + +static gint +pidgin_plugins_menu_get_n_items(GMenuModel *model) { + PidginPluginsMenu *menu = PIDGIN_PLUGINS_MENU(model); + + return g_queue_get_length(menu->plugins); } static void -pidgin_plugins_menu_plugin_unloaded_cb(GObject *manager, GPluginPlugin *plugin, - gpointer data) +pidgin_plugins_menu_get_item_attributes(GMenuModel *model, gint index, + GHashTable **attributes) { - pidgin_plugins_menu_remove_plugin_actions(PIDGIN_PLUGINS_MENU(data), - plugin); + PidginPluginsMenu *menu = PIDGIN_PLUGINS_MENU(model); + GPluginPlugin *plugin = NULL; + GPluginPluginInfo *info = NULL; + GVariant *value = NULL; + + /* Create our hash table of attributes to return. */ + *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + (GDestroyNotify)g_variant_unref); + + /* Get the plugin the caller is interested in. */ + plugin = g_queue_peek_nth(menu->plugins, index); + if(plugin == NULL) { + return; + } + + /* Grab the plugin info and set the label attribute to the name of the + * plugin and set the action name space to the plugin's id. + */ + info = gplugin_plugin_get_info(plugin); + value = g_variant_new_string(gplugin_plugin_info_get_name(info)); + g_hash_table_insert(*attributes, G_MENU_ATTRIBUTE_LABEL, + g_variant_ref_sink(value)); + + value = g_variant_new_string(gplugin_plugin_info_get_id(info)); + g_hash_table_insert(*attributes, G_MENU_ATTRIBUTE_ACTION_NAMESPACE, + g_variant_ref_sink(value)); + + g_object_unref(info); +} + +static void +pidgin_plugins_menu_get_item_links(GMenuModel *model, gint index, + GHashTable **links) +{ + PidginPluginsMenu *menu = PIDGIN_PLUGINS_MENU(model); + PurplePluginInfo *purple_info = NULL; + GPluginPlugin *plugin = NULL; + GPluginPluginInfo *info = NULL; + GMenuModel *actions_model = NULL; + + *links = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, + g_object_unref); + + plugin = g_queue_peek_nth(menu->plugins, index); + if(!GPLUGIN_IS_PLUGIN(plugin)) { + return; + } + + info = gplugin_plugin_get_info(plugin); + purple_info = PURPLE_PLUGIN_INFO(info); + + actions_model = purple_plugin_info_get_action_menu(purple_info); + if(G_IS_MENU_MODEL(actions_model)) { + g_hash_table_insert(*links, G_MENU_LINK_SUBMENU, actions_model); + } + + g_object_unref(info); } /****************************************************************************** * GObject Implementation *****************************************************************************/ -G_DEFINE_TYPE(PidginPluginsMenu, pidgin_plugins_menu, GTK_TYPE_MENU) +static void +pidgin_plugins_menu_finalize(GObject *obj) { + PidginPluginsMenu *menu = PIDGIN_PLUGINS_MENU(obj); + + g_queue_free_full(menu->plugins, g_object_unref); + + G_OBJECT_CLASS(pidgin_plugins_menu_parent_class)->finalize(obj); +} + +static void +pidgin_plugins_menu_constructed(GObject *obj) { + G_OBJECT_CLASS(pidgin_plugins_menu_parent_class)->constructed(obj); + + pidgin_plugins_menu_refresh(PIDGIN_PLUGINS_MENU(obj)); +} static void pidgin_plugins_menu_init(PidginPluginsMenu *menu) { GPluginManager *manager = NULL; - /* initialize our template */ - gtk_widget_init_template(GTK_WIDGET(menu)); - - /* create our internal action group and assign it to ourself */ - menu->action_group = g_simple_action_group_new(); - gtk_widget_insert_action_group(GTK_WIDGET(menu), - PIDGIN_PLUGINS_MENU_ACTION_PREFIX, - G_ACTION_GROUP(menu->action_group)); - - /* create our storage for the items */ - menu->plugin_items = g_hash_table_new_full(g_direct_hash, g_direct_equal, - g_object_unref, - (GDestroyNotify)gtk_widget_destroy); + menu->plugins = g_queue_new(); /* Connect to the plugin manager's signals so we can stay up to date. */ manager = gplugin_manager_get_default(); @@ -267,22 +239,22 @@ static void pidgin_plugins_menu_class_init(PidginPluginsMenuClass *klass) { - GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + GMenuModelClass *model_class = G_MENU_MODEL_CLASS(klass); - gtk_widget_class_set_template_from_resource( - widget_class, - "/im/pidgin/Pidgin3/Plugins/menu.ui" - ); + obj_class->finalize = pidgin_plugins_menu_finalize; + obj_class->constructed = pidgin_plugins_menu_constructed; - gtk_widget_class_bind_template_child(widget_class, PidginPluginsMenu, - separator); + model_class->is_mutable = pidgin_plugins_menu_is_mutable; + model_class->get_n_items = pidgin_plugins_menu_get_n_items; + model_class->get_item_attributes = pidgin_plugins_menu_get_item_attributes; + model_class->get_item_links = pidgin_plugins_menu_get_item_links; } /****************************************************************************** * Public API *****************************************************************************/ -GtkWidget * +GMenuModel * pidgin_plugins_menu_new(void) { - return GTK_WIDGET(g_object_new(PIDGIN_TYPE_PLUGINS_MENU, NULL)); + return g_object_new(PIDGIN_TYPE_PLUGINS_MENU, NULL); } -
--- a/pidgin/pidginpluginsmenu.h Fri May 27 04:11:45 2022 -0500 +++ b/pidgin/pidginpluginsmenu.h Fri May 27 14:52:34 2022 -0500 @@ -1,5 +1,6 @@ /* - * pidgin + * 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 @@ -16,8 +17,7 @@ * 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION) @@ -34,18 +34,15 @@ /** * PidginPluginsMenu: * - * #PidginPluginsMenu is a #GtkMenu that provides an interface to users to open - * the plugin manager as well as activate plugin actions. - * - * It manages itself as plugins are loaded and unloaded and can be added as a - * submenu to any #GtkMenuItem. + * #PidginPluginsMenu is a [class@Gio.MenuModel] that automatically updates + * itself when plugins are loaded and unloaded. * * Since: 3.0.0 */ #define PIDGIN_TYPE_PLUGINS_MENU (pidgin_plugins_menu_get_type()) G_DECLARE_FINAL_TYPE(PidginPluginsMenu, pidgin_plugins_menu, PIDGIN, - PLUGINS_MENU, GtkMenu) + PLUGINS_MENU, GMenuModel) /** * pidgin_plugins_menu_new: @@ -54,7 +51,7 @@ * * Returns: (transfer full): The new #PidginPluginsMenu instance. */ -GtkWidget *pidgin_plugins_menu_new(void); +GMenuModel *pidgin_plugins_menu_new(void); G_END_DECLS
--- a/pidgin/resources/BuddyList/window.ui Fri May 27 04:11:45 2022 -0500 +++ b/pidgin/resources/BuddyList/window.ui Fri May 27 14:52:34 2022 -0500 @@ -69,14 +69,6 @@ <property name="use-underline">True</property> </object> </child> - <child> - <object class="GtkMenuItem" id="plugins"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">_Plugins</property> - <property name="use-underline">True</property> - </object> - </child> </object> <packing> <property name="expand">False</property> @@ -91,8 +83,4 @@ <property name="visible">True</property> <property name="can-focus">False</property> </object> - <object class="PidginPluginsMenu" id="plugins_menu"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> </interface>
--- a/pidgin/resources/gtk/menus.ui Fri May 27 04:11:45 2022 -0500 +++ b/pidgin/resources/gtk/menus.ui Fri May 27 14:52:34 2022 -0500 @@ -235,9 +235,7 @@ <attribute name="accel"><Primary>U</attribute> </item> </section> - <section> - <attribute name="id">plugins-menu</attribute> - </section> + <section id="plugins-menu"/> </submenu> <submenu> <attribute name="label" translatable="yes">_Help</attribute>
--- a/pidgin/resources/pidgin.gresource.xml Fri May 27 04:11:45 2022 -0500 +++ b/pidgin/resources/pidgin.gresource.xml Fri May 27 14:52:34 2022 -0500 @@ -22,7 +22,6 @@ <file compressed="true">Debug/plugininfo.ui</file> <file compressed="true">Log/log-viewer.ui</file> <file compressed="true">Plugins/dialog.ui</file> - <file compressed="true">Plugins/menu.ui</file> <file compressed="true">Prefs/away.ui</file> <file compressed="true">Prefs/conversation.ui</file> <file compressed="true">Prefs/credentials.ui</file>