/*
* Geeqie
* (C) 2006 John Ellis
- * Copyright (C) 2008 The Geeqie Team
+ * Copyright (C) 2008 - 2012 The Geeqie Team
*
* Author: John Ellis
*
#include "filedata.h"
#include "filefilter.h"
#include "misc.h"
+#include "pixbuf_util.h"
#include "ui_fileops.h"
#include "ui_spinner.h"
#include "ui_utildlg.h"
typedef struct _EditorData EditorData;
struct _EditorData {
- gint flags;
+ EditorFlags flags;
GPid pid;
- gchar *command_template;
GList *list;
gint count;
gint total;
EditorVerboseData *vd;
EditorCallback callback;
gpointer data;
+ const EditorDescription *editor;
+ gchar *working_directory; /* fallback if no files are given (editor_no_param) */
};
-static Editor editor_slot_defaults[GQ_EDITOR_SLOTS] = {
- { 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\"" },
- { N_("Symlink"), "ln -s %p %d"},
- { 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);
/*
*-----------------------------------------------------------------------------
*-----------------------------------------------------------------------------
*/
-void editor_set_name(gint n, gchar *name)
+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 (n < 0 || n >= GQ_EDITOR_SLOTS) return;
+ 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);
+}
- g_free(options->editor[n].name);
-
- options->editor[n].name = name ? utf8_validate_or_convert(name) : NULL;
+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;
}
-void editor_set_command(gint n, gchar *command)
+gboolean editor_read_desktop_file(const gchar *path)
{
- if (n < 0 || n >= GQ_EDITOR_SLOTS) return;
+ 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;
+ }
- g_free(options->editor[n].command);
- options->editor[n].command = command ? utf8_validate_or_convert(command) : NULL;
+ 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;
}
-void editor_reset_defaults(void)
+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);
- for (i = 0; i < GQ_EDITOR_SLOTS; i++)
+ g_free(xdg_data_dirs);
+
+ split_dirs = g_strsplit(all_dirs, ":", 0);
+
+ g_free(all_dirs);
+
+ for (i = 0; split_dirs[i]; i++);
+ for (--i; i >= 0; 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);
+ 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;
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);
}
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;
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;
}
if (p) break;
}
- string_list_free(ext_list);
if (!p) return NULL;
}
}
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;
-/*
- * 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".
- */
+ if (!single_quotes)
+ {
+ if (!double_quotes)
+ g_string_append_c(str, '\'');
+ else
+ g_string_append(str, "\"'");
+ }
+ for (p = s; *p != '\0'; p++)
+ {
+ if (*p == '\'')
+ g_string_append(str, "'\\''");
+ else
+ g_string_append_c(str, *p);
+ }
-gint editor_command_parse(const gchar *template, GList *list, gchar **output)
+ if (!single_quotes)
+ {
+ if (!double_quotes)
+ g_string_append_c(str, '\'');
+ else
+ g_string_append(str, "'\"");
+ }
+
+ return str;
+}
+
+
+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;
}
-
- /* 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 */
+ p = editor->exec;
+ /* skip leading 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 /* *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 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))
{
goto err;
}
- if (output)
+ if (list)
{
/* use whole list */
GList *work = list;
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;
}
}
}
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;
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);
}
-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 = !(ed->flags & EDITOR_ERROR_MASK);
+ ok = !EDITOR_ERRORS(ed->flags);
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);
gchar *args[4];
guint n = 0;
- working_directory = remove_level_from_path(fd->path);
+ 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 */
NULL, NULL,
ed->vd ? &standard_output : NULL,
ed->vd ? &standard_error : NULL,
NULL);
-
+
g_free(working_directory);
if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
{
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);
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);
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;
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);
}
ed->count = 0;
- flags = ed->flags & EDITOR_ERROR_MASK;
+ flags = EDITOR_ERRORS(ed->flags);
if (!ed->vd) editor_data_free(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;
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);
}
-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)
+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;
- if (!list) return FALSE;
- if (!is_valid_editor_command(n)) return FALSE;
+ editor = g_hash_table_lookup(editors, key);
- 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 (!editor) return EDITOR_ERROR_EMPTY;
+ if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
+
+ error = editor_command_parse(editor, list, TRUE, NULL);
- if (n < GQ_EDITOR_GENERIC_SLOTS && (error & EDITOR_ERROR_MASK))
+ if (EDITOR_ERRORS(error)) return error;
+
+ error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
+
+ if (EDITOR_ERRORS(error))
{
- 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);
}
- 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);
+}
+
+gboolean editor_window_flag_set(const gchar *key)
+{
+ EditorDescription *editor;
+ if (!key) return TRUE;
- return (editor_command_parse(options->editor[n].command, NULL, NULL) & EDITOR_KEEP_FS);
+ editor = g_hash_table_lookup(editors, key);
+ if (!editor) return TRUE;
+
+ return !!(editor->flags & EDITOR_KEEP_FS);
}
-gint editor_is_filter(gint n)
+gboolean 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)
+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.");
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 (!editor) return NULL;
- if (options->editor[n].name && strlen(options->editor[n].name) > 0)
- return options->editor[n].name;
-
- return _("(unknown)");
+ return editor->name;
}
/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */