pidgin/pidgin

b76bc2b4d7cc
Parents 911c16e255ff
Children 253e831b0a1e
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">&lt;Primary&gt;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>