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!
17 #include "ui_fileops.h"
18 #include "ui_spinner.h"
19 #include "ui_utildlg.h"
24 #define EDITOR_WINDOW_WIDTH 500
25 #define EDITOR_WINDOW_HEIGHT 300
27 #define COMMAND_SHELL "sh"
28 #define COMMAND_OPT "-c"
31 typedef struct _EditorVerboseData EditorVerboseData;
32 struct _EditorVerboseData {
36 GtkWidget *button_close;
37 GtkWidget *button_stop;
44 gchar *command_template;
49 static gchar *editor_slot_defaults[GQVIEW_EDITOR_SLOTS * 2] = {
50 N_("The Gimp"), "gimp-remote -n %f",
52 N_("Xpaint"), "xpaint %f",
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",
61 "External Copy command", NULL,
62 "External Move command", NULL,
63 "External Rename command", NULL,
64 "External Delete command", NULL,
65 "External New Folder command", NULL
68 static void editor_verbose_window_progress(EditorVerboseData *vd, const gchar *text);
69 static gint editor_command_next(EditorVerboseData *vd);
73 *-----------------------------------------------------------------------------
74 * external editor routines
75 *-----------------------------------------------------------------------------
78 void editor_reset_defaults(void)
82 for (i = 0; i < GQVIEW_EDITOR_SLOTS; i++)
84 g_free(editor_name[i]);
85 editor_name[i] = g_strdup(_(editor_slot_defaults[i * 2]));
86 g_free(editor_command[i]);
87 editor_command[i] = g_strdup(editor_slot_defaults[i * 2 + 1]);
91 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
93 EditorVerboseData *vd = data;
95 generic_dialog_close(gd);
96 g_free(vd->command_template);
100 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
102 EditorVerboseData *vd = data;
104 path_list_free(vd->list);
108 editor_verbose_window_progress(vd, _("stopping..."));
111 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
113 vd->gd->cancel_cb = editor_verbose_window_close;
115 spinner_set_interval(vd->spinner, -1);
116 gtk_widget_set_sensitive(vd->button_stop, FALSE);
117 gtk_widget_set_sensitive(vd->button_close, TRUE);
120 static EditorVerboseData *editor_verbose_window(const gchar *template, const gchar *text)
122 EditorVerboseData *vd;
127 vd = g_new0(EditorVerboseData, 1);
130 vd->command_template = g_strdup(template);
135 vd->gd = file_util_gen_dlg(_("Edit command results"), "GQview", "editor_results",
138 buf = g_strdup_printf(_("Output of %s"), text);
139 generic_dialog_add_message(vd->gd, NULL, buf, NULL);
141 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
142 editor_verbose_window_stop, FALSE);
143 gtk_widget_set_sensitive(vd->button_stop, FALSE);
144 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
145 editor_verbose_window_close, TRUE);
146 gtk_widget_set_sensitive(vd->button_close, FALSE);
148 scrolled = gtk_scrolled_window_new(NULL, NULL);
149 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
150 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
151 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
152 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
153 gtk_widget_show(scrolled);
155 vd->text = gtk_text_view_new();
156 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
157 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
158 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
159 gtk_widget_show(vd->text);
161 hbox = gtk_hbox_new(FALSE, 0);
162 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
163 gtk_widget_show(hbox);
165 vd->progress = gtk_progress_bar_new();
166 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
167 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
168 gtk_widget_show(vd->progress);
170 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
171 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
172 gtk_widget_show(vd->spinner);
174 gtk_widget_show(vd->gd->dialog);
179 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
181 GtkTextBuffer *buffer;
184 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
185 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
186 gtk_text_buffer_insert(buffer, &iter, text, len);
189 static void editor_verbose_window_progress(EditorVerboseData *vd, const gchar *text)
193 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), (double)vd->count / vd->total);
196 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), (text) ? text : "");
199 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
201 EditorVerboseData *vd = data;
208 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
210 if (!g_utf8_validate(buf, count, NULL))
213 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
216 editor_verbose_window_fill(vd, utf8, -1);
221 editor_verbose_window_fill(vd, "GQview: Error converting text to valid utf8\n", -1);
226 editor_verbose_window_fill(vd, buf, count);
231 printf("Error reading from command\n");
233 if (debug) printf("Editor command HUP\n");
235 while (g_source_remove_by_user_data(vd));
238 editor_command_next(vd);
246 static int command_pipe(char *command)
252 args[0] = COMMAND_SHELL;
253 args[1] = COMMAND_OPT;
259 printf("pipe setup failed: %s\n", strerror(errno));
267 printf("fork failed: %s\n", strerror(errno));
278 execvp(args[0], args);
280 msg = g_strdup_printf("Unable to exec command:\n%s\n\n%s\n", command, strerror(errno));
281 write(1, msg, strlen(msg));
288 fcntl(fpipe[0], F_SETFL, O_NONBLOCK);
297 static gint editor_verbose_start(EditorVerboseData *vd, gchar *command)
302 fd = command_pipe(command);
307 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), command);
308 editor_verbose_window_fill(vd, buf, strlen(buf));
315 channel = g_io_channel_unix_new(fd);
317 g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_IN,
318 editor_verbose_io_cb, vd, NULL);
319 g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_ERR,
320 editor_verbose_io_cb, vd, NULL);
321 g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_HUP,
322 editor_verbose_io_cb, vd, NULL);
323 g_io_channel_unref(channel);
328 static gchar *editor_command_path_parse(const gchar *path)
334 string = g_string_new("");
338 /* must escape \, ", `, and $ to avoid problems,
339 * we assume system shell supports bash-like escaping
341 if (strchr("\\\"`$", *p) != NULL)
343 string = g_string_append_c(string, '\\');
345 string = g_string_append_c(string, *p);
349 pathl = path_from_utf8(string->str);
350 g_string_free(string, TRUE);
355 static gint editor_command_one(const gchar *template, const gchar *path, EditorVerboseData *vd)
357 GString *result = NULL;
361 gchar path_buffer[512];
363 gint path_change = FALSE;
366 current_path = getcwd(path_buffer, sizeof(path_buffer));
368 result = g_string_new("");
369 pathl = editor_command_path_parse(path);
372 while ( (found = strstr(ptr, "%p")) )
374 result = g_string_append_len(result, ptr, found - ptr);
376 result = g_string_append_c(result, '"');
377 result = g_string_append(result, pathl);
378 result = g_string_append_c(result, '"');
380 result = g_string_append(result, ptr);
382 if (debug) printf("system command: %s\n", result->str);
387 base = remove_level_from_path(path);
388 if (chdir(base) == 0) path_change = TRUE;
394 result = g_string_append(result, " 2>&1");
395 ret = editor_verbose_start(vd, result->str);
399 ret = system(result->str);
402 if (path_change) chdir(current_path);
404 g_string_free(result, TRUE);
410 static gint editor_command_next(EditorVerboseData *vd)
414 editor_verbose_window_fill(vd, "\n", 1);
421 path = vd->list->data;
422 vd->list = g_list_remove(vd->list, path);
424 editor_verbose_window_progress(vd, path);
427 success = editor_command_one(vd->command_template, path, vd);
430 gtk_widget_set_sensitive(vd->button_stop, (vd->list != NULL) );
431 editor_verbose_window_fill(vd, path, strlen(path));
432 editor_verbose_window_fill(vd, "\n", 1);
436 if (success) return TRUE;
439 if (vd->count == vd->total)
445 text = _("stopped by user");
448 editor_verbose_window_progress(vd, text);
449 editor_verbose_window_enable_close(vd);
453 static void editor_command_start(const gchar *template, const gchar *text, GList *list)
455 EditorVerboseData *vd;
457 vd = editor_verbose_window(template, text);
458 vd->list = path_list_copy(list);
459 vd->total = g_list_length(list);
461 editor_command_next(vd);
464 static gint editor_line_break(const gchar *template, gchar **front, const gchar **end)
468 *front = g_strdup(template);
469 found = strstr(*front, "%f");
483 * The supported macros for editor commands:
485 * %f first occurence replaced by quoted sequence of filenames, command is run once.
486 * only one occurence of this macro is supported.
487 * ([ls %f] results in [ls "file1" "file2" ... "lastfile"])
488 * %p command is run for each filename in turn, each instance replaced with single filename.
489 * multiple occurences of this macro is supported for complex shell commands.
490 * This macro will BLOCK THE APPLICATION until it completes, since command is run once
491 * for every file in syncronous order. To avoid blocking add the %v macro, below.
492 * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"])
493 * none if no macro is supplied, the result is equivalent to "command %f"
494 * ([ls] results in [ls "file1" "file2" ... "lastfile"])
496 * Only one of the macros %f or %p may be used in a given commmand.
498 * %v must be the first two characters[1] in a command, causes a window to display
499 * showing the output of the command(s).
500 * %V same as %v except in the case of %p only displays a window for multiple files,
501 * operating on a single file is suppresses the output dialog.
503 * %w must be first two characters in a command, presence will disable full screen
504 * from exiting upon invocation.
507 * [1] Note: %v,%V may also be preceded by "%w".
509 static gint editor_command_run(const gchar *template, const gchar *text, GList *list)
511 gint verbose = FALSE;
512 gint for_each = FALSE;
515 if (!template || template[0] == '\0') return;
517 for_each = (strstr(template, "%p") != NULL);
519 /* no window state change flag, skip */
520 if (strncmp(template, "%w", 2) == 0) template += 2;
522 if (strncmp(template, "%v", 2) == 0)
527 else if (strncmp(template, "%V", 2) == 0)
530 if (!for_each || list->next) verbose = TRUE;
537 editor_command_start(template, text, list);
546 gchar *path = work->data;
547 editor_command_one(template, path, NULL);
557 GString *result = NULL;
560 parser_match = editor_line_break(template, &front, &end);
561 result = g_string_new((parser_match) ? "" : " ");
566 gchar *path = work->data;
569 if (work != list) g_string_append_c(result, ' ');
570 result = g_string_append_c(result, '"');
571 pathl = editor_command_path_parse(path);
572 result = g_string_append(result, pathl);
574 result = g_string_append_c(result, '"');
578 result = g_string_prepend(result, front);
579 result = g_string_append(result, end);
580 if (verbose) result = g_string_append(result, " 2>&1 ");
581 result = g_string_append(result, "&");
583 if (debug) printf("system command: %s\n", result->str);
587 EditorVerboseData *vd;
589 vd = editor_verbose_window(template, text);
590 editor_verbose_window_progress(vd, _("running..."));
591 editor_verbose_start(vd, result->str);
595 int status = system(result->str);
596 /* FIXME: consistent return values */
597 if (!WIFEXITED(status) || WEXITSTATUS(status))
602 g_string_free(result, TRUE);
607 gint start_editor_from_path_list(gint n, GList *list)
612 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS || !list ||
613 !editor_command[n] ||
614 strlen(editor_command[n]) == 0) return FALSE;
616 command = g_locale_from_utf8(editor_command[n], -1, NULL, NULL, NULL);
617 ret = editor_command_run(command, editor_name[n], list);
622 gint start_editor_from_file(gint n, const gchar *path)
627 if (!path) return FALSE;
629 list = g_list_append(NULL, (gchar *)path);
630 ret = start_editor_from_path_list(n, list);
635 gint editor_window_flag_set(gint n)
637 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS ||
638 !editor_command[n] ||
639 strlen(editor_command[n]) == 0) return TRUE;
641 return (strncmp(editor_command[n], "%w", 2) == 0);