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