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