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