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 }