SF.net SVN: geany: [2060] trunk

eht16 at users.sourceforge.net eht16 at xxxxx
Tue Nov 20 18:15:46 UTC 2007


Revision: 2060
          http://geany.svn.sourceforge.net/geany/?rev=2060&view=rev
Author:   eht16
Date:     2007-11-20 10:15:46 -0800 (Tue, 20 Nov 2007)

Log Message:
-----------
Add plugin manager dialog to select plugins to load at startup and to call a plugin configure dialog.
Add configure symbol for plugins which is called by Geany when a configure dialog for the plugin is requested, optionally.
Add author field to plugin info struct.
Add sample configure dialog to the demo plugin.
Fix cleanup code in filebrowser plugin to remove it completely when unloaded.			

Modified Paths:
--------------
    trunk/ChangeLog
    trunk/TODO
    trunk/doc/geany.html
    trunk/doc/geany.txt
    trunk/geany.glade
    trunk/plugins/classbuilder.c
    trunk/plugins/demoplugin.c
    trunk/plugins/export.c
    trunk/plugins/filebrowser.c
    trunk/plugins/htmlchars.c
    trunk/plugins/svndiff.c
    trunk/plugins/vcdiff.c
    trunk/src/callbacks.c
    trunk/src/callbacks.h
    trunk/src/geany.h
    trunk/src/interface.c
    trunk/src/keyfile.c
    trunk/src/main.c
    trunk/src/plugindata.h
    trunk/src/plugins.c
    trunk/src/plugins.h

Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/ChangeLog	2007-11-20 18:15:46 UTC (rev 2060)
@@ -1,3 +1,18 @@
+2007-11-20  Enrico Tröger  <enrico(dot)troeger(at)uvena(dot)de>
+
+ * geany.glade, plugins/*.c src/callbacks.c, src/callbacks.h,
+   src/geany.h, src/interface.c, src/keyfile.c, src/main.c,
+   src/plugindata.h, src/plugins.c, src/plugins.h:
+   Add plugin manager dialog to select plugins to load at startup and
+   to call a plugin configure dialog.
+   Add configure symbol for plugins which is called by Geany when a
+   configure dialog for the plugin is requested, optionally.
+   Add author field to plugin info struct.
+   Add sample configure dialog to the demo plugin.
+   Fix cleanup code in filebrowser plugin to remove it completely when
+   unloaded.
+
+
 2007-11-19  Enrico Tröger  <enrico(dot)troeger(at)uvena(dot)de>
 
  * tagmanager/read.c: Fix file type detection for parsers using regexp

Modified: trunk/TODO
===================================================================
--- trunk/TODO	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/TODO	2007-11-20 18:15:46 UTC (rev 2060)
@@ -14,9 +14,7 @@
 	o line breaking mode to limit words on a line for e.g. XML content.
 	o common default highlighting for all programming languages
 	o configurable default and project make commands
-	o project session support
 	o project indentation settings support
-	o plugin management
 	o plugin keybindings
 	o (DBUS)
 	o (startup notification)

Modified: trunk/doc/geany.html
===================================================================
--- trunk/doc/geany.html	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/doc/geany.html	2007-11-20 18:15:46 UTC (rev 2060)
@@ -6,7 +6,7 @@
 <meta name="generator" content="Docutils 0.4.1: http://docutils.sourceforge.net/" />
 <title>Geany</title>
 <meta name="authors" content="Enrico Tröger  Nick Treleaven  Frank Lanitz" />
-<meta name="date" content="2007-11-17" />
+<meta name="date" content="2007-11-18" />
 <style type="text/css">
 
 /*
@@ -133,7 +133,7 @@
 <br />Nick Treleaven
 <br />Frank Lanitz</td></tr>
 <tr><th class="docinfo-name">Date:</th>
-<td>2007-11-17</td></tr>
+<td>2007-11-18</td></tr>
 <tr><th class="docinfo-name">Version:</th>
 <td>0.13</td></tr>
 </tbody>
@@ -1534,9 +1534,13 @@
 <li><tt class="docutils literal"><span class="pre">~/.geany/plugins</span></tt></li>
 </ul>
 <p>Most plugins add menu items to the <em>Tools</em> menu when they are loaded.</p>
-<p>In future, there will be a plugin management dialog to load and unload
-plugins, but for now any unwanted plugin library files can be deleted
-after installation of Geany.</p>
+<p>Since Geany 0.13, there is a Plugin Manager to let you choose which plugins
+should be loaded at startup. You can also load and unload plugins on the
+fly using this dialog. Once you click the checkbox for a specific plugin
+in the dialog, it is loaded or unloaded according to its previous state.
+By default, no plugins are loaded at startup until you select some.
+You can also configure some plugin specific options when the plugin
+provides some.</p>
 </div>
 <div class="section">
 <h2><a class="toc-backref" href="#id82" id="keybindings" name="keybindings">Keybindings</a></h2>
@@ -3134,7 +3138,7 @@
 <div class="footer">
 <hr class="footer" />
 <a class="reference" href="geany.txt">View document source</a>.
-Generated on: 2007-11-18 15:02 UTC.
+Generated on: 2007-11-20 18:13 UTC.
 Generated by <a class="reference" href="http://docutils.sourceforge.net/">Docutils</a> from <a class="reference" href="http://docutils.sourceforge.net/rst.html">reStructuredText</a> source.
 
 </div>

Modified: trunk/doc/geany.txt
===================================================================
--- trunk/doc/geany.txt	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/doc/geany.txt	2007-11-20 18:15:46 UTC (rev 2060)
@@ -1432,9 +1432,13 @@
 
 Most plugins add menu items to the *Tools* menu when they are loaded.
 
-In future, there will be a plugin management dialog to load and unload
-plugins, but for now any unwanted plugin library files can be deleted
-after installation of Geany.
+Since Geany 0.13, there is a Plugin Manager to let you choose which plugins
+should be loaded at startup. You can also load and unload plugins on the
+fly using this dialog. Once you click the checkbox for a specific plugin
+in the dialog, it is loaded or unloaded according to its previous state.
+By default, no plugins are loaded at startup until you select some.
+You can also configure some plugin specific options when the plugin
+provides some.
 
 
 

Modified: trunk/geany.glade
===================================================================
--- trunk/geany.glade	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/geany.glade	2007-11-20 18:15:46 UTC (rev 2060)
@@ -1381,6 +1381,7 @@
 	      <property name="visible">True</property>
 	      <property name="label" translatable="yes">_Tools</property>
 	      <property name="use_underline">True</property>
+	      <signal name="activate" handler="on_tools1_activate" last_modification_time="Tue, 20 Nov 2007 16:31:10 GMT"/>
 
 	      <child>
 		<widget class="GtkMenu" id="tools1_menu">

Modified: trunk/plugins/classbuilder.c
===================================================================
--- trunk/plugins/classbuilder.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/plugins/classbuilder.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -46,7 +46,8 @@
 
 VERSION_CHECK(7)
 
-PLUGIN_INFO(_("Class Builder"), _("Creates source files for new class types."), VERSION)
+PLUGIN_INFO(_("Class Builder"), _("Creates source files for new class types."), VERSION,
+	_("The Geany developer team"))
 
 
 enum

Modified: trunk/plugins/demoplugin.c
===================================================================
--- trunk/plugins/demoplugin.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/plugins/demoplugin.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -51,10 +51,14 @@
  * for binary compatibility. */
 VERSION_CHECK(7)
 
-/* All plugins must set name, description and version. */
-PLUGIN_INFO(_("Demo"), _("Example plugin."), VERSION)
+/* All plugins must set name, description, version and author. */
+PLUGIN_INFO(_("Demo"), _("Example plugin."), VERSION, _("The Geany developer team"))
 
 
+// text to be shown in the plugin dialog
+static gchar *welcome_text = NULL;
+
+
 /* Callback when the menu item is clicked. */
 static void
 item_activate(GtkMenuItem *menuitem, gpointer gdata)
@@ -66,7 +70,7 @@
 		GTK_DIALOG_DESTROY_WITH_PARENT,
 		GTK_MESSAGE_INFO,
 		GTK_BUTTONS_OK,
-		_("Hello World!"));
+		welcome_text);
 	gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
 		_("(From the %s plugin)"), info()->name);
 
@@ -87,15 +91,65 @@
 	gtk_container_add(GTK_CONTAINER(geany_data->tools_menu), demo_item);
 	g_signal_connect(G_OBJECT(demo_item), "activate", G_CALLBACK(item_activate), NULL);
 
+	welcome_text = g_strdup(_("Hello World!"));
+
 	// keep a pointer to the menu item, so we can remove it when the plugin is unloaded
 	plugin_fields->menu_item = demo_item;
 }
 
 
+/* Called by Geany to show the plugin's configure dialog. This function is always called after
+ * init() was called.
+ * You can omit this function if the plugin doesn't need to be configured.
+ * Note: parent is the parent window which can be used as the transient window for the created
+ *       dialog. */
+void configure(GtkWidget *parent)
+{
+	GtkWidget *dialog, *label, *entry, *vbox;
+
+	// example configuration dialog
+	dialog = gtk_dialog_new_with_buttons(_("Demo"),
+		GTK_WINDOW(parent), GTK_DIALOG_DESTROY_WITH_PARENT,
+		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
+	vbox = ui->dialog_vbox_new(GTK_DIALOG(dialog));
+	gtk_widget_set_name(dialog, "GeanyDialog");
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+
+	// add a label and a text entry t the dialog
+	label = gtk_label_new("Welcome text to show:");
+	gtk_widget_show(label);
+	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+	entry = gtk_entry_new();
+	gtk_widget_show(entry);
+	if (welcome_text != NULL)
+		gtk_entry_set_text(GTK_ENTRY(entry), welcome_text);
+
+	gtk_container_add(GTK_CONTAINER(vbox), label);
+	gtk_container_add(GTK_CONTAINER(vbox), entry);
+	gtk_widget_show(vbox);
+
+	// run the dialog and check for the response code
+	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT)
+	{
+		g_free(welcome_text);
+		welcome_text = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry)));
+		// maybe the plugin should write here the settings into a file
+		// (e.g. using GLib's GKeyFile API)
+		// all plugin specific files should be created in:
+		// app->configdir G_DIR_SEPARATOR_S plugins G_DIR_SEPARATOR_S pluginname G_DIR_SEPARATOR_S
+		// e.g. this could be: ~/.geany/plugins/Demo/, please use app->configdir
+	}
+	gtk_widget_destroy(dialog);
+}
+
+
 /* Called by Geany before unloading the plugin.
- * Here any UI changes should be removed, memory freed and any other finalization done. */
+ * Here any UI changes should be removed, memory freed and any other finalization done.
+ * Be sure to leave Geany as it was before init(). */
 void cleanup()
 {
 	// remove the menu item added in init()
 	gtk_widget_destroy(plugin_fields->menu_item);
+	// release other allocated strings and objects
+	g_free(welcome_text);
 }

Modified: trunk/plugins/export.c
===================================================================
--- trunk/plugins/export.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/plugins/export.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -40,7 +40,8 @@
 GeanyData		*geany_data;
 
 VERSION_CHECK(20)
-PLUGIN_INFO(_("Export"), _("Exports the current file into different formats."), VERSION)
+PLUGIN_INFO(_("Export"), _("Exports the current file into different formats."), VERSION,
+	_("The Geany developer team"))
 
 #define ROTATE_RGB(color) \
 	(((color) & 0xFF0000) >> 16) + ((color) & 0x00FF00) + (((color) & 0x0000FF) << 16)

Modified: trunk/plugins/filebrowser.c
===================================================================
--- trunk/plugins/filebrowser.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/plugins/filebrowser.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -45,7 +45,8 @@
 
 VERSION_CHECK(26)
 
-PLUGIN_INFO(_("File Browser"), _("Adds a file browser tab to the sidebar."), VERSION)
+PLUGIN_INFO(_("File Browser"), _("Adds a file browser tab to the sidebar."), VERSION,
+	_("The Geany developer team"))
 
 
 enum
@@ -58,6 +59,7 @@
 static gboolean show_hidden_files = FALSE;
 static gboolean hide_object_files = TRUE;
 
+static GtkWidget	*file_view_vbox;
 static GtkWidget	*file_view;
 static GtkListStore	*file_store;
 static GtkTreeIter	*last_dir_iter = NULL;
@@ -455,15 +457,15 @@
 
 void init(GeanyData *data)
 {
-	GtkWidget *scrollwin, *toolbar, *vbox;
+	GtkWidget *scrollwin, *toolbar;
 
-	vbox = gtk_vbox_new(FALSE, 0);
+	file_view_vbox = gtk_vbox_new(FALSE, 0);
 	toolbar = make_toolbar();
-	gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(file_view_vbox), toolbar, FALSE, FALSE, 0);
 
 	path_entry = gtk_entry_new();
 	gtk_editable_set_editable(GTK_EDITABLE(path_entry), FALSE);
-	gtk_box_pack_start(GTK_BOX(vbox), path_entry, FALSE, FALSE, 2);
+	gtk_box_pack_start(GTK_BOX(file_view_vbox), path_entry, FALSE, FALSE, 2);
 
 	file_view = gtk_tree_view_new();
 	prepare_file_view();
@@ -473,15 +475,15 @@
 		GTK_SCROLLED_WINDOW(scrollwin),
 		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 	gtk_container_add(GTK_CONTAINER(scrollwin), file_view);
-	gtk_container_add(GTK_CONTAINER(vbox), scrollwin);
+	gtk_container_add(GTK_CONTAINER(file_view_vbox), scrollwin);
 
-	gtk_widget_show_all(vbox);
-	gtk_notebook_append_page(GTK_NOTEBOOK(app->treeview_notebook), vbox,
+	gtk_widget_show_all(file_view_vbox);
+	gtk_notebook_append_page(GTK_NOTEBOOK(app->treeview_notebook), file_view_vbox,
 		gtk_label_new(_("Files")));
 }
 
 
 void cleanup()
 {
-	gtk_widget_destroy(file_view);
+	gtk_widget_destroy(file_view_vbox);
 }

Modified: trunk/plugins/htmlchars.c
===================================================================
--- trunk/plugins/htmlchars.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/plugins/htmlchars.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -42,7 +42,8 @@
 
 VERSION_CHECK(7)
 
-PLUGIN_INFO(_("HTML Characters"), _("Inserts HTML character entities like '&'."), VERSION)
+PLUGIN_INFO(_("HTML Characters"), _("Inserts HTML character entities like '&'."), VERSION,
+	_("The Geany developer team"))
 
 
 enum

Modified: trunk/plugins/svndiff.c
===================================================================
--- trunk/plugins/svndiff.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/plugins/svndiff.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -39,7 +39,8 @@
 
 VERSION_CHECK(27)
 
-PLUGIN_INFO(_("SVNdiff"), _("Plugin to create a patch of a file against svn"), VERSION)
+PLUGIN_INFO(_("SVNdiff"), _("Plugin to create a patch of a file against svn"), VERSION,
+	_("The Geany developer team"))
 
 static int find_by_filename(const gchar* filename)
 {

Modified: trunk/plugins/vcdiff.c
===================================================================
--- trunk/plugins/vcdiff.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/plugins/vcdiff.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -42,7 +42,8 @@
 
 VERSION_CHECK(27)
 
-PLUGIN_INFO(_("VC Diff"), _("Creates a patch of a file against version control."), VERSION)
+PLUGIN_INFO(_("VC Diff"), _("Creates a patch of a file against version control."), VERSION,
+	_("The Geany developer team"))
 
 
 enum

Modified: trunk/src/callbacks.c
===================================================================
--- trunk/src/callbacks.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/src/callbacks.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -62,6 +62,7 @@
 #include "project.h"
 #include "navqueue.h"
 #include "printing.h"
+#include "plugins.h"
 
 #include "geanyobject.h"
 
@@ -2146,3 +2147,13 @@
 #endif
 }
 
+
+void
+on_tools1_activate                     (GtkMenuItem     *menuitem,
+                                        gpointer         user_data)
+{
+#ifdef HAVE_PLUGINS
+	plugins_update_tools_menu();
+#endif
+}
+

Modified: trunk/src/callbacks.h
===================================================================
--- trunk/src/callbacks.h	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/src/callbacks.h	2007-11-20 18:15:46 UTC (rev 2060)
@@ -595,3 +595,7 @@
 void
 on_page_setup1_activate                (GtkMenuItem     *menuitem,
                                         gpointer         user_data);
+
+void
+on_tools1_activate                     (GtkMenuItem     *menuitem,
+                                        gpointer         user_data);

Modified: trunk/src/geany.h
===================================================================
--- trunk/src/geany.h	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/src/geany.h	2007-11-20 18:15:46 UTC (rev 2060)
@@ -91,6 +91,7 @@
 	gchar				*configdir;
 	gchar				*datadir;
 	gchar				*docdir;
+	gchar			   **active_plugins; // list of plugin filenames to load at startup
 	const TMWorkspace	*tm_workspace;
 	GeanyProject		*project;	// currently active project or NULL if none is open
 	gboolean			ignore_callback;	// should not be used in new code (use clicked instead of toggled signal)

Modified: trunk/src/interface.c
===================================================================
--- trunk/src/interface.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/src/interface.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -1543,6 +1543,9 @@
   g_signal_connect ((gpointer) project_properties1, "activate",
                     G_CALLBACK (on_project_properties1_activate),
                     NULL);
+  g_signal_connect ((gpointer) tools1, "activate",
+                    G_CALLBACK (on_tools1_activate),
+                    NULL);
   g_signal_connect ((gpointer) menu_choose_color1, "activate",
                     G_CALLBACK (on_show_color_chooser1_activate),
                     NULL);

Modified: trunk/src/keyfile.c
===================================================================
--- trunk/src/keyfile.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/src/keyfile.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -52,6 +52,7 @@
 #include "project.h"
 #include "editor.h"
 #include "printing.h"
+#include "plugins.h"
 
 
 static gchar *scribble_text = NULL;
@@ -141,13 +142,27 @@
 }
 
 
+#ifdef HAVE_PLUGINS
+static void save_plugin_prefs(GKeyFile *config)
+{
+	g_key_file_set_boolean(config, "plugins", "load_plugins", prefs.load_plugins);
+	plugins_create_active_list();
+	if (app->active_plugins != NULL)
+		g_key_file_set_string_list(config, "plugins", "active_plugins",
+			(const gchar**)app->active_plugins, g_strv_length(app->active_plugins));
+	else
+		// use an empty dummy array to override maybe exisiting value
+		g_key_file_set_string_list(config, "plugins", "active_plugins", (const gchar*[1]){ "" }, 1);
+}
+#endif
+
+
 static void save_dialog_prefs(GKeyFile *config)
 {
 	/* Some of the key names are not consistent, but this is for backwards compatibility */
 
 	// general
 	g_key_file_set_boolean(config, PACKAGE, "pref_main_load_session", prefs.load_session);
-	g_key_file_set_boolean(config, PACKAGE, "load_plugins", prefs.load_plugins);
 	g_key_file_set_boolean(config, PACKAGE, "pref_main_save_winpos", prefs.save_winpos);
 	g_key_file_set_boolean(config, PACKAGE, "pref_main_confirm_exit", prefs.confirm_exit);
 	g_key_file_set_boolean(config, PACKAGE, "pref_main_suppress_search_dialogs", prefs.suppress_search_dialogs);
@@ -358,6 +373,9 @@
 	g_key_file_load_from_file(config, configfile, G_KEY_FILE_NONE, NULL);
 
 	save_dialog_prefs(config);
+#ifdef HAVE_PLUGINS
+	save_plugin_prefs(config);
+#endif
 	save_hidden_prefs(config);
 	save_ui_prefs(config);
 	project_save_prefs(config);	// save project filename, etc.
@@ -441,7 +459,6 @@
 	prefs.suppress_search_dialogs = utils_get_setting_boolean(config, PACKAGE, "pref_main_suppress_search_dialogs", FALSE);
 	prefs.suppress_status_messages = utils_get_setting_boolean(config, PACKAGE, "pref_main_suppress_status_messages", FALSE);
 	prefs.load_session = utils_get_setting_boolean(config, PACKAGE, "pref_main_load_session", TRUE);
-	prefs.load_plugins = utils_get_setting_boolean(config, PACKAGE, "load_plugins", TRUE);
 	prefs.save_winpos = utils_get_setting_boolean(config, PACKAGE, "pref_main_save_winpos", TRUE);
 	prefs.beep_on_errors = utils_get_setting_boolean(config, PACKAGE, "beep_on_errors", TRUE);
 	prefs.switch_msgwin_pages = utils_get_setting_boolean(config, PACKAGE, "switch_msgwin_pages", FALSE);
@@ -644,6 +661,15 @@
 }
 
 
+#ifdef HAVE_PLUGINS
+static void load_plugin_prefs(GKeyFile *config)
+{
+	prefs.load_plugins = utils_get_setting_boolean(config, "plugins", "load_plugins", TRUE);
+	app->active_plugins = g_key_file_get_string_list(config, "plugins", "active_plugins", NULL, NULL);
+}
+#endif
+
+
 static void load_ui_prefs(GKeyFile *config)
 {
 	gint *geo;
@@ -753,6 +779,9 @@
 	g_key_file_load_from_file(config, configfile, G_KEY_FILE_NONE, NULL);
 
 	load_dialog_prefs(config);
+#ifdef HAVE_PLUGINS
+	load_plugin_prefs(config);
+#endif
 	load_ui_prefs(config);
 	project_load_prefs(config);
 	configuration_load_session_files(config);

Modified: trunk/src/main.c
===================================================================
--- trunk/src/main.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/src/main.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -272,8 +272,9 @@
 	// inits
 	app->window				= NULL;
 	app->project			= NULL;
+	app->active_plugins		= NULL;
 	ui_widgets.open_fontsel		= NULL;
-	ui_widgets.open_colorsel		= NULL;
+	ui_widgets.open_colorsel	= NULL;
 	ui_widgets.open_filesel		= NULL;
 	ui_widgets.save_filesel		= NULL;
 	ui_widgets.prefs_dialog		= NULL;
@@ -837,6 +838,7 @@
 	g_free(printing_prefs.external_print_cmd);
 	g_free(printing_prefs.page_header_datefmt);
 	g_strfreev(ui_prefs.custom_commands);
+	g_strfreev(app->active_plugins);
 	while (! g_queue_is_empty(ui_prefs.recent_queue))
 	{
 		g_free(g_queue_pop_tail(ui_prefs.recent_queue));

Modified: trunk/src/plugindata.h
===================================================================
--- trunk/src/plugindata.h	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/src/plugindata.h	2007-11-20 18:15:46 UTC (rev 2060)
@@ -44,6 +44,10 @@
  * 	An array for connecting GeanyObject events, which should be terminated with
  * 	{NULL, NULL, FALSE, NULL}. See signals below.
  *
+ * void configure(GtkWidget *parent)
+ * 	Called when the plugin should show a configure dialog to let the user set some basic
+ *  plugin configuration. Optionally, can be omitted when not needed.
+ *
  * void init(GeanyData *data)
  * 	Called after loading the plugin. data is the same as geany_data.
  *
@@ -71,12 +75,12 @@
 
 /* The API version should be incremented whenever any plugin data types below are
  * modified or appended to. */
-static const gint api_version = 30;
+static const gint api_version = 31;
 
 /* The ABI version should be incremented whenever existing fields in the plugin
  * data types below have to be changed or reordered. It should stay the same if fields
  * are only appended, as this doesn't affect existing fields. */
-static const gint abi_version = 15;
+static const gint abi_version = 16;
 
 /* This performs runtime checks that try to ensure:
  * 1. Geany ABI data types are compatible with this plugin.
@@ -98,7 +102,7 @@
 	gchar	*name;			// name of plugin
 	gchar	*description;	// description of plugin
 	gchar	*version;		// version of plugin
-	gpointer reserved1;		// reserved for later use
+	gchar	*author;		// author of plugin
 	gpointer reserved2;		// reserved for later use
 }
 PluginInfo;
@@ -106,7 +110,7 @@
 #include <string.h>
 
 /* Sets the plugin name and a brief description of what it is. */
-#define PLUGIN_INFO(p_name, p_description, p_version) \
+#define PLUGIN_INFO(p_name, p_description, p_version, p_author) \
 	PluginInfo *info() \
 	{ \
 		static PluginInfo p_info; \
@@ -115,6 +119,7 @@
 		p_info.name = (p_name); \
 		p_info.description = (p_description); \
 		p_info.version = (p_version); \
+		p_info.author = (p_author); \
 		return &p_info; \
 	}
 

Modified: trunk/src/plugins.c
===================================================================
--- trunk/src/plugins.c	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/src/plugins.c	2007-11-20 18:15:46 UTC (rev 2060)
@@ -71,13 +71,18 @@
 
 	PluginInfo*	(*info) ();	/* Returns plugin name, description */
 	void	(*init) (GeanyData *data);	/* Called when the plugin is enabled */
+	void	(*configure) (GtkWidget *parent);	/* plugin configure dialog, optionally */
 	void	(*cleanup) ();		/* Called when the plugin is disabled or when Geany exits */
 }
 Plugin;
 
-static GList *plugin_list = NULL;
 
+static GList *plugin_list = NULL; // list of all available, loadable plugins
+static GList *active_plugin_list = NULL; // list of only actually loaded plugins
+static GtkWidget *separator = NULL; // separator in the Tools menu
+static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data);
 
+
 static DocumentFuncs doc_funcs = {
 	&document_new_file,
 	&document_get_cur_idx,
@@ -296,6 +301,51 @@
 }
 
 
+static gboolean
+is_active_plugin(const gchar *name)
+{
+	gint i, len;
+
+	g_return_val_if_fail(name, FALSE);
+
+	// if app->active_plugins is NULL, guess it was not yet set and don't load any plugins
+	if (app->active_plugins == NULL || (len = g_strv_length(app->active_plugins)) == 0)
+		return FALSE;
+
+	for (i = 0; i < len; i++)
+	{
+		if (utils_str_equal(name, app->active_plugins[i]))
+			return TRUE;
+	}
+	return FALSE;
+}
+
+
+static void
+plugin_init(Plugin *plugin)
+{
+	GeanyCallback *callbacks;
+
+	if (plugin->init)
+		plugin->init(&geany_data);
+
+	if (plugin->fields.flags & PLUGIN_IS_DOCUMENT_SENSITIVE)
+	{
+		gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(app->notebook)) ? TRUE : FALSE;
+		gtk_widget_set_sensitive(plugin->fields.menu_item, enable);
+	}
+
+	g_module_symbol(plugin->module, "geany_callbacks", (void *) &callbacks);
+	if (callbacks)
+		add_callbacks(plugin, callbacks);
+
+	active_plugin_list = g_list_append(active_plugin_list, plugin);
+
+	geany_debug("Loaded:   %s (%s)", plugin->filename,
+		NVL(plugin->info()->name, "<Unknown>"));
+}
+
+
 static Plugin*
 plugin_new(const gchar *fname)
 {
@@ -304,7 +354,6 @@
 	PluginInfo* (*info)();
 	PluginFields **plugin_fields;
 	GeanyData **p_geany_data;
-	GeanyCallback *callbacks;
 
 	g_return_val_if_fail(fname, NULL);
 	g_return_val_if_fail(g_module_supported(), NULL);
@@ -346,8 +395,7 @@
 			g_warning("%s: %s", fname, g_module_error());
 		return NULL;
 	}
-	geany_debug("Initializing plugin '%s' (%s)",
-		info()->name, info()->description);
+	geany_debug("Initializing plugin '%s'", info()->name);
 
 	plugin = g_new0(Plugin, 1);
 	plugin->info = info;
@@ -362,6 +410,7 @@
 		*plugin_fields = &plugin->fields;
 
 	g_module_symbol(module, "init", (void *) &plugin->init);
+	g_module_symbol(module, "configure", (void *) &plugin->configure);
 	g_module_symbol(module, "cleanup", (void *) &plugin->cleanup);
 	if (plugin->init != NULL && plugin->cleanup == NULL)
 	{
@@ -370,21 +419,11 @@
 				info()->name);
 	}
 
-	if (plugin->init)
-		plugin->init(&geany_data);
-
-	if (plugin->fields.flags & PLUGIN_IS_DOCUMENT_SENSITIVE)
+	// only initialise the plugin if it should be loaded
+	if (is_active_plugin(fname))
 	{
-		gboolean enable = gtk_notebook_get_n_pages(GTK_NOTEBOOK(app->notebook)) ? TRUE : FALSE;
-		gtk_widget_set_sensitive(plugin->fields.menu_item, enable);
+		plugin_init(plugin);
 	}
-
-	g_module_symbol(module, "geany_callbacks", (void *) &callbacks);
-	if (callbacks)
-		add_callbacks(plugin, callbacks);
-
-	geany_debug("Loaded:   %s (%s)", fname,
-		NVL(plugin->info()->name, "<Unknown>"));
 	return plugin;
 }
 
@@ -403,20 +442,40 @@
 
 
 static void
+plugin_unload(Plugin *plugin)
+{
+	g_return_if_fail(plugin);
+	g_return_if_fail(plugin->module);
+
+	if (g_list_find(active_plugin_list, plugin))
+	{	// only do cleanup if the plugin was actually loaded
+		if (plugin->cleanup)
+			plugin->cleanup();
+
+		remove_callbacks(plugin);
+
+		active_plugin_list = g_list_remove(active_plugin_list, plugin);
+	}
+	geany_debug("Unloaded: %s", plugin->filename);
+}
+
+
+static void
 plugin_free(Plugin *plugin)
 {
 	g_return_if_fail(plugin);
 	g_return_if_fail(plugin->module);
 
-	if (plugin->cleanup)
-		plugin->cleanup();
+	if (g_list_find(active_plugin_list, plugin) != NULL)
+	{	// only do cleanup if the plugin was actually loaded
+		if (plugin->cleanup)
+			plugin->cleanup();
 
+		remove_callbacks(plugin);
+	}
 	if (! g_module_close(plugin->module))
 		g_warning("%s: %s", plugin->filename, g_module_error());
-	else
-		geany_debug("Unloaded: %s", plugin->filename);
 
-	remove_callbacks(plugin);
 	g_free(plugin->filename);
 	g_free(plugin);
 }
@@ -490,24 +549,54 @@
 	geany_data_init();
 	geany_object = geany_object_new();
 
-	widget = gtk_separator_menu_item_new();
-	gtk_container_add(GTK_CONTAINER(geany_data.tools_menu), widget);
+	widget = gtk_menu_item_new_with_mnemonic(_("Plugin Manager"));
+	gtk_container_add(GTK_CONTAINER (geany_data.tools_menu), widget);
+	gtk_widget_show(widget);
+	g_signal_connect((gpointer) widget, "activate", G_CALLBACK(pm_show_dialog), NULL);
 
+	separator = gtk_separator_menu_item_new();
+	gtk_container_add(GTK_CONTAINER(geany_data.tools_menu), separator);
+
 	load_plugin_paths();
 
-	if (g_list_length(plugin_list) > 0)
-		gtk_widget_show(widget);
+	plugins_update_tools_menu();
 }
 
 
+void plugins_create_active_list()
+{
+	gint i = 0;
+	GList *list;
+
+	g_strfreev(app->active_plugins);
+
+	if (active_plugin_list == NULL)
+	{
+		app->active_plugins = NULL;
+		return;
+	}
+
+	app->active_plugins = g_new0(gchar*, g_list_length(active_plugin_list) + 1);
+	for (list = g_list_first(active_plugin_list); list != NULL; list = list->next)
+	{
+		app->active_plugins[i] = g_strdup(((Plugin*)list->data)->filename);
+		i++;
+	}
+	app->active_plugins[i] = NULL;
+}
+
+
 void plugins_free()
 {
-
 	if (plugin_list != NULL)
 	{
 		g_list_foreach(plugin_list, (GFunc) plugin_free, NULL);
 		g_list_free(plugin_list);
 	}
+	if (active_plugin_list != NULL)
+		// no need to do more here, active_plugin_list holds the same pointer as plugin_list
+		g_list_free(active_plugin_list);
+
 	g_object_unref(geany_object);
 }
 
@@ -516,7 +605,7 @@
 {
 	GList *item;
 
-	for (item = plugin_list; item != NULL; item = g_list_next(item))
+	for (item = active_plugin_list; item != NULL; item = g_list_next(item))
 	{
 		Plugin *plugin = item->data;
 
@@ -525,4 +614,244 @@
 	}
 }
 
+
+void plugins_update_tools_menu()
+{
+	if (separator == NULL)
+		return;
+
+	ui_widget_show_hide(separator, g_list_length(active_plugin_list) > 0);
+}
+
+
+/* Plugin Manager */
+
+enum
+{
+	PLUGIN_COLUMN_CHECK = 0,
+	PLUGIN_COLUMN_NAME,
+	PLUGIN_COLUMN_FILE,
+	PLUGIN_COLUMN_PLUGIN,
+	PLUGIN_N_COLUMNS
+};
+
+typedef struct
+{
+	GtkWidget *dialog;
+	GtkWidget *tree;
+	GtkListStore *store;
+	GtkWidget *description_label;
+	GtkWidget *configure_button;
+} PluginManagerWidgets;
+static PluginManagerWidgets pm_widgets;
+
+
+void pm_selection_changed(GtkTreeSelection *selection, gpointer user_data)
+{
+	GtkTreeIter iter;
+	GtkTreeModel *model;
+	Plugin *p;
+	PluginInfo *pi;
+
+	if (gtk_tree_selection_get_selected(selection, &model, &iter))
+	{
+		gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, -1);
+
+		if (p != NULL)
+		{
+			gchar *text;
+
+			pi = p->info();
+			text = g_strdup_printf(
+				_("Plugin: %s %s\nDescription: %s\nAuthor(s): %s"),
+				pi->name, pi->version, pi->description, pi->author);
+
+			gtk_label_set_text(GTK_LABEL(pm_widgets.description_label), text);
+			g_free(text);
+
+			gtk_widget_set_sensitive(pm_widgets.configure_button,
+				p->configure != NULL && g_list_find(active_plugin_list, p) != NULL);
+		}
+	}
+}
+
+
+static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer data)
+{
+	gboolean old_state, state;
+	GtkTreeIter iter;
+	GtkTreePath *path = gtk_tree_path_new_from_string(pth);
+	Plugin *p;
+
+	gtk_tree_model_get_iter(GTK_TREE_MODEL(pm_widgets.store), &iter, path);
+	gtk_tree_path_free(path);
+
+	gtk_tree_model_get(GTK_TREE_MODEL(pm_widgets.store), &iter,
+		PLUGIN_COLUMN_CHECK, &old_state, PLUGIN_COLUMN_PLUGIN, &p, -1);
+	if (p == NULL)
+		return;
+	state = ! old_state; // toggle the state
+	gtk_list_store_set(pm_widgets.store, &iter, PLUGIN_COLUMN_CHECK, state, -1);
+
+	// load/unload the plugin once it was toggled for correct behaviour of the preferences button
+	// we want to call plugin's configure() only after init() has been called
+
+	// plugin should be loaded, so load it if it is not already
+	if (state && g_list_find(active_plugin_list, p) == NULL)
+	{
+		plugin_init(p);
+	}
+	// plugin should be unloaded, so unload it if it is loaded
+	if (! state && g_list_find(active_plugin_list, p) != NULL)
+	{
+		plugin_unload(p);
+	}
+
+	// set again the sensitiveness of the configure button
+	gtk_widget_set_sensitive(pm_widgets.configure_button,
+		p->configure != NULL && g_list_find(active_plugin_list, p) != NULL);
+
+}
+
+
+static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store)
+{
+	GtkCellRenderer *text_renderer, *checkbox_renderer;
+	GtkTreeViewColumn *column;
+	GtkTreeIter iter;
+	GList *list;
+	GtkTreeSelection *sel;
+
+	checkbox_renderer = gtk_cell_renderer_toggle_new();
+    column = gtk_tree_view_column_new_with_attributes(
+		_("Active"), checkbox_renderer, "active", PLUGIN_COLUMN_CHECK, NULL);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+	g_signal_connect((gpointer) checkbox_renderer, "toggled", G_CALLBACK(pm_plugin_toggled), NULL);
+
+	text_renderer = gtk_cell_renderer_text_new();
+    column = gtk_tree_view_column_new_with_attributes(
+		_("Plugin"), text_renderer, "text", PLUGIN_COLUMN_NAME, NULL);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	text_renderer = gtk_cell_renderer_text_new();
+	g_object_set(text_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+    column = gtk_tree_view_column_new_with_attributes(
+		_("File"), text_renderer, "text", PLUGIN_COLUMN_FILE, NULL);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(tree), FALSE);
+	gtk_tree_sortable_set_sort_column_id(
+		GTK_TREE_SORTABLE(store), PLUGIN_COLUMN_NAME, GTK_SORT_ASCENDING);
+
+	// selection handling
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
+	gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
+	g_signal_connect((gpointer) sel, "changed", G_CALLBACK(pm_selection_changed), NULL);
+
+	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,
+				PLUGIN_COLUMN_NAME, _("No plugins available."),
+				PLUGIN_COLUMN_FILE, "", PLUGIN_COLUMN_PLUGIN, NULL, -1);
+	}
+	else
+	{
+		for (; list != NULL; list = list->next)
+		{
+			gboolean active = (g_list_find(active_plugin_list, list->data) != NULL) ? TRUE : FALSE;
+			gtk_list_store_append(store, &iter);
+			gtk_list_store_set(store, &iter,
+				PLUGIN_COLUMN_CHECK, active,
+				PLUGIN_COLUMN_NAME, ((Plugin*)list->data)->info()->name,
+				PLUGIN_COLUMN_FILE, ((Plugin*)list->data)->filename,
+				PLUGIN_COLUMN_PLUGIN, list->data,
+				-1);
+		}
+	}
+	gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store));
+}
+
+
+void pm_on_configure_button_clicked(GtkButton *button, gpointer user_data)
+{
+	GtkTreeModel *model;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter;
+	Plugin *p;
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pm_widgets.tree));
+	if (gtk_tree_selection_get_selected(selection, &model, &iter))
+	{
+		gtk_tree_model_get(model, &iter, PLUGIN_COLUMN_PLUGIN, &p, -1);
+
+		if (p != NULL)
+		{
+			p->configure(pm_widgets.dialog);
+		}
+	}
+}
+
+
+static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data)
+{
+	GtkWidget *vbox, *vbox2, *label_vbox, *hbox, *swin, *label, *label2;
+
+	pm_widgets.dialog = gtk_dialog_new_with_buttons(_("Plugins"), GTK_WINDOW(app->window),
+						GTK_DIALOG_DESTROY_WITH_PARENT,
+						GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
+	vbox = ui_dialog_vbox_new(GTK_DIALOG(pm_widgets.dialog));
+	gtk_widget_set_name(pm_widgets.dialog, "GeanyDialog");
+	gtk_box_set_spacing(GTK_BOX(vbox), 6);
+
+	gtk_window_set_default_size(GTK_WINDOW(pm_widgets.dialog), 400, 350);
+	gtk_dialog_set_default_response(GTK_DIALOG(pm_widgets.dialog), GTK_RESPONSE_CANCEL);
+
+	pm_widgets.tree = gtk_tree_view_new();
+	pm_widgets.store = gtk_list_store_new(
+		PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
+	pm_prepare_treeview(pm_widgets.tree, pm_widgets.store);
+
+	swin = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin),
+		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(swin), GTK_SHADOW_IN);
+	gtk_container_add(GTK_CONTAINER(swin), pm_widgets.tree);
+
+	label = gtk_label_new(_("Below is a list of available plugins. "
+		"Select the plugins which should be loaded when Geany is started."));
+
+	pm_widgets.configure_button = gtk_button_new_from_stock(GTK_STOCK_PREFERENCES);
+	gtk_widget_set_sensitive(pm_widgets.configure_button, FALSE);
+	g_signal_connect((gpointer) pm_widgets.configure_button, "clicked",
+		G_CALLBACK(pm_on_configure_button_clicked), NULL);
+
+	label2 = gtk_label_new(_("<b>Plugin details:</b>"));
+	gtk_label_set_use_markup(GTK_LABEL(label2), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(label2), 0, 0.5);
+	pm_widgets.description_label = gtk_label_new("");
+	gtk_label_set_line_wrap(GTK_LABEL(pm_widgets.description_label), TRUE);
+	gtk_misc_set_alignment(GTK_MISC(pm_widgets.description_label), 0, 0.5);
+
+	hbox = gtk_hbox_new(FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), label2, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), pm_widgets.configure_button, FALSE, FALSE, 0);
+
+	label_vbox = gtk_vbox_new(FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(label_vbox), hbox, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(label_vbox), pm_widgets.description_label, FALSE, FALSE, 0);
+
+	vbox2 = gtk_vbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 5);
+	gtk_box_pack_start(GTK_BOX(vbox2), swin, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox2), label_vbox, FALSE, FALSE, 0);
+
+	g_signal_connect_swapped((gpointer) pm_widgets.dialog, "response",
+		G_CALLBACK(gtk_widget_destroy), pm_widgets.dialog);
+
+	gtk_container_add(GTK_CONTAINER(vbox), vbox2);
+	gtk_widget_show_all(pm_widgets.dialog);
+}
+
 #endif

Modified: trunk/src/plugins.h
===================================================================
--- trunk/src/plugins.h	2007-11-19 15:35:30 UTC (rev 2059)
+++ trunk/src/plugins.h	2007-11-20 18:15:46 UTC (rev 2060)
@@ -32,7 +32,10 @@
 
 void plugins_free();
 
+void plugins_create_active_list();
 
+void plugins_update_tools_menu();
+
 void plugins_update_document_sensitive(gboolean enabled);
 
 #endif


This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.



More information about the Commits mailing list