Remove unnecessary case statement braces.
[geany-mirror.git] / src / plugins.c
blob20711a48af74d9f5e67450fcbbada1be18950fef
1 /*
2 * plugins.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2007-2009 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
5 * Copyright 2007-2009 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
22 * $Id$
25 /* Code to manage, load and unload plugins. */
27 #include "geany.h"
29 #ifdef HAVE_PLUGINS
31 #include <string.h>
33 #include "Scintilla.h"
34 #include "ScintillaWidget.h"
36 #include "prefix.h"
37 #include "plugins.h"
38 #include "plugindata.h"
39 #include "support.h"
40 #include "utils.h"
41 #include "document.h"
42 #include "filetypes.h"
43 #include "templates.h"
44 #include "sciwrappers.h"
45 #include "ui_utils.h"
46 #include "editor.h"
47 #include "dialogs.h"
48 #include "msgwindow.h"
49 #include "prefs.h"
50 #include "geanywraplabel.h"
51 #include "build.h"
52 #include "encodings.h"
53 #include "search.h"
54 #include "highlighting.h"
55 #include "keybindings.h"
56 #include "navqueue.h"
57 #include "main.h"
58 #include "toolbar.h"
59 #include "stash.h"
60 #include "keyfile.h"
61 #include "win32.h"
62 #include "pluginutils.h"
63 #include "pluginprivate.h"
66 GList *active_plugin_list = NULL; /* list of only actually loaded plugins, always valid */
69 static gboolean want_plugins = FALSE;
71 /* list of all available, loadable plugins, only valid as long as the plugin manager dialog is
72 * opened, afterwards it will be destroyed */
73 static GList *plugin_list = NULL;
74 static gchar **active_plugins_pref = NULL; /* list of plugin filenames to load at startup */
75 static GList *failed_plugins_list = NULL; /* plugins the user wants active but can't be used */
77 static GtkWidget *menu_separator = NULL;
79 static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data);
82 static PluginFuncs plugin_funcs = {
83 &plugin_add_toolbar_item,
84 &plugin_module_make_resident,
85 &plugin_signal_connect,
86 &plugin_set_key_group,
87 &plugin_show_configure
90 static DocumentFuncs doc_funcs = {
91 &document_new_file,
92 &document_get_current,
93 &document_get_from_page,
94 &document_find_by_filename,
95 &document_find_by_real_path,
96 &document_save_file,
97 &document_open_file,
98 &document_open_files,
99 &document_remove_page,
100 &document_reload_file,
101 &document_set_encoding,
102 &document_set_text_changed,
103 &document_set_filetype,
104 &document_close,
105 &document_index,
106 &document_save_file_as,
107 &document_rename_file,
108 &document_get_status_color,
109 &document_get_basename_for_display,
110 &document_get_notebook_page
113 static EditorFuncs editor_funcs = {
114 &editor_get_indent_prefs,
115 &editor_create_widget,
116 &editor_indicator_set_on_range,
117 &editor_indicator_set_on_line,
118 &editor_indicator_clear,
119 &editor_set_indent_type,
120 &editor_get_word_at_pos
123 static ScintillaFuncs scintilla_funcs = {
124 &scintilla_send_message,
125 &scintilla_new
128 /* Macro to prevent a duplicate macro being generated in geanyfunctions.h */
129 #define _scintilla_send_message_macro scintilla_send_message
131 static SciFuncs sci_funcs = {
132 &_scintilla_send_message_macro,
133 &sci_send_command,
134 &sci_start_undo_action,
135 &sci_end_undo_action,
136 &sci_set_text,
137 &sci_insert_text,
138 &sci_get_text,
139 &sci_get_length,
140 &sci_get_current_position,
141 &sci_set_current_position,
142 &sci_get_col_from_position,
143 &sci_get_line_from_position,
144 &sci_get_position_from_line,
145 &sci_replace_sel,
146 &sci_get_selected_text,
147 &sci_get_selected_text_length,
148 &sci_get_selection_start,
149 &sci_get_selection_end,
150 &sci_get_selection_mode,
151 &sci_set_selection_mode,
152 &sci_set_selection_start,
153 &sci_set_selection_end,
154 &sci_get_text_range,
155 &sci_get_line,
156 &sci_get_line_length,
157 &sci_get_line_count,
158 &sci_get_line_is_visible,
159 &sci_ensure_line_is_visible,
160 &sci_scroll_caret,
161 &sci_find_matching_brace,
162 &sci_get_style_at,
163 &sci_get_char_at,
164 &sci_get_current_line,
165 &sci_has_selection,
166 &sci_get_tab_width,
167 &sci_indicator_clear,
168 &sci_indicator_set,
169 &sci_get_contents,
170 &sci_get_contents_range,
171 &sci_get_selection_contents,
172 &sci_set_font,
173 &sci_get_line_end_position,
174 &sci_set_target_start,
175 &sci_set_target_end,
176 &sci_replace_target,
177 &sci_set_marker_at_line,
178 &sci_delete_marker_at_line,
179 &sci_is_marker_set_at_line
182 static TemplateFuncs template_funcs = {
183 &templates_get_template_fileheader
186 static UtilsFuncs utils_funcs = {
187 &utils_str_equal,
188 &utils_string_replace_all,
189 &utils_get_file_list,
190 &utils_write_file,
191 &utils_get_locale_from_utf8,
192 &utils_get_utf8_from_locale,
193 &utils_remove_ext_from_filename,
194 &utils_mkdir,
195 &utils_get_setting_boolean,
196 &utils_get_setting_integer,
197 &utils_get_setting_string,
198 &utils_spawn_sync,
199 &utils_spawn_async,
200 &utils_str_casecmp,
201 &utils_get_date_time,
202 &utils_open_browser,
203 &utils_string_replace_first,
204 &utils_str_middle_truncate,
205 &utils_str_remove_chars,
206 &utils_get_file_list_full
209 static UIUtilsFuncs uiutils_funcs = {
210 &ui_dialog_vbox_new,
211 &ui_frame_new_with_alignment,
212 &ui_set_statusbar,
213 &ui_table_add_row,
214 &ui_path_box_new,
215 &ui_button_new_with_image,
216 &ui_add_document_sensitive,
217 &ui_widget_set_tooltip_text,
218 &ui_image_menu_item_new,
219 &ui_lookup_widget,
220 &ui_progress_bar_start,
221 &ui_progress_bar_stop,
222 &ui_entry_add_clear_icon,
223 &ui_menu_add_document_items
226 static DialogFuncs dialog_funcs = {
227 &dialogs_show_question,
228 &dialogs_show_msgbox,
229 &dialogs_show_save_as,
230 &dialogs_show_input_numeric
233 /* Macro to prevent confusing macro being generated in geanyfunctions.h */
234 #define _lookup_widget_macro ui_lookup_widget
236 /* deprecated */
237 static SupportFuncs support_funcs = {
238 &_lookup_widget_macro
241 static MsgWinFuncs msgwin_funcs = {
242 &msgwin_status_add,
243 &msgwin_compiler_add,
244 &msgwin_msg_add,
245 &msgwin_clear_tab,
246 &msgwin_switch_tab
249 static EncodingFuncs encoding_funcs = {
250 &encodings_convert_to_utf8,
251 &encodings_convert_to_utf8_from_charset,
252 &encodings_get_charset_from_index
255 static KeybindingFuncs keybindings_funcs = {
256 &keybindings_send_command,
257 &keybindings_set_item,
258 &keybindings_get_item
261 static TagManagerFuncs tagmanager_funcs = {
262 &tm_get_real_path,
263 &tm_source_file_new,
264 &tm_workspace_add_object,
265 &tm_source_file_update,
266 &tm_work_object_free,
267 &tm_workspace_remove_object
270 static SearchFuncs search_funcs = {
271 &search_show_find_in_files_dialog
274 static HighlightingFuncs highlighting_funcs = {
275 &highlighting_get_style
278 static FiletypeFuncs filetype_funcs = {
279 &filetypes_detect_from_file,
280 &filetypes_lookup_by_name,
281 &filetypes_index
284 static NavQueueFuncs navqueue_funcs = {
285 &navqueue_goto_line
288 static MainFuncs main_funcs = {
289 &main_reload_configuration,
290 &main_locale_init
293 static GeanyFunctions geany_functions = {
294 &doc_funcs,
295 &sci_funcs,
296 &template_funcs,
297 &utils_funcs,
298 &uiutils_funcs,
299 &support_funcs,
300 &dialog_funcs,
301 &msgwin_funcs,
302 &encoding_funcs,
303 &keybindings_funcs,
304 &tagmanager_funcs,
305 &search_funcs,
306 &highlighting_funcs,
307 &filetype_funcs,
308 &navqueue_funcs,
309 &editor_funcs,
310 &main_funcs,
311 &plugin_funcs,
312 &scintilla_funcs,
313 &msgwin_funcs
316 static GeanyData geany_data;
319 static void
320 geany_data_init(void)
322 GeanyData gd = {
323 app,
324 &main_widgets,
325 documents_array,
326 filetypes_array,
327 &prefs,
328 &interface_prefs,
329 &toolbar_prefs,
330 &editor_prefs,
331 &file_prefs,
332 &search_prefs,
333 &tool_prefs,
334 &template_prefs,
335 &build_info,
336 filetypes_by_title
338 memcpy(&geany_data, &gd, sizeof(GeanyData));
342 /* Prevent the same plugin filename being loaded more than once.
343 * Note: g_module_name always returns the .so name, even when Plugin::filename is an .la file. */
344 static gboolean
345 plugin_loaded(GModule *module)
347 gchar *basename_module, *basename_loaded;
348 GList *item;
350 basename_module = g_path_get_basename(g_module_name(module));
351 for (item = plugin_list; item != NULL; item = g_list_next(item))
353 basename_loaded = g_path_get_basename(
354 g_module_name(((Plugin*)item->data)->module));
356 if (utils_str_equal(basename_module, basename_loaded))
358 g_free(basename_loaded);
359 g_free(basename_module);
360 return TRUE;
362 g_free(basename_loaded);
364 /* Look also through the list of active plugins. This prevents problems when we have the same
365 * plugin in libdir/geany/ AND in configdir/plugins/ and the one in libdir/geany/ is loaded
366 * as active plugin. The plugin manager list would only take the one in configdir/geany/ and
367 * the plugin manager would list both plugins. Additionally, unloading the active plugin
368 * would cause a crash. */
369 for (item = active_plugin_list; item != NULL; item = g_list_next(item))
371 basename_loaded = g_path_get_basename(g_module_name(((Plugin*)item->data)->module));
373 if (utils_str_equal(basename_module, basename_loaded))
375 g_free(basename_loaded);
376 g_free(basename_module);
377 return TRUE;
379 g_free(basename_loaded);
381 g_free(basename_module);
382 return FALSE;
386 static Plugin *find_active_plugin_by_name(const gchar *filename)
388 GList *item;
390 g_return_val_if_fail(filename, FALSE);
392 for (item = active_plugin_list; item != NULL; item = g_list_next(item))
394 if (utils_str_equal(filename, ((Plugin*)item->data)->filename))
395 return item->data;
398 return NULL;
402 static gboolean
403 plugin_check_version(GModule *module)
405 gint (*version_check)(gint) = NULL;
407 g_module_symbol(module, "plugin_version_check", (void *) &version_check);
409 if (G_UNLIKELY(! version_check))
411 geany_debug("Plugin \"%s\" has no plugin_version_check() function - ignoring plugin!",
412 g_module_name(module));
413 return FALSE;
415 else
417 gint result = version_check(GEANY_ABI_VERSION);
419 if (result < 0)
421 msgwin_status_add(_("The plugin \"%s\" is not binary compatible with this "
422 "release of Geany - please recompile it."), g_module_name(module));
423 geany_debug("Plugin \"%s\" is not binary compatible with this "
424 "release of Geany - recompile it.", g_module_name(module));
425 return FALSE;
427 if (result > 0)
429 geany_debug("Plugin \"%s\" requires a newer version of Geany (API >= v%d).",
430 g_module_name(module), result);
431 return FALSE;
434 return TRUE;
438 static void add_callbacks(Plugin *plugin, PluginCallback *callbacks)
440 PluginCallback *cb;
441 guint i, len = 0;
443 while (TRUE)
445 cb = &callbacks[len];
446 if (!cb->signal_name || !cb->callback)
447 break;
448 len++;
450 if (len == 0)
451 return;
453 for (i = 0; i < len; i++)
455 cb = &callbacks[i];
457 plugin_signal_connect(&plugin->public, NULL, cb->signal_name, cb->after,
458 cb->callback, cb->user_data);
463 static void read_key_group(Plugin *plugin)
465 GeanyKeyGroupInfo *p_key_info;
466 GeanyKeyGroup **p_key_group;
468 g_module_symbol(plugin->module, "plugin_key_group_info", (void *) &p_key_info);
469 g_module_symbol(plugin->module, "plugin_key_group", (void *) &p_key_group);
470 if (p_key_info && p_key_group)
472 GeanyKeyGroupInfo *key_info = p_key_info;
474 if (*p_key_group)
475 geany_debug("Ignoring plugin_key_group symbol for plugin '%s' - "
476 "use plugin_set_key_group() instead to allocate keybindings dynamically.",
477 plugin->info.name);
478 else
480 if (key_info->count)
482 GeanyKeyGroup *key_group =
483 plugin_set_key_group(&plugin->public, key_info->name, key_info->count, NULL);
484 if (key_group)
485 *p_key_group = key_group;
487 else
488 geany_debug("Ignoring plugin_key_group_info symbol for plugin '%s' - "
489 "count field is zero. Maybe use plugin_set_key_group() instead?",
490 plugin->info.name);
493 else if (p_key_info || p_key_group)
494 geany_debug("Ignoring only one of plugin_key_group[_info] symbols defined for plugin '%s'. "
495 "Maybe use plugin_set_key_group() instead?",
496 plugin->info.name);
500 static void
501 plugin_init(Plugin *plugin)
503 GeanyPlugin **p_geany_plugin;
504 PluginCallback *callbacks;
505 PluginInfo **p_info;
506 PluginFields **plugin_fields;
507 GeanyData **p_geany_data;
508 GeanyFunctions **p_geany_functions;
510 /* set these symbols before plugin_init() is called */
511 g_module_symbol(plugin->module, "geany_plugin", (void *) &p_geany_plugin);
512 if (p_geany_plugin)
513 *p_geany_plugin = &plugin->public;
514 g_module_symbol(plugin->module, "plugin_info", (void *) &p_info);
515 if (p_info)
516 *p_info = &plugin->info;
517 g_module_symbol(plugin->module, "geany_data", (void *) &p_geany_data);
518 if (p_geany_data)
519 *p_geany_data = &geany_data;
520 g_module_symbol(plugin->module, "geany_functions", (void *) &p_geany_functions);
521 if (p_geany_functions)
522 *p_geany_functions = &geany_functions;
523 g_module_symbol(plugin->module, "plugin_fields", (void *) &plugin_fields);
524 if (plugin_fields)
525 *plugin_fields = &plugin->fields;
526 read_key_group(plugin);
528 /* start the plugin */
529 g_return_if_fail(plugin->init);
530 plugin->init(&geany_data);
532 /* store some function pointers for later use */
533 g_module_symbol(plugin->module, "plugin_configure", (void *) &plugin->configure);
534 g_module_symbol(plugin->module, "plugin_configure_single", (void *) &plugin->configure_single);
535 if (app->debug_mode && plugin->configure && plugin->configure_single)
536 g_warning("Plugin '%s' implements plugin_configure_single() unnecessarily - "
537 "only plugin_configure() will be used!",
538 plugin->info.name);
540 g_module_symbol(plugin->module, "plugin_help", (void *) &plugin->help);
541 g_module_symbol(plugin->module, "plugin_cleanup", (void *) &plugin->cleanup);
542 if (plugin->cleanup == NULL)
544 if (app->debug_mode)
545 g_warning("Plugin '%s' has no plugin_cleanup() function - there may be memory leaks!",
546 plugin->info.name);
549 /* now read any plugin-owned data that might have been set in plugin_init() */
551 if (plugin->fields.flags & PLUGIN_IS_DOCUMENT_SENSITIVE)
553 ui_add_document_sensitive(plugin->fields.menu_item);
556 g_module_symbol(plugin->module, "plugin_callbacks", (void *) &callbacks);
557 if (callbacks)
558 add_callbacks(plugin, callbacks);
560 /* remember which plugins are active */
561 active_plugin_list = g_list_append(active_plugin_list, plugin);
563 geany_debug("Loaded: %s (%s)", plugin->filename,
564 NVL(plugin->info.name, "<Unknown>"));
568 /* Load and optionally init a plugin.
569 * init_plugin decides whether the plugin's plugin_init() function should be called or not. If it is
570 * called, the plugin will be started, if not the plugin will be read only (for the list of
571 * available plugins in the plugin manager).
572 * When add_to_list is set, the plugin will be added to the plugin manager's plugin_list. */
573 static Plugin*
574 plugin_new(const gchar *fname, gboolean init_plugin, gboolean add_to_list)
576 Plugin *plugin;
577 GModule *module;
578 void (*plugin_set_info)(PluginInfo*);
580 g_return_val_if_fail(fname, NULL);
581 g_return_val_if_fail(g_module_supported(), NULL);
583 /* find the plugin in the list of already loaded, active plugins and use it, otherwise
584 * load the module */
585 plugin = find_active_plugin_by_name(fname);
586 if (plugin != NULL)
588 geany_debug("Plugin \"%s\" already loaded.", fname);
589 if (add_to_list)
590 plugin_list = g_list_append(plugin_list, plugin);
591 return plugin;
594 /* Don't use G_MODULE_BIND_LAZY otherwise we can get unresolved symbols at runtime,
595 * causing a segfault. Without that flag the module will safely fail to load.
596 * G_MODULE_BIND_LOCAL also helps find undefined symbols e.g. app when it would
597 * otherwise not be detected due to the shadowing of Geany's app variable.
598 * Also without G_MODULE_BIND_LOCAL calling public functions e.g. the old info()
599 * function from a plugin will be shadowed. */
600 module = g_module_open(fname, G_MODULE_BIND_LOCAL);
601 if (! module)
603 geany_debug("Can't load plugin: %s", g_module_error());
604 return NULL;
607 if (plugin_loaded(module))
609 geany_debug("Plugin \"%s\" already loaded.", fname);
611 if (! g_module_close(module))
612 g_warning("%s: %s", fname, g_module_error());
613 return NULL;
616 if (! plugin_check_version(module))
618 if (! g_module_close(module))
619 g_warning("%s: %s", fname, g_module_error());
620 return NULL;
623 g_module_symbol(module, "plugin_set_info", (void *) &plugin_set_info);
624 if (plugin_set_info == NULL)
626 geany_debug("No plugin_set_info() defined for \"%s\" - ignoring plugin!", fname);
628 if (! g_module_close(module))
629 g_warning("%s: %s", fname, g_module_error());
630 return NULL;
633 plugin = g_new0(Plugin, 1);
635 /* read plugin name, etc. */
636 plugin_set_info(&plugin->info);
637 if (!NZV(plugin->info.name))
639 geany_debug("No plugin name set in plugin_set_info() for \"%s\" - ignoring plugin!",
640 fname);
642 if (! g_module_close(module))
643 g_warning("%s: %s", fname, g_module_error());
644 g_free(plugin);
645 return NULL;
648 g_module_symbol(module, "plugin_init", (void *) &plugin->init);
649 if (plugin->init == NULL)
651 geany_debug("Plugin '%s' has no plugin_init() function - ignoring plugin!",
652 plugin->info.name);
654 if (! g_module_close(module))
655 g_warning("%s: %s", fname, g_module_error());
656 g_free(plugin);
657 return NULL;
659 /*geany_debug("Initializing plugin '%s'", plugin->info.name);*/
661 plugin->filename = g_strdup(fname);
662 plugin->module = module;
663 plugin->public.info = &plugin->info;
664 plugin->public.priv = plugin;
666 if (init_plugin)
667 plugin_init(plugin);
669 if (add_to_list)
670 plugin_list = g_list_append(plugin_list, plugin);
672 return plugin;
676 static void remove_callbacks(Plugin *plugin)
678 GArray *signal_ids = plugin->signal_ids;
679 SignalConnection *sc;
681 if (signal_ids == NULL)
682 return;
684 foreach_array(SignalConnection, sc, signal_ids)
685 g_signal_handler_disconnect(sc->object, sc->handler_id);
687 g_array_free(signal_ids, TRUE);
691 static gboolean is_active_plugin(Plugin *plugin)
693 return (g_list_find(active_plugin_list, plugin) != NULL);
697 /* Clean up anything used by an active plugin */
698 static void
699 plugin_cleanup(Plugin *plugin)
701 GtkWidget *widget;
703 if (plugin->cleanup)
704 plugin->cleanup();
706 remove_callbacks(plugin);
708 if (plugin->key_group)
709 keybindings_free_group(plugin->key_group);
711 widget = plugin->toolbar_separator.widget;
712 if (widget)
713 gtk_widget_destroy(widget);
715 geany_debug("Unloaded: %s", plugin->filename);
719 static void
720 plugin_free(Plugin *plugin)
722 g_return_if_fail(plugin);
723 g_return_if_fail(plugin->module);
725 if (is_active_plugin(plugin))
726 plugin_cleanup(plugin);
728 active_plugin_list = g_list_remove(active_plugin_list, plugin);
730 if (! g_module_close(plugin->module))
731 g_warning("%s: %s", plugin->filename, g_module_error());
733 plugin_list = g_list_remove(plugin_list, plugin);
735 g_free(plugin->filename);
736 g_free(plugin);
737 plugin = NULL;
741 /* load active plugins at startup */
742 static void
743 load_active_plugins(void)
745 guint i, len;
747 if (active_plugins_pref == NULL || (len = g_strv_length(active_plugins_pref)) == 0)
748 return;
750 for (i = 0; i < len; i++)
752 const gchar *fname = active_plugins_pref[i];
754 if (NZV(fname) && g_file_test(fname, G_FILE_TEST_EXISTS))
756 if (plugin_new(fname, TRUE, FALSE) == NULL)
757 failed_plugins_list = g_list_append(failed_plugins_list, g_strdup(fname));
763 static void
764 load_plugins_from_path(const gchar *path)
766 GSList *list, *item;
767 gchar *fname, *tmp;
769 list = utils_get_file_list(path, NULL, NULL);
771 for (item = list; item != NULL; item = g_slist_next(item))
773 tmp = strrchr(item->data, '.');
774 if (tmp == NULL || utils_str_casecmp(tmp, "." G_MODULE_SUFFIX) != 0)
775 continue;
777 fname = g_strconcat(path, G_DIR_SEPARATOR_S, item->data, NULL);
778 plugin_new(fname, FALSE, TRUE);
779 g_free(fname);
782 g_slist_foreach(list, (GFunc) g_free, NULL);
783 g_slist_free(list);
787 #ifdef G_OS_WIN32
788 static gchar *get_plugin_path()
790 gchar *install_dir = win32_get_installation_dir();
791 gchar *path;
793 path = g_strconcat(install_dir, "\\lib", NULL);
794 g_free(install_dir);
796 return path;
798 #endif
801 /* Load (but don't initialize) all plugins for the Plugin Manager dialog */
802 static void load_all_plugins(void)
804 gchar *path;
806 path = g_strconcat(app->configdir, G_DIR_SEPARATOR_S, "plugins", NULL);
807 /* first load plugins in ~/.config/geany/plugins/ */
808 load_plugins_from_path(path);
809 g_free(path);
811 /* load plugins from a custom path */
812 if (NZV(prefs.custom_plugin_path))
813 load_plugins_from_path(prefs.custom_plugin_path);
815 /* finally load plugins from $prefix/lib/geany */
816 #ifdef G_OS_WIN32
817 path = get_plugin_path();
818 #else
819 path = g_strconcat(GEANY_LIBDIR, G_DIR_SEPARATOR_S "geany", NULL);
820 #endif
821 load_plugins_from_path(path);
822 g_free(path);
826 static void on_tools_menu_show(GtkWidget *menu_item, G_GNUC_UNUSED gpointer user_data)
828 GList *item, *list = gtk_container_get_children(GTK_CONTAINER(menu_item));
829 guint i = 0;
830 gboolean have_plugin_menu_items = FALSE;
832 for (item = list; item != NULL; item = g_list_next(item))
834 if (item->data == menu_separator)
836 if (i < g_list_length(list) - 1)
838 have_plugin_menu_items = TRUE;
839 break;
842 i++;
844 g_list_free(list);
846 ui_widget_show_hide(menu_separator, have_plugin_menu_items);
850 /* Calling this starts up plugin support */
851 void plugins_load_active(void)
853 GtkWidget *widget;
855 want_plugins = TRUE;
857 geany_data_init();
859 widget = gtk_separator_menu_item_new();
860 gtk_widget_show(widget);
861 gtk_container_add(GTK_CONTAINER(main_widgets.tools_menu), widget);
863 widget = gtk_menu_item_new_with_mnemonic(_("_Plugin Manager"));
864 gtk_container_add(GTK_CONTAINER(main_widgets.tools_menu), widget);
865 gtk_widget_show(widget);
866 g_signal_connect(widget, "activate", G_CALLBACK(pm_show_dialog), NULL);
868 menu_separator = gtk_separator_menu_item_new();
869 gtk_container_add(GTK_CONTAINER(main_widgets.tools_menu), menu_separator);
870 g_signal_connect(main_widgets.tools_menu, "show", G_CALLBACK(on_tools_menu_show), NULL);
872 load_active_plugins();
876 static gint cmp_plugin_names(gconstpointer a, gconstpointer b)
878 const Plugin *pa = a;
879 const Plugin *pb = b;
881 return strcmp(pa->info.name, pb->info.name);
885 static void update_active_plugins_pref(void)
887 gint i = 0;
888 GList *list;
889 gsize count = g_list_length(active_plugin_list) + g_list_length(failed_plugins_list);
891 g_strfreev(active_plugins_pref);
893 if (count == 0)
895 active_plugins_pref = NULL;
896 return;
899 /* sort the list so next time tools menu items are sorted by plugin name
900 * (not ideal to do here, but better than nothing) */
901 active_plugin_list = g_list_sort(active_plugin_list, cmp_plugin_names);
903 active_plugins_pref = g_new0(gchar*, count + 1);
905 for (list = g_list_first(active_plugin_list); list != NULL; list = list->next)
907 Plugin *plugin = list->data;
909 active_plugins_pref[i] = g_strdup(plugin->filename);
910 i++;
912 for (list = g_list_first(failed_plugins_list); list != NULL; list = list->next)
914 const gchar *fname = list->data;
916 active_plugins_pref[i] = g_strdup(fname);
917 i++;
919 active_plugins_pref[i] = NULL;
923 static void on_save_settings(GKeyFile *config)
925 /* if plugins are disabled, don't clear list of active plugins */
926 if (want_plugins)
927 update_active_plugins_pref();
931 /* called even if plugin support is disabled */
932 void plugins_init(void)
934 GeanyPrefGroup *group;
936 group = stash_group_new("plugins");
937 configuration_add_pref_group(group, TRUE);
939 stash_group_add_toggle_button(group, &prefs.load_plugins,
940 "load_plugins", TRUE, "check_plugins");
941 stash_group_add_entry(group, &prefs.custom_plugin_path,
942 "custom_plugin_path", "", "extra_plugin_path_entry");
944 g_signal_connect(geany_object, "save-settings", G_CALLBACK(on_save_settings), NULL);
945 stash_group_add_string_vector(group, &active_plugins_pref, "active_plugins", NULL);
949 /* called even if plugin support is disabled */
950 void plugins_finalize(void)
952 if (failed_plugins_list != NULL)
954 g_list_foreach(failed_plugins_list, (GFunc) g_free, NULL);
955 g_list_free(failed_plugins_list);
957 if (active_plugin_list != NULL)
959 g_list_foreach(active_plugin_list, (GFunc) plugin_free, NULL);
960 g_list_free(active_plugin_list);
962 g_strfreev(active_plugins_pref);
966 /* Plugin Manager */
968 enum
970 PLUGIN_COLUMN_CHECK = 0,
971 PLUGIN_COLUMN_NAME,
972 PLUGIN_COLUMN_FILE,
973 PLUGIN_COLUMN_PLUGIN,
974 PLUGIN_N_COLUMNS,
975 PM_BUTTON_CONFIGURE,
976 PM_BUTTON_HELP
979 typedef struct
981 GtkWidget *dialog;
982 GtkWidget *tree;
983 GtkListStore *store;
984 GtkWidget *description_label;
985 GtkWidget *configure_button;
986 GtkWidget *help_button;
988 PluginManagerWidgets;
990 static PluginManagerWidgets pm_widgets;
993 static void pm_update_buttons(Plugin *p)
995 gboolean is_active;
997 is_active = is_active_plugin(p);
998 gtk_widget_set_sensitive(pm_widgets.configure_button,
999 (p->configure || p->configure_single) && is_active);
1000 gtk_widget_set_sensitive(pm_widgets.help_button, p->help != NULL && is_active);
1004 static void pm_selection_changed(GtkTreeSelection *selection, gpointer user_data)
1006 GtkTreeIter iter;
1007 GtkTreeModel *model;
1008 Plugin *p;
1010 if (gtk_tree_selection_get_selected(selection, &model, &iter))
1012 gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, -1);
1014 if (p != NULL)
1016 gchar *text;
1017 PluginInfo *pi;
1019 pi = &p->info;
1020 text = g_strdup_printf(
1021 _("Plugin: %s %s\nDescription: %s\nAuthor(s): %s"),
1022 pi->name, pi->version, pi->description, pi->author);
1024 geany_wrap_label_set_text(GTK_LABEL(pm_widgets.description_label), text);
1025 g_free(text);
1027 pm_update_buttons(p);
1033 static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data)
1035 gboolean old_state, state;
1036 gchar *file_name;
1037 GtkTreeIter iter;
1038 GtkTreePath *path = gtk_tree_path_new_from_string(pth);
1039 Plugin *p;
1041 gtk_tree_model_get_iter(GTK_TREE_MODEL(pm_widgets.store), &iter, path);
1042 gtk_tree_path_free(path);
1044 gtk_tree_model_get(GTK_TREE_MODEL(pm_widgets.store), &iter,
1045 PLUGIN_COLUMN_CHECK, &old_state, PLUGIN_COLUMN_PLUGIN, &p, -1);
1046 g_return_if_fail(p != NULL);
1047 state = ! old_state; /* toggle the state */
1049 /* save the filename of the plugin */
1050 file_name = g_strdup(p->filename);
1052 /* unload plugin module */
1053 if (!state)
1054 /* save shortcuts (only need this group, but it doesn't take long) */
1055 keybindings_write_to_file();
1057 plugin_free(p);
1059 /* reload plugin module and initialize it if item is checked */
1060 p = plugin_new(file_name, state, TRUE);
1061 if (!p)
1063 /* plugin file may no longer be on disk, or is now incompatible */
1064 gtk_list_store_remove(pm_widgets.store, &iter);
1066 else
1068 if (state)
1069 keybindings_load_keyfile(); /* load shortcuts */
1071 /* update model */
1072 gtk_list_store_set(pm_widgets.store, &iter,
1073 PLUGIN_COLUMN_CHECK, state,
1074 PLUGIN_COLUMN_PLUGIN, p, -1);
1076 /* set again the sensitiveness of the configure and help buttons */
1077 pm_update_buttons(p);
1079 g_free(file_name);
1083 static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store)
1085 GtkCellRenderer *text_renderer, *checkbox_renderer;
1086 GtkTreeViewColumn *column;
1087 GtkTreeIter iter;
1088 GList *list;
1089 GtkTreeSelection *sel;
1091 checkbox_renderer = gtk_cell_renderer_toggle_new();
1092 column = gtk_tree_view_column_new_with_attributes(
1093 _("Active"), checkbox_renderer, "active", PLUGIN_COLUMN_CHECK, NULL);
1094 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
1095 g_signal_connect(checkbox_renderer, "toggled", G_CALLBACK(pm_plugin_toggled), NULL);
1097 text_renderer = gtk_cell_renderer_text_new();
1098 column = gtk_tree_view_column_new_with_attributes(
1099 _("Plugin"), text_renderer, "text", PLUGIN_COLUMN_NAME, NULL);
1100 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
1102 text_renderer = gtk_cell_renderer_text_new();
1103 g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1104 column = gtk_tree_view_column_new_with_attributes(
1105 _("File"), text_renderer, "text", PLUGIN_COLUMN_FILE, NULL);
1106 gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
1108 gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
1109 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(tree), FALSE);
1110 gtk_tree_sortable_set_sort_column_id(
1111 GTK_TREE_SORTABLE(store), PLUGIN_COLUMN_NAME, GTK_SORT_ASCENDING);
1113 /* selection handling */
1114 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
1115 gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
1116 g_signal_connect(sel, "changed", G_CALLBACK(pm_selection_changed), NULL);
1118 list = g_list_first(plugin_list);
1119 if (list == NULL)
1121 gtk_list_store_append(store, &iter);
1122 gtk_list_store_set(store, &iter, PLUGIN_COLUMN_CHECK, FALSE,
1123 PLUGIN_COLUMN_NAME, _("No plugins available."),
1124 PLUGIN_COLUMN_FILE, "", PLUGIN_COLUMN_PLUGIN, NULL, -1);
1126 else
1128 Plugin *p;
1129 for (; list != NULL; list = list->next)
1131 p = list->data;
1133 gtk_list_store_append(store, &iter);
1134 gtk_list_store_set(store, &iter,
1135 PLUGIN_COLUMN_CHECK, is_active_plugin(p),
1136 PLUGIN_COLUMN_NAME, p->info.name,
1137 PLUGIN_COLUMN_FILE, p->filename,
1138 PLUGIN_COLUMN_PLUGIN, p,
1139 -1);
1142 gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
1143 g_object_unref(store);
1147 static void pm_on_plugin_button_clicked(GtkButton *button, gpointer user_data)
1149 GtkTreeModel *model;
1150 GtkTreeSelection *selection;
1151 GtkTreeIter iter;
1152 Plugin *p;
1154 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pm_widgets.tree));
1155 if (gtk_tree_selection_get_selected(selection, &model, &iter))
1157 gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, -1);
1159 if (p != NULL)
1161 if (GPOINTER_TO_INT(user_data) == PM_BUTTON_CONFIGURE)
1162 plugin_show_configure(&p->public);
1163 else if (GPOINTER_TO_INT(user_data) == PM_BUTTON_HELP && p->help != NULL)
1164 p->help();
1170 static void
1171 free_non_active_plugin(gpointer data, gpointer user_data)
1173 Plugin *plugin = data;
1175 /* don't do anything when closing the plugin manager and it is an active plugin */
1176 if (is_active_plugin(plugin))
1177 return;
1179 plugin_free(plugin);
1183 static void pm_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
1185 if (plugin_list != NULL)
1187 /* remove all non-active plugins from the list */
1188 g_list_foreach(plugin_list, free_non_active_plugin, NULL);
1189 g_list_free(plugin_list);
1190 plugin_list = NULL;
1192 gtk_widget_destroy(GTK_WIDGET(dialog));
1196 static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data)
1198 GtkWidget *vbox, *vbox2, *label_vbox, *hbox, *swin, *label, *label2, *desc_win;
1200 /* before showing the dialog, we need to create the list of available plugins */
1201 load_all_plugins();
1203 pm_widgets.dialog = gtk_dialog_new_with_buttons(_("Plugins"), GTK_WINDOW(main_widgets.window),
1204 GTK_DIALOG_DESTROY_WITH_PARENT,
1205 GTK_STOCK_OK, GTK_RESPONSE_CANCEL, NULL);
1206 vbox = ui_dialog_vbox_new(GTK_DIALOG(pm_widgets.dialog));
1207 gtk_widget_set_name(pm_widgets.dialog, "GeanyDialog");
1208 gtk_box_set_spacing(GTK_BOX(vbox), 6);
1210 gtk_window_set_default_size(GTK_WINDOW(pm_widgets.dialog), 500, 450);
1212 pm_widgets.tree = gtk_tree_view_new();
1213 pm_widgets.store = gtk_list_store_new(
1214 PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
1215 pm_prepare_treeview(pm_widgets.tree, pm_widgets.store);
1217 swin = gtk_scrolled_window_new(NULL, NULL);
1218 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
1219 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1220 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin), GTK_SHADOW_IN);
1221 gtk_container_add(GTK_CONTAINER(swin), pm_widgets.tree);
1223 label = geany_wrap_label_new(_("Choose which plugins should be loaded at startup:"));
1225 pm_widgets.configure_button = gtk_button_new_from_stock(GTK_STOCK_PREFERENCES);
1226 gtk_widget_set_sensitive(pm_widgets.configure_button, FALSE);
1227 g_signal_connect(pm_widgets.configure_button, "clicked",
1228 G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_CONFIGURE));
1230 pm_widgets.help_button = gtk_button_new_from_stock(GTK_STOCK_HELP);
1231 gtk_widget_set_sensitive(pm_widgets.help_button, FALSE);
1232 g_signal_connect(pm_widgets.help_button, "clicked",
1233 G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_HELP));
1235 label2 = gtk_label_new(_("<b>Plugin details:</b>"));
1236 gtk_label_set_use_markup(GTK_LABEL(label2), TRUE);
1237 gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5);
1239 pm_widgets.description_label = geany_wrap_label_new("");
1240 desc_win = gtk_scrolled_window_new(NULL, NULL);
1241 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(desc_win),
1242 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1243 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(desc_win),
1244 pm_widgets.description_label);
1246 hbox = gtk_hbox_new(FALSE, 0);
1247 gtk_box_pack_start(GTK_BOX(hbox), label2, TRUE, TRUE, 0);
1248 gtk_box_pack_start(GTK_BOX(hbox), pm_widgets.help_button, FALSE, FALSE, 4);
1249 gtk_box_pack_start(GTK_BOX(hbox), pm_widgets.configure_button, FALSE, FALSE, 0);
1251 label_vbox = gtk_vbox_new(FALSE, 3);
1252 gtk_box_pack_start(GTK_BOX(label_vbox), hbox, FALSE, FALSE, 0);
1253 gtk_box_pack_start(GTK_BOX(label_vbox), desc_win, FALSE, FALSE, 0);
1255 vbox2 = gtk_vbox_new(FALSE, 3);
1256 gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 5);
1257 gtk_box_pack_start(GTK_BOX(vbox2), swin, TRUE, TRUE, 0);
1258 gtk_box_pack_start(GTK_BOX(vbox2), label_vbox, FALSE, FALSE, 0);
1260 g_signal_connect(pm_widgets.dialog, "response", G_CALLBACK(pm_dialog_response), NULL);
1262 gtk_container_add(GTK_CONTAINER(vbox), vbox2);
1263 gtk_widget_show_all(pm_widgets.dialog);
1267 #endif