Trim trailing white spaces.
[geeqie.git] / src / editors.c
index 0d9bc18..270c5d3 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Geeqie
  * (C) 2006 John Ellis
- * Copyright (C) 2008 - 2009 The Geeqie Team
+ * Copyright (C) 2008 - 2012 The Geeqie Team
  *
  * Author: John Ellis
  *
@@ -17,6 +17,7 @@
 #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"
@@ -52,6 +53,7 @@ struct _EditorData {
        EditorCallback callback;
        gpointer data;
        const EditorDescription *editor;
+       gchar *working_directory; /* fallback if no files are given (editor_no_param) */
 };
 
 
@@ -68,7 +70,7 @@ static EditorFlags editor_command_done(EditorData *ed);
 
 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
@@ -107,7 +109,7 @@ static GList *editor_mime_types_to_extensions(gchar **mime_types)
                {"image/png",           ".png"},
                {"image/svg",           ".svg"},
                {"image/svg+xml",       ".svg"},
-               {"image/svg+xml-compressed",    ".svg"},        
+               {"image/svg+xml-compressed",    ".svg"},
                {"image/tiff",          ".tiff;.tif"},
                {"image/x-bmp",         ".bmp"},
                {"image/x-canon-crw",   ".crw"},
@@ -121,7 +123,7 @@ static GList *editor_mime_types_to_extensions(gchar **mime_types)
                {"image/x-pcx",         ".pcx"},
                {"image/xpm",           ".xpm"},
                {"image/x-png",         ".png"},
-               {"image/x-portable-anymap",     ".pam"},        
+               {"image/x-portable-anymap",     ".pam"},
                {"image/x-portable-bitmap",     ".pbm"},
                {"image/x-portable-graymap",    ".pgm"},
                {"image/x-portable-pixmap",     ".ppm"},
@@ -133,12 +135,13 @@ static GList *editor_mime_types_to_extensions(gchar **mime_types)
                {"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 (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]));
@@ -146,7 +149,7 @@ static GList *editor_mime_types_to_extensions(gchar **mime_types)
        return list;
 }
 
-static gboolean editor_read_desktop_file(const gchar *path)
+gboolean editor_read_desktop_file(const gchar *path)
 {
        GKeyFile *key_file;
        EditorDescription *editor;
@@ -195,14 +198,14 @@ static gboolean editor_read_desktop_file(const gchar *path)
                {
                gboolean found = FALSE;
                gint i;
-               for (i = 0; categories[i]; 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) 
+                       if (strcmp(categories[i], "X-Geeqie") == 0)
                                {
                                found = TRUE;
                                category_geeqie = TRUE;
@@ -222,7 +225,7 @@ static gboolean editor_read_desktop_file(const gchar *path)
                {
                gboolean found = FALSE;
                gint i;
-               for (i = 0; only_show_in[i]; i++) 
+               for (i = 0; only_show_in[i]; i++)
                        if (strcmp(only_show_in[i], "X-Geeqie") == 0)
                                {
                                found = TRUE;
@@ -237,7 +240,7 @@ static gboolean editor_read_desktop_file(const gchar *path)
                {
                gboolean found = FALSE;
                gint i;
-               for (i = 0; not_show_in[i]; i++) 
+               for (i = 0; not_show_in[i]; i++)
                        if (strcmp(not_show_in[i], "X-Geeqie") == 0)
                                {
                                found = TRUE;
@@ -257,7 +260,7 @@ static gboolean editor_read_desktop_file(const gchar *path)
                g_free(try_exec);
                }
 
-       if (editor->ignored) 
+       if (editor->ignored)
                {
                /* ignored editors will be deleted, no need to parse the rest */
                g_key_file_free(key_file);
@@ -272,7 +275,7 @@ static gboolean editor_read_desktop_file(const gchar *path)
                {
                gchar *ext = strrchr(editor->icon, '.');
                
-               if (ext && strlen(ext) == 4 && 
+               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"),
@@ -282,6 +285,11 @@ static gboolean editor_read_desktop_file(const gchar *path)
                        *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);
        
@@ -302,7 +310,7 @@ static gboolean editor_read_desktop_file(const gchar *path)
                        {
                        editor->ext_list = editor_mime_types_to_extensions(mime_types);
                        g_strfreev(mime_types);
-                       if (!editor->ext_list) editor->hidden = TRUE; 
+                       if (!editor->ext_list) editor->hidden = TRUE;
                        }
                }
                
@@ -312,7 +320,7 @@ static gboolean editor_read_desktop_file(const gchar *path)
        if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
        if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
        
-       editor->flags |= editor_command_parse(editor, NULL, NULL);
+       editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
 
        if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
 
@@ -321,14 +329,14 @@ static gboolean editor_read_desktop_file(const gchar *path)
        if (editor->ignored) return TRUE;
        
        gtk_list_store_append(desktop_file_list, &iter);
-       gtk_list_store_set(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,
+                          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;    
+       return TRUE;
 }
 
 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
@@ -337,7 +345,31 @@ static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpoi
        return editor->hidden || editor->ignored;
 }
 
-static void editor_read_desktop_dir(const gchar *path)
+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;
@@ -349,7 +381,7 @@ static void editor_read_desktop_dir(const gchar *path)
        if (!dp)
                {
                /* dir not found */
-               return;
+               return list;
                }
        while ((dir = readdir(dp)) != NULL)
                {
@@ -359,36 +391,23 @@ static void editor_read_desktop_dir(const gchar *path)
                        {
                        gchar *name = path_to_utf8(namel);
                        gchar *dpath = g_build_filename(path, name, NULL);
-                       editor_read_desktop_file(dpath);
-                       g_free(dpath);
+                       list = g_list_prepend(list, dpath);
                        g_free(name);
-                       }       
+                       }
                }
        closedir(dp);
+       return list;
 }
 
-void editor_load_descriptions(void)
+GList *editor_get_desktop_files(void)
 {
        gchar *path;
        gchar *xdg_data_dirs;
        gchar *all_dirs;
        gchar **split_dirs;
        gint i;
+       GList *list = NULL;
        
-       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_BOOLEAN, 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);
-
        xdg_data_dirs = getenv("XDG_DATA_DIRS");
        if (xdg_data_dirs && xdg_data_dirs[0])
                xdg_data_dirs = path_to_utf8(xdg_data_dirs);
@@ -403,16 +422,16 @@ void editor_load_descriptions(void)
        
        g_free(all_dirs);
 
-       for (i = 0; split_dirs[i]; i++)
+       for (i = 0; split_dirs[i]; i++);
+       for (--i; i >= 0; i--)
                {
                path = g_build_filename(split_dirs[i], "applications", NULL);
-               editor_read_desktop_dir(path);
+               list = editor_add_desktop_dir(list, path);
                g_free(path);
                }
                
        g_strfreev(split_dirs);
-       
-       g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
+       return list;
 }
 
 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
@@ -420,9 +439,9 @@ 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 */ 
+       /* 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_MOVE) == 0 ||
            strcmp(editor->key, CMD_RENAME) == 0 ||
            strcmp(editor->key, CMD_DELETE) == 0 ||
            strcmp(editor->key, CMD_FOLDER) == 0) return;
@@ -445,6 +464,9 @@ static gint editor_sort(gconstpointer a, gconstpointer b)
 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);
 
@@ -464,6 +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->working_directory);
        g_free(ed);
 }
 
@@ -617,12 +640,14 @@ typedef enum {
 } PathType;
 
 
-static gchar *editor_command_path_parse(const FileData *fd, PathType type, const EditorDescription *editor)
+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 || type == PATH_FILE_URL)
@@ -646,7 +671,7 @@ static gchar *editor_command_path_parse(const FileData *fd, PathType type, const
                                        break;
                                        }
 
-                               work2 = fd->sidecar_files;
+                               work2 = consider_sidecars ? fd->sidecar_files : NULL;
                                while (work2)
                                        {
                                        FileData *sfd = work2->data;
@@ -683,7 +708,8 @@ static gchar *editor_command_path_parse(const FileData *fd, PathType type, const
                g_free(pathl);
                pathl = NULL;
                }
-
+       
+       DEBUG_2("editor_command_path_parse: return %s", pathl);
        return pathl;
 }
 
@@ -719,7 +745,7 @@ static GString *append_quoted(GString *str, const char *s, gboolean single_quote
 }
 
 
-EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gchar **output)
+EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
 {
        EditorFlags flags = 0;
        const gchar *p;
@@ -728,6 +754,8 @@ EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, g
        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("");
 
@@ -796,8 +824,26 @@ EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, g
                                                        goto err;
                                                        }
                                                pathl = editor_command_path_parse((FileData *)list->data,
+                                                                                 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;
@@ -829,7 +875,7 @@ EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, g
                                                while (work)
                                                        {
                                                        FileData *fd = work->data;
-                                                       pathl = editor_command_path_parse(fd, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
+                                                       pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
                                                        if (pathl)
                                                                {
                                                                ok = TRUE;
@@ -939,7 +985,7 @@ static EditorFlags editor_command_one(const EditorDescription *editor, GList *li
 
        ed->pid = -1;
        ed->flags = editor->flags;
-       ed->flags |= editor_command_parse(editor, list, &command);
+       ed->flags |= editor_command_parse(editor, list, TRUE, &command);
 
        ok = !EDITOR_ERRORS(ed->flags);
 
@@ -963,7 +1009,7 @@ static EditorFlags editor_command_one(const EditorDescription *editor, GList *li
                gchar *args[4];
                guint n = 0;
 
-               working_directory = fd ? remove_level_from_path(fd->path) : 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;
@@ -1165,7 +1211,7 @@ void editor_skip(gpointer ed)
        editor_command_done(ed);
 }
 
-static EditorFlags editor_command_start(const EditorDescription *editor, 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;
        EditorFlags flags = editor->flags;
@@ -1178,7 +1224,8 @@ static EditorFlags editor_command_start(const EditorDescription *editor, const g
        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;
@@ -1197,18 +1244,22 @@ gboolean is_valid_editor_command(const gchar *key)
        return g_hash_table_lookup(editors, key) != NULL;
 }
 
-EditorFlags start_editor_from_filelist_full(const gchar *key, 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)
 {
        EditorFlags error;
        EditorDescription *editor;
-       if (!key) return FALSE;
+       if (!key) return EDITOR_ERROR_EMPTY;
        
        editor = g_hash_table_lookup(editors, key);
 
-       if (!editor) return FALSE;
-       if (!list && !(editor->flags & EDITOR_NO_PARAM)) return FALSE;
+       if (!editor) return EDITOR_ERROR_EMPTY;
+       if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
 
-       error = editor_command_start(editor, editor->name, list, cb, data);
+       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 (EDITOR_ERRORS(error))
                {
@@ -1218,12 +1269,12 @@ EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, Edito
                g_free(text);
                }
 
-       return error;
+       return EDITOR_ERRORS(error);
 }
 
 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
 {
-       return start_editor_from_filelist_full(key, list,  NULL, NULL);
+       return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
 }
 
 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
@@ -1234,7 +1285,7 @@ EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCa
        if (!fd) return FALSE;
 
        list = g_list_append(NULL, fd);
-       error = start_editor_from_filelist_full(key, list, cb, data);
+       error = start_editor_from_filelist_full(key, list, NULL, cb, data);
        g_list_free(list);
        return error;
 }
@@ -1244,9 +1295,9 @@ EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
        return start_editor_from_file_full(key, fd, NULL, NULL);
 }
 
-EditorFlags start_editor(const gchar *key)
+EditorFlags start_editor(const gchar *key, const gchar *working_directory)
 {
-       return start_editor_from_filelist_full(key, NULL, NULL, NULL);
+       return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
 }
 
 gboolean editor_window_flag_set(const gchar *key)
@@ -1282,6 +1333,23 @@ gboolean editor_no_param(const gchar *key)
        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.");