Tue, 31 Mar 2020 23:30:54 -0500
Add the new PidginPluginsMenu which manages itself when plugins and loaded/unloaded.
--- a/pidgin/glade/pidgin3.xml.in Tue Mar 31 05:38:10 2020 -0500 +++ b/pidgin/glade/pidgin3.xml.in Tue Mar 31 23:30:54 2020 -0500 @@ -4,12 +4,14 @@ <glade-widget-class name="PidginAccountChooser" generic-name="account_chooser" title="AccountChooser"/> <glade-widget-class name="PidginInviteDialog" generic-name="invite_dialog" title="InviteDialog"/> <glade-widget-class name="PidginMenuTray" generic-name="menu_tray" title="MenuTray"/> + <glade-widget-class name="PidginPluginsMenu" generic-name="plugins_menu" title="PluginsMenu"/> <glade-widget-class name="PidginScrollBook" generic-name="scroll_book" title="ScrollBook"/> </glade-widget-classes> <glade-widget-group name="pidgin" title="Pidgin"> <glade-widget-class-ref name="PidginAccountChooser"/> <glade-widget-class-ref name="PidginInviteDialog"/> <glade-widget-class-ref name="PidginMenuTray"/> + <glade-widget-class-ref name="PidginPluginsMenu"/> <glade-widget-class-ref name="PidginScrollBook"/> </glade-widget-group> </glade-catalog>
--- a/pidgin/meson.build Tue Mar 31 05:38:10 2020 -0500 +++ b/pidgin/meson.build Tue Mar 31 23:30:54 2020 -0500 @@ -46,6 +46,7 @@ 'pidginmessage.c', 'pidginplugininfo.c', 'pidginpluginsdialog.c', + 'pidginpluginsmenu.c', 'pidginprotocolchooser.c', 'pidginprotocolstore.c', 'pidgintalkatu.c', @@ -102,6 +103,7 @@ 'pidginmessage.h', 'pidginplugininfo.h', 'pidginpluginsdialog.h', + 'pidginpluginsmenu.h', 'pidginprotocolchooser.h', 'pidginprotocolstore.h', 'pidgintalkatu.h',
--- a/pidgin/pidginactiongroup.c Tue Mar 31 05:38:10 2020 -0500 +++ b/pidgin/pidginactiongroup.c Tue Mar 31 23:30:54 2020 -0500 @@ -36,7 +36,6 @@ #include "pidgin/gtkxfer.h" #include "pidgin/pidginabout.h" #include "pidgin/pidginlog.h" -#include "pidgin/pidginpluginsdialog.h" struct _PidginActionGroup { GSimpleActionGroup parent; @@ -430,20 +429,6 @@ } static void -pidgin_action_group_plugins(GSimpleAction *simple, GVariant *parameter, - gpointer data) -{ - GtkWidget *dialog = pidgin_plugins_dialog_new(); - - /* fixme? */ -#if 0 - gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); -#endif - - gtk_widget_show_all(dialog); -} - -static void pidgin_action_group_preferences(GSimpleAction *simple, GVariant *parameter, gpointer data) { @@ -587,9 +572,6 @@ .name = PIDGIN_ACTION_ONLINE_HELP, .activate = pidgin_action_group_online_help, }, { - .name = PIDGIN_ACTION_PLUGINS, - .activate = pidgin_action_group_plugins, - }, { .name = PIDGIN_ACTION_PREFERENCES, .activate = pidgin_action_group_preferences, }, {
--- a/pidgin/pidginactiongroup.h Tue Mar 31 05:38:10 2020 -0500 +++ b/pidgin/pidginactiongroup.h Tue Mar 31 23:30:54 2020 -0500 @@ -125,13 +125,6 @@ #define PIDGIN_ACTION_ONLINE_HELP ("online-help") /** - * PIDGIN_ACTION_PLUGINS: - * - * A constant that represents the plugins action. - */ -#define PIDGIN_ACTION_PLUGINS ("plugins") - -/** * PIDGIN_ACTION_PREFERENCES: * * A constant that represents the preferences action.
--- a/pidgin/pidginbuddylistmenu.c Tue Mar 31 05:38:10 2020 -0500 +++ b/pidgin/pidginbuddylistmenu.c Tue Mar 31 23:30:54 2020 -0500 @@ -22,10 +22,13 @@ #include "pidginbuddylistmenu.h" +#include <pidgin/pidginpluginsmenu.h> + struct _PidginBuddyListMenu { GtkMenuBar parent; GtkWidget *sort_buddies; + GtkWidget *plugins; }; /****************************************************************************** @@ -36,6 +39,9 @@ static void pidgin_buddy_list_menu_init(PidginBuddyListMenu *menu) { gtk_widget_init_template(GTK_WIDGET(menu)); + + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu->plugins), + pidgin_plugins_menu_new()); } static void @@ -49,6 +55,8 @@ gtk_widget_class_bind_template_child(widget_class, PidginBuddyListMenu, sort_buddies); + gtk_widget_class_bind_template_child(widget_class, PidginBuddyListMenu, + plugins); } /******************************************************************************
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pidginpluginsmenu.c Tue Mar 31 23:30:54 2020 -0500 @@ -0,0 +1,303 @@ +/* + * pidgin + * + * 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 + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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 + */ + +#include "pidginpluginsmenu.h" + +#include <gplugin.h> + +#include <purple.h> + +#include "internal.h" + +#include "pidgin/pidginpluginsdialog.h" + +struct _PidginPluginsMenu { + GtkMenu parent; + + GtkWidget *separator; + + GSimpleActionGroup *action_group; + + GHashTable *plugin_items; +}; + +#define PIDGIN_PLUGINS_MENU_ACTION_PREFIX "plugins-menu" + +/****************************************************************************** + * Helpers + *****************************************************************************/ +static void +pidgin_plugins_menu_action_activated(GSimpleAction *simple, GVariant *parameter, + gpointer data) +{ + PurplePluginAction *action = (PurplePluginAction *)data; + + if(action != NULL && action->callback != NULL) { + action->callback(action); + } +} + +static void +pidgin_plugins_menu_add_plugin_actions(PidginPluginsMenu *menu, + PurplePlugin *plugin) +{ + GPluginPluginInfo *info = NULL; + PurplePluginActionsCb actions_cb = NULL; + GList *actions = NULL, *l = 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(l = actions, i = 0; l != NULL; l = l->next, i++) { + PurplePluginAction *action = (PurplePluginAction *)l->data; + GSimpleAction *gaction = NULL; + GtkWidget *action_item = NULL; + gchar *action_base_name = NULL; + gchar *action_full_name = NULL; + + if(action->label == NULL) { + 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 Creation the gaction with the base name */ + gaction = g_simple_action_new(action_base_name, NULL); + g_free(action_base_name); + + /* now connect to the activate signal of the action using + * g_signal_connect_data with a destory 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)); + } + + 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); +} + +static void +pidgin_plugins_menu_remove_plugin_actions(PidginPluginsMenu *menu, + PurplePlugin *plugin) +{ + GPluginPluginInfo *info = NULL; + PurplePluginActionsCb actions_cb = NULL; + GList *actions = NULL, *l = 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; + } + + 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(l = actions, i = 0; l != NULL; l = l->next, i++) { + gchar *name = NULL; + + name = g_strdup_printf("%s-%d", gplugin_plugin_info_get_id(info), i); + + g_action_map_remove_action(G_ACTION_MAP(menu->action_group), name); + g_free(name); + } + + 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); + } +} + +/****************************************************************************** + * Purple Signal Callbacks + *****************************************************************************/ +static void +pidgin_plugins_menu_plugin_load_cb(PurplePlugin *plugin, gpointer data) { + pidgin_plugins_menu_add_plugin_actions(PIDGIN_PLUGINS_MENU(data), plugin); +} + +static void +pidgin_plugins_menu_plugin_unload_cb(PurplePlugin *plugin, gpointer data) { + pidgin_plugins_menu_remove_plugin_actions(PIDGIN_PLUGINS_MENU(data), + plugin); +} + +/****************************************************************************** + * Static Actions + *****************************************************************************/ +static void +pidgin_plugins_menu_show_manager(GSimpleAction *action, GVariant *parameter, + gpointer data) +{ + GtkWidget *dialog = pidgin_plugins_dialog_new(); + + /* fixme? */ +#if 0 + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window)); +#endif + + gtk_widget_show_all(dialog); +} + +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +G_DEFINE_TYPE(PidginPluginsMenu, pidgin_plugins_menu, GTK_TYPE_MENU) + +static void +pidgin_plugins_menu_init(PidginPluginsMenu *menu) { + GActionEntry actions[] = { + { + .name = "manager", + .activate = pidgin_plugins_menu_show_manager, + } + }; + gpointer handle; + + /* 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(); + g_action_map_add_action_entries(G_ACTION_MAP(menu->action_group), actions, + G_N_ELEMENTS(actions), NULL); + 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); + + /* finally connect to the purple signals to stay up to date */ + handle = purple_plugins_get_handle(); + purple_signal_connect(handle, "plugin-load", menu, + PURPLE_CALLBACK(pidgin_plugins_menu_plugin_load_cb), + menu); + purple_signal_connect(handle, "plugin-unload", menu, + PURPLE_CALLBACK(pidgin_plugins_menu_plugin_unload_cb), + menu); +}; + +static void +pidgin_plugins_menu_finalize(GObject *obj) { + purple_signals_disconnect_by_handle(obj); + + G_OBJECT_CLASS(pidgin_plugins_menu_parent_class)->finalize(obj); +} + +static void +pidgin_plugins_menu_class_init(PidginPluginsMenuClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + + obj_class->finalize = pidgin_plugins_menu_finalize; + + gtk_widget_class_set_template_from_resource( + widget_class, + "/im/pidgin/Pidgin/Plugins/menu.ui" + ); + + gtk_widget_class_bind_template_child(widget_class, PidginPluginsMenu, + separator); +} + +/****************************************************************************** + * Public API + *****************************************************************************/ +GtkWidget * +pidgin_plugins_menu_new(void) { + return GTK_WIDGET(g_object_new(PIDGIN_TYPE_PLUGINS_MENU, NULL)); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pidginpluginsmenu.h Tue Mar 31 23:30:54 2020 -0500 @@ -0,0 +1,44 @@ +/* + * pidgin + * + * 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 + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * 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 + */ +#ifndef PIDGIN_PLUGINS_MENU_H +#define PIDGIN_PLUGINS_MENU_H + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define PIDGIN_TYPE_PLUGINS_MENU (pidgin_plugins_menu_get_type()) +G_DECLARE_FINAL_TYPE(PidginPluginsMenu, pidgin_plugins_menu, PIDGIN, + PLUGINS_MENU, GtkMenu) + +/** + * pidgin_action_group_new: + * + * Creates a new #PidginPluginsMenu instance that keeps itself up to date. + * + * Returns: (transfer full): The new #PidginPluginsMenu instance. + */ +GtkWidget *pidgin_plugins_menu_new(void); + +G_END_DECLS + +#endif /* PIDGIN_PLUGINS_MENU_H */
--- a/pidgin/resources/BuddyList/menu.ui Tue Mar 31 05:38:10 2020 -0500 +++ b/pidgin/resources/BuddyList/menu.ui Tue Mar 31 23:30:54 2020 -0500 @@ -236,16 +236,6 @@ </object> </child> <child> - <object class="GtkMenuItem" id="plugins"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="action_name">blist.plugins</property> - <property name="label" translatable="yes">Plu_gins</property> - <property name="use_underline">True</property> - <accelerator key="u" signal="activate" modifiers="GDK_CONTROL_MASK"/> - </object> - </child> - <child> <object class="GtkMenuItem" id="preferences"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -328,6 +318,14 @@ </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> + <child> <object class="GtkMenuItem"> <property name="visible">True</property> <property name="can_focus">False</property>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/resources/Plugins/menu.ui Tue Mar 31 23:30:54 2020 -0500 @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.2 + +Pidgin - Internet Messenger +Copyright (C) Pidgin Developers <devel@pidgin.im> + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +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 02110-1301, USA. + +--> +<interface> + <requires lib="gtk+" version="3.22"/> + <!-- interface-license-type gplv2 --> + <!-- interface-name Pidgin --> + <!-- interface-description Internet Messenger --> + <!-- interface-copyright Pidgin Developers <devel@pidgin.im> --> + <template class="PidginPluginsMenu" parent="GtkMenu"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkMenuItem"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="action_name">plugins-menu.manager</property> + <property name="label" translatable="yes">_Manager</property> + <property name="use_underline">True</property> + <accelerator key="u" signal="activate" modifiers="GDK_CONTROL_MASK"/> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem" id="separator"> + <property name="can_focus">False</property> + </object> + </child> + </template> +</interface>
--- a/pidgin/resources/pidgin.gresource.xml Tue Mar 31 05:38:10 2020 -0500 +++ b/pidgin/resources/pidgin.gresource.xml Tue Mar 31 23:30:54 2020 -0500 @@ -12,6 +12,7 @@ <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/prefs.ui</file> <file compressed="true">Prefs/vv.ui</file> <file compressed="true">Privacy/dialog.ui</file>