On Sun, 29 Aug 2010 21:35:35 +0400%
Eugene Arshinov <[email protected]> wrote:

> On Wed, 18 Aug 2010 09:56:45 +1000%
> Lex Trotman <[email protected]> wrote:
> 
> > On 18 August 2010 00:35, Nick Treleaven
> > <[email protected]> wrote:
> > > On Tue, 17 Aug 2010 10:08:08 +1000
> > > Lex Trotman <[email protected]> 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.

Sorry, that patch is a little outdated.  Here is the last version. 
diff --git a/src/editor.c b/src/editor.c
index 052d6c7..98f92f4 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,263 @@ 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 *kbs;
+
+	keys = g_key_file_get_keys(config, section, &len, NULL);
+	if( keys )
+	{
+		kbs = g_new(SnippetKeybinding, len);
+		for (i = 0; i < len; i++)
+		{
+			kbs[i].name = keys[i];
+			accel = g_key_file_get_value(config, section, keys[i], NULL);
+			gtk_accelerator_parse(accel, &kbs[i].key, &kbs[i].mods);
+			g_free(accel);
+		}
+		merge_snippet_keybindings(kbs, len, FALSE);
+		g_free(kbs);
+		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
[email protected]
http://lists.uvena.de/cgi-bin/mailman/listinfo/geany-devel

Reply via email to