changeset 1272:e0e12512cde2

read external editors from .desktop files
author nadvornik
date Sun, 01 Feb 2009 12:48:14 +0000
parents 4fcdbb497df3
children 1ac2baeb8dc9
files Makefile.am configure.in plugins/Makefile.am plugins/symlink/Makefile.am plugins/symlink/symlink.desktop src/bar_sort.c src/collect-table.c src/collect.c src/dupe.c src/editors.c src/editors.h src/img-view.c src/layout_image.c src/layout_util.c src/layout_util.h src/main.c src/main.h src/menu.c src/options.c src/options.h src/pan-view.c src/preferences.c src/rcfile.c src/search.c src/typedefs.h src/utilops.c src/utilops.h src/view_dir.c src/view_file.c
diffstat 29 files changed, 745 insertions(+), 369 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.am	Thu Jan 29 19:43:34 2009 +0000
+++ b/Makefile.am	Sun Feb 01 12:48:14 2009 +0000
@@ -1,7 +1,7 @@
 ## Process this file with automake to produce Makefile.in.
 
-SUBDIRS = src po doc
-DIST_SUBDIRS = src po doc
+SUBDIRS = src po doc plugins
+DIST_SUBDIRS = src po doc plugins
 
 man_MANS = geeqie.1
 
--- a/configure.in	Thu Jan 29 19:43:34 2009 +0000
+++ b/configure.in	Sun Feb 01 12:48:14 2009 +0000
@@ -208,6 +208,9 @@
 AC_SUBST(readmedir)
 AC_SUBST(htmldir)
 
+eval "eval appdir=${datadir}/${PACKAGE}"
+AC_DEFINE_UNQUOTED([GQ_APP_DIR], "$appdir", [Location of application data])
+
 #  LIRC support
 # ----------------------------------------------------------------------
 
@@ -351,6 +354,8 @@
     src/icons/svg/Makefile
     po/Makefile.in
     doc/Makefile
+    plugins/Makefile
+    plugins/symlink/Makefile
     geeqie.spec
 ])
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/Makefile.am	Sun Feb 01 12:48:14 2009 +0000
@@ -0,0 +1,2 @@
+SUBDIRS = symlink
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/symlink/Makefile.am	Sun Feb 01 12:48:14 2009 +0000
@@ -0,0 +1,5 @@
+
+qq_desktopdir = $(pkgdatadir)/applications
+qq_desktop_DATA = symlink.desktop
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/symlink/symlink.desktop	Sun Feb 01 12:48:14 2009 +0000
@@ -0,0 +1,24 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Symlink
+#Name[cs]=
+
+# FIXME: this can't be an oneliner, it needs to be changed to a full
+# featured script in separate file, with error handling etc.
+# expansion of environment variables directly in Exec is not supported 
+# by the specification and it will be removed
+Exec=ln -s %f "$GEEQIE_DESTINATION"
+
+# Desktop files that are usable only in Geeqie should be marked like this:
+Categories=X-Geeqie;
+OnlyShowIn=X-Geeqie;
+
+# Show in menu "File"
+X-Geeqie-Menu-Path=FileMenu
+
+# This is a filter - $GEEQIE_DESTINATION is required
+X-Geeqie-Filter=true
+
+# It can be made verbose
+# X-Geeqie-Verbose=true
--- a/src/bar_sort.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/bar_sort.c	Sun Feb 01 12:48:14 2009 +0000
@@ -44,7 +44,7 @@
 	BAR_SORT_COPY = 0,
 	BAR_SORT_MOVE,
 	BAR_SORT_FILTER,
-	BAR_SORT_ACTION_COUNT = BAR_SORT_FILTER + GQ_EDITOR_GENERIC_SLOTS
+	BAR_SORT_ACTION_COUNT
 } SortActionType;
 
 typedef enum {
@@ -65,6 +65,8 @@
 
 	SortModeType mode;
 	SortActionType action;
+	const gchar *filter_key;
+	
 	SortSelectionType selection;
 
 	GtkWidget *folder_group;
@@ -282,13 +284,12 @@
 			file_util_move_simple(list, path, sd->lw->window);
 			list = NULL;
 			break;
+		case BAR_SORT_FILTER:
+			file_util_start_filter_from_filelist(sd->filter_key, list, path, sd->lw->window);
+			list = NULL;
+			layout_image_next(sd->lw);
+			break;
 		default:
-			if (sd->action >= BAR_SORT_FILTER && sd->action < BAR_SORT_ACTION_COUNT)
-				{
-				file_util_start_filter_from_filelist(sd->action - BAR_SORT_FILTER, list, path, sd->lw->window);
-				list = NULL;
-				layout_image_next(sd->lw);
-				}
 			break;
 		}
 
@@ -348,35 +349,46 @@
 		}
 }
 
-static void bar_sort_set_action(SortData *sd, SortActionType action)
+static void bar_sort_set_action(SortData *sd, SortActionType action, const gchar *filter_key)
 {
 	options->panels.sort.action_state = sd->action = action;
+	if (action == BAR_SORT_FILTER)
+		{
+		if (!filter_key) filter_key = "";
+		sd->filter_key = filter_key;
+		g_free(options->panels.sort.action_filter);
+		options->panels.sort.action_filter = g_strdup(filter_key);
+		}
+	else
+		{
+		sd->filter_key = NULL;
+		g_free(options->panels.sort.action_filter);
+		options->panels.sort.action_filter = g_strdup("");
+		}
 }
 
 static void bar_sort_set_copy_cb(GtkWidget *button, gpointer data)
 {
 	SortData *sd = data;
 	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) return;
-	bar_sort_set_action(sd, BAR_SORT_COPY);
+	bar_sort_set_action(sd, BAR_SORT_COPY, NULL);
 }
 
 static void bar_sort_set_move_cb(GtkWidget *button, gpointer data)
 {
 	SortData *sd = data;
 	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) return;
-	bar_sort_set_action(sd, BAR_SORT_MOVE);
+	bar_sort_set_action(sd, BAR_SORT_MOVE, NULL);
 }
 
 static void bar_sort_set_filter_cb(GtkWidget *button, gpointer data)
 {
 	SortData *sd = data;
-	guint n;
+	const gchar *key;
 
 	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) return;
-	n = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(button), "filter_idx"));
-	if (n == 0) return;
-	n--;
-	bar_sort_set_action(sd, BAR_SORT_FILTER + n);
+	key = g_object_get_data(G_OBJECT(button), "filter_key");
+	bar_sort_set_action(sd, BAR_SORT_FILTER, key);
 }
 
 static void bar_sort_set_selection(SortData *sd, SortSelectionType selection)
@@ -551,7 +563,8 @@
 	GtkWidget *tbar;
 	GtkWidget *combo;
 	SortModeType mode;
-	guint i;
+	GList *editors_list, *work;
+	gboolean have_filter;
 
 	if (!lw) return NULL;
 
@@ -562,7 +575,9 @@
 	mode = CLAMP(options->panels.sort.mode_state, 0, BAR_SORT_MODE_COUNT - 1);
 	sd->action = CLAMP(options->panels.sort.action_state, 0, BAR_SORT_ACTION_COUNT - 1);
 	
-	while (sd->action >= BAR_SORT_FILTER && !editor_is_filter(sd->action - BAR_SORT_FILTER)) sd->action--;
+	if (sd->action == BAR_SORT_FILTER && 
+	    (!options->panels.sort.action_filter || !options->panels.sort.action_filter[0]))
+		sd->action = BAR_SORT_COPY;
 	
 	sd->selection = CLAMP(options->panels.sort.selection_state, 0, BAR_SORT_SELECTION_COUNT - 1);
 	sd->undo_src = NULL;
@@ -598,21 +613,35 @@
 			     G_CALLBACK(bar_sort_set_move_cb), sd);
 
 
-	for (i = 0; i < GQ_EDITOR_GENERIC_SLOTS; i++)
+	have_filter = FALSE;
+	editors_list = editor_list_get();
+	work = editors_list;
+	while (work)
 		{
 		GtkWidget *button;
+		EditorDescription *editor = work->data;
+		work = work->next;
+		gboolean select = FALSE;
+		
+		if (!editor_is_filter(editor->key)) continue;
 
-		const gchar *name = editor_get_name(i);
-		if (!name || !editor_is_filter(i)) continue;
+		if (sd->action == BAR_SORT_FILTER && strcmp(editor->key, options->panels.sort.action_filter) == 0)
+			{
+			bar_sort_set_action(sd, sd->action, editor->key);
+			select = TRUE;
+			have_filter = TRUE;
+			}
 
 		button = pref_radiobutton_new(sd->folder_group, buttongrp,
-					      name, (sd->action == BAR_SORT_FILTER + i),
+					      editor->name, select,
 					      G_CALLBACK(bar_sort_set_filter_cb), sd);
 
 
-		g_object_set_data(G_OBJECT(button), "filter_idx", GUINT_TO_POINTER(i + 1));
+		g_object_set_data(G_OBJECT(button), "filter_key", editor->key);
 		}
-
+	g_list_free(editors_list);
+	
+	if (sd->action == BAR_SORT_FILTER && !have_filter) sd->action = BAR_SORT_COPY;
 
 	sd->collection_group = pref_box_new(sd->vbox, FALSE, GTK_ORIENTATION_VERTICAL, 0);
 
--- a/src/collect-table.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/collect-table.c	Sun Feb 01 12:48:14 2009 +0000
@@ -654,18 +654,17 @@
 static void collection_table_popup_edit_cb(GtkWidget *widget, gpointer data)
 {
 	CollectTable *ct;
-	gint n;
+	const gchar *key = data;
 	GList *list;
 
 	ct = submenu_item_get_data(widget);
 
 	if (!ct) return;
-	n = GPOINTER_TO_INT(data);
 
 	list = collection_table_popup_file_list(ct);
 	if (list)
 		{
-		file_util_start_editor_from_filelist(n, list, ct->listview);
+		file_util_start_editor_from_filelist(key, list, ct->listview);
 		filelist_free(list);
 		}
 }
--- a/src/collect.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/collect.c	Sun Feb 01 12:48:14 2009 +0000
@@ -947,14 +947,14 @@
 				break;
 			}
 		}
-
+#if 0
 	if (edit_val != -1)
 		{
 		list = collection_table_selection_get_list(cw->table);
 		file_util_start_editor_from_filelist(edit_val, list, cw->window);
 		filelist_free(list);
 		}
-
+#endif
 	return stop_signal;
 }
 
--- a/src/dupe.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/dupe.c	Sun Feb 01 12:48:14 2009 +0000
@@ -2051,13 +2051,13 @@
 	dupe_listview_realign_colors(dw);
 }
 
-static void dupe_window_edit_selected(DupeWindow *dw, gint n)
+static void dupe_window_edit_selected(DupeWindow *dw, const gchar *key)
 {
 	GList *list;
 
 	list = dupe_listview_get_selection(dw, dw->listview);
 
-	file_util_start_editor_from_filelist(n, list, dw->window);
+	file_util_start_editor_from_filelist(key, list, dw->window);
 
 	filelist_free(list);
 }
@@ -2139,13 +2139,12 @@
 static void dupe_menu_edit_cb(GtkWidget *widget, gpointer data)
 {
 	DupeWindow *dw;
-	gint n;
+	const gchar *key = data;
 
 	dw = submenu_item_get_data(widget);
-	n = GPOINTER_TO_INT(data);
 	if (!dw) return;
 
-	dupe_window_edit_selected(dw, n);
+	dupe_window_edit_selected(dw, key);
 }
 
 static void dupe_menu_info_cb(GtkWidget *widget, gpointer data)
@@ -2990,11 +2989,12 @@
 					break;
 				}
 			}
-
+#if 0
 		if (edit_val >= 0)
 			{
 			dupe_window_edit_selected(dw, edit_val);
 			}
+#endif
 		}
 	else
 		{
--- a/src/editors.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/editors.c	Sun Feb 01 12:48:14 2009 +0000
@@ -44,7 +44,6 @@
 struct _EditorData {
 	gint flags;
 	GPid pid;
-	gchar *command_template;
 	GList *list;
 	gint count;
 	gint total;
@@ -52,41 +51,15 @@
 	EditorVerboseData *vd;
 	EditorCallback callback;
 	gpointer data;
+	const EditorDescription *editor;
 };
 
 
-static Editor editor_slot_defaults[GQ_EDITOR_SLOTS] = {
-	{ N_("The Gimp"), "gimp-remote %{%raw;*}f" },
-	{ N_("XV"), "xv %f" },
-	{ N_("Xpaint"), "xpaint %f" },
-	{ N_("UFraw"), "ufraw %{%raw}p" },
-	{ N_("Symlink"), "ln -s %p %d"},
-	{ NULL, NULL },
-	{ NULL, NULL },
-	{ NULL, NULL },
-	{ N_("Rotate jpeg clockwise"), "%vif jpegtran -rotate 90 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}p_tmp;fi" },
-	{ N_("Rotate jpeg counterclockwise"), "%vif jpegtran -rotate 270 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}p_tmp;fi" },
-	/* special slots */
-#if 1
-	/* for testing */
-	{ N_("External Copy command"), "%vset -x;cp %p %d" },
-	{ N_("External Move command"), "%vset -x;mv %p %d" },
-	{ N_("External Rename command"), "%vset -x;mv %p %d" },
-	{ N_("External Delete command"), NULL },
-	{ N_("External New Folder command"), NULL },
-#else
-	{ N_("External Copy command"), NULL },
-	{ N_("External Move command"), NULL },
-	{ N_("External Rename command"), NULL },
-	{ N_("External Delete command"), NULL },
-	{ N_("External New Folder command"), NULL },
-#endif
-};
-
 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
 static gint editor_command_next_start(EditorData *ed);
 static gint editor_command_next_finish(EditorData *ed, gint status);
 static gint editor_command_done(EditorData *ed);
+static gint editor_command_parse(const EditorDescription *editor, GList *list, gchar **output);
 
 /*
  *-----------------------------------------------------------------------------
@@ -94,34 +67,310 @@
  *-----------------------------------------------------------------------------
  */
 
-void editor_set_name(gint n, gchar *name)
+GHashTable *editors = NULL;
+
+#ifdef G_KEY_FILE_DESKTOP_GROUP
+#define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
+#else
+#define DESKTOP_GROUP "Desktop Entry"
+#endif
+
+void editor_description_free(EditorDescription *editor)
 {
-	if (n < 0 || n >= GQ_EDITOR_SLOTS) return;
+	if (!editor) return;
+	
+	g_free(editor->key);
+	g_free(editor->name);
+	g_free(editor->exec);
+	g_free(editor->menu_path);
+	g_free(editor->hotkey);
+	string_list_free(editor->ext_list);
+	g_free(editor->icon);
+	g_free(editor->file);
+	g_free(editor);
+}
 
-	g_free(options->editor[n].name);
+static GList *editor_mime_types_to_extensions(gchar **mime_types)
+{
+	/* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
 	
-	options->editor[n].name = name ? utf8_validate_or_convert(name) : NULL;
+	static const gchar *conv_table[][2] = {
+		{"application/x-ufraw",	"%raw"},
+		{"image/*",		"*"},
+		{"image/bmp",		".bmp"},
+		{"image/gif",		".gif"},
+		{"image/jpeg",		".jpeg;.jpg"},
+		{"image/jpg",		".jpg;.jpeg"},
+		{"image/pcx",		".pcx"},
+		{"image/png",		".png"},
+		{"image/svg",		".svg"},
+		{"image/svg+xml",	".svg"},
+		{"image/svg+xml-compressed", 	".svg"},	
+		{"image/tiff",		".tiff;.tif"},
+		{"image/x-bmp",		".bmp"},
+		{"image/x-canon-crw",	".crw"},
+		{"image/x-cr2",		".cr2"},
+		{"image/x-dcraw",	"%raw"},
+		{"image/x-ico",		".ico"},
+		{"image/x-mrw",		".mrw"},
+		{"image/x-MS-bmp",	".bmp"},
+		{"image/x-nef",		".nef"},
+		{"image/x-orf",		".orf"},
+		{"image/x-pcx",		".pcx"},
+		{"image/xpm",		".xpm"},
+		{"image/x-png",		".png"},
+		{"image/x-portable-anymap",	".pam"},	
+		{"image/x-portable-bitmap",	".pbm"},
+		{"image/x-portable-graymap",	".pgm"},
+		{"image/x-portable-pixmap",	".ppm"},
+		{"image/x-psd",		".psd"},
+		{"image/x-raf",		".raf"},
+		{"image/x-sgi",		".sgi"},
+		{"image/x-tga",		".tga"},
+		{"image/x-xbitmap",	".xbm"},
+		{"image/x-xcf",		".xcf"},
+		{"image/x-xpixmap",	".xpm"},
+		{"image/x-x3f",		".x3f"},
+		{NULL, NULL}};
+	
+	gint i, j;
+	GList *list = NULL;
+	
+	for (i = 0; mime_types[i]; i++) 
+		for (j = 0; conv_table[j][0]; j++)
+			if (strcmp(mime_types[i], conv_table[j][0]) == 0)
+				list = g_list_concat(list, filter_to_list(conv_table[j][1]));
+	
+	return list;
 }
 
-void editor_set_command(gint n, gchar *command)
+static gboolean editor_read_desktop_file(const gchar *path)
 {
-	if (n < 0 || n >= GQ_EDITOR_SLOTS) return;
+	GKeyFile *key_file;
+	EditorDescription *editor;
+	gchar *extensions;
+	const gchar *key = filename_from_path(path);
+	gchar **categories, **only_show_in, **not_show_in;
+
+	if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
+	
+	key_file = g_key_file_new();
+	if (!g_key_file_load_from_file(key_file, path, 0, NULL))
+		{
+		g_key_file_free(key_file);
+		return FALSE;
+		}
+	
+	editor = g_new0(EditorDescription, 1);
+	
+	editor->key = g_strdup(key);
+	editor->file = g_strdup(path);
+
+	g_hash_table_insert(editors, editor->key, editor);
+
+	if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)) editor->hidden = TRUE;
+
+	categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
+	if (categories)
+		{
+		gboolean found = FALSE;
+		gint i;
+		for (i = 0; categories[i]; i++) 
+			/* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
+			if (strcmp(categories[i], "Graphics") == 0 ||
+			    strcmp(categories[i], "X-Geeqie") == 0) 
+				{
+				found = TRUE;
+				break;
+				}
+		if (!found) editor->hidden = TRUE;
+		g_strfreev(categories);
+		}
+	else
+		{
+		editor->hidden = TRUE;
+		}
+
+	only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
+	if (only_show_in)
+		{
+		gboolean found = FALSE;
+		gint i;
+		for (i = 0; only_show_in[i]; i++) 
+			if (strcmp(only_show_in[i], "X-Geeqie") == 0)
+				{
+				found = TRUE;
+				break;
+				}
+		if (!found) editor->hidden = TRUE;
+		g_strfreev(only_show_in);
+		}
 
-	g_free(options->editor[n].command);
-	options->editor[n].command = command ? utf8_validate_or_convert(command) : NULL;
+	not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
+	if (not_show_in)
+		{
+		gboolean found = FALSE;
+		gint i;
+		for (i = 0; not_show_in[i]; i++) 
+			if (strcmp(not_show_in[i], "X-Geeqie") == 0)
+				{
+				found = TRUE;
+				break;
+				}
+		if (found) editor->hidden = TRUE;
+		g_strfreev(not_show_in);
+		}
+		
+	if (editor->hidden) 
+		{
+		/* hidden editors will be deleted, no need to parse the rest */
+		g_key_file_free(key_file);
+		return TRUE;
+		}
+	
+	editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
+	editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
+
+	editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
+	
+	/* we take only editors that accept parameters, FIXME: the test can be improved */
+	if (!strchr(editor->exec, '%')) editor->hidden = TRUE; 
+	
+	editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
+	if (!editor->menu_path) editor->menu_path = g_strdup("EditMenu/ExternalMenu");
+	
+	editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
+
+	extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
+	if (extensions)
+		editor->ext_list = filter_to_list(extensions);
+	else
+		{
+		gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
+		if (mime_types)
+			{
+			editor->ext_list = editor_mime_types_to_extensions(mime_types);
+			g_strfreev(mime_types);
+			if (!editor->ext_list) editor->hidden = TRUE; 
+			}
+		
+		}
+		
+
+	if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
+	if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
+	if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
+	if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
+	if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
+
+	
+	editor->flags |= editor_command_parse(editor, NULL, NULL);
+	g_key_file_free(key_file);
+	
+	return TRUE;	
 }
 
-void editor_reset_defaults(void)
+static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
+{
+	EditorDescription *editor = value;
+	return editor->hidden;
+}
+
+static void editor_read_desktop_dir(const gchar *path)
 {
+	DIR *dp;
+	struct dirent *dir;
+	gchar *pathl;
+
+	pathl = path_from_utf8(path);
+	dp = opendir(pathl);
+	g_free(pathl);
+	if (!dp)
+		{
+		/* dir not found */
+		return;
+		}
+	while ((dir = readdir(dp)) != NULL)
+		{
+		gchar *namel = dir->d_name;
+		size_t len = strlen(namel);
+		
+		if (len > 8 && strncasecmp(namel + len - 8, ".desktop", 8) == 0)
+			{
+			gchar *name = path_to_utf8(namel);
+			gchar *dpath = g_build_filename(path, name, NULL);
+			editor_read_desktop_file(dpath);
+			g_free(dpath);
+			g_free(name);
+			}	
+		}
+	closedir(dp);
+}
+
+void editor_load_descriptions(void)
+{
+	gchar *path;
+	gchar *xdg_data_dirs;
+	gchar *all_dirs;
+	gchar **split_dirs;
 	gint i;
+	
+	if (!editors)
+		{
+		editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
+		}
 
-	for (i = 0; i < GQ_EDITOR_SLOTS; i++)
+	xdg_data_dirs = getenv("XDG_DATA_DIRS");
+	if (xdg_data_dirs && xdg_data_dirs[0])
+		xdg_data_dirs = path_to_utf8(xdg_data_dirs);
+	else
+		xdg_data_dirs = g_strdup("/usr/share");
+	
+	all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_dirs, NULL);
+	
+	g_free(xdg_data_dirs);
+
+	split_dirs = g_strsplit(all_dirs, ":", 0);
+	
+	g_free(all_dirs);
+
+	for (i = 0; split_dirs[i]; i++)
 		{
-		editor_set_name(i, _(editor_slot_defaults[i].name));
-		editor_set_command(i, _(editor_slot_defaults[i].command));
+		path = g_build_filename(split_dirs[i], "applications", NULL);
+		editor_read_desktop_dir(path);
+		g_free(path);
 		}
+		
+	g_strfreev(split_dirs);
+	
+	g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
 }
 
+static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
+{
+	GList **listp = data;
+	EditorDescription *editor = value;
+	
+	/* do not show the special commands in any list, they are called explicitelly */ 
+	if (strcmp(editor->key, CMD_COPY) == 0 ||
+	    strcmp(editor->key, CMD_MOVE) == 0 ||  
+	    strcmp(editor->key, CMD_RENAME) == 0 ||
+	    strcmp(editor->key, CMD_DELETE) == 0 ||
+	    strcmp(editor->key, CMD_FOLDER) == 0) return;
+
+	*listp = g_list_prepend(*listp, editor);
+}
+
+GList *editor_list_get(void)
+{
+	GList *editors_list = NULL;
+	g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
+	return editors_list;
+}
+
+/* ------------------------------ */
+
+
 static void editor_verbose_data_free(EditorData *ed)
 {
 	if (!ed->vd) return;
@@ -132,7 +381,6 @@
 static void editor_data_free(EditorData *ed)
 {
 	editor_verbose_data_free(ed);
-	g_free(ed->command_template);
 	g_free(ed);
 }
 
@@ -281,11 +529,12 @@
 
 typedef enum {
 	PATH_FILE,
+	PATH_FILE_URL,
 	PATH_DEST
 } PathType;
 
 
-static gchar *editor_command_path_parse(const FileData *fd, PathType type, const gchar *extensions)
+static gchar *editor_command_path_parse(const FileData *fd, PathType type, const EditorDescription *editor)
 {
 	GString *string;
 	gchar *pathl;
@@ -293,10 +542,9 @@
 
 	string = g_string_new("");
 
-	if (type == PATH_FILE)
+	if (type == PATH_FILE || type == PATH_FILE_URL)
 		{
-		GList *ext_list = filter_to_list(extensions);
-		GList *work = ext_list;
+		GList *work = editor->ext_list;
 
 		if (!work)
 			p = fd->path;
@@ -329,7 +577,6 @@
 					}
 				if (p) break;
 				}
-			string_list_free(ext_list);
 			if (!p) return NULL;
 			}
 		}
@@ -354,6 +601,7 @@
 		p++;
 		}
 
+	if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
 	pathl = path_from_utf8(string->str);
 	g_string_free(string, TRUE);
 
@@ -361,80 +609,25 @@
 }
 
 
-/*
- * The supported macros for editor commands:
- *
- *   %f   first occurence replaced by quoted sequence of filenames, command is run once.
- *        only one occurence of this macro is supported.
- *        ([ls %f] results in [ls "file1" "file2" ... "lastfile"])
- *   %p   command is run for each filename in turn, each instance replaced with single filename.
- *        multiple occurences of this macro is supported for complex shell commands.
- *        This macro will BLOCK THE APPLICATION until it completes, since command is run once
- *        for every file in syncronous order. To avoid blocking add the %v macro, below.
- *        ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"])
- *   none if no macro is supplied, the result is equivalent to "command %f"
- *        ([ls] results in [ls "file1" "file2" ... "lastfile"])
- *
- *  Only one of the macros %f or %p may be used in a given commmand.
- *
- *   %v   must be the first two characters[1] in a command, causes a window to display
- *        showing the output of the command(s).
- *   %V   same as %v except in the case of %p only displays a window for multiple files,
- *        operating on a single file is suppresses the output dialog.
- *
- *   %w   must be first two characters in a command, presence will disable full screen
- *        from exiting upon invocation.
- *
- *
- * [1] Note: %v,%V may also be preceded by "%w".
- */
-
-
-gint editor_command_parse(const gchar *template, GList *list, gchar **output)
+static gint editor_command_parse(const EditorDescription *editor, GList *list, gchar **output)
 {
 	gint flags = 0;
-	const gchar *p = template;
+	const gchar *p;
 	GString *result = NULL;
-	gchar *extensions = NULL;
 
 	if (output)
 		result = g_string_new("");
 
-	if (!template || template[0] == '\0')
+	if (editor->exec[0] == '\0')
 		{
 		flags |= EDITOR_ERROR_EMPTY;
 		goto err;
 		}
 	
+	p = editor->exec;
 	/* skip leading whitespaces if any */
 	while (g_ascii_isspace(*p)) p++;
 
-	/* global flags */
-	while (*p == '%')
-		{
-		switch (*++p)
-			{
-			case 'w':
-				flags |= EDITOR_KEEP_FS;
-				p++;
-				break;
-			case 'v':
-				flags |= EDITOR_VERBOSE;
-				p++;
-				break;
-			case 'V':
-				flags |= EDITOR_VERBOSE_MULTI;
-				p++;
-				break;
-			default:
-				flags |= EDITOR_ERROR_SYNTAX;
-				goto err;
-			}
-		}
-
-	/* skip whitespaces if any */
-	while (g_ascii_isspace(*p)) p++;
-
 	/* command */
 
 	while (*p)
@@ -445,34 +638,14 @@
 			}
 		else /* *p == '%' */
 			{
-			extensions = NULL;
 			gchar *pathl = NULL;
 
 			p++;
 
-			/* for example "%f" or "%{.crw;.raw;.cr2}f" */
-			if (*p == '{')
-				{
-				gchar *end;
-				
-				p++;
-				end = strchr(p, '}');
-				if (!end)
-					{
-					flags |= EDITOR_ERROR_SYNTAX;
-					goto err;
-					}
-
-				extensions = g_strndup(p, end - p);
-				p = end + 1;
-				}
-
 			switch (*p)
 				{
-				case 'd':
-					flags |= EDITOR_DEST;
-					/* fall through */
-				case 'p':
+				case 'f': /* single file */
+				case 'u': /* single url */
 					flags |= EDITOR_FOR_EACH;
 					if (flags & EDITOR_SINGLE_COMMAND)
 						{
@@ -488,8 +661,8 @@
 							goto err;
 							}
 						pathl = editor_command_path_parse((FileData *)list->data,
-										  (flags & EDITOR_DEST) ? PATH_DEST : PATH_FILE,
-										  extensions);
+										  (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
+										  editor);
 						if (!pathl)
 							{
 							flags |= EDITOR_ERROR_NO_FILE;
@@ -502,7 +675,8 @@
 						}
 					break;
 
-				case 'f':
+				case 'F':
+				case 'U':
 					flags |= EDITOR_SINGLE_COMMAND;
 					if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
 						{
@@ -519,7 +693,7 @@
 						while (work)
 							{
 							FileData *fd = work->data;
-							pathl = editor_command_path_parse(fd, PATH_FILE, extensions);
+							pathl = editor_command_path_parse(fd, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
 
 							if (pathl)
 								{
@@ -539,16 +713,40 @@
 							}
 						}
 					break;
+				case 'i':
+					if (output)
+						{
+						result = g_string_append(result, editor->icon);
+						}
+					break;
+				case 'c':
+					if (output)
+						{
+						result = g_string_append(result, editor->name);
+						}
+					break;
+				case 'k':
+					if (output)
+						{
+						result = g_string_append(result, editor->file);
+						}
+					break;
 				case '%':
 					/* %% = % escaping */
 					if (output) result = g_string_append_c(result, *p);
 					break;
+				case 'd':
+				case 'D':
+				case 'n':
+				case 'N':
+				case 'v':
+				case 'm':
+					/* deprecated according to spec, ignore */
+					break;
 				default:
 					flags |= EDITOR_ERROR_SYNTAX;
 					goto err;
 				}
-			if (extensions) g_free(extensions);
-			extensions = NULL;
 			}
 		p++;
 		}
@@ -563,10 +761,10 @@
 		g_string_free(result, TRUE);
 		*output = NULL;
 		}
-	if (extensions) g_free(extensions);
 	return flags;
 }
 
+
 static void editor_child_exit_cb (GPid pid, gint status, gpointer data)
 {
 	EditorData *ed = data;
@@ -577,7 +775,7 @@
 }
 
 
-static gint editor_command_one(const gchar *template, GList *list, EditorData *ed)
+static gint editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
 {
 	gchar *command;
 	FileData *fd = list->data;
@@ -587,7 +785,7 @@
 	gboolean ok;
 
 	ed->pid = -1;
-	ed->flags = editor_command_parse(template, list, &command);
+	ed->flags = editor->flags | editor_command_parse(editor, list, &command);
 
 	ok = !(ed->flags & EDITOR_ERROR_MASK);
 
@@ -618,6 +816,15 @@
 		args[n++] = command;
 		args[n] = NULL;
 
+		if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
+			{
+			setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
+			}
+		else
+			{
+			unsetenv("GEEQIE_DESTINATION");
+			}
+
 		ok = g_spawn_async_with_pipes(working_directory, args, NULL,
 				      G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
 				      NULL, NULL,
@@ -644,7 +851,7 @@
 			{
 			gchar *buf;
 
-			buf = g_strdup_printf(_("Failed to run command:\n%s\n"), template);
+			buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
 			editor_verbose_window_fill(ed->vd, buf, strlen(buf));
 			g_free(buf);
 
@@ -694,7 +901,7 @@
 			}
 		ed->count++;
 
-		error = editor_command_one(ed->command_template, ed->list, ed);
+		error = editor_command_one(ed->editor, ed->list, ed);
 		if (!error && ed->vd)
 			{
 			gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
@@ -801,17 +1008,17 @@
 	editor_command_done(ed);
 }
 
-static gint editor_command_start(const gchar *template, const gchar *text, GList *list, EditorCallback cb, gpointer data)
+static gint editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, EditorCallback cb, gpointer data)
 {
 	EditorData *ed;
-	gint flags = editor_command_parse(template, NULL, NULL);
+	gint flags = editor->flags;
 
 	if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK;
 
 	ed = g_new0(EditorData, 1);
 	ed->list = filelist_copy(list);
 	ed->flags = flags;
-	ed->command_template = g_strdup(template);
+	ed->editor = editor;
 	ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list);
 	ed->count = 0;
 	ed->stopping = FALSE;
@@ -829,29 +1036,28 @@
 	return flags & EDITOR_ERROR_MASK;
 }
 
-gboolean is_valid_editor_command(gint n)
+gboolean is_valid_editor_command(const gchar *key)
 {
-	return (n >= 0 && n < GQ_EDITOR_SLOTS
-		&& options->editor[n].command
-		&& strlen(options->editor[n].command) > 0);
+	if (!key) return FALSE;
+	return g_hash_table_lookup(editors, key) != NULL;
 }
 
-gint start_editor_from_filelist_full(gint n, GList *list, EditorCallback cb, gpointer data)
+gint start_editor_from_filelist_full(const gchar *key, GList *list, EditorCallback cb, gpointer data)
 {
-	gchar *command;
 	gint error;
+	EditorDescription *editor;
+	if (!key) return FALSE;
+	
+	editor = g_hash_table_lookup(editors, key);
 
 	if (!list) return FALSE;
-	if (!is_valid_editor_command(n)) return FALSE;
+	if (!editor) return FALSE;
+
+	error = editor_command_start(editor, editor->name, list, cb, data);
 
-	command = g_locale_from_utf8(options->editor[n].command, -1, NULL, NULL, NULL);
-	error = editor_command_start(command, options->editor[n].name, list, cb, data);
-	g_free(command);
-
-	if (n < GQ_EDITOR_GENERIC_SLOTS && (error & EDITOR_ERROR_MASK))
+	if (error & EDITOR_ERROR_MASK)
 		{
-		gchar *text = g_strdup_printf(_("%s\n#%d \"%s\":\n%s"), editor_get_error_str(error), n+1,
-					      options->editor[n].name, options->editor[n].command);
+		gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
 		
 		file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
 		g_free(text);
@@ -860,12 +1066,12 @@
 	return error;
 }
 
-gint start_editor_from_filelist(gint n, GList *list)
+gint start_editor_from_filelist(const gchar *key, GList *list)
 {
-	return start_editor_from_filelist_full(n, list,  NULL, NULL);
+	return start_editor_from_filelist_full(key, list,  NULL, NULL);
 }
 
-gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data)
+gint start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
 {
 	GList *list;
 	gint error;
@@ -873,28 +1079,36 @@
 	if (!fd) return FALSE;
 
 	list = g_list_append(NULL, fd);
-	error = start_editor_from_filelist_full(n, list, cb, data);
+	error = start_editor_from_filelist_full(key, list, cb, data);
 	g_list_free(list);
 	return error;
 }
 
-gint start_editor_from_file(gint n, FileData *fd)
+gint start_editor_from_file(const gchar *key, FileData *fd)
 {
-	return start_editor_from_file_full(n, fd, NULL, NULL);
+	return start_editor_from_file_full(key, fd, NULL, NULL);
 }
 
-gint editor_window_flag_set(gint n)
+gint editor_window_flag_set(const gchar *key)
 {
-	if (!is_valid_editor_command(n)) return TRUE;
+	EditorDescription *editor;
+	if (!key) return TRUE;
+	
+	editor = g_hash_table_lookup(editors, key);
+	if (!editor) return TRUE;
 
-	return (editor_command_parse(options->editor[n].command, NULL, NULL) & EDITOR_KEEP_FS);
+	return (editor->flags & EDITOR_KEEP_FS);
 }
 
-gint editor_is_filter(gint n)
+gint editor_is_filter(const gchar *key)
 {
-	if (!is_valid_editor_command(n)) return FALSE;
+	EditorDescription *editor;
+	if (!key) return TRUE;
+	
+	editor = g_hash_table_lookup(editors, key);
+	if (!editor) return TRUE;
 
-	return (editor_command_parse(options->editor[n].command, NULL, NULL) & EDITOR_DEST);
+	return (editor->flags & EDITOR_DEST);
 }
 
 const gchar *editor_get_error_str(gint flags)
@@ -909,13 +1123,12 @@
 	return _("Unknown error.");
 }
 
-const gchar *editor_get_name(gint n)
+const gchar *editor_get_name(const gchar *key)
 {
-	if (!is_valid_editor_command(n)) return NULL;
+	EditorDescription *editor = g_hash_table_lookup(editors, key);
 
-	if (options->editor[n].name && strlen(options->editor[n].name) > 0)
-		return options->editor[n].name;
-	
-	return _("(unknown)");
+	if (!editor) return NULL;
+
+	return editor->name;
 }
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- a/src/editors.h	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/editors.h	Sun Feb 01 12:48:14 2009 +0000
@@ -18,6 +18,7 @@
 #define	EDITOR_KEEP_FS            0x00000001
 #define	EDITOR_VERBOSE            0x00000002
 #define	EDITOR_VERBOSE_MULTI      0x00000004
+#define EDITOR_TERMINAL		  0x00000008
 
 #define	EDITOR_DEST               0x00000100
 #define	EDITOR_FOR_EACH           0x00000200
@@ -42,6 +43,11 @@
 				   must be called later */
 };
 
+extern GHashTable *editors;
+
+void editor_load_descriptions(void);
+GList *editor_list_get(void);
+
 
 /*
 Callback is called even on skipped files, with the EDITOR_ERROR_SKIPPED flag set.
@@ -54,28 +60,23 @@
 */
 typedef gint (*EditorCallback) (gpointer ed, gint flags, GList *list, gpointer data);
 
-void editor_set_name(gint n, gchar *name);
-void editor_set_command(gint n, gchar *command);
-
 
 void editor_resume(gpointer ed);
 void editor_skip(gpointer ed);
 
 
-gint editor_command_parse(const gchar *template, GList *list, gchar **output);
 
-void editor_reset_defaults(void);
-gint start_editor_from_file(gint n, FileData *fd);
-gint start_editor_from_filelist(gint n, GList *list);
-gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data);
-gint start_editor_from_filelist_full(gint n, GList *list, EditorCallback cb, gpointer data);
-gint editor_window_flag_set(gint n);
-gint editor_is_filter(gint n);
+gint start_editor_from_file(const gchar *key, FileData *fd);
+gint start_editor_from_filelist(const gchar *key, GList *list);
+gint start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data);
+gint start_editor_from_filelist_full(const gchar *key, GList *list, EditorCallback cb, gpointer data);
+gint editor_window_flag_set(const gchar *key);
+gint editor_is_filter(const gchar *key);
 const gchar *editor_get_error_str(gint flags);
 
-const gchar *editor_get_name(gint n);
+const gchar *editor_get_name(const gchar *key);
 
-gboolean is_valid_editor_command(gint n);
+gboolean is_valid_editor_command(const gchar *key);
 
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- a/src/img-view.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/img-view.c	Sun Feb 01 12:48:14 2009 +0000
@@ -431,6 +431,7 @@
 				stop_signal = FALSE;
 				break;
 			}
+#if 0
 		if (n != -1)
 			{
 			if (!editor_window_flag_set(n))
@@ -440,6 +441,7 @@
 			imd = view_window_active_image(vw);
 			file_util_start_editor_from_file(n, image_get_fd(imd), imd->widget);
 			}
+#endif
 		}
 	else if (event->state & GDK_SHIFT_MASK)
 		{
@@ -1074,19 +1076,18 @@
 {
 	ViewWindow *vw;
 	ImageWindow *imd;
-	gint n;
+	const gchar *key = data;
 
 	vw = submenu_item_get_data(widget);
-	n = GPOINTER_TO_INT(data);
 	if (!vw) return;
 
-	if (!editor_window_flag_set(n))
+	if (!editor_window_flag_set(key))
 		{
 		view_fullscreen_toggle(vw, TRUE);
 		}
 
 	imd = view_window_active_image(vw);
-	file_util_start_editor_from_file(n, image_get_fd(imd), imd->widget);
+	file_util_start_editor_from_file(key, image_get_fd(imd), imd->widget);
 }
 
 static void view_alter_cb(GtkWidget *widget, gpointer data)
--- a/src/layout_image.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/layout_image.c	Sun Feb 01 12:48:14 2009 +0000
@@ -309,16 +309,15 @@
 static void li_pop_menu_edit_cb(GtkWidget *widget, gpointer data)
 {
 	LayoutWindow *lw;
-	gint n;
+	const gchar *key = data;
 
 	lw = submenu_item_get_data(widget);
-	n = GPOINTER_TO_INT(data);
 
-	if (!editor_window_flag_set(n))
+	if (!editor_window_flag_set(key))
 		{
 		layout_image_full_screen_stop(lw);
 		}
-	file_util_start_editor_from_file(n, layout_image_get_fd(lw), lw->window);
+	file_util_start_editor_from_file(key, layout_image_get_fd(lw), lw->window);
 }
 
 static void li_pop_menu_wallpaper_cb(GtkWidget *widget, gpointer data)
--- a/src/layout_util.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/layout_util.c	Sun Feb 01 12:48:14 2009 +0000
@@ -902,18 +902,17 @@
 {
 	LayoutWindow *lw = data;
 	GList *list;
-	gint n;
-
-	n = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(action), "edit_index"));
-
-	if (!editor_window_flag_set(n))
+	const gchar *key = gtk_action_get_name(action);
+	
+	if (!editor_window_flag_set(key))
 		layout_exit_fullscreen(lw);
 
 	list = layout_selection_list(lw);
-	file_util_start_editor_from_filelist(n, list, lw->window);
+	file_util_start_editor_from_filelist(key, list, lw->window);
 	filelist_free(list);
 }
 
+#if 0
 static void layout_menu_edit_update(LayoutWindow *lw)
 {
 	gint i;
@@ -969,6 +968,8 @@
 		}
 }
 
+#endif
+
 /*
  *-----------------------------------------------------------------------------
  * recent menu
@@ -1109,6 +1110,7 @@
   { "EditMenu",		NULL,		N_("_Edit"),			NULL, 		NULL, 	NULL },
   { "SelectMenu",	NULL,		N_("_Select"),			NULL, 		NULL, 	NULL },
   { "AdjustMenu",	NULL,		N_("_Adjust"),			NULL, 		NULL, 	NULL },
+  { "ExternalMenu",	NULL,		N_("E_xternal Editors"),	NULL, 		NULL, 	NULL },
   { "ViewMenu",		NULL,		N_("_View"),			NULL, 		NULL, 	NULL },
   { "DirMenu",          NULL,           N_("_View Directory as"),	NULL, 		NULL, 	NULL },
   { "ZoomMenu",		NULL,		N_("_Zoom"),			NULL, 		NULL, 	NULL },
@@ -1145,17 +1147,6 @@
   { "CloseWindow",	GTK_STOCK_CLOSE,N_("C_lose window"),	"<control>W",	NULL,	CB(layout_menu_close_cb) },
   { "Quit",		GTK_STOCK_QUIT, N_("_Quit"),		"<control>Q",	NULL,	CB(layout_menu_exit_cb) },
 
-  { "Editor0",		NULL,		"editor0",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-  { "Editor1",		NULL,		"editor1",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-  { "Editor2",		NULL,		"editor2",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-  { "Editor3",		NULL,		"editor3",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-  { "Editor4",		NULL,		"editor4",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-  { "Editor5",		NULL,		"editor5",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-  { "Editor6",		NULL,		"editor6",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-  { "Editor7",		NULL,		"editor7",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-  { "Editor8",		NULL,		"editor8",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-  { "Editor9",		NULL,		"editor9",		NULL,		NULL,	CB(layout_menu_edit_cb) },
-
   { "RotateCW",		NULL,	N_("_Rotate clockwise"),	"bracketright",	NULL,	CB(layout_menu_alter_90_cb) },
   { "RotateCCW",	NULL,	N_("Rotate _counterclockwise"),	"bracketleft",	NULL,	CB(layout_menu_alter_90cc_cb) },
   { "Rotate180",	NULL,		N_("Rotate 1_80"),	"<shift>R",	NULL,	CB(layout_menu_alter_180_cb) },
@@ -1279,12 +1270,14 @@
 "      <separator/>"
 "      <menuitem action='CloseWindow'/>"
 "      <menuitem action='Quit'/>"
+"      <separator/>"
 "    </menu>"
 "    <menu action='GoMenu'>"
 "      <menuitem action='FirstImage'/>"
 "      <menuitem action='PrevImage'/>"
 "      <menuitem action='NextImage'/>"
 "      <menuitem action='LastImage'/>"
+"      <separator/>"
 "    </menu>"
 "    <menu action='SelectMenu'>"
 "      <menuitem action='SelectAll'/>"
@@ -1295,17 +1288,6 @@
 "      <separator/>"
 "    </menu>"
 "    <menu action='EditMenu'>"
-"      <menuitem action='Editor0'/>"
-"      <menuitem action='Editor1'/>"
-"      <menuitem action='Editor2'/>"
-"      <menuitem action='Editor3'/>"
-"      <menuitem action='Editor4'/>"
-"      <menuitem action='Editor5'/>"
-"      <menuitem action='Editor6'/>"
-"      <menuitem action='Editor7'/>"
-"      <menuitem action='Editor8'/>"
-"      <menuitem action='Editor9'/>"
-"      <separator/>"
 "      <menu action='AdjustMenu'>"
 "        <menuitem action='RotateCW'/>"
 "        <menuitem action='RotateCCW'/>"
@@ -1321,6 +1303,9 @@
 "      <menuitem action='Maintenance'/>"
 "      <separator/>"
 "      <menuitem action='Wallpaper'/>"
+"      <separator/>"
+"      <menu action='ExternalMenu'>"
+"      </menu>"
 "    </menu>"
 "    <menu action='ViewMenu'>"
 "      <menuitem action='ViewInNewWindow'/>"
@@ -1386,6 +1371,7 @@
 "      <menuitem action='SlideShow'/>"
 "      <menuitem action='SlideShowPause'/>"
 "      <menuitem action='Refresh'/>"
+"      <separator/>"
 "    </menu>"
 "    <menu action='HelpMenu'>"
 "      <separator/>"
@@ -1396,6 +1382,7 @@
 "      <menuitem action='About'/>"
 "      <separator/>"
 "      <menuitem action='LogWindow'/>"
+"      <separator/>"
 "    </menu>"
 "  </menubar>"
 "<accelerator action='PrevImageAlt1'/>"
@@ -1503,6 +1490,116 @@
 	g_string_free(desc, TRUE);
 }
 
+static gint layout_actions_editor_sort(gconstpointer a, gconstpointer b)
+{
+	const EditorDescription *ea = a;
+	const EditorDescription *eb = b;
+	int ret;
+	
+	ret = strcmp(ea->menu_path, eb->menu_path);
+	if (ret != 0) return ret;
+	
+	return g_utf8_collate(ea->name, eb->name);
+}
+
+static GList *layout_actions_editor_menu_path(EditorDescription *editor)
+{
+	gchar **split = g_strsplit(editor->menu_path, "/", 0);
+	gint i = 0;
+	GList *ret = NULL;
+	
+	if (split[0] == NULL) 
+		{
+		g_strfreev(split);
+		return NULL;
+		}
+	
+	while(split[i])
+		{
+		ret = g_list_prepend(ret, g_strdup(split[i]));
+		i++;
+		}
+	
+	g_strfreev(split);
+	
+	ret = g_list_prepend(ret, g_strdup(editor->key));
+	
+	return g_list_reverse(ret);
+}
+
+static void layout_actions_editor_add(GString *desc, GList *path, GList *old_path)
+{
+	gint to_open, to_close, i;
+	while (path && old_path && strcmp((gchar *)path->data, (gchar *)old_path->data) == 0)
+		{
+		path = path->next;
+		old_path = old_path->next;
+		}
+	to_open = g_list_length(path) - 1;
+	to_close = g_list_length(old_path) - 1;
+	
+	for (i =  0; i < to_close; i++)
+		{
+		g_string_append(desc,	"    </menu>");
+		}
+
+	for (i =  0; i < to_open; i++)
+		{
+		g_string_append_printf(desc,	"    <menu action='%s'>", (gchar *)path->data);
+		path = path->next;
+		}
+	
+	if (path)
+		g_string_append_printf(desc, "      <menuitem action='%s'/>", (gchar *)path->data);
+}
+
+static void layout_actions_setup_editors(LayoutWindow *lw)
+{
+	GError *error;
+	GList *editors_list;
+	GList *work;
+	GList *old_path;
+	GString *desc = g_string_new(
+				"<ui>"
+				"  <menubar name='MainMenu'>");
+
+	editors_list = editor_list_get();
+	editors_list = g_list_sort(editors_list, layout_actions_editor_sort);
+	
+	old_path = NULL;
+	work = editors_list;
+	while(work)
+		{
+		GList *path;
+		EditorDescription *editor = work->data;
+		GtkActionEntry entry = { editor->key, NULL, editor->name, editor->hotkey, NULL, G_CALLBACK(layout_menu_edit_cb) };
+		gtk_action_group_add_actions(lw->action_group, &entry, 1, lw);
+		
+		path = layout_actions_editor_menu_path(editor);
+		layout_actions_editor_add(desc, path, old_path);
+		
+		string_list_free(old_path);
+		old_path = path;
+		work = work->next;
+		}
+
+	layout_actions_editor_add(desc, NULL, old_path);
+	string_list_free(old_path);
+
+	g_string_append(desc,   "  </menubar>"
+				"</ui>" );
+
+	error = NULL;
+	if (!gtk_ui_manager_add_ui_from_string(lw->ui_manager, desc->str, -1, &error))
+		{
+		g_message("building menus failed: %s", error->message);
+		g_error_free(error);
+		exit(EXIT_FAILURE);
+		}
+	g_string_free(desc, TRUE);
+	g_list_free(editors_list);
+}
+
 void layout_actions_setup(LayoutWindow *lw)
 {
 	GError *error;
@@ -1539,6 +1636,7 @@
 		}
 	
 	layout_actions_setup_marks(lw);
+	layout_actions_setup_editors(lw);
 	layout_copy_path_update(lw);
 }
 
@@ -1775,7 +1873,7 @@
 	layout_util_sync_views(lw);
 	layout_util_sync_thumb(lw);
 	layout_menu_recent_update(lw);
-	layout_menu_edit_update(lw);
+//	layout_menu_edit_update(lw);
 }
 
 /*
--- a/src/layout_util.h	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/layout_util.h	Sun Feb 01 12:48:14 2009 +0000
@@ -23,7 +23,7 @@
 void layout_util_sync(LayoutWindow *lw);
 
 
-void layout_edit_update_all(void);
+//void layout_edit_update_all(void);
 
 void layout_recent_update_all(void);
 void layout_recent_add_path(const gchar *path);
--- a/src/main.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/main.c	Sun Feb 01 12:48:14 2009 +0000
@@ -31,6 +31,7 @@
 #include "cache_maint.h"
 #include "thumb.h"
 #include "metadata.h"
+#include "editors.h"
 
 #include <gdk/gdkkeysyms.h> /* for keyboard values */
 
@@ -771,6 +772,8 @@
 	filter_add_defaults();
 	filter_rebuild();
 
+	editor_load_descriptions();
+
 	accel_map_load();
 
 	if (startup_blank)
--- a/src/main.h	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/main.h	Sun Feb 01 12:48:14 2009 +0000
@@ -87,7 +87,6 @@
 
 #define MOUSEWHEEL_SCROLL_SIZE 20
 
-#define GQ_EDITOR_GENERIC_SLOTS 10
 
 #define GQ_DEFAULT_SHELL_PATH "/bin/sh"
 #define GQ_DEFAULT_SHELL_OPTIONS "-c"
--- a/src/menu.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/menu.c	Sun Feb 01 12:48:14 2009 +0000
@@ -64,26 +64,22 @@
  *-----------------------------------------------------------------------------
  */
 
-static void add_edit_items(GtkWidget *menu, GCallback func, GtkAccelGroup *accel_grp)
+static void add_edit_items(GtkWidget *menu, GCallback func)
 {
-	gint i;
-
-	for (i = 0; i < GQ_EDITOR_GENERIC_SLOTS; i++)
-		{
-		gchar *text;
-		const gchar *name = editor_get_name(i);
+	GList *editors_list = editor_list_get();
+	GList *work = editors_list;
 
-		if (!name) continue;
+	while (work)
+		{
+		const EditorDescription *editor = work->data;
+		work = work->next;
+		
+		menu_item_add(menu, editor->name, func, editor->key);
+		}
+	
+	g_list_free(editors_list);
+}
 
-		text = g_strdup_printf(_("_%d %s..."), i, name);
-		if (accel_grp)
-			add_menu_item(menu, text, accel_grp, i + 49, GDK_CONTROL_MASK, func, GINT_TO_POINTER(i));
-		else
-			menu_item_add(menu, text, func, GINT_TO_POINTER(i));
-		g_free(text);
-		
-		}
-}
 
 GtkWidget *submenu_add_edit(GtkWidget *menu, GtkWidget **menu_item, GCallback func, gpointer data)
 {
@@ -94,7 +90,7 @@
 
 	submenu = gtk_menu_new();
 	g_object_set_data(G_OBJECT(submenu), "submenu_data", data);
-	add_edit_items(submenu, func, NULL);
+	add_edit_items(submenu, func);
 
 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
 
--- a/src/options.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/options.c	Sun Feb 01 12:48:14 2009 +0000
@@ -131,7 +131,8 @@
 	options->panels.sort.enabled = FALSE;
 	options->panels.sort.mode_state = 0;
 	options->panels.sort.selection_state = 0;
-
+	options->panels.sort.action_filter = NULL;
+	
 	options->progressive_key_scrolling = TRUE;
 	
 	options->metadata.enable_metadata_dirs = FALSE;
@@ -178,14 +179,6 @@
 	gchar *path;
 	gint i;
 
-	for (i = 0; i < GQ_EDITOR_SLOTS; i++)
-		{
-		editor_set_name(i, NULL);
-		editor_set_command(i, NULL);
-		}
-
-	editor_reset_defaults();
-
 	bookmark_add_default(_("Home"), homedir());
 	path = g_build_filename(homedir(), "Desktop", NULL);
 	bookmark_add_default(_("Desktop"), path);
--- a/src/options.h	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/options.h	Sun Feb 01 12:48:14 2009 +0000
@@ -110,9 +110,6 @@
 		gboolean rectangular_selection;
 	} collections;
 
-	/* editors */
-	Editor editor[GQ_EDITOR_SLOTS];
-
 	/* shell */
 	struct {
 		gchar *path;
@@ -221,6 +218,7 @@
 			gint mode_state;
 			gint action_state;
 			gint selection_state;
+			gchar *action_filter;
 		} sort;
 	} panels;
 
--- a/src/pan-view.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/pan-view.c	Sun Feb 01 12:48:14 2009 +0000
@@ -1330,7 +1330,7 @@
 				stop_signal = FALSE;
 				break;
 			}
-
+#if 0
 		if (n != -1 && fd)
 			{
 			if (!editor_window_flag_set(n))
@@ -1339,6 +1339,7 @@
 				}
 			file_util_start_editor_from_file(n, fd, GTK_WIDGET(pr));
 			}
+#endif
 		}
 	else
 		{
@@ -2669,20 +2670,19 @@
 {
 	PanWindow *pw;
 	FileData *fd;
-	gint n;
+	const gchar *key = data;
 
 	pw = submenu_item_get_data(widget);
-	n = GPOINTER_TO_INT(data);
 	if (!pw) return;
 
 	fd = pan_menu_click_fd(pw);
 	if (fd)
 		{
-		if (!editor_window_flag_set(n))
+		if (!editor_window_flag_set(key))
 			{
 			pan_fullscreen_toggle(pw, TRUE);
 			}
-		file_util_start_editor_from_file(n, fd, pw->imd->widget);
+		file_util_start_editor_from_file(key, fd, pw->imd->widget);
 		}
 }
 
--- a/src/preferences.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/preferences.c	Sun Feb 01 12:48:14 2009 +0000
@@ -99,8 +99,6 @@
 static GtkWidget *startup_path_entry;
 static GtkWidget *home_path_entry;
 static GtkListStore *filter_store = NULL;
-static GtkWidget *editor_name_entry[GQ_EDITOR_SLOTS];
-static GtkWidget *editor_command_entry[GQ_EDITOR_SLOTS];
 
 static GtkWidget *layout_widget;
 
@@ -177,6 +175,7 @@
 		}
 }
 
+#if 0
 static void config_parse_editor_entries(GtkWidget **editor_name_entry, GtkWidget **editor_command_entry)
 {
 	gint i;
@@ -223,7 +222,7 @@
 
 	g_string_free(errmsg, TRUE);
 }
-
+#endif
 
 static void config_window_apply(void)
 {
@@ -231,8 +230,8 @@
 	gint i;
 	gint refresh = FALSE;
 
-	config_parse_editor_entries(editor_name_entry, editor_command_entry); 
-	layout_edit_update_all();
+//	config_parse_editor_entries(editor_name_entry, editor_command_entry); 
+//	layout_edit_update_all();
 
 	config_entry_to_option(safe_delete_path_entry, &options->file_ops.safe_delete_path, remove_trailing_slash);
 	
@@ -874,6 +873,7 @@
 				 !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
 }
 
+#if 0
 static void editor_default_ok_cb(GenericDialog *gd, gpointer data)
 {
 	gint i;
@@ -908,6 +908,7 @@
 {
 	help_window_show("editors");
 }
+#endif
 
 static void safe_delete_view_cb(GtkWidget *widget, gpointer data)
 {
@@ -1389,7 +1390,7 @@
 	gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
 	gtk_widget_show(button);
 }
-
+#if 0
 /* editors tab */
 static void config_tab_editors(GtkWidget *notebook)
 {
@@ -1466,6 +1467,7 @@
 	gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, FALSE, 0);
 	gtk_widget_show(button);
 }
+#endif
 
 /* properties tab */
 static void config_tab_properties(GtkWidget *notebook)
@@ -1756,7 +1758,7 @@
 
 		entry = gtk_entry_new();
 		gtk_entry_set_max_length(GTK_ENTRY(entry), EDITOR_NAME_MAX_LENGTH);
-		gtk_widget_set_size_request(editor_name_entry[i], 30, -1);
+//		gtk_widget_set_size_request(editor_name_entry[i], 30, -1);
 		if (options->color_profile.input_name[i])
 			{
 			gtk_entry_set_text(GTK_ENTRY(entry), options->color_profile.input_name[i]);
@@ -1862,7 +1864,7 @@
 	config_tab_image(notebook);
 	config_tab_windows(notebook);
 	config_tab_filtering(notebook);
-	config_tab_editors(notebook);
+//	config_tab_editors(notebook);
 	config_tab_properties(notebook);
 	config_tab_advanced(notebook);
 
--- a/src/rcfile.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/rcfile.c	Sun Feb 01 12:48:14 2009 +0000
@@ -353,6 +353,7 @@
 	WRITE_INT(panels.sort.action_state);
 	WRITE_INT(panels.sort.mode_state);
 	WRITE_INT(panels.sort.selection_state);
+	WRITE_CHAR(panels.sort.action_filter);
 
 	WRITE_SUBTITLE("Properties dialog Options");
 	WRITE_CHAR(properties.tabs_order);
@@ -511,7 +512,7 @@
 	WRITE_CHAR(helpers.html_browser.command_name);
 	WRITE_CHAR(helpers.html_browser.command_line);
 
-
+#if 0
 	WRITE_SUBTITLE("External Programs");
 	secure_fprintf(ssi, "# Maximum of %d programs (external_1 through external_%d)\n", GQ_EDITOR_GENERIC_SLOTS, GQ_EDITOR_GENERIC_SLOTS);
 	secure_fprintf(ssi, "# external_%d through external_%d are used for file ops\n", GQ_EDITOR_GENERIC_SLOTS + 1, GQ_EDITOR_SLOTS);
@@ -526,7 +527,7 @@
 		g_free(qname);
 		g_free(qcommand);
 		}
-
+#endif
 
 	WRITE_SUBTITLE("Exif Options");
 	secure_fprintf(ssi, "# Display: 0: never\n"
@@ -734,6 +735,7 @@
 		READ_INT(panels.sort.action_state);
 		READ_INT(panels.sort.mode_state);
 		READ_INT(panels.sort.selection_state);
+		READ_CHAR(panels.sort.action_filter);
 
 		/* properties dialog options */
 		READ_CHAR(properties.tabs_order);
@@ -878,7 +880,7 @@
 		READ_CHAR(helpers.html_browser.command_line);
 
 		/* External Programs */
-
+#if 0
 		if (is_numbered_option(option, "external_", &i))
 			{
 			if (i > 0 && i <= GQ_EDITOR_SLOTS)
@@ -891,7 +893,7 @@
 				}
 			continue;
 			}
-
+#endif
 		/* Exif */
 		if (0 == g_ascii_strncasecmp(option, "exif.display.", 13))
 			{
--- a/src/search.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/search.c	Sun Feb 01 12:48:14 2009 +0000
@@ -657,12 +657,12 @@
 	search_status_update(sd);
 }
 
-static void search_result_edit_selected(SearchData *sd, gint n)
+static void search_result_edit_selected(SearchData *sd, const gchar *key)
 {
 	GList *list;
 
 	list = search_result_selection_list(sd);
-	file_util_start_editor_from_filelist(n, list, sd->window);
+	file_util_start_editor_from_filelist(key, list, sd->window);
 	filelist_free(list);
 }
 
@@ -908,13 +908,12 @@
 static void sr_menu_edit_cb(GtkWidget *widget, gpointer data)
 {
 	SearchData *sd;
-	gint n;
+	const gchar *key = data;
 
 	sd = submenu_item_get_data(widget);
-	n = GPOINTER_TO_INT(data);
 	if (!sd) return;
 
-	search_result_edit_selected(sd, n);
+	search_result_edit_selected(sd, key);
 }
 
 static void sr_menu_info_cb(GtkWidget *widget, gpointer data)
@@ -1282,11 +1281,12 @@
 				stop_signal = FALSE;
 				break;
 			}
-
+#if 0
 		if (edit_val >= 0)
 			{
 			search_result_edit_selected(sd, edit_val);
 			}
+#endif
 		}
 	else
 		{
--- a/src/typedefs.h	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/typedefs.h	Sun Feb 01 12:48:14 2009 +0000
@@ -38,14 +38,11 @@
 	FILEVIEW_ICON
 } FileViewType;
 
-typedef enum {
-	CMD_COPY = GQ_EDITOR_GENERIC_SLOTS,
-	CMD_MOVE,
-	CMD_RENAME,
-	CMD_DELETE,
-	CMD_FOLDER,
-	GQ_EDITOR_SLOTS
-} SpecialEditor;
+#define	CMD_COPY     "geeqie-copy-command.desktop"
+#define	CMD_MOVE     "geeqie-move-command.desktop"
+#define	CMD_RENAME   "geeqie-rename-command.desktop"
+#define	CMD_DELETE   "geeqie-delete-command.desktop"
+#define	CMD_FOLDER   "geeqie-folder-command.desktop"
 
 typedef enum {
 	SORT_NONE,
@@ -202,12 +199,22 @@
 
 typedef struct _ExifData ExifData;
 
-typedef struct _Editor Editor;
-struct _Editor {
-	gchar *name;
-	gchar *command;
+typedef struct _EditorDescription EditorDescription;
+
+struct _EditorDescription {
+	gchar *key; /* desktop file name, not including path, including extension */
+	gchar *name; /* localized name presented to user */
+	gchar *exec;
+	gchar *menu_path;
+	gchar *hotkey;
+	GList *ext_list;
+	gchar *icon;
+	gchar *file;
+	gint flags;
+	gboolean hidden;
 };
 
+
 struct _ImageLoader;
 
 typedef void (* ThumbLoaderFunc)(ThumbLoader *tl, gpointer data);
@@ -745,6 +752,5 @@
 	gint unlink_on_error; /**< whether to remove temporary file on save failure, TRUE by default */
 };
 
-
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- a/src/utilops.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/utilops.c	Sun Feb 01 12:48:14 2009 +0000
@@ -312,7 +312,7 @@
 	/* data for the operation itself, internal or external */
 	gboolean external; /* TRUE for external command, FALSE for internal */
 	
-	gint external_command;
+	gchar *external_command;
 	gpointer resume_data;
 	
 	FileUtilDoneFunc done_func;
@@ -345,7 +345,7 @@
 	ud->phase = UTILITY_PHASE_START;
 	ud->update_idle_id = -1;
 	ud->perform_idle_id = -1;
-	ud->external_command = -1;
+	ud->external_command = NULL;
 	return ud;
 }
 
@@ -363,6 +363,7 @@
 	if (ud->gd) generic_dialog_close(ud->gd);
 	
 	g_free(ud->dest_path);
+	g_free(ud->external_command);
 
 	g_free(ud);
 }
@@ -791,29 +792,29 @@
 	switch (ud->type)
 		{
 		case UTILITY_TYPE_COPY:
-			ud->external_command = CMD_COPY;
+			ud->external_command = g_strdup(CMD_COPY);
 			break;
 		case UTILITY_TYPE_MOVE:
-			ud->external_command = CMD_MOVE;
+			ud->external_command = g_strdup(CMD_MOVE);
 			break;
 		case UTILITY_TYPE_RENAME:
 		case UTILITY_TYPE_RENAME_FOLDER:
-			ud->external_command = CMD_RENAME;
+			ud->external_command = g_strdup(CMD_RENAME);
 			break;
 		case UTILITY_TYPE_DELETE:
 		case UTILITY_TYPE_DELETE_LINK:
 		case UTILITY_TYPE_DELETE_FOLDER:
-			ud->external_command = CMD_DELETE;
+			ud->external_command = g_strdup(CMD_DELETE);
 			break;
 		case UTILITY_TYPE_CREATE_FOLDER:
-			ud->external_command = CMD_FOLDER;
+			ud->external_command = g_strdup(CMD_FOLDER);
 			break;
 		case UTILITY_TYPE_FILTER:
 		case UTILITY_TYPE_EDITOR:
-			g_assert(ud->external_command != -1); /* it should be already set */
+			g_assert(ud->external_command != NULL); /* it should be already set */
 			break;
 		case UTILITY_TYPE_WRITE_METADATA:
-			ud->external_command = -1;
+			ud->external_command = NULL;
 		}
 
 	if (is_valid_editor_command(ud->external_command))
@@ -1809,7 +1810,7 @@
 	file_util_dialog_run(ud);
 }
 
-static void file_util_start_editor_full(gint n, FileData *source_fd, GList *source_list, const gchar *dest_path, GtkWidget *parent, UtilityPhase phase)
+static void file_util_start_editor_full(const gchar *key, FileData *source_fd, GList *source_list, const gchar *dest_path, GtkWidget *parent, UtilityPhase phase)
 {
 	UtilityData *ud;
 	GList *flist = filelist_copy(source_list);
@@ -1828,7 +1829,7 @@
 		return;
 		}
 
-	if (editor_is_filter(n))
+	if (editor_is_filter(key))
 		ud = file_util_data_new(UTILITY_TYPE_FILTER);
 	else
 		ud = file_util_data_new(UTILITY_TYPE_EDITOR);
@@ -1841,7 +1842,7 @@
 
 	ud->with_sidecars = TRUE;
 	
-	ud->external_command = n;
+	ud->external_command = g_strdup(key);
 
 	ud->dir_fd = NULL;
 	ud->flist = flist;
@@ -2331,24 +2332,24 @@
 }
 
 
-void file_util_start_editor_from_file(gint n, FileData *fd, GtkWidget *parent)
+void file_util_start_editor_from_file(const gchar *key, FileData *fd, GtkWidget *parent)
 {
-	file_util_start_editor_full(n, fd, NULL, NULL, parent, UTILITY_PHASE_ENTERING);
+	file_util_start_editor_full(key, fd, NULL, NULL, parent, UTILITY_PHASE_ENTERING);
 }
 
-void file_util_start_editor_from_filelist(gint n, GList *list, GtkWidget *parent)
+void file_util_start_editor_from_filelist(const gchar *key, GList *list, GtkWidget *parent)
 {
-	file_util_start_editor_full(n, NULL, list, NULL, parent, UTILITY_PHASE_ENTERING);
+	file_util_start_editor_full(key, NULL, list, NULL, parent, UTILITY_PHASE_ENTERING);
 }
 
-void file_util_start_filter_from_file(gint n, FileData *fd, const gchar *dest_path, GtkWidget *parent)
+void file_util_start_filter_from_file(const gchar *key, FileData *fd, const gchar *dest_path, GtkWidget *parent)
 {
-	file_util_start_editor_full(n, fd, NULL, dest_path, parent, UTILITY_PHASE_ENTERING);
+	file_util_start_editor_full(key, fd, NULL, dest_path, parent, UTILITY_PHASE_ENTERING);
 }
 
-void file_util_start_filter_from_filelist(gint n, GList *list, const gchar *dest_path, GtkWidget *parent)
+void file_util_start_filter_from_filelist(const gchar *key, GList *list, const gchar *dest_path, GtkWidget *parent)
 {
-	file_util_start_editor_full(n, NULL, list, dest_path, parent, UTILITY_PHASE_ENTERING);
+	file_util_start_editor_full(key, NULL, list, dest_path, parent, UTILITY_PHASE_ENTERING);
 }
 
 void file_util_delete_dir(FileData *fd, GtkWidget *parent)
--- a/src/utilops.h	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/utilops.h	Sun Feb 01 12:48:14 2009 +0000
@@ -50,10 +50,10 @@
 void file_util_copy_simple(GList *list, const gchar *dest_path, GtkWidget *parent);
 void file_util_rename_simple(FileData *fd, const gchar *dest_path, GtkWidget *parent);
 
-void file_util_start_editor_from_file(gint n, FileData *fd, GtkWidget *parent);
-void file_util_start_editor_from_filelist(gint n, GList *list, GtkWidget *parent);
-void file_util_start_filter_from_file(gint n, FileData *fd, const gchar *dest_path, GtkWidget *parent);
-void file_util_start_filter_from_filelist(gint n, GList *list, const gchar *dest_path, GtkWidget *parent);
+void file_util_start_editor_from_file(const gchar *key, FileData *fd, GtkWidget *parent);
+void file_util_start_editor_from_filelist(const gchar *key, GList *list, GtkWidget *parent);
+void file_util_start_filter_from_file(const gchar *key, FileData *fd, const gchar *dest_path, GtkWidget *parent);
+void file_util_start_filter_from_filelist(const gchar *key, GList *list, const gchar *dest_path, GtkWidget *parent);
 
 void file_util_delete_dir(FileData *source_fd, GtkWidget *parent);
 
--- a/src/view_dir.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/view_dir.c	Sun Feb 01 12:48:14 2009 +0000
@@ -327,20 +327,18 @@
 	ViewDir *vd = data;
 	const gchar *path;
 	GList *list;
-	guint n;
-
+	const gchar *key;
+	
 	if (!vd->drop_fd) return;
 	
-	n = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), "filter_idx"));
-	if (n == 0) return;
-	n--;
+	key = g_object_get_data(G_OBJECT(widget), "filter_key");
 
 	path = vd->drop_fd->path;
 	list = vd->drop_list;
 
 	vd->drop_list = NULL;
 
-	file_util_start_filter_from_filelist(n, list, path, vd->widget);
+	file_util_start_filter_from_filelist(key, list, path, vd->widget);
 }
 
 
@@ -348,7 +346,8 @@
 GtkWidget *vd_drop_menu(ViewDir *vd, gint active)
 {
 	GtkWidget *menu;
-	guint i;
+	GList *editors_list = editor_list_get();
+	GList *work = editors_list;
 
 	menu = popup_menu_short_lived();
 	g_signal_connect(G_OBJECT(menu), "destroy",
@@ -358,17 +357,19 @@
 				      G_CALLBACK(vd_drop_menu_copy_cb), vd);
 	menu_item_add_sensitive(menu, _("_Move"), active, G_CALLBACK(vd_drop_menu_move_cb), vd);
 
-	for (i = 0; i < GQ_EDITOR_GENERIC_SLOTS; i++)
+	while (work)
 		{
 		GtkWidget *item;
-
-		const gchar *name = editor_get_name(i);
-		if (!name || !editor_is_filter(i)) continue;
+		const EditorDescription *editor = work->data;
+		work = work->next;
+		
+		if (!editor_is_filter(editor->key)) continue;
+		item = menu_item_add_sensitive(menu, editor->name, active, G_CALLBACK(vd_drop_menu_filter_cb), vd);
 
-		item = menu_item_add_sensitive(menu, name, active, G_CALLBACK(vd_drop_menu_filter_cb), vd);
-
-		g_object_set_data(G_OBJECT(item), "filter_idx", GUINT_TO_POINTER(i + 1));
+		g_object_set_data(G_OBJECT(item), "filter_key", editor->key);
 		}
+	
+	g_list_free(editors_list);
 
 	menu_item_add_divider(menu);
 	menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, NULL, vd);
--- a/src/view_file.c	Thu Jan 29 19:43:34 2009 +0000
+++ b/src/view_file.c	Sun Feb 01 12:48:14 2009 +0000
@@ -299,16 +299,15 @@
 static void vf_pop_menu_edit_cb(GtkWidget *widget, gpointer data)
 {
 	ViewFile *vf;
-	gint n;
+	const gchar *key = data;
 	GList *list;
 
 	vf = submenu_item_get_data(widget);
-	n = GPOINTER_TO_INT(data);
 
 	if (!vf) return;
 
 	list = vf_pop_menu_file_list(vf);
-	file_util_start_editor_from_filelist(n, list, vf->listview);
+	file_util_start_editor_from_filelist(key, list, vf->listview);
 	filelist_free(list);
 }