9
|
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 }
|