diff --git a/ChangeLog b/ChangeLog index fcd6c974a..b47593fc9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2008-05-28 Nick Treleaven + + * src/plugindata.h, src/plugins.c, doc/plugin-symbols.c, + plugins/demoplugin.c, plugins/filebrowser.c, plugins/autosave.c: + Note: this breaks the plugin API. + Remove plugin symbol configure(). + Add plugin symbol plugin_configure() which is used to tell Geany a + widget to pack into the plugin preferences dialog, and connect a + response callback for when the dialog receives a user decision. + This allows Geany to in future implement a common preferences dialog + for all plugins, without breaking the plugin API/ABI. + Add Apply button for plugin preference dialogs (to indicate plugins + should handle the apply response as well as OK, as a multiple plugin + configuration dialog would want an apply button). + + 2008-05-27 Nick Treleaven * src/plugins.c: diff --git a/doc/plugin-symbols.c b/doc/plugin-symbols.c index d93d73959..9235de2a9 100644 --- a/doc/plugin-symbols.c +++ b/doc/plugin-symbols.c @@ -76,10 +76,13 @@ PluginCallback plugin_callbacks[]; KeyBindingGroup plugin_key_group[1]; -/** 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. - * @param parent The Plugin Manager dialog widget. */ -void configure(GtkWidget *parent); +/** Called before showing the plugin preferences dialog to let the user set some basic + * plugin configuration options. Can be omitted when not needed. + * @param dialog The plugin preferences dialog widget - this should only be used to + * connect the @c "response" signal. If settings should be read from the dialog, the + * reponse will be either @c GTK_RESPONSE_OK or @c GTK_RESPONSE_APPLY. + * @return A container widget holding preference widgets. */ +GtkWidget* plugin_configure(GtkDialog *dialog); /** Called after loading the plugin. * @param data The same as #geany_data. */ diff --git a/plugins/autosave.c b/plugins/autosave.c index 5da9373b1..dca9d76dc 100644 --- a/plugins/autosave.c +++ b/plugins/autosave.c @@ -116,61 +116,26 @@ void plugin_init(GeanyData *data) } -void configure(GtkWidget *parent) +static struct { - GtkWidget *dialog, *label, *spin, *vbox, *hbox, *checkbox, *radio1, *radio2; + GtkWidget *interval_spin; + GtkWidget *print_msg_checkbox; + GtkWidget *save_all_radio; +} +pref_widgets; - dialog = gtk_dialog_new_with_buttons(_("Auto Save"), - GTK_WINDOW(parent), GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); - vbox = p_ui->dialog_vbox_new(GTK_DIALOG(dialog)); - gtk_widget_set_name(dialog, "GeanyDialog"); - gtk_box_set_spacing(GTK_BOX(vbox), 6); - - label = gtk_label_new(_("Auto save interval:")); - gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); - gtk_container_add(GTK_CONTAINER(vbox), label); - - spin = gtk_spin_button_new_with_range(1, 1800, 1); - gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), interval); - - label = gtk_label_new(_("seconds")); - - hbox = gtk_hbox_new(FALSE, 5); - gtk_box_pack_start(GTK_BOX(hbox), spin, TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); - - gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); - - checkbox = gtk_check_button_new_with_label( - _("Print status message if files have been automatically saved")); - gtk_button_set_focus_on_click(GTK_BUTTON(checkbox), FALSE); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), print_msg); - gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 5); - - radio1 = gtk_radio_button_new_with_label(NULL, - _("Save only current open file")); - gtk_button_set_focus_on_click(GTK_BUTTON(radio1), FALSE); - gtk_container_add(GTK_CONTAINER(vbox), radio1); - - radio2 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio1), - _("Save all open files")); - gtk_button_set_focus_on_click(GTK_BUTTON(radio2), FALSE); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio2), save_all); - gtk_container_add(GTK_CONTAINER(vbox), radio2); - - gtk_widget_show_all(vbox); - - /* run the dialog and check for the response code */ - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) +static void +on_configure_response(GtkDialog *dialog, gint response, G_GNUC_UNUSED gpointer user_data) +{ + if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY) { GKeyFile *config = g_key_file_new(); gchar *data; gchar *config_dir = g_path_get_dirname(config_file); - interval = gtk_spin_button_get_value_as_int((GTK_SPIN_BUTTON(spin))); - print_msg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox)); - save_all = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(radio2)); + interval = gtk_spin_button_get_value_as_int((GTK_SPIN_BUTTON(pref_widgets.interval_spin))); + print_msg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets.print_msg_checkbox)); + save_all = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets.save_all_radio)); g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL); @@ -196,7 +161,54 @@ void configure(GtkWidget *parent) g_free(config_dir); g_key_file_free(config); } - gtk_widget_destroy(dialog); +} + + +GtkWidget *plugin_configure(GtkDialog *dialog) +{ + GtkWidget *vbox, *label, *spin, *hbox, *checkbox, *radio1, *radio2; + + vbox = gtk_vbox_new(FALSE, 6); + + label = gtk_label_new(_("Auto save interval:")); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_container_add(GTK_CONTAINER(vbox), label); + + spin = gtk_spin_button_new_with_range(1, 1800, 1); + gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), interval); + pref_widgets.interval_spin = spin; + + label = gtk_label_new(_("seconds")); + + hbox = gtk_hbox_new(FALSE, 5); + gtk_box_pack_start(GTK_BOX(hbox), spin, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 5); + + checkbox = gtk_check_button_new_with_label( + _("Print status message if files have been automatically saved")); + gtk_button_set_focus_on_click(GTK_BUTTON(checkbox), FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), print_msg); + gtk_box_pack_start(GTK_BOX(vbox), checkbox, FALSE, FALSE, 5); + pref_widgets.print_msg_checkbox = checkbox; + + radio1 = gtk_radio_button_new_with_label(NULL, + _("Save only current open file")); + gtk_button_set_focus_on_click(GTK_BUTTON(radio1), FALSE); + gtk_container_add(GTK_CONTAINER(vbox), radio1); + + radio2 = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(radio1), + _("Save all open files")); + gtk_button_set_focus_on_click(GTK_BUTTON(radio2), FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio2), save_all); + gtk_container_add(GTK_CONTAINER(vbox), radio2); + pref_widgets.save_all_radio = radio2; + + gtk_widget_show_all(vbox); + + g_signal_connect(dialog, "response", G_CALLBACK(on_configure_response), NULL); + return vbox; } diff --git a/plugins/demoplugin.c b/plugins/demoplugin.c index f2c71a572..4da86163d 100644 --- a/plugins/demoplugin.c +++ b/plugins/demoplugin.c @@ -101,39 +101,16 @@ void plugin_init(GeanyData *data) } -/* Called by Geany to show the plugin's configure dialog. This function is always called after - * plugin_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) +/* Callback connected in plugin_configure(). */ +static void +on_configure_response(GtkDialog *dialog, gint response, gpointer user_data) { - 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 = p_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 to 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) + /* catch OK or Apply clicked */ + if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY) { + /* We only have one pref here, but for more you would use a struct for user_data */ + GtkWidget *entry = GTK_WIDGET(user_data); + 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 @@ -142,7 +119,36 @@ void configure(GtkWidget *parent) * 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 to show the plugin's configure dialog. This function is always called after + * plugin_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. */ +GtkWidget *plugin_configure(GtkDialog *dialog) +{ + GtkWidget *label, *entry, *vbox; + + /* example configuration dialog */ + vbox = gtk_vbox_new(FALSE, 6); + + /* add a label and a text entry to the dialog */ + label = gtk_label_new(_("Welcome text to show:")); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + entry = gtk_entry_new(); + 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_all(vbox); + + /* Connect a callback for when the user clicks a dialog button */ + g_signal_connect(dialog, "response", G_CALLBACK(on_configure_response), entry); + return vbox; } diff --git a/plugins/filebrowser.c b/plugins/filebrowser.c index e3833c8f9..4ad94e1c0 100644 --- a/plugins/filebrowser.c +++ b/plugins/filebrowser.c @@ -952,61 +952,27 @@ void plugin_init(GeanyData *data) } -void configure(GtkWidget *parent) +static struct { - GtkWidget *dialog, *label, *entry, *checkbox_of, *checkbox_hf, *vbox; - GtkTooltips *tooltips = gtk_tooltips_new(); + GtkWidget *open_cmd_entry; + GtkWidget *show_hidden_checkbox; + GtkWidget *hide_objects_checkbox; +} +pref_widgets; - dialog = gtk_dialog_new_with_buttons(_("File Browser"), - GTK_WINDOW(parent), GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); - vbox = p_ui->dialog_vbox_new(GTK_DIALOG(dialog)); - gtk_widget_set_name(dialog, "GeanyDialog"); - gtk_box_set_spacing(GTK_BOX(vbox), 6); - - label = gtk_label_new(_("External open command:")); - gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); - gtk_container_add(GTK_CONTAINER(vbox), label); - - entry = gtk_entry_new(); - gtk_widget_show(entry); - if (open_cmd != NULL) - gtk_entry_set_text(GTK_ENTRY(entry), open_cmd); - gtk_tooltips_set_tip(tooltips, entry, - _("The command to execute when using \"Open with\". You can use %f and %d wildcards.\n" - "%f will be replaced with the filename including full path\n" - "%d will be replaced with the path name of the selected file without the filename"), - NULL); - gtk_container_add(GTK_CONTAINER(vbox), entry); - - checkbox_hf = gtk_check_button_new_with_label(_("Show hidden files")); - gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_hf), FALSE); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_hf), show_hidden_files); - gtk_box_pack_start(GTK_BOX(vbox), checkbox_hf, FALSE, FALSE, 5); - - checkbox_of = gtk_check_button_new_with_label(_("Hide object files")); - gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_of), FALSE); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_of), hide_object_files); - gtk_tooltips_set_tip(tooltips, checkbox_of, - _("Don't show generated object files in the file browser, this includes " - "*.o, *.obj. *.so, *.dll, *.a, *.lib"), - NULL); - gtk_box_pack_start(GTK_BOX(vbox), checkbox_of, FALSE, FALSE, 5); - - - gtk_widget_show_all(vbox); - - /* run the dialog and check for the response code */ - if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) +static void +on_configure_response(GtkDialog *dialog, gint response, gpointer user_data) +{ + if (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_APPLY) { GKeyFile *config = g_key_file_new(); gchar *data; gchar *config_dir = g_path_get_dirname(config_file); g_free(open_cmd); - open_cmd = g_strdup(gtk_entry_get_text(GTK_ENTRY(entry))); - show_hidden_files = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox_hf)); - hide_object_files = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkbox_of)); + open_cmd = g_strdup(gtk_entry_get_text(GTK_ENTRY(pref_widgets.open_cmd_entry))); + show_hidden_files = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets.show_hidden_checkbox)); + hide_object_files = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pref_widgets.hide_objects_checkbox)); g_key_file_load_from_file(config, config_file, G_KEY_FILE_NONE, NULL); @@ -1033,7 +999,52 @@ void configure(GtkWidget *parent) g_free(config_dir); g_key_file_free(config); } - gtk_widget_destroy(dialog); +} + + +GtkWidget *plugin_configure(GtkDialog *dialog) +{ + GtkWidget *label, *entry, *checkbox_of, *checkbox_hf, *vbox; + GtkTooltips *tooltips = gtk_tooltips_new(); + + vbox = gtk_vbox_new(FALSE, 6); + + label = gtk_label_new(_("External open command:")); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_container_add(GTK_CONTAINER(vbox), label); + + entry = gtk_entry_new(); + gtk_widget_show(entry); + if (open_cmd != NULL) + gtk_entry_set_text(GTK_ENTRY(entry), open_cmd); + gtk_tooltips_set_tip(tooltips, entry, + _("The command to execute when using \"Open with\". You can use %f and %d wildcards.\n" + "%f will be replaced with the filename including full path\n" + "%d will be replaced with the path name of the selected file without the filename"), + NULL); + gtk_container_add(GTK_CONTAINER(vbox), entry); + pref_widgets.open_cmd_entry = entry; + + checkbox_hf = gtk_check_button_new_with_label(_("Show hidden files")); + gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_hf), FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_hf), show_hidden_files); + gtk_box_pack_start(GTK_BOX(vbox), checkbox_hf, FALSE, FALSE, 5); + pref_widgets.show_hidden_checkbox = checkbox_hf; + + checkbox_of = gtk_check_button_new_with_label(_("Hide object files")); + gtk_button_set_focus_on_click(GTK_BUTTON(checkbox_of), FALSE); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox_of), hide_object_files); + gtk_tooltips_set_tip(tooltips, checkbox_of, + _("Don't show generated object files in the file browser, this includes " + "*.o, *.obj. *.so, *.dll, *.a, *.lib"), + NULL); + gtk_box_pack_start(GTK_BOX(vbox), checkbox_of, FALSE, FALSE, 5); + pref_widgets.hide_objects_checkbox = checkbox_of; + + gtk_widget_show_all(vbox); + + g_signal_connect(dialog, "response", G_CALLBACK(on_configure_response), NULL); + return vbox; } diff --git a/src/plugindata.h b/src/plugindata.h index b08b376d4..4bdb76fd4 100644 --- a/src/plugindata.h +++ b/src/plugindata.h @@ -36,7 +36,7 @@ /* The API version should be incremented whenever any plugin data types below are * modified or appended to. */ -static const gint api_version = 64; +static const gint api_version = 65; /* 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 diff --git a/src/plugins.c b/src/plugins.c index 6662efcf2..e81ed7c4c 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -74,9 +74,9 @@ typedef struct Plugin gsize signal_ids_len; KeyBindingGroup *key_group; - void (*init) (GeanyData *data); /* Called when the plugin is enabled */ - void (*configure) (GtkWidget *parent); /* plugin configure dialog, optionally */ - void (*cleanup) (void); /* Called when the plugin is disabled or when Geany exits */ + void (*init) (GeanyData *data); /* Called when the plugin is enabled */ + GtkWidget* (*configure) (GtkDialog *dialog); /* plugin configure dialog, optional */ + void (*cleanup) (void); /* Called when the plugin is disabled or when Geany exits */ } Plugin; @@ -467,9 +467,9 @@ plugin_init(Plugin *plugin) plugin->init(&geany_data); /* store some function pointers for later use */ - g_module_symbol(plugin->module, "configure", (void *) &plugin->configure); + g_module_symbol(plugin->module, "plugin_configure", (void *) &plugin->configure); g_module_symbol(plugin->module, "plugin_cleanup", (void *) &plugin->cleanup); - if (plugin->init != NULL && plugin->cleanup == NULL) + if (plugin->cleanup == NULL) { if (app->debug_mode) g_warning("Plugin '%s' has no plugin_cleanup() function - there may be memory leaks!", @@ -898,6 +898,7 @@ typedef struct GtkWidget *description_label; GtkWidget *configure_button; } PluginManagerWidgets; + static PluginManagerWidgets pm_widgets; @@ -1036,6 +1037,39 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) } +static void configure_plugin(Plugin *p) +{ + GtkWidget *parent = pm_widgets.dialog; + GtkWidget *prefs_page, *dialog, *vbox; + + dialog = gtk_dialog_new_with_buttons(p->info.name, + GTK_WINDOW(parent), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_STOCK_APPLY, GTK_RESPONSE_APPLY, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); + gtk_widget_set_name(dialog, "GeanyDialog"); + + vbox = ui_dialog_vbox_new(GTK_DIALOG(dialog)); + gtk_widget_show(vbox); + + prefs_page = p->configure(GTK_DIALOG(dialog)); + + if (! GTK_IS_WIDGET(prefs_page)) + { + geany_debug("Invalid widget returned from plugin_configure() in plugin \"%s\"!", + p->info.name); + } + else + { + gtk_container_add(GTK_CONTAINER(vbox), prefs_page); + + /* run the dialog */ + while (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_APPLY); + } + gtk_widget_destroy(dialog); +} + + void pm_on_configure_button_clicked(GtkButton *button, gpointer user_data) { GtkTreeModel *model; @@ -1050,7 +1084,7 @@ void pm_on_configure_button_clicked(GtkButton *button, gpointer user_data) if (p != NULL) { - p->configure(pm_widgets.dialog); + configure_plugin(p); } } }