SF.net SVN: geany: [2324] trunk

ntrel at users.sourceforge.net ntrel at xxxxx
Wed Mar 12 17:07:51 UTC 2008


Revision: 2324
          http://geany.svn.sourceforge.net/geany/?rev=2324&view=rev
Author:   ntrel
Date:     2008-03-12 10:07:43 -0700 (Wed, 12 Mar 2008)

Log Message:
-----------
Merge plugin-keybindings branch:
Change keybindings code to use keybinding groups, each with a
separate enum set, which will make the plugin ABI more stable, and
lay the groundwork for plugin keybindings support.
Note: this breaks the plugin API.
Rename keybindings_cmd() to keybindings_send_command(), which now
takes a group_id argument.
Add keybindings_lookup_item().
Add documentation for keybindings structs.
Add support for plugin keybindings using the PLUGIN_KEY_GROUP()
macro.
Add a plugin keybinding for 'Insert Special HTML Characters'.
Rename KBCallback, cmd_id, cb_func with clearer names.
Add KeyBinding::menu_item field for setting accelerators (currently
does nothing).
Load keybindings keyfile after plugins have loaded at startup, so
their shortcuts are also loaded. Still to do: loading individual
plugin keybindings after startup.
Group most keybinding callbacks together according to keybinding
group.
Add keybindings_set_item() and add it to the plugin API.
Set main menu widget accels with keybindings_set_item() - they are
ignored for now. Popup menu accels are set separately.

Modified Paths:
--------------
    trunk/ChangeLog
    trunk/doc/plugins.dox
    trunk/plugins/filebrowser.c
    trunk/plugins/htmlchars.c
    trunk/src/build.c
    trunk/src/callbacks.c
    trunk/src/keybindings.c
    trunk/src/keybindings.h
    trunk/src/main.c
    trunk/src/plugindata.h
    trunk/src/plugins.c
    trunk/src/prefs.c
    trunk/src/tools.c

Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/ChangeLog	2008-03-12 17:07:43 UTC (rev 2324)
@@ -1,3 +1,34 @@
+2008-03-12  Nick Treleaven  <nick(dot)treleaven(at)btinternet(dot)com>
+
+ * src/build.c, src/keybindings.c, src/keybindings.h,
+   src/tools.c, src/prefs.c, src/plugindata.h, src/callbacks.c,
+   src/plugins.c, src/main.c, doc/plugins.dox, plugins/filebrowser.c,
+   plugins/htmlchars.c:
+   Merge plugin-keybindings branch:
+   Change keybindings code to use keybinding groups, each with a
+   separate enum set, which will make the plugin ABI more stable, and
+   lay the groundwork for plugin keybindings support.
+   Note: this breaks the plugin API.
+   Rename keybindings_cmd() to keybindings_send_command(), which now
+   takes a group_id argument.
+   Add keybindings_lookup_item().
+   Add documentation for keybindings structs.
+   Add support for plugin keybindings using the PLUGIN_KEY_GROUP()
+   macro.
+   Add a plugin keybinding for 'Insert Special HTML Characters'.
+   Rename KBCallback, cmd_id, cb_func with clearer names.
+   Add KeyBinding::menu_item field for setting accelerators (currently
+   does nothing).
+   Load keybindings keyfile after plugins have loaded at startup, so
+   their shortcuts are also loaded. Still to do: loading individual
+   plugin keybindings after startup.
+   Group most keybinding callbacks together according to keybinding
+   group.
+   Add keybindings_set_item() and add it to the plugin API.
+   Set main menu widget accels with keybindings_set_item() - they are
+   ignored for now. Popup menu accels are set separately.
+
+
 2008-03-09  Enrico Tröger  <enrico(dot)troeger(at)uvena(dot)de>
 
  * src/document.c:

Modified: trunk/doc/plugins.dox
===================================================================
--- trunk/doc/plugins.dox	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/doc/plugins.dox	2008-03-12 17:07:43 UTC (rev 2324)
@@ -49,10 +49,10 @@
  *  are optional, i.e. they can be omitted, others are required and must be defined.
  *
  *  - @code version_check() @endcode
- * 	  Use VERSION_CHECK() macro instead. Required by Geany.
+ * 	  Use the VERSION_CHECK() macro instead. Required by Geany.
  *
  *  - @code PluginInfo* info() @endcode
- * 	  Use PLUGIN_INFO() macro to define it. Required by Geany.
+ * 	  Use the PLUGIN_INFO() macro to define it. Required by Geany.
  *
  *  - @code GeanyData* geany_data @endcode
  * 	  Geany owned fields and functions.
@@ -64,6 +64,9 @@
  * 	  An array for connecting GeanyObject events, which should be terminated with
  * 	  {NULL, NULL, FALSE, NULL}. See @link signals Signal documentation @endlink.
  *
+ *  - @code KeyBindingGroup plugin_key_group[1] @endcode
+ * 	  Use the PLUGIN_KEY_GROUP() macro to define it.
+ *
  *  - @code void configure(GtkWidget *parent) @endcode
  * 	  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.
@@ -84,7 +87,7 @@
  *
  *  To use plugin signals in Geany, you simply create a GeanyCallback array, list the signals
  *  you want to listen to and create the appropiate signal callbacks for each signal.
- *  @note The GeanyCallback array has be ended with a final NULL entry.
+ *  @note The GeanyCallback array has to be ended with a final NULL entry.
  *
  *  The following code demonstrates how to use signals in Geany plugins. The code can be inserted
  *  in your plugin code at any desired position.
@@ -193,7 +196,7 @@
  *  @section intro Introduction
  *
  *  Since Geany 0.12 there is a plugin interface to extend Geany's functionality and
- *  add new features. This %document gives a briefly overview about how to add new
+ *  add new features. This %document gives a brief overview about how to add new
  *  plugins by writing a simple "Hello World" plugin in C.
  *
  *

Modified: trunk/plugins/filebrowser.c
===================================================================
--- trunk/plugins/filebrowser.c	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/plugins/filebrowser.c	2008-03-12 17:07:43 UTC (rev 2324)
@@ -495,6 +495,12 @@
 }
 
 
+static void on_hide_sidebar(void)
+{
+	p_keybindings->send_command(GEANY_KEY_GROUP_VIEW, GEANY_KEYS_VIEW_SIDEBAR);
+}
+
+
 static GtkWidget *create_popup_menu(void)
 {
 	GtkWidget *item, *menu, *image;
@@ -545,9 +551,7 @@
 		gtk_image_new_from_stock("gtk-close", GTK_ICON_SIZE_MENU));
 	gtk_widget_show(item);
 	gtk_container_add(GTK_CONTAINER(menu), item);
-	g_signal_connect_swapped((gpointer) item, "activate",
-		G_CALLBACK(p_keybindings->send_command),
-		GINT_TO_POINTER(GEANY_KEYS_MENU_SIDEBAR));
+	g_signal_connect((gpointer) item, "activate", G_CALLBACK(on_hide_sidebar), NULL);
 
 	return menu;
 }

Modified: trunk/plugins/htmlchars.c
===================================================================
--- trunk/plugins/htmlchars.c	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/plugins/htmlchars.c	2008-03-12 17:07:43 UTC (rev 2324)
@@ -28,6 +28,7 @@
 #include "support.h"
 #include "plugindata.h"
 #include "document.h"
+#include "keybindings.h"
 #include "pluginmacros.h"
 
 
@@ -41,8 +42,18 @@
 	_("The Geany developer team"))
 
 
+/* Keybinding(s) */
 enum
 {
+	KB_INSERT_HTML_CHARS,
+	KB_COUNT
+};
+
+PLUGIN_KEY_GROUP(html_chars, KB_COUNT)
+
+
+enum
+{
 	COLUMN_CHARACTER,
 	COLUMN_HTML_NAME,
 	N_COLUMNS
@@ -503,13 +514,20 @@
 }
 
 
+static void kb_activate(G_GNUC_UNUSED guint key_id)
+{
+	item_activate(NULL, NULL);
+}
+
+
 /* Called by Geany to initialize the plugin */
 void init(GeanyData *data)
 {
 	GtkWidget *demo_item;
+	const gchar *menu_text = _("_Insert Special HTML Characters");
 
 	/* Add an item to the Tools menu */
-	demo_item = gtk_menu_item_new_with_mnemonic(_("_Insert Special HTML Characters"));
+	demo_item = gtk_menu_item_new_with_mnemonic(menu_text);
 	gtk_widget_show(demo_item);
 	gtk_container_add(GTK_CONTAINER(geany_data->tools_menu), demo_item);
 	g_signal_connect(G_OBJECT(demo_item), "activate", G_CALLBACK(item_activate), NULL);
@@ -517,6 +535,10 @@
 	/* disable menu_item when there are no documents open */
 	plugin_fields->menu_item = demo_item;
 	plugin_fields->flags = PLUGIN_IS_DOCUMENT_SENSITIVE;
+
+	/* setup keybindings */
+	p_keybindings->set_item(plugin_key_group, KB_INSERT_HTML_CHARS, kb_activate,
+		0, 0, "insert_html_chars", menu_text, demo_item);
 }
 
 

Modified: trunk/src/build.c
===================================================================
--- trunk/src/build.c	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/src/build.c	2008-03-12 17:07:43 UTC (rev 2324)
@@ -1087,16 +1087,29 @@
 }
 
 
-#define GEANY_ADD_WIDGET_ACCEL(gkey, menuitem) \
-	if (keys[(gkey)]->key != 0) \
-		gtk_widget_add_accelerator(menuitem, "activate", accel_group, \
-			keys[(gkey)]->key, keys[(gkey)]->mods, GTK_ACCEL_VISIBLE)
+/* note: copied from keybindings.c.
+ * Perhaps the separate Tex menu could be merged with the default build menu?
+ * Then this could be done with Glade and set the accels in keybindings.c. */
+static void add_menu_accel(KeyBindingGroup *group, guint kb_id,
+	GtkAccelGroup *accel_group, GtkWidget *menuitem)
+{
+	KeyBinding *kb = &group->keys[kb_id];
 
+	if (kb->key != 0)
+		gtk_widget_add_accelerator(menuitem, "activate", accel_group,
+			kb->key, kb->mods, GTK_ACCEL_VISIBLE);
+}
+
+
+#define GEANY_ADD_WIDGET_ACCEL(kb_id, menuitem) \
+	add_menu_accel(group, kb_id, accel_group, menuitem)
+
 static void create_build_menu_gen(BuildMenuItems *menu_items)
 {
 	GtkWidget *menu, *item = NULL, *image, *separator;
 	GtkAccelGroup *accel_group = gtk_accel_group_new();
 	GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips"));
+	KeyBindingGroup *group = g_ptr_array_index(keybinding_groups, GEANY_KEY_GROUP_BUILD);
 
 	menu = gtk_menu_new();
 
@@ -1213,6 +1226,7 @@
 	GtkWidget *menu, *item, *image, *separator;
 	GtkAccelGroup *accel_group = gtk_accel_group_new();
 	GtkTooltips *tooltips = GTK_TOOLTIPS(lookup_widget(app->window, "tooltips"));
+	KeyBindingGroup *group = g_ptr_array_index(keybinding_groups, GEANY_KEY_GROUP_BUILD);
 
 	menu = gtk_menu_new();
 

Modified: trunk/src/callbacks.c
===================================================================
--- trunk/src/callbacks.c	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/src/callbacks.c	2008-03-12 17:07:43 UTC (rev 2324)
@@ -654,7 +654,7 @@
 {
 	if (event->keyval == GDK_Escape)
 	{
-		keybindings_cmd(GEANY_KEYS_SWITCH_EDITOR);
+		keybindings_send_command(GEANY_KEY_GROUP_FOCUS, GEANY_KEYS_FOCUS_EDITOR);
 		return TRUE;
 	}
 	return FALSE;
@@ -925,7 +925,7 @@
 
 	if (! sci_can_copy(sci))
 	{
-		keybindings_cmd(GEANY_KEYS_EDIT_SELECTWORD);
+		keybindings_send_command(GEANY_KEY_GROUP_SELECT, GEANY_KEYS_SELECT_WORD);
 		keep_sel = FALSE;
 	}
 
@@ -1133,7 +1133,7 @@
 on_compile_button_clicked              (GtkToolButton   *toolbutton,
                                         gpointer         user_data)
 {
-	keybindings_cmd(GEANY_KEYS_BUILD_COMPILE);
+	keybindings_send_command(GEANY_KEY_GROUP_BUILD, GEANY_KEYS_BUILD_COMPILE);
 }
 
 
@@ -1581,7 +1581,7 @@
 on_run_button_clicked                  (GtkToolButton   *toolbutton,
                                         gpointer         user_data)
 {
-	keybindings_cmd(GEANY_KEYS_BUILD_RUN);
+	keybindings_send_command(GEANY_KEY_GROUP_BUILD, GEANY_KEYS_BUILD_RUN);
 }
 
 
@@ -1737,7 +1737,7 @@
 on_menu_duplicate_line1_activate       (GtkMenuItem     *menuitem,
                                         gpointer         user_data)
 {
-	keybindings_cmd(GEANY_KEYS_EDIT_DUPLICATELINE);
+	keybindings_send_command(GEANY_KEY_GROUP_EDITOR, GEANY_KEYS_EDITOR_DUPLICATELINE);
 }
 
 

Modified: trunk/src/keybindings.c
===================================================================
--- trunk/src/keybindings.c	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/src/keybindings.c	2008-03-12 17:07:43 UTC (rev 2324)
@@ -46,28 +46,31 @@
 #include "vte.h"
 
 
-KeyBinding *keys[GEANY_MAX_KEYS];
+GPtrArray *keybinding_groups;	/* array of KeyBindingGroup pointers */
 
+/* keyfile group name for non-plugin KB groups */
+static const gchar default_group_name[] = "Bindings";
+
 static const gboolean swap_alt_tab_order = FALSE;
 
 
-/* simple convenience function to allocate and fill the struct */
-static KeyBinding *fill(KBCallback func, guint key, GdkModifierType mod, const gchar *name,
-		const gchar *label);
+static gboolean check_current_word(void);
 
 static void cb_func_file_action(guint key_id);
-static void cb_func_menu_print(guint key_id);
+static void cb_func_project_action(guint key_id);
+static void cb_func_editor_action(guint key_id);
+static void cb_func_select_action(guint key_id);
+static void cb_func_format_action(guint key_id);
+static void cb_func_insert_action(guint key_id);
+static void cb_func_search_action(guint key_id);
+static void cb_func_goto_action(guint key_id);
+static void cb_func_clipboard(guint key_id);
+static void cb_func_build_action(guint key_id);
 
-static void cb_func_menu_undo(guint key_id);
-static void cb_func_menu_redo(guint key_id);
-static void cb_func_clipboard(guint key_id);
-static void cb_func_menu_selectall(guint key_id);
+/* TODO: refactor individual callbacks per group */
 static void cb_func_menu_help(guint key_id);
 static void cb_func_menu_preferences(guint key_id);
-static void cb_func_menu_insert_date(guint key_id);
 
-static void cb_func_menu_search(guint key_id);
-
 static void cb_func_menu_toggle_all(guint key_id);
 static void cb_func_menu_fullscreen(guint key_id);
 static void cb_func_menu_messagewindow(guint key_id);
@@ -80,10 +83,7 @@
 static void cb_func_reloadtaglist(guint key_id);
 
 static void cb_func_menu_opencolorchooser(guint key_id);
-static void cb_func_menu_insert_specialchars(guint key_id);
 
-static void cb_func_build_action(guint key_id);
-
 static void cb_func_switch_editor(guint key_id);
 static void cb_func_switch_scribble(guint key_id);
 static void cb_func_switch_vte(guint key_id);
@@ -92,270 +92,402 @@
 static void cb_func_switch_tabright(guint key_id);
 static void cb_func_switch_tablastused(guint key_id);
 static void cb_func_move_tab(guint key_id);
-static void cb_func_nav_back(guint key_id);
-static void cb_func_nav_forward(guint key_id);
 static void cb_func_toggle_sidebar(guint key_id);
 
-/* common function for editing keybindings, only valid when scintilla has focus. */
-static void cb_func_edit(guint key_id);
+static void add_popup_menu_accels(void);
 
-/* common function for global editing keybindings. */
-static void cb_func_edit_global(guint key_id);
 
-/* common function for keybindings using current word */
-static void cb_func_current_word(guint key_id);
+/** Simple convenience function to fill a KeyBinding struct item */
+void keybindings_set_item(KeyBindingGroup *group, gsize key_id,
+		KeyCallback callback, guint key, GdkModifierType mod,
+		const gchar *name, const gchar *label, GtkWidget *menu_item)
+{
+	KeyBinding *kb;
 
-static void add_menu_accels(void);
+	g_assert(key_id < group->count);
 
+	kb = &group->keys[key_id];
 
+	kb->name = name;
+	kb->label = label;
+	kb->key = key;
+	kb->mods = mod;
+	kb->callback = callback;
+	kb->menu_item = menu_item;
+}
+
+
+static KeyBindingGroup *add_kb_group(KeyBindingGroup *group,
+		const gchar *name, const gchar *label, gsize count, KeyBinding *keys)
+{
+	g_ptr_array_add(keybinding_groups, group);
+
+	group->name = name;
+	group->label = label;
+	group->count = count;
+	group->keys = keys;
+	return group;
+}
+
+
+/* Lookup a widget in the main window */
+#define LW(widget_name) \
+	lookup_widget(app->window, G_STRINGIFY(widget_name))
+
+/* Expansion for group_id = FILE:
+ * static KeyBinding FILE_keys[GEANY_KEYS_FILE_COUNT]; */
+#define DECLARE_KEYS(group_id) \
+	static KeyBinding group_id ## _keys[GEANY_KEYS_ ## group_id ## _COUNT]
+
+/* Expansion for group_id = FILE:
+ * add_kb_group(&groups[GEANY_KEY_GROUP_FILE], NULL, _("File menu"),
+ * 	GEANY_KEYS_FILE_COUNT, FILE_keys); */
+#define ADD_KB_GROUP(group_id, label) \
+	add_kb_group(&groups[GEANY_KEY_GROUP_ ## group_id], default_group_name, label, \
+		GEANY_KEYS_ ## group_id ## _COUNT, group_id ## _keys)
+
+/* Init all fields of keys with default values.
+ * The menu_item field is always the main menu item, popup menu accelerators are
+ * set in add_popup_menu_accels(). */
 static void init_default_kb(void)
 {
-	/* init all fields of keys with default values */
-	keys[GEANY_KEYS_MENU_NEW] = fill(cb_func_file_action,
-		GDK_n, GDK_CONTROL_MASK, "menu_new", _("New"));
-	keys[GEANY_KEYS_MENU_OPEN] = fill(cb_func_file_action,
-		GDK_o, GDK_CONTROL_MASK, "menu_open", _("Open"));
-	keys[GEANY_KEYS_MENU_OPENSELECTED] = fill(cb_func_file_action,
-		GDK_o, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "menu_open_selected", _("Open selected file"));
-	keys[GEANY_KEYS_MENU_SAVE] = fill(cb_func_file_action,
-		GDK_s, GDK_CONTROL_MASK, "menu_save", _("Save"));
-	keys[GEANY_KEYS_MENU_SAVEAS] = fill(cb_func_file_action,
-		0, 0, "menu_saveas", _("Save as"));
-	keys[GEANY_KEYS_MENU_SAVEALL] = fill(cb_func_file_action,
-		GDK_S, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "menu_saveall", _("Save all"));
-	keys[GEANY_KEYS_MENU_PRINT] = fill(cb_func_menu_print,
-		GDK_p, GDK_CONTROL_MASK, "menu_print", _("Print"));
-	keys[GEANY_KEYS_MENU_CLOSE] = fill(cb_func_file_action,
-		GDK_w, GDK_CONTROL_MASK, "menu_close", _("Close"));
-	keys[GEANY_KEYS_MENU_CLOSEALL] = fill(cb_func_file_action,
-		GDK_w, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "menu_closeall", _("Close all"));
-	keys[GEANY_KEYS_MENU_RELOADFILE] = fill(cb_func_file_action,
-		GDK_r, GDK_CONTROL_MASK, "menu_reloadfile", _("Reload file"));
-	keys[GEANY_KEYS_MENU_PROJECTPROPERTIES] = fill(cb_func_file_action,
-		0, 0, "project_properties", _("Project properties"));
+	static KeyBindingGroup groups[GEANY_KEY_GROUP_COUNT];
+	KeyBindingGroup *group;
+	DECLARE_KEYS(FILE);
+	DECLARE_KEYS(PROJECT);
+	DECLARE_KEYS(EDITOR);
+	DECLARE_KEYS(CLIPBOARD);
+	DECLARE_KEYS(SELECT);
+	DECLARE_KEYS(FORMAT);
+	DECLARE_KEYS(INSERT);
+	DECLARE_KEYS(SETTINGS);
+	DECLARE_KEYS(SEARCH);
+	DECLARE_KEYS(GOTO);
+	DECLARE_KEYS(VIEW);
+	DECLARE_KEYS(FOCUS);
+	DECLARE_KEYS(NOTEBOOK);
+	DECLARE_KEYS(DOCUMENT);
+	DECLARE_KEYS(BUILD);
+	DECLARE_KEYS(TOOLS);
+	DECLARE_KEYS(HELP);
 
-	keys[GEANY_KEYS_MENU_UNDO] = fill(cb_func_menu_undo,
-		GDK_z, GDK_CONTROL_MASK, "menu_undo", _("Undo"));
-	keys[GEANY_KEYS_MENU_REDO] = fill(cb_func_menu_redo,
-		GDK_y, GDK_CONTROL_MASK, "menu_redo", _("Redo"));
-	keys[GEANY_KEYS_MENU_CUT] = fill(cb_func_clipboard,
-		GDK_x, GDK_CONTROL_MASK, "menu_cut", _("Cut"));
-	keys[GEANY_KEYS_MENU_COPY] = fill(cb_func_clipboard,
-		GDK_c, GDK_CONTROL_MASK, "menu_copy", _("Copy"));
-	keys[GEANY_KEYS_MENU_PASTE] = fill(cb_func_clipboard,
-		GDK_v, GDK_CONTROL_MASK, "menu_paste", _("Paste"));
-	keys[GEANY_KEYS_MENU_SELECTALL] = fill(cb_func_menu_selectall,
-		GDK_a, GDK_CONTROL_MASK, "menu_selectall", _("Select All"));
-	keys[GEANY_KEYS_MENU_INSERTDATE] = fill(cb_func_menu_insert_date,
-		GDK_d, GDK_SHIFT_MASK | GDK_MOD1_MASK, "menu_insert_date", _("Insert date"));
-	keys[GEANY_KEYS_MENU_PREFERENCES] = fill(cb_func_menu_preferences,
-		GDK_p, GDK_CONTROL_MASK | GDK_MOD1_MASK, "menu_preferences", _("Preferences"));
+	group = ADD_KB_GROUP(FILE, _("File"));
 
-	/* search */
-	keys[GEANY_KEYS_MENU_FIND] = fill(cb_func_menu_search,
-		GDK_f, GDK_CONTROL_MASK, "menu_find", _("Find"));
-	keys[GEANY_KEYS_MENU_FINDNEXT] = fill(cb_func_menu_search,
-		GDK_g, GDK_CONTROL_MASK, "menu_findnext", _("Find Next"));
-	keys[GEANY_KEYS_MENU_FINDPREVIOUS] = fill(cb_func_menu_search,
-		GDK_g, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "menu_findprevious", _("Find Previous"));
-	keys[GEANY_KEYS_MENU_FINDNEXTSEL] = fill(cb_func_menu_search,
-		0, 0, "menu_findnextsel", _("Find Next Selection"));
-	keys[GEANY_KEYS_MENU_FINDPREVSEL] = fill(cb_func_menu_search,
-		0, 0, "menu_findprevsel", _("Find Previous Selection"));
-	keys[GEANY_KEYS_MENU_REPLACE] = fill(cb_func_menu_search,
-		GDK_h, GDK_CONTROL_MASK, "menu_replace", _("Replace"));
-	keys[GEANY_KEYS_MENU_FINDINFILES] = fill(cb_func_menu_search, GDK_f,
-		GDK_CONTROL_MASK | GDK_SHIFT_MASK, "menu_findinfiles", _("Find in Files"));
-	keys[GEANY_KEYS_MENU_NEXTMESSAGE] = fill(cb_func_menu_search,
-		0, 0, "menu_nextmessage", _("Next Message"));
-	keys[GEANY_KEYS_MENU_GOTOLINE] = fill(cb_func_menu_search,
-		GDK_l, GDK_CONTROL_MASK, "menu_gotoline", _("Go to Line"));
+	keybindings_set_item(group, GEANY_KEYS_FILE_NEW, cb_func_file_action,
+		GDK_n, GDK_CONTROL_MASK, "menu_new", _("New"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FILE_OPEN, cb_func_file_action,
+		GDK_o, GDK_CONTROL_MASK, "menu_open", _("Open"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FILE_OPENSELECTED, cb_func_file_action,
+		GDK_o, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "menu_open_selected",
+		_("Open selected file"), LW(menu_open_selected_file1));
+	keybindings_set_item(group, GEANY_KEYS_FILE_SAVE, cb_func_file_action,
+		GDK_s, GDK_CONTROL_MASK, "menu_save", _("Save"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FILE_SAVEAS, cb_func_file_action,
+		0, 0, "menu_saveas", _("Save as"), LW(menu_save_as1));
+	keybindings_set_item(group, GEANY_KEYS_FILE_SAVEALL, cb_func_file_action,
+		GDK_S, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "menu_saveall", _("Save all"),
+		LW(menu_save_all1));
+	keybindings_set_item(group, GEANY_KEYS_FILE_PRINT, cb_func_file_action,
+		GDK_p, GDK_CONTROL_MASK, "menu_print", _("Print"), LW(print1));
+	keybindings_set_item(group, GEANY_KEYS_FILE_CLOSE, cb_func_file_action,
+		GDK_w, GDK_CONTROL_MASK, "menu_close", _("Close"), LW(menu_close1));
+	keybindings_set_item(group, GEANY_KEYS_FILE_CLOSEALL, cb_func_file_action,
+		GDK_w, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "menu_closeall", _("Close all"),
+		LW(menu_close_all1));
+	keybindings_set_item(group, GEANY_KEYS_FILE_RELOAD, cb_func_file_action,
+		GDK_r, GDK_CONTROL_MASK, "menu_reloadfile", _("Reload file"), LW(menu_reload1));
 
-	keys[GEANY_KEYS_MENU_TOGGLEALL] = fill(cb_func_menu_toggle_all,
-		0, 0, "menu_toggleall", _("Toggle All Additional Widgets"));
-	keys[GEANY_KEYS_MENU_FULLSCREEN] = fill(cb_func_menu_fullscreen,
-		GDK_F11, 0, "menu_fullscreen", _("Fullscreen"));
-	keys[GEANY_KEYS_MENU_MESSAGEWINDOW] = fill(cb_func_menu_messagewindow,
-		0, 0, "menu_messagewindow", _("Toggle Messages Window"));
-	keys[GEANY_KEYS_MENU_SIDEBAR] = fill(cb_func_toggle_sidebar,
-		0, 0, "toggle_sidebar", _("Toggle Sidebar"));
-	keys[GEANY_KEYS_MENU_ZOOMIN] = fill(cb_func_menu_zoomin,
-		GDK_plus, GDK_CONTROL_MASK, "menu_zoomin", _("Zoom In"));
-	keys[GEANY_KEYS_MENU_ZOOMOUT] = fill(cb_func_menu_zoomout,
-		GDK_minus, GDK_CONTROL_MASK, "menu_zoomout", _("Zoom Out"));
+	group = ADD_KB_GROUP(PROJECT, _("Project"));
 
-	keys[GEANY_KEYS_MENU_OPENCOLORCHOOSER] = fill(cb_func_menu_opencolorchooser,
-		0, 0, "menu_opencolorchooser", _("Show Color Chooser"));
-	keys[GEANY_KEYS_MENU_INSERTSPECIALCHARS] = fill(cb_func_menu_insert_specialchars,
-		0, 0, "menu_insert_specialchars", _("Insert Special HTML Characters"));
+	keybindings_set_item(group, GEANY_KEYS_PROJECT_PROPERTIES, cb_func_project_action,
+		0, 0, "project_properties", _("Project properties"), LW(project_properties1));
 
-	keys[GEANY_KEYS_MENU_REPLACETABS] = fill(cb_func_menu_replacetabs,
-		0, 0, "menu_replacetabs", _("Replace tabs by space"));
-	keys[GEANY_KEYS_MENU_FOLDALL] = fill(cb_func_menu_foldall,
-		0, 0, "menu_foldall", _("Fold all"));
-	keys[GEANY_KEYS_MENU_UNFOLDALL] = fill(cb_func_menu_unfoldall,
-		0, 0, "menu_unfoldall", _("Unfold all"));
-	keys[GEANY_KEYS_RELOADTAGLIST] = fill(cb_func_reloadtaglist,
-		GDK_r, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "reloadtaglist", _("Reload symbol list"));
+	group = ADD_KB_GROUP(EDITOR, _("Editor"));
 
-	keys[GEANY_KEYS_BUILD_COMPILE] = fill(cb_func_build_action,
-		GDK_F8, 0, "build_compile", _("Compile"));
-	keys[GEANY_KEYS_BUILD_LINK] = fill(cb_func_build_action,
-		GDK_F9, 0, "build_link", _("Build"));
-	keys[GEANY_KEYS_BUILD_MAKE] = fill(cb_func_build_action,
-		GDK_F9, GDK_SHIFT_MASK, "build_make", _("Make all"));
-	keys[GEANY_KEYS_BUILD_MAKEOWNTARGET] = fill(cb_func_build_action,
-		GDK_F9, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "build_makeowntarget",
-		_("Make custom target"));
-	keys[GEANY_KEYS_BUILD_MAKEOBJECT] = fill(cb_func_build_action,
-		0, 0, "build_makeobject", _("Make object"));
-	keys[GEANY_KEYS_BUILD_NEXTERROR] = fill(cb_func_build_action,
-		0, 0, "build_nexterror", _("Next error"));
-	keys[GEANY_KEYS_BUILD_RUN] = fill(cb_func_build_action,
-		GDK_F5, 0, "build_run", _("Run"));
-	keys[GEANY_KEYS_BUILD_RUN2] = fill(cb_func_build_action,
-		0, 0, "build_run2", _("Run (alternative command)"));
-	keys[GEANY_KEYS_BUILD_OPTIONS] = fill(cb_func_build_action,
-		0, 0, "build_options", _("Build options"));
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_UNDO, cb_func_editor_action,
+		GDK_z, GDK_CONTROL_MASK, "menu_undo", _("Undo"), LW(menu_undo2));
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_REDO, cb_func_editor_action,
+		GDK_y, GDK_CONTROL_MASK, "menu_redo", _("Redo"), LW(menu_redo2));
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_DUPLICATELINE, cb_func_editor_action,
+		GDK_d, GDK_CONTROL_MASK, "edit_duplicateline", _("Duplicate line or selection"),
+		LW(menu_duplicate_line1));
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_DELETELINE, cb_func_editor_action,
+		GDK_k, GDK_CONTROL_MASK, "edit_deleteline", _("Delete current line(s)"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_TRANSPOSELINE, cb_func_editor_action,
+		GDK_t, GDK_CONTROL_MASK, "edit_transposeline", _("Transpose current line"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_SCROLLTOLINE, cb_func_editor_action,
+		GDK_l, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "edit_scrolltoline", _("Scroll to current line"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_SCROLLLINEUP, cb_func_editor_action,
+		GDK_Up, GDK_MOD1_MASK, "edit_scrolllineup", _("Scroll up the view by one line"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_SCROLLLINEDOWN, cb_func_editor_action,
+		GDK_Down, GDK_MOD1_MASK, "edit_scrolllinedown", _("Scroll down the view by one line"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_COMPLETESNIPPET, NULL,	/* handled specially in check_snippet_completion() */
+		GDK_Tab, 0, "edit_completesnippet", _("Complete snippet"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_SUPPRESSSNIPPETCOMPLETION, cb_func_editor_action,
+		0, 0, "edit_suppresssnippetcompletion", _("Suppress snippet completion"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_CONTEXTACTION, cb_func_editor_action,
+		0, 0, "popup_contextaction", _("Context Action"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_AUTOCOMPLETE, cb_func_editor_action,
+		GDK_space, GDK_CONTROL_MASK, "edit_autocomplete", _("Complete word"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_CALLTIP, cb_func_editor_action,
+		GDK_space, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "edit_calltip", _("Show calltip"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_MACROLIST, cb_func_editor_action,
+		GDK_Return, GDK_CONTROL_MASK, "edit_macrolist", _("Show macro list"), NULL);
 
-	keys[GEANY_KEYS_MENU_HELP] = fill(cb_func_menu_help,
-		GDK_F1, 0, "menu_help", _("Help"));
+	group = ADD_KB_GROUP(CLIPBOARD, _("Clipboard"));
 
-	keys[GEANY_KEYS_SWITCH_EDITOR] = fill(cb_func_switch_editor,
-		GDK_F2, 0, "switch_editor", _("Switch to Editor"));
-	keys[GEANY_KEYS_SWITCH_SCRIBBLE] = fill(cb_func_switch_scribble,
-		GDK_F6, 0, "switch_scribble", _("Switch to Scribble"));
-	keys[GEANY_KEYS_SWITCH_VTE] = fill(cb_func_switch_vte,
-		GDK_F4, 0, "switch_vte", _("Switch to VTE"));
-	keys[GEANY_KEYS_SWITCH_SEARCH_BAR] = fill(cb_func_switch_search_bar,
-		GDK_F7, 0, "switch_search_bar", _("Switch to Search Bar"));
-	keys[GEANY_KEYS_SWITCH_TABLEFT] = fill(cb_func_switch_tableft,
-		GDK_Page_Up, GDK_CONTROL_MASK, "switch_tableft", _("Switch to left document"));
-	keys[GEANY_KEYS_SWITCH_TABRIGHT] = fill(cb_func_switch_tabright,
-		GDK_Page_Down, GDK_CONTROL_MASK, "switch_tabright", _("Switch to right document"));
-	keys[GEANY_KEYS_SWITCH_TABLASTUSED] = fill(cb_func_switch_tablastused,
-		GDK_Tab, GDK_CONTROL_MASK, "switch_tablastused", _("Switch to last used document"));
-	keys[GEANY_KEYS_MOVE_TABLEFT] = fill(cb_func_move_tab,
-		GDK_Page_Up, GDK_MOD1_MASK, "move_tableft", _("Move document left"));
-	keys[GEANY_KEYS_MOVE_TABRIGHT] = fill(cb_func_move_tab,
-		GDK_Page_Down, GDK_MOD1_MASK, "move_tabright", _("Move document right"));
-	keys[GEANY_KEYS_MOVE_TABFIRST] = fill(cb_func_move_tab,
-		0, 0, "move_tabfirst", _("Move document first"));
-	keys[GEANY_KEYS_MOVE_TABLAST] = fill(cb_func_move_tab,
-		0, 0, "move_tablast", _("Move document last"));
-	keys[GEANY_KEYS_NAV_BACK] = fill(cb_func_nav_back,
-		0, 0, "nav_back", _("Navigate back a location"));
-	keys[GEANY_KEYS_NAV_FORWARD] = fill(cb_func_nav_forward,
-		0, 0, "nav_forward", _("Navigate forward a location"));
+	keybindings_set_item(group, GEANY_KEYS_CLIPBOARD_CUT, cb_func_clipboard,
+		GDK_x, GDK_CONTROL_MASK, "menu_cut", _("Cut"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_CLIPBOARD_COPY, cb_func_clipboard,
+		GDK_c, GDK_CONTROL_MASK, "menu_copy", _("Copy"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_CLIPBOARD_PASTE, cb_func_clipboard,
+		GDK_v, GDK_CONTROL_MASK, "menu_paste", _("Paste"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_CLIPBOARD_COPYLINE, cb_func_clipboard,
+		GDK_c, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "edit_copyline", _("Copy current line(s)"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_CLIPBOARD_CUTLINE, cb_func_clipboard,
+		GDK_x, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "edit_cutline", _("Cut current line(s)"), NULL);
 
-	keys[GEANY_KEYS_EDIT_DUPLICATELINE] = fill(cb_func_edit,
-		GDK_d, GDK_CONTROL_MASK, "edit_duplicateline", _("Duplicate line or selection"));
-	keys[GEANY_KEYS_EDIT_DELETELINE] = fill(cb_func_edit,
-		GDK_k, GDK_CONTROL_MASK, "edit_deleteline", _("Delete current line(s)"));
-	keys[GEANY_KEYS_EDIT_COPYLINE] = fill(cb_func_edit,
-		GDK_c, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "edit_copyline", _("Copy current line(s)"));
-	keys[GEANY_KEYS_EDIT_CUTLINE] = fill(cb_func_edit,
-		GDK_x, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "edit_cutline", _("Cut current line(s)"));
-	keys[GEANY_KEYS_EDIT_TRANSPOSELINE] = fill(cb_func_edit,
-		GDK_t, GDK_CONTROL_MASK, "edit_transposeline", _("Transpose current line"));
-	keys[GEANY_KEYS_EDIT_TOGGLECASE] = fill(cb_func_edit,
-		GDK_u, GDK_CONTROL_MASK | GDK_MOD1_MASK, "edit_togglecase", _("Toggle Case of Selection"));
-	keys[GEANY_KEYS_EDIT_COMMENTLINETOGGLE] = fill(cb_func_edit,
-		GDK_e, GDK_CONTROL_MASK, "edit_commentlinetoggle", _("Toggle line commentation"));
-	keys[GEANY_KEYS_EDIT_COMMENTLINE] = fill(cb_func_edit,
-		0, 0, "edit_commentline", _("Comment line(s)"));
-	keys[GEANY_KEYS_EDIT_UNCOMMENTLINE] = fill(cb_func_edit,
-		0, 0, "edit_uncommentline", _("Uncomment line(s)"));
-	keys[GEANY_KEYS_EDIT_INCREASEINDENT] = fill(cb_func_edit,
-		GDK_i, GDK_CONTROL_MASK, "edit_increaseindent", _("Increase indent"));
-	keys[GEANY_KEYS_EDIT_DECREASEINDENT] = fill(cb_func_edit,
-		GDK_u, GDK_CONTROL_MASK, "edit_decreaseindent", _("Decrease indent"));
-	keys[GEANY_KEYS_EDIT_INCREASEINDENTBYSPACE] = fill(cb_func_edit,
-		0, 0, "edit_increaseindentbyspace", _("Increase indent by one space"));
-	keys[GEANY_KEYS_EDIT_DECREASEINDENTBYSPACE] = fill(cb_func_edit,
-		0, 0, "edit_decreaseindentbyspace", _("Decrease indent by one space"));
-	keys[GEANY_KEYS_EDIT_AUTOINDENT] = fill(cb_func_edit,
-		0, 0, "edit_autoindent", _("Smart line indent"));
-	keys[GEANY_KEYS_EDIT_SENDTOCMD1] = fill(cb_func_edit,
-		GDK_1, GDK_CONTROL_MASK, "edit_sendtocmd1", _("Send to Custom Command 1"));
-	keys[GEANY_KEYS_EDIT_SENDTOCMD2] = fill(cb_func_edit,
-		GDK_2, GDK_CONTROL_MASK, "edit_sendtocmd2", _("Send to Custom Command 2"));
-	keys[GEANY_KEYS_EDIT_SENDTOCMD3] = fill(cb_func_edit,
-		GDK_3, GDK_CONTROL_MASK, "edit_sendtocmd3", _("Send to Custom Command 3"));
-	keys[GEANY_KEYS_EDIT_GOTOMATCHINGBRACE] = fill(cb_func_edit_global,
+	group = ADD_KB_GROUP(SELECT, _("Select"));
+
+	keybindings_set_item(group, GEANY_KEYS_SELECT_ALL, cb_func_select_action,
+		GDK_a, GDK_CONTROL_MASK, "menu_selectall", _("Select All"), LW(menu_select_all1));
+	keybindings_set_item(group, GEANY_KEYS_SELECT_WORD, cb_func_select_action,
+		GDK_w, GDK_SHIFT_MASK | GDK_MOD1_MASK, "edit_selectword", _("Select current word"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_SELECT_LINE, cb_func_select_action,
+		GDK_l, GDK_SHIFT_MASK | GDK_MOD1_MASK, "edit_selectline", _("Select current line(s)"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_SELECT_PARAGRAPH, cb_func_select_action,
+		GDK_p, GDK_SHIFT_MASK | GDK_MOD1_MASK, "edit_selectparagraph", _("Select current paragraph"), NULL);
+
+	group = ADD_KB_GROUP(FORMAT, _("Format"));
+
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_TOGGLECASE, cb_func_format_action,
+		GDK_u, GDK_CONTROL_MASK | GDK_MOD1_MASK, "edit_togglecase",
+		_("Toggle Case of Selection"), LW(menu_toggle_case2));
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_COMMENTLINETOGGLE, cb_func_format_action,
+		GDK_e, GDK_CONTROL_MASK, "edit_commentlinetoggle", _("Toggle line commentation"),
+		LW(menu_toggle_line_commentation1));
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_COMMENTLINE, cb_func_format_action,
+		0, 0, "edit_commentline", _("Comment line(s)"), LW(menu_comment_line1));
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_UNCOMMENTLINE, cb_func_format_action,
+		0, 0, "edit_uncommentline", _("Uncomment line(s)"), LW(menu_uncomment_line1));
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_INCREASEINDENT, cb_func_format_action,
+		GDK_i, GDK_CONTROL_MASK, "edit_increaseindent", _("Increase indent"),
+		LW(menu_increase_indent1));
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_DECREASEINDENT, cb_func_format_action,
+		GDK_u, GDK_CONTROL_MASK, "edit_decreaseindent", _("Decrease indent"),
+		LW(menu_decrease_indent1));
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_INCREASEINDENTBYSPACE, cb_func_format_action,
+		0, 0, "edit_increaseindentbyspace", _("Increase indent by one space"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_DECREASEINDENTBYSPACE, cb_func_format_action,
+		0, 0, "edit_decreaseindentbyspace", _("Decrease indent by one space"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_AUTOINDENT, cb_func_format_action,
+		0, 0, "edit_autoindent", _("Smart line indent"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_SENDTOCMD1, cb_func_format_action,
+		GDK_1, GDK_CONTROL_MASK, "edit_sendtocmd1", _("Send to Custom Command 1"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_SENDTOCMD2, cb_func_format_action,
+		GDK_2, GDK_CONTROL_MASK, "edit_sendtocmd2", _("Send to Custom Command 2"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_SENDTOCMD3, cb_func_format_action,
+		GDK_3, GDK_CONTROL_MASK, "edit_sendtocmd3", _("Send to Custom Command 3"), NULL);
+
+	group = ADD_KB_GROUP(INSERT, _("Insert"));
+
+	keybindings_set_item(group, GEANY_KEYS_INSERT_DATE, cb_func_insert_action,
+		GDK_d, GDK_SHIFT_MASK | GDK_MOD1_MASK, "menu_insert_date", _("Insert date"),
+		LW(insert_date_custom1));
+	keybindings_set_item(group, GEANY_KEYS_INSERT_ALTWHITESPACE, cb_func_insert_action,
+		0, 0, "edit_insertwhitespace", _("Insert alternative whitespace"), NULL);
+
+	group = ADD_KB_GROUP(SETTINGS, _("Settings"));
+
+	keybindings_set_item(group, GEANY_KEYS_SETTINGS_PREFERENCES, cb_func_menu_preferences,
+		GDK_p, GDK_CONTROL_MASK | GDK_MOD1_MASK, "menu_preferences", _("Preferences"),
+		LW(preferences1));
+
+	group = ADD_KB_GROUP(SEARCH, _("Search"));
+
+	keybindings_set_item(group, GEANY_KEYS_SEARCH_FIND, cb_func_search_action,
+		GDK_f, GDK_CONTROL_MASK, "menu_find", _("Find"), LW(find1));
+	keybindings_set_item(group, GEANY_KEYS_SEARCH_FINDNEXT, cb_func_search_action,
+		GDK_g, GDK_CONTROL_MASK, "menu_findnext", _("Find Next"), LW(find_next1));
+	keybindings_set_item(group, GEANY_KEYS_SEARCH_FINDPREVIOUS, cb_func_search_action,
+		GDK_g, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "menu_findprevious", _("Find Previous"),
+		LW(find_previous1));
+	keybindings_set_item(group, GEANY_KEYS_SEARCH_FINDNEXTSEL, cb_func_search_action,
+		0, 0, "menu_findnextsel", _("Find Next Selection"),LW(find_nextsel1));
+	keybindings_set_item(group, GEANY_KEYS_SEARCH_FINDPREVSEL, cb_func_search_action,
+		0, 0, "menu_findprevsel", _("Find Previous Selection"), LW(find_prevsel1));
+	keybindings_set_item(group, GEANY_KEYS_SEARCH_REPLACE, cb_func_search_action,
+		GDK_h, GDK_CONTROL_MASK, "menu_replace", _("Replace"), LW(replace1));
+	keybindings_set_item(group, GEANY_KEYS_SEARCH_FINDINFILES, cb_func_search_action, GDK_f,
+		GDK_CONTROL_MASK | GDK_SHIFT_MASK, "menu_findinfiles", _("Find in Files"),
+		LW(find_in_files1));
+	keybindings_set_item(group, GEANY_KEYS_SEARCH_NEXTMESSAGE, cb_func_search_action,
+		0, 0, "menu_nextmessage", _("Next Message"), LW(next_message1));
+	keybindings_set_item(group, GEANY_KEYS_SEARCH_FINDUSAGE, cb_func_search_action,
+		0, 0, "popup_findusage", _("Find Usage"), NULL);
+
+	group = ADD_KB_GROUP(GOTO, _("Go to"));
+
+	keybindings_set_item(group, GEANY_KEYS_GOTO_BACK, cb_func_goto_action,
+		0, 0, "nav_back", _("Navigate back a location"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_GOTO_FORWARD, cb_func_goto_action,
+		0, 0, "nav_forward", _("Navigate forward a location"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_GOTO_LINE, cb_func_goto_action,
+		GDK_l, GDK_CONTROL_MASK, "menu_gotoline", _("Go to Line"), LW(go_to_line1));
+	keybindings_set_item(group, GEANY_KEYS_GOTO_MATCHINGBRACE, cb_func_goto_action,
 		GDK_b, GDK_CONTROL_MASK, "edit_gotomatchingbrace",
-		_("Go to matching brace"));
-	keys[GEANY_KEYS_EDIT_TOGGLEMARKER] = fill(cb_func_edit_global,
+		_("Go to matching brace"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_GOTO_TOGGLEMARKER, cb_func_goto_action,
 		GDK_m, GDK_CONTROL_MASK, "edit_togglemarker",
-		_("Toggle marker"));
-	keys[GEANY_KEYS_EDIT_GOTONEXTMARKER] = fill(cb_func_edit_global,
+		_("Toggle marker"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_GOTO_NEXTMARKER, cb_func_goto_action,
 		GDK_period, GDK_CONTROL_MASK, "edit_gotonextmarker",
-		_("Go to next marker"));
-	keys[GEANY_KEYS_EDIT_GOTOPREVIOUSMARKER] = fill(cb_func_edit_global,
+		_("Go to next marker"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_GOTO_PREVIOUSMARKER, cb_func_goto_action,
 		GDK_comma, GDK_CONTROL_MASK, "edit_gotopreviousmarker",
-		_("Go to previous marker"));
+		_("Go to previous marker"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_GOTO_TAGDEFINITION, cb_func_goto_action,
+		0, 0, "popup_gototagdefinition", _("Go to Tag Definition"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_GOTO_TAGDECLARATION, cb_func_goto_action,
+		0, 0, "popup_gototagdeclaration", _("Go to Tag Declaration"), NULL);
 
-	keys[GEANY_KEYS_EDIT_AUTOCOMPLETE] = fill(cb_func_edit,
-		GDK_space, GDK_CONTROL_MASK, "edit_autocomplete", _("Complete word"));
-	keys[GEANY_KEYS_EDIT_CALLTIP] = fill(cb_func_edit,
-		GDK_space, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "edit_calltip", _("Show calltip"));
-	keys[GEANY_KEYS_EDIT_MACROLIST] = fill(cb_func_edit,
-		GDK_Return, GDK_CONTROL_MASK, "edit_macrolist", _("Show macro list"));
-	keys[GEANY_KEYS_EDIT_COMPLETESNIPPET] = fill(NULL,	/* has special callback */
-		GDK_Tab, 0, "edit_completesnippet", _("Complete snippet"));
-	keys[GEANY_KEYS_EDIT_SUPPRESSSNIPPETCOMPLETION] = fill(cb_func_edit,
-		0, 0, "edit_suppresssnippetcompletion", _("Suppress snippet completion"));
+	group = ADD_KB_GROUP(VIEW, _("View"));
 
-	keys[GEANY_KEYS_EDIT_SELECTWORD] = fill(cb_func_edit,
-		GDK_w, GDK_SHIFT_MASK | GDK_MOD1_MASK, "edit_selectword", _("Select current word"));
-	keys[GEANY_KEYS_EDIT_SELECTLINE] = fill(cb_func_edit,
-		GDK_l, GDK_SHIFT_MASK | GDK_MOD1_MASK, "edit_selectline", _("Select current line(s)"));
-	keys[GEANY_KEYS_EDIT_SELECTPARAGRAPH] = fill(cb_func_edit,
-		GDK_p, GDK_SHIFT_MASK | GDK_MOD1_MASK, "edit_selectparagraph", _("Select current paragraph"));
-	keys[GEANY_KEYS_EDIT_SCROLLTOLINE] = fill(cb_func_edit,
-		GDK_l, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "edit_scrolltoline", _("Scroll to current line"));
-	keys[GEANY_KEYS_EDIT_SCROLLLINEUP] = fill(cb_func_edit,
-		GDK_Up, GDK_MOD1_MASK, "edit_scrolllineup", _("Scroll up the view by one line"));
-	keys[GEANY_KEYS_EDIT_SCROLLLINEDOWN] = fill(cb_func_edit,
-		GDK_Down, GDK_MOD1_MASK, "edit_scrolllinedown", _("Scroll down the view by one line"));
+	keybindings_set_item(group, GEANY_KEYS_VIEW_TOGGLEALL, cb_func_menu_toggle_all,
+		0, 0, "menu_toggleall", _("Toggle All Additional Widgets"),
+		LW(menu_toggle_all_additional_widgets1));
+	keybindings_set_item(group, GEANY_KEYS_VIEW_FULLSCREEN, cb_func_menu_fullscreen,
+		GDK_F11, 0, "menu_fullscreen", _("Fullscreen"), LW(menu_fullscreen1));
+	keybindings_set_item(group, GEANY_KEYS_VIEW_MESSAGEWINDOW, cb_func_menu_messagewindow,
+		0, 0, "menu_messagewindow", _("Toggle Messages Window"),
+		LW(menu_show_messages_window1));
+	keybindings_set_item(group, GEANY_KEYS_VIEW_SIDEBAR, cb_func_toggle_sidebar,
+		0, 0, "toggle_sidebar", _("Toggle Sidebar"), LW(menu_show_sidebar1));
+	keybindings_set_item(group, GEANY_KEYS_VIEW_ZOOMIN, cb_func_menu_zoomin,
+		GDK_plus, GDK_CONTROL_MASK, "menu_zoomin", _("Zoom In"), LW(menu_zoom_in1));
+	keybindings_set_item(group, GEANY_KEYS_VIEW_ZOOMOUT, cb_func_menu_zoomout,
+		GDK_minus, GDK_CONTROL_MASK, "menu_zoomout", _("Zoom Out"), LW(menu_zoom_out1));
 
-	keys[GEANY_KEYS_EDIT_INSERTALTWHITESPACE] = fill(cb_func_edit,
-		0, 0, "edit_insertwhitespace", _("Insert alternative whitespace"));
+	group = ADD_KB_GROUP(FOCUS, _("Focus"));
 
-	keys[GEANY_KEYS_POPUP_FINDUSAGE] = fill(cb_func_current_word,
-		0, 0, "popup_findusage", _("Find Usage"));
-	keys[GEANY_KEYS_POPUP_GOTOTAGDEFINITION] = fill(cb_func_current_word,
-		0, 0, "popup_gototagdefinition", _("Go to Tag Definition"));
-	keys[GEANY_KEYS_POPUP_GOTOTAGDECLARATION] = fill(cb_func_current_word,
-		0, 0, "popup_gototagdeclaration", _("Go to Tag Declaration"));
-	keys[GEANY_KEYS_POPUP_CONTEXTACTION] = fill(cb_func_current_word,
-		0, 0, "popup_contextaction", _("Context Action"));
+	keybindings_set_item(group, GEANY_KEYS_FOCUS_EDITOR, cb_func_switch_editor,
+		GDK_F2, 0, "switch_editor", _("Switch to Editor"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FOCUS_SCRIBBLE, cb_func_switch_scribble,
+		GDK_F6, 0, "switch_scribble", _("Switch to Scribble"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FOCUS_VTE, cb_func_switch_vte,
+		GDK_F4, 0, "switch_vte", _("Switch to VTE"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FOCUS_SEARCHBAR, cb_func_switch_search_bar,
+		GDK_F7, 0, "switch_search_bar", _("Switch to Search Bar"), NULL);
+
+	group = ADD_KB_GROUP(NOTEBOOK, _("Notebook tab"));
+
+	keybindings_set_item(group, GEANY_KEYS_NOTEBOOK_SWITCHTABLEFT, cb_func_switch_tableft,
+		GDK_Page_Up, GDK_CONTROL_MASK, "switch_tableft", _("Switch to left document"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_NOTEBOOK_SWITCHTABRIGHT, cb_func_switch_tabright,
+		GDK_Page_Down, GDK_CONTROL_MASK, "switch_tabright", _("Switch to right document"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_NOTEBOOK_SWITCHTABLASTUSED, cb_func_switch_tablastused,
+		GDK_Tab, GDK_CONTROL_MASK, "switch_tablastused", _("Switch to last used document"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_NOTEBOOK_MOVETABLEFT, cb_func_move_tab,
+		GDK_Page_Up, GDK_MOD1_MASK, "move_tableft", _("Move document left"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_NOTEBOOK_MOVETABRIGHT, cb_func_move_tab,
+		GDK_Page_Down, GDK_MOD1_MASK, "move_tabright", _("Move document right"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_NOTEBOOK_MOVETABFIRST, cb_func_move_tab,
+		0, 0, "move_tabfirst", _("Move document first"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_NOTEBOOK_MOVETABLAST, cb_func_move_tab,
+		0, 0, "move_tablast", _("Move document last"), NULL);
+
+	group = ADD_KB_GROUP(DOCUMENT, _("Document"));
+
+	keybindings_set_item(group, GEANY_KEYS_DOCUMENT_REPLACETABS, cb_func_menu_replacetabs,
+		0, 0, "menu_replacetabs", _("Replace tabs by space"), LW(menu_replace_tabs));
+	keybindings_set_item(group, GEANY_KEYS_DOCUMENT_FOLDALL, cb_func_menu_foldall,
+		0, 0, "menu_foldall", _("Fold all"), LW(menu_fold_all1));
+	keybindings_set_item(group, GEANY_KEYS_DOCUMENT_UNFOLDALL, cb_func_menu_unfoldall,
+		0, 0, "menu_unfoldall", _("Unfold all"), LW(menu_unfold_all1));
+	keybindings_set_item(group, GEANY_KEYS_DOCUMENT_RELOADTAGLIST, cb_func_reloadtaglist,
+		GDK_r, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "reloadtaglist", _("Reload symbol list"), NULL);
+
+	group = ADD_KB_GROUP(BUILD, _("Build"));
+
+	keybindings_set_item(group, GEANY_KEYS_BUILD_COMPILE, cb_func_build_action,
+		GDK_F8, 0, "build_compile", _("Compile"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_BUILD_LINK, cb_func_build_action,
+		GDK_F9, 0, "build_link", _("Build"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_BUILD_MAKE, cb_func_build_action,
+		GDK_F9, GDK_SHIFT_MASK, "build_make", _("Make all"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_BUILD_MAKEOWNTARGET, cb_func_build_action,
+		GDK_F9, GDK_SHIFT_MASK | GDK_CONTROL_MASK, "build_makeowntarget",
+		_("Make custom target"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_BUILD_MAKEOBJECT, cb_func_build_action,
+		0, 0, "build_makeobject", _("Make object"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_BUILD_NEXTERROR, cb_func_build_action,
+		0, 0, "build_nexterror", _("Next error"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_BUILD_RUN, cb_func_build_action,
+		GDK_F5, 0, "build_run", _("Run"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_BUILD_RUN2, cb_func_build_action,
+		0, 0, "build_run2", _("Run (alternative command)"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_BUILD_OPTIONS, cb_func_build_action,
+		0, 0, "build_options", _("Build options"), NULL);
+
+	group = ADD_KB_GROUP(TOOLS, _("Tools"));
+
+	keybindings_set_item(group, GEANY_KEYS_TOOLS_OPENCOLORCHOOSER, cb_func_menu_opencolorchooser,
+		0, 0, "menu_opencolorchooser", _("Show Color Chooser"), LW(menu_choose_color1));
+
+	group = ADD_KB_GROUP(HELP, _("Help"));
+
+	keybindings_set_item(group, GEANY_KEYS_HELP_HELP, cb_func_menu_help,
+		GDK_F1, 0, "menu_help", _("Help"), LW(help1));
 }
 
 
-static void load_user_kb(void)
+typedef void (*KBItemCallback) (KeyBindingGroup *group, KeyBinding *kb, gpointer user_data);
+
+static void keybindings_foreach(KBItemCallback cb, gpointer user_data)
 {
-	gchar *configfile = g_strconcat(app->configdir, G_DIR_SEPARATOR_S, "keybindings.conf", NULL);
+	gsize g, i;
+
+	for (g = 0; g < keybinding_groups->len; g++)
+	{
+		KeyBindingGroup *group = g_ptr_array_index(keybinding_groups, g);
+
+		for (i = 0; i < group->count; i++)
+		{
+			KeyBinding *kb = &group->keys[i];
+
+			cb(group, kb, user_data);
+		}
+	}
+}
+
+
+static void get_keyfile_kb(KeyBindingGroup *group, KeyBinding *kb, gpointer user_data)
+{
+	GKeyFile *config = user_data;
 	gchar *val;
-	guint i;
 	guint key;
 	GdkModifierType mods;
+
+	val = g_key_file_get_string(config, group->name, kb->name, NULL);
+	if (val != NULL)
+	{
+		gtk_accelerator_parse(val, &key, &mods);
+		kb->key = key;
+		kb->mods = mods;
+	}
+	g_free(val);
+}
+
+
+static void load_user_kb(void)
+{
+	gchar *configfile = g_strconcat(app->configdir, G_DIR_SEPARATOR_S, "keybindings.conf", NULL);
 	GKeyFile *config = g_key_file_new();
 
 	/* now load user defined keys */
 	if (g_key_file_load_from_file(config, configfile, G_KEY_FILE_KEEP_COMMENTS, NULL))
 	{
-		for (i = 0; i < GEANY_MAX_KEYS; i++)
-		{
-			val = g_key_file_get_string(config, "Bindings", keys[i]->name, NULL);
-			if (val != NULL)
-			{
-				gtk_accelerator_parse(val, &key, &mods);
-				keys[i]->key = key;
-				keys[i]->mods = mods;
-			}
-			g_free(val);
-		}
+		keybindings_foreach(get_keyfile_kb, config);
 	}
 	g_free(configfile);
 	g_key_file_free(config);
@@ -364,125 +496,92 @@
 
 void keybindings_init(void)
 {
+	keybinding_groups = g_ptr_array_sized_new(GEANY_KEY_GROUP_COUNT);
+
 	init_default_kb();
+}
+
+
+void keybindings_load_keyfile(void)
+{
 	load_user_kb();
+	add_popup_menu_accels();
+}
 
-	/* set section name */
-	keys[GEANY_KEYS_GROUP_FILE]->section = _("File menu");
-	keys[GEANY_KEYS_GROUP_EDIT]->section = _("Edit menu");
-	keys[GEANY_KEYS_GROUP_SEARCH]->section = _("Search menu");
-	keys[GEANY_KEYS_GROUP_VIEW]->section = _("View menu");
-	keys[GEANY_KEYS_GROUP_DOCUMENT]->section = _("Document menu");
-	keys[GEANY_KEYS_GROUP_BUILD]->section = _("Build menu");
-	keys[GEANY_KEYS_GROUP_TOOLS]->section = _("Tools menu");
-	keys[GEANY_KEYS_GROUP_HELP]->section = _("Help menu");
-	keys[GEANY_KEYS_GROUP_FOCUS]->section = _("Focus commands");
-	keys[GEANY_KEYS_GROUP_TABS]->section = _("Notebook tab commands");
-	keys[GEANY_KEYS_GROUP_EDITING]->section = _("Editing commands");
-	keys[GEANY_KEYS_GROUP_TAGS]->section = _("Tag commands");
-	keys[GEANY_KEYS_GROUP_OTHER]->section = _("Other commands");
 
-	add_menu_accels();
+static void add_menu_accel(KeyBindingGroup *group, guint kb_id,
+	GtkAccelGroup *accel_group, GtkWidget *menuitem)
+{
+	KeyBinding *kb = &group->keys[kb_id];
+
+	if (kb->key != 0)
+		gtk_widget_add_accelerator(menuitem, "activate", accel_group,
+			kb->key, kb->mods, GTK_ACCEL_VISIBLE);
 }
 
 
-#define GEANY_ADD_ACCEL(gkey, wid) \
-	if (keys[(gkey)]->key != 0) \
-		gtk_widget_add_accelerator( \
-			lookup_widget(app->window, G_STRINGIFY(wid)), \
-			"activate", accel_group, keys[(gkey)]->key, keys[(gkey)]->mods, \
-			GTK_ACCEL_VISIBLE)
+#define GEANY_ADD_POPUP_ACCEL(kb_id, wid) \
+	add_menu_accel(group, kb_id, accel_group, lookup_widget(app->popup_menu, G_STRINGIFY(wid)))
 
-#define GEANY_ADD_POPUP_ACCEL(gkey, wid) \
-	if (keys[(gkey)]->key != 0) \
-		gtk_widget_add_accelerator( \
-			lookup_widget(app->popup_menu, G_STRINGIFY(wid)), \
-			"activate", accel_group, keys[(gkey)]->key, keys[(gkey)]->mods, \
-			GTK_ACCEL_VISIBLE)
-
-static void add_menu_accels()
+/* set the menu item accelerator shortcuts (just for visibility, they are handled anyway) */
+static void add_popup_menu_accels(void)
 {
 	GtkAccelGroup *accel_group = gtk_accel_group_new();
+	KeyBindingGroup *group;
 
-	/* apply the settings */
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_OPENSELECTED, menu_open_selected_file1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_SAVEALL, menu_save_all1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_SAVEAS, menu_save_as1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_PRINT, print1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_CLOSE, menu_close1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_CLOSEALL, menu_close_all1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_RELOADFILE, menu_reload1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_UNDO, menu_undo2);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_REDO, menu_redo2);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_SELECTALL, menu_select_all1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_INSERTDATE, insert_date_custom1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_PREFERENCES, preferences1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_HELP, help1);
+	group = g_ptr_array_index(keybinding_groups, GEANY_KEY_GROUP_EDITOR);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDITOR_UNDO, undo1);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDITOR_REDO, redo1);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDITOR_DUPLICATELINE, menu_duplicate_line2);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDITOR_CONTEXTACTION, context_action1);
 
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_FIND, find1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_FINDNEXT, find_next1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_FINDPREVIOUS, find_previous1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_FINDNEXTSEL, find_nextsel1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_FINDPREVSEL, find_prevsel1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_REPLACE, replace1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_FINDINFILES, find_in_files1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_NEXTMESSAGE, next_message1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_GOTOLINE, go_to_line1);
+	group = g_ptr_array_index(keybinding_groups, GEANY_KEY_GROUP_SELECT);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_SELECT_ALL, menu_select_all2);
 
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_TOGGLEALL, menu_toggle_all_additional_widgets1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_FULLSCREEN, menu_fullscreen1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_MESSAGEWINDOW, menu_show_messages_window1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_SIDEBAR, menu_show_sidebar1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_ZOOMIN, menu_zoom_in1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_ZOOMOUT, menu_zoom_out1);
+	group = g_ptr_array_index(keybinding_groups, GEANY_KEY_GROUP_INSERT);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_INSERT_DATE, insert_date_custom2);
 
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_REPLACETABS, menu_replace_tabs);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_FOLDALL, menu_fold_all1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_UNFOLDALL, menu_unfold_all1);
+	group = g_ptr_array_index(keybinding_groups, GEANY_KEY_GROUP_FILE);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_FILE_OPENSELECTED, menu_open_selected_file2);
 
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_PROJECTPROPERTIES, project_properties1);
+	group = g_ptr_array_index(keybinding_groups, GEANY_KEY_GROUP_SEARCH);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_SEARCH_FINDUSAGE, find_usage1);
 
-	GEANY_ADD_ACCEL(GEANY_KEYS_MENU_OPENCOLORCHOOSER, menu_choose_color1);
-	/*GEANY_ADD_ACCEL(GEANY_KEYS_MENU_INSERTSPECIALCHARS, menu_insert_special_chars1);*/
+	group = g_ptr_array_index(keybinding_groups, GEANY_KEY_GROUP_GOTO);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_GOTO_LINE, go_to_line);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_GOTO_TAGDEFINITION, goto_tag_definition1);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_GOTO_TAGDECLARATION, goto_tag_declaration1);
 
-	GEANY_ADD_ACCEL(GEANY_KEYS_EDIT_TOGGLECASE, menu_toggle_case2);
-	GEANY_ADD_ACCEL(GEANY_KEYS_EDIT_COMMENTLINE, menu_comment_line1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_EDIT_UNCOMMENTLINE, menu_uncomment_line1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_EDIT_COMMENTLINETOGGLE, menu_toggle_line_commentation1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_EDIT_DUPLICATELINE, menu_duplicate_line1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_EDIT_INCREASEINDENT, menu_increase_indent1);
-	GEANY_ADD_ACCEL(GEANY_KEYS_EDIT_DECREASEINDENT, menu_decrease_indent1);
+	group = g_ptr_array_index(keybinding_groups, GEANY_KEY_GROUP_FORMAT);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_FORMAT_TOGGLECASE, toggle_case1);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_FORMAT_COMMENTLINE, menu_comment_line2);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_FORMAT_UNCOMMENTLINE, menu_uncomment_line2);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_FORMAT_COMMENTLINETOGGLE, menu_toggle_line_commentation2);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_FORMAT_INCREASEINDENT, menu_increase_indent2);
+	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_FORMAT_DECREASEINDENT, menu_decrease_indent2);
 
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_MENU_UNDO, undo1);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_MENU_REDO, redo1);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_MENU_SELECTALL, menu_select_all2);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_MENU_INSERTDATE, insert_date_custom2);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_MENU_OPENSELECTED, menu_open_selected_file2);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_POPUP_FINDUSAGE, find_usage1);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_POPUP_GOTOTAGDEFINITION, goto_tag_definition1);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_POPUP_GOTOTAGDECLARATION, goto_tag_declaration1);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_POPUP_CONTEXTACTION, context_action1);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_MENU_GOTOLINE, go_to_line);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDIT_TOGGLECASE, toggle_case1);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDIT_COMMENTLINE, menu_comment_line2);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDIT_UNCOMMENTLINE, menu_uncomment_line2);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDIT_COMMENTLINETOGGLE, menu_toggle_line_commentation2);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDIT_DUPLICATELINE, menu_duplicate_line2);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDIT_INCREASEINDENT, menu_increase_indent2);
-	GEANY_ADD_POPUP_ACCEL(GEANY_KEYS_EDIT_DECREASEINDENT, menu_decrease_indent2);
-
 	/* the build menu items are set if the build menus are created */
 
 	gtk_window_add_accel_group(GTK_WINDOW(app->window), accel_group);
 }
 
 
+static void set_keyfile_kb(KeyBindingGroup *group, KeyBinding *kb, gpointer user_data)
+{
+	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);
+}
+
+
 /* just write the content of the keys array to the config file */
 void keybindings_write_to_file(void)
 {
 	gchar *configfile = g_strconcat(app->configdir, G_DIR_SEPARATOR_S, "keybindings.conf", NULL);
-	gchar *val, *data;
-	guint i;
+	gchar *data;
 	GKeyFile *config = g_key_file_new();
 
  	/* add comment if the file is newly created */
@@ -491,12 +590,7 @@
 		g_key_file_set_comment(config, NULL, NULL, "Keybindings for Geany\nThe format looks like \"<Control>a\" or \"<Shift><Alt>F1\".\nBut you can also change the keys in Geany's preferences dialog.", NULL);
 	}
 
-	for (i = 0; i < GEANY_MAX_KEYS; i++)
-	{
-		val = gtk_accelerator_name(keys[i]->key, keys[i]->mods);
-		g_key_file_set_string(config, "Bindings", keys[i]->name, val);
-		g_free(val);
-	}
+	keybindings_foreach(set_keyfile_kb, config);
 
 	/* write the file */
 	data = g_key_file_to_data(config, NULL, NULL);
@@ -510,53 +604,51 @@
 
 void keybindings_free(void)
 {
-	guint i;
-
-	for (i = 0; i < GEANY_MAX_KEYS; i++)
-	{
-		g_free(keys[i]);
-	}
+	g_ptr_array_free(keybinding_groups, TRUE);
 }
 
 
 static void get_shortcut_labels_text(GString **text_names_str, GString **text_keys_str)
 {
-	guint i;
+	gsize g, i;
 	GString *text_names = g_string_sized_new(600);
 	GString *text_keys = g_string_sized_new(600);
 
 	*text_names_str = text_names;
 	*text_keys_str = text_keys;
 
-	for (i = 0; i < GEANY_MAX_KEYS; i++)
+	for (g = 0; g < keybinding_groups->len; g++)
 	{
-		gchar *shortcut;
+		KeyBindingGroup *group = g_ptr_array_index(keybinding_groups, g);
 
-		if (keys[i]->section != NULL)
+		if (g == 0)
 		{
-			if (i == GEANY_KEYS_MENU_NEW)
-			{
-				g_string_append_printf(text_names, "<b>%s</b>\n", keys[i]->section);
-				g_string_append(text_keys, "\n");
-			}
-			else
-			{
-				g_string_append_printf(text_names, "\n<b>%s</b>\n", keys[i]->section);
-				g_string_append(text_keys, "\n\n");
-			}
+			g_string_append_printf(text_names, "<b>%s</b>\n", group->label);
+			g_string_append(text_keys, "\n");
 		}
+		else
+		{
+			g_string_append_printf(text_names, "\n<b>%s</b>\n", group->label);
+			g_string_append(text_keys, "\n\n");
+		}
 
-		shortcut = gtk_accelerator_get_label(keys[i]->key, keys[i]->mods);
-		g_string_append(text_names, keys[i]->label);
-		g_string_append(text_names, "\n");
-		g_string_append(text_keys, shortcut);
-		g_string_append(text_keys, "\n");
-		g_free(shortcut);
+		for (i = 0; i < group->count; i++)
+		{
+			KeyBinding *kb = &group->keys[i];
+			gchar *shortcut;
+
+			shortcut = gtk_accelerator_get_label(kb->key, kb->mods);
+			g_string_append(text_names, kb->label);
+			g_string_append(text_names, "\n");
+			g_string_append(text_keys, shortcut);
+			g_string_append(text_keys, "\n");
+			g_free(shortcut);
+		}
 	}
 }
 
 
-void keybindings_show_shortcuts()
+void keybindings_show_shortcuts(void)
 {
 	GtkWidget *dialog, *hbox, *label1, *label2, *label3, *swin, *vbox;
 	GString *text_names;
@@ -664,9 +756,10 @@
  * return FALSE if no completion occurs, so the tab or space is handled normally. */
 static gboolean check_snippet_completion(guint keyval, guint state)
 {
-	const guint i = GEANY_KEYS_EDIT_COMPLETESNIPPET;
+	KeyBinding *kb = keybindings_lookup_item(GEANY_KEY_GROUP_EDITOR,
+		GEANY_KEYS_EDITOR_COMPLETESNIPPET);
 
-	if (keys[i]->key == keyval && keys[i]->mods == state)
+	if (kb->key == keyval && kb->mods == state)
 	{
 		gint idx = document_get_cur_idx();
 		GtkWidget *focusw = gtk_window_get_focus(GTK_WINDOW(app->window));
@@ -709,9 +802,11 @@
 		return FALSE;
 
 	/* make focus commands override any bash commands */
-	for (i = GEANY_KEYS_GROUP_FOCUS; i < GEANY_KEYS_GROUP_TABS; i++)
+	for (i = 0; i < GEANY_KEYS_FOCUS_COUNT; i++)
 	{
-		if (state == keys[i]->mods && keyval == keys[i]->key)
+		KeyBinding *kb = keybindings_lookup_item(GEANY_KEY_GROUP_FOCUS, i);
+
+		if (state == kb->mods && keyval == kb->key)
 			return FALSE;
 	}
 
@@ -733,7 +828,8 @@
 /* central keypress event handler, almost all keypress events go to this function */
 gboolean keybindings_got_event(GtkWidget *widget, GdkEventKey *ev, gpointer user_data)
 {
-	guint i, state, keyval;
+	guint state, keyval;
+	gsize g, i;
 
 	if (ev->keyval == 0)
 		return FALSE;
@@ -756,47 +852,60 @@
 	if (check_snippet_completion(keyval, state))
 		return TRUE;
 
-	for (i = 0; i < GEANY_MAX_KEYS; i++)
+	for (g = 0; g < keybinding_groups->len; g++)
 	{
-		if (keyval == keys[i]->key && state == keys[i]->mods)
+		KeyBindingGroup *group = g_ptr_array_index(keybinding_groups, g);
+
+		for (i = 0; i < group->count; i++)
 		{
-			if (keys[i]->cb_func == NULL)
-				return FALSE;	/* ignore the keybinding */
+			KeyBinding *kb = &group->keys[i];
 
-			/* call the corresponding callback function for this shortcut */
-			keys[i]->cb_func(i);
-			return TRUE;
+			if (keyval == kb->key && state == kb->mods)
+			{
+				if (kb->callback == NULL)
+					return FALSE;	/* ignore the keybinding */
+
+				/* call the corresponding callback function for this shortcut */
+				kb->callback(i);
+				return TRUE;
+			}
 		}
 	}
-	/* fixed keybindings can be overridden by user bindings */
+	/* fixed keybindings can be overridden by user bindings, so check them last */
 	if (check_fixed_kb(keyval, state))
 		return TRUE;
 	return FALSE;
 }
 
 
-/* simple convenience function to allocate and fill the struct */
-static KeyBinding *fill(KBCallback func, guint key, GdkModifierType mod, const gchar *name,
-		const gchar *label)
+KeyBinding *keybindings_lookup_item(guint group_id, guint key_id)
 {
-	KeyBinding *result;
+	KeyBindingGroup *group;
 
-	result = g_new0(KeyBinding, 1);
-	result->name = name;
-	result->label = label;
-	result->key = key;
-	result->mods = mod;
-	result->cb_func = func;
-	result->section = NULL;
+	g_return_val_if_fail(group_id < keybinding_groups->len, NULL);
 
-	return result;
+	group = g_ptr_array_index(keybinding_groups, group_id);
+
+	g_return_val_if_fail(group, NULL);
+	g_return_val_if_fail(key_id < group->count, NULL);
+
+	return &group->keys[key_id];
 }
 
 
-/* Mimic a keybinding action */
-void keybindings_cmd(GeanyKeyCommand cmd_id)
+/** Mimic a (built-in only) keybinding action.
+ * 	Example: @code keybindings_send_command(GEANY_KEY_GROUP_FILE, GEANY_KEYS_FILE_OPEN); @endcode
+ * 	@param group_id The index for the key group that contains the @a key_id keybinding.
+ * 	@param key_id The keybinding command index. */
+void keybindings_send_command(guint group_id, guint key_id)
 {
-	keys[cmd_id]->cb_func(cmd_id);
+	KeyBinding *kb;
+
+	g_return_if_fail(group_id < GEANY_KEY_GROUP_COUNT);	/* can't use this for plugin groups */
+
+	kb = keybindings_lookup_item(group_id, key_id);
+	if (kb)
+		kb->callback(key_id);
 }
 
 
@@ -808,61 +917,52 @@
 {
 	switch (key_id)
 	{
-		case GEANY_KEYS_MENU_NEW:
+		case GEANY_KEYS_FILE_NEW:
 			document_new_file(NULL, NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_OPEN:
+		case GEANY_KEYS_FILE_OPEN:
 			on_open1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_OPENSELECTED:
+		case GEANY_KEYS_FILE_OPENSELECTED:
 			on_menu_open_selected_file1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_SAVE:
+		case GEANY_KEYS_FILE_SAVE:
 			on_save1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_SAVEAS:
+		case GEANY_KEYS_FILE_SAVEAS:
 			on_save_as1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_SAVEALL:
+		case GEANY_KEYS_FILE_SAVEALL:
 			on_save_all1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_CLOSE:
+		case GEANY_KEYS_FILE_CLOSE:
 			on_close1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_CLOSEALL:
+		case GEANY_KEYS_FILE_CLOSEALL:
 			on_close_all1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_RELOADFILE:
+		case GEANY_KEYS_FILE_RELOAD:
 			on_toolbutton23_clicked(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_PROJECTPROPERTIES:
-			if (app->project)
-				on_project_properties1_activate(NULL, NULL);
+		case GEANY_KEYS_FILE_PRINT:
+			on_print1_activate(NULL, NULL);
 			break;
 	}
 }
 
 
-static void cb_func_menu_print(G_GNUC_UNUSED guint key_id)
+static void cb_func_project_action(guint key_id)
 {
-	on_print1_activate(NULL, NULL);
+	switch (key_id)
+	{
+		case GEANY_KEYS_PROJECT_PROPERTIES:
+			if (app->project)
+				on_project_properties1_activate(NULL, NULL);
+			break;
+	}
 }
 
-static void cb_func_menu_undo(G_GNUC_UNUSED guint key_id)
-{
-	on_undo1_activate(NULL, NULL);
-}
 
-static void cb_func_menu_redo(G_GNUC_UNUSED guint key_id)
-{
-	on_redo1_activate(NULL, NULL);
-}
-
-static void cb_func_menu_selectall(G_GNUC_UNUSED guint key_id)
-{
-	on_menu_select_all1_activate(NULL, NULL);
-}
-
 static void cb_func_menu_preferences(G_GNUC_UNUSED guint key_id)
 {
 	on_preferences1_activate(NULL, NULL);
@@ -873,28 +973,30 @@
 	on_help1_activate(NULL, NULL);
 }
 
-static void cb_func_menu_search(guint key_id)
+static void cb_func_search_action(guint key_id)
 {
 	switch (key_id)
 	{
-		case GEANY_KEYS_MENU_FIND:
+		case GEANY_KEYS_SEARCH_FIND:
 			on_find1_activate(NULL, NULL); break;
-		case GEANY_KEYS_MENU_FINDNEXT:
+		case GEANY_KEYS_SEARCH_FINDNEXT:
 			on_find_next1_activate(NULL, NULL); break;
-		case GEANY_KEYS_MENU_FINDPREVIOUS:
+		case GEANY_KEYS_SEARCH_FINDPREVIOUS:
 			on_find_previous1_activate(NULL, NULL); break;
-		case GEANY_KEYS_MENU_FINDPREVSEL:
+		case GEANY_KEYS_SEARCH_FINDPREVSEL:
 			on_find_prevsel1_activate(NULL, NULL); break;
-		case GEANY_KEYS_MENU_FINDNEXTSEL:
+		case GEANY_KEYS_SEARCH_FINDNEXTSEL:
 			on_find_nextsel1_activate(NULL, NULL); break;
-		case GEANY_KEYS_MENU_REPLACE:
+		case GEANY_KEYS_SEARCH_REPLACE:
 			on_replace1_activate(NULL, NULL); break;
-		case GEANY_KEYS_MENU_FINDINFILES:
+		case GEANY_KEYS_SEARCH_FINDINFILES:
 			on_find_in_files1_activate(NULL, NULL); break;
-		case GEANY_KEYS_MENU_NEXTMESSAGE:
+		case GEANY_KEYS_SEARCH_NEXTMESSAGE:
 			on_next_message1_activate(NULL, NULL); break;
-		case GEANY_KEYS_MENU_GOTOLINE:
-			on_go_to_line1_activate(NULL, NULL); break;
+		case GEANY_KEYS_SEARCH_FINDUSAGE:
+			if (check_current_word())
+				on_find_usage1_activate(NULL, NULL);
+			break;
 	}
 }
 
@@ -1001,13 +1103,13 @@
 }
 
 
-/* common function for keybindings using current word */
-static void cb_func_current_word(guint key_id)
+static gboolean check_current_word(void)
 {
 	gint idx = document_get_cur_idx();
 	gint pos;
 
-	if (idx == -1 || ! doc_list[idx].is_valid) return;
+	if (DOC_IDX_VALID(idx))
+		return FALSE;
 
 	pos = sci_get_current_position(doc_list[idx].sci);
 
@@ -1015,24 +1117,11 @@
 		editor_info.current_word, GEANY_MAX_WORD_LENGTH, NULL);
 
 	if (*editor_info.current_word == 0)
+	{
 		utils_beep();
-	else
-		switch (key_id)
-		{
-			case GEANY_KEYS_POPUP_FINDUSAGE:
-				on_find_usage1_activate(NULL, NULL);
-				break;
-			case GEANY_KEYS_POPUP_GOTOTAGDEFINITION:
-				symbols_goto_tag(editor_info.current_word, TRUE);
-				break;
-			case GEANY_KEYS_POPUP_GOTOTAGDECLARATION:
-				symbols_goto_tag(editor_info.current_word, FALSE);
-				break;
-			case GEANY_KEYS_POPUP_CONTEXTACTION:
-				on_context_action1_activate(GTK_MENU_ITEM(lookup_widget(app->popup_menu,
-					"context_action1")), NULL);
-				break;
-		}
+		return FALSE;
+	}
+	return TRUE;
 }
 
 
@@ -1089,26 +1178,27 @@
 	if (! DOC_IDX_VALID(idx))
 		return;
 
-	if (key_id == GEANY_KEYS_MOVE_TABLEFT)
+	switch (key_id)
 	{
-		gtk_notebook_reorder_child(nb, sci, cur_page - 1);	/* notebook wraps around by default */
-	}
-	else if (key_id == GEANY_KEYS_MOVE_TABRIGHT)
-	{
-		gint npage = cur_page + 1;
+		case GEANY_KEYS_NOTEBOOK_MOVETABLEFT:
+			gtk_notebook_reorder_child(nb, sci, cur_page - 1);	/* notebook wraps around by default */
+			break;
+		case GEANY_KEYS_NOTEBOOK_MOVETABRIGHT:
+		{
+			gint npage = cur_page + 1;
 
-		if (npage == gtk_notebook_get_n_pages(nb))
-			npage = 0;	/* wraparound */
-		gtk_notebook_reorder_child(nb, sci, npage);
+			if (npage == gtk_notebook_get_n_pages(nb))
+				npage = 0;	/* wraparound */
+			gtk_notebook_reorder_child(nb, sci, npage);
+			break;
+		}
+		case GEANY_KEYS_NOTEBOOK_MOVETABFIRST:
+			gtk_notebook_reorder_child(nb, sci, (prefs.tab_order_ltr) ? 0 : -1);
+			break;
+		case GEANY_KEYS_NOTEBOOK_MOVETABLAST:
+			gtk_notebook_reorder_child(nb, sci, (prefs.tab_order_ltr) ? -1 : 0);
+			break;
 	}
-	else if (key_id == GEANY_KEYS_MOVE_TABFIRST)
-	{
-		gtk_notebook_reorder_child(nb, sci, (prefs.tab_order_ltr) ? 0 : -1);
-	}
-	else if (key_id == GEANY_KEYS_MOVE_TABLAST)
-	{
-		gtk_notebook_reorder_child(nb, sci, (prefs.tab_order_ltr) ? -1 : 0);
-	}
 	return;
 }
 
@@ -1145,27 +1235,36 @@
 
 static void cb_func_clipboard(guint key_id)
 {
+	gint idx = document_get_cur_idx();
+
+	if (! DOC_IDX_VALID(idx)) return;
+
 	switch (key_id)
 	{
-		case GEANY_KEYS_MENU_CUT:
+		case GEANY_KEYS_CLIPBOARD_CUT:
 			on_cut1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_COPY:
+		case GEANY_KEYS_CLIPBOARD_COPY:
 			on_copy1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_MENU_PASTE:
+		case GEANY_KEYS_CLIPBOARD_PASTE:
 			on_paste1_activate(NULL, NULL);
 			break;
+		case GEANY_KEYS_CLIPBOARD_COPYLINE:
+			sci_cmd(doc_list[idx].sci, SCI_LINECOPY);
+			break;
+		case GEANY_KEYS_CLIPBOARD_CUTLINE:
+			sci_cmd(doc_list[idx].sci, SCI_LINECUT);
+			break;
 	}
 }
 
 
-/* Common function for editing keybindings that don't change any text, and are
- * useful even when sci doesn't have focus. */
-static void cb_func_edit_global(guint key_id)
+/* Common function for goto keybindings, useful even when sci doesn't have focus. */
+static void cb_func_goto_action(guint key_id)
 {
+	gint cur_line;
 	gint idx = document_get_cur_idx();
-	gint cur_line;
 
 	if (! DOC_IDX_VALID(idx)) return;
 
@@ -1173,17 +1272,26 @@
 
 	switch (key_id)
 	{
-		case GEANY_KEYS_EDIT_GOTOMATCHINGBRACE:
+		case GEANY_KEYS_GOTO_BACK:
+			navqueue_go_back();
+			break;
+		case GEANY_KEYS_GOTO_FORWARD:
+			navqueue_go_forward();
+			break;
+		case GEANY_KEYS_GOTO_LINE:
+			on_go_to_line1_activate(NULL, NULL);
+			break;
+		case GEANY_KEYS_GOTO_MATCHINGBRACE:
 			goto_matching_brace(idx);
 			break;
-		case GEANY_KEYS_EDIT_TOGGLEMARKER:
+		case GEANY_KEYS_GOTO_TOGGLEMARKER:
 		{
 			gboolean set = sci_is_marker_set_at_line(doc_list[idx].sci, cur_line, 1);
 
 			sci_set_marker_at_line(doc_list[idx].sci, cur_line, ! set, 1);
 			break;
 		}
-		case GEANY_KEYS_EDIT_GOTONEXTMARKER:
+		case GEANY_KEYS_GOTO_NEXTMARKER:
 		{
 			gint mline = sci_marker_next(doc_list[idx].sci, cur_line + 1, 1 << 1, TRUE);
 
@@ -1194,7 +1302,7 @@
 			}
 			break;
 		}
-		case GEANY_KEYS_EDIT_GOTOPREVIOUSMARKER:
+		case GEANY_KEYS_GOTO_PREVIOUSMARKER:
 		{
 			gint mline = sci_marker_previous(doc_list[idx].sci, cur_line - 1, 1 << 1, TRUE);
 
@@ -1205,6 +1313,14 @@
 			}
 			break;
 		}
+		case GEANY_KEYS_GOTO_TAGDEFINITION:
+			if (check_current_word())
+				symbols_goto_tag(editor_info.current_word, TRUE);
+			break;
+		case GEANY_KEYS_GOTO_TAGDECLARATION:
+			if (check_current_word())
+				symbols_goto_tag(editor_info.current_word, FALSE);
+			break;
 	}
 }
 
@@ -1230,8 +1346,8 @@
 }
 
 
-/* common function for editing keybindings, only valid when scintilla has focus. */
-static void cb_func_edit(guint key_id)
+/* common function for editor keybindings, only valid when scintilla has focus. */
+static void cb_func_editor_action(guint key_id)
 {
 	gint idx = document_get_cur_idx();
 	GtkWidget *focusw = gtk_window_get_focus(GTK_WINDOW(app->window));
@@ -1241,51 +1357,50 @@
 
 	switch (key_id)
 	{
-		case GEANY_KEYS_EDIT_SCROLLTOLINE:
+		case GEANY_KEYS_EDITOR_UNDO:
+			on_undo1_activate(NULL, NULL);
+			break;
+		case GEANY_KEYS_EDITOR_REDO:
+			on_redo1_activate(NULL, NULL);
+			break;
+		case GEANY_KEYS_EDITOR_SCROLLTOLINE:
 			editor_scroll_to_line(doc_list[idx].sci, -1, 0.5F);
 			break;
-		case GEANY_KEYS_EDIT_SCROLLLINEUP:
+		case GEANY_KEYS_EDITOR_SCROLLLINEUP:
 			sci_cmd(doc_list[idx].sci, SCI_LINESCROLLUP);
 			break;
-		case GEANY_KEYS_EDIT_SCROLLLINEDOWN:
+		case GEANY_KEYS_EDITOR_SCROLLLINEDOWN:
 			sci_cmd(doc_list[idx].sci, SCI_LINESCROLLDOWN);
 			break;
-		case GEANY_KEYS_EDIT_DUPLICATELINE:
+		case GEANY_KEYS_EDITOR_DUPLICATELINE:
 			duplicate_lines(doc_list[idx].sci);
 			break;
-		case GEANY_KEYS_EDIT_DELETELINE:
+		case GEANY_KEYS_EDITOR_DELETELINE:
 			delete_lines(doc_list[idx].sci);
 			break;
-		case GEANY_KEYS_EDIT_COPYLINE:
-			sci_cmd(doc_list[idx].sci, SCI_LINECOPY);
-			break;
-		case GEANY_KEYS_EDIT_CUTLINE:
-			sci_cmd(doc_list[idx].sci, SCI_LINECUT);
-			break;
-		case GEANY_KEYS_EDIT_TRANSPOSELINE:
+		case GEANY_KEYS_EDITOR_TRANSPOSELINE:
 			sci_cmd(doc_list[idx].sci, SCI_LINETRANSPOSE);
 			break;
-		case GEANY_KEYS_EDIT_COMMENTLINETOGGLE:
-			on_menu_toggle_line_commentation1_activate(NULL, NULL);
-			break;
-		case GEANY_KEYS_EDIT_COMMENTLINE:
-			on_menu_comment_line1_activate(NULL, NULL);
-			break;
-		case GEANY_KEYS_EDIT_UNCOMMENTLINE:
-			on_menu_uncomment_line1_activate(NULL, NULL);
-			break;
-		case GEANY_KEYS_EDIT_AUTOCOMPLETE:
+		case GEANY_KEYS_EDITOR_AUTOCOMPLETE:
 			editor_start_auto_complete(idx, sci_get_current_position(doc_list[idx].sci), TRUE);
 			break;
-		case GEANY_KEYS_EDIT_CALLTIP:
+		case GEANY_KEYS_EDITOR_CALLTIP:
 			editor_show_calltip(idx, -1);
 			break;
-		case GEANY_KEYS_EDIT_MACROLIST:
+		case GEANY_KEYS_EDITOR_MACROLIST:
 			editor_show_macro_list(doc_list[idx].sci);
 			break;
+		case GEANY_KEYS_EDITOR_CONTEXTACTION:
+			if (check_current_word())
+				on_context_action1_activate(GTK_MENU_ITEM(lookup_widget(app->popup_menu,
+					"context_action1")), NULL);
+			break;
+		case GEANY_KEYS_EDITOR_SUPPRESSSNIPPETCOMPLETION:
+		{
+			KeyBinding *kb = keybindings_lookup_item(GEANY_KEY_GROUP_EDITOR,
+				GEANY_KEYS_EDITOR_COMPLETESNIPPET);
 
-		case GEANY_KEYS_EDIT_SUPPRESSSNIPPETCOMPLETION:
-			switch (keys[GEANY_KEYS_EDIT_COMPLETESNIPPET]->key)
+			switch (kb->key)
 			{
 				case GDK_space:
 					sci_add_text(doc_list[idx].sci, " ");
@@ -1297,46 +1412,58 @@
 					break;
 			}
 			break;
+		}
+	}
+}
 
-		case GEANY_KEYS_EDIT_SELECTWORD:
-			editor_select_word(doc_list[idx].sci);
+
+/* common function for format keybindings, only valid when scintilla has focus. */
+static void cb_func_format_action(guint key_id)
+{
+	gint idx = document_get_cur_idx();
+	GtkWidget *focusw = gtk_window_get_focus(GTK_WINDOW(app->window));
+
+	/* keybindings only valid when scintilla widget has focus */
+	if (! DOC_IDX_VALID(idx) || focusw != GTK_WIDGET(doc_list[idx].sci)) return;
+
+	switch (key_id)
+	{
+		case GEANY_KEYS_FORMAT_COMMENTLINETOGGLE:
+			on_menu_toggle_line_commentation1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_EDIT_SELECTLINE:
-			editor_select_lines(doc_list[idx].sci, FALSE);
+		case GEANY_KEYS_FORMAT_COMMENTLINE:
+			on_menu_comment_line1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_EDIT_SELECTPARAGRAPH:
-			editor_select_paragraph(doc_list[idx].sci);
+		case GEANY_KEYS_FORMAT_UNCOMMENTLINE:
+			on_menu_uncomment_line1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_EDIT_INSERTALTWHITESPACE:
-			editor_insert_alternative_whitespace(idx);
-			break;
-		case GEANY_KEYS_EDIT_INCREASEINDENT:
+		case GEANY_KEYS_FORMAT_INCREASEINDENT:
 			on_menu_increase_indent1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_EDIT_DECREASEINDENT:
+		case GEANY_KEYS_FORMAT_DECREASEINDENT:
 			on_menu_decrease_indent1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_EDIT_INCREASEINDENTBYSPACE:
+		case GEANY_KEYS_FORMAT_INCREASEINDENTBYSPACE:
 			editor_indentation_by_one_space(idx, -1, FALSE);
 			break;
-		case GEANY_KEYS_EDIT_DECREASEINDENTBYSPACE:
+		case GEANY_KEYS_FORMAT_DECREASEINDENTBYSPACE:
 			editor_indentation_by_one_space(idx, -1, TRUE);
 			break;
-		case GEANY_KEYS_EDIT_AUTOINDENT:
+		case GEANY_KEYS_FORMAT_AUTOINDENT:
 			editor_auto_line_indentation(idx, -1);
 			break;
-		case GEANY_KEYS_EDIT_TOGGLECASE:
+		case GEANY_KEYS_FORMAT_TOGGLECASE:
 			on_toggle_case1_activate(NULL, NULL);
 			break;
-		case GEANY_KEYS_EDIT_SENDTOCMD1:
+		case GEANY_KEYS_FORMAT_SENDTOCMD1:
 			if (ui_prefs.custom_commands && g_strv_length(ui_prefs.custom_commands) > 0)
 				tools_execute_custom_command(idx, ui_prefs.custom_commands[0]);
 			break;
-		case GEANY_KEYS_EDIT_SENDTOCMD2:
+		case GEANY_KEYS_FORMAT_SENDTOCMD2:
 			if (ui_prefs.custom_commands && g_strv_length(ui_prefs.custom_commands) > 1)
 				tools_execute_custom_command(idx, ui_prefs.custom_commands[1]);
 			break;
-		case GEANY_KEYS_EDIT_SENDTOCMD3:
+		case GEANY_KEYS_FORMAT_SENDTOCMD3:
 			if (ui_prefs.custom_commands && g_strv_length(ui_prefs.custom_commands) > 2)
 				tools_execute_custom_command(idx, ui_prefs.custom_commands[2]);
 			break;
@@ -1344,29 +1471,56 @@
 }
 
 
-static void cb_func_menu_replacetabs(G_GNUC_UNUSED guint key_id)
+/* common function for select keybindings, only valid when scintilla has focus. */
+static void cb_func_select_action(guint key_id)
 {
-	on_replace_tabs_activate(NULL, NULL);
-}
+	gint idx = document_get_cur_idx();
+	GtkWidget *focusw = gtk_window_get_focus(GTK_WINDOW(app->window));
 
-static void cb_func_menu_insert_date(G_GNUC_UNUSED guint key_id)
-{
-	gtk_menu_item_activate(GTK_MENU_ITEM(lookup_widget(app->window, "insert_date_custom1")));
+	/* keybindings only valid when scintilla widget has focus */
+	if (! DOC_IDX_VALID(idx) || focusw != GTK_WIDGET(doc_list[idx].sci)) return;
+
+	switch (key_id)
+	{
+		case GEANY_KEYS_SELECT_ALL:
+			on_menu_select_all1_activate(NULL, NULL);
+			break;
+		case GEANY_KEYS_SELECT_WORD:
+			editor_select_word(doc_list[idx].sci);
+			break;
+		case GEANY_KEYS_SELECT_LINE:
+			editor_select_lines(doc_list[idx].sci, FALSE);
+			break;
+		case GEANY_KEYS_SELECT_PARAGRAPH:
+			editor_select_paragraph(doc_list[idx].sci);
+			break;
+	}
 }
 
-static void cb_func_menu_insert_specialchars(G_GNUC_UNUSED guint key_id)
+
+static void cb_func_menu_replacetabs(G_GNUC_UNUSED guint key_id)
 {
-	/** TODO: add plugin keybinding support */
-	/*on_menu_insert_special_chars1_activate(NULL, NULL);*/
+	on_replace_tabs_activate(NULL, NULL);
 }
 
-static void cb_func_nav_back(G_GNUC_UNUSED guint key_id)
+
+/* common function for insert keybindings, only valid when scintilla has focus. */
+static void cb_func_insert_action(guint key_id)
 {
-	navqueue_go_back();
-}
+	gint idx = document_get_cur_idx();
+	GtkWidget *focusw = gtk_window_get_focus(GTK_WINDOW(app->window));
 
-static void cb_func_nav_forward(G_GNUC_UNUSED guint key_id)
-{
-	navqueue_go_forward();
+	/* keybindings only valid when scintilla widget has focus */
+	if (! DOC_IDX_VALID(idx) || focusw != GTK_WIDGET(doc_list[idx].sci)) return;
+
+	switch (key_id)
+	{
+		case GEANY_KEYS_INSERT_ALTWHITESPACE:
+			editor_insert_alternative_whitespace(idx);
+			break;
+		case GEANY_KEYS_INSERT_DATE:
+			gtk_menu_item_activate(GTK_MENU_ITEM(lookup_widget(app->window, "insert_date_custom1")));
+			break;
+	}
 }
 

Modified: trunk/src/keybindings.h
===================================================================
--- trunk/src/keybindings.h	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/src/keybindings.h	2008-03-12 17:07:43 UTC (rev 2324)
@@ -21,30 +21,15 @@
  * $Id$
  */
 
+/**
+ * @file keybindings.h
+ * Configurable keyboard shortcuts.
+ **/
 
+
 #ifndef GEANY_KEYBINDINGS_H
 #define GEANY_KEYBINDINGS_H 1
 
-typedef void (*KBCallback) (guint key_id);
-
-/* holds all user-definable key bindings */
-typedef struct KeyBinding
-{
-	guint key;
-	GdkModifierType mods;
-	/* at the moment only needed as keys for the configuration file because indices or translatable
-	 * strings as keys are not very useful */
-	const gchar *name;
-	const gchar *label;
-	/* function pointer to a callback function, just to keep the code in keypress event
-	 * callback function clear */
-	KBCallback cb_func;
-	/* string to use as a section name in the preferences dialog in keybinding treeview as well as
-	 * in the keybinding help dialog, set only for the first binding in the section */
-	gchar *section;
-} KeyBinding;
-
-
 /* allowed modifier keys (especially NOT Caps lock, no Num lock) */
 #if GTK_CHECK_VERSION(2, 10, 0)
 # define GEANY_KEYS_MODIFIER_MASK (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | \
@@ -53,59 +38,234 @@
 # define GEANY_KEYS_MODIFIER_MASK (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)
 #endif
 
-/* Plugin API */
-typedef enum
+
+typedef void (*KeyCallback) (guint key_id);
+
+/** Represents a single keybinding action */
+typedef struct KeyBinding
 {
-	GEANY_KEYS_GROUP_FILE = 0,
-	GEANY_KEYS_MENU_NEW = GEANY_KEYS_GROUP_FILE,
-	GEANY_KEYS_MENU_OPEN,
-	GEANY_KEYS_MENU_OPENSELECTED,
-	GEANY_KEYS_MENU_SAVE,
-	GEANY_KEYS_MENU_SAVEAS,
-	GEANY_KEYS_MENU_SAVEALL,
-	GEANY_KEYS_MENU_PRINT,
-	GEANY_KEYS_MENU_CLOSE,
-	GEANY_KEYS_MENU_CLOSEALL,
-	GEANY_KEYS_MENU_RELOADFILE,
-	GEANY_KEYS_MENU_PROJECTPROPERTIES,
+	guint key;				/**< Key value in lower-case, such as @c GDK_a */
+	GdkModifierType mods;	/**< Modifier keys, such as @c GDK_CONTROL_MASK */
+	const gchar *name;		/**< Key name for the configuration file, such as @c "menu_new" */
+	const gchar *label;		/**< Label used in the preferences dialog keybindings tab */
+	KeyCallback callback;	/**< Callback function called when the key combination is pressed */
+	GtkWidget *menu_item;	/**< Menu item widget for setting the menu accelerator */
+} KeyBinding;
 
-	GEANY_KEYS_GROUP_EDIT,
-	GEANY_KEYS_MENU_UNDO = GEANY_KEYS_GROUP_EDIT,
-	GEANY_KEYS_MENU_REDO,
-	GEANY_KEYS_MENU_CUT,
-	GEANY_KEYS_MENU_COPY,
-	GEANY_KEYS_MENU_PASTE,
-	GEANY_KEYS_MENU_SELECTALL,
-	GEANY_KEYS_MENU_INSERTDATE,
-	GEANY_KEYS_MENU_PREFERENCES,
 
-	GEANY_KEYS_GROUP_SEARCH,
-	GEANY_KEYS_MENU_FIND = GEANY_KEYS_GROUP_SEARCH,
-	GEANY_KEYS_MENU_FINDNEXT,
-	GEANY_KEYS_MENU_FINDPREVIOUS,
-	GEANY_KEYS_MENU_FINDINFILES,
-	GEANY_KEYS_MENU_REPLACE,
-	GEANY_KEYS_MENU_FINDNEXTSEL,
-	GEANY_KEYS_MENU_FINDPREVSEL,
-	GEANY_KEYS_MENU_NEXTMESSAGE,
-	GEANY_KEYS_MENU_GOTOLINE,
+/** A collection of keybindings grouped together. */
+typedef struct KeyBindingGroup
+{
+	const gchar *name;		/**< Group name used in the configuration file, such as @c "html_chars" */
+	const gchar *label;		/**< Group label used in the preferences dialog keybindings tab */
+	gsize count;			/**< Count of KeyBinding structs in @a keys */
+	KeyBinding *keys;		/**< Fixed array of KeyBinding structs */
+}
+KeyBindingGroup;
 
-	GEANY_KEYS_GROUP_VIEW,
-	GEANY_KEYS_MENU_TOGGLEALL = GEANY_KEYS_GROUP_VIEW,
-	GEANY_KEYS_MENU_FULLSCREEN,
-	GEANY_KEYS_MENU_MESSAGEWINDOW,
-	GEANY_KEYS_MENU_SIDEBAR,
-	GEANY_KEYS_MENU_ZOOMIN,
-	GEANY_KEYS_MENU_ZOOMOUT,
+extern GPtrArray *keybinding_groups;	/* array of KeyBindingGroup pointers */
 
-	GEANY_KEYS_GROUP_DOCUMENT,
-	GEANY_KEYS_MENU_REPLACETABS = GEANY_KEYS_GROUP_DOCUMENT,
-	GEANY_KEYS_MENU_FOLDALL,
-	GEANY_KEYS_MENU_UNFOLDALL,
-	GEANY_KEYS_RELOADTAGLIST,
 
-	GEANY_KEYS_GROUP_BUILD,
-	GEANY_KEYS_BUILD_COMPILE = GEANY_KEYS_GROUP_BUILD,
+/** Keybinding group IDs */
+enum
+{
+	GEANY_KEY_GROUP_FILE,
+	GEANY_KEY_GROUP_PROJECT,
+	GEANY_KEY_GROUP_EDITOR,
+	GEANY_KEY_GROUP_CLIPBOARD,
+	GEANY_KEY_GROUP_SELECT,
+	GEANY_KEY_GROUP_FORMAT,
+	GEANY_KEY_GROUP_INSERT,
+	GEANY_KEY_GROUP_SETTINGS,
+	GEANY_KEY_GROUP_SEARCH,
+	GEANY_KEY_GROUP_GOTO,
+	GEANY_KEY_GROUP_VIEW,
+	GEANY_KEY_GROUP_FOCUS,
+	GEANY_KEY_GROUP_NOTEBOOK,
+	GEANY_KEY_GROUP_DOCUMENT,
+	GEANY_KEY_GROUP_BUILD,
+	GEANY_KEY_GROUP_TOOLS,
+	GEANY_KEY_GROUP_HELP,
+	GEANY_KEY_GROUP_COUNT
+};
+
+/** File group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_FILE_NEW,
+	GEANY_KEYS_FILE_OPEN,
+	GEANY_KEYS_FILE_OPENSELECTED,
+	GEANY_KEYS_FILE_SAVE,
+	GEANY_KEYS_FILE_SAVEAS,
+	GEANY_KEYS_FILE_SAVEALL,
+	GEANY_KEYS_FILE_PRINT,
+	GEANY_KEYS_FILE_CLOSE,
+	GEANY_KEYS_FILE_CLOSEALL,
+	GEANY_KEYS_FILE_RELOAD,
+	GEANY_KEYS_FILE_COUNT
+};
+
+/** Project group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_PROJECT_PROPERTIES,
+	GEANY_KEYS_PROJECT_COUNT
+};
+
+/** Editor group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_EDITOR_UNDO,
+	GEANY_KEYS_EDITOR_REDO,
+	GEANY_KEYS_EDITOR_DELETELINE,
+	GEANY_KEYS_EDITOR_DUPLICATELINE,
+	GEANY_KEYS_EDITOR_TRANSPOSELINE,
+	GEANY_KEYS_EDITOR_SCROLLTOLINE,
+	GEANY_KEYS_EDITOR_SCROLLLINEUP,
+	GEANY_KEYS_EDITOR_SCROLLLINEDOWN,
+	GEANY_KEYS_EDITOR_COMPLETESNIPPET,
+	GEANY_KEYS_EDITOR_SUPPRESSSNIPPETCOMPLETION,
+	GEANY_KEYS_EDITOR_CONTEXTACTION,
+	GEANY_KEYS_EDITOR_AUTOCOMPLETE,
+	GEANY_KEYS_EDITOR_CALLTIP,
+	GEANY_KEYS_EDITOR_MACROLIST,
+	GEANY_KEYS_EDITOR_COUNT
+};
+
+/** Clipboard group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_CLIPBOARD_CUT,
+	GEANY_KEYS_CLIPBOARD_COPY,
+	GEANY_KEYS_CLIPBOARD_PASTE,
+	GEANY_KEYS_CLIPBOARD_CUTLINE,
+	GEANY_KEYS_CLIPBOARD_COPYLINE,
+	GEANY_KEYS_CLIPBOARD_COUNT
+};
+
+/** Select group keybinding command IDs */
+enum
+{
+
+	GEANY_KEYS_SELECT_ALL,
+	GEANY_KEYS_SELECT_WORD,
+	GEANY_KEYS_SELECT_LINE,
+	GEANY_KEYS_SELECT_PARAGRAPH,
+	GEANY_KEYS_SELECT_COUNT
+};
+
+/** Format group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_FORMAT_TOGGLECASE,
+	GEANY_KEYS_FORMAT_COMMENTLINETOGGLE,
+	GEANY_KEYS_FORMAT_COMMENTLINE,
+	GEANY_KEYS_FORMAT_UNCOMMENTLINE,
+	GEANY_KEYS_FORMAT_INCREASEINDENT,
+	GEANY_KEYS_FORMAT_DECREASEINDENT,
+	GEANY_KEYS_FORMAT_INCREASEINDENTBYSPACE,
+	GEANY_KEYS_FORMAT_DECREASEINDENTBYSPACE,
+	GEANY_KEYS_FORMAT_AUTOINDENT,
+	GEANY_KEYS_FORMAT_SENDTOCMD1,
+	GEANY_KEYS_FORMAT_SENDTOCMD2,
+	GEANY_KEYS_FORMAT_SENDTOCMD3,
+	GEANY_KEYS_FORMAT_COUNT
+};
+
+/** Insert group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_INSERT_DATE,
+	GEANY_KEYS_INSERT_ALTWHITESPACE,
+	GEANY_KEYS_INSERT_COUNT
+};
+
+/** Settings group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_SETTINGS_PREFERENCES,
+	GEANY_KEYS_SETTINGS_COUNT
+};
+
+/** Search group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_SEARCH_FIND,
+	GEANY_KEYS_SEARCH_FINDNEXT,
+	GEANY_KEYS_SEARCH_FINDPREVIOUS,
+	GEANY_KEYS_SEARCH_FINDINFILES,
+	GEANY_KEYS_SEARCH_REPLACE,
+	GEANY_KEYS_SEARCH_FINDNEXTSEL,
+	GEANY_KEYS_SEARCH_FINDPREVSEL,
+	GEANY_KEYS_SEARCH_NEXTMESSAGE,
+	GEANY_KEYS_SEARCH_FINDUSAGE,
+	GEANY_KEYS_SEARCH_COUNT
+};
+
+/** Go To group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_GOTO_FORWARD,
+	GEANY_KEYS_GOTO_BACK,
+	GEANY_KEYS_GOTO_LINE,
+	GEANY_KEYS_GOTO_MATCHINGBRACE,
+	GEANY_KEYS_GOTO_TOGGLEMARKER,
+	GEANY_KEYS_GOTO_NEXTMARKER,
+	GEANY_KEYS_GOTO_PREVIOUSMARKER,
+	GEANY_KEYS_GOTO_TAGDEFINITION,
+	GEANY_KEYS_GOTO_TAGDECLARATION,
+	GEANY_KEYS_GOTO_COUNT
+};
+
+/** View group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_VIEW_TOGGLEALL,
+	GEANY_KEYS_VIEW_FULLSCREEN,
+	GEANY_KEYS_VIEW_MESSAGEWINDOW,
+	GEANY_KEYS_VIEW_SIDEBAR,
+	GEANY_KEYS_VIEW_ZOOMIN,
+	GEANY_KEYS_VIEW_ZOOMOUT,
+	GEANY_KEYS_VIEW_COUNT
+};
+
+/** Focus group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_FOCUS_EDITOR,
+	GEANY_KEYS_FOCUS_SCRIBBLE,
+	GEANY_KEYS_FOCUS_VTE,
+	GEANY_KEYS_FOCUS_SEARCHBAR,
+	GEANY_KEYS_FOCUS_COUNT
+};
+
+/** Notebook Tab group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_NOTEBOOK_SWITCHTABLEFT,
+	GEANY_KEYS_NOTEBOOK_SWITCHTABRIGHT,
+	GEANY_KEYS_NOTEBOOK_SWITCHTABLASTUSED,
+	GEANY_KEYS_NOTEBOOK_MOVETABLEFT,
+	GEANY_KEYS_NOTEBOOK_MOVETABRIGHT,
+	GEANY_KEYS_NOTEBOOK_MOVETABFIRST,
+	GEANY_KEYS_NOTEBOOK_MOVETABLAST,
+	GEANY_KEYS_NOTEBOOK_COUNT
+};
+
+/** Document group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_DOCUMENT_REPLACETABS,
+	GEANY_KEYS_DOCUMENT_FOLDALL,
+	GEANY_KEYS_DOCUMENT_UNFOLDALL,
+	GEANY_KEYS_DOCUMENT_RELOADTAGLIST,
+	GEANY_KEYS_DOCUMENT_COUNT
+};
+
+/** Build group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_BUILD_COMPILE,
 	GEANY_KEYS_BUILD_LINK,
 	GEANY_KEYS_BUILD_MAKE,
 	GEANY_KEYS_BUILD_MAKEOWNTARGET,
@@ -114,86 +274,38 @@
 	GEANY_KEYS_BUILD_RUN,
 	GEANY_KEYS_BUILD_RUN2,
 	GEANY_KEYS_BUILD_OPTIONS,
+	GEANY_KEYS_BUILD_COUNT
+};
 
-	GEANY_KEYS_GROUP_TOOLS,
-	GEANY_KEYS_MENU_OPENCOLORCHOOSER = GEANY_KEYS_GROUP_TOOLS,
-	GEANY_KEYS_MENU_INSERTSPECIALCHARS,
+/** Tools group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_TOOLS_OPENCOLORCHOOSER,
+	GEANY_KEYS_TOOLS_COUNT
+};
 
-	GEANY_KEYS_GROUP_HELP,
-	GEANY_KEYS_MENU_HELP = GEANY_KEYS_GROUP_HELP,
+/** Help group keybinding command IDs */
+enum
+{
+	GEANY_KEYS_HELP_HELP,
+	GEANY_KEYS_HELP_COUNT
+};
 
-	GEANY_KEYS_GROUP_FOCUS,
-	GEANY_KEYS_SWITCH_EDITOR = GEANY_KEYS_GROUP_FOCUS,
-	GEANY_KEYS_SWITCH_SCRIBBLE,
-	GEANY_KEYS_SWITCH_VTE,
-	GEANY_KEYS_SWITCH_SEARCH_BAR,
 
-	GEANY_KEYS_GROUP_TABS,
-	GEANY_KEYS_SWITCH_TABLEFT = GEANY_KEYS_GROUP_TABS,
-	GEANY_KEYS_SWITCH_TABRIGHT,
-	GEANY_KEYS_SWITCH_TABLASTUSED,
-	GEANY_KEYS_MOVE_TABLEFT,
-	GEANY_KEYS_MOVE_TABRIGHT,
-	GEANY_KEYS_MOVE_TABFIRST,
-	GEANY_KEYS_MOVE_TABLAST,
+void keybindings_init(void);
 
-	GEANY_KEYS_GROUP_EDITING,
-	GEANY_KEYS_EDIT_TOGGLECASE = GEANY_KEYS_GROUP_EDITING,
-	GEANY_KEYS_EDIT_DUPLICATELINE,
-	GEANY_KEYS_EDIT_DELETELINE,
-	GEANY_KEYS_EDIT_COPYLINE,
-	GEANY_KEYS_EDIT_CUTLINE,
-	GEANY_KEYS_EDIT_TRANSPOSELINE,
-	GEANY_KEYS_EDIT_COMMENTLINETOGGLE,
-	GEANY_KEYS_EDIT_COMMENTLINE,
-	GEANY_KEYS_EDIT_UNCOMMENTLINE,
-	GEANY_KEYS_EDIT_INCREASEINDENT,
-	GEANY_KEYS_EDIT_DECREASEINDENT,
-	GEANY_KEYS_EDIT_INCREASEINDENTBYSPACE,
-	GEANY_KEYS_EDIT_DECREASEINDENTBYSPACE,
-	GEANY_KEYS_EDIT_AUTOINDENT,
-	GEANY_KEYS_EDIT_SENDTOCMD1,
-	GEANY_KEYS_EDIT_SENDTOCMD2,
-	GEANY_KEYS_EDIT_SENDTOCMD3,
-	GEANY_KEYS_EDIT_GOTOMATCHINGBRACE,
-	GEANY_KEYS_EDIT_TOGGLEMARKER,
-	GEANY_KEYS_EDIT_GOTONEXTMARKER,
-	GEANY_KEYS_EDIT_GOTOPREVIOUSMARKER,
-	GEANY_KEYS_EDIT_SELECTWORD,
-	GEANY_KEYS_EDIT_SELECTLINE,
-	GEANY_KEYS_EDIT_SELECTPARAGRAPH,
-	GEANY_KEYS_EDIT_SCROLLTOLINE,
-	GEANY_KEYS_EDIT_SCROLLLINEUP,
-	GEANY_KEYS_EDIT_SCROLLLINEDOWN,
-	GEANY_KEYS_EDIT_INSERTALTWHITESPACE,
-	GEANY_KEYS_POPUP_FINDUSAGE,
-	GEANY_KEYS_POPUP_CONTEXTACTION,
+void keybindings_load_keyfile(void);
 
-	GEANY_KEYS_GROUP_TAGS,
-	GEANY_KEYS_EDIT_AUTOCOMPLETE = GEANY_KEYS_GROUP_TAGS,
-	GEANY_KEYS_EDIT_CALLTIP,
-	GEANY_KEYS_EDIT_MACROLIST,
-	GEANY_KEYS_EDIT_COMPLETESNIPPET,
-	GEANY_KEYS_EDIT_SUPPRESSSNIPPETCOMPLETION,
-	GEANY_KEYS_POPUP_GOTOTAGDEFINITION,
-	GEANY_KEYS_POPUP_GOTOTAGDECLARATION,
+void keybindings_free(void);
 
-	GEANY_KEYS_GROUP_OTHER,
-	GEANY_KEYS_NAV_FORWARD = GEANY_KEYS_GROUP_OTHER,
-	GEANY_KEYS_NAV_BACK,
-	GEANY_MAX_KEYS
-}
-GeanyKeyCommand;
+void keybindings_set_item(KeyBindingGroup *group, gsize key_id,
+		KeyCallback callback, guint key, GdkModifierType mod,
+		const gchar *name, const gchar *label, GtkWidget *menu_item);
 
-extern KeyBinding *keys[GEANY_MAX_KEYS];
+void keybindings_send_command(guint group_id, guint key_id);
 
+KeyBinding *keybindings_lookup_item(guint group_id, guint key_id);
 
-void keybindings_init(void);
-
-void keybindings_free(void);
-
-void keybindings_cmd(GeanyKeyCommand cmd_id);
-
 /* just write the content of the keys array to the config file */
 void keybindings_write_to_file(void);
 

Modified: trunk/src/main.c
===================================================================
--- trunk/src/main.c	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/src/main.c	2008-03-12 17:07:43 UTC (rev 2324)
@@ -853,6 +853,9 @@
 		plugins_init();
 #endif
 
+	/* load keybinding settings after plugins have added their groups */
+	keybindings_load_keyfile();
+
 	/* load any command line files or session files */
 	main_status.opening_session_files = TRUE;
 	if (! open_cl_files(argc, argv))

Modified: trunk/src/plugindata.h
===================================================================
--- trunk/src/plugindata.h	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/src/plugindata.h	2008-03-12 17:07:43 UTC (rev 2324)
@@ -22,28 +22,27 @@
  * $Id$
  */
 
-#ifndef PLUGIN_H
-#define PLUGIN_H
-
-
 /**
- *  @file plugindata.h
- *  This file defines the plugin API, the interface between Geany and its plugins.
- *  For detailed documentation of the plugin system please read the plugin
- *  API documentation.
+ * @file plugindata.h
+ * This file defines the plugin API, the interface between Geany and its plugins.
+ * For detailed documentation of the plugin system please read the plugin
+ * API documentation.
  **/
 
 
+#ifndef PLUGIN_H
+#define PLUGIN_H
+
 /* The API version should be incremented whenever any plugin data types below are
  * modified or appended to. */
-static const gint api_version = 45;
+static const gint api_version = 48;
 
 /* 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
  * are only appended, as this doesn't affect existing fields. */
-static const gint abi_version = 20;
+static const gint abi_version = 22;
 
-/* This performs runtime checks that try to ensure:
+/** This performs runtime checks that try to ensure:
  * 1. Geany ABI data types are compatible with this plugin.
  * 2. Geany sources provide the required API for this plugin. */
 /* TODO: if possible, the API version should be checked at compile time, not runtime. */
@@ -92,6 +91,27 @@
 	}
 
 
+/** Declare and initialise a keybinding group.
+ * @code KeyBindingGroup plugin_key_group[1]; @endcode
+ * You must then set the @c plugin_key_group::keys[] entries for the group in init().
+ * The @c plugin_key_group::label field is set by Geany after @c init()
+ * is called, to the name of the plugin.
+ * @param group_name A unique group name (without quotes) to be used in the
+ * configuration file, such as @c html_chars.
+ * @param key_count	The number of keybindings the group will hold.
+ * @note This is a single element array for implementation reasons,
+ * but you can treat it like a pointer. */
+#define PLUGIN_KEY_GROUP(group_name, key_count) \
+	static KeyBinding plugin_keys[key_count]; \
+	\
+	/* We have to declare plugin_key_group as a single element array.
+	 * Declaring as a pointer to a struct doesn't work with g_module_symbol(). */ \
+	KeyBindingGroup plugin_key_group[1] = \
+	{ \
+		{G_STRINGIFY(group_name), NULL, key_count, plugin_keys} \
+	};
+
+
 /** callback array entry */
 typedef struct GeanyCallback
 {
@@ -309,10 +329,15 @@
 EncodingFuncs;
 
 
+struct KeyBindingGroup;
+typedef void (*_KeyCallback) (guint key_id);
+
 typedef struct KeybindingFuncs
 {
-	/* See GeanyKeyCommand enum for cmd_id. */
-	void		(*send_command) (gint cmd_id);
+	void		(*send_command) (guint group_id, guint key_id);
+	void		(*set_item) (struct KeyBindingGroup *group, gsize key_id,
+					_KeyCallback callback, guint key, GdkModifierType mod,
+					const gchar *name, const gchar *label, GtkWidget *menu_item);
 }
 KeybindingFuncs;
 

Modified: trunk/src/plugins.c
===================================================================
--- trunk/src/plugins.c	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/src/plugins.c	2008-03-12 17:07:43 UTC (rev 2324)
@@ -53,11 +53,9 @@
 #include "encodings.h"
 #include "search.h"
 #include "highlighting.h"
+#include "keybindings.h"
 
-void keybindings_cmd(gint cmd_id);	/* don't require keybindings.h enum in plugindata.h */
 
-
-
 #ifdef G_OS_WIN32
 # define PLUGIN_EXT "dll"
 #else
@@ -68,20 +66,21 @@
 typedef struct Plugin
 {
 	GModule 	*module;
-	gchar		*filename;		/* plugin filename (/path/libname.so) */
+	gchar		*filename;				/* plugin filename (/path/libname.so) */
 	PluginFields	fields;
-	gulong		*signal_ids;		/* signal IDs to disconnect when unloading */
+	gulong		*signal_ids;			/* signal IDs to disconnect when unloading */
 	gsize		signal_ids_len;
+	KeyBindingGroup	*key_group;
 
-	PluginInfo*	(*info) (void);	/* Returns plugin name, description */
+	PluginInfo*	(*info) (void);			/* Returns plugin name, description */
 	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	(*cleanup) (void);			/* Called when the plugin is disabled or when Geany exits */
 }
 Plugin;
 
 
-static GList *plugin_list = NULL; /* list of all available, loadable plugins */
+static GList *plugin_list = NULL; 		/* list of all available, loadable plugins */
 static GList *active_plugin_list = NULL; /* list of only actually loaded plugins */
 static GtkWidget *separator = NULL;
 static void pm_show_dialog(GtkMenuItem *menuitem, gpointer user_data);
@@ -189,7 +188,8 @@
 };
 
 static KeybindingFuncs keybindings_funcs = {
-	&keybindings_cmd
+	&keybindings_send_command,
+	&keybindings_set_item
 };
 
 static TagManagerFuncs tagmanager_funcs = {
@@ -360,6 +360,15 @@
 
 
 static void
+add_kb_group(Plugin *plugin)
+{
+	g_ptr_array_add(keybinding_groups, plugin->key_group);
+
+	plugin->key_group->label = plugin->info()->name;
+}
+
+
+static void
 plugin_init(Plugin *plugin)
 {
 	GeanyCallback *callbacks;
@@ -377,6 +386,11 @@
 	if (callbacks)
 		add_callbacks(plugin, callbacks);
 
+	g_module_symbol(plugin->module, "plugin_key_group",
+		(void *) &plugin->key_group);
+	if (plugin->key_group)
+		add_kb_group(plugin);
+
 	active_plugin_list = g_list_append(active_plugin_list, plugin);
 
 	geany_debug("Loaded:   %s (%s)", plugin->filename,
@@ -485,16 +499,20 @@
 	g_return_if_fail(plugin);
 	g_return_if_fail(plugin->module);
 
-	if (g_list_find(active_plugin_list, plugin))
-	{	/* only do cleanup if the plugin was actually loaded */
-		if (plugin->cleanup)
-			plugin->cleanup();
+	/* only do cleanup if the plugin was actually loaded */
+	if (! g_list_find(active_plugin_list, plugin))
+		return;
 
-		remove_callbacks(plugin);
+	if (plugin->cleanup)
+		plugin->cleanup();
 
-		active_plugin_list = g_list_remove(active_plugin_list, plugin);
-		geany_debug("Unloaded: %s", plugin->filename);
-	}
+	remove_callbacks(plugin);
+
+	if (plugin->key_group)
+		g_ptr_array_remove_fast(keybinding_groups, plugin->key_group);
+
+	active_plugin_list = g_list_remove(active_plugin_list, plugin);
+	geany_debug("Unloaded: %s", plugin->filename);
 }
 
 

Modified: trunk/src/prefs.c
===================================================================
--- trunk/src/prefs.c	2008-03-12 13:37:39 UTC (rev 2323)
+++ trunk/src/prefs.c	2008-03-12 17:07:43 UTC (rev 2324)
@@ -73,7 +73,8 @@
 static void on_cell_edited(GtkCellRendererText *cellrenderertext, gchar *path, gchar *new_text, gpointer user_data);
 static gboolean on_keytype_dialog_response(GtkWidget *dialog, GdkEventKey *event, gpointer user_data);
 static void on_dialog_response(GtkWidget *dialog, gint response, gpointer user_data);
-static gboolean find_duplicate(guint idx, guint key, GdkModifierType mods, const gchar *action);
+static gboolean find_duplicate(KeyBinding *search_kb,
+		guint key, GdkModifierType mods, const gchar *action);
 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_use_folding_toggled(GtkToggleButton *togglebutton, gpointer user_data);
@@ -127,25 +128,30 @@
 static void init_keybindings(void)
 {
 	GtkTreeIter parent, iter;
-	gint i;
-	gchar *key_string;
+	gsize g, i;
 
 	if (store == NULL)
 		init_kb_tree();
 
-	for (i = 0; i < GEANY_MAX_KEYS; i++)
+	for (g = 0; g < keybinding_groups->len; g++)
 	{
-		if (keys[i]->section != NULL)
+		KeyBindingGroup *group = g_ptr_array_index(keybinding_groups, g);
+
+		gtk_tree_store_append(store, &parent, NULL);
+		gtk_tree_store_set(store, &parent, KB_TREE_ACTION, group->label,
+			KB_TREE_INDEX, g, -1);
+
+		for (i = 0; i < group->count; i++)
 		{
-			gtk_tree_store_append(store, &parent, NULL);
-			gtk_tree_store_set(store, &parent, KB_TREE_ACTION, keys[i]->section, -1);
+			KeyBinding *kb = &group->keys[i];
+			gchar *key_string;
+
+			key_string = gtk_accelerator_name(kb->key, kb->mods);
+			gtk_tree_store_append(store, &iter, &parent);
+			gtk_tree_store_set(store, &iter, KB_TREE_ACTION, kb->label,
+				KB_TREE_SHORTCUT, key_string, KB_TREE_INDEX, i, -1);
+			g_free(key_string);
 		}
-
-		key_string = gtk_accelerator_name(keys[i]->key, keys[i]->mods);
-		gtk_tree_store_append(store, &iter, &parent);
-		gtk_tree_store_set(store, &iter, KB_TREE_ACTION, keys[i]->label,
-			KB_TREE_SHORTCUT, key_string, KB_TREE_INDEX, i, -1);
-		g_free(key_string);
 	}
 	gtk_tree_view_expand_all(GTK_TREE_VIEW(tree));
 }
@@ -1065,7 +1071,7 @@
 			return TRUE;
 		}
 
-		gtk_tree_model_get(model, &g_iter, 0, &name, -1);
+		gtk_tree_model_get(model, &g_iter, KB_TREE_ACTION, &name, -1);
 		if (name != NULL)
 		{
 			GtkWidget *dialog;
@@ -1103,33 +1109,48 @@
 }
 
 
+static KeyBinding *lookup_kb_from_iter(G_GNUC_UNUSED GtkTreeModel *model, GtkTreeIter *iter)
+{
+	guint group_idx, keybinding_idx;
+	GtkTreeIter parent;
+
+	/* get kb index */
+	gtk_tree_model_get(GTK_TREE_MODEL(store), iter, KB_TREE_INDEX, &keybinding_idx, -1);
+
+	/* lookup the parent to get group index */
+	gtk_tree_model_iter_parent(GTK_TREE_MODEL(store), &parent, iter);
+	gtk_tree_model_get(GTK_TREE_MODEL(store), &parent, KB_TREE_INDEX, &group_idx, -1);
+
+	return keybindings_lookup_item(group_idx, keybinding_idx);
+}
+
+
 static void on_cell_edited(GtkCellRendererText *cellrenderertext, gchar *path, gchar *new_text, gpointer user_data)
 {
 	if (path != NULL && new_text != NULL)
 	{
-		guint idx;
+		GtkTreeIter iter;
 		guint lkey;
 		GdkModifierType lmods;
-		GtkTreeIter iter;
+		KeyBinding *kb;
 
 		gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path);
 		if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(store), &iter))
-			return;
+			return;	/* ignore group items */
 
 		gtk_accelerator_parse(new_text, &lkey, &lmods);
 
-		/* get index */
-		gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 2, &idx, -1);
+		kb = lookup_kb_from_iter(GTK_TREE_MODEL(store), &iter);
 
-		if (find_duplicate(idx, lkey, lmods, new_text))
+		if (find_duplicate(kb, lkey, lmods, new_text))
 			return;
 
 		/* set the values here, because of the above check, setting it in
 		 * gtk_accelerator_parse would return a wrong key combination if it is duplicate */
-		keys[idx]->key = lkey;
-		keys[idx]->mods = lmods;
+		kb->key = lkey;
+		kb->mods = lmods;
 
-		gtk_tree_store_set(store, &iter, 1, new_text, -1);
+		gtk_tree_store_set(store, &iter, KB_TREE_SHORTCUT, new_text, -1);
 
 		edited = TRUE;
 	}
@@ -1155,29 +1176,28 @@
 }
 
 
-static void on_dialog_response(GtkWidget *dialog, gint response, gpointer iter)
+static void on_dialog_response(GtkWidget *dialog, gint response, G_GNUC_UNUSED gpointer iter)
 {
 	if (response == GTK_RESPONSE_ACCEPT)
 	{
-		guint idx;
 		guint lkey;
 		GdkModifierType lmods;
+		KeyBinding *kb;
 
-		/* get index */
-		gtk_tree_model_get(GTK_TREE_MODEL(store), &g_iter, 2, &idx, -1);
+		kb = lookup_kb_from_iter(GTK_TREE_MODEL(store), &g_iter);
 
 		gtk_accelerator_parse(gtk_label_get_text(GTK_LABEL(dialog_label)), &lkey, &lmods);
 
-		if (find_duplicate(idx, lkey, lmods, gtk_label_get_text(GTK_LABEL(dialog_label))))
+		if (find_duplicate(kb, lkey, lmods, gtk_label_get_text(GTK_LABEL(dialog_label))))
 			return;
 
 		/* set the values here, because of the above check, setting it in
 		 * gtk_accelerator_parse would return a wrong key combination if it is duplicate */
-		keys[idx]->key = lkey;
-		keys[idx]->mods = lmods;
+		kb->key = lkey;
+		kb->mods = lmods;
 
 		gtk_tree_store_set(store, &g_iter,
-				1, gtk_label_get_text(GTK_LABEL(dialog_label)), -1);
+			KB_TREE_SHORTCUT, gtk_label_get_text(GTK_LABEL(dialog_label)), -1);
 
 		g_free(dialog_key_name);
 		dialog_key_name = NULL;
@@ -1188,63 +1208,81 @@
 }
 
 
-static gboolean find_iter(guint i, GtkTreeIter *iter)
+/* Look for a (1st-level) child of parent whose KB_TREE_INDEX matches i,
+ * setting iter to point to the node if found.
+ * If parent is NULL, look for a parent node whose KB_TREE_INDEX matches i. */
+static gboolean find_child_iter(GtkTreeIter *parent, guint i, GtkTreeIter *iter)
 {
 	GtkTreeModel *model = GTK_TREE_MODEL(store);
 	guint idx;
-	GtkTreeIter parent;
 
-	if (! gtk_tree_model_get_iter_first(model, &parent))
-		return FALSE;	/* no items */
+	/* get first child of parent */
+	if (! gtk_tree_model_iter_children(model, iter, parent))
+		return FALSE;
 
-	while (TRUE)
+	while (TRUE)	/* foreach child */
 	{
-		if (! gtk_tree_model_iter_children(model, iter, &parent))
-			return FALSE;
-
-		while (TRUE)
-		{
-			gtk_tree_model_get(model, iter, 2, &idx, -1);
-			if (idx == i)
-				return TRUE;
-			if (! gtk_tree_model_iter_next(model, iter))
-				break;
-		}
-		if (! gtk_tree_model_iter_next(model, &parent))
-			return FALSE;
+		gtk_tree_model_get(model, iter, KB_TREE_INDEX, &idx, -1);
+		if (idx == i)
+			return TRUE;
+		if (! gtk_tree_model_iter_next(model, iter))

@@ Diff output truncated at 100000 characters. @@

This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.



More information about the Commits mailing list