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 %f",
64 "External Move command", "%vset -x;mv %f",
65 "External Rename command", "%vset -x;mv %f",
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 path_list_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);
337 static gchar *editor_command_path_parse(const gchar *path)
343 string = g_string_new("");
347 /* must escape \, ", `, and $ to avoid problems,
348 * we assume system shell supports bash-like escaping
350 if (strchr("\\\"`$", *p) != NULL)
352 string = g_string_append_c(string, '\\');
354 string = g_string_append_c(string, *p);
358 pathl = path_from_utf8(string->str);
359 g_string_free(string, TRUE);
364 static gint editor_command_one(const gchar *template, const gchar *path, EditorVerboseData *vd)
366 GString *result = NULL;
370 gchar path_buffer[512];
372 gint path_change = FALSE;
375 current_path = getcwd(path_buffer, sizeof(path_buffer));
377 result = g_string_new("");
378 pathl = editor_command_path_parse(path);
381 while ( (found = strstr(ptr, "%p")) )
383 result = g_string_append_len(result, ptr, found - ptr);
385 result = g_string_append_c(result, '"');
386 result = g_string_append(result, pathl);
387 result = g_string_append_c(result, '"');
389 result = g_string_append(result, ptr);
391 if (debug) printf("system command: %s\n", result->str);
396 base = remove_level_from_path(path);
397 if (chdir(base) == 0) path_change = TRUE;
403 result = g_string_append(result, " 2>&1");
404 ret = editor_verbose_start(vd, result->str);
408 ret = !system(result->str);
411 if (path_change) chdir(current_path);
413 g_string_free(result, TRUE);
419 static gint editor_command_next(EditorVerboseData *vd)
423 editor_verbose_window_fill(vd, "\n", 1);
430 path = vd->list->data;
431 vd->list = g_list_remove(vd->list, path);
433 editor_verbose_window_progress(vd, path);
436 success = editor_command_one(vd->command_template, path, vd);
439 gtk_widget_set_sensitive(vd->button_stop, (vd->list != NULL) );
440 editor_verbose_window_fill(vd, path, strlen(path));
441 editor_verbose_window_fill(vd, "\n", 1);
445 if (success) return TRUE;
448 if (vd->count == vd->total)
454 text = _("stopped by user");
457 editor_verbose_window_progress(vd, text);
458 editor_verbose_window_enable_close(vd);
462 static gint editor_command_start(const gchar *template, const gchar *text, GList *list)
464 EditorVerboseData *vd;
466 vd = editor_verbose_window(template, text);
467 vd->list = path_list_copy(list);
468 vd->total = g_list_length(list);
470 return editor_command_next(vd);
473 static gint editor_line_break(const gchar *template, gchar **front, const gchar **end)
477 *front = g_strdup(template);
478 found = strstr(*front, "%f");
492 * The supported macros for editor commands:
494 * %f first occurence replaced by quoted sequence of filenames, command is run once.
495 * only one occurence of this macro is supported.
496 * ([ls %f] results in [ls "file1" "file2" ... "lastfile"])
497 * %p command is run for each filename in turn, each instance replaced with single filename.
498 * multiple occurences of this macro is supported for complex shell commands.
499 * This macro will BLOCK THE APPLICATION until it completes, since command is run once
500 * for every file in syncronous order. To avoid blocking add the %v macro, below.
501 * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"])
502 * none if no macro is supplied, the result is equivalent to "command %f"
503 * ([ls] results in [ls "file1" "file2" ... "lastfile"])
505 * Only one of the macros %f or %p may be used in a given commmand.
507 * %v must be the first two characters[1] in a command, causes a window to display
508 * showing the output of the command(s).
509 * %V same as %v except in the case of %p only displays a window for multiple files,
510 * operating on a single file is suppresses the output dialog.
512 * %w must be first two characters in a command, presence will disable full screen
513 * from exiting upon invocation.
516 * [1] Note: %v,%V may also be preceded by "%w".
518 static gint editor_command_run(const gchar *template, const gchar *text, GList *list)
520 gint verbose = FALSE;
521 gint for_each = FALSE;
524 if (!template || template[0] == '\0') return;
526 for_each = (strstr(template, "%p") != NULL);
528 /* no window state change flag, skip */
529 if (strncmp(template, "%w", 2) == 0) template += 2;
531 if (strncmp(template, "%v", 2) == 0)
536 else if (strncmp(template, "%V", 2) == 0)
539 if (!for_each || list->next) verbose = TRUE;
546 editor_command_start(template, text, list);
555 gchar *path = work->data;
556 ret = editor_command_one(template, path, NULL);
566 GString *result = NULL;
569 parser_match = editor_line_break(template, &front, &end);
570 result = g_string_new((parser_match) ? "" : " ");
575 gchar *path = work->data;
578 if (work != list) g_string_append_c(result, ' ');
579 result = g_string_append_c(result, '"');
580 pathl = editor_command_path_parse(path);
581 result = g_string_append(result, pathl);
583 result = g_string_append_c(result, '"');
587 result = g_string_prepend(result, front);
588 result = g_string_append(result, end);
589 if (verbose) result = g_string_append(result, " 2>&1 ");
590 result = g_string_append(result, "&");
592 if (debug) printf("system command: %s\n", result->str);
596 EditorVerboseData *vd;
598 vd = editor_verbose_window(template, text);
599 editor_verbose_window_progress(vd, _("running..."));
600 ret = editor_verbose_start(vd, result->str);
604 ret = !system(result->str);
608 g_string_free(result, TRUE);
613 gint start_editor_from_path_list(gint n, GList *list)
618 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS || !list ||
619 !editor_command[n] ||
620 strlen(editor_command[n]) == 0) return FALSE;
622 command = g_locale_from_utf8(editor_command[n], -1, NULL, NULL, NULL);
623 ret = editor_command_run(command, editor_name[n], list);
628 gint start_editor_from_file(gint n, const gchar *path)
633 if (!path) return FALSE;
635 list = g_list_append(NULL, (gchar *)path);
636 ret = start_editor_from_path_list(n, list);
641 gint start_editor_from_pair(gint n, const gchar *source, const gchar *target)
646 if (!source) return FALSE;
647 if (!target) return FALSE;
649 list = g_list_append(NULL, (gchar *)source);
650 list = g_list_append(list, (gchar *)target);
651 ret = start_editor_from_path_list(n, list);
656 gint editor_window_flag_set(gint n)
658 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS ||
659 !editor_command[n] ||
660 strlen(editor_command[n]) == 0) return TRUE;
662 return (strncmp(editor_command[n], "%w", 2) == 0);