From cafa5a6513979523090e9b16fbe4cc59e0312590 Mon Sep 17 00:00:00 2001 From: Matthew Brush Date: Tue, 20 Sep 2016 20:35:54 -0700 Subject: [PATCH 1/3] Add helper macro to register module-scoped GObjects --- src/plugindata.h | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/plugindata.h b/src/plugindata.h index 7ee8f3090f..aa17093e94 100644 --- a/src/plugindata.h +++ b/src/plugindata.h @@ -59,7 +59,7 @@ G_BEGIN_DECLS * @warning You should not test for values below 200 as previously * @c GEANY_API_VERSION was defined as an enum value, not a macro. */ -#define GEANY_API_VERSION 229 +#define GEANY_API_VERSION 230 /* hack to have a different ABI when built with GTK3 because loading GTK2-linked plugins * with GTK3-linked Geany leads to crash */ @@ -339,6 +339,39 @@ void geany_plugin_set_data(GeanyPlugin *plugin, gpointer data, GDestroyNotify fr geany_plugin_register_full((plugin), GEANY_API_VERSION, \ (min_api_version), GEANY_ABI_VERSION, (pdata), (free_func)) +/** + * Convenience macro to register a GObject instance. + * + * The instance is scoped to the module's lifetime, which is distinct + * from the activation lifetime of the plugin. Typically the GObject + * will have member functions to handle the `init()`, `cleanup()` and + * other GeanyPluginFuncs. + * + * @warning Forgetting to pass `NULL` as the last argument is likely + * to lead strange and confusing (undefined) behaviour. The compiler + * should offer useful warnings if not ignored. + * + * @warning Never call geany_plugin_set_data() if you have used this + * macro. The data has already been bound to the instance of the + * given GType. + * + * @param plugin The GeanyPlugin instance. + * @param min_api The minimum GEANY_API_VERSION required. + * @param gtype The GType of the GObject class. + * @param ... A `NULL`-terminated list of construction properties + * as passed to `g_object_new()`. + * + * @since Geany 1.29 (API 230) + * + * @see GEANY_PLUGIN_REGISTER_FULL() + */ +#define GEANY_PLUGIN_REGISTER_OBJECT(plugin, min_api, gtype, ...) \ + plugin_module_make_resident(plugin); \ + GEANY_PLUGIN_REGISTER_FULL(plugin, \ + min_api, \ + g_object_new(gtype, __VA_ARGS__), \ + g_object_unref) + /** Return values for GeanyProxyHooks::probe() * * @see geany_plugin_register_proxy() for a full description of the proxy plugin mechanisms. From 16c487aa0db51e41c39026636cec13f0a959c431 Mon Sep 17 00:00:00 2001 From: Matthew Brush Date: Tue, 20 Sep 2016 20:44:52 -0700 Subject: [PATCH 2/3] Add typedefs and more documentation for plugin funcs --- src/plugindata.h | 91 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/src/plugindata.h b/src/plugindata.h index aa17093e94..7fb43046a4 100644 --- a/src/plugindata.h +++ b/src/plugindata.h @@ -59,7 +59,7 @@ G_BEGIN_DECLS * @warning You should not test for values below 200 as previously * @c GEANY_API_VERSION was defined as an enum value, not a macro. */ -#define GEANY_API_VERSION 230 +#define GEANY_API_VERSION 231 /* hack to have a different ABI when built with GTK3 because loading GTK2-linked plugins * with GTK3-linked Geany leads to crash */ @@ -284,6 +284,85 @@ void geany_load_module(GeanyPlugin *plugin); #endif +/** + * Type of function called when the plugin is activated. + * + * This happens either when the user activates it from the Plugin Manager + * dialog or when Geany starts, if the plugin was activated at last run. + * + * @param plugin Pointer to the GeanyPlugin structure for the plugin. + * @param pdata Pointer passed to one of the `_register()` or `_set_data()` + * functions for the plugin. + * + * @returns `TRUE` if the plugin loaded successfully, `FALSE` otherwise. + * + * @since Geany 1.29 (API 231) + * @see GeanyPluginCleanupFunc + */ +typedef gboolean (*GeanyPluginInitFunc) (GeanyPlugin *plugin, gpointer pdata); + +/** + * Type of function called when the plugin should create a configuration GUI. + * + * This happens when the user activates Geany's "Plugin Preferences" dialog + * in order to configure plugins. The plugin should create its preferences + * GUI (if not already created) and return the top-most GtkWidget. Usually + * this will be some kind of GtkBox or GtkNotebook or other container, and + * at present it will be added to a GtkNotebook as a page along with other + * plugins. If you intend to recycle to same GUI each call, remember to + * hold a GObject reference on the widget so it is not destroyed when + * the "Plugin Preferences" dialog is. + * + * The @a dialog parameter is provided so plugins can connect to its + * "response" signal. Typically a plugin will handle `GTK_RESPONSE_OK` + * (the dialog was accepted and dismissed), `GTK_RESPONSE_ACCEPT` (the + * "Apply" button was clicked but the dialog was not dismissed), or any + * other response meaning the dialog was cancelled without accepting any + * changes. + * + * @param plugin Pointer to the GeanyPlugin structure for the plugin. + * @param dialog The top-level window/dialog beneath which the provided + * GUI will be packed into. + * @param pdata Pointer passed to one of the `_register()` or `_set_data()` + * functions for the plugin. + * + * @returns A GtkWidget providing the GUI needed to configure the plugin. + * + * @since Geany 1.29 (API 231) + */ +typedef GtkWidget* (*GeanyPluginConfigureFunc) (GeanyPlugin *plugin, GtkDialog *dialog, gpointer pdata); + +/** + * Type of function called when the plugin should show its help documentation. + * + * Commonly, a plugin will ship with some kind of documentation viewable + * in a web browser. To enable this, the utils_open_browser function is + * provided to plugins. Alternatively, a plugin could load its README or + * other documentation file(s) directly into Geany using document_open_file. + * + * @param plugin Pointer to the GeanyPlugin structure for the plugin. + * @param pdata Pointer passed to one of the `_register()` or `_set_data()` + * functions for the plugin. + * + * @since Geany 1.29 (API 231) + */ +typedef void (*GeanyPluginHelpFunc) (GeanyPlugin *plugin, gpointer pdata); + +/** + * Type of function called when the plugin is de-activated. + * + * This either occurs when the user de-activates the plugin using the + * Plugin Manager dialog or when Geany itself is closing. Plugins should + * perform any required cleanup or restoration at this point. + * + * @param plugin Pointer to the GeanyPlugin structure for the plugin. + * @param pdata Pointer passed to one of the `_register()` or `_set_data()` + * functions for the plugin. + * + * @since Geany 1.29 (API 231) + */ +typedef void (*GeanyPluginCleanupFunc) (GeanyPlugin *plugin, gpointer pdata); + /** Callback functions that need to be implemented for every plugin. * * These callbacks should be registered by the plugin within Geany's call to @@ -301,13 +380,13 @@ struct GeanyPluginFuncs /** Array of plugin-provided signal handlers @see PluginCallback */ PluginCallback *callbacks; /** Called to initialize the plugin, when the user activates it (must not be @c NULL) */ - gboolean (*init) (GeanyPlugin *plugin, gpointer pdata); - /** plugins configure dialog, optional (can be @c NULL) */ - GtkWidget* (*configure) (GeanyPlugin *plugin, GtkDialog *dialog, gpointer pdata); + GeanyPluginInitFunc init; + /** Plugin's configure dialog, optional (can be @c NULL) */ + GeanyPluginConfigureFunc configure; /** Called when the plugin should show some help, optional (can be @c NULL) */ - void (*help) (GeanyPlugin *plugin, gpointer pdata); + GeanyPluginHelpFunc help; /** Called when the plugin is disabled or when Geany exits (must not be @c NULL) */ - void (*cleanup) (GeanyPlugin *plugin, gpointer pdata); + GeanyPluginCleanupFunc cleanup; }; gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, From f5e4796cfffb327e95dbf0860992ff0429fd52d0 Mon Sep 17 00:00:00 2001 From: Matthew Brush Date: Tue, 20 Sep 2016 21:36:34 -0700 Subject: [PATCH 3/3] Add swapped plugin funcs, typedefs and documentation To allow plugins using plugin data as an instance to use "methods" directly rather than having to add small boilerplate wrapper functions. --- src/plugindata.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++- src/plugins.c | 48 +++++++++++++++++++++++++----------- src/pluginutils.c | 11 ++++++--- 3 files changed, 103 insertions(+), 19 deletions(-) diff --git a/src/plugindata.h b/src/plugindata.h index 7fb43046a4..f733629603 100644 --- a/src/plugindata.h +++ b/src/plugindata.h @@ -59,7 +59,7 @@ G_BEGIN_DECLS * @warning You should not test for values below 200 as previously * @c GEANY_API_VERSION was defined as an enum value, not a macro. */ -#define GEANY_API_VERSION 231 +#define GEANY_API_VERSION 232 /* hack to have a different ABI when built with GTK3 because loading GTK2-linked plugins * with GTK3-linked Geany leads to crash */ @@ -301,6 +301,15 @@ void geany_load_module(GeanyPlugin *plugin); */ typedef gboolean (*GeanyPluginInitFunc) (GeanyPlugin *plugin, gpointer pdata); +/** + * Like @ref GeanyPluginInitFunc but with argument 1 and 2 swapped. + * + * @since Geany 1.29 (API 232) + * @see GeanyPluginInitFunc + * @see GeanyPluginSwappedCleanup + */ +typedef gboolean (*GeanyPluginSwappedInitFunc) (gpointer pdata, GeanyPlugin *plugin); + /** * Type of function called when the plugin should create a configuration GUI. * @@ -332,6 +341,14 @@ typedef gboolean (*GeanyPluginInitFunc) (GeanyPlugin *plugin, gpointer pdata); */ typedef GtkWidget* (*GeanyPluginConfigureFunc) (GeanyPlugin *plugin, GtkDialog *dialog, gpointer pdata); +/** + * Like @ref GeanyPluginConfigureFunc but with argument 1 and 3 swapped. + * + * @since Geany 1.29 (API 232) + * @see GeanyPluginConfigureFunc + */ +typedef GtkWidget* (*GeanyPluginSwappedConfigureFunc) (gpointer pdata, GtkDialog *dialog, GeanyPlugin *plugin); + /** * Type of function called when the plugin should show its help documentation. * @@ -348,6 +365,14 @@ typedef GtkWidget* (*GeanyPluginConfigureFunc) (GeanyPlugin *plugin, GtkDialog * */ typedef void (*GeanyPluginHelpFunc) (GeanyPlugin *plugin, gpointer pdata); +/** + * Like @ref GeanyPluginHelpFunc but with argument 1 and 2 swapped. + * + * @since Geany 1.29 (API 232) + * @see GeanyPluginHelpFunc + */ +typedef void (*GeanyPluginSwappedHelpFunc) (gpointer pdata, GeanyPlugin *plugin); + /** * Type of function called when the plugin is de-activated. * @@ -363,6 +388,14 @@ typedef void (*GeanyPluginHelpFunc) (GeanyPlugin *plugin, gpointer pdata); */ typedef void (*GeanyPluginCleanupFunc) (GeanyPlugin *plugin, gpointer pdata); +/** + * Like @ref GeanyPluginCleanupFunc but with argument 1 and 2 swapped. + * + * @since Geany 1.29 (API 232) + * @see GeanyPluginCleanupFunc + */ +typedef void (*GeanyPluginSwappedCleanupFunc) (gpointer pdata, GeanyPlugin *plugin); + /** Callback functions that need to be implemented for every plugin. * * These callbacks should be registered by the plugin within Geany's call to @@ -387,6 +420,34 @@ struct GeanyPluginFuncs GeanyPluginHelpFunc help; /** Called when the plugin is disabled or when Geany exits (must not be @c NULL) */ GeanyPluginCleanupFunc cleanup; + /** + * Like GeanyPluginFuncs::init but with parameter 1 and 2 swapped. If `NULL` + * then GeanyPluginFuncs::init will be tried. + * + * @since Geany 1.29 (API 232) + */ + GeanyPluginSwappedInitFunc init_swapped; + /** + * Like GeanyPluginFuncs::configure but with parameter 1 and 3 swapped. + * If `NULL` then GeanyPluginFuncs::configure will be tried. + * + * @since Geany 1.29 (API 232) + */ + GeanyPluginSwappedConfigureFunc configure_swapped; + /** + * Like GeanyPluginFuncs::help but with parameter 1 and 2 swapped. If `NULL` + * then GeanyPluginFuncs::help will be tried. + * + * @since Geany 1.29 (API 232) + */ + GeanyPluginSwappedHelpFunc help_swapped; + /** + * Like GeanyPluginFuncs::cleanup but with parameter 1 and 2 swapped. If + * `NULL` then GeanyPluginFuncs::cleanup will be tried. + * + * @since Geany 1.29 (API 232) + */ + GeanyPluginSwappedCleanupFunc cleanup_swapped; }; gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, diff --git a/src/plugins.c b/src/plugins.c index e0300a98d1..fa83fba0c0 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -369,20 +369,21 @@ gboolean geany_plugin_register(GeanyPlugin *plugin, gint api_version, gint min_a return FALSE; /* Only init and cleanup callbacks are truly mandatory. */ - if (! cbs->init || ! cbs->cleanup) - { - gchar *name = g_path_get_basename(p->filename); - geany_debug("Plugin '%s' has no %s function - ignoring plugin!", name, - cbs->init ? "cleanup" : "init"); - g_free(name); - } - else + if ((cbs->init && cbs->cleanup) || + (cbs->init_swapped && cbs->cleanup_swapped)) { /* Yes, name is checked again later on, however we want return FALSE here * to signal the error back to the plugin (but we don't print the message twice) */ if (! EMPTY(p->info.name)) p->flags = LOADED_OK; } + else + { + gchar *name = g_path_get_basename(p->filename); + geany_debug("Plugin '%s' has no %s function - ignoring plugin!", name, + (cbs->init || cbs->init_swapped) ? "cleanup" : "init"); + g_free(name); + } /* If it ever becomes necessary we can save the api version in Plugin * and apply compat code on a per-plugin basis, because we learn about @@ -579,7 +580,10 @@ plugin_load(Plugin *plugin) } else { - init_ok = plugin->cbs.init(&plugin->public, plugin->cb_data); + if (plugin->cbs.init_swapped) + init_ok = plugin->cbs.init_swapped(plugin->cb_data, &plugin->public); + else + init_ok = plugin->cbs.init(&plugin->public, plugin->cb_data); } if (! init_ok) @@ -895,7 +899,10 @@ plugin_cleanup(Plugin *plugin) GtkWidget *widget; /* With geany_register_plugin cleanup is mandatory */ - plugin->cbs.cleanup(&plugin->public, plugin->cb_data); + if (plugin->cbs.cleanup_swapped) + plugin->cbs.cleanup_swapped(plugin->cb_data, &plugin->public); + else + plugin->cbs.cleanup(&plugin->public, plugin->cb_data); remove_doc_data(plugin); remove_callbacks(plugin); @@ -1374,6 +1381,14 @@ void plugins_finalize(void) } +static gboolean plugin_has_configure(Plugin *plugin) +{ + return (plugin->configure_single != NULL || + plugin->cbs.configure != NULL || + plugin->cbs.configure_swapped != NULL); +} + + /* Check whether there are any plugins loaded which provide a configure symbol */ gboolean plugins_have_preferences(void) { @@ -1385,7 +1400,7 @@ gboolean plugins_have_preferences(void) foreach_list(item, active_plugin_list) { Plugin *plugin = item->data; - if (plugin->configure_single != NULL || plugin->cbs.configure != NULL) + if (plugin_has_configure(plugin)) return TRUE; } @@ -1433,8 +1448,8 @@ static void pm_update_buttons(Plugin *p) if (p != NULL && is_active_plugin(p)) { - has_configure = p->cbs.configure || p->configure_single; - has_help = p->cbs.help != NULL; + has_configure = plugin_has_configure(p); + has_help = p->cbs.help != NULL || p->cbs.help_swapped != NULL; has_keybindings = p->key_group && p->key_group->plugin_key_count; } @@ -1865,7 +1880,12 @@ static void pm_on_plugin_button_clicked(G_GNUC_UNUSED GtkButton *button, gpointe if (GPOINTER_TO_INT(user_data) == PM_BUTTON_CONFIGURE) plugin_show_configure(&p->public); else if (GPOINTER_TO_INT(user_data) == PM_BUTTON_HELP) - p->cbs.help(&p->public, p->cb_data); + { + if (p->cbs.help_swapped) + p->cbs.help_swapped(p->cb_data, &p->public); + else + p->cbs.help(&p->public, p->cb_data); + } else if (GPOINTER_TO_INT(user_data) == PM_BUTTON_KEYBINDINGS && p->key_group && p->key_group->plugin_key_count > 0) keybindings_dialog_show_prefs_scroll(p->info.name); } diff --git a/src/pluginutils.c b/src/pluginutils.c index 546b1575b8..9f861fc40a 100644 --- a/src/pluginutils.c +++ b/src/pluginutils.c @@ -365,12 +365,15 @@ static GtkWidget *create_pref_page(Plugin *p, GtkWidget *dialog) { GtkWidget *page = NULL; /* some plugins don't have prefs */ - if (p->cbs.configure) + if (p->cbs.configure || p->cbs.configure_swapped) { - page = p->cbs.configure(&p->public, GTK_DIALOG(dialog), p->cb_data); + if (p->cbs.configure_swapped) + page = p->cbs.configure_swapped(p->cb_data, GTK_DIALOG(dialog), &p->public); + else + page = p->cbs.configure(&p->public, GTK_DIALOG(dialog), p->cb_data); if (! GTK_IS_WIDGET(page)) { - geany_debug("Invalid widget returned from plugin_configure() in plugin \"%s\"!", + geany_debug("Invalid widget returned from \"%s\" plugin's configure function!", p->info.name); return NULL; } @@ -467,7 +470,7 @@ void plugin_show_configure(GeanyPlugin *plugin) } p = plugin->priv; - if (p->cbs.configure) + if (p->cbs.configure || p->cbs.configure_swapped) configure_plugins(p); else {