[geany/geany] 7ac89d: plugins: improve PM dialog for proxy and sub-plugins
Thomas Martitz
git-noreply at xxxxx
Tue Oct 6 13:40:57 UTC 2015
Branch: refs/heads/master
Author: Thomas Martitz <kugel at rockbox.org>
Committer: Colomban Wendling <ban at herbesfolles.org>
Date: Tue, 06 Oct 2015 13:40:57 UTC
Commit: 7ac89deebdaf4052ed795495cfbbb8c35043a29f
https://github.com/geany/geany/commit/7ac89deebdaf4052ed795495cfbbb8c35043a29f
Log Message:
-----------
plugins: improve PM dialog for proxy and sub-plugins
Geany now remembers how many plugins depend on a pluxy. It uses this
information to disable the "Active" checkbox in the PM dialog.
Additionally, the PM dialog displays plugins in a hierarchical manner, so that
sub-plugins are shown next to pluxy. This is espcially handy since it makes
the sub-plugin <-> pluxy relationship really obvious, and it's easier to spot
which plugins need to be disabled before the pluxy can be disabled. This allows
to remove code to re-select the plugin because the row (respective to the
hierarchy level) does not change anymore.
Modified Paths:
--------------
src/pluginprivate.h
src/plugins.c
Modified: src/pluginprivate.h
2 lines changed, 2 insertions(+), 0 deletions(-)
===================================================================
@@ -73,6 +73,8 @@ typedef struct GeanyPluginPrivate
Plugin *proxy; /* The proxy that handles this plugin */
gpointer proxy_data; /* Data passed to the proxy hooks of above proxy, so
* this gives the proxy a pointer to each plugin */
+ gint proxied_count; /* count of active plugins this provides a proxy for
+ * (a count because of possibly nested proxies) */
}
GeanyPluginPrivate;
Modified: src/plugins.c
243 lines changed, 194 insertions(+), 49 deletions(-)
===================================================================
@@ -99,6 +99,8 @@ static PluginProxy builtin_so_proxy = {
static GPtrArray *active_proxies = NULL;
+static void plugin_free(Plugin *plugin);
+
static GeanyData geany_data;
static void
@@ -125,6 +127,30 @@ geany_data_init(void)
}
+/* In order to have nested proxies work the count of dependent plugins must propagate up.
+ * This prevents that any plugin in the tree is unloaded while a leaf plugin is active. */
+static void proxied_count_inc(Plugin *proxy)
+{
+ do
+ {
+ proxy->proxied_count += 1;
+ proxy = proxy->proxy;
+ } while (proxy != NULL);
+}
+
+
+static void proxied_count_dec(Plugin *proxy)
+{
+ g_warn_if_fail(proxy->proxied_count > 0);
+
+ do
+ {
+ proxy->proxied_count -= 1;
+ proxy = proxy->proxy;
+ } while (proxy != NULL);
+}
+
+
/* Prevent the same plugin filename being loaded more than once.
* Note: g_module_name always returns the .so name, even when Plugin::filename is a .la file. */
static gboolean
@@ -557,6 +583,7 @@ plugin_load(Plugin *plugin)
* keep list sorted so tools menu items and plugin preference tabs are
* sorted by plugin name */
active_plugin_list = g_list_insert_sorted(active_plugin_list, plugin, cmp_plugin_names);
+ proxied_count_inc(plugin->proxy);
geany_debug("Loaded: %s (%s)", plugin->filename, plugin->info.name);
return TRUE;
@@ -847,10 +874,56 @@ plugin_cleanup(Plugin *plugin)
plugin->cb_data_destroy = NULL;
}
+ proxied_count_dec(plugin->proxy);
geany_debug("Unloaded: %s", plugin->filename);
}
+/* Remove all plugins that proxy is a proxy for from plugin_list (and free) */
+static void free_subplugins(Plugin *proxy)
+{
+ GList *item;
+
+ item = plugin_list;
+ while (item)
+ {
+ GList *next = g_list_next(item);
+ if (proxy == ((Plugin *) item->data)->proxy)
+ {
+ /* plugin_free modifies plugin_list */
+ plugin_free((Plugin *) item->data);
+ }
+ item = next;
+ }
+}
+
+
+/* Returns true if the removal was successful (=> never for non-proxies) */
+static gboolean unregister_proxy(Plugin *proxy)
+{
+ PluginProxy *p;
+ guint i;
+ gboolean is_proxy = FALSE;
+
+ /* Remove the proxy from the proxy list first. It might appear more than once (once
+ * for each extension), but if it doesn't appear at all it's not actually a proxy */
+ foreach_ptr_array(p, i, active_proxies)
+ {
+ if (p->plugin == proxy)
+ {
+ is_proxy = TRUE;
+ g_ptr_array_remove(active_proxies, p);
+ i -= 1;
+ }
+ }
+ return is_proxy;
+}
+
+
+/* Cleanup a plugin and free all resources allocated on behalf of it.
+ *
+ * If the plugin is a proxy then this also takes special care to unload all
+ * subplugin loaded through it (make sure none of them is active!) */
static void
plugin_free(Plugin *plugin)
{
@@ -858,10 +931,17 @@ plugin_free(Plugin *plugin)
g_return_if_fail(plugin);
g_return_if_fail(plugin->proxy);
+ g_return_if_fail(plugin->proxied_count == 0);
proxy = plugin->proxy;
+ /* If this a proxy remove all depending subplugins. We can assume none of them is *activated*
+ * (but potentially loaded). Note that free_subplugins() might call us through recursion */
if (is_active_plugin(plugin))
+ {
+ if (unregister_proxy(plugin))
+ free_subplugins(plugin);
plugin_cleanup(plugin);
+ }
active_plugin_list = g_list_remove(active_plugin_list, plugin);
plugin_list = g_list_remove(plugin_list, plugin);
@@ -873,7 +953,6 @@ plugin_free(Plugin *plugin)
g_free(plugin->filename);
g_free(plugin);
- plugin = NULL;
}
@@ -1048,6 +1127,28 @@ static gchar *get_plugin_path(void)
}
+/* See load_all_plugins(), this simply sorts items with lower hierarchy level first
+ * (where hierarchy level == number of intermediate proxies before the builtin so loader) */
+static gint cmp_plugin_by_proxy(gconstpointer a, gconstpointer b)
+{
+ const Plugin *pa = a;
+ const Plugin *pb = b;
+
+ while (TRUE)
+ {
+ if (pa->proxy == pb->proxy)
+ return 0;
+ else if (pa->proxy == &builtin_so_proxy_plugin)
+ return -1;
+ else if (pb->proxy == &builtin_so_proxy_plugin)
+ return 1;
+
+ pa = pa->proxy;
+ pb = pb->proxy;
+ }
+}
+
+
/* Load (but don't initialize) all plugins for the Plugin Manager dialog */
static void load_all_plugins(void)
{
@@ -1072,6 +1173,13 @@ static void load_all_plugins(void)
/* finally load plugins from $prefix/lib/geany */
load_plugins_from_path(plugin_path_system);
+ /* It is important to sort any plugins that are proxied after their proxy because
+ * pm_populate() needs the proxy to be loaded and active (if selected by user) in order
+ * to properly set the value for the PLUGIN_COLUMN_CAN_UNCHECK column. The order between
+ * sub-plugins does not matter, only between sub-plugins and their proxy, thus
+ * sorting by hierarchy level is perfectly sufficient */
+ plugin_list = g_list_sort(plugin_list, cmp_plugin_by_proxy);
+
g_free(plugin_path_config);
g_free(plugin_path_system);
}
@@ -1195,6 +1303,15 @@ void plugins_init(void)
}
+/* Same as plugin_free(), except it does nothing for proxies-in-use, to be called on
+ * finalize in a loop */
+static void plugin_free_leaf(Plugin *p)
+{
+ if (p->proxied_count == 0)
+ plugin_free(p);
+}
+
+
/* called even if plugin support is disabled */
void plugins_finalize(void)
{
@@ -1203,11 +1320,11 @@ void plugins_finalize(void)
g_list_foreach(failed_plugins_list, (GFunc) g_free, NULL);
g_list_free(failed_plugins_list);
}
- if (active_plugin_list != NULL)
- {
- g_list_foreach(active_plugin_list, (GFunc) plugin_free, NULL);
- g_list_free(active_plugin_list);
- }
+ /* Have to loop because proxys cannot be unloaded until after all their
+ * plugins are unloaded as well (the second loop should should catch all the remaining ones) */
+ while (active_plugin_list != NULL)
+ g_list_foreach(active_plugin_list, (GFunc) plugin_free_leaf, NULL);
+
g_strfreev(active_plugins_pref);
}
@@ -1236,6 +1353,7 @@ gboolean plugins_have_preferences(void)
enum
{
PLUGIN_COLUMN_CHECK = 0,
+ PLUGIN_COLUMN_CAN_UNCHECK,
PLUGIN_COLUMN_PLUGIN,
PLUGIN_N_COLUMNS,
PM_BUTTON_KEYBINDINGS,
@@ -1247,7 +1365,7 @@ typedef struct
{
GtkWidget *dialog;
GtkWidget *tree;
- GtkListStore *store;
+ GtkTreeStore *store;
GtkWidget *filter_entry;
GtkWidget *configure_button;
GtkWidget *keybindings_button;
@@ -1319,24 +1437,7 @@ static gboolean find_iter_for_plugin(Plugin *p, GtkTreeModel *model, GtkTreeIter
}
-static gboolean select_plugin(gpointer data)
-{
- GtkTreeIter iter;
- Plugin *p = data;
- GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_widgets.tree));
-
- /* restore selection */
- if (find_iter_for_plugin(p, model, &iter))
- {
- GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pm_widgets.tree));
- gtk_tree_selection_select_iter(sel, &iter);
- }
-
- return G_SOURCE_REMOVE;
-}
-
-
-static void pm_populate(GtkListStore *store);
+static void pm_populate(GtkTreeStore *store);
static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data)
@@ -1352,7 +1453,6 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer
guint prev_num_proxies;
gtk_tree_model_get_iter(model, &iter, path);
- gtk_tree_path_free(path);
gtk_tree_model_get(model, &iter,
PLUGIN_COLUMN_CHECK, &old_state,
@@ -1360,7 +1460,10 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer
/* no plugins item */
if (p == NULL)
+ {
+ gtk_tree_path_free(path);
return;
+ }
gtk_tree_model_filter_convert_iter_to_child_iter(
GTK_TREE_MODEL_FILTER(model), &store_iter, &iter);
@@ -1384,7 +1487,7 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer
if (!p)
{
/* plugin file may no longer be on disk, or is now incompatible */
- gtk_list_store_remove(pm_widgets.store, &store_iter);
+ gtk_tree_store_remove(pm_widgets.store, &store_iter);
}
else
{
@@ -1392,40 +1495,66 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer
keybindings_load_keyfile(); /* load shortcuts */
/* update model */
- gtk_list_store_set(pm_widgets.store, &store_iter,
+ gtk_tree_store_set(pm_widgets.store, &store_iter,
PLUGIN_COLUMN_CHECK, state,
PLUGIN_COLUMN_PLUGIN, p, -1);
/* set again the sensitiveness of the configure and help buttons */
pm_update_buttons(p);
+
+ /* Depending on the state disable the checkbox for the proxy of this plugin, and
+ * only re-enable if the proxy is not used by any other plugin */
+ if (p->proxy != &builtin_so_proxy_plugin)
+ {
+ GtkTreeIter parent;
+ gboolean can_uncheck;
+ GtkTreePath *store_path = gtk_tree_model_filter_convert_path_to_child_path(
+ GTK_TREE_MODEL_FILTER(model), path);
+
+ g_warn_if_fail(store_path != NULL);
+ if (gtk_tree_path_up(store_path))
+ {
+ gtk_tree_model_get_iter(GTK_TREE_MODEL(pm_widgets.store), &parent, store_path);
+
+ if (state)
+ can_uncheck = FALSE;
+ else
+ can_uncheck = p->proxy->proxied_count == 0;
+
+ gtk_tree_store_set(pm_widgets.store, &parent,
+ PLUGIN_COLUMN_CAN_UNCHECK, can_uncheck, -1);
+ }
+ gtk_tree_path_free(store_path);
+ }
}
/* We need to find out if a proxy was added or removed because that affects the plugin list
- * presented by the plugin manager. The current solution counts active_proxies twice,
- * this suboptimal from an algorithmic POV, however most efficient for the extremely small
- * number (at most 3) of pluxies we expect users to load */
+ * presented by the plugin manager */
if (prev_num_proxies != active_proxies->len)
{
- /* Rescan the plugin list as we now support more */
+ /* Rescan the plugin list as we now support more. Gives some "already loaded" warnings
+ * they are unproblematic */
if (prev_num_proxies < active_proxies->len)
load_all_plugins();
+
pm_populate(pm_widgets.store);
- /* restore selection. doesn't work if it's done immediately (same row keeps selected) */
- g_idle_add(select_plugin, p);
+ gtk_tree_view_expand_row(GTK_TREE_VIEW(pm_widgets.tree), path, FALSE);
}
+
+ gtk_tree_path_free(path);
g_free(file_name);
}
-static void pm_populate(GtkListStore *store)
+static void pm_populate(GtkTreeStore *store)
{
GtkTreeIter iter;
GList *list;
- gtk_list_store_clear(store);
+ gtk_tree_store_clear(store);
list = g_list_first(plugin_list);
if (list == NULL)
{
- gtk_list_store_append(store, &iter);
- gtk_list_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE,
+ gtk_tree_store_append(store, &iter, NULL);
+ gtk_tree_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE,
PLUGIN_COLUMN_PLUGIN, NULL, -1);
}
else
@@ -1433,11 +1562,18 @@ static void pm_populate(GtkListStore *store)
for (; list != NULL; list = list->next)
{
Plugin *p = list->data;
+ GtkTreeIter parent;
+
+ if (p->proxy != &builtin_so_proxy_plugin
+ && find_iter_for_plugin(p->proxy, GTK_TREE_MODEL(pm_widgets.store), &parent))
+ gtk_tree_store_append(store, &iter, &parent);
+ else
+ gtk_tree_store_append(store, &iter, NULL);
- gtk_list_store_append(store, &iter);
- gtk_list_store_set(store, &iter,
+ gtk_tree_store_set(store, &iter,
PLUGIN_COLUMN_CHECK, is_active_plugin(p),
PLUGIN_COLUMN_PLUGIN, p,
+ PLUGIN_COLUMN_CAN_UNCHECK, (p->proxied_count == 0),
-1);
}
}
@@ -1450,26 +1586,33 @@ static gboolean pm_treeview_query_tooltip(GtkWidget *widget, gint x, gint y,
GtkTreeIter iter;
GtkTreePath *path;
Plugin *p = NULL;
+ gboolean can_uncheck = TRUE;
if (! gtk_tree_view_get_tooltip_context(GTK_TREE_VIEW(widget), &x, &y, keyboard_mode,
&model, &path, &iter))
return FALSE;
- gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, -1);
+ gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, PLUGIN_COLUMN_CAN_UNCHECK, &can_uncheck, -1);
if (p != NULL)
{
- gchar *markup;
- gchar *details;
+ gchar *prefix, *suffix, *details, *markup;
+ const gchar *uchk;
+ uchk = can_uncheck ?
+ "" : _("\n<i>Other plugins depend on this. Disable them first to allow deactivation.</i>\n");
+ /* Four allocations is less than ideal but meh */
details = g_strdup_printf(_("Version:\t%s\nAuthor(s):\t%s\nFilename:\t%s"),
p->info.version, p->info.author, p->filename);
- markup = g_markup_printf_escaped("<b>%s</b>\n%s\n<small><i>\n%s</i></small>",
- p->info.name, p->info.description, details);
+ prefix = g_markup_printf_escaped("<b>%s</b>\n%s\n", p->info.name, p->info.description);
+ suffix = g_markup_printf_escaped("<small><i>\n%s</i></small>", details);
+ markup = g_strconcat(prefix, uchk, suffix, NULL);
gtk_tooltip_set_markup(tooltip, markup);
gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, path);
g_free(details);
+ g_free(suffix);
+ g_free(prefix);
g_free(markup);
}
gtk_tree_path_free(path);
@@ -1605,7 +1748,7 @@ static void on_pm_tree_filter_entry_icon_release_cb(GtkEntry *entry, GtkEntryIco
}
-static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store)
+static void pm_prepare_treeview(GtkWidget *tree, GtkTreeStore *store)
{
GtkCellRenderer *text_renderer, *checkbox_renderer;
GtkTreeViewColumn *column;
@@ -1618,7 +1761,8 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store)
checkbox_renderer = gtk_cell_renderer_toggle_new();
column = gtk_tree_view_column_new_with_attributes(
- _("Active"), checkbox_renderer, "active", PLUGIN_COLUMN_CHECK, NULL);
+ _("Active"), checkbox_renderer,
+ "active", PLUGIN_COLUMN_CHECK, "activatable", PLUGIN_COLUMN_CAN_UNCHECK, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
g_signal_connect(checkbox_renderer, "toggled", G_CALLBACK(pm_plugin_toggled), NULL);
@@ -1760,9 +1904,10 @@ static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data)
/* prepare treeview */
pm_widgets.tree = gtk_tree_view_new();
- pm_widgets.store = gtk_list_store_new(
- PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_POINTER);
+ pm_widgets.store = gtk_tree_store_new(
+ PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_POINTER);
pm_prepare_treeview(pm_widgets.tree, pm_widgets.store);
+ gtk_tree_view_expand_all(GTK_TREE_VIEW(pm_widgets.tree));
g_object_unref(pm_widgets.store);
swin = gtk_scrolled_window_new(NULL, NULL);
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
More information about the Commits
mailing list