Mercurial > geeqie
comparison src/editors.c @ 9:d907d608745f
Sync to GQview 1.5.9 release.
########
DO NOT BASE ENHANCEMENTS OR TRANSLATION UPDATES ON CODE IN THIS CVS!
This CVS is never up to date with current development and is provided
solely for reference purposes, please use the latest official release
package when making any changes or translation updates.
########
author | gqview |
---|---|
date | Sat, 26 Feb 2005 00:13:35 +0000 |
parents | |
children | 9c0c402b0ef3 |
comparison
equal
deleted
inserted
replaced
8:e0d0593d519e | 9:d907d608745f |
---|---|
1 /* | |
2 * GQview | |
3 * (C) 2004 John Ellis | |
4 * | |
5 * Author: John Ellis | |
6 * | |
7 * This software is released under the GNU General Public License (GNU GPL). | |
8 * Please read the included file COPYING for more information. | |
9 * This software comes with no warranty of any kind, use at your own risk! | |
10 */ | |
11 | |
12 | |
13 #include "gqview.h" | |
14 #include "editors.h" | |
15 | |
16 #include "utilops.h" | |
17 #include "ui_fileops.h" | |
18 #include "ui_spinner.h" | |
19 #include "ui_utildlg.h" | |
20 | |
21 #include <errno.h> | |
22 | |
23 | |
24 #define EDITOR_WINDOW_WIDTH 500 | |
25 #define EDITOR_WINDOW_HEIGHT 300 | |
26 | |
27 #define COMMAND_SHELL "sh" | |
28 #define COMMAND_OPT "-c" | |
29 | |
30 | |
31 typedef struct _EditorVerboseData EditorVerboseData; | |
32 struct _EditorVerboseData { | |
33 int fd; | |
34 | |
35 GenericDialog *gd; | |
36 GtkWidget *button_close; | |
37 GtkWidget *button_stop; | |
38 GtkWidget *text; | |
39 GtkWidget *progress; | |
40 GtkWidget *spinner; | |
41 gint count; | |
42 gint total; | |
43 | |
44 gchar *command_template; | |
45 GList *list; | |
46 }; | |
47 | |
48 | |
49 static gchar *editor_slot_defaults[] = { | |
50 N_("The Gimp"), "gimp-remote -n %f", | |
51 N_("XV"), "xv %f", | |
52 N_("Xpaint"), "xpaint %f", | |
53 NULL, NULL, | |
54 NULL, NULL, | |
55 NULL, NULL, | |
56 NULL, NULL, | |
57 NULL, NULL, | |
58 N_("Rotate jpeg clockwise"), "%vif jpegtran -rotate 90 -copy all -outfile %p_tmp %p; then mv %p_tmp %p;else rm %p_tmp;fi", | |
59 N_("Rotate jpeg counterclockwise"), "%vif jpegtran -rotate 270 -copy all -outfile %p_tmp %p; then mv %p_tmp %p;else rm %p_tmp;fi", | |
60 NULL, NULL | |
61 }; | |
62 | |
63 | |
64 static void editor_verbose_window_progress(EditorVerboseData *vd, const gchar *text); | |
65 static gint editor_command_next(EditorVerboseData *vd); | |
66 | |
67 | |
68 /* | |
69 *----------------------------------------------------------------------------- | |
70 * external editor routines | |
71 *----------------------------------------------------------------------------- | |
72 */ | |
73 | |
74 void editor_reset_defaults(void) | |
75 { | |
76 gint i; | |
77 | |
78 for (i = 0; i < GQVIEW_EDITOR_SLOTS; i++) | |
79 { | |
80 g_free(editor_name[i]); | |
81 editor_name[i] = g_strdup(_(editor_slot_defaults[i * 2])); | |
82 g_free(editor_command[i]); | |
83 editor_command[i] = g_strdup(editor_slot_defaults[i * 2 + 1]); | |
84 } | |
85 } | |
86 | |
87 static void editor_verbose_window_close(GenericDialog *gd, gpointer data) | |
88 { | |
89 EditorVerboseData *vd = data; | |
90 | |
91 generic_dialog_close(gd); | |
92 g_free(vd->command_template); | |
93 g_free(vd); | |
94 } | |
95 | |
96 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data) | |
97 { | |
98 EditorVerboseData *vd = data; | |
99 | |
100 path_list_free(vd->list); | |
101 vd->list = NULL; | |
102 | |
103 vd->count = 0; | |
104 editor_verbose_window_progress(vd, _("stopping...")); | |
105 } | |
106 | |
107 static void editor_verbose_window_enable_close(EditorVerboseData *vd) | |
108 { | |
109 vd->gd->cancel_cb = editor_verbose_window_close; | |
110 | |
111 spinner_set_interval(vd->spinner, -1); | |
112 gtk_widget_set_sensitive(vd->button_stop, FALSE); | |
113 gtk_widget_set_sensitive(vd->button_close, TRUE); | |
114 } | |
115 | |
116 static EditorVerboseData *editor_verbose_window(const gchar *template, const gchar *text) | |
117 { | |
118 EditorVerboseData *vd; | |
119 GtkWidget *scrolled; | |
120 GtkWidget *hbox; | |
121 gchar *buf; | |
122 | |
123 vd = g_new0(EditorVerboseData, 1); | |
124 | |
125 vd->list = NULL; | |
126 vd->command_template = g_strdup(template); | |
127 vd->total = 0; | |
128 vd->count = 0; | |
129 vd->fd = -1; | |
130 | |
131 vd->gd = file_util_gen_dlg(_("Edit command results"), "GQview", "editor_results", | |
132 NULL, FALSE, | |
133 NULL, vd); | |
134 buf = g_strdup_printf(_("Output of %s"), text); | |
135 generic_dialog_add_message(vd->gd, NULL, buf, NULL); | |
136 g_free(buf); | |
137 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL, | |
138 editor_verbose_window_stop, FALSE); | |
139 gtk_widget_set_sensitive(vd->button_stop, FALSE); | |
140 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL, | |
141 editor_verbose_window_close, TRUE); | |
142 gtk_widget_set_sensitive(vd->button_close, FALSE); | |
143 | |
144 scrolled = gtk_scrolled_window_new(NULL, NULL); | |
145 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN); | |
146 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), | |
147 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); | |
148 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5); | |
149 gtk_widget_show(scrolled); | |
150 | |
151 vd->text = gtk_text_view_new(); | |
152 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE); | |
153 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT); | |
154 gtk_container_add(GTK_CONTAINER(scrolled), vd->text); | |
155 gtk_widget_show(vd->text); | |
156 | |
157 hbox = gtk_hbox_new(FALSE, 0); | |
158 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0); | |
159 gtk_widget_show(hbox); | |
160 | |
161 vd->progress = gtk_progress_bar_new(); | |
162 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0); | |
163 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0); | |
164 gtk_widget_show(vd->progress); | |
165 | |
166 vd->spinner = spinner_new(NULL, SPINNER_SPEED); | |
167 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0); | |
168 gtk_widget_show(vd->spinner); | |
169 | |
170 gtk_widget_show(vd->gd->dialog); | |
171 | |
172 return vd; | |
173 } | |
174 | |
175 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len) | |
176 { | |
177 GtkTextBuffer *buffer; | |
178 GtkTextIter iter; | |
179 | |
180 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text)); | |
181 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1); | |
182 gtk_text_buffer_insert(buffer, &iter, text, len); | |
183 } | |
184 | |
185 static void editor_verbose_window_progress(EditorVerboseData *vd, const gchar *text) | |
186 { | |
187 if (vd->total) | |
188 { | |
189 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), (double)vd->count / vd->total); | |
190 } | |
191 | |
192 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), (text) ? text : ""); | |
193 } | |
194 | |
195 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data) | |
196 { | |
197 EditorVerboseData *vd = data; | |
198 gchar buf[512]; | |
199 gsize count; | |
200 | |
201 switch (condition) | |
202 { | |
203 case G_IO_IN: | |
204 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL) | |
205 { | |
206 if (!g_utf8_validate(buf, count, NULL)) | |
207 { | |
208 gchar *utf8; | |
209 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL); | |
210 if (utf8) | |
211 { | |
212 editor_verbose_window_fill(vd, utf8, -1); | |
213 g_free(utf8); | |
214 } | |
215 else | |
216 { | |
217 editor_verbose_window_fill(vd, "GQview: Error converting text to valid utf8\n", -1); | |
218 } | |
219 } | |
220 else | |
221 { | |
222 editor_verbose_window_fill(vd, buf, count); | |
223 } | |
224 } | |
225 break; | |
226 case G_IO_ERR: | |
227 printf("Error reading from command\n"); | |
228 case G_IO_HUP: | |
229 if (debug) printf("Editor command HUP\n"); | |
230 default: | |
231 while (g_source_remove_by_user_data(vd)); | |
232 close(vd->fd); | |
233 vd->fd = -1; | |
234 editor_command_next(vd); | |
235 return FALSE; | |
236 break; | |
237 } | |
238 | |
239 return TRUE; | |
240 } | |
241 | |
242 static int command_pipe(char *command) | |
243 { | |
244 char *args[4]; | |
245 int fpipe[2]; | |
246 pid_t fpid; | |
247 | |
248 args[0] = COMMAND_SHELL; | |
249 args[1] = COMMAND_OPT; | |
250 args[2] = command; | |
251 args[3] = NULL; | |
252 | |
253 if (pipe(fpipe) < 0) | |
254 { | |
255 printf("pipe setup failed: %s\n", strerror(errno)); | |
256 return -1; | |
257 } | |
258 | |
259 fpid = fork(); | |
260 if (fpid < 0) | |
261 { | |
262 /* fork failed */ | |
263 printf("fork failed: %s\n", strerror(errno)); | |
264 } | |
265 else if (fpid == 0) | |
266 { | |
267 /* child */ | |
268 gchar *msg; | |
269 | |
270 dup2(fpipe[1], 1); | |
271 dup2(fpipe[1], 2); | |
272 close(fpipe[0]); | |
273 | |
274 execvp(args[0], args); | |
275 | |
276 msg = g_strdup_printf("Unable to exec command:\n%s\n\n%s\n", command, strerror(errno)); | |
277 write(1, msg, strlen(msg)); | |
278 | |
279 _exit(1); | |
280 } | |
281 else | |
282 { | |
283 /* parent */ | |
284 fcntl(fpipe[0], F_SETFL, O_NONBLOCK); | |
285 close(fpipe[1]); | |
286 | |
287 return fpipe[0]; | |
288 } | |
289 | |
290 return -1; | |
291 } | |
292 | |
293 static gint editor_verbose_start(EditorVerboseData *vd, gchar *command) | |
294 { | |
295 GIOChannel *channel; | |
296 int fd; | |
297 | |
298 fd = command_pipe(command); | |
299 if (fd < 0) | |
300 { | |
301 gchar *buf; | |
302 | |
303 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), command); | |
304 editor_verbose_window_fill(vd, buf, strlen(buf)); | |
305 g_free(buf); | |
306 | |
307 return FALSE; | |
308 } | |
309 | |
310 vd->fd = fd; | |
311 channel = g_io_channel_unix_new(fd); | |
312 | |
313 g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_IN, | |
314 editor_verbose_io_cb, vd, NULL); | |
315 g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_ERR, | |
316 editor_verbose_io_cb, vd, NULL); | |
317 g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_HUP, | |
318 editor_verbose_io_cb, vd, NULL); | |
319 g_io_channel_unref(channel); | |
320 | |
321 return TRUE; | |
322 } | |
323 | |
324 static gint editor_command_one(const gchar *template, const gchar *path, EditorVerboseData *vd) | |
325 { | |
326 GString *result = NULL; | |
327 gchar *pathl; | |
328 gchar *found; | |
329 const gchar *ptr; | |
330 gchar path_buffer[512]; | |
331 gchar *current_path; | |
332 gint path_change = FALSE; | |
333 gint ret; | |
334 | |
335 current_path = getcwd(path_buffer, sizeof(path_buffer)); | |
336 | |
337 result = g_string_new(""); | |
338 pathl = path_from_utf8(path); | |
339 | |
340 ptr = template; | |
341 while ( (found = strstr(ptr, "%p")) ) | |
342 { | |
343 result = g_string_append_len(result, ptr, found - ptr); | |
344 ptr = found + 2; | |
345 result = g_string_append_c(result, '"'); | |
346 result = g_string_append(result, pathl); | |
347 result = g_string_append_c(result, '"'); | |
348 } | |
349 result = g_string_append(result, ptr); | |
350 | |
351 if (debug) printf("system command: %s\n", result->str); | |
352 | |
353 if (current_path) | |
354 { | |
355 gchar *base; | |
356 base = remove_level_from_path(path); | |
357 if (chdir(base) == 0) path_change = TRUE; | |
358 g_free(base); | |
359 } | |
360 | |
361 if (vd) | |
362 { | |
363 result = g_string_append(result, " 2>&1"); | |
364 ret = editor_verbose_start(vd, result->str); | |
365 } | |
366 else | |
367 { | |
368 ret = system(result->str); | |
369 } | |
370 | |
371 if (path_change) chdir(current_path); | |
372 | |
373 g_string_free(result, TRUE); | |
374 g_free(pathl); | |
375 | |
376 return ret; | |
377 } | |
378 | |
379 static gint editor_command_next(EditorVerboseData *vd) | |
380 { | |
381 const gchar *text; | |
382 | |
383 editor_verbose_window_fill(vd, "\n", 1); | |
384 | |
385 while (vd->list) | |
386 { | |
387 gchar *path; | |
388 gint success; | |
389 | |
390 path = vd->list->data; | |
391 vd->list = g_list_remove(vd->list, path); | |
392 | |
393 editor_verbose_window_progress(vd, path); | |
394 | |
395 vd->count++; | |
396 success = editor_command_one(vd->command_template, path, vd); | |
397 if (success) | |
398 { | |
399 gtk_widget_set_sensitive(vd->button_stop, (vd->list != NULL) ); | |
400 editor_verbose_window_fill(vd, path, strlen(path)); | |
401 editor_verbose_window_fill(vd, "\n", 1); | |
402 } | |
403 | |
404 g_free(path); | |
405 if (success) return TRUE; | |
406 } | |
407 | |
408 if (vd->count == vd->total) | |
409 { | |
410 text = _("done"); | |
411 } | |
412 else | |
413 { | |
414 text = _("stopped by user"); | |
415 } | |
416 vd->count = 0; | |
417 editor_verbose_window_progress(vd, text); | |
418 editor_verbose_window_enable_close(vd); | |
419 return FALSE; | |
420 } | |
421 | |
422 static void editor_command_start(const gchar *template, const gchar *text, GList *list) | |
423 { | |
424 EditorVerboseData *vd; | |
425 | |
426 vd = editor_verbose_window(template, text); | |
427 vd->list = path_list_copy(list); | |
428 vd->total = g_list_length(list); | |
429 | |
430 editor_command_next(vd); | |
431 } | |
432 | |
433 static gint editor_line_break(const gchar *template, gchar **front, const gchar **end) | |
434 { | |
435 gchar *found; | |
436 | |
437 *front = g_strdup(template); | |
438 found = strstr(*front, "%f"); | |
439 | |
440 if (found) | |
441 { | |
442 *found = '\0'; | |
443 *end = found + 2; | |
444 return TRUE; | |
445 } | |
446 | |
447 *end = ""; | |
448 return FALSE; | |
449 } | |
450 | |
451 /* | |
452 * The supported macros for editor commands: | |
453 * | |
454 * %f first occurence replaced by quoted sequence of filenames, command is run once. | |
455 * only one occurence of this macro is supported. | |
456 * ([ls %f] results in [ls "file1" "file2" ... "lastfile"]) | |
457 * %p command is run for each filename in turn, each instance replaced with single filename. | |
458 * multiple occurences of this macro is supported for complex shell commands. | |
459 * This macro will BLOCK THE APPLICATION until it completes, since command is run once | |
460 * for every file in syncronous order. To avoid blocking add the %v macro, below. | |
461 * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"]) | |
462 * none if no macro is supplied, the result is equivalent to "command %f" | |
463 * ([ls] results in [ls "file1" "file2" ... "lastfile"]) | |
464 * | |
465 * Only one of the macros %f or %p may be used in a given commmand. | |
466 * | |
467 * %v must be the first two characters in a command, causes a window to display | |
468 * showing the output of the command(s). | |
469 * %V same as %v except in the case of %p only displays a window for multiple files, | |
470 * operating on a single file is suppresses the output dialog. | |
471 */ | |
472 static void editor_command_run(const gchar *template, const gchar *text, GList *list) | |
473 { | |
474 gint verbose = FALSE; | |
475 gint for_each = FALSE; | |
476 | |
477 if (!template || template[0] == '\0') return; | |
478 | |
479 for_each = (strstr(template, "%p") != NULL); | |
480 | |
481 if (strncmp(template, "%v", 2) == 0) | |
482 { | |
483 template += 2; | |
484 verbose = TRUE; | |
485 } | |
486 else if (strncmp(template, "%V", 2) == 0) | |
487 { | |
488 template += 2; | |
489 if (!for_each || list->next) verbose = TRUE; | |
490 } | |
491 | |
492 if (for_each) | |
493 { | |
494 if (verbose) | |
495 { | |
496 editor_command_start(template, text, list); | |
497 } | |
498 else | |
499 { | |
500 GList *work; | |
501 | |
502 work = list; | |
503 while (work) | |
504 { | |
505 gchar *path = work->data; | |
506 editor_command_one(template, path, NULL); | |
507 work = work->next; | |
508 } | |
509 } | |
510 } | |
511 else | |
512 { | |
513 gchar *front; | |
514 const gchar *end; | |
515 GList *work; | |
516 GString *result = NULL; | |
517 gint parser_match; | |
518 | |
519 parser_match = editor_line_break(template, &front, &end); | |
520 result = g_string_new((parser_match) ? "" : " "); | |
521 | |
522 work = list; | |
523 while (work) | |
524 { | |
525 gchar *path = work->data; | |
526 gchar *pathl; | |
527 | |
528 if (work != list) g_string_append_c(result, ' '); | |
529 result = g_string_append_c(result, '"'); | |
530 pathl = path_from_utf8(path); | |
531 result = g_string_append(result, pathl); | |
532 g_free(pathl); | |
533 result = g_string_append_c(result, '"'); | |
534 work = work->next; | |
535 } | |
536 | |
537 result = g_string_prepend(result, front); | |
538 result = g_string_append(result, end); | |
539 if (verbose) result = g_string_append(result, " 2>&1 "); | |
540 result = g_string_append(result, "&"); | |
541 | |
542 if (debug) printf("system command: %s\n", result->str); | |
543 | |
544 if (verbose) | |
545 { | |
546 EditorVerboseData *vd; | |
547 | |
548 vd = editor_verbose_window(template, text); | |
549 editor_verbose_window_progress(vd, _("running...")); | |
550 editor_verbose_start(vd, result->str); | |
551 } | |
552 else | |
553 { | |
554 system(result->str); | |
555 } | |
556 | |
557 g_free(front); | |
558 g_string_free(result, TRUE); | |
559 } | |
560 } | |
561 | |
562 void start_editor_from_path_list(gint n, GList *list) | |
563 { | |
564 gchar *command; | |
565 | |
566 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS || !list || | |
567 !editor_command[n] || | |
568 strlen(editor_command[n]) == 0) return; | |
569 | |
570 command = g_locale_from_utf8(editor_command[n], -1, NULL, NULL, NULL); | |
571 editor_command_run(command, editor_name[n], list); | |
572 g_free(command); | |
573 } | |
574 | |
575 void start_editor_from_file(gint n, const gchar *path) | |
576 { | |
577 GList *list; | |
578 | |
579 if (!path) return; | |
580 | |
581 list = g_list_append(NULL, (gchar *)path); | |
582 start_editor_from_path_list(n, list); | |
583 g_list_free(list); | |
584 } |