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",
63 "External Copy command", "%vset -x;cp %p %t",
64 "External Move command", "%vset -x;mv %p %t",
65 "External Rename command", "%vset -x;mv %p %t",
66 "External Delete command", "%vset -x;rm %f",
67 "External New Folder command", NULL
69 "External Copy command", NULL,
70 "External Move command", NULL,
71 "External Rename command", NULL,
72 "External Delete command", NULL,
73 "External New Folder command", NULL
77 static void editor_verbose_window_progress(EditorVerboseData *vd, const gchar *text);
78 static gint editor_command_next(EditorVerboseData *vd);
82 *-----------------------------------------------------------------------------
83 * external editor routines
84 *-----------------------------------------------------------------------------
87 void editor_reset_defaults(void)
91 for (i = 0; i < GQVIEW_EDITOR_SLOTS; i++)
93 g_free(editor_name[i]);
94 editor_name[i] = g_strdup(_(editor_slot_defaults[i * 2]));
95 g_free(editor_command[i]);
96 editor_command[i] = g_strdup(editor_slot_defaults[i * 2 + 1]);
100 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
102 EditorVerboseData *vd = data;
104 generic_dialog_close(gd);
105 g_free(vd->command_template);
109 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
111 EditorVerboseData *vd = data;
113 filelist_free(vd->list);
117 editor_verbose_window_progress(vd, _("stopping..."));
120 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
122 vd->gd->cancel_cb = editor_verbose_window_close;
124 spinner_set_interval(vd->spinner, -1);
125 gtk_widget_set_sensitive(vd->button_stop, FALSE);
126 gtk_widget_set_sensitive(vd->button_close, TRUE);
129 static EditorVerboseData *editor_verbose_window(const gchar *template, const gchar *text)
131 EditorVerboseData *vd;
136 vd = g_new0(EditorVerboseData, 1);
139 vd->command_template = g_strdup(template);
144 vd->gd = file_util_gen_dlg(_("Edit command results"), "GQview", "editor_results",
147 buf = g_strdup_printf(_("Output of %s"), text);
148 generic_dialog_add_message(vd->gd, NULL, buf, NULL);
150 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
151 editor_verbose_window_stop, FALSE);
152 gtk_widget_set_sensitive(vd->button_stop, FALSE);
153 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
154 editor_verbose_window_close, TRUE);
155 gtk_widget_set_sensitive(vd->button_close, FALSE);
157 scrolled = gtk_scrolled_window_new(NULL, NULL);
158 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
159 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
160 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
161 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
162 gtk_widget_show(scrolled);
164 vd->text = gtk_text_view_new();
165 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
166 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
167 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
168 gtk_widget_show(vd->text);
170 hbox = gtk_hbox_new(FALSE, 0);
171 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
172 gtk_widget_show(hbox);
174 vd->progress = gtk_progress_bar_new();
175 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
176 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
177 gtk_widget_show(vd->progress);
179 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
180 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
181 gtk_widget_show(vd->spinner);
183 gtk_widget_show(vd->gd->dialog);
188 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
190 GtkTextBuffer *buffer;
193 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
194 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
195 gtk_text_buffer_insert(buffer, &iter, text, len);
198 static void editor_verbose_window_progress(EditorVerboseData *vd, const gchar *text)
202 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), (double)vd->count / vd->total);
205 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(vd->progress), (text) ? text : "");
208 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
210 EditorVerboseData *vd = data;
217 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
219 if (!g_utf8_validate(buf, count, NULL))
222 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
225 editor_verbose_window_fill(vd, utf8, -1);
230 editor_verbose_window_fill(vd, "GQview: Error converting text to valid utf8\n", -1);
235 editor_verbose_window_fill(vd, buf, count);
240 printf("Error reading from command\n");
242 if (debug) printf("Editor command HUP\n");
244 while (g_source_remove_by_user_data(vd));
247 editor_command_next(vd);
255 static int command_pipe(char *command)
261 args[0] = COMMAND_SHELL;
262 args[1] = COMMAND_OPT;
268 printf("pipe setup failed: %s\n", strerror(errno));
276 printf("fork failed: %s\n", strerror(errno));
287 execvp(args[0], args);
289 msg = g_strdup_printf("Unable to exec command:\n%s\n\n%s\n", command, strerror(errno));
290 write(1, msg, strlen(msg));
297 fcntl(fpipe[0], F_SETFL, O_NONBLOCK);
306 static gint editor_verbose_start(EditorVerboseData *vd, gchar *command)
311 fd = command_pipe(command);
316 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), command);
317 editor_verbose_window_fill(vd, buf, strlen(buf));
324 channel = g_io_channel_unix_new(fd);
326 g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_IN,
327 editor_verbose_io_cb, vd, NULL);
328 g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_ERR,
329 editor_verbose_io_cb, vd, NULL);
330 g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, G_IO_HUP,
331 editor_verbose_io_cb, vd, NULL);
332 g_io_channel_unref(channel);
343 static gchar *editor_command_path_parse(const FileData *fd, PathType type)
349 string = g_string_new("");
351 if (type == PATH_FILE)
355 else if (type == PATH_TARGET)
357 if (fd->change && fd->change->dest)
358 p = fd->change->dest;
364 /* must escape \, ", `, and $ to avoid problems,
365 * we assume system shell supports bash-like escaping
367 if (strchr("\\\"`$", *p) != NULL)
369 string = g_string_append_c(string, '\\');
371 string = g_string_append_c(string, *p);
375 pathl = path_from_utf8(string->str);
376 g_string_free(string, TRUE);
381 static gint editor_command_one(const gchar *template, const FileData *fd, EditorVerboseData *vd)
383 GString *result = NULL;
384 gchar *pathl, *targetl;
387 gchar path_buffer[512];
389 gint path_change = FALSE;
392 current_path = getcwd(path_buffer, sizeof(path_buffer));
394 result = g_string_new("");
395 pathl = editor_command_path_parse(fd, PATH_FILE);
396 targetl = editor_command_path_parse(fd, PATH_TARGET);
399 while ( (found = strstr(ptr, "%")) )
401 result = g_string_append_len(result, ptr, found - ptr);
406 result = g_string_append_c(result, '"');
407 result = g_string_append(result, pathl);
408 result = g_string_append_c(result, '"');
411 result = g_string_append_c(result, '"');
412 result = g_string_append(result, targetl);
413 result = g_string_append_c(result, '"');
416 result = g_string_append_c(result, '%');
422 result = g_string_append(result, ptr);
424 if (debug) printf("system command: %s\n", result->str);
429 base = remove_level_from_path(fd->path);
430 if (chdir(base) == 0) path_change = TRUE;
436 result = g_string_append(result, " 2>&1");
437 ret = editor_verbose_start(vd, result->str);
441 ret = !system(result->str);
444 if (path_change) chdir(current_path);
446 g_string_free(result, TRUE);
453 static gint editor_command_next(EditorVerboseData *vd)
457 editor_verbose_window_fill(vd, "\n", 1);
465 vd->list = g_list_remove(vd->list, fd);
467 editor_verbose_window_progress(vd, fd->path);
470 success = editor_command_one(vd->command_template, fd, vd);
473 gtk_widget_set_sensitive(vd->button_stop, (vd->list != NULL) );
474 editor_verbose_window_fill(vd, fd->path, strlen(fd->path));
475 editor_verbose_window_fill(vd, "\n", 1);
479 if (success) return TRUE;
482 if (vd->count == vd->total)
488 text = _("stopped by user");
491 editor_verbose_window_progress(vd, text);
492 editor_verbose_window_enable_close(vd);
496 static gint editor_command_start(const gchar *template, const gchar *text, GList *list)
498 EditorVerboseData *vd;
500 vd = editor_verbose_window(template, text);
501 vd->list = filelist_copy(list);
502 vd->total = g_list_length(list);
504 return editor_command_next(vd);
507 static gint editor_line_break(const gchar *template, gchar **front, const gchar **end)
511 *front = g_strdup(template);
512 found = strstr(*front, "%f");
526 * The supported macros for editor commands:
528 * %f first occurence replaced by quoted sequence of filenames, command is run once.
529 * only one occurence of this macro is supported.
530 * ([ls %f] results in [ls "file1" "file2" ... "lastfile"])
531 * %p command is run for each filename in turn, each instance replaced with single filename.
532 * multiple occurences of this macro is supported for complex shell commands.
533 * This macro will BLOCK THE APPLICATION until it completes, since command is run once
534 * for every file in syncronous order. To avoid blocking add the %v macro, below.
535 * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"])
536 * none if no macro is supplied, the result is equivalent to "command %f"
537 * ([ls] results in [ls "file1" "file2" ... "lastfile"])
539 * Only one of the macros %f or %p may be used in a given commmand.
541 * %v must be the first two characters[1] in a command, causes a window to display
542 * showing the output of the command(s).
543 * %V same as %v except in the case of %p only displays a window for multiple files,
544 * operating on a single file is suppresses the output dialog.
546 * %w must be first two characters in a command, presence will disable full screen
547 * from exiting upon invocation.
550 * [1] Note: %v,%V may also be preceded by "%w".
552 static gint editor_command_run(const gchar *template, const gchar *text, GList *list)
554 gint verbose = FALSE;
555 gint for_each = FALSE;
558 if (!template || template[0] == '\0') return;
560 for_each = (strstr(template, "%p") != NULL);
562 /* no window state change flag, skip */
563 if (strncmp(template, "%w", 2) == 0) template += 2;
565 if (strncmp(template, "%v", 2) == 0)
570 else if (strncmp(template, "%V", 2) == 0)
573 if (!for_each || list->next) verbose = TRUE;
580 editor_command_start(template, text, list);
589 FileData *fd = work->data;
590 ret = editor_command_one(template, fd, NULL);
600 GString *result = NULL;
603 parser_match = editor_line_break(template, &front, &end);
604 result = g_string_new((parser_match) ? "" : " ");
609 FileData *fd = work->data;
612 if (work != list) g_string_append_c(result, ' ');
613 result = g_string_append_c(result, '"');
614 pathl = editor_command_path_parse(fd, PATH_FILE);
615 result = g_string_append(result, pathl);
617 result = g_string_append_c(result, '"');
621 result = g_string_prepend(result, front);
622 result = g_string_append(result, end);
623 if (verbose) result = g_string_append(result, " 2>&1 ");
624 result = g_string_append(result, "&");
626 if (debug) printf("system command: %s\n", result->str);
630 EditorVerboseData *vd;
632 vd = editor_verbose_window(template, text);
633 editor_verbose_window_progress(vd, _("running..."));
634 ret = editor_verbose_start(vd, result->str);
638 ret = !system(result->str);
642 g_string_free(result, TRUE);
647 gint start_editor_from_filelist(gint n, GList *list)
652 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS || !list ||
653 !editor_command[n] ||
654 strlen(editor_command[n]) == 0) return FALSE;
656 command = g_locale_from_utf8(editor_command[n], -1, NULL, NULL, NULL);
657 ret = editor_command_run(command, editor_name[n], list);
662 gint start_editor_from_file(gint n, FileData *fd)
667 if (!fd) return FALSE;
669 list = g_list_append(NULL, fd);
670 ret = start_editor_from_filelist(n, list);
675 gint start_editor_from_pair(gint n, const gchar *source, const gchar *target)
680 if (!source) return FALSE;
681 if (!target) return FALSE;
683 list = g_list_append(NULL, (gchar *)source);
684 list = g_list_append(list, (gchar *)target);
685 ret = start_editor_from_filelist(n, list);
690 gint editor_window_flag_set(gint n)
692 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS ||
693 !editor_command[n] ||
694 strlen(editor_command[n]) == 0) return TRUE;
696 return (strncmp(editor_command[n], "%w", 2) == 0);