On Wed, 18 Aug 2010 09:56:45 +1000%
Lex Trotman <ele...@gmail.com> wrote:

> On 18 August 2010 00:35, Nick Treleaven
> <nick.trelea...@btinternet.com> wrote:
> > On Tue, 17 Aug 2010 10:08:08 +1000
> > Lex Trotman <ele...@gmail.com> wrote:
> >
> >> >> If we do want snippet keybindings perhaps it would be better to
> >> >> integrate with the existing keybindings rather than storing them
> >> >> separately - then users could configure them in the normal way.
> >> >>
> >> >
> >> > It's worth thinking.
> >>
> >> This could be a bit of work since the number of snippets is
> >> variable and the keybindings GUI is fixed.  I had a quick look a
> >> while ago with the view of allowing the extra build commands to
> >> have keybindings but never came up with a simple way of handling
> >> variable numbers of commands.
> >>
> >> If you decide to add a general way of adding variable numbers of
> >> entries in the keybindings GUI it could then be used elsewhere.
> >
> > Plugins can change the number of keybindings in their plugin's key
> > group*, so maybe this could be done for other things in the core. It
> > would probably only take some small changes. 'Format->Send
> > selection to' keybindings could be variable size too.
> >
> > *
> > http://www.geany.org/manual/reference/pluginutils_8h.html#e8eeecc54d81ce05457e04ad98028a68
> 
> Looks like a good solution, I never thought of looking in the plugin
> interface.
> 

Here is the new patch.  It seems to be not so simple and clean as the
previous one, but now keybindings are edited in Preferences dialog as
(I believe) most users prefer.

Unfortunately, keybindings_set_item was not enough for me to implement
this.  For example, I had to extend GeanyKeyGroup struct so that I have
a way to tweak loading from / saving to keybindings.conf.  Anyway, any
suggestions about how to improve the patch are welcome.

Currently there are two little problems

- All underscores from keybinding names are removed (I wonder why), so
  names of some "special" snippets are displayed incorrectly (**)

- Editing of keybindings in the Preferences dialog isn't "caught"
  completely by Geany.  For example, if I remove a keybinding using
  click - BackSpace - Enter and press OK, Geany thinks I edited nothing
  and does not update keybindings.conf.  Though, as expected, the
  removed keybinding no longer functions. 

** and users are allowed to assign keybindings for "special" snippets

I finished this patch today, so I hadn't much time for testing.
As for now, everything except the two issues above seems to work fine.

Best regards,
Eugene.
diff --git a/src/editor.c b/src/editor.c
index 052d6c7..cbaf0dd 100644
--- a/src/editor.c
+++ b/src/editor.c
@@ -37,6 +37,7 @@
 
 
 #include <ctype.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include <gdk/gdkkeysyms.h>
@@ -105,6 +106,17 @@ static enum
 
 static gchar indent[100];
 
+/* snippet keybindings */
+
+typedef struct
+{
+	gchar *name;
+	guint key;
+	GdkModifierType mods;
+} SnippetKeybinding;
+
+static GeanyKeyGroup *kb_group;
+
 
 static void on_new_line_added(GeanyEditor *editor);
 static gboolean handle_xml(GeanyEditor *editor, gint pos, gchar ch);
@@ -120,6 +132,15 @@ static void read_current_word(GeanyEditor *editor, gint pos, gchar *word, size_t
 		const gchar *wc, gboolean stem);
 static gsize count_indent_size(GeanyEditor *editor, const gchar *base_indent);
 
+/* snippet keybindings */
+static void create_snippet_keybindings_group(guint count);
+static void save_snippet_keybindings(GKeyFile *config, const gchar *section, GeanyKeyGroup *group);
+static void load_snippet_keybindings_from_snippets(void);
+static void load_snippet_keybindings_from_keyfile(GKeyFile *config, const gchar *section,
+	GeanyKeyGroup *group);
+static void merge_snippet_keybindings(SnippetKeybinding *kbs, gsize len, gboolean from_snippets);
+static gboolean activate_snippet(guint key_id);
+
 
 void editor_snippets_free(void)
 {
@@ -134,7 +155,6 @@ void editor_snippets_init(void)
 	gchar *sysconfigfile, *userconfigfile;
 	gchar **groups_user, **groups_sys;
 	gchar **keys_user, **keys_sys;
-	gchar *value;
 	GKeyFile *sysconfig = g_key_file_new();
 	GKeyFile *userconfig = g_key_file_new();
 	GHashTable *tmp;
@@ -162,15 +182,15 @@ void editor_snippets_init(void)
 	for (i = 0; i < len; i++)
 	{
 		keys_sys = g_key_file_get_keys(sysconfig, groups_sys[i], &len_keys, NULL);
+
 		/* create new hash table for the read section (=> filetype) */
 		tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 		g_hash_table_insert(snippet_hash, g_strdup(groups_sys[i]), tmp);
 
 		for (j = 0; j < len_keys; j++)
-		{
 			g_hash_table_insert(tmp, g_strdup(keys_sys[j]),
-						utils_get_setting_string(sysconfig, groups_sys[i], keys_sys[j], ""));
-		}
+					g_key_file_get_string(sysconfig, groups_sys[i], keys_sys[j], NULL));
+
 		g_strfreev(keys_sys);
 	}
 
@@ -186,23 +206,16 @@ void editor_snippets_init(void)
 			tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 			g_hash_table_insert(snippet_hash, g_strdup(groups_user[i]), tmp);
 		}
+
 		for (j = 0; j < len_keys; j++)
-		{
-			value = g_hash_table_lookup(tmp, keys_user[j]);
-			if (value == NULL)
-			{	/* value = NULL means the key doesn't yet exist, so insert */
-				g_hash_table_insert(tmp, g_strdup(keys_user[j]),
-						utils_get_setting_string(userconfig, groups_user[i], keys_user[j], ""));
-			}
-			else
-			{	/* old key and value will be freed by destroy function (g_free) */
-				g_hash_table_replace(tmp, g_strdup(keys_user[j]),
-						utils_get_setting_string(userconfig, groups_user[i], keys_user[j], ""));
-			}
-		}
+			g_hash_table_insert(tmp, g_strdup(keys_user[j]),
+					g_key_file_get_string(userconfig, groups_user[i], keys_user[j], NULL));
+
 		g_strfreev(keys_user);
 	}
 
+	load_snippet_keybindings_from_snippets();
+
 	g_free(sysconfigfile);
 	g_free(userconfigfile);
 	g_strfreev(groups_sys);
@@ -5093,3 +5106,261 @@ void editor_indent(GeanyEditor *editor, gboolean increase)
 		sci_set_current_line(sci, lstart);
 	}
 }
+
+/* Snippet keybindings */
+
+static void create_snippet_keybindings_group(guint count)
+{
+	kb_group = keybindings_set_group(NULL, "snippets", _("Snippets"), count, activate_snippet);
+	kb_group->load = load_snippet_keybindings_from_keyfile;
+	kb_group->save = save_snippet_keybindings;
+}
+
+
+/* Save snippet keybindings to keybindings.conf omitting empty records */
+static void save_snippet_keybindings(GKeyFile *config, const gchar *section, GeanyKeyGroup *group)
+{
+	gsize i;
+	gchar *accel;
+	GeanyKeyBinding *kb;
+
+	/* clear */
+	g_key_file_remove_group(config, section, NULL);
+
+	/* write */
+	for (i = 0; i < kb_group->count; i++)
+	{
+		kb = &kb_group->keys[i];
+		if (kb->key == 0 && kb->mods == 0)
+			continue;
+
+		accel = gtk_accelerator_name(kb->key, kb->mods);
+		g_key_file_set_string(config, section, kb->name, accel);
+		g_free(accel);
+	}
+}
+
+
+/* Create snippet keybindings for snippets in `snippet_hash' loaded from snippets.conf */
+static void load_snippet_keybindings_from_snippets(void)
+{
+	GArray *kbs;
+	GList *list, *names;
+
+	kbs = g_array_sized_new(FALSE, FALSE, sizeof(SnippetKeybinding), 20);
+	for (list = g_hash_table_get_values(snippet_hash); list; list = list->next)
+		for (names = g_hash_table_get_keys(list->data); names; names = names->next)
+		{
+			SnippetKeybinding kb = { names->data, 0, 0 };
+			g_array_append_val(kbs, kb);
+		}
+	merge_snippet_keybindings((SnippetKeybinding *)kbs->data, kbs->len, TRUE);
+	g_array_free(kbs, TRUE);
+}
+
+
+/* Load snippet keybindings from keybindings.conf */
+static void load_snippet_keybindings_from_keyfile(GKeyFile *config, const gchar *section,
+	GeanyKeyGroup *group)
+{
+	gchar **keys;
+	gchar *accel;
+	gsize len, i;
+	SnippetKeybinding *kb;
+
+	keys = g_key_file_get_keys(config, section, &len, NULL);
+	if( keys )
+	{
+		kb = g_new(SnippetKeybinding, len);
+		for (i = 0; i < len; i++)
+		{
+			kb[i].name = keys[i];
+			accel = g_key_file_get_value(config, section, keys[i], NULL);
+			gtk_accelerator_parse(accel, &kb[i].key, &kb[i].mods);
+		}
+		merge_snippet_keybindings(kb, len, FALSE);
+		g_strfreev(keys);
+	}
+}
+
+
+static int cmp_snippet_keybindings(const void *first, const void *second)
+{
+	gchar *first_name = ((const SnippetKeybinding *)first)->name;
+	gchar *second_name = ((const SnippetKeybinding *)second)->name;
+
+	return strcmp(first_name, second_name);
+}
+
+
+/* Helper function for merge_snippet_keybindings().
+ * Handles the case when a new keybinding should be added to kb_group->keys */
+static void handle_new(const SnippetKeybinding *kb, gsize *i, gboolean from_snippets)
+{
+	gchar *label = NULL;
+
+	if (!from_snippets)
+		label = g_strconcat(kb->name, _(" (missing in snippets.conf)"), NULL);
+
+	keybindings_set_item(kb_group, (*i)++, NULL, kb->key, kb->mods, kb->name,
+		label ? label : kb->name, NULL);
+
+	g_free(label);
+}
+
+
+/* Helper function for merge_snippet_keybindings().
+ * Handles the case when a keybinding existed in kb_group->keys before merge and is still present */
+static void handle_updated(const GeanyKeyBinding *old, const SnippetKeybinding *kb, gsize *i)
+{
+	guint key = kb->key;
+	GdkModifierType mods = kb->mods;
+
+	if (key == 0 && mods == 0)
+	{
+		key = old->key;
+		mods = old->mods;
+	}
+
+	keybindings_set_item(kb_group, (*i)++, NULL, key, mods, old->name, old->name, NULL);
+}
+
+
+/* Helper function for merge_snippet_keybindings().
+ * Handles the case when a keybinding existed in kb_group->keys before merge and now is absent */
+static void handle_removed(const GeanyKeyBinding *kb, gsize *i, gboolean from_snippets)
+{
+	gchar *label = NULL;
+
+	if (from_snippets)
+	{
+		if (kb->key == 0 && kb->mods == 0)
+			return;
+		else
+			label = g_strconcat(kb->name, _(" (missing in snippets.conf)"), NULL);
+	}
+
+	keybindings_set_item(kb_group, (*i)++, NULL, kb->key, kb->mods, kb->name,
+		label ? label : kb->name, NULL);
+
+	g_free(label);
+}
+
+
+/* Update keybindings in kb_group->keys
+ *
+ * @param kbs - array of SnippetKeybinding's
+ * @param len - number of elements in `kbs'
+ * @param from_snippets - TRUE if `kbs' come from snippets.conf, FALSE -- from keybindings.conf
+ */
+static void merge_snippet_keybindings(SnippetKeybinding *kbs, gsize len, gboolean from_snippets)
+{
+	gsize i, j, k, last_count;
+	GeanyKeyBinding *last_keys;
+	gchar *name;
+
+	if (len == 0)
+		return;
+
+	/* sort lexicographically */
+	qsort(kbs, len, sizeof(SnippetKeybinding), cmp_snippet_keybindings);
+
+	/* omit duplicates */
+	len = utils_unique(kbs, len, sizeof(SnippetKeybinding), cmp_snippet_keybindings);
+	if (len == 0)
+		return;
+
+	last_count = 0;
+	last_keys = NULL;
+	if (kb_group)
+	{
+		last_count = kb_group->count;
+		last_keys = kb_group->keys;
+		kb_group->count = len + last_count;
+		kb_group->keys = g_new0(GeanyKeyBinding, len + last_count);
+	}
+	else
+		create_snippet_keybindings_group(len + last_count);
+
+	/* Update kb_group->keys.
+	 * - i iterates over kb_group->keys
+	 * - j iterates over last_keys
+	 * - k iterates over kbs
+	 * All of these array are both sorted by snippet name */
+
+	for (i = j = k = 0; k < len; k++)
+	{
+		int r;
+		name = kbs[k].name;
+
+		/* 'r' stores 0 if there is an item with same name in 'last_keys' and it's index is 'j';
+		 * otherwise stores a positive value */
+		for (r = 1; j < last_count && (r = strcmp(name, last_keys[j].name)) > 0; j++)
+		{
+			keybindings_unregister_kb_accel(kb_group, &last_keys[j]);
+			handle_removed(&last_keys[j], &i, from_snippets);
+		}
+
+		if (r == 0)
+		{
+			keybindings_unregister_kb_accel(kb_group, &last_keys[j]);
+			handle_updated(&last_keys[j], &kbs[k], &i);
+			j++;
+		}
+		else
+			handle_new(&kbs[k], &i, from_snippets);
+	}
+
+	for (; j < last_count; j++)
+	{
+		keybindings_unregister_kb_accel(kb_group, &last_keys[j]);
+		handle_removed(&last_keys[j], &i, from_snippets);
+	}
+
+	kb_group->count = i;
+	keybindings_free_group_keys(last_keys, last_count);
+}
+
+
+/* Called when a snippet keybinding is activated */
+static gboolean activate_snippet(guint key_id)
+{
+	gchar *name = kb_group->keys[key_id].name;
+	GeanyDocument *doc = document_get_current();
+	const gchar *s;
+	GHashTable *specials;
+	GString *pattern;
+	gint pos, line, indent_width, cursor_pos;
+
+	if (!doc)
+	{
+		utils_beep();
+		return FALSE;
+	}
+
+	s = snippets_find_completion_by_name(doc->file_type->name, name);
+	if (!s) /* may be "special" snippet */
+	{
+		specials = g_hash_table_lookup(snippet_hash, "Special");
+		if (G_LIKELY(specials != NULL))
+			s = g_hash_table_lookup(specials, name);
+	}
+	if (!s)
+	{
+		utils_beep();
+		return FALSE;
+	}
+
+	pos = sci_get_current_position(doc->editor->sci);
+	line = sci_get_line_from_position(doc->editor->sci, pos);
+	indent_width = sci_get_line_indentation(doc->editor->sci, line);
+
+	pattern = g_string_new(s);
+	cursor_pos = snippets_make_replacements(doc->editor, pattern, indent_width);
+
+	editor_insert_text_block(doc->editor, pattern->str, pos, cursor_pos, indent_width, FALSE);
+	sci_scroll_caret(doc->editor->sci);
+
+	g_string_free(pattern, TRUE);
+	return TRUE;
+}
diff --git a/src/keybindings.c b/src/keybindings.c
index 4e68723..6eab379 100644
--- a/src/keybindings.c
+++ b/src/keybindings.c
@@ -679,8 +679,20 @@ static void load_user_kb(void)
 	/* now load user defined keys */
 	if (g_key_file_load_from_file(config, configfile, G_KEY_FILE_KEEP_COMMENTS, NULL))
 	{
-		keybindings_foreach(load_kb, config);
+		gsize g, i;
+		GeanyKeyGroup *group;
+
+		for (g = 0; g < keybinding_groups->len; g++)
+		{
+			group = g_ptr_array_index(keybinding_groups, g);
+			if (group->load)
+				group->load(config, group->name, group);
+			else
+				for (i = 0; i < group->count; i++)
+					load_kb(group, &group->keys[i], config);
+		}
 	}
+
 	g_free(configfile);
 	g_key_file_free(config);
 }
@@ -696,6 +708,13 @@ static void apply_kb_accel(GeanyKeyGroup *group, GeanyKeyBinding *kb, gpointer u
 }
 
 
+void keybindings_unregister_kb_accel(GeanyKeyGroup *group, GeanyKeyBinding *kb)
+{
+	if (kb->key != 0 && kb->menu_item)
+		gtk_widget_remove_accelerator(kb->menu_item, kb_accel_group, kb->key, kb->mods);
+}
+
+
 void keybindings_load_keyfile(void)
 {
 	load_user_kb();
@@ -769,6 +788,8 @@ void keybindings_write_to_file(void)
 	gchar *configfile = g_strconcat(app->configdir, G_DIR_SEPARATOR_S, "keybindings.conf", NULL);
 	gchar *data;
 	GKeyFile *config = g_key_file_new();
+	gsize g, i;
+	GeanyKeyGroup *group;
 
  	/* add comment if the file is newly created */
 	if (! g_key_file_load_from_file(config, configfile, G_KEY_FILE_KEEP_COMMENTS, NULL))
@@ -778,7 +799,16 @@ void keybindings_write_to_file(void)
 			"But you can also change the keys in Geany's preferences dialog.", NULL);
 	}
 
-	keybindings_foreach(set_keyfile_kb, config);
+	/* save */
+	for (g = 0; g < keybinding_groups->len; g++)
+	{
+		group = g_ptr_array_index(keybinding_groups, g);
+		if (group->save)
+			group->save(config, group->name, group);
+		else
+			for (i = 0; i < group->count; i++)
+				set_keyfile_kb(group, &group->keys[i], config);
+	}
 
 	/* write the file */
 	data = g_key_file_to_data(config, NULL, NULL);
@@ -2641,16 +2671,22 @@ GeanyKeyGroup *keybindings_set_group(GeanyKeyGroup *group, const gchar *section_
 /* used for plugins */
 void keybindings_free_group(GeanyKeyGroup *group)
 {
-	GeanyKeyBinding *kb;
-
 	g_assert(group->plugin);
 
-	foreach_c_array(kb, group->keys, group->count)
+	keybindings_free_group_keys(group->keys, group->count);
+	g_ptr_array_remove_fast(keybinding_groups, group);
+	g_free(group);
+}
+
+
+void keybindings_free_group_keys(GeanyKeyBinding *keys, int count)
+{
+	GeanyKeyBinding *kb;
+
+	foreach_c_array(kb, keys, count)
 	{
 		g_free(kb->name);
 		g_free(kb->label);
 	}
-	g_free(group->keys);
-	g_ptr_array_remove_fast(keybinding_groups, group);
-	g_free(group);
+	g_free(keys);
 }
diff --git a/src/keybindings.h b/src/keybindings.h
index b876b61..0eaf2e1 100644
--- a/src/keybindings.h
+++ b/src/keybindings.h
@@ -51,6 +51,11 @@ GeanyKeyBinding;
  * with the same key combination to handle it). */
 typedef gboolean (*GeanyKeyGroupCallback) (guint key_id);
 
+struct GeanyKeyGroup;
+typedef void (*GeanyKeyGroupLoadCallback) (GKeyFile *config, const gchar *section,
+	struct GeanyKeyGroup *group);
+typedef GeanyKeyGroupLoadCallback GeanyKeyGroupSaveCallback;
+
 /** A collection of keybindings grouped together. */
 typedef struct GeanyKeyGroup GeanyKeyGroup;
 
@@ -64,6 +69,8 @@ struct GeanyKeyGroup
 	GeanyKeyBinding *keys;	/* array of GeanyKeyBinding structs */
 	gboolean plugin;		/* used by plugin */
 	GeanyKeyGroupCallback callback;	/* use this or individual keybinding callbacks */
+	GeanyKeyGroupLoadCallback load; /* custom callback to load data from the configuration file */
+	GeanyKeyGroupSaveCallback save; /* custom callback to save data from the configuration file */
 };
 #endif
 
@@ -348,6 +355,8 @@ GeanyKeyGroup *keybindings_set_group(GeanyKeyGroup *group, const gchar *section_
 
 void keybindings_free_group(GeanyKeyGroup *group);
 
+void keybindings_free_group_keys(GeanyKeyBinding *keys, int count);
+
 GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id,
 		GeanyKeyCallback callback, guint key, GdkModifierType mod,
 		const gchar *name, const gchar *label, GtkWidget *menu_item);
@@ -369,5 +378,7 @@ void keybindings_show_shortcuts(void);
 
 const GeanyKeyBinding *keybindings_check_event(GdkEventKey *ev, gint *group_id, gint *binding_id);
 
+void keybindings_unregister_kb_accel(GeanyKeyGroup *group, GeanyKeyBinding *kb);
+
 #endif
 
diff --git a/src/utils.c b/src/utils.c
index 1d1161f..c6a5ad6 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -2007,3 +2007,33 @@ gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_va
 }
 
 
+/* "Remove" duplicate elements from an array
+ *
+ * Actually, this function moves all copies to the end of the array and returns the number of
+ * unique elements, which will be placed in the beginning.
+ *
+ * @param start - start address of the array
+ * @param len - number of elements in the array
+ * @param element_size - size of an element
+ * @cmp - Function to compare elements. Takes two pointers to array elements
+ * @returns number of unique elements
+ */
+gsize utils_unique(void *start, gsize len, gsize element_size, GCompareFunc cmp)
+{
+	gsize i, j;
+
+	for (i = 0, j = 1; j < len; j++)
+	{
+		if (cmp(start + j*element_size, start + i*element_size) != 0)
+		{
+			/* swap(elements[++i], elements[j]) */
+			void *mem = g_alloca(element_size);
+
+			i++;
+			memcpy(mem, start + i*element_size, element_size);
+			memcpy(start + i*element_size, start + j*element_size, element_size);
+			memcpy(start + j*element_size, mem, element_size);
+		}
+	}
+	return i + 1;
+}
diff --git a/src/utils.h b/src/utils.h
index 0e53d56..c16b5cf 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -231,4 +231,6 @@ gchar *utils_str_remove_chars(gchar *string, const gchar *chars);
 
 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...) G_GNUC_NULL_TERMINATED;
 
+gsize utils_unique(void *start, gsize len, gsize element_size, GCompareFunc cmp);
+
 #endif
_______________________________________________
Geany-devel mailing list
Geany-devel@uvena.de
http://lists.uvena.de/cgi-bin/mailman/listinfo/geany-devel

Reply via email to