Branch: refs/heads/master Author: Enrico Tröger enrico.troeger@uvena.de Committer: GitHub noreply@github.com Date: Sun, 19 Jan 2025 14:25:22 UTC Commit: eb5c3fb35110c21d7e28a88c24a8606013b24d3d https://github.com/geany/geany/commit/eb5c3fb35110c21d7e28a88c24a8606013b24d...
Log Message: ----------- Merge pull request #4192 from eht16/issue2848_add_filter_for_keybindings
Add filter for keybindings in preferences dialog
Modified Paths: -------------- data/geany.glade src/plugins.c src/prefs.c src/utils.c src/utils.h
Modified: data/geany.glade 18 lines changed, 16 insertions(+), 2 deletions(-) =================================================================== @@ -5532,6 +5532,20 @@ <property name="position">0</property> </packing> </child> + <child> + <object class="GtkEntry" id="entry_keybinding_filter"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="primary-icon-stock">gtk-find</property> + <property name="secondary-icon-activatable">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> <object class="GtkScrolledWindow" id="scrolledwindow8"> <property name="visible">True</property> @@ -5551,7 +5565,7 @@ <packing> <property name="expand">True</property> <property name="fill">True</property> - <property name="position">1</property> + <property name="position">2</property> </packing> </child> <child> @@ -5573,7 +5587,7 @@ <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">2</property> + <property name="position">3</property> </packing> </child> </object>
Modified: src/plugins.c 56 lines changed, 13 insertions(+), 43 deletions(-) =================================================================== @@ -1648,6 +1648,17 @@ static gboolean pm_treeview_button_press_cb(GtkWidget *widget, GdkEventButton *e }
+static gboolean pm_treeview_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data) +{ + if (event->keyval == GDK_KEY_f && (event->state & GEANY_PRIMARY_MOD_MASK)) + { + gtk_widget_grab_focus(pm_widgets.filter_entry); + return TRUE; + } + return FALSE; +} + + static gint pm_tree_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { @@ -1663,48 +1674,6 @@ 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; @@ -1721,7 +1690,7 @@ static gboolean pm_tree_filter_func(GtkTreeModel *model, GtkTreeIter *iter, gpoi 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); + matched = utils_utf8_substring_match(key, haystack); g_free(haystack); g_free(filename);
@@ -1782,6 +1751,7 @@ static void pm_prepare_treeview(GtkWidget *tree, GtkTreeStore *store) 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); + g_signal_connect(tree, "key-press-event", G_CALLBACK(pm_treeview_key_press_cb), NULL);
/* filter */ filter_model = gtk_tree_model_filter_new(GTK_TREE_MODEL(store), NULL);
Modified: src/prefs.c 124 lines changed, 115 insertions(+), 9 deletions(-) =================================================================== @@ -84,6 +84,10 @@ static gboolean kb_grab_key_dialog_key_press_cb(GtkWidget *dialog, GdkEventKey * static void kb_change_iter_shortcut(KbData *kbdata, GtkTreeIter *iter, const gchar *new_text); static gboolean kb_find_duplicate(GtkTreeStore *store, GtkWidget *parent, GtkTreeIter *old_iter, guint key, GdkModifierType mods, const gchar *shortcut); +static void kb_filter_entry_changed_cb(GtkEntry *entry, gpointer user_data); +static void kb_filter_entry_icon_release_cb(GtkEntry *entry, GtkEntryIconPosition icon_pos, + GdkEvent *event, gpointer user_data); +static gboolean kb_tree_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data); static void on_toolbar_show_toggled(GtkToggleButton *togglebutton, gpointer user_data); static void on_show_notebook_tabs_toggled(GtkToggleButton *togglebutton, gpointer user_data); static void on_enable_plugins_toggled(GtkToggleButton *togglebutton, gpointer user_data); @@ -141,6 +145,7 @@ enum KB_TREE_INDEX, KB_TREE_EDITABLE, KB_TREE_WEIGHT, + KB_TREE_VISIBLE, KB_TREE_COLUMNS };
@@ -176,7 +181,8 @@ static void kb_tree_view_change_button_clicked_cb(GtkWidget *button, KbData *kbd GtkWidget *accel_label; gchar *str;
- dialog = gtk_dialog_new_with_buttons(_("Grab Key"), GTK_WINDOW(ui_widgets.prefs_dialog), + dialog = gtk_dialog_new_with_buttons(_("Assign Keybinding"), + GTK_WINDOW(ui_widgets.prefs_dialog), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL); @@ -261,16 +267,21 @@ static gboolean kb_tree_view_button_press_event_cb(GtkWidget *widget, GdkEventBu }
-static void kb_init_tree(KbData *kbdata) +static void kb_init_tree(KbData *kbdata, GtkWidget *kb_filter_entry) { GtkCellRenderer *renderer; + GtkTreeModel *filter_model; GtkTreeViewColumn *column;
kbdata->tree = GTK_TREE_VIEW(ui_lookup_widget(ui_widgets.prefs_dialog, "treeview7"));
kbdata->store = gtk_tree_store_new(KB_TREE_COLUMNS, - G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_INT); - gtk_tree_view_set_model(GTK_TREE_VIEW(kbdata->tree), GTK_TREE_MODEL(kbdata->store)); + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN, G_TYPE_INT, G_TYPE_BOOLEAN); + filter_model = gtk_tree_model_filter_new(GTK_TREE_MODEL(kbdata->store), NULL); + gtk_tree_model_filter_set_visible_column(GTK_TREE_MODEL_FILTER(filter_model), KB_TREE_VISIBLE); + /* set model to tree view */ + gtk_tree_view_set_model(GTK_TREE_VIEW(kbdata->tree), filter_model); + g_object_unref(filter_model); g_object_unref(kbdata->store);
renderer = gtk_cell_renderer_text_new(); @@ -292,9 +303,13 @@ static void kb_init_tree(KbData *kbdata)
g_signal_connect(renderer, "edited", G_CALLBACK(kb_cell_edited_cb), kbdata); g_signal_connect(kbdata->tree, "button-press-event", G_CALLBACK(kb_tree_view_button_press_event_cb), kbdata); + g_signal_connect(kbdata->tree, "key-press-event", G_CALLBACK(kb_tree_key_press_cb), kb_filter_entry); g_signal_connect(kbdata->tree, "popup-menu", G_CALLBACK(kb_popup_menu_cb), kbdata); g_signal_connect(ui_lookup_widget(ui_widgets.prefs_dialog, "button2"), "clicked", G_CALLBACK(kb_tree_view_change_button_clicked_cb), kbdata); + g_signal_connect(kb_filter_entry, "changed", G_CALLBACK(kb_filter_entry_changed_cb), NULL); + g_signal_connect(kb_filter_entry, "icon-release", G_CALLBACK(kb_filter_entry_icon_release_cb), NULL); + }
@@ -348,7 +363,7 @@ void prefs_kb_search_name(const gchar *search) }
-static void kb_init(KbData *kbdata) +static void kb_init(KbData *kbdata, GtkWidget *kb_filter_entry) { GtkTreeIter parent, iter; gsize g, i; @@ -357,20 +372,21 @@ static void kb_init(KbData *kbdata) GeanyKeyBinding *kb;
if (kbdata->store == NULL) - kb_init_tree(kbdata); + kb_init_tree(kbdata, kb_filter_entry);
+ gtk_entry_set_text(GTK_ENTRY(kb_filter_entry), ""); foreach_ptr_array(group, g, keybinding_groups) { gtk_tree_store_append(kbdata->store, &parent, NULL); gtk_tree_store_set(kbdata->store, &parent, KB_TREE_ACTION, group->label, - KB_TREE_INDEX, g, -1); + KB_TREE_INDEX, g, KB_TREE_VISIBLE, TRUE, -1);
foreach_ptr_array(kb, i, group->key_items) { label = keybindings_get_label(kb); gtk_tree_store_append(kbdata->store, &iter, &parent); gtk_tree_store_set(kbdata->store, &iter, KB_TREE_ACTION, label, - KB_TREE_EDITABLE, TRUE, KB_TREE_INDEX, kb->id, -1); + KB_TREE_EDITABLE, TRUE, KB_TREE_INDEX, kb->id, KB_TREE_VISIBLE, TRUE, -1); kb_set_shortcut(kbdata->store, &iter, kb->key, kb->mods); g_free(label); } @@ -691,7 +707,8 @@ static void prefs_init_dialog(void)
/* Keybindings */ - kb_init(&global_kb_data); + widget = ui_lookup_widget(ui_widgets.prefs_dialog, "entry_keybinding_filter"); + kb_init(&global_kb_data, widget);
/* Printing */ { @@ -1397,6 +1414,94 @@ static void kb_cell_edited_cb(GtkCellRendererText *cellrenderertext, }
+static void kb_tree_update_visibility(GtkTreeStore *store, const gchar *entry_text) +{ + GtkTreeModel *model = GTK_TREE_MODEL(store); + GtkTreeIter child, parent; + + /* get first parent */ + if (! gtk_tree_model_iter_children(model, &parent, NULL)) + return; + + /* foreach parent */ + while (TRUE) + { + gboolean visible_parent = FALSE; + gboolean visible_children = FALSE; + gchar *group_name; + + /* check if group name matches and then display *all* its children, otherwise + * check each child if the action name matches */ + gtk_tree_model_get(model, &parent, KB_TREE_ACTION, &group_name, -1); + if (group_name && ! EMPTY(entry_text)) + visible_parent = utils_utf8_substring_match(entry_text, group_name); + else + visible_parent = FALSE; + g_free(group_name); + + /* get first child */ + if (! gtk_tree_model_iter_children(model, &child, &parent)) + return; + + /* foreach child */ + while (TRUE) + { + gboolean visible; + gchar *action_name; + + gtk_tree_model_get(model, &child, KB_TREE_ACTION, &action_name, -1); + if (!action_name || EMPTY(entry_text)) + visible = TRUE; + else + visible = utils_utf8_substring_match(entry_text, action_name); + g_free(action_name); + + if (visible) + visible_children = TRUE; + + gtk_tree_store_set(store, &child, KB_TREE_VISIBLE, visible || visible_parent, -1); + + if (! gtk_tree_model_iter_next(model, &child)) + break; + } + + gtk_tree_store_set(store, &parent, KB_TREE_VISIBLE, visible_children || visible_parent, -1); + + if (! gtk_tree_model_iter_next(model, &parent)) + return; + } +} + + +static void kb_filter_entry_changed_cb(GtkEntry *entry, gpointer user_data) +{ + const gchar *entry_text = gtk_entry_get_text(entry); + + kb_tree_update_visibility(global_kb_data.store, entry_text); + gtk_tree_view_expand_all(global_kb_data.tree); +} + + +static void kb_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) + kb_filter_entry_changed_cb(entry, user_data); +} + + +static gboolean kb_tree_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data) +{ + if (event->keyval == GDK_KEY_f && (event->state & GEANY_PRIMARY_MOD_MASK)) + { + GtkWidget *kb_filter_entry = user_data; + gtk_widget_grab_focus(kb_filter_entry); + return TRUE; + } + return FALSE; +} + + static gboolean kb_grab_key_dialog_key_press_cb(GtkWidget *dialog, GdkEventKey *event, GtkLabel *label) { gchar *str; @@ -1720,6 +1825,7 @@ void prefs_show_dialog(void) "entry_com_term", "entry_browser", "entry_grep", + "entry_keybinding_filter", "entry_contextaction", "entry_template_developer", "entry_template_initial",
Modified: src/utils.c 48 lines changed, 48 insertions(+), 0 deletions(-) =================================================================== @@ -475,6 +475,54 @@ gchar *utils_utf8_strdown(const gchar *str) }
+/* Returns @c TRUE if @a key is a substring of @a haystack, case-insensitive. + * Applies @c g_utf8_normalize and @c g_utf8_casefold on both input strings before comparison. + */ +gboolean utils_utf8_substring_match(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; + + g_return_val_if_fail(key != NULL, FALSE); + g_return_val_if_fail(haystack != NULL, FALSE); + + 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; +} + + /** * A replacement function for g_strncasecmp() to compare strings case-insensitive. * It converts both strings into lowercase using g_utf8_strdown() and then compare
Modified: src/utils.h 2 lines changed, 2 insertions(+), 0 deletions(-) =================================================================== @@ -338,6 +338,8 @@ gchar *utils_get_os_info_string(void);
gchar *utils_utf8_strdown(const gchar *str);
+gboolean utils_utf8_substring_match(const gchar *key, const gchar *haystack); + #endif /* GEANY_PRIVATE */
G_END_DECLS
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).