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