Merge pull request #414 from eht16/plugin_manager_usability
Plugin manager usability improvements
This commit is contained in:
commit
ccec1fe29a
257
src/plugins.c
257
src/plugins.c
@ -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;
|
||||
gboolean is_active = FALSE;
|
||||
gboolean has_configure = FALSE;
|
||||
gboolean has_help = FALSE;
|
||||
gboolean has_keybindings = FALSE;
|
||||
|
||||
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);
|
||||
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));
|
||||
/* plugin popup menu */
|
||||
pm_widgets.popup_menu = gtk_menu_new();
|
||||
|
||||
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));
|
||||
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;
|
||||
|
||||
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));
|
||||
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;
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
vbox2 = gtk_vbox_new(FALSE, 3);
|
||||
gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 5);
|
||||
/* 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);
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user