diff -ru a/src/build.c b/src/build.c --- a/src/build.c 2021-10-09 18:02:14.000000000 +0300 +++ b/src/build.c 2022-07-21 03:20:14.464751826 +0300 @@ -190,9 +190,8 @@ { GeanyKeyBinding *kb = keybindings_get_item(group, kb_id); - if (kb->key != 0) - gtk_widget_add_accelerator(menuitem, "activate", accel_group, - kb->key, kb->mods, GTK_ACCEL_VISIBLE); + geany_widget_add_accelerators(menuitem, "activate", accel_group, + kb->combo, GTK_ACCEL_VISIBLE); } diff -ru a/src/editor.c b/src/editor.c --- a/src/editor.c 2021-10-09 18:02:14.000000000 +0300 +++ b/src/editor.c 2022-07-21 03:20:14.465751829 +0300 @@ -2665,8 +2665,8 @@ if (sci_has_selection(sci)) return FALSE; /* return if we are editing an existing line (chars on right of cursor) */ - if (keybindings_lookup_item(GEANY_KEY_GROUP_EDITOR, - GEANY_KEYS_EDITOR_COMPLETESNIPPET)->key == GDK_KEY_space && + if (keybindings_has_key(keybindings_lookup_item(GEANY_KEY_GROUP_EDITOR, + GEANY_KEYS_EDITOR_COMPLETESNIPPET), GDK_KEY_space) && ! editor_prefs.complete_snippets_whilst_editing && ! at_eol(sci, pos)) return FALSE; diff -ru a/src/keybindings.c b/src/keybindings.c --- a/src/keybindings.c 2021-10-09 18:02:14.000000000 +0300 +++ b/src/keybindings.c 2022-07-22 01:08:49.405221457 +0300 @@ -191,10 +191,15 @@ kb->name = (gchar *)kf_name; kb->label = (gchar *)label; } - kb->key = key; - kb->mods = mod; - kb->default_key = key; - kb->default_mods = mod; + kb->combo[0].key = key; + kb->combo[0].mods = mod; + for (gint c = 1; c < GEANY_MAX_KEY_BINDING_COMBOS; ++ c) + { + kb->combo[c].key = 0; + kb->combo[c].mods = 0; + } + kb->default_combo.key = key; + kb->default_combo.mods = mod; kb->callback = callback; kb->cb_func = NULL; kb->cb_data = NULL; @@ -776,13 +781,22 @@ guint key; GdkModifierType mods; - val = g_key_file_get_string(config, group->name, kb->name, NULL); - if (val != NULL) + for (gint c = 0; c < GEANY_MAX_KEY_BINDING_COMBOS; ++ c) { - gtk_accelerator_parse(val, &key, &mods); - kb->key = key; - kb->mods = mods; - g_free(val); + GString * name = g_string_new(kb->name); + if (c > 0) + { + g_string_append_printf(name, "-%d", c); + } + val = g_key_file_get_string(config, group->name, name->str, NULL); + if (val != NULL) + { + gtk_accelerator_parse(val, &key, &mods); + kb->combo[c].key = key; + kb->combo[c].mods = mods; + g_free(val); + } + g_string_free(name, TRUE); } } @@ -821,11 +835,8 @@ static void apply_kb_accel(GeanyKeyGroup *group, GeanyKeyBinding *kb, gpointer user_data) { - if (kb->key != 0 && kb->menu_item) - { - gtk_widget_add_accelerator(kb->menu_item, "activate", kb_accel_group, - kb->key, kb->mods, GTK_ACCEL_VISIBLE); - } + geany_widget_add_accelerators(kb->menu_item, "activate", kb_accel_group, + kb->combo, GTK_ACCEL_VISIBLE); } @@ -853,9 +864,8 @@ { GeanyKeyBinding *kb = keybindings_get_item(group, kb_id); - if (kb->key != 0) - gtk_widget_add_accelerator(menuitem, "activate", kb_accel_group, - kb->key, kb->mods, GTK_ACCEL_VISIBLE); + geany_widget_add_accelerators(menuitem, "activate", kb_accel_group, + kb->combo, GTK_ACCEL_VISIBLE); } @@ -905,9 +915,21 @@ GKeyFile *config = user_data; gchar *val; - val = gtk_accelerator_name(kb->key, kb->mods); - g_key_file_set_string(config, group->name, kb->name, val); - g_free(val); + for (gint c = 0; c < GEANY_MAX_KEY_BINDING_COMBOS; ++ c) + { + if (c == 0 || kb->combo[c].key != 0) + { + val = gtk_accelerator_name(kb->combo[c].key, kb->combo[c].mods); + GString * name = g_string_new(kb->name); + if (c > 0) + { + g_string_append_printf(name, "-%d", c); + } + g_key_file_set_string(config, group->name, name->str, val); + g_string_free(name, TRUE); + g_free(val); + } + } } @@ -948,6 +970,16 @@ return utils_str_remove_chars(g_strdup(kb->label), "_"); } +gboolean keybindings_has_key(GeanyKeyBinding *kb, guint key) +{ + for (gint c = 0; c < GEANY_MAX_KEY_BINDING_COMBOS; ++ c) + { + if (kb->combo[c].key == key) { + return TRUE; + } + } + return FALSE; +} static void fill_shortcut_labels_treeview(GtkWidget *tree) { @@ -971,15 +1003,24 @@ foreach_ptr_array(kb, i, group->key_items) { - gchar *shortcut, *label; - - label = keybindings_get_label(kb); - shortcut = gtk_accelerator_get_label(kb->key, kb->mods); - + gchar * label = keybindings_get_label(kb); + GString * shortcuts = g_string_new(NULL); + for (gint c = 0; c < GEANY_MAX_KEY_BINDING_COMBOS; ++ c) + { + if (kb->combo[c].key != 0) + { + if (shortcuts->len > 0) + { + g_string_append(shortcuts, _(" or ")); + } + gchar * shortcut = gtk_accelerator_get_label(kb->combo[c].key, kb->combo[c].mods); + g_string_append(shortcuts, shortcut); + g_free(shortcut); + } + } gtk_list_store_append(store, &iter); - gtk_list_store_set(store, &iter, 0, label, 1, shortcut, 2, PANGO_WEIGHT_NORMAL, -1); - - g_free(shortcut); + gtk_list_store_set(store, &iter, 0, label, 1, shortcuts->str, 2, PANGO_WEIGHT_NORMAL, -1); + g_string_free(shortcuts, TRUE); g_free(label); } } @@ -1248,8 +1289,11 @@ group = keybindings_get_core_group(GEANY_KEY_GROUP_FOCUS); foreach_ptr_array(kb, i, group->key_items) { - if (state == kb->mods && keyval == kb->key) - return FALSE; + for (gint c = 0; c < GEANY_MAX_KEY_BINDING_COMBOS; ++ c) + { + if (state == kb->combo[c].mods && keyval == kb->combo[c].key) + return FALSE; + } } /* Temporarily disable the menus to prevent conflicting menu accelerators @@ -1316,7 +1360,15 @@ if (keyval >= GDK_KEY_KP_Space && keyval < GDK_KEY_KP_Equal) keyval = key_kp_translate(keyval); - return (keyval == kb->key && state == kb->mods); + for (gint c = 0; c < GEANY_MAX_KEY_BINDING_COMBOS; ++ c) + { + if (keyval == kb->combo[c].key && state == kb->combo[c].mods) + { + return TRUE; + }; + } + + return FALSE; } @@ -1389,10 +1441,13 @@ { foreach_ptr_array(kb, i, group->key_items) { - if (keyval == kb->key && state == kb->mods) + for (gint c = 0; c < GEANY_MAX_KEY_BINDING_COMBOS; ++ c) { - if (run_kb(kb, group)) - return TRUE; + if (keyval == kb->combo[c].key && state == kb->combo[c].mods) + { + if (run_kb(kb, group)) + return TRUE; + } } } } @@ -2163,18 +2218,13 @@ { GeanyKeyBinding *kb = keybindings_lookup_item(GEANY_KEY_GROUP_EDITOR, GEANY_KEYS_EDITOR_COMPLETESNIPPET); - - switch (kb->key) + if (keybindings_has_key(kb, GDK_KEY_space)) { - case GDK_KEY_space: - sci_add_text(doc->editor->sci, " "); - break; - case GDK_KEY_Tab: - sci_send_command(doc->editor->sci, SCI_TAB); - break; - default: - break; - } + sci_add_text(doc->editor->sci, " "); + } else if (keybindings_has_key(kb, GDK_KEY_Tab)) + { + sci_send_command(doc->editor->sci, SCI_TAB); + }; break; } case GEANY_KEYS_EDITOR_WORDPARTCOMPLETION: @@ -2641,19 +2691,17 @@ /* update key combination */ -void keybindings_update_combo(GeanyKeyBinding *kb, guint key, GdkModifierType mods) +void keybindings_update_combo(GeanyKeyBinding *kb, gint c, guint key, GdkModifierType mods) { GtkWidget *widget = kb->menu_item; - if (widget && kb->key) - gtk_widget_remove_accelerator(widget, kb_accel_group, kb->key, kb->mods); + geany_widget_remove_accelerator(widget, kb_accel_group, kb->combo[c]); - kb->key = key; - kb->mods = mods; + kb->combo[c].key = key; + kb->combo[c].mods = mods; - if (widget && kb->key) - gtk_widget_add_accelerator(widget, "activate", kb_accel_group, - kb->key, kb->mods, GTK_ACCEL_VISIBLE); + geany_widget_add_accelerator(widget, "activate", kb_accel_group, + kb->combo[c], GTK_ACCEL_VISIBLE); } @@ -2686,3 +2734,44 @@ { g_ptr_array_remove_fast(keybinding_groups, group); } + +void +geany_widget_add_accelerator( + GtkWidget* widget, + const gchar* accel_signal, + GtkAccelGroup* accel_group, + GeanyKeyCombo combo, + GtkAccelFlags accel_flags +) { + if (widget != NULL && combo.key != 0) + { + gtk_widget_add_accelerator(widget, accel_signal, accel_group, combo.key, combo.mods, accel_flags); + } +} + +void +geany_widget_add_accelerators( + GtkWidget* widget, + const gchar* accel_signal, + GtkAccelGroup* accel_group, + GeanyKeyCombo combos[], + GtkAccelFlags accel_flags +) { + + for (gint c = 0; c < GEANY_MAX_KEY_BINDING_COMBOS; ++ c) + { + geany_widget_add_accelerator(widget, accel_signal, accel_group, combos[c], accel_flags); + } +} + +void +geany_widget_remove_accelerator( + GtkWidget* widget, + GtkAccelGroup* accel_group, + GeanyKeyCombo combo +) { + if (widget != NULL && combo.key != 0) + { + gtk_widget_remove_accelerator(widget, accel_group, combo.key, combo.mods); + } +} diff -ru a/src/keybindings.h b/src/keybindings.h --- a/src/keybindings.h 2021-10-09 18:02:14.000000000 +0300 +++ b/src/keybindings.h 2022-07-21 03:20:14.466751831 +0300 @@ -68,13 +68,21 @@ * @since 1.26 (API 226) */ typedef gboolean (*GeanyKeyBindingFunc)(GeanyKeyBinding *key, guint key_id, gpointer user_data); +struct GeanyKeyCombo +{ + guint key; /**< Key value in lower-case, such as @c GDK_KEY_a or 0 */ + GdkModifierType mods; /**< Modifier keys, such as @c GDK_CONTROL_MASK or 0 */ +}; +typedef struct GeanyKeyCombo GeanyKeyCombo; + +#define GEANY_MAX_KEY_BINDING_COMBOS 2 + /** Represents a single keybinding action. * * Use keybindings_set_item() to set. */ struct GeanyKeyBinding { - guint key; /**< Key value in lower-case, such as @c GDK_KEY_a or 0 */ - GdkModifierType mods; /**< Modifier keys, such as @c GDK_CONTROL_MASK or 0 */ + GeanyKeyCombo combo[GEANY_MAX_KEY_BINDING_COMBOS]; gchar *name; /**< Key name for the configuration file, such as @c "menu_new" */ /** Label used in the preferences dialog keybindings tab. * May contain underscores - these won't be displayed. */ @@ -84,8 +92,7 @@ GeanyKeyCallback callback; GtkWidget *menu_item; /**< Optional widget to set an accelerator for, or @c NULL */ guint id; - guint default_key; - GdkModifierType default_mods; + GeanyKeyCombo default_combo; GeanyKeyBindingFunc cb_func; gpointer cb_data; GDestroyNotify cb_data_destroy; @@ -316,7 +323,9 @@ gchar *keybindings_get_label(GeanyKeyBinding *kb); -void keybindings_update_combo(GeanyKeyBinding *kb, guint key, GdkModifierType mods); +gboolean keybindings_has_key(GeanyKeyBinding *kb, guint key); + +void keybindings_update_combo(GeanyKeyBinding *kb, gint c, guint key, GdkModifierType mods); GeanyKeyBinding *keybindings_lookup_item(guint group_id, guint key_id); @@ -331,6 +340,31 @@ #endif /* GEANY_PRIVATE */ +void +geany_widget_add_accelerator( + GtkWidget* widget, + const gchar* accel_signal, + GtkAccelGroup* accel_group, + GeanyKeyCombo combo, + GtkAccelFlags accel_flags +); + +void +geany_widget_add_accelerators( + GtkWidget* widget, + const gchar* accel_signal, + GtkAccelGroup* accel_group, + GeanyKeyCombo combos[], + GtkAccelFlags accel_flags +); + +void +geany_widget_remove_accelerator( + GtkWidget* widget, + GtkAccelGroup* accel_group, + GeanyKeyCombo combo +); + G_END_DECLS #endif /* GEANY_KEYBINDINGS_H */ diff -ru a/src/prefs.c b/src/prefs.c --- a/src/prefs.c 2021-10-09 18:02:14.000000000 +0300 +++ b/src/prefs.c 2022-07-21 03:29:42.242069760 +0300 @@ -139,6 +139,7 @@ KB_TREE_ACTION, KB_TREE_SHORTCUT, KB_TREE_INDEX, + KB_TREE_COMBO, KB_TREE_EDITABLE, KB_TREE_WEIGHT, KB_TREE_COLUMNS @@ -282,7 +283,7 @@ 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); + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT, 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_object_unref(kbdata->store); @@ -327,7 +328,7 @@ gtk_tree_model_iter_parent(GTK_TREE_MODEL(store), &parent, iter); gtk_tree_model_get(GTK_TREE_MODEL(store), &parent, KB_TREE_INDEX, &gid, -1); kb = kb_index(gid, kid); - bold = key != kb->default_key || mods != kb->default_mods; + bold = key != kb->default_combo.key || mods != kb->default_combo.mods; gtk_tree_store_set(store, iter, KB_TREE_WEIGHT, bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, -1); } @@ -365,7 +366,6 @@ { GtkTreeIter parent, iter; gsize g, i; - gchar *label; GeanyKeyGroup *group; GeanyKeyBinding *kb; @@ -380,12 +380,18 @@ 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_set_shortcut(kbdata->store, &iter, kb->key, kb->mods); - g_free(label); + for (int i = 0; i < GEANY_MAX_KEY_BINDING_COMBOS; ++ i) + { + if (i == 0 || kb->combo[i].key != 0) + { + gchar * label = i == 0 ? keybindings_get_label(kb) : g_strdup("\tor"); + 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, KB_TREE_COMBO, i, -1); + kb_set_shortcut(kbdata->store, &iter, kb->combo[i].key, kb->combo[i].mods); + g_free(label); + } + }; } } gtk_tree_view_expand_all(GTK_TREE_VIEW(kbdata->tree)); @@ -837,17 +843,18 @@ while (TRUE) { guint kid; + gint c; gchar *str; guint key; GdkModifierType mods; GeanyKeyBinding *kb; - gtk_tree_model_get(model, &child, KB_TREE_INDEX, &kid, KB_TREE_SHORTCUT, &str, -1); + gtk_tree_model_get(model, &child, KB_TREE_INDEX, &kid, KB_TREE_COMBO, &c, KB_TREE_SHORTCUT, &str, -1); gtk_accelerator_parse(str, &key, &mods); g_free(str); kb = kb_index(gid, kid); - if (kb->key != key || kb->mods != mods) - keybindings_update_combo(kb, key, mods); + if (kb->combo[c].key != key || kb->combo[c].mods != mods) + keybindings_update_combo(kb, c, key, mods); if (! gtk_tree_model_iter_next(model, &child)) break; diff -ru a/src/tools.c b/src/tools.c --- a/src/tools.c 2021-10-09 18:02:15.000000000 +0300 +++ b/src/tools.c 2022-07-21 03:20:14.467751834 +0300 @@ -565,11 +565,8 @@ { GeanyKeyBinding *kb = keybindings_lookup_item(GEANY_KEY_GROUP_FORMAT, key_idx); - if (kb->key > 0) - { - gtk_widget_add_accelerator(item, "activate", gtk_accel_group_new(), - kb->key, kb->mods, GTK_ACCEL_VISIBLE); - } + geany_widget_add_accelerators(item, "activate", gtk_accel_group_new(), + kb->combo, GTK_ACCEL_VISIBLE); } gtk_container_add(GTK_CONTAINER(me), item); gtk_widget_show(item);