Trim trailing white spaces.
[geeqie.git] / src / editors.c
index d71f177..270c5d3 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Geeqie
  * (C) 2006 John Ellis
- * Copyright (C) 2008 The Geeqie Team
+ * Copyright (C) 2008 - 2012 The Geeqie Team
  *
  * Author: John Ellis
  *
 #include "main.h"
 #include "editors.h"
 
-#include "debug.h"
 #include "filedata.h"
 #include "filefilter.h"
-#include "utilops.h"
+#include "misc.h"
+#include "pixbuf_util.h"
 #include "ui_fileops.h"
 #include "ui_spinner.h"
 #include "ui_utildlg.h"
+#include "utilops.h"
 
 #include <errno.h>
 
@@ -28,8 +29,6 @@
 #define EDITOR_WINDOW_WIDTH 500
 #define EDITOR_WINDOW_HEIGHT 300
 
-#define COMMAND_SHELL "/bin/sh"
-#define COMMAND_OPT  "-c"
 
 
 typedef struct _EditorVerboseData EditorVerboseData;
@@ -44,9 +43,8 @@ struct _EditorVerboseData {
 
 typedef struct _EditorData EditorData;
 struct _EditorData {
-       gint flags;
+       EditorFlags flags;
        GPid pid;
-       gchar *command_template;
        GList *list;
        gint count;
        gint total;
@@ -54,41 +52,15 @@ struct _EditorData {
        EditorVerboseData *vd;
        EditorCallback callback;
        gpointer data;
+       const EditorDescription *editor;
+       gchar *working_directory; /* fallback if no files are given (editor_no_param) */
 };
 
 
-static gchar *editor_slot_defaults[GQ_EDITOR_SLOTS * 2] = {
-       N_("The Gimp"), "gimp-remote %{.cr2;.crw;.nef;.raw;*}f",
-       N_("XV"), "xv %f",
-       N_("Xpaint"), "xpaint %f",
-       N_("UFraw"), "ufraw %{.cr2;.crw;.nef;.raw}p",
-       N_("Add XMP sidecar"), "%vFILE=%{.cr2;.crw;.nef;.raw}p;XMP=`echo \"$FILE\"|sed -e 's|\\.[^.]*$|.xmp|'`; exiftool -tagsfromfile \"$FILE\" \"$XMP\"",
-       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 EditorFlags editor_command_next_start(EditorData *ed);
+static EditorFlags editor_command_next_finish(EditorData *ed, gint status);
+static EditorFlags editor_command_done(EditorData *ed);
 
 /*
  *-----------------------------------------------------------------------------
@@ -96,19 +68,414 @@ static gint editor_command_done(EditorData *ed);
  *-----------------------------------------------------------------------------
  */
 
-void editor_reset_defaults(void)
+GHashTable *editors = NULL;
+GtkListStore *desktop_file_list;
+gboolean editors_finished = FALSE;
+
+#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 (!editor) return;
+       
+       g_free(editor->key);
+       g_free(editor->name);
+       g_free(editor->icon);
+       g_free(editor->exec);
+       g_free(editor->menu_path);
+       g_free(editor->hotkey);
+       g_free(editor->comment);
+       string_list_free(editor->ext_list);
+       g_free(editor->file);
+       g_free(editor);
+}
+
+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 */
+       
+       static const gchar *conv_table[][2] = {
+               {"application/x-ufraw", ".ufraw"},
+               {"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"},
+               {"application/x-ptoptimizer-script",    ".pto"},
+               {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;
+}
+
+gboolean editor_read_desktop_file(const gchar *path)
 {
+       GKeyFile *key_file;
+       EditorDescription *editor;
+       gchar *extensions;
+       gchar *type;
+       const gchar *key = filename_from_path(path);
+       gchar **categories, **only_show_in, **not_show_in;
+       gchar *try_exec;
+       GtkTreeIter iter;
+       gboolean category_geeqie = FALSE;
+
+       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;
+               }
+
+       type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
+       if (!type || strcmp(type, "Application") != 0)
+               {
+               /* We only consider desktop entries of Application type */
+               g_key_file_free(key_file);
+               g_free(type);
+               return FALSE;
+               }
+       g_free(type);
+       
+       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)
+           || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", 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)
+                               {
+                               found = TRUE;
+                               }
+                       if (strcmp(categories[i], "X-Geeqie") == 0)
+                               {
+                               found = TRUE;
+                               category_geeqie = TRUE;
+                               break;
+                               }
+                       }
+               if (!found) editor->ignored = TRUE;
+               g_strfreev(categories);
+               }
+       else
+               {
+               editor->ignored = 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->ignored = TRUE;
+               g_strfreev(only_show_in);
+               }
+
+       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->ignored = TRUE;
+               g_strfreev(not_show_in);
+               }
+               
+               
+       try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
+       if (try_exec && !editor->hidden && !editor->ignored)
+               {
+               gchar *try_exec_res = g_find_program_in_path(try_exec);
+               if (!try_exec_res) editor->hidden = TRUE;
+               g_free(try_exec_res);
+               g_free(try_exec);
+               }
+
+       if (editor->ignored)
+               {
+               /* ignored 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);
+       
+       /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
+       if (editor->icon && !g_path_is_absolute(editor->icon))
+               {
+               gchar *ext = strrchr(editor->icon, '.');
+               
+               if (ext && strlen(ext) == 4 &&
+                   (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
+                       {
+                       log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
+                                  editor->file, editor->icon);
+                       
+                       // drop extension
+                       *ext = '\0';
+                       }
+               }
+       if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
+               {
+               g_free(editor->icon);
+               editor->icon = NULL;
+               }
+
+       editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
+       
+       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);
+
+       editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", 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, FALSE, NULL);
+
+       if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
+
+       g_key_file_free(key_file);
+
+       if (editor->ignored) return TRUE;
+       
+       gtk_list_store_append(desktop_file_list, &iter);
+       gtk_list_store_set(desktop_file_list, &iter,
+                          DESKTOP_FILE_COLUMN_KEY, key,
+                          DESKTOP_FILE_COLUMN_NAME, editor->name,
+                          DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
+                          DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
+                          DESKTOP_FILE_COLUMN_PATH, path, -1);
+       
+       return TRUE;
+}
+
+static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
+{
+       EditorDescription *editor = value;
+       return editor->hidden || editor->ignored;
+}
+
+void editor_table_finish(void)
+{
+       g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
+       editors_finished = TRUE;
+}
+
+void editor_table_clear(void)
+{
+       if (desktop_file_list)
+               {
+               gtk_list_store_clear(desktop_file_list);
+               }
+       else
+               {
+               desktop_file_list = gtk_list_store_new(DESKTOP_FILE_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING);
+               }
+       if (editors)
+               {
+               g_hash_table_destroy(editors);
+               }
+       editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
+       editors_finished = FALSE;
+}
+
+static GList *editor_add_desktop_dir(GList *list, 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 list;
+               }
+       while ((dir = readdir(dp)) != NULL)
+               {
+               gchar *namel = dir->d_name;
+               
+               if (g_str_has_suffix(namel, ".desktop"))
+                       {
+                       gchar *name = path_to_utf8(namel);
+                       gchar *dpath = g_build_filename(path, name, NULL);
+                       list = g_list_prepend(list, dpath);
+                       g_free(name);
+                       }
+               }
+       closedir(dp);
+       return list;
+}
+
+GList *editor_get_desktop_files(void)
+{
+       gchar *path;
+       gchar *xdg_data_dirs;
+       gchar *all_dirs;
+       gchar **split_dirs;
        gint i;
+       GList *list = NULL;
+       
+       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_home_get(), ":", xdg_data_dirs, NULL);
+       
+       g_free(xdg_data_dirs);
+
+       split_dirs = g_strsplit(all_dirs, ":", 0);
+       
+       g_free(all_dirs);
 
-       for (i = 0; i < GQ_EDITOR_SLOTS; i++)
+       for (i = 0; split_dirs[i]; i++);
+       for (--i; i >= 0; i--)
                {
-               g_free(options->editor_name[i]);
-               options->editor_name[i] = g_strdup(_(editor_slot_defaults[i * 2]));
-               g_free(options->editor_command[i]);
-               options->editor_command[i] = g_strdup(editor_slot_defaults[i * 2 + 1]);
+               path = g_build_filename(split_dirs[i], "applications", NULL);
+               list = editor_add_desktop_dir(list, path);
+               g_free(path);
                }
+               
+       g_strfreev(split_dirs);
+       return list;
 }
 
+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 explicitly */
+       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);
+}
+
+static gint editor_sort(gconstpointer a, gconstpointer b)
+{
+       const EditorDescription *ea = a;
+       const EditorDescription *eb = b;
+       gint ret;
+       
+       ret = strcmp(ea->menu_path, eb->menu_path);
+       if (ret != 0) return ret;
+       
+       return g_utf8_collate(ea->name, eb->name);
+}
+
+GList *editor_list_get(void)
+{
+       GList *editors_list = NULL;
+       
+       if (!editors_finished) return NULL;
+       
+       g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
+       editors_list = g_list_sort(editors_list, editor_sort);
+
+       return editors_list;
+}
+
+/* ------------------------------ */
+
+
 static void editor_verbose_data_free(EditorData *ed)
 {
        if (!ed->vd) return;
@@ -119,7 +486,7 @@ static void editor_verbose_data_free(EditorData *ed)
 static void editor_data_free(EditorData *ed)
 {
        editor_verbose_data_free(ed);
-       g_free(ed->command_template);
+       g_free(ed->working_directory);
        g_free(ed);
 }
 
@@ -158,7 +525,7 @@ static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *tex
 
        vd = g_new0(EditorVerboseData, 1);
 
-       vd->gd = file_util_gen_dlg(_("Edit command results"), GQ_WMCLASS, "editor_results",
+       vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
                                   NULL, FALSE,
                                   NULL, ed);
        buf = g_strdup_printf(_("Output of %s"), text);
@@ -219,7 +586,7 @@ static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
 
        if (ed->total)
                {
-               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (double)ed->count / ed->total);
+               gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
                }
 
        gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
@@ -268,22 +635,24 @@ static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition,
 
 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, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
 {
        GString *string;
        gchar *pathl;
        const gchar *p = NULL;
 
+       DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
+
        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;
@@ -296,19 +665,19 @@ static gchar *editor_command_path_parse(const FileData *fd, PathType type, const
                                work = work->next;
 
                                if (strcmp(ext, "*") == 0 ||
-                                   strcasecmp(ext, fd->extension) == 0)
+                                   g_ascii_strcasecmp(ext, fd->extension) == 0)
                                        {
                                        p = fd->path;
                                        break;
                                        }
 
-                               work2 = fd->sidecar_files;
+                               work2 = consider_sidecars ? fd->sidecar_files : NULL;
                                while (work2)
                                        {
                                        FileData *sfd = work2->data;
                                        work2 = work2->next;
 
-                                       if (strcasecmp(ext, sfd->extension) == 0)
+                                       if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
                                                {
                                                p = sfd->path;
                                                break;
@@ -316,7 +685,6 @@ static gchar *editor_command_path_parse(const FileData *fd, PathType type, const
                                        }
                                if (p) break;
                                }
-                       string_list_free(ext_list);
                        if (!p) return NULL;
                        }
                }
@@ -328,168 +696,169 @@ static gchar *editor_command_path_parse(const FileData *fd, PathType type, const
                        p = "";
                }
 
-       while (*p != '\0')
-               {
-               /* must escape \, ", `, and $ to avoid problems,
-                * we assume system shell supports bash-like escaping
-                */
-               if (strchr("\\\"`$", *p) != NULL)
-                       {
-                       string = g_string_append_c(string, '\\');
-                       }
-               string = g_string_append_c(string, *p);
-               p++;
-               }
+       g_assert(p);
+       string = g_string_append(string, p);
 
+       if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
        pathl = path_from_utf8(string->str);
        g_string_free(string, TRUE);
 
+       if (pathl && !pathl[0]) /* empty string case */
+               {
+               g_free(pathl);
+               pathl = NULL;
+               }
+       
+       DEBUG_2("editor_command_path_parse: return %s", pathl);
        return pathl;
 }
 
+static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
+{
+       const char *p;
+       
+       if (!single_quotes)
+               {
+               if (!double_quotes)
+                       g_string_append_c(str, '\'');
+               else
+                       g_string_append(str, "\"'");
+               }
 
-/*
- * 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".
- */
+       for (p = s; *p != '\0'; p++)
+               {
+               if (*p == '\'')
+                       g_string_append(str, "'\\''");
+               else
+                       g_string_append_c(str, *p);
+               }
+       
+       if (!single_quotes)
+               {
+               if (!double_quotes)
+                       g_string_append_c(str, '\'');
+               else
+                       g_string_append(str, "'\"");
+               }
+
+       return str;
+}
 
 
-gint editor_command_parse(const gchar *template, GList *list, gchar **output)
+EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
 {
-       gint flags = 0;
-       const gchar *p = template;
+       EditorFlags flags = 0;
+       const gchar *p;
        GString *result = NULL;
-       gchar *extensions = NULL;
+       gboolean escape = FALSE;
+       gboolean single_quotes = FALSE;
+       gboolean double_quotes = FALSE;
+
+       DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
 
        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)
                {
-               if (*p != '%')
+               if (escape)
+                       {
+                       escape = FALSE;
+                       if (output) result = g_string_append_c(result, *p);
+                       }
+               else if (*p == '\\')
+                       {
+                       if (!single_quotes) escape = TRUE;
+                       if (output) result = g_string_append_c(result, *p);
+                       }
+               else if (*p == '\'')
                        {
                        if (output) result = g_string_append_c(result, *p);
+                       if (!single_quotes && !double_quotes)
+                               single_quotes = TRUE;
+                       else if (single_quotes)
+                               single_quotes = FALSE;
                        }
-               else /* *p == '%' */
+               else if (*p == '"')
+                       {
+                       if (output) result = g_string_append_c(result, *p);
+                       if (!single_quotes && !double_quotes)
+                               double_quotes = TRUE;
+                       else if (double_quotes)
+                               double_quotes = FALSE;
+                       }
+               else if (*p == '%' && p[1])
                        {
-                       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)
                                                {
                                                flags |= EDITOR_ERROR_INCOMPATIBLE;
                                                goto err;
                                                }
-                                       if (output)
+                                       if (list)
                                                {
                                                /* use the first file from the list */
-                                               if (!list || !list->data)
+                                               if (!list->data)
                                                        {
                                                        flags |= EDITOR_ERROR_NO_FILE;
                                                        goto err;
                                                        }
                                                pathl = editor_command_path_parse((FileData *)list->data,
-                                                                                 (flags & EDITOR_DEST) ? PATH_DEST : PATH_FILE,
-                                                                                 extensions);
+                                                                                 consider_sidecars,
+                                                                                 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
+                                                                                 editor);
+                                               if (!output)
+                                                       {
+                                                       /* just testing, check also the rest of the list (like with F and U)
+                                                          any matching file is OK */
+                                                       GList *work = list->next;
+                                                       
+                                                       while (!pathl && work)
+                                                               {
+                                                               FileData *fd = work->data;
+                                                               pathl = editor_command_path_parse(fd,
+                                                                                                 consider_sidecars,
+                                                                                                 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
+                                                                                                 editor);
+                                                               work = work->next;
+                                                               }
+                                                       }
+                                                       
                                                if (!pathl)
                                                        {
                                                        flags |= EDITOR_ERROR_NO_FILE;
                                                        goto err;
                                                        }
-                                               result = g_string_append_c(result, '"');
-                                               result = g_string_append(result, pathl);
+                                               if (output)
+                                                       {
+                                                       result = append_quoted(result, pathl, single_quotes, double_quotes);
+                                                       }
                                                g_free(pathl);
-                                               result = g_string_append_c(result, '"');
                                                }
                                        break;
 
-                               case 'f':
+                               case 'F':
+                               case 'U':
                                        flags |= EDITOR_SINGLE_COMMAND;
                                        if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
                                                {
@@ -497,7 +866,7 @@ gint editor_command_parse(const gchar *template, GList *list, gchar **output)
                                                goto err;
                                                }
 
-                                       if (output)
+                                       if (list)
                                                {
                                                /* use whole list */
                                                GList *work = list;
@@ -506,16 +875,18 @@ gint editor_command_parse(const gchar *template, GList *list, gchar **output)
                                                while (work)
                                                        {
                                                        FileData *fd = work->data;
-                                                       pathl = editor_command_path_parse(fd, PATH_FILE, extensions);
-
+                                                       pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
                                                        if (pathl)
                                                                {
                                                                ok = TRUE;
-                                                               if (work != list) g_string_append_c(result, ' ');
-                                                               result = g_string_append_c(result, '"');
-                                                               result = g_string_append(result, pathl);
+
+                                                               if (output)
+                                                                       {
+                                                                       ok = TRUE;
+                                                                       if (work != list) g_string_append_c(result, ' ');
+                                                                       result = append_quoted(result, pathl, single_quotes, double_quotes);
+                                                                       }
                                                                g_free(pathl);
-                                                               result = g_string_append_c(result, '"');
                                                                }
                                                        work = work->next;
                                                        }
@@ -526,21 +897,60 @@ gint editor_command_parse(const gchar *template, GList *list, gchar **output)
                                                        }
                                                }
                                        break;
+                               case 'i':
+                                       if (editor->icon && *editor->icon)
+                                               {
+                                               if (output)
+                                                       {
+                                                       result = g_string_append(result, "--icon ");
+                                                       result = append_quoted(result, editor->icon, single_quotes, double_quotes);
+                                                       }
+                                               }
+                                       break;
+                               case 'c':
+                                       if (output)
+                                               {
+                                               result = append_quoted(result, editor->name, single_quotes, double_quotes);
+                                               }
+                                       break;
+                               case 'k':
+                                       if (output)
+                                               {
+                                               result = append_quoted(result, editor->file, single_quotes, double_quotes);
+                                               }
+                                       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;
+                       }
+               else
+                       {
+                       if (output) result = g_string_append_c(result, *p);
                        }
                p++;
                }
 
-       if (output) *output = g_string_free(result, FALSE);
+       if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
+
+       if (output)
+               {
+               *output = g_string_free(result, FALSE);
+               DEBUG_3("Editor cmd: %s", *output);
+               }
+
        return flags;
 
 
@@ -550,11 +960,11 @@ err:
                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)
+
+static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
 {
        EditorData *ed = data;
        g_spawn_close_pid(pid);
@@ -564,30 +974,56 @@ static void editor_child_exit_cb (GPid pid, gint status, gpointer data)
 }
 
 
-static gint editor_command_one(const gchar *template, GList *list, EditorData *ed)
+static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
 {
        gchar *command;
-       FileData *fd = list->data;
+       FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
        GPid pid;
        gint standard_output;
        gint standard_error;
        gboolean ok;
 
        ed->pid = -1;
-       ed->flags = editor_command_parse(template, list, &command);
+       ed->flags = editor->flags;
+       ed->flags |= editor_command_parse(editor, list, TRUE, &command);
+
+       ok = !EDITOR_ERRORS(ed->flags);
 
-       ok = !(ed->flags & EDITOR_ERROR_MASK);
+       if (ok)
+               {
+               ok = (options->shell.path && *options->shell.path);
+               if (!ok) log_printf("ERROR: empty shell command\n");
+                       
+               if (ok)
+                       {
+                       ok = (access(options->shell.path, X_OK) == 0);
+                       if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
+                       }
+
+               if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
+               }
 
        if (ok)
                {
                gchar *working_directory;
                gchar *args[4];
+               guint n = 0;
 
-               working_directory = remove_level_from_path(fd->path);
-               args[0] = COMMAND_SHELL;
-               args[1] = COMMAND_OPT;
-               args[2] = command;
-               args[3] = NULL;
+               working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
+               args[n++] = options->shell.path;
+               if (options->shell.options && *options->shell.options)
+                       args[n++] = options->shell.options;
+               args[n++] = command;
+               args[n] = NULL;
+
+               if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
+                       {
+                       g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
+                       }
+               else
+                       {
+                       g_unsetenv("GEEQIE_DESTINATION");
+                       }
 
                ok = g_spawn_async_with_pipes(working_directory, args, NULL,
                                      G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
@@ -615,7 +1051,7 @@ static gint editor_command_one(const gchar *template, GList *list, EditorData *e
                        {
                        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);
 
@@ -627,6 +1063,7 @@ static gint editor_command_one(const gchar *template, GList *list, EditorData *e
 
                        channel_output = g_io_channel_unix_new(standard_output);
                        g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
+                       g_io_channel_set_encoding(channel_output, NULL, NULL);
 
                        g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
                                            editor_verbose_io_cb, ed, NULL);
@@ -634,6 +1071,7 @@ static gint editor_command_one(const gchar *template, GList *list, EditorData *e
 
                        channel_error = g_io_channel_unix_new(standard_error);
                        g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
+                       g_io_channel_set_encoding(channel_error, NULL, NULL);
 
                        g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
                                            editor_verbose_io_cb, ed, NULL);
@@ -643,31 +1081,34 @@ static gint editor_command_one(const gchar *template, GList *list, EditorData *e
 
        g_free(command);
 
-       return ed->flags & EDITOR_ERROR_MASK;
+       return EDITOR_ERRORS(ed->flags);
 }
 
-static gint editor_command_next_start(EditorData *ed)
+static EditorFlags editor_command_next_start(EditorData *ed)
 {
        if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
 
-       if (ed->list && ed->count < ed->total)
+       if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
                {
                FileData *fd;
-               gint error;
+               EditorFlags error;
 
-               fd = ed->list->data;
+               fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
 
                if (ed->vd)
                        {
-                       editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running..."));
+                       if ((ed->flags & EDITOR_FOR_EACH) && fd)
+                               editor_verbose_window_progress(ed, fd->path);
+                       else
+                               editor_verbose_window_progress(ed, _("running..."));
                        }
                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) );
-                       if (ed->flags & EDITOR_FOR_EACH)
+                       if ((ed->flags & EDITOR_FOR_EACH) && fd)
                                {
                                editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
                                editor_verbose_window_fill(ed->vd, "\n", 1);
@@ -676,16 +1117,16 @@ static gint editor_command_next_start(EditorData *ed)
 
                if (!error)
                        return 0;
-               else
-                       /* command was not started, call the finish immediately */
-                       return editor_command_next_finish(ed, 0);
+               
+               /* command was not started, call the finish immediately */
+               return editor_command_next_finish(ed, 0);
                }
 
        /* everything is done */
        return editor_command_done(ed);
 }
 
-static gint editor_command_next_finish(EditorData *ed, gint status)
+static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
 {
        gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
 
@@ -699,7 +1140,10 @@ static gint editor_command_next_finish(EditorData *ed, gint status)
 
                ed->list = g_list_remove_link(ed->list, fd_element);
                if (ed->callback)
+                       {
                        cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
+                       if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
+                       }
                filelist_free(fd_element);
                }
        else
@@ -711,31 +1155,31 @@ static gint editor_command_next_finish(EditorData *ed, gint status)
                ed->list = NULL;
                }
 
-       if (cont == EDITOR_CB_SUSPEND)
-               return ed->flags & EDITOR_ERROR_MASK;
-       else if (cont == EDITOR_CB_SKIP)
-               return editor_command_done(ed);
-       else
-               return editor_command_next_start(ed);
+       switch (cont)
+               {
+               case EDITOR_CB_SUSPEND:
+                       return EDITOR_ERRORS(ed->flags);
+               case EDITOR_CB_SKIP:
+                       return editor_command_done(ed);
+               }
+       
+       return editor_command_next_start(ed);
 }
 
-static gint editor_command_done(EditorData *ed)
+static EditorFlags editor_command_done(EditorData *ed)
 {
-       gint flags;
+       EditorFlags flags;
 
        if (ed->vd)
                {
-               const gchar *text;
-
                if (ed->count == ed->total)
                        {
-                       text = _("done");
+                       editor_verbose_window_progress(ed, _("done"));
                        }
                else
                        {
-                       text = _("stopped by user");
+                       editor_verbose_window_progress(ed, _("stopped by user"));
                        }
-               editor_verbose_window_progress(ed, text);
                editor_verbose_window_enable_close(ed->vd);
                }
 
@@ -750,7 +1194,7 @@ static gint editor_command_done(EditorData *ed)
 
        ed->count = 0;
 
-       flags = ed->flags & EDITOR_ERROR_MASK;
+       flags = EDITOR_ERRORS(ed->flags);
 
        if (!ed->vd) editor_data_free(ed);
 
@@ -767,22 +1211,21 @@ void editor_skip(gpointer ed)
        editor_command_done(ed);
 }
 
-static gint editor_command_start(const gchar *template, const gchar *text, GList *list, EditorCallback cb, gpointer data)
+static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
 {
        EditorData *ed;
-       gint flags = editor_command_parse(template, NULL, NULL);
+       EditorFlags flags = editor->flags;
 
-       if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK;
+       if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
 
        ed = g_new0(EditorData, 1);
        ed->list = filelist_copy(list);
        ed->flags = flags;
-       ed->command_template = g_strdup(template);
-       ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list);
-       ed->count = 0;
-       ed->stopping = FALSE;
+       ed->editor = editor;
+       ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
        ed->callback = cb;
-       ed->data =  data;
+       ed->data = data;
+       ed->working_directory = g_strdup(working_directory);
 
        if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
                flags |= EDITOR_VERBOSE;
@@ -792,71 +1235,122 @@ static gint editor_command_start(const gchar *template, const gchar *text, GList
 
        editor_command_next_start(ed);
        /* errors from editor_command_next_start will be handled via callback */
-       return flags & EDITOR_ERROR_MASK;
+       return EDITOR_ERRORS(flags);
 }
 
-static gint is_valid_editor_command(gint n)
+gboolean is_valid_editor_command(const gchar *key)
 {
-       return (n >= 0 && n < GQ_EDITOR_SLOTS
-               && options->editor_command[n]
-               && strlen(options->editor_command[n]) > 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)
+EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
 {
-       gchar *command;
-       gint error;
+       EditorFlags error;
+       EditorDescription *editor;
+       if (!key) return EDITOR_ERROR_EMPTY;
+       
+       editor = g_hash_table_lookup(editors, key);
 
-       if (!list) return FALSE;
-       if (!is_valid_editor_command(n)) return FALSE;
+       if (!editor) return EDITOR_ERROR_EMPTY;
+       if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
 
-       command = g_locale_from_utf8(options->editor_command[n], -1, NULL, NULL, NULL);
-       error = editor_command_start(command, options->editor_name[n], list, cb, data);
-       g_free(command);
+       error = editor_command_parse(editor, list, TRUE, NULL);
+
+       if (EDITOR_ERRORS(error)) return error;
+
+       error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
 
-       if (n < GQ_EDITOR_GENERIC_SLOTS && (error & EDITOR_ERROR_SYNTAX))
+       if (EDITOR_ERRORS(error))
                {
-               gchar *text = g_strdup_printf(_("Syntax error in the editor template \"%s\":\n%s"),
-                                             options->editor_name[n], options->editor_command[n]);
+               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);
                }
 
-       return error;
+       return EDITOR_ERRORS(error);
 }
 
-gint start_editor_from_filelist(gint n, GList *list)
+EditorFlags 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, NULL);
 }
 
-gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data)
+EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
 {
        GList *list;
-       gint error;
+       EditorFlags error;
 
        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, NULL, cb, data);
        g_list_free(list);
        return error;
 }
 
-gint start_editor_from_file(gint n, FileData *fd)
+EditorFlags 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)
+EditorFlags start_editor(const gchar *key, const gchar *working_directory)
 {
-       if (!is_valid_editor_command(n)) return TRUE;
+       return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
+}
 
-       return (editor_command_parse(options->editor_command[n], NULL, NULL) & EDITOR_KEEP_FS);
+gboolean editor_window_flag_set(const gchar *key)
+{
+       EditorDescription *editor;
+       if (!key) return TRUE;
+       
+       editor = g_hash_table_lookup(editors, key);
+       if (!editor) return TRUE;
+
+       return !!(editor->flags & EDITOR_KEEP_FS);
 }
 
-const gchar *editor_get_error_str(gint flags)
+gboolean editor_is_filter(const gchar *key)
+{
+       EditorDescription *editor;
+       if (!key) return TRUE;
+       
+       editor = g_hash_table_lookup(editors, key);
+       if (!editor) return TRUE;
+
+       return !!(editor->flags & EDITOR_DEST);
+}
+
+gboolean editor_no_param(const gchar *key)
+{
+       EditorDescription *editor;
+       if (!key) return FALSE;
+       
+       editor = g_hash_table_lookup(editors, key);
+       if (!editor) return FALSE;
+
+       return !!(editor->flags & EDITOR_NO_PARAM);
+}
+
+gboolean editor_blocks_file(const gchar *key)
+{
+       EditorDescription *editor;
+       if (!key) return FALSE;
+       
+       editor = g_hash_table_lookup(editors, key);
+       if (!editor) return FALSE;
+
+       /* Decide if the image file should be blocked during editor execution
+          Editors like gimp can be used long time after the original file was
+          saved, for editing unrelated files.
+          %f vs. %F seems to be a good heuristic to detect this kind of editors.
+       */
+          
+       return !(editor->flags & EDITOR_SINGLE_COMMAND);
+}
+
+const gchar *editor_get_error_str(EditorFlags flags)
 {
        if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
        if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
@@ -867,3 +1361,13 @@ const gchar *editor_get_error_str(gint flags)
        if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
        return _("Unknown error.");
 }
+
+const gchar *editor_get_name(const gchar *key)
+{
+       EditorDescription *editor = g_hash_table_lookup(editors, key);
+
+       if (!editor) return NULL;
+
+       return editor->name;
+}
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */