Branch: refs/heads/master Author: Enrico Tröger enrico.troeger@uvena.de Committer: Enrico Tröger enrico.troeger@uvena.de Date: Fri, 05 Jun 2015 16:45:56 UTC Commit: ccec1fe29acc88c72b8324d74d2e63c1b0bc8466 https://github.com/geany/geany/commit/ccec1fe29acc88c72b8324d74d2e63c1b0bc84...
Log Message: ----------- Merge pull request #414 from eht16/plugin_manager_usability
Plugin manager usability improvements
Modified Paths: -------------- src/plugins.c
Modified: src/plugins.c 267 lines changed, 214 insertions(+), 53 deletions(-) =================================================================== @@ -876,9 +876,14 @@ typedef struct GtkWidget *dialog; GtkWidget *tree; GtkListStore *store; + GtkWidget *filter_entry; GtkWidget *configure_button; GtkWidget *keybindings_button; GtkWidget *help_button; + GtkWidget *popup_menu; + GtkWidget *popup_configure_menu_item; + GtkWidget *popup_keybindings_menu_item; + GtkWidget *popup_help_menu_item; } PluginManagerWidgets;
@@ -887,14 +892,26 @@ static PluginManagerWidgets pm_widgets;
static void pm_update_buttons(Plugin *p) { - gboolean is_active; - - is_active = is_active_plugin(p); - gtk_widget_set_sensitive(pm_widgets.configure_button, - (p->configure || p->configure_single) && is_active); - gtk_widget_set_sensitive(pm_widgets.help_button, p->help != NULL && is_active); - gtk_widget_set_sensitive(pm_widgets.keybindings_button, - p->key_group && p->key_group->plugin_key_count > 0 && is_active); + gboolean is_active = FALSE; + gboolean has_configure = FALSE; + gboolean has_help = FALSE; + gboolean has_keybindings = FALSE; + + if (p != NULL) + { + is_active = is_active_plugin(p); + has_configure = (p->configure || p->configure_single) && is_active; + has_help = p->help != NULL && is_active; + has_keybindings = p->key_group && p->key_group->plugin_key_count > 0 && is_active; + } + + gtk_widget_set_sensitive(pm_widgets.configure_button, has_configure); + gtk_widget_set_sensitive(pm_widgets.help_button, has_help); + gtk_widget_set_sensitive(pm_widgets.keybindings_button, has_keybindings); + + gtk_widget_set_sensitive(pm_widgets.popup_configure_menu_item, has_configure); + gtk_widget_set_sensitive(pm_widgets.popup_help_menu_item, has_help); + gtk_widget_set_sensitive(pm_widgets.popup_keybindings_menu_item, has_keybindings); }
@@ -919,19 +936,25 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer gboolean old_state, state; gchar *file_name; GtkTreeIter iter; + GtkTreeIter store_iter; GtkTreePath *path = gtk_tree_path_new_from_string(pth); + GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_widgets.tree)); Plugin *p;
- gtk_tree_model_get_iter(GTK_TREE_MODEL(pm_widgets.store), &iter, path); + gtk_tree_model_get_iter(model, &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); + gtk_tree_model_get(model, &iter, + PLUGIN_COLUMN_CHECK, &old_state, + PLUGIN_COLUMN_PLUGIN, &p, -1);
/* no plugins item */ if (p == NULL) return;
+ gtk_tree_model_filter_convert_iter_to_child_iter( + GTK_TREE_MODEL_FILTER(model), &store_iter, &iter); + state = ! old_state; /* toggle the state */
/* save the filename of the plugin */ @@ -949,7 +972,7 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer if (!p) { /* plugin file may no longer be on disk, or is now incompatible */ - gtk_list_store_remove(pm_widgets.store, &iter); + gtk_list_store_remove(pm_widgets.store, &store_iter); } else { @@ -957,7 +980,7 @@ static void pm_plugin_toggled(GtkCellRendererToggle *cell, gchar *pth, gpointer keybindings_load_keyfile(); /* load shortcuts */
/* update model */ - gtk_list_store_set(pm_widgets.store, &iter, + gtk_list_store_set(pm_widgets.store, &store_iter, PLUGIN_COLUMN_CHECK, state, PLUGIN_COLUMN_PLUGIN, p, -1);
@@ -1022,6 +1045,18 @@ static void pm_treeview_text_cell_data_func(GtkTreeViewColumn *column, GtkCellRe }
+static gboolean pm_treeview_button_press_cb(GtkWidget *widget, GdkEventButton *event, + G_GNUC_UNUSED gpointer user_data) +{ + if (event->button == 3) + { + gtk_menu_popup(GTK_MENU(pm_widgets.popup_menu), NULL, NULL, NULL, NULL, + event->button, event->time); + } + return FALSE; +} + + static gint pm_tree_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { @@ -1037,10 +1072,89 @@ static gint pm_tree_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter * }
+static gboolean pm_tree_search(const gchar *key, const gchar *haystack) +{ + gchar *normalized_string = NULL; + gchar *normalized_key = NULL; + gchar *case_normalized_string = NULL; + gchar *case_normalized_key = NULL; + gboolean matched = TRUE; + + normalized_string = g_utf8_normalize(haystack, -1, G_NORMALIZE_ALL); + normalized_key = g_utf8_normalize(key, -1, G_NORMALIZE_ALL); + + if (normalized_string != NULL && normalized_key != NULL) + { + GString *stripped_key; + gchar **subkey, **subkeys; + + case_normalized_string = g_utf8_casefold(normalized_string, -1); + case_normalized_key = g_utf8_casefold(normalized_key, -1); + stripped_key = g_string_new(case_normalized_key); + do {} while (utils_string_replace_all(stripped_key, " ", " ")); + subkeys = g_strsplit(stripped_key->str, " ", -1); + g_string_free(stripped_key, TRUE); + foreach_strv(subkey, subkeys) + { + if (strstr(case_normalized_string, *subkey) == NULL) + { + matched = FALSE; + break; + } + } + g_strfreev(subkeys); + } + + g_free(normalized_key); + g_free(normalized_string); + g_free(case_normalized_key); + g_free(case_normalized_string); + + return matched; +} + + +static gboolean pm_tree_filter_func(GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) +{ + Plugin *plugin; + gboolean matched; + const gchar *key; + gchar *haystack, *filename; + + gtk_tree_model_get(model, iter, PLUGIN_COLUMN_PLUGIN, &plugin, -1); + key = gtk_entry_get_text(GTK_ENTRY(pm_widgets.filter_entry)); + + filename = g_path_get_basename(plugin->filename); + haystack = g_strjoin(" ", plugin->info.name, plugin->info.description, + plugin->info.author, filename, NULL); + matched = pm_tree_search(key, haystack); + g_free(haystack); + g_free(filename); + + return matched; +} + + +static void on_pm_tree_filter_entry_changed_cb(GtkEntry *entry, gpointer user_data) +{ + GtkTreeModel *filter_model = gtk_tree_view_get_model(GTK_TREE_VIEW(pm_widgets.tree)); + gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(filter_model)); +} + + +static void on_pm_tree_filter_entry_icon_release_cb(GtkEntry *entry, GtkEntryIconPosition icon_pos, + GdkEvent *event, gpointer user_data) +{ + if (event->button.button == 1 && icon_pos == GTK_ENTRY_ICON_PRIMARY) + on_pm_tree_filter_entry_changed_cb(entry, user_data); +} + + static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) { GtkCellRenderer *text_renderer, *checkbox_renderer; GtkTreeViewColumn *column; + GtkTreeModel *filter_model; GtkTreeIter iter; GList *list; GtkTreeSelection *sel; @@ -1074,6 +1188,8 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE); g_signal_connect(sel, "changed", G_CALLBACK(pm_selection_changed), NULL);
+ g_signal_connect(tree, "button-press-event", G_CALLBACK(pm_treeview_button_press_cb), NULL); + list = g_list_first(plugin_list); if (list == NULL) { @@ -1095,12 +1211,19 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkListStore *store) -1); } } - gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store)); + /* filter */ + filter_model = gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL); + gtk_tree_model_filter_set_visible_func( + GTK_TREE_MODEL_FILTER(filter_model), pm_tree_filter_func, NULL, NULL); + + /* set model to tree view */ + gtk_tree_view_set_model(GTK_TREE_VIEW(tree), filter_model); g_object_unref(store); + g_object_unref(filter_model); }
-static void pm_on_plugin_button_clicked(GtkButton *button, gpointer user_data) +static void pm_on_plugin_button_clicked(G_GNUC_UNUSED GtkButton *button, gpointer user_data) { GtkTreeModel *model; GtkTreeSelection *selection; @@ -1138,39 +1261,71 @@ free_non_active_plugin(gpointer data, gpointer user_data) }
-/* Callback when plugin manager dialog closes, only ever has response of - * GTK_RESPONSE_OK or GTK_RESPONSE_DELETE_EVENT and both are treated the same. */ +/* Callback when plugin manager dialog closes, responses GTK_RESPONSE_CLOSE and + * GTK_RESPONSE_DELETE_EVENT are treated the same. */ static void pm_dialog_response(GtkDialog *dialog, gint response, gpointer user_data) { - if (plugin_list != NULL) + switch (response) { - /* remove all non-active plugins from the list */ - g_list_foreach(plugin_list, free_non_active_plugin, NULL); - g_list_free(plugin_list); - plugin_list = NULL; - } - gtk_widget_destroy(GTK_WIDGET(dialog)); + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + if (plugin_list != NULL) + { + /* remove all non-active plugins from the list */ + g_list_foreach(plugin_list, free_non_active_plugin, NULL); + g_list_free(plugin_list); + plugin_list = NULL; + } + gtk_widget_destroy(GTK_WIDGET(dialog));
- configuration_save(); + configuration_save(); + break; + case PM_BUTTON_CONFIGURE: + case PM_BUTTON_HELP: + case PM_BUTTON_KEYBINDINGS: + /* forward event to the generic handler */ + pm_on_plugin_button_clicked(NULL, GINT_TO_POINTER(response)); + break; + } }
static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data) { - GtkWidget *vbox, *vbox2, *hbox, *swin, *label; + GtkWidget *vbox, *vbox2, *swin, *label, *menu_item, *filter_entry;
/* before showing the dialog, we need to create the list of available plugins */ load_all_plugins();
- pm_widgets.dialog = gtk_dialog_new_with_buttons(_("Plugins"), GTK_WINDOW(main_widgets.window), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); + pm_widgets.dialog = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(pm_widgets.dialog), _("Plugins")); + gtk_window_set_transient_for(GTK_WINDOW(pm_widgets.dialog), GTK_WINDOW(main_widgets.window)); + gtk_window_set_destroy_with_parent(GTK_WINDOW(pm_widgets.dialog), TRUE); + 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), 500, 450);
+ pm_widgets.help_button = gtk_dialog_add_button( + GTK_DIALOG(pm_widgets.dialog), GTK_STOCK_HELP, PM_BUTTON_HELP); + pm_widgets.configure_button = gtk_dialog_add_button( + GTK_DIALOG(pm_widgets.dialog), GTK_STOCK_PREFERENCES, PM_BUTTON_CONFIGURE); + pm_widgets.keybindings_button = gtk_dialog_add_button( + GTK_DIALOG(pm_widgets.dialog), _("Keybindings"), PM_BUTTON_KEYBINDINGS); + gtk_dialog_add_button(GTK_DIALOG(pm_widgets.dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE); + gtk_dialog_set_default_response(GTK_DIALOG(pm_widgets.dialog), GTK_RESPONSE_CLOSE); + + /* filter */ + pm_widgets.filter_entry = filter_entry = gtk_entry_new(); + gtk_entry_set_icon_from_stock(GTK_ENTRY(filter_entry), GTK_ENTRY_ICON_PRIMARY, GTK_STOCK_FIND); + ui_entry_add_clear_icon(GTK_ENTRY(filter_entry)); + g_signal_connect(filter_entry, "changed", G_CALLBACK(on_pm_tree_filter_entry_changed_cb), NULL); + g_signal_connect(filter_entry, "icon-release", + G_CALLBACK(on_pm_tree_filter_entry_icon_release_cb), NULL); + + /* prepare treeview */ pm_widgets.tree = gtk_tree_view_new(); pm_widgets.store = gtk_list_store_new( PLUGIN_N_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_POINTER); @@ -1184,36 +1339,42 @@ static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data)
label = geany_wrap_label_new(_("Choose which plugins should be loaded at startup:"));
- pm_widgets.keybindings_button = gtk_button_new_with_label(_("Keybindings")); - gtk_widget_set_sensitive(pm_widgets.keybindings_button, FALSE); - g_signal_connect(pm_widgets.keybindings_button, "clicked", - G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_KEYBINDINGS)); - - pm_widgets.configure_button = gtk_button_new_from_stock(GTK_STOCK_PREFERENCES); - gtk_widget_set_sensitive(pm_widgets.configure_button, FALSE); - g_signal_connect(pm_widgets.configure_button, "clicked", - G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_CONFIGURE)); - - pm_widgets.help_button = gtk_button_new_from_stock(GTK_STOCK_HELP); - gtk_widget_set_sensitive(pm_widgets.help_button, FALSE); - g_signal_connect(pm_widgets.help_button, "clicked", - G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_HELP)); - - hbox = gtk_hbox_new(FALSE, 0); - gtk_box_set_spacing(GTK_BOX(hbox), 6); - gtk_box_pack_end(GTK_BOX(hbox), pm_widgets.keybindings_button, FALSE, FALSE, 0); - gtk_box_pack_end(GTK_BOX(hbox), pm_widgets.configure_button, FALSE, FALSE, 0); - gtk_box_pack_end(GTK_BOX(hbox), pm_widgets.help_button, FALSE, FALSE, 0); - - vbox2 = gtk_vbox_new(FALSE, 3); - gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 5); + /* plugin popup menu */ + pm_widgets.popup_menu = gtk_menu_new(); + + menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, NULL); + gtk_container_add(GTK_CONTAINER(pm_widgets.popup_menu), menu_item); + g_signal_connect(menu_item, "activate", + G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_CONFIGURE)); + pm_widgets.popup_configure_menu_item = menu_item; + + menu_item = gtk_image_menu_item_new_with_mnemonic(_("Keybindings")); + gtk_container_add(GTK_CONTAINER(pm_widgets.popup_menu), menu_item); + g_signal_connect(menu_item, "activate", + G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_KEYBINDINGS)); + pm_widgets.popup_keybindings_menu_item = menu_item; + + menu_item = gtk_image_menu_item_new_from_stock(GTK_STOCK_HELP, NULL); + gtk_container_add(GTK_CONTAINER(pm_widgets.popup_menu), menu_item); + g_signal_connect(menu_item, "activate", + G_CALLBACK(pm_on_plugin_button_clicked), GINT_TO_POINTER(PM_BUTTON_HELP)); + pm_widgets.popup_help_menu_item = menu_item; + + /* put it together */ + vbox2 = gtk_vbox_new(FALSE, 6); + gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox2), filter_entry, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox2), swin, TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, TRUE, 0);
g_signal_connect(pm_widgets.dialog, "response", G_CALLBACK(pm_dialog_response), NULL);
gtk_box_pack_start(GTK_BOX(vbox), vbox2, TRUE, TRUE, 0); gtk_widget_show_all(pm_widgets.dialog); + gtk_widget_show_all(pm_widgets.popup_menu); + + /* set initial plugin buttons state, pass NULL as no plugin is selected by default */ + pm_update_buttons(NULL); + gtk_widget_grab_focus(pm_widgets.filter_entry); }
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).