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