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 %{.cr2;.crw;.nef;.raw;*}f",
61 N_("Xpaint"), "xpaint %f",
62 N_("UFraw"), "ufraw %{.cr2;.crw;.nef;.raw}p",
63 N_("Add XMP sidecar"), "%vFILE=%{.cr2;.crw;.nef;.raw}p;XMP=`echo \"$FILE\"|sed -e 's|\\.[^.]*$|.xmp|'`; exiftool -tagsfromfile \"$FILE\" \"$XMP\"",
67 N_("Rotate jpeg clockwise"), "%vif jpegtran -rotate 90 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}p_tmp;fi",
68 N_("Rotate jpeg counterclockwise"), "%vif jpegtran -rotate 270 -copy all -outfile %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p; then mv %{.jpg;.jpeg}p_tmp %{.jpg;.jpeg}p;else rm %{.jpg;.jpeg}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)
276 const gchar *p = NULL;
278 string = g_string_new("");
280 if (type == PATH_FILE)
282 GList *ext_list = filter_to_list(extensions);
283 GList *work = ext_list;
291 gchar *ext = work->data;
294 if (strcmp(ext, "*") == 0 ||
295 strcasecmp(ext, fd->extension) == 0)
301 GList *work2 = fd->sidecar_files;
305 FileData *sfd = work2->data;
308 if (strcasecmp(ext, sfd->extension) == 0)
316 string_list_free(ext_list);
320 else if (type == PATH_DEST)
322 if (fd->change && fd->change->dest)
323 p = fd->change->dest;
329 /* must escape \, ", `, and $ to avoid problems,
330 * we assume system shell supports bash-like escaping
332 if (strchr("\\\"`$", *p) != NULL)
334 string = g_string_append_c(string, '\\');
336 string = g_string_append_c(string, *p);
340 pathl = path_from_utf8(string->str);
341 g_string_free(string, TRUE);
348 * The supported macros for editor commands:
350 * %f first occurence replaced by quoted sequence of filenames, command is run once.
351 * only one occurence of this macro is supported.
352 * ([ls %f] results in [ls "file1" "file2" ... "lastfile"])
353 * %p command is run for each filename in turn, each instance replaced with single filename.
354 * multiple occurences of this macro is supported for complex shell commands.
355 * This macro will BLOCK THE APPLICATION until it completes, since command is run once
356 * for every file in syncronous order. To avoid blocking add the %v macro, below.
357 * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"])
358 * none if no macro is supplied, the result is equivalent to "command %f"
359 * ([ls] results in [ls "file1" "file2" ... "lastfile"])
361 * Only one of the macros %f or %p may be used in a given commmand.
363 * %v must be the first two characters[1] in a command, causes a window to display
364 * showing the output of the command(s).
365 * %V same as %v except in the case of %p only displays a window for multiple files,
366 * operating on a single file is suppresses the output dialog.
368 * %w must be first two characters in a command, presence will disable full screen
369 * from exiting upon invocation.
372 * [1] Note: %v,%V may also be preceded by "%w".
376 gint editor_command_parse(const gchar *template, GList *list, gchar **output)
379 const gchar *p = template;
380 GString *result = NULL;
381 gchar *extensions = NULL;
385 result = g_string_new("");
387 if (!template || template[0] == '\0')
389 flags |= EDITOR_ERROR_EMPTY;
400 flags |= EDITOR_KEEP_FS;
404 flags |= EDITOR_VERBOSE;
408 flags |= EDITOR_VERBOSE_MULTI;
420 if (output) result = g_string_append_c(result, *p);
429 /* for example "%f" or "%{.crw;.raw;.cr2}f" */
433 gchar *end = strchr(p, '}');
436 flags |= EDITOR_ERROR_SYNTAX;
440 extensions = g_strndup(p, end - p);
447 flags |= EDITOR_DEST;
449 flags |= EDITOR_FOR_EACH;
450 if (flags & EDITOR_SINGLE_COMMAND)
452 flags |= EDITOR_ERROR_INCOMPATIBLE;
457 /* use the first file from the list */
458 if (!list || !list->data)
460 flags |= EDITOR_ERROR_NO_FILE;
463 pathl = editor_command_path_parse((FileData *)list->data, (*p == 'd') ? PATH_DEST : PATH_FILE, extensions);
466 flags |= EDITOR_ERROR_NO_FILE;
469 result = g_string_append_c(result, '"');
470 result = g_string_append(result, pathl);
472 result = g_string_append_c(result, '"');
477 flags |= EDITOR_SINGLE_COMMAND;
478 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
480 flags |= EDITOR_ERROR_INCOMPATIBLE;
491 FileData *fd = work->data;
492 pathl = editor_command_path_parse(fd, PATH_FILE, extensions);
497 if (work != list) g_string_append_c(result, ' ');
498 result = g_string_append_c(result, '"');
499 result = g_string_append(result, pathl);
501 result = g_string_append_c(result, '"');
507 flags |= EDITOR_ERROR_NO_FILE;
513 flags |= EDITOR_ERROR_SYNTAX;
516 if (extensions) g_free(extensions);
522 if (output) *output = g_string_free(result, FALSE);
529 g_string_free(result, TRUE);
532 if (extensions) g_free(extensions);
536 static void editor_child_exit_cb (GPid pid, gint status, gpointer data)
538 EditorData *ed = data;
539 g_spawn_close_pid(pid);
542 editor_command_next_finish(ed, status);
546 static gint editor_command_one(const gchar *template, GList *list, EditorData *ed)
549 gchar *working_directory;
550 FileData *fd = list->data;
553 gint standard_output;
560 working_directory = remove_level_from_path(fd->path);
562 ed->flags = editor_command_parse(template, list, &command);
564 ok = !(ed->flags & EDITOR_ERROR_MASK);
567 args[0] = COMMAND_SHELL;
568 args[1] = COMMAND_OPT;
574 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
575 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
579 ed->vd ? &standard_output : NULL,
580 ed->vd ? &standard_error : NULL,
583 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
588 g_child_watch_add(pid, editor_child_exit_cb, ed);
600 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), template);
601 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
608 GIOChannel *channel_output;
609 GIOChannel *channel_error;
610 channel_output = g_io_channel_unix_new(standard_output);
611 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
613 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
614 editor_verbose_io_cb, ed, NULL);
615 g_io_channel_unref(channel_output);
617 channel_error = g_io_channel_unix_new(standard_error);
618 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
620 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
621 editor_verbose_io_cb, ed, NULL);
622 g_io_channel_unref(channel_error);
629 g_free(working_directory);
631 return ed->flags & EDITOR_ERROR_MASK;
634 static gint editor_command_next_start(EditorData *ed)
637 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
639 if (ed->list && ed->count < ed->total)
648 editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running..."));
652 error = editor_command_one(ed->command_template, ed->list, ed);
653 if (!error && ed->vd)
655 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
656 if (ed->flags & EDITOR_FOR_EACH)
658 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
659 editor_verbose_window_fill(ed->vd, "\n", 1);
666 /* command was not started, call the finish immediately */
667 return editor_command_next_finish(ed, 0);
670 /* everything is done */
671 editor_command_done(ed);
675 static gint editor_command_next_finish(EditorData *ed, gint status)
677 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
680 ed->flags |= EDITOR_ERROR_STATUS;
682 if (ed->flags & EDITOR_FOR_EACH)
684 /* handle the first element from the list */
685 GList *fd_element = ed->list;
686 ed->list = g_list_remove_link(ed->list, fd_element);
688 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
689 filelist_free(fd_element);
693 /* handle whole list */
695 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
696 filelist_free(ed->list);
700 if (cont == EDITOR_CB_SUSPEND)
701 return ed->flags & EDITOR_ERROR_MASK;
702 else if (cont == EDITOR_CB_SKIP)
703 return editor_command_done(ed);
705 return editor_command_next_start(ed);
709 static gint editor_command_done(EditorData *ed)
716 if (ed->count == ed->total)
722 text = _("stopped by user");
724 editor_verbose_window_progress(ed, text);
725 editor_verbose_window_enable_close(ed->vd);
728 /* free the not-handled items */
731 ed->flags |= EDITOR_ERROR_SKIPPED;
732 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
733 filelist_free(ed->list);
739 flags = ed->flags & EDITOR_ERROR_MASK;
741 if (!ed->vd) editor_data_free(ed);
746 void editor_resume(gpointer ed)
748 editor_command_next_start(ed);
750 void editor_skip(gpointer ed)
752 editor_command_done(ed);
755 static gint editor_command_start(const gchar *template, const gchar *text, GList *list, EditorCallback cb, gpointer data)
758 gint flags = editor_command_parse(template, NULL, NULL);
760 if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK;
762 ed = g_new0(EditorData, 1);
763 ed->list = filelist_copy(list);
765 ed->command_template = g_strdup(template);
766 ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list);
768 ed->stopping = FALSE;
772 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
773 flags |= EDITOR_VERBOSE;
776 if (flags & EDITOR_VERBOSE)
777 editor_verbose_window(ed, text);
779 editor_command_next_start(ed);
780 /* errors from editor_command_next_start will be handled via callback */
781 return flags & EDITOR_ERROR_MASK;
784 gint start_editor_from_filelist_full(gint n, GList *list, EditorCallback cb, gpointer data)
789 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS || !list ||
790 !editor_command[n] ||
791 strlen(editor_command[n]) == 0) return FALSE;
793 command = g_locale_from_utf8(editor_command[n], -1, NULL, NULL, NULL);
794 error = editor_command_start(command, editor_name[n], list, cb, data);
799 gint start_editor_from_filelist(gint n, GList *list)
801 return start_editor_from_filelist_full(n, list, NULL, NULL);
805 gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data)
810 if (!fd) return FALSE;
812 list = g_list_append(NULL, fd);
813 error = start_editor_from_filelist_full(n, list, cb, data);
818 gint start_editor_from_file(gint n, FileData *fd)
820 return start_editor_from_file_full(n, fd, NULL, NULL);
823 gint editor_window_flag_set(gint n)
825 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS ||
826 !editor_command[n] ||
827 strlen(editor_command[n]) == 0) return TRUE;
829 return (editor_command_parse(editor_command[n], NULL, NULL) & EDITOR_KEEP_FS);
833 const gchar *editor_get_error_str(gint flags)
835 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
836 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
837 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
838 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
839 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
840 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
841 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
842 return _("Unknown error.");