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