Plugins sort order
[geeqie.git] / src / editors.c
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "editors.h"
24
25 #include "filedata.h"
26 #include "filefilter.h"
27 #include "misc.h"
28 #include "pixbuf_util.h"
29 #include "ui_fileops.h"
30 #include "ui_spinner.h"
31 #include "ui_utildlg.h"
32 #include "utilops.h"
33
34 #include <errno.h>
35
36
37 #define EDITOR_WINDOW_WIDTH 500
38 #define EDITOR_WINDOW_HEIGHT 300
39
40
41
42 typedef struct _EditorVerboseData EditorVerboseData;
43 struct _EditorVerboseData {
44         GenericDialog *gd;
45         GtkWidget *button_close;
46         GtkWidget *button_stop;
47         GtkWidget *text;
48         GtkWidget *progress;
49         GtkWidget *spinner;
50 };
51
52 typedef struct _EditorData EditorData;
53 struct _EditorData {
54         EditorFlags flags;
55         GPid pid;
56         GList *list;
57         gint count;
58         gint total;
59         gboolean stopping;
60         EditorVerboseData *vd;
61         EditorCallback callback;
62         gpointer data;
63         const EditorDescription *editor;
64         gchar *working_directory; /* fallback if no files are given (editor_no_param) */
65 };
66
67
68 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
69 static EditorFlags editor_command_next_start(EditorData *ed);
70 static EditorFlags editor_command_next_finish(EditorData *ed, gint status);
71 static EditorFlags editor_command_done(EditorData *ed);
72
73 /*
74  *-----------------------------------------------------------------------------
75  * external editor routines
76  *-----------------------------------------------------------------------------
77  */
78
79 GHashTable *editors = NULL;
80 GtkListStore *desktop_file_list;
81 gboolean editors_finished = FALSE;
82
83 #ifdef G_KEY_FILE_DESKTOP_GROUP
84 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
85 #else
86 #define DESKTOP_GROUP "Desktop Entry"
87 #endif
88
89 void editor_description_free(EditorDescription *editor)
90 {
91         if (!editor) return;
92
93         g_free(editor->key);
94         g_free(editor->name);
95         g_free(editor->icon);
96         g_free(editor->exec);
97         g_free(editor->menu_path);
98         g_free(editor->hotkey);
99         g_free(editor->comment);
100         string_list_free(editor->ext_list);
101         g_free(editor->file);
102         g_free(editor);
103 }
104
105 static GList *editor_mime_types_to_extensions(gchar **mime_types)
106 {
107         /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
108
109         static const gchar *conv_table[][2] = {
110                 {"application/x-ufraw", ".ufraw"},
111                 {"image/*",             "*"},
112                 {"image/bmp",           ".bmp"},
113                 {"image/gif",           ".gif"},
114                 {"image/jpeg",          ".jpeg;.jpg;.mpo"},
115                 {"image/jpg",           ".jpg;.jpeg"},
116                 {"image/pcx",           ".pcx"},
117                 {"image/png",           ".png"},
118                 {"image/svg",           ".svg"},
119                 {"image/svg+xml",       ".svg"},
120                 {"image/svg+xml-compressed",    ".svg"},
121                 {"image/tiff",          ".tiff;.tif;.mef"},
122                 {"image/vnd-ms.dds",    ".dds"},
123                 {"image/x-adobe-dng",   ".dng"},
124                 {"image/x-bmp",         ".bmp"},
125                 {"image/x-canon-crw",   ".crw"},
126                 {"image/x-canon-cr2",   ".cr2"},
127                 {"image/x-canon-cr3",   ".cr3"},
128                 {"image/x-cr2",         ".cr2"},
129                 {"image/x-dcraw",       "%raw;.mos"},
130                 {"image/x-epson-erf",   "%erf"},
131                 {"image/x-ico",         ".ico"},
132                 {"image/x-kodak-kdc",   ".kdc"},
133                 {"image/x-mrw",         ".mrw"},
134                 {"image/x-minolta-mrw", ".mrw"},
135                 {"image/x-MS-bmp",      ".bmp"},
136                 {"image/x-nef",         ".nef"},
137                 {"image/x-nikon-nef",   ".nef"},
138                 {"image/x-panasonic-raw",       ".raw"},
139                 {"image/x-panasonic-rw2",       ".rw2"},
140                 {"image/x-pentax-pef",  ".pef"},
141                 {"image/x-orf",         ".orf"},
142                 {"image/x-olympus-orf", ".orf"},
143                 {"image/x-pcx",         ".pcx"},
144                 {"image/xpm",           ".xpm"},
145                 {"image/x-png",         ".png"},
146                 {"image/x-portable-anymap",     ".pam"},
147                 {"image/x-portable-bitmap",     ".pbm"},
148                 {"image/x-portable-graymap",    ".pgm"},
149                 {"image/x-portable-pixmap",     ".ppm"},
150                 {"image/x-psd",         ".psd"},
151                 {"image/x-raf",         ".raf"},
152                 {"image/x-fuji-raf",    ".raf"},
153                 {"image/x-sgi",         ".sgi"},
154                 {"image/x-sony-arw",    ".arw"},
155                 {"image/x-sony-sr2",    ".sr2"},
156                 {"image/x-sony-srf",    ".srf"},
157                 {"image/x-tga",         ".tga"},
158                 {"image/x-xbitmap",     ".xbm"},
159                 {"image/x-xcf",         ".xcf"},
160                 {"image/x-xpixmap",     ".xpm"},
161                 {"image/x-x3f",         ".x3f"},
162                 {"application/x-navi-animation",                ".ani"},
163                 {"application/x-ptoptimizer-script",    ".pto"},
164                 {NULL, NULL}};
165
166         gint i, j;
167         GList *list = NULL;
168
169         for (i = 0; mime_types[i]; i++)
170                 for (j = 0; conv_table[j][0]; j++)
171                         if (strcmp(mime_types[i], conv_table[j][0]) == 0)
172                                 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
173
174         return list;
175 }
176
177 gboolean editor_read_desktop_file(const gchar *path)
178 {
179         GKeyFile *key_file;
180         EditorDescription *editor;
181         gchar *extensions;
182         gchar *type;
183         const gchar *key = filename_from_path(path);
184         gchar **categories, **only_show_in, **not_show_in;
185         gchar *try_exec;
186         GtkTreeIter iter;
187         gboolean category_geeqie = FALSE;
188         GList *work;
189         gboolean disabled;
190
191         if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
192
193         key_file = g_key_file_new();
194         if (!g_key_file_load_from_file(key_file, path, 0, NULL))
195                 {
196                 g_key_file_free(key_file);
197                 return FALSE;
198                 }
199
200         type = g_key_file_get_string(key_file, DESKTOP_GROUP, "Type", NULL);
201         if (!type || strcmp(type, "Application") != 0)
202                 {
203                 /* We only consider desktop entries of Application type */
204                 g_key_file_free(key_file);
205                 g_free(type);
206                 return FALSE;
207                 }
208         g_free(type);
209
210         editor = g_new0(EditorDescription, 1);
211
212         editor->key = g_strdup(key);
213         editor->file = g_strdup(path);
214
215         g_hash_table_insert(editors, editor->key, editor);
216
217         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)
218             || g_key_file_get_boolean(key_file, DESKTOP_GROUP, "NoDisplay", NULL))
219                 {
220                 editor->hidden = TRUE;
221                 }
222
223         categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
224         if (categories)
225                 {
226                 gboolean found = FALSE;
227                 gint i;
228                 for (i = 0; categories[i]; i++)
229                         {
230                         /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
231                         if (strcmp(categories[i], "Graphics") == 0)
232                                 {
233                                 found = TRUE;
234                                 }
235                         if (strcmp(categories[i], "X-Geeqie") == 0)
236                                 {
237                                 found = TRUE;
238                                 category_geeqie = TRUE;
239                                 break;
240                                 }
241                         }
242                 if (!found) editor->ignored = TRUE;
243                 g_strfreev(categories);
244                 }
245         else
246                 {
247                 editor->ignored = TRUE;
248                 }
249
250         only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
251         if (only_show_in)
252                 {
253                 gboolean found = FALSE;
254                 gint i;
255                 for (i = 0; only_show_in[i]; i++)
256                         if (strcmp(only_show_in[i], "X-Geeqie") == 0)
257                                 {
258                                 found = TRUE;
259                                 break;
260                                 }
261                 if (!found) editor->ignored = TRUE;
262                 g_strfreev(only_show_in);
263                 }
264
265         not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
266         if (not_show_in)
267                 {
268                 gboolean found = FALSE;
269                 gint i;
270                 for (i = 0; not_show_in[i]; i++)
271                         if (strcmp(not_show_in[i], "X-Geeqie") == 0)
272                                 {
273                                 found = TRUE;
274                                 break;
275                                 }
276                 if (found) editor->ignored = TRUE;
277                 g_strfreev(not_show_in);
278                 }
279
280
281         try_exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "TryExec", NULL);
282         if (try_exec && !editor->hidden && !editor->ignored)
283                 {
284                 gchar *try_exec_res = g_find_program_in_path(try_exec);
285                 if (!try_exec_res) editor->hidden = TRUE;
286                 g_free(try_exec_res);
287                 g_free(try_exec);
288                 }
289
290         if (editor->ignored)
291                 {
292                 /* ignored editors will be deleted, no need to parse the rest */
293                 g_key_file_free(key_file);
294                 return TRUE;
295                 }
296
297         editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
298         editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
299
300         /* Icon key can be either a full path (absolute with file name extension) or an icon name (without extension) */
301         if (editor->icon && !g_path_is_absolute(editor->icon))
302                 {
303                 gchar *ext = strrchr(editor->icon, '.');
304
305                 if (ext && strlen(ext) == 4 &&
306                     (!strcmp(ext, ".png") || !strcmp(ext, ".xpm") || !strcmp(ext, ".svg")))
307                         {
308                         log_printf(_("Desktop file '%s' should not include extension in Icon key: '%s'\n"),
309                                    editor->file, editor->icon);
310
311                         // drop extension
312                         *ext = '\0';
313                         }
314                 }
315         if (editor->icon && !register_theme_icon_as_stock(editor->key, editor->icon))
316                 {
317                 g_free(editor->icon);
318                 editor->icon = NULL;
319                 }
320
321         editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
322
323         editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
324         if (!editor->menu_path) editor->menu_path = g_strdup("PluginsMenu");
325
326         editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
327
328         editor->comment = g_key_file_get_string(key_file, DESKTOP_GROUP, "Comment", NULL);
329
330         extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
331         if (extensions)
332                 editor->ext_list = filter_to_list(extensions);
333         else
334                 {
335                 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
336                 if (mime_types)
337                         {
338                         editor->ext_list = editor_mime_types_to_extensions(mime_types);
339                         g_strfreev(mime_types);
340                         if (!editor->ext_list) editor->hidden = TRUE;
341                         }
342                 }
343
344         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
345         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
346         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
347         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
348         if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
349
350         editor->flags |= editor_command_parse(editor, NULL, FALSE, NULL);
351
352         if ((editor->flags & EDITOR_NO_PARAM) && !category_geeqie) editor->hidden = TRUE;
353
354         g_key_file_free(key_file);
355
356         if (editor->ignored) return TRUE;
357
358         work = options->disabled_plugins;
359
360         disabled = FALSE;
361         while (work)
362                 {
363                 if (g_strcmp0(path, work->data) == 0)
364                         {
365                         disabled = TRUE;
366                         break;
367                         }
368                 work = work->next;
369                 }
370
371         editor->disabled = disabled;
372
373         gtk_list_store_append(desktop_file_list, &iter);
374         gtk_list_store_set(desktop_file_list, &iter,
375                            DESKTOP_FILE_COLUMN_KEY, key,
376                            DESKTOP_FILE_COLUMN_DISABLED, editor->disabled,
377                            DESKTOP_FILE_COLUMN_NAME, editor->name,
378                            DESKTOP_FILE_COLUMN_HIDDEN, editor->hidden ? _("yes") : _("no"),
379                            DESKTOP_FILE_COLUMN_WRITABLE, access_file(path, W_OK),
380                            DESKTOP_FILE_COLUMN_PATH, path, -1);
381
382         return TRUE;
383 }
384
385 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
386 {
387         EditorDescription *editor = value;
388         return editor->hidden || editor->ignored;
389 }
390
391 void editor_table_finish(void)
392 {
393         g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
394         editors_finished = TRUE;
395 }
396
397 void editor_table_clear(void)
398 {
399         if (desktop_file_list)
400                 {
401                 gtk_list_store_clear(desktop_file_list);
402                 }
403         else
404                 {
405                 desktop_file_list = gtk_list_store_new(DESKTOP_FILE_COLUMN_COUNT, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING);
406                 }
407         if (editors)
408                 {
409                 g_hash_table_destroy(editors);
410                 }
411         editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
412         editors_finished = FALSE;
413 }
414
415 static GList *editor_add_desktop_dir(GList *list, const gchar *path)
416 {
417         DIR *dp;
418         struct dirent *dir;
419         gchar *pathl;
420
421         pathl = path_from_utf8(path);
422         dp = opendir(pathl);
423         g_free(pathl);
424         if (!dp)
425                 {
426                 /* dir not found */
427                 return list;
428                 }
429         while ((dir = readdir(dp)) != NULL)
430                 {
431                 gchar *namel = dir->d_name;
432
433                 if (g_str_has_suffix(namel, ".desktop"))
434                         {
435                         gchar *name = path_to_utf8(namel);
436                         gchar *dpath = g_build_filename(path, name, NULL);
437                         list = g_list_prepend(list, dpath);
438                         g_free(name);
439                         }
440                 }
441         closedir(dp);
442         return list;
443 }
444
445 GList *editor_get_desktop_files(void)
446 {
447         gchar *path;
448         gchar *xdg_data_dirs;
449         gchar *all_dirs;
450         gchar **split_dirs;
451         gint i;
452         GList *list = NULL;
453
454         xdg_data_dirs = getenv("XDG_DATA_DIRS");
455         if (xdg_data_dirs && xdg_data_dirs[0])
456                 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
457         else
458                 xdg_data_dirs = g_strdup("/usr/share");
459
460         all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_home_get(), ":", xdg_data_dirs, NULL);
461
462         g_free(xdg_data_dirs);
463
464         split_dirs = g_strsplit(all_dirs, ":", 0);
465
466         g_free(all_dirs);
467
468         for (i = 0; split_dirs[i]; i++);
469         for (--i; i >= 0; i--)
470                 {
471                 path = g_build_filename(split_dirs[i], "applications", NULL);
472                 list = editor_add_desktop_dir(list, path);
473                 g_free(path);
474                 }
475
476         g_strfreev(split_dirs);
477         return list;
478 }
479
480 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
481 {
482         GList **listp = data;
483         EditorDescription *editor = value;
484
485         /* do not show the special commands in any list, they are called explicitly */
486         if (strcmp(editor->key, CMD_COPY) == 0 ||
487             strcmp(editor->key, CMD_MOVE) == 0 ||
488             strcmp(editor->key, CMD_RENAME) == 0 ||
489             strcmp(editor->key, CMD_DELETE) == 0 ||
490             strcmp(editor->key, CMD_FOLDER) == 0) return;
491
492         if (editor->disabled)
493                 {
494                 return;
495                 }
496
497         *listp = g_list_prepend(*listp, editor);
498 }
499
500 static gint editor_sort(gconstpointer a, gconstpointer b)
501 {
502         const EditorDescription *ea = a;
503         const EditorDescription *eb = b;
504         gchar *caseless_name_ea;
505         gchar *caseless_name_eb;
506         gchar *collate_key_ea;
507         gchar *collate_key_eb;
508         gint ret;
509
510         ret = strcmp(ea->menu_path, eb->menu_path);
511         if (ret != 0) return ret;
512
513         caseless_name_ea = g_utf8_casefold(ea->name, -1);
514         caseless_name_eb = g_utf8_casefold(eb->name, -1);
515         collate_key_ea = g_utf8_collate_key_for_filename(caseless_name_ea, -1);
516         collate_key_eb = g_utf8_collate_key_for_filename(caseless_name_eb, -1);
517         ret = g_strcmp0(collate_key_ea, collate_key_eb);
518
519         g_free(collate_key_ea);
520         g_free(collate_key_eb);
521         g_free(caseless_name_ea);
522         g_free(caseless_name_eb);
523
524         return ret;
525 }
526
527 GList *editor_list_get(void)
528 {
529         GList *editors_list = NULL;
530
531         if (!editors_finished) return NULL;
532
533         g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
534         editors_list = g_list_sort(editors_list, editor_sort);
535
536         return editors_list;
537 }
538
539 /* ------------------------------ */
540
541
542 static void editor_verbose_data_free(EditorData *ed)
543 {
544         if (!ed->vd) return;
545         g_free(ed->vd);
546         ed->vd = NULL;
547 }
548
549 static void editor_data_free(EditorData *ed)
550 {
551         editor_verbose_data_free(ed);
552         g_free(ed->working_directory);
553         g_free(ed);
554 }
555
556 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
557 {
558         EditorData *ed = data;
559
560         generic_dialog_close(gd);
561         editor_verbose_data_free(ed);
562         if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
563 }
564
565 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
566 {
567         EditorData *ed = data;
568         ed->stopping = TRUE;
569         ed->count = 0;
570         editor_verbose_window_progress(ed, _("stopping..."));
571 }
572
573 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
574 {
575         vd->gd->cancel_cb = editor_verbose_window_close;
576
577         spinner_set_interval(vd->spinner, -1);
578         gtk_widget_set_sensitive(vd->button_stop, FALSE);
579         gtk_widget_set_sensitive(vd->button_close, TRUE);
580 }
581
582 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
583 {
584         EditorVerboseData *vd;
585         GtkWidget *scrolled;
586         GtkWidget *hbox;
587         gchar *buf;
588
589         vd = g_new0(EditorVerboseData, 1);
590
591         vd->gd = file_util_gen_dlg(_("Edit command results"), "editor_results",
592                                    NULL, FALSE,
593                                    NULL, ed);
594         buf = g_strdup_printf(_("Output of %s"), text);
595         generic_dialog_add_message(vd->gd, NULL, buf, NULL, FALSE);
596         g_free(buf);
597         vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
598                                                    editor_verbose_window_stop, FALSE);
599         gtk_widget_set_sensitive(vd->button_stop, FALSE);
600         vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
601                                                     editor_verbose_window_close, TRUE);
602         gtk_widget_set_sensitive(vd->button_close, FALSE);
603
604         scrolled = gtk_scrolled_window_new(NULL, NULL);
605         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
606         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
607                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
608         gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
609         gtk_widget_show(scrolled);
610
611         vd->text = gtk_text_view_new();
612         gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
613         gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
614         gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
615         gtk_widget_show(vd->text);
616
617         hbox = gtk_hbox_new(FALSE, 0);
618         gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
619         gtk_widget_show(hbox);
620
621         vd->progress = gtk_progress_bar_new();
622         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
623         gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
624 #if GTK_CHECK_VERSION(3,0,0)
625         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), "");
626         gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(vd->progress), TRUE);
627 #endif
628         gtk_widget_show(vd->progress);
629
630         vd->spinner = spinner_new(NULL, SPINNER_SPEED);
631         gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
632         gtk_widget_show(vd->spinner);
633
634         gtk_widget_show(vd->gd->dialog);
635
636         ed->vd = vd;
637         return vd;
638 }
639
640 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
641 {
642         GtkTextBuffer *buffer;
643         GtkTextIter iter;
644
645         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
646         gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
647         gtk_text_buffer_insert(buffer, &iter, text, len);
648 }
649
650 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
651 {
652         if (!ed->vd) return;
653
654         if (ed->total)
655                 {
656                 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (gdouble)ed->count / ed->total);
657                 }
658
659         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
660 }
661
662 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
663 {
664         EditorData *ed = data;
665         gchar buf[512];
666         gsize count;
667
668         if (condition & G_IO_IN)
669                 {
670                 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
671                         {
672                         if (!g_utf8_validate(buf, count, NULL))
673                                 {
674                                 gchar *utf8;
675
676                                 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
677                                 if (utf8)
678                                         {
679                                         editor_verbose_window_fill(ed->vd, utf8, -1);
680                                         g_free(utf8);
681                                         }
682                                 else
683                                         {
684                                         editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1);
685                                         }
686                                 }
687                         else
688                                 {
689                                 editor_verbose_window_fill(ed->vd, buf, count);
690                                 }
691                         }
692                 }
693
694         if (condition & (G_IO_ERR | G_IO_HUP))
695                 {
696                 g_io_channel_shutdown(source, TRUE, NULL);
697                 return FALSE;
698                 }
699
700         return TRUE;
701 }
702
703 typedef enum {
704         PATH_FILE,
705         PATH_FILE_URL,
706         PATH_DEST
707 } PathType;
708
709
710 static gchar *editor_command_path_parse(const FileData *fd, gboolean consider_sidecars, PathType type, const EditorDescription *editor)
711 {
712         GString *string;
713         gchar *pathl;
714         const gchar *p = NULL;
715
716         DEBUG_2("editor_command_path_parse: %s %d %d %s", fd->path, consider_sidecars, type, editor->key);
717
718         string = g_string_new("");
719
720         if (type == PATH_FILE || type == PATH_FILE_URL)
721                 {
722                 GList *work = editor->ext_list;
723
724                 if (!work)
725                         p = fd->path;
726                 else
727                         {
728                         while (work)
729                                 {
730                                 GList *work2;
731                                 gchar *ext = work->data;
732                                 work = work->next;
733
734                                 if (strcmp(ext, "*") == 0 ||
735                                     g_ascii_strcasecmp(ext, fd->extension) == 0)
736                                         {
737                                         p = fd->path;
738                                         break;
739                                         }
740
741                                 work2 = consider_sidecars ? fd->sidecar_files : NULL;
742                                 while (work2)
743                                         {
744                                         FileData *sfd = work2->data;
745                                         work2 = work2->next;
746
747                                         if (g_ascii_strcasecmp(ext, sfd->extension) == 0)
748                                                 {
749                                                 p = sfd->path;
750                                                 break;
751                                                 }
752                                         }
753                                 if (p) break;
754                                 }
755                         if (!p) return NULL;
756                         }
757                 }
758         else if (type == PATH_DEST)
759                 {
760                 if (fd->change && fd->change->dest)
761                         p = fd->change->dest;
762                 else
763                         p = "";
764                 }
765
766         g_assert(p);
767         string = g_string_append(string, p);
768
769         if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
770         pathl = path_from_utf8(string->str);
771         g_string_free(string, TRUE);
772
773         if (pathl && !pathl[0]) /* empty string case */
774                 {
775                 g_free(pathl);
776                 pathl = NULL;
777                 }
778
779         DEBUG_2("editor_command_path_parse: return %s", pathl);
780         return pathl;
781 }
782
783 static GString *append_quoted(GString *str, const char *s, gboolean single_quotes, gboolean double_quotes)
784 {
785         const char *p;
786
787         if (!single_quotes)
788                 {
789                 if (!double_quotes)
790                         g_string_append_c(str, '\'');
791                 else
792                         g_string_append(str, "\"'");
793                 }
794
795         for (p = s; *p != '\0'; p++)
796                 {
797                 if (*p == '\'')
798                         g_string_append(str, "'\\''");
799                 else
800                         g_string_append_c(str, *p);
801                 }
802
803         if (!single_quotes)
804                 {
805                 if (!double_quotes)
806                         g_string_append_c(str, '\'');
807                 else
808                         g_string_append(str, "'\"");
809                 }
810
811         return str;
812 }
813
814
815 EditorFlags editor_command_parse(const EditorDescription *editor, GList *list, gboolean consider_sidecars, gchar **output)
816 {
817         EditorFlags flags = 0;
818         const gchar *p;
819         GString *result = NULL;
820         gboolean escape = FALSE;
821         gboolean single_quotes = FALSE;
822         gboolean double_quotes = FALSE;
823
824         DEBUG_2("editor_command_parse: %s %d %d", editor->key, consider_sidecars, !!output);
825
826         if (output)
827                 result = g_string_new("");
828
829         if (editor->exec == NULL || editor->exec[0] == '\0')
830                 {
831                 flags |= EDITOR_ERROR_EMPTY;
832                 goto err;
833                 }
834
835         p = editor->exec;
836         /* skip leading whitespaces if any */
837         while (g_ascii_isspace(*p)) p++;
838
839         /* command */
840
841         while (*p)
842                 {
843                 if (escape)
844                         {
845                         escape = FALSE;
846                         if (output) result = g_string_append_c(result, *p);
847                         }
848                 else if (*p == '\\')
849                         {
850                         if (!single_quotes) escape = TRUE;
851                         if (output) result = g_string_append_c(result, *p);
852                         }
853                 else if (*p == '\'')
854                         {
855                         if (output) result = g_string_append_c(result, *p);
856                         if (!single_quotes && !double_quotes)
857                                 single_quotes = TRUE;
858                         else if (single_quotes)
859                                 single_quotes = FALSE;
860                         }
861                 else if (*p == '"')
862                         {
863                         if (output) result = g_string_append_c(result, *p);
864                         if (!single_quotes && !double_quotes)
865                                 double_quotes = TRUE;
866                         else if (double_quotes)
867                                 double_quotes = FALSE;
868                         }
869                 else if (*p == '%' && p[1])
870                         {
871                         gchar *pathl = NULL;
872
873                         p++;
874
875                         switch (*p)
876                                 {
877                                 case 'f': /* single file */
878                                 case 'u': /* single url */
879                                         flags |= EDITOR_FOR_EACH;
880                                         if (flags & EDITOR_SINGLE_COMMAND)
881                                                 {
882                                                 flags |= EDITOR_ERROR_INCOMPATIBLE;
883                                                 goto err;
884                                                 }
885                                         if (list)
886                                                 {
887                                                 /* use the first file from the list */
888                                                 if (!list->data)
889                                                         {
890                                                         flags |= EDITOR_ERROR_NO_FILE;
891                                                         goto err;
892                                                         }
893                                                 pathl = editor_command_path_parse((FileData *)list->data,
894                                                                                   consider_sidecars,
895                                                                                   (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
896                                                                                   editor);
897                                                 if (!output)
898                                                         {
899                                                         /* just testing, check also the rest of the list (like with F and U)
900                                                            any matching file is OK */
901                                                         GList *work = list->next;
902
903                                                         while (!pathl && work)
904                                                                 {
905                                                                 FileData *fd = work->data;
906                                                                 pathl = editor_command_path_parse(fd,
907                                                                                                   consider_sidecars,
908                                                                                                   (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
909                                                                                                   editor);
910                                                                 work = work->next;
911                                                                 }
912                                                         }
913
914                                                 if (!pathl)
915                                                         {
916                                                         flags |= EDITOR_ERROR_NO_FILE;
917                                                         goto err;
918                                                         }
919                                                 if (output)
920                                                         {
921                                                         result = append_quoted(result, pathl, single_quotes, double_quotes);
922                                                         }
923                                                 g_free(pathl);
924                                                 }
925                                         break;
926
927                                 case 'F':
928                                 case 'U':
929                                         flags |= EDITOR_SINGLE_COMMAND;
930                                         if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
931                                                 {
932                                                 flags |= EDITOR_ERROR_INCOMPATIBLE;
933                                                 goto err;
934                                                 }
935
936                                         if (list)
937                                                 {
938                                                 /* use whole list */
939                                                 GList *work = list;
940                                                 gboolean ok = FALSE;
941
942                                                 while (work)
943                                                         {
944                                                         FileData *fd = work->data;
945                                                         pathl = editor_command_path_parse(fd, consider_sidecars, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
946                                                         if (pathl)
947                                                                 {
948                                                                 ok = TRUE;
949
950                                                                 if (output)
951                                                                         {
952                                                                         ok = TRUE;
953                                                                         if (work != list) g_string_append_c(result, ' ');
954                                                                         result = append_quoted(result, pathl, single_quotes, double_quotes);
955                                                                         }
956                                                                 g_free(pathl);
957                                                                 }
958                                                         work = work->next;
959                                                         }
960                                                 if (!ok)
961                                                         {
962                                                         flags |= EDITOR_ERROR_NO_FILE;
963                                                         goto err;
964                                                         }
965                                                 }
966                                         break;
967                                 case 'i':
968                                         if (editor->icon && *editor->icon)
969                                                 {
970                                                 if (output)
971                                                         {
972                                                         result = g_string_append(result, "--icon ");
973                                                         result = append_quoted(result, editor->icon, single_quotes, double_quotes);
974                                                         }
975                                                 }
976                                         break;
977                                 case 'c':
978                                         if (output)
979                                                 {
980                                                 result = append_quoted(result, editor->name, single_quotes, double_quotes);
981                                                 }
982                                         break;
983                                 case 'k':
984                                         if (output)
985                                                 {
986                                                 result = append_quoted(result, editor->file, single_quotes, double_quotes);
987                                                 }
988                                         break;
989                                 case '%':
990                                         /* %% = % escaping */
991                                         if (output) result = g_string_append_c(result, *p);
992                                         break;
993                                 case 'd':
994                                 case 'D':
995                                 case 'n':
996                                 case 'N':
997                                 case 'v':
998                                 case 'm':
999                                         /* deprecated according to spec, ignore */
1000                                         break;
1001                                 default:
1002                                         flags |= EDITOR_ERROR_SYNTAX;
1003                                         goto err;
1004                                 }
1005                         }
1006                 else
1007                         {
1008                         if (output) result = g_string_append_c(result, *p);
1009                         }
1010                 p++;
1011                 }
1012
1013         if (!(flags & (EDITOR_FOR_EACH | EDITOR_SINGLE_COMMAND))) flags |= EDITOR_NO_PARAM;
1014
1015         if (output)
1016                 {
1017                 *output = g_string_free(result, FALSE);
1018                 DEBUG_3("Editor cmd: %s", *output);
1019                 }
1020
1021         return flags;
1022
1023
1024 err:
1025         if (output)
1026                 {
1027                 g_string_free(result, TRUE);
1028                 *output = NULL;
1029                 }
1030         return flags;
1031 }
1032
1033
1034 static void editor_child_exit_cb(GPid pid, gint status, gpointer data)
1035 {
1036         EditorData *ed = data;
1037         g_spawn_close_pid(pid);
1038         ed->pid = -1;
1039
1040         editor_command_next_finish(ed, status);
1041 }
1042
1043
1044 static EditorFlags editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
1045 {
1046         gchar *command;
1047         FileData *fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : list->data;;
1048         GPid pid;
1049         gint standard_output;
1050         gint standard_error;
1051         gboolean ok;
1052
1053         ed->pid = -1;
1054         ed->flags = editor->flags;
1055         ed->flags |= editor_command_parse(editor, list, TRUE, &command);
1056
1057         ok = !EDITOR_ERRORS(ed->flags);
1058
1059         if (ok)
1060                 {
1061                 ok = (options->shell.path && *options->shell.path);
1062                 if (!ok) log_printf("ERROR: empty shell command\n");
1063
1064                 if (ok)
1065                         {
1066                         ok = (access(options->shell.path, X_OK) == 0);
1067                         if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path);
1068                         }
1069
1070                 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1071                 }
1072
1073         if (ok)
1074                 {
1075                 gchar *working_directory;
1076                 gchar *args[4];
1077                 guint n = 0;
1078
1079                 working_directory = fd ? remove_level_from_path(fd->path) : g_strdup(ed->working_directory);
1080                 args[n++] = options->shell.path;
1081                 if (options->shell.options && *options->shell.options)
1082                         args[n++] = options->shell.options;
1083                 args[n++] = command;
1084                 args[n] = NULL;
1085
1086                 if ((ed->flags & EDITOR_DEST) && fd && fd->change && fd->change->dest) /* FIXME: error handling */
1087                         {
1088                         g_setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
1089                         }
1090                 else
1091                         {
1092                         g_unsetenv("GEEQIE_DESTINATION");
1093                         }
1094
1095                 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
1096                                       G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
1097                                       NULL, NULL,
1098                                       &pid,
1099                                       NULL,
1100                                       ed->vd ? &standard_output : NULL,
1101                                       ed->vd ? &standard_error : NULL,
1102                                       NULL);
1103
1104                 g_free(working_directory);
1105
1106                 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
1107                 }
1108
1109         if (ok)
1110                 {
1111                 g_child_watch_add(pid, editor_child_exit_cb, ed);
1112                 ed->pid = pid;
1113                 }
1114
1115         if (ed->vd)
1116                 {
1117                 if (!ok)
1118                         {
1119                         gchar *buf;
1120
1121                         buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
1122                         editor_verbose_window_fill(ed->vd, buf, strlen(buf));
1123                         g_free(buf);
1124
1125                         }
1126                 else
1127                         {
1128                         GIOChannel *channel_output;
1129                         GIOChannel *channel_error;
1130
1131                         channel_output = g_io_channel_unix_new(standard_output);
1132                         g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
1133                         g_io_channel_set_encoding(channel_output, NULL, NULL);
1134
1135                         g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1136                                             editor_verbose_io_cb, ed, NULL);
1137                         g_io_channel_unref(channel_output);
1138
1139                         channel_error = g_io_channel_unix_new(standard_error);
1140                         g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
1141                         g_io_channel_set_encoding(channel_error, NULL, NULL);
1142
1143                         g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
1144                                             editor_verbose_io_cb, ed, NULL);
1145                         g_io_channel_unref(channel_error);
1146                         }
1147                 }
1148
1149         g_free(command);
1150
1151         return EDITOR_ERRORS(ed->flags);
1152 }
1153
1154 static EditorFlags editor_command_next_start(EditorData *ed)
1155 {
1156         if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
1157
1158         if ((ed->list || (ed->flags & EDITOR_NO_PARAM)) && ed->count < ed->total)
1159                 {
1160                 FileData *fd;
1161                 EditorFlags error;
1162
1163                 fd = (ed->flags & EDITOR_NO_PARAM) ? NULL : ed->list->data;
1164
1165                 if (ed->vd)
1166                         {
1167                         if ((ed->flags & EDITOR_FOR_EACH) && fd)
1168                                 editor_verbose_window_progress(ed, fd->path);
1169                         else
1170                                 editor_verbose_window_progress(ed, _("running..."));
1171                         }
1172                 ed->count++;
1173
1174                 error = editor_command_one(ed->editor, ed->list, ed);
1175                 if (!error && ed->vd)
1176                         {
1177                         gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
1178                         if ((ed->flags & EDITOR_FOR_EACH) && fd)
1179                                 {
1180                                 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
1181                                 editor_verbose_window_fill(ed->vd, "\n", 1);
1182                                 }
1183                         }
1184
1185                 if (!error)
1186                         return 0;
1187
1188                 /* command was not started, call the finish immediately */
1189                 return editor_command_next_finish(ed, 0);
1190                 }
1191
1192         /* everything is done */
1193         return editor_command_done(ed);
1194 }
1195
1196 static EditorFlags editor_command_next_finish(EditorData *ed, gint status)
1197 {
1198         gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
1199
1200         if (status)
1201                 ed->flags |= EDITOR_ERROR_STATUS;
1202
1203         if (ed->flags & EDITOR_FOR_EACH)
1204                 {
1205                 /* handle the first element from the list */
1206                 GList *fd_element = ed->list;
1207
1208                 ed->list = g_list_remove_link(ed->list, fd_element);
1209                 if (ed->callback)
1210                         {
1211                         cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
1212                         if (ed->stopping && cont == EDITOR_CB_CONTINUE) cont = EDITOR_CB_SKIP;
1213                         }
1214                 filelist_free(fd_element);
1215                 }
1216         else
1217                 {
1218                 /* handle whole list */
1219                 if (ed->callback)
1220                         cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
1221                 filelist_free(ed->list);
1222                 ed->list = NULL;
1223                 }
1224
1225         switch (cont)
1226                 {
1227                 case EDITOR_CB_SUSPEND:
1228                         return EDITOR_ERRORS(ed->flags);
1229                 case EDITOR_CB_SKIP:
1230                         return editor_command_done(ed);
1231                 }
1232
1233         return editor_command_next_start(ed);
1234 }
1235
1236 static EditorFlags editor_command_done(EditorData *ed)
1237 {
1238         EditorFlags flags;
1239
1240         if (ed->vd)
1241                 {
1242                 if (ed->count == ed->total)
1243                         {
1244                         editor_verbose_window_progress(ed, _("done"));
1245                         }
1246                 else
1247                         {
1248                         editor_verbose_window_progress(ed, _("stopped by user"));
1249                         }
1250                 editor_verbose_window_enable_close(ed->vd);
1251                 }
1252
1253         /* free the not-handled items */
1254         if (ed->list)
1255                 {
1256                 ed->flags |= EDITOR_ERROR_SKIPPED;
1257                 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
1258                 filelist_free(ed->list);
1259                 ed->list = NULL;
1260                 }
1261
1262         ed->count = 0;
1263
1264         flags = EDITOR_ERRORS(ed->flags);
1265
1266         if (!ed->vd) editor_data_free(ed);
1267
1268         return flags;
1269 }
1270
1271 void editor_resume(gpointer ed)
1272 {
1273         editor_command_next_start(ed);
1274 }
1275
1276 void editor_skip(gpointer ed)
1277 {
1278         editor_command_done(ed);
1279 }
1280
1281 static EditorFlags editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1282 {
1283         EditorData *ed;
1284         EditorFlags flags = editor->flags;
1285
1286         if (EDITOR_ERRORS(flags)) return EDITOR_ERRORS(flags);
1287
1288         ed = g_new0(EditorData, 1);
1289         ed->list = filelist_copy(list);
1290         ed->flags = flags;
1291         ed->editor = editor;
1292         ed->total = (flags & (EDITOR_SINGLE_COMMAND | EDITOR_NO_PARAM)) ? 1 : g_list_length(list);
1293         ed->callback = cb;
1294         ed->data = data;
1295         ed->working_directory = g_strdup(working_directory);
1296
1297         if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
1298                 flags |= EDITOR_VERBOSE;
1299
1300         if (flags & EDITOR_VERBOSE)
1301                 editor_verbose_window(ed, text);
1302
1303         editor_command_next_start(ed);
1304         /* errors from editor_command_next_start will be handled via callback */
1305         return EDITOR_ERRORS(flags);
1306 }
1307
1308 gboolean is_valid_editor_command(const gchar *key)
1309 {
1310         if (!key) return FALSE;
1311         return g_hash_table_lookup(editors, key) != NULL;
1312 }
1313
1314 EditorFlags start_editor_from_filelist_full(const gchar *key, GList *list, const gchar *working_directory, EditorCallback cb, gpointer data)
1315 {
1316         EditorFlags error;
1317         EditorDescription *editor;
1318         if (!key) return EDITOR_ERROR_EMPTY;
1319
1320         editor = g_hash_table_lookup(editors, key);
1321
1322         if (!editor) return EDITOR_ERROR_EMPTY;
1323         if (!list && !(editor->flags & EDITOR_NO_PARAM)) return EDITOR_ERROR_NO_FILE;
1324
1325         error = editor_command_parse(editor, list, TRUE, NULL);
1326
1327         if (EDITOR_ERRORS(error)) return error;
1328
1329         error |= editor_command_start(editor, editor->name, list, working_directory, cb, data);
1330
1331         if (EDITOR_ERRORS(error))
1332                 {
1333                 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
1334
1335                 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
1336                 g_free(text);
1337                 }
1338
1339         return EDITOR_ERRORS(error);
1340 }
1341
1342 EditorFlags start_editor_from_filelist(const gchar *key, GList *list)
1343 {
1344         return start_editor_from_filelist_full(key, list, NULL, NULL, NULL);
1345 }
1346
1347 EditorFlags start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
1348 {
1349         GList *list;
1350         EditorFlags error;
1351
1352         if (!fd) return FALSE;
1353
1354         list = g_list_append(NULL, fd);
1355         error = start_editor_from_filelist_full(key, list, NULL, cb, data);
1356         g_list_free(list);
1357         return error;
1358 }
1359
1360 EditorFlags start_editor_from_file(const gchar *key, FileData *fd)
1361 {
1362         return start_editor_from_file_full(key, fd, NULL, NULL);
1363 }
1364
1365 EditorFlags start_editor(const gchar *key, const gchar *working_directory)
1366 {
1367         return start_editor_from_filelist_full(key, NULL, working_directory, NULL, NULL);
1368 }
1369
1370 gboolean editor_window_flag_set(const gchar *key)
1371 {
1372         EditorDescription *editor;
1373         if (!key) return TRUE;
1374
1375         editor = g_hash_table_lookup(editors, key);
1376         if (!editor) return TRUE;
1377
1378         return !!(editor->flags & EDITOR_KEEP_FS);
1379 }
1380
1381 gboolean editor_is_filter(const gchar *key)
1382 {
1383         EditorDescription *editor;
1384         if (!key) return TRUE;
1385
1386         editor = g_hash_table_lookup(editors, key);
1387         if (!editor) return TRUE;
1388
1389         return !!(editor->flags & EDITOR_DEST);
1390 }
1391
1392 gboolean editor_no_param(const gchar *key)
1393 {
1394         EditorDescription *editor;
1395         if (!key) return FALSE;
1396
1397         editor = g_hash_table_lookup(editors, key);
1398         if (!editor) return FALSE;
1399
1400         return !!(editor->flags & EDITOR_NO_PARAM);
1401 }
1402
1403 gboolean editor_blocks_file(const gchar *key)
1404 {
1405         EditorDescription *editor;
1406         if (!key) return FALSE;
1407
1408         editor = g_hash_table_lookup(editors, key);
1409         if (!editor) return FALSE;
1410
1411         /* Decide if the image file should be blocked during editor execution
1412            Editors like gimp can be used long time after the original file was
1413            saved, for editing unrelated files.
1414            %f vs. %F seems to be a good heuristic to detect this kind of editors.
1415         */
1416
1417         return !(editor->flags & EDITOR_SINGLE_COMMAND);
1418 }
1419
1420 const gchar *editor_get_error_str(EditorFlags flags)
1421 {
1422         if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
1423         if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
1424         if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
1425         if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
1426         if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
1427         if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
1428         if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
1429         return _("Unknown error.");
1430 }
1431
1432 const gchar *editor_get_name(const gchar *key)
1433 {
1434         EditorDescription *editor = g_hash_table_lookup(editors, key);
1435
1436         if (!editor) return NULL;
1437
1438         return editor->name;
1439 }
1440 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */