comparison src/editors.c @ 1272:e0e12512cde2

read external editors from .desktop files
author nadvornik
date Sun, 01 Feb 2009 12:48:14 +0000
parents 878718372aca
children 8fcdfacce91d
comparison
equal deleted inserted replaced
1271:4fcdbb497df3 1272:e0e12512cde2
42 42
43 typedef struct _EditorData EditorData; 43 typedef struct _EditorData EditorData;
44 struct _EditorData { 44 struct _EditorData {
45 gint flags; 45 gint flags;
46 GPid pid; 46 GPid pid;
47 gchar *command_template;
48 GList *list; 47 GList *list;
49 gint count; 48 gint count;
50 gint total; 49 gint total;
51 gboolean stopping; 50 gboolean stopping;
52 EditorVerboseData *vd; 51 EditorVerboseData *vd;
53 EditorCallback callback; 52 EditorCallback callback;
54 gpointer data; 53 gpointer data;
54 const EditorDescription *editor;
55 }; 55 };
56 56
57
58 static Editor editor_slot_defaults[GQ_EDITOR_SLOTS] = {
59 { N_("The Gimp"), "gimp-remote %{%raw;*}f" },
60 { N_("XV"), "xv %f" },
61 { N_("Xpaint"), "xpaint %f" },
62 { N_("UFraw"), "ufraw %{%raw}p" },
63 { N_("Symlink"), "ln -s %p %d"},
64 { NULL, NULL },
65 { NULL, NULL },
66 { NULL, NULL },
67 { N_("Rotate jpeg clockwise"), "%vif jpegtran -rotate 90 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}p_tmp;fi" },
68 { N_("Rotate jpeg counterclockwise"), "%vif jpegtran -rotate 270 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}p_tmp;fi" },
69 /* special slots */
70 #if 1
71 /* for testing */
72 { N_("External Copy command"), "%vset -x;cp %p %d" },
73 { N_("External Move command"), "%vset -x;mv %p %d" },
74 { N_("External Rename command"), "%vset -x;mv %p %d" },
75 { N_("External Delete command"), NULL },
76 { N_("External New Folder command"), NULL },
77 #else
78 { N_("External Copy command"), NULL },
79 { N_("External Move command"), NULL },
80 { N_("External Rename command"), NULL },
81 { N_("External Delete command"), NULL },
82 { N_("External New Folder command"), NULL },
83 #endif
84 };
85 57
86 static void editor_verbose_window_progress(EditorData *ed, const gchar *text); 58 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
87 static gint editor_command_next_start(EditorData *ed); 59 static gint editor_command_next_start(EditorData *ed);
88 static gint editor_command_next_finish(EditorData *ed, gint status); 60 static gint editor_command_next_finish(EditorData *ed, gint status);
89 static gint editor_command_done(EditorData *ed); 61 static gint editor_command_done(EditorData *ed);
62 static gint editor_command_parse(const EditorDescription *editor, GList *list, gchar **output);
90 63
91 /* 64 /*
92 *----------------------------------------------------------------------------- 65 *-----------------------------------------------------------------------------
93 * external editor routines 66 * external editor routines
94 *----------------------------------------------------------------------------- 67 *-----------------------------------------------------------------------------
95 */ 68 */
96 69
97 void editor_set_name(gint n, gchar *name) 70 GHashTable *editors = NULL;
98 { 71
99 if (n < 0 || n >= GQ_EDITOR_SLOTS) return; 72 #ifdef G_KEY_FILE_DESKTOP_GROUP
100 73 #define DESKTOP_GROUP G_KEY_FILE_DESKTOP_GROUP
101 g_free(options->editor[n].name); 74 #else
102 75 #define DESKTOP_GROUP "Desktop Entry"
103 options->editor[n].name = name ? utf8_validate_or_convert(name) : NULL; 76 #endif
104 } 77
105 78 void editor_description_free(EditorDescription *editor)
106 void editor_set_command(gint n, gchar *command) 79 {
107 { 80 if (!editor) return;
108 if (n < 0 || n >= GQ_EDITOR_SLOTS) return; 81
109 82 g_free(editor->key);
110 g_free(options->editor[n].command); 83 g_free(editor->name);
111 options->editor[n].command = command ? utf8_validate_or_convert(command) : NULL; 84 g_free(editor->exec);
112 } 85 g_free(editor->menu_path);
113 86 g_free(editor->hotkey);
114 void editor_reset_defaults(void) 87 string_list_free(editor->ext_list);
115 { 88 g_free(editor->icon);
89 g_free(editor->file);
90 g_free(editor);
91 }
92
93 static GList *editor_mime_types_to_extensions(gchar **mime_types)
94 {
95 /* FIXME: this should be rewritten to use the shared mime database, as soon as we switch to gio */
96
97 static const gchar *conv_table[][2] = {
98 {"application/x-ufraw", "%raw"},
99 {"image/*", "*"},
100 {"image/bmp", ".bmp"},
101 {"image/gif", ".gif"},
102 {"image/jpeg", ".jpeg;.jpg"},
103 {"image/jpg", ".jpg;.jpeg"},
104 {"image/pcx", ".pcx"},
105 {"image/png", ".png"},
106 {"image/svg", ".svg"},
107 {"image/svg+xml", ".svg"},
108 {"image/svg+xml-compressed", ".svg"},
109 {"image/tiff", ".tiff;.tif"},
110 {"image/x-bmp", ".bmp"},
111 {"image/x-canon-crw", ".crw"},
112 {"image/x-cr2", ".cr2"},
113 {"image/x-dcraw", "%raw"},
114 {"image/x-ico", ".ico"},
115 {"image/x-mrw", ".mrw"},
116 {"image/x-MS-bmp", ".bmp"},
117 {"image/x-nef", ".nef"},
118 {"image/x-orf", ".orf"},
119 {"image/x-pcx", ".pcx"},
120 {"image/xpm", ".xpm"},
121 {"image/x-png", ".png"},
122 {"image/x-portable-anymap", ".pam"},
123 {"image/x-portable-bitmap", ".pbm"},
124 {"image/x-portable-graymap", ".pgm"},
125 {"image/x-portable-pixmap", ".ppm"},
126 {"image/x-psd", ".psd"},
127 {"image/x-raf", ".raf"},
128 {"image/x-sgi", ".sgi"},
129 {"image/x-tga", ".tga"},
130 {"image/x-xbitmap", ".xbm"},
131 {"image/x-xcf", ".xcf"},
132 {"image/x-xpixmap", ".xpm"},
133 {"image/x-x3f", ".x3f"},
134 {NULL, NULL}};
135
136 gint i, j;
137 GList *list = NULL;
138
139 for (i = 0; mime_types[i]; i++)
140 for (j = 0; conv_table[j][0]; j++)
141 if (strcmp(mime_types[i], conv_table[j][0]) == 0)
142 list = g_list_concat(list, filter_to_list(conv_table[j][1]));
143
144 return list;
145 }
146
147 static gboolean editor_read_desktop_file(const gchar *path)
148 {
149 GKeyFile *key_file;
150 EditorDescription *editor;
151 gchar *extensions;
152 const gchar *key = filename_from_path(path);
153 gchar **categories, **only_show_in, **not_show_in;
154
155 if (g_hash_table_lookup(editors, key)) return FALSE; /* the file found earlier wins */
156
157 key_file = g_key_file_new();
158 if (!g_key_file_load_from_file(key_file, path, 0, NULL))
159 {
160 g_key_file_free(key_file);
161 return FALSE;
162 }
163
164 editor = g_new0(EditorDescription, 1);
165
166 editor->key = g_strdup(key);
167 editor->file = g_strdup(path);
168
169 g_hash_table_insert(editors, editor->key, editor);
170
171 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Hidden", NULL)) editor->hidden = TRUE;
172
173 categories = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "Categories", NULL, NULL);
174 if (categories)
175 {
176 gboolean found = FALSE;
177 gint i;
178 for (i = 0; categories[i]; i++)
179 /* IMHO "Graphics" is exactly the category that we are interested in, so this does not have to be configurable */
180 if (strcmp(categories[i], "Graphics") == 0 ||
181 strcmp(categories[i], "X-Geeqie") == 0)
182 {
183 found = TRUE;
184 break;
185 }
186 if (!found) editor->hidden = TRUE;
187 g_strfreev(categories);
188 }
189 else
190 {
191 editor->hidden = TRUE;
192 }
193
194 only_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "OnlyShowIn", NULL, NULL);
195 if (only_show_in)
196 {
197 gboolean found = FALSE;
198 gint i;
199 for (i = 0; only_show_in[i]; i++)
200 if (strcmp(only_show_in[i], "X-Geeqie") == 0)
201 {
202 found = TRUE;
203 break;
204 }
205 if (!found) editor->hidden = TRUE;
206 g_strfreev(only_show_in);
207 }
208
209 not_show_in = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "NotShowIn", NULL, NULL);
210 if (not_show_in)
211 {
212 gboolean found = FALSE;
213 gint i;
214 for (i = 0; not_show_in[i]; i++)
215 if (strcmp(not_show_in[i], "X-Geeqie") == 0)
216 {
217 found = TRUE;
218 break;
219 }
220 if (found) editor->hidden = TRUE;
221 g_strfreev(not_show_in);
222 }
223
224 if (editor->hidden)
225 {
226 /* hidden editors will be deleted, no need to parse the rest */
227 g_key_file_free(key_file);
228 return TRUE;
229 }
230
231 editor->name = g_key_file_get_locale_string(key_file, DESKTOP_GROUP, "Name", NULL, NULL);
232 editor->icon = g_key_file_get_string(key_file, DESKTOP_GROUP, "Icon", NULL);
233
234 editor->exec = g_key_file_get_string(key_file, DESKTOP_GROUP, "Exec", NULL);
235
236 /* we take only editors that accept parameters, FIXME: the test can be improved */
237 if (!strchr(editor->exec, '%')) editor->hidden = TRUE;
238
239 editor->menu_path = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Menu-Path", NULL);
240 if (!editor->menu_path) editor->menu_path = g_strdup("EditMenu/ExternalMenu");
241
242 editor->hotkey = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-Hotkey", NULL);
243
244 extensions = g_key_file_get_string(key_file, DESKTOP_GROUP, "X-Geeqie-File-Extensions", NULL);
245 if (extensions)
246 editor->ext_list = filter_to_list(extensions);
247 else
248 {
249 gchar **mime_types = g_key_file_get_string_list(key_file, DESKTOP_GROUP, "MimeType", NULL, NULL);
250 if (mime_types)
251 {
252 editor->ext_list = editor_mime_types_to_extensions(mime_types);
253 g_strfreev(mime_types);
254 if (!editor->ext_list) editor->hidden = TRUE;
255 }
256
257 }
258
259
260 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Keep-Fullscreen", NULL)) editor->flags |= EDITOR_KEEP_FS;
261 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose", NULL)) editor->flags |= EDITOR_VERBOSE;
262 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Verbose-Multi", NULL)) editor->flags |= EDITOR_VERBOSE_MULTI;
263 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "X-Geeqie-Filter", NULL)) editor->flags |= EDITOR_DEST;
264 if (g_key_file_get_boolean(key_file, DESKTOP_GROUP, "Terminal", NULL)) editor->flags |= EDITOR_TERMINAL;
265
266
267 editor->flags |= editor_command_parse(editor, NULL, NULL);
268 g_key_file_free(key_file);
269
270 return TRUE;
271 }
272
273 static gboolean editor_remove_desktop_file_cb(gpointer key, gpointer value, gpointer user_data)
274 {
275 EditorDescription *editor = value;
276 return editor->hidden;
277 }
278
279 static void editor_read_desktop_dir(const gchar *path)
280 {
281 DIR *dp;
282 struct dirent *dir;
283 gchar *pathl;
284
285 pathl = path_from_utf8(path);
286 dp = opendir(pathl);
287 g_free(pathl);
288 if (!dp)
289 {
290 /* dir not found */
291 return;
292 }
293 while ((dir = readdir(dp)) != NULL)
294 {
295 gchar *namel = dir->d_name;
296 size_t len = strlen(namel);
297
298 if (len > 8 && strncasecmp(namel + len - 8, ".desktop", 8) == 0)
299 {
300 gchar *name = path_to_utf8(namel);
301 gchar *dpath = g_build_filename(path, name, NULL);
302 editor_read_desktop_file(dpath);
303 g_free(dpath);
304 g_free(name);
305 }
306 }
307 closedir(dp);
308 }
309
310 void editor_load_descriptions(void)
311 {
312 gchar *path;
313 gchar *xdg_data_dirs;
314 gchar *all_dirs;
315 gchar **split_dirs;
116 gint i; 316 gint i;
117 317
118 for (i = 0; i < GQ_EDITOR_SLOTS; i++) 318 if (!editors)
119 { 319 {
120 editor_set_name(i, _(editor_slot_defaults[i].name)); 320 editors = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)editor_description_free);
121 editor_set_command(i, _(editor_slot_defaults[i].command)); 321 }
122 } 322
123 } 323 xdg_data_dirs = getenv("XDG_DATA_DIRS");
324 if (xdg_data_dirs && xdg_data_dirs[0])
325 xdg_data_dirs = path_to_utf8(xdg_data_dirs);
326 else
327 xdg_data_dirs = g_strdup("/usr/share");
328
329 all_dirs = g_strconcat(get_rc_dir(), ":", GQ_APP_DIR, ":", xdg_data_dirs, NULL);
330
331 g_free(xdg_data_dirs);
332
333 split_dirs = g_strsplit(all_dirs, ":", 0);
334
335 g_free(all_dirs);
336
337 for (i = 0; split_dirs[i]; i++)
338 {
339 path = g_build_filename(split_dirs[i], "applications", NULL);
340 editor_read_desktop_dir(path);
341 g_free(path);
342 }
343
344 g_strfreev(split_dirs);
345
346 g_hash_table_foreach_remove(editors, editor_remove_desktop_file_cb, NULL);
347 }
348
349 static void editor_list_add_cb(gpointer key, gpointer value, gpointer data)
350 {
351 GList **listp = data;
352 EditorDescription *editor = value;
353
354 /* do not show the special commands in any list, they are called explicitelly */
355 if (strcmp(editor->key, CMD_COPY) == 0 ||
356 strcmp(editor->key, CMD_MOVE) == 0 ||
357 strcmp(editor->key, CMD_RENAME) == 0 ||
358 strcmp(editor->key, CMD_DELETE) == 0 ||
359 strcmp(editor->key, CMD_FOLDER) == 0) return;
360
361 *listp = g_list_prepend(*listp, editor);
362 }
363
364 GList *editor_list_get(void)
365 {
366 GList *editors_list = NULL;
367 g_hash_table_foreach(editors, editor_list_add_cb, &editors_list);
368 return editors_list;
369 }
370
371 /* ------------------------------ */
372
124 373
125 static void editor_verbose_data_free(EditorData *ed) 374 static void editor_verbose_data_free(EditorData *ed)
126 { 375 {
127 if (!ed->vd) return; 376 if (!ed->vd) return;
128 g_free(ed->vd); 377 g_free(ed->vd);
130 } 379 }
131 380
132 static void editor_data_free(EditorData *ed) 381 static void editor_data_free(EditorData *ed)
133 { 382 {
134 editor_verbose_data_free(ed); 383 editor_verbose_data_free(ed);
135 g_free(ed->command_template);
136 g_free(ed); 384 g_free(ed);
137 } 385 }
138 386
139 static void editor_verbose_window_close(GenericDialog *gd, gpointer data) 387 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
140 { 388 {
279 return TRUE; 527 return TRUE;
280 } 528 }
281 529
282 typedef enum { 530 typedef enum {
283 PATH_FILE, 531 PATH_FILE,
532 PATH_FILE_URL,
284 PATH_DEST 533 PATH_DEST
285 } PathType; 534 } PathType;
286 535
287 536
288 static gchar *editor_command_path_parse(const FileData *fd, PathType type, const gchar *extensions) 537 static gchar *editor_command_path_parse(const FileData *fd, PathType type, const EditorDescription *editor)
289 { 538 {
290 GString *string; 539 GString *string;
291 gchar *pathl; 540 gchar *pathl;
292 const gchar *p = NULL; 541 const gchar *p = NULL;
293 542
294 string = g_string_new(""); 543 string = g_string_new("");
295 544
296 if (type == PATH_FILE) 545 if (type == PATH_FILE || type == PATH_FILE_URL)
297 { 546 {
298 GList *ext_list = filter_to_list(extensions); 547 GList *work = editor->ext_list;
299 GList *work = ext_list;
300 548
301 if (!work) 549 if (!work)
302 p = fd->path; 550 p = fd->path;
303 else 551 else
304 { 552 {
327 break; 575 break;
328 } 576 }
329 } 577 }
330 if (p) break; 578 if (p) break;
331 } 579 }
332 string_list_free(ext_list);
333 if (!p) return NULL; 580 if (!p) return NULL;
334 } 581 }
335 } 582 }
336 else if (type == PATH_DEST) 583 else if (type == PATH_DEST)
337 { 584 {
352 } 599 }
353 string = g_string_append_c(string, *p); 600 string = g_string_append_c(string, *p);
354 p++; 601 p++;
355 } 602 }
356 603
604 if (type == PATH_FILE_URL) g_string_prepend(string, "file://");
357 pathl = path_from_utf8(string->str); 605 pathl = path_from_utf8(string->str);
358 g_string_free(string, TRUE); 606 g_string_free(string, TRUE);
359 607
360 return pathl; 608 return pathl;
361 } 609 }
362 610
363 611
364 /* 612 static gint editor_command_parse(const EditorDescription *editor, GList *list, gchar **output)
365 * The supported macros for editor commands:
366 *
367 * %f first occurence replaced by quoted sequence of filenames, command is run once.
368 * only one occurence of this macro is supported.
369 * ([ls %f] results in [ls "file1" "file2" ... "lastfile"])
370 * %p command is run for each filename in turn, each instance replaced with single filename.
371 * multiple occurences of this macro is supported for complex shell commands.
372 * This macro will BLOCK THE APPLICATION until it completes, since command is run once
373 * for every file in syncronous order. To avoid blocking add the %v macro, below.
374 * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"])
375 * none if no macro is supplied, the result is equivalent to "command %f"
376 * ([ls] results in [ls "file1" "file2" ... "lastfile"])
377 *
378 * Only one of the macros %f or %p may be used in a given commmand.
379 *
380 * %v must be the first two characters[1] in a command, causes a window to display
381 * showing the output of the command(s).
382 * %V same as %v except in the case of %p only displays a window for multiple files,
383 * operating on a single file is suppresses the output dialog.
384 *
385 * %w must be first two characters in a command, presence will disable full screen
386 * from exiting upon invocation.
387 *
388 *
389 * [1] Note: %v,%V may also be preceded by "%w".
390 */
391
392
393 gint editor_command_parse(const gchar *template, GList *list, gchar **output)
394 { 613 {
395 gint flags = 0; 614 gint flags = 0;
396 const gchar *p = template; 615 const gchar *p;
397 GString *result = NULL; 616 GString *result = NULL;
398 gchar *extensions = NULL;
399 617
400 if (output) 618 if (output)
401 result = g_string_new(""); 619 result = g_string_new("");
402 620
403 if (!template || template[0] == '\0') 621 if (editor->exec[0] == '\0')
404 { 622 {
405 flags |= EDITOR_ERROR_EMPTY; 623 flags |= EDITOR_ERROR_EMPTY;
406 goto err; 624 goto err;
407 } 625 }
408 626
627 p = editor->exec;
409 /* skip leading whitespaces if any */ 628 /* skip leading whitespaces if any */
410 while (g_ascii_isspace(*p)) p++; 629 while (g_ascii_isspace(*p)) p++;
411 630
412 /* global flags */
413 while (*p == '%')
414 {
415 switch (*++p)
416 {
417 case 'w':
418 flags |= EDITOR_KEEP_FS;
419 p++;
420 break;
421 case 'v':
422 flags |= EDITOR_VERBOSE;
423 p++;
424 break;
425 case 'V':
426 flags |= EDITOR_VERBOSE_MULTI;
427 p++;
428 break;
429 default:
430 flags |= EDITOR_ERROR_SYNTAX;
431 goto err;
432 }
433 }
434
435 /* skip whitespaces if any */
436 while (g_ascii_isspace(*p)) p++;
437
438 /* command */ 631 /* command */
439 632
440 while (*p) 633 while (*p)
441 { 634 {
442 if (*p != '%') 635 if (*p != '%')
443 { 636 {
444 if (output) result = g_string_append_c(result, *p); 637 if (output) result = g_string_append_c(result, *p);
445 } 638 }
446 else /* *p == '%' */ 639 else /* *p == '%' */
447 { 640 {
448 extensions = NULL;
449 gchar *pathl = NULL; 641 gchar *pathl = NULL;
450 642
451 p++; 643 p++;
452
453 /* for example "%f" or "%{.crw;.raw;.cr2}f" */
454 if (*p == '{')
455 {
456 gchar *end;
457
458 p++;
459 end = strchr(p, '}');
460 if (!end)
461 {
462 flags |= EDITOR_ERROR_SYNTAX;
463 goto err;
464 }
465
466 extensions = g_strndup(p, end - p);
467 p = end + 1;
468 }
469 644
470 switch (*p) 645 switch (*p)
471 { 646 {
472 case 'd': 647 case 'f': /* single file */
473 flags |= EDITOR_DEST; 648 case 'u': /* single url */
474 /* fall through */
475 case 'p':
476 flags |= EDITOR_FOR_EACH; 649 flags |= EDITOR_FOR_EACH;
477 if (flags & EDITOR_SINGLE_COMMAND) 650 if (flags & EDITOR_SINGLE_COMMAND)
478 { 651 {
479 flags |= EDITOR_ERROR_INCOMPATIBLE; 652 flags |= EDITOR_ERROR_INCOMPATIBLE;
480 goto err; 653 goto err;
486 { 659 {
487 flags |= EDITOR_ERROR_NO_FILE; 660 flags |= EDITOR_ERROR_NO_FILE;
488 goto err; 661 goto err;
489 } 662 }
490 pathl = editor_command_path_parse((FileData *)list->data, 663 pathl = editor_command_path_parse((FileData *)list->data,
491 (flags & EDITOR_DEST) ? PATH_DEST : PATH_FILE, 664 (*p == 'f') ? PATH_FILE : PATH_FILE_URL,
492 extensions); 665 editor);
493 if (!pathl) 666 if (!pathl)
494 { 667 {
495 flags |= EDITOR_ERROR_NO_FILE; 668 flags |= EDITOR_ERROR_NO_FILE;
496 goto err; 669 goto err;
497 } 670 }
500 g_free(pathl); 673 g_free(pathl);
501 result = g_string_append_c(result, '"'); 674 result = g_string_append_c(result, '"');
502 } 675 }
503 break; 676 break;
504 677
505 case 'f': 678 case 'F':
679 case 'U':
506 flags |= EDITOR_SINGLE_COMMAND; 680 flags |= EDITOR_SINGLE_COMMAND;
507 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST)) 681 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
508 { 682 {
509 flags |= EDITOR_ERROR_INCOMPATIBLE; 683 flags |= EDITOR_ERROR_INCOMPATIBLE;
510 goto err; 684 goto err;
517 gboolean ok = FALSE; 691 gboolean ok = FALSE;
518 692
519 while (work) 693 while (work)
520 { 694 {
521 FileData *fd = work->data; 695 FileData *fd = work->data;
522 pathl = editor_command_path_parse(fd, PATH_FILE, extensions); 696 pathl = editor_command_path_parse(fd, (*p == 'F') ? PATH_FILE : PATH_FILE_URL, editor);
523 697
524 if (pathl) 698 if (pathl)
525 { 699 {
526 ok = TRUE; 700 ok = TRUE;
527 if (work != list) g_string_append_c(result, ' '); 701 if (work != list) g_string_append_c(result, ' ');
537 flags |= EDITOR_ERROR_NO_FILE; 711 flags |= EDITOR_ERROR_NO_FILE;
538 goto err; 712 goto err;
539 } 713 }
540 } 714 }
541 break; 715 break;
716 case 'i':
717 if (output)
718 {
719 result = g_string_append(result, editor->icon);
720 }
721 break;
722 case 'c':
723 if (output)
724 {
725 result = g_string_append(result, editor->name);
726 }
727 break;
728 case 'k':
729 if (output)
730 {
731 result = g_string_append(result, editor->file);
732 }
733 break;
542 case '%': 734 case '%':
543 /* %% = % escaping */ 735 /* %% = % escaping */
544 if (output) result = g_string_append_c(result, *p); 736 if (output) result = g_string_append_c(result, *p);
737 break;
738 case 'd':
739 case 'D':
740 case 'n':
741 case 'N':
742 case 'v':
743 case 'm':
744 /* deprecated according to spec, ignore */
545 break; 745 break;
546 default: 746 default:
547 flags |= EDITOR_ERROR_SYNTAX; 747 flags |= EDITOR_ERROR_SYNTAX;
548 goto err; 748 goto err;
549 } 749 }
550 if (extensions) g_free(extensions);
551 extensions = NULL;
552 } 750 }
553 p++; 751 p++;
554 } 752 }
555 753
556 if (output) *output = g_string_free(result, FALSE); 754 if (output) *output = g_string_free(result, FALSE);
561 if (output) 759 if (output)
562 { 760 {
563 g_string_free(result, TRUE); 761 g_string_free(result, TRUE);
564 *output = NULL; 762 *output = NULL;
565 } 763 }
566 if (extensions) g_free(extensions);
567 return flags; 764 return flags;
568 } 765 }
766
569 767
570 static void editor_child_exit_cb (GPid pid, gint status, gpointer data) 768 static void editor_child_exit_cb (GPid pid, gint status, gpointer data)
571 { 769 {
572 EditorData *ed = data; 770 EditorData *ed = data;
573 g_spawn_close_pid(pid); 771 g_spawn_close_pid(pid);
575 773
576 editor_command_next_finish(ed, status); 774 editor_command_next_finish(ed, status);
577 } 775 }
578 776
579 777
580 static gint editor_command_one(const gchar *template, GList *list, EditorData *ed) 778 static gint editor_command_one(const EditorDescription *editor, GList *list, EditorData *ed)
581 { 779 {
582 gchar *command; 780 gchar *command;
583 FileData *fd = list->data; 781 FileData *fd = list->data;
584 GPid pid; 782 GPid pid;
585 gint standard_output; 783 gint standard_output;
586 gint standard_error; 784 gint standard_error;
587 gboolean ok; 785 gboolean ok;
588 786
589 ed->pid = -1; 787 ed->pid = -1;
590 ed->flags = editor_command_parse(template, list, &command); 788 ed->flags = editor->flags | editor_command_parse(editor, list, &command);
591 789
592 ok = !(ed->flags & EDITOR_ERROR_MASK); 790 ok = !(ed->flags & EDITOR_ERROR_MASK);
593 791
594 if (ok) 792 if (ok)
595 { 793 {
615 args[n++] = options->shell.path; 813 args[n++] = options->shell.path;
616 if (options->shell.options && *options->shell.options) 814 if (options->shell.options && *options->shell.options)
617 args[n++] = options->shell.options; 815 args[n++] = options->shell.options;
618 args[n++] = command; 816 args[n++] = command;
619 args[n] = NULL; 817 args[n] = NULL;
818
819 if ((ed->flags & EDITOR_DEST) && fd->change && fd->change->dest) /* FIXME: error handling */
820 {
821 setenv("GEEQIE_DESTINATION", fd->change->dest, TRUE);
822 }
823 else
824 {
825 unsetenv("GEEQIE_DESTINATION");
826 }
620 827
621 ok = g_spawn_async_with_pipes(working_directory, args, NULL, 828 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
622 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */ 829 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
623 NULL, NULL, 830 NULL, NULL,
624 &pid, 831 &pid,
642 { 849 {
643 if (!ok) 850 if (!ok)
644 { 851 {
645 gchar *buf; 852 gchar *buf;
646 853
647 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), template); 854 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), editor->file);
648 editor_verbose_window_fill(ed->vd, buf, strlen(buf)); 855 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
649 g_free(buf); 856 g_free(buf);
650 857
651 } 858 }
652 else 859 else
692 { 899 {
693 editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running...")); 900 editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running..."));
694 } 901 }
695 ed->count++; 902 ed->count++;
696 903
697 error = editor_command_one(ed->command_template, ed->list, ed); 904 error = editor_command_one(ed->editor, ed->list, ed);
698 if (!error && ed->vd) 905 if (!error && ed->vd)
699 { 906 {
700 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) ); 907 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
701 if (ed->flags & EDITOR_FOR_EACH) 908 if (ed->flags & EDITOR_FOR_EACH)
702 { 909 {
799 void editor_skip(gpointer ed) 1006 void editor_skip(gpointer ed)
800 { 1007 {
801 editor_command_done(ed); 1008 editor_command_done(ed);
802 } 1009 }
803 1010
804 static gint editor_command_start(const gchar *template, const gchar *text, GList *list, EditorCallback cb, gpointer data) 1011 static gint editor_command_start(const EditorDescription *editor, const gchar *text, GList *list, EditorCallback cb, gpointer data)
805 { 1012 {
806 EditorData *ed; 1013 EditorData *ed;
807 gint flags = editor_command_parse(template, NULL, NULL); 1014 gint flags = editor->flags;
808 1015
809 if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK; 1016 if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK;
810 1017
811 ed = g_new0(EditorData, 1); 1018 ed = g_new0(EditorData, 1);
812 ed->list = filelist_copy(list); 1019 ed->list = filelist_copy(list);
813 ed->flags = flags; 1020 ed->flags = flags;
814 ed->command_template = g_strdup(template); 1021 ed->editor = editor;
815 ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list); 1022 ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list);
816 ed->count = 0; 1023 ed->count = 0;
817 ed->stopping = FALSE; 1024 ed->stopping = FALSE;
818 ed->callback = cb; 1025 ed->callback = cb;
819 ed->data = data; 1026 ed->data = data;
827 editor_command_next_start(ed); 1034 editor_command_next_start(ed);
828 /* errors from editor_command_next_start will be handled via callback */ 1035 /* errors from editor_command_next_start will be handled via callback */
829 return flags & EDITOR_ERROR_MASK; 1036 return flags & EDITOR_ERROR_MASK;
830 } 1037 }
831 1038
832 gboolean is_valid_editor_command(gint n) 1039 gboolean is_valid_editor_command(const gchar *key)
833 { 1040 {
834 return (n >= 0 && n < GQ_EDITOR_SLOTS 1041 if (!key) return FALSE;
835 && options->editor[n].command 1042 return g_hash_table_lookup(editors, key) != NULL;
836 && strlen(options->editor[n].command) > 0); 1043 }
837 } 1044
838 1045 gint start_editor_from_filelist_full(const gchar *key, GList *list, EditorCallback cb, gpointer data)
839 gint start_editor_from_filelist_full(gint n, GList *list, EditorCallback cb, gpointer data) 1046 {
840 {
841 gchar *command;
842 gint error; 1047 gint error;
1048 EditorDescription *editor;
1049 if (!key) return FALSE;
1050
1051 editor = g_hash_table_lookup(editors, key);
843 1052
844 if (!list) return FALSE; 1053 if (!list) return FALSE;
845 if (!is_valid_editor_command(n)) return FALSE; 1054 if (!editor) return FALSE;
846 1055
847 command = g_locale_from_utf8(options->editor[n].command, -1, NULL, NULL, NULL); 1056 error = editor_command_start(editor, editor->name, list, cb, data);
848 error = editor_command_start(command, options->editor[n].name, list, cb, data); 1057
849 g_free(command); 1058 if (error & EDITOR_ERROR_MASK)
850 1059 {
851 if (n < GQ_EDITOR_GENERIC_SLOTS && (error & EDITOR_ERROR_MASK)) 1060 gchar *text = g_strdup_printf(_("%s\n\"%s\""), editor_get_error_str(error), editor->file);
852 {
853 gchar *text = g_strdup_printf(_("%s\n#%d \"%s\":\n%s"), editor_get_error_str(error), n+1,
854 options->editor[n].name, options->editor[n].command);
855 1061
856 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL); 1062 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL);
857 g_free(text); 1063 g_free(text);
858 } 1064 }
859 1065
860 return error; 1066 return error;
861 } 1067 }
862 1068
863 gint start_editor_from_filelist(gint n, GList *list) 1069 gint start_editor_from_filelist(const gchar *key, GList *list)
864 { 1070 {
865 return start_editor_from_filelist_full(n, list, NULL, NULL); 1071 return start_editor_from_filelist_full(key, list, NULL, NULL);
866 } 1072 }
867 1073
868 gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data) 1074 gint start_editor_from_file_full(const gchar *key, FileData *fd, EditorCallback cb, gpointer data)
869 { 1075 {
870 GList *list; 1076 GList *list;
871 gint error; 1077 gint error;
872 1078
873 if (!fd) return FALSE; 1079 if (!fd) return FALSE;
874 1080
875 list = g_list_append(NULL, fd); 1081 list = g_list_append(NULL, fd);
876 error = start_editor_from_filelist_full(n, list, cb, data); 1082 error = start_editor_from_filelist_full(key, list, cb, data);
877 g_list_free(list); 1083 g_list_free(list);
878 return error; 1084 return error;
879 } 1085 }
880 1086
881 gint start_editor_from_file(gint n, FileData *fd) 1087 gint start_editor_from_file(const gchar *key, FileData *fd)
882 { 1088 {
883 return start_editor_from_file_full(n, fd, NULL, NULL); 1089 return start_editor_from_file_full(key, fd, NULL, NULL);
884 } 1090 }
885 1091
886 gint editor_window_flag_set(gint n) 1092 gint editor_window_flag_set(const gchar *key)
887 { 1093 {
888 if (!is_valid_editor_command(n)) return TRUE; 1094 EditorDescription *editor;
889 1095 if (!key) return TRUE;
890 return (editor_command_parse(options->editor[n].command, NULL, NULL) & EDITOR_KEEP_FS); 1096
891 } 1097 editor = g_hash_table_lookup(editors, key);
892 1098 if (!editor) return TRUE;
893 gint editor_is_filter(gint n) 1099
894 { 1100 return (editor->flags & EDITOR_KEEP_FS);
895 if (!is_valid_editor_command(n)) return FALSE; 1101 }
896 1102
897 return (editor_command_parse(options->editor[n].command, NULL, NULL) & EDITOR_DEST); 1103 gint editor_is_filter(const gchar *key)
1104 {
1105 EditorDescription *editor;
1106 if (!key) return TRUE;
1107
1108 editor = g_hash_table_lookup(editors, key);
1109 if (!editor) return TRUE;
1110
1111 return (editor->flags & EDITOR_DEST);
898 } 1112 }
899 1113
900 const gchar *editor_get_error_str(gint flags) 1114 const gchar *editor_get_error_str(gint flags)
901 { 1115 {
902 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty."); 1116 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
907 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status."); 1121 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
908 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped."); 1122 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
909 return _("Unknown error."); 1123 return _("Unknown error.");
910 } 1124 }
911 1125
912 const gchar *editor_get_name(gint n) 1126 const gchar *editor_get_name(const gchar *key)
913 { 1127 {
914 if (!is_valid_editor_command(n)) return NULL; 1128 EditorDescription *editor = g_hash_table_lookup(editors, key);
915 1129
916 if (options->editor[n].name && strlen(options->editor[n].name) > 0) 1130 if (!editor) return NULL;
917 return options->editor[n].name; 1131
918 1132 return editor->name;
919 return _("(unknown)");
920 } 1133 }
921 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */ 1134 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */