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