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"
26 #define EDITOR_WINDOW_WIDTH 500
27 #define EDITOR_WINDOW_HEIGHT 300
29 #define COMMAND_SHELL "/bin/sh"
30 #define COMMAND_OPT "-c"
33 typedef struct _EditorVerboseData EditorVerboseData;
34 struct _EditorVerboseData {
36 GtkWidget *button_close;
37 GtkWidget *button_stop;
43 typedef struct _EditorData EditorData;
47 gchar *command_template;
52 EditorVerboseData *vd;
53 EditorCallback callback;
58 static gchar *editor_slot_defaults[GQVIEW_EDITOR_SLOTS * 2] = {
59 N_("The Gimp"), "gimp-remote -n %f",
61 N_("Xpaint"), "xpaint %f",
67 N_("Rotate jpeg clockwise"), "%vif jpegtran -rotate 90 -copy all -outfile %p_tmp %p; then mv %p_tmp %p;else rm %p_tmp;fi",
68 N_("Rotate jpeg counterclockwise"), "%vif jpegtran -rotate 270 -copy all -outfile %p_tmp %p; then mv %p_tmp %p;else rm %p_tmp;fi",
72 "External Copy command", "%vset -x;cp %p %d",
73 "External Move command", "%vset -x;mv %p %d",
74 "External Rename command", "%vset -x;mv %p %d",
75 "External Delete command", "%vset -x;rm %f",
76 "External New Folder command", NULL
78 "External Copy command", NULL,
79 "External Move command", NULL,
80 "External Rename command", NULL,
81 "External Delete command", NULL,
82 "External New Folder command", NULL
86 static void editor_verbose_window_progress(EditorData *ed, const gchar *text);
87 static gint editor_command_next_start(EditorData *ed);
88 static gint editor_command_next_finish(EditorData *ed, gint status);
89 static gint editor_command_done(EditorData *ed);
92 *-----------------------------------------------------------------------------
93 * external editor routines
94 *-----------------------------------------------------------------------------
97 void editor_reset_defaults(void)
101 for (i = 0; i < GQVIEW_EDITOR_SLOTS; i++)
103 g_free(editor_name[i]);
104 editor_name[i] = g_strdup(_(editor_slot_defaults[i * 2]));
105 g_free(editor_command[i]);
106 editor_command[i] = g_strdup(editor_slot_defaults[i * 2 + 1]);
110 static void editor_verbose_data_free(EditorData *ed)
117 static void editor_data_free(EditorData *ed)
119 editor_verbose_data_free(ed);
120 g_free(ed->command_template);
124 static void editor_verbose_window_close(GenericDialog *gd, gpointer data)
126 EditorData *ed = data;
128 generic_dialog_close(gd);
129 editor_verbose_data_free(ed);
130 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */
133 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data)
135 EditorData *ed = data;
138 editor_verbose_window_progress(ed, _("stopping..."));
141 static void editor_verbose_window_enable_close(EditorVerboseData *vd)
143 vd->gd->cancel_cb = editor_verbose_window_close;
145 spinner_set_interval(vd->spinner, -1);
146 gtk_widget_set_sensitive(vd->button_stop, FALSE);
147 gtk_widget_set_sensitive(vd->button_close, TRUE);
150 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text)
152 EditorVerboseData *vd;
157 vd = g_new0(EditorVerboseData, 1);
159 vd->gd = file_util_gen_dlg(_("Edit command results"), "GQview", "editor_results",
162 buf = g_strdup_printf(_("Output of %s"), text);
163 generic_dialog_add_message(vd->gd, NULL, buf, NULL);
165 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
166 editor_verbose_window_stop, FALSE);
167 gtk_widget_set_sensitive(vd->button_stop, FALSE);
168 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL,
169 editor_verbose_window_close, TRUE);
170 gtk_widget_set_sensitive(vd->button_close, FALSE);
172 scrolled = gtk_scrolled_window_new(NULL, NULL);
173 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
174 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
175 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
176 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5);
177 gtk_widget_show(scrolled);
179 vd->text = gtk_text_view_new();
180 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE);
181 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT);
182 gtk_container_add(GTK_CONTAINER(scrolled), vd->text);
183 gtk_widget_show(vd->text);
185 hbox = gtk_hbox_new(FALSE, 0);
186 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0);
187 gtk_widget_show(hbox);
189 vd->progress = gtk_progress_bar_new();
190 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0);
191 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0);
192 gtk_widget_show(vd->progress);
194 vd->spinner = spinner_new(NULL, SPINNER_SPEED);
195 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0);
196 gtk_widget_show(vd->spinner);
198 gtk_widget_show(vd->gd->dialog);
204 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len)
206 GtkTextBuffer *buffer;
209 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text));
210 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1);
211 gtk_text_buffer_insert(buffer, &iter, text, len);
214 static void editor_verbose_window_progress(EditorData *ed, const gchar *text)
220 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (double)ed->count / ed->total);
223 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : "");
226 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data)
228 EditorData *ed = data;
232 if (condition & G_IO_IN)
234 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL)
236 if (!g_utf8_validate(buf, count, NULL))
239 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL);
242 editor_verbose_window_fill(ed->vd, utf8, -1);
247 editor_verbose_window_fill(ed->vd, "GQview: Error converting text to valid utf8\n", -1);
252 editor_verbose_window_fill(ed->vd, buf, count);
257 if (condition & (G_IO_ERR | G_IO_HUP))
259 g_io_channel_shutdown(source, TRUE, NULL);
272 static gchar *editor_command_path_parse(const FileData *fd, PathType type, const gchar *extensions)
278 string = g_string_new("");
280 if (type == PATH_FILE)
284 else if (type == PATH_DEST)
286 if (fd->change && fd->change->dest)
287 p = fd->change->dest;
293 /* must escape \, ", `, and $ to avoid problems,
294 * we assume system shell supports bash-like escaping
296 if (strchr("\\\"`$", *p) != NULL)
298 string = g_string_append_c(string, '\\');
300 string = g_string_append_c(string, *p);
304 pathl = path_from_utf8(string->str);
305 g_string_free(string, TRUE);
312 * The supported macros for editor commands:
314 * %f first occurence replaced by quoted sequence of filenames, command is run once.
315 * only one occurence of this macro is supported.
316 * ([ls %f] results in [ls "file1" "file2" ... "lastfile"])
317 * %p command is run for each filename in turn, each instance replaced with single filename.
318 * multiple occurences of this macro is supported for complex shell commands.
319 * This macro will BLOCK THE APPLICATION until it completes, since command is run once
320 * for every file in syncronous order. To avoid blocking add the %v macro, below.
321 * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"])
322 * none if no macro is supplied, the result is equivalent to "command %f"
323 * ([ls] results in [ls "file1" "file2" ... "lastfile"])
325 * Only one of the macros %f or %p may be used in a given commmand.
327 * %v must be the first two characters[1] in a command, causes a window to display
328 * showing the output of the command(s).
329 * %V same as %v except in the case of %p only displays a window for multiple files,
330 * operating on a single file is suppresses the output dialog.
332 * %w must be first two characters in a command, presence will disable full screen
333 * from exiting upon invocation.
336 * [1] Note: %v,%V may also be preceded by "%w".
340 gint editor_command_parse(const gchar *template, GList *list, gchar **output)
343 const gchar *p = template;
344 GString *result = NULL;
345 gchar *extensions = NULL;
349 result = g_string_new("");
351 if (!template || template[0] == '\0')
353 flags |= EDITOR_ERROR_EMPTY;
364 flags |= EDITOR_KEEP_FS;
368 flags |= EDITOR_VERBOSE;
372 flags |= EDITOR_VERBOSE_MULTI;
384 if (output) result = g_string_append_c(result, *p);
393 /* for example "%f" or "%{crw,raw,cr2}f" */
397 gchar *end = strchr(p, '}');
400 flags |= EDITOR_ERROR_SYNTAX;
404 extensions = g_strndup(p, end - p);
411 flags |= EDITOR_DEST;
413 flags |= EDITOR_FOR_EACH;
414 if (flags & EDITOR_SINGLE_COMMAND)
416 flags |= EDITOR_ERROR_INCOMPATIBLE;
421 /* use the first file from the list */
422 if (!list || !list->data)
424 flags |= EDITOR_ERROR_NO_FILE;
427 pathl = editor_command_path_parse((FileData *)list->data, (*p == 'd') ? PATH_DEST : PATH_FILE, extensions);
430 flags |= EDITOR_ERROR_NO_FILE;
433 result = g_string_append_c(result, '"');
434 result = g_string_append(result, pathl);
436 result = g_string_append_c(result, '"');
441 flags |= EDITOR_SINGLE_COMMAND;
442 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
444 flags |= EDITOR_ERROR_INCOMPATIBLE;
455 FileData *fd = work->data;
456 pathl = editor_command_path_parse(fd, PATH_FILE, extensions);
461 if (work != list) g_string_append_c(result, ' ');
462 result = g_string_append_c(result, '"');
463 result = g_string_append(result, pathl);
465 result = g_string_append_c(result, '"');
471 flags |= EDITOR_ERROR_NO_FILE;
477 flags |= EDITOR_ERROR_SYNTAX;
480 if (extensions) g_free(extensions);
486 if (output) *output = g_string_free(result, FALSE);
493 g_string_free(result, TRUE);
496 if (extensions) g_free(extensions);
500 static void editor_child_exit_cb (GPid pid, gint status, gpointer data)
502 EditorData *ed = data;
503 g_spawn_close_pid(pid);
506 editor_command_next_finish(ed, status);
510 static gint editor_command_one(const gchar *template, GList *list, EditorData *ed)
513 gchar *working_directory;
514 FileData *fd = list->data;
517 gint standard_output;
524 working_directory = remove_level_from_path(fd->path);
526 ed->flags = editor_command_parse(template, list, &command);
528 ok = !(ed->flags & EDITOR_ERROR_MASK);
531 args[0] = COMMAND_SHELL;
532 args[1] = COMMAND_OPT;
538 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
539 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
543 ed->vd ? &standard_output : NULL,
544 ed->vd ? &standard_error : NULL,
547 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
552 g_child_watch_add(pid, editor_child_exit_cb, ed);
564 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), command);
565 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
572 GIOChannel *channel_output;
573 GIOChannel *channel_error;
574 channel_output = g_io_channel_unix_new(standard_output);
575 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
577 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
578 editor_verbose_io_cb, ed, NULL);
579 g_io_channel_unref(channel_output);
581 channel_error = g_io_channel_unix_new(standard_error);
582 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
584 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
585 editor_verbose_io_cb, ed, NULL);
586 g_io_channel_unref(channel_error);
593 g_free(working_directory);
595 return ed->flags & EDITOR_ERROR_MASK;
598 static gint editor_command_next_start(EditorData *ed)
601 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
603 if (ed->list && ed->count < ed->total)
612 editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running..."));
616 error = editor_command_one(ed->command_template, ed->list, ed);
617 if (!error && ed->vd)
619 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
620 if (ed->flags & EDITOR_FOR_EACH)
622 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
623 editor_verbose_window_fill(ed->vd, "\n", 1);
630 /* command was not started, call the finish immediately */
631 return editor_command_next_finish(ed, 0);
634 /* everything is done */
635 editor_command_done(ed);
639 static gint editor_command_next_finish(EditorData *ed, gint status)
641 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
644 ed->flags |= EDITOR_ERROR_STATUS;
646 if (ed->flags & EDITOR_FOR_EACH)
648 /* handle the first element from the list */
649 GList *fd_element = ed->list;
650 ed->list = g_list_remove_link(ed->list, fd_element);
652 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
653 filelist_free(fd_element);
657 /* handle whole list */
659 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
660 filelist_free(ed->list);
664 if (cont == EDITOR_CB_SUSPEND)
665 return ed->flags & EDITOR_ERROR_MASK;
666 else if (cont == EDITOR_CB_SKIP)
667 return editor_command_done(ed);
669 return editor_command_next_start(ed);
673 static gint editor_command_done(EditorData *ed)
680 if (ed->count == ed->total)
686 text = _("stopped by user");
688 editor_verbose_window_progress(ed, text);
689 editor_verbose_window_enable_close(ed->vd);
692 /* free the not-handled items */
695 ed->flags |= EDITOR_ERROR_SKIPPED;
696 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
697 filelist_free(ed->list);
703 flags = ed->flags & EDITOR_ERROR_MASK;
705 if (!ed->vd) editor_data_free(ed);
710 void editor_resume(gpointer ed)
712 editor_command_next_start(ed);
714 void editor_skip(gpointer ed)
716 editor_command_done(ed);
719 static gint editor_command_start(const gchar *template, const gchar *text, GList *list, EditorCallback cb, gpointer data)
722 gint flags = editor_command_parse(template, NULL, NULL);
724 if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK;
726 ed = g_new0(EditorData, 1);
727 ed->list = filelist_copy(list);
729 ed->command_template = g_strdup(template);
730 ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list);
732 ed->stopping = FALSE;
736 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
737 flags |= EDITOR_VERBOSE;
740 if (flags & EDITOR_VERBOSE)
741 editor_verbose_window(ed, text);
743 editor_command_next_start(ed);
744 /* errors from editor_command_next_start will be handled via callback */
745 return flags & EDITOR_ERROR_MASK;
748 gint start_editor_from_filelist_full(gint n, GList *list, EditorCallback cb, gpointer data)
753 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS || !list ||
754 !editor_command[n] ||
755 strlen(editor_command[n]) == 0) return FALSE;
757 command = g_locale_from_utf8(editor_command[n], -1, NULL, NULL, NULL);
758 error = editor_command_start(command, editor_name[n], list, cb, data);
763 gint start_editor_from_filelist(gint n, GList *list)
765 return start_editor_from_filelist_full(n, list, NULL, NULL);
769 gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data)
774 if (!fd) return FALSE;
776 list = g_list_append(NULL, fd);
777 error = start_editor_from_filelist_full(n, list, cb, data);
782 gint start_editor_from_file(gint n, FileData *fd)
784 return start_editor_from_file_full(n, fd, NULL, NULL);
787 gint editor_window_flag_set(gint n)
789 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS ||
790 !editor_command[n] ||
791 strlen(editor_command[n]) == 0) return TRUE;
793 return (editor_command_parse(editor_command[n], NULL, NULL) & EDITOR_KEEP_FS);
797 const gchar *editor_get_error_str(gint flags)
799 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
800 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
801 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
802 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
803 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
804 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
805 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
806 return _("Unknown error.");