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"), "Geeqie", "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, "Geeqie: 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;
384 result = g_string_new("");
386 if (!template || template[0] == '\0')
388 flags |= EDITOR_ERROR_EMPTY;
399 flags |= EDITOR_KEEP_FS;
403 flags |= EDITOR_VERBOSE;
407 flags |= EDITOR_VERBOSE_MULTI;
419 if (output) result = g_string_append_c(result, *p);
428 /* for example "%f" or "%{.crw;.raw;.cr2}f" */
432 gchar *end = strchr(p, '}');
435 flags |= EDITOR_ERROR_SYNTAX;
439 extensions = g_strndup(p, end - p);
446 flags |= EDITOR_DEST;
448 flags |= EDITOR_FOR_EACH;
449 if (flags & EDITOR_SINGLE_COMMAND)
451 flags |= EDITOR_ERROR_INCOMPATIBLE;
456 /* use the first file from the list */
457 if (!list || !list->data)
459 flags |= EDITOR_ERROR_NO_FILE;
462 pathl = editor_command_path_parse((FileData *)list->data, (*p == 'd') ? PATH_DEST : PATH_FILE, extensions);
465 flags |= EDITOR_ERROR_NO_FILE;
468 result = g_string_append_c(result, '"');
469 result = g_string_append(result, pathl);
471 result = g_string_append_c(result, '"');
476 flags |= EDITOR_SINGLE_COMMAND;
477 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST))
479 flags |= EDITOR_ERROR_INCOMPATIBLE;
490 FileData *fd = work->data;
491 pathl = editor_command_path_parse(fd, PATH_FILE, extensions);
496 if (work != list) g_string_append_c(result, ' ');
497 result = g_string_append_c(result, '"');
498 result = g_string_append(result, pathl);
500 result = g_string_append_c(result, '"');
506 flags |= EDITOR_ERROR_NO_FILE;
512 flags |= EDITOR_ERROR_SYNTAX;
515 if (extensions) g_free(extensions);
521 if (output) *output = g_string_free(result, FALSE);
528 g_string_free(result, TRUE);
531 if (extensions) g_free(extensions);
535 static void editor_child_exit_cb (GPid pid, gint status, gpointer data)
537 EditorData *ed = data;
538 g_spawn_close_pid(pid);
541 editor_command_next_finish(ed, status);
545 static gint editor_command_one(const gchar *template, GList *list, EditorData *ed)
548 gchar *working_directory;
549 FileData *fd = list->data;
552 gint standard_output;
559 working_directory = remove_level_from_path(fd->path);
561 ed->flags = editor_command_parse(template, list, &command);
563 ok = !(ed->flags & EDITOR_ERROR_MASK);
566 args[0] = COMMAND_SHELL;
567 args[1] = COMMAND_OPT;
573 ok = g_spawn_async_with_pipes(working_directory, args, NULL,
574 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */
578 ed->vd ? &standard_output : NULL,
579 ed->vd ? &standard_error : NULL,
582 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC;
587 g_child_watch_add(pid, editor_child_exit_cb, ed);
599 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), template);
600 editor_verbose_window_fill(ed->vd, buf, strlen(buf));
607 GIOChannel *channel_output;
608 GIOChannel *channel_error;
609 channel_output = g_io_channel_unix_new(standard_output);
610 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL);
612 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
613 editor_verbose_io_cb, ed, NULL);
614 g_io_channel_unref(channel_output);
616 channel_error = g_io_channel_unix_new(standard_error);
617 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL);
619 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP,
620 editor_verbose_io_cb, ed, NULL);
621 g_io_channel_unref(channel_error);
628 g_free(working_directory);
630 return ed->flags & EDITOR_ERROR_MASK;
633 static gint editor_command_next_start(EditorData *ed)
636 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1);
638 if (ed->list && ed->count < ed->total)
647 editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running..."));
651 error = editor_command_one(ed->command_template, ed->list, ed);
652 if (!error && ed->vd)
654 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) );
655 if (ed->flags & EDITOR_FOR_EACH)
657 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path));
658 editor_verbose_window_fill(ed->vd, "\n", 1);
665 /* command was not started, call the finish immediately */
666 return editor_command_next_finish(ed, 0);
669 /* everything is done */
670 return editor_command_done(ed);
673 static gint editor_command_next_finish(EditorData *ed, gint status)
675 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE;
678 ed->flags |= EDITOR_ERROR_STATUS;
680 if (ed->flags & EDITOR_FOR_EACH)
682 /* handle the first element from the list */
683 GList *fd_element = ed->list;
684 ed->list = g_list_remove_link(ed->list, fd_element);
686 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data);
687 filelist_free(fd_element);
691 /* handle whole list */
693 cont = ed->callback(NULL, ed->flags, ed->list, ed->data);
694 filelist_free(ed->list);
698 if (cont == EDITOR_CB_SUSPEND)
699 return ed->flags & EDITOR_ERROR_MASK;
700 else if (cont == EDITOR_CB_SKIP)
701 return editor_command_done(ed);
703 return editor_command_next_start(ed);
707 static gint editor_command_done(EditorData *ed)
714 if (ed->count == ed->total)
720 text = _("stopped by user");
722 editor_verbose_window_progress(ed, text);
723 editor_verbose_window_enable_close(ed->vd);
726 /* free the not-handled items */
729 ed->flags |= EDITOR_ERROR_SKIPPED;
730 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data);
731 filelist_free(ed->list);
737 flags = ed->flags & EDITOR_ERROR_MASK;
739 if (!ed->vd) editor_data_free(ed);
744 void editor_resume(gpointer ed)
746 editor_command_next_start(ed);
748 void editor_skip(gpointer ed)
750 editor_command_done(ed);
753 static gint editor_command_start(const gchar *template, const gchar *text, GList *list, EditorCallback cb, gpointer data)
756 gint flags = editor_command_parse(template, NULL, NULL);
758 if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK;
760 ed = g_new0(EditorData, 1);
761 ed->list = filelist_copy(list);
763 ed->command_template = g_strdup(template);
764 ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list);
766 ed->stopping = FALSE;
770 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next)
771 flags |= EDITOR_VERBOSE;
774 if (flags & EDITOR_VERBOSE)
775 editor_verbose_window(ed, text);
777 editor_command_next_start(ed);
778 /* errors from editor_command_next_start will be handled via callback */
779 return flags & EDITOR_ERROR_MASK;
782 gint start_editor_from_filelist_full(gint n, GList *list, EditorCallback cb, gpointer data)
787 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS || !list ||
788 !editor_command[n] ||
789 strlen(editor_command[n]) == 0) return FALSE;
791 command = g_locale_from_utf8(editor_command[n], -1, NULL, NULL, NULL);
792 error = editor_command_start(command, editor_name[n], list, cb, data);
797 gint start_editor_from_filelist(gint n, GList *list)
799 return start_editor_from_filelist_full(n, list, NULL, NULL);
803 gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data)
808 if (!fd) return FALSE;
810 list = g_list_append(NULL, fd);
811 error = start_editor_from_filelist_full(n, list, cb, data);
816 gint start_editor_from_file(gint n, FileData *fd)
818 return start_editor_from_file_full(n, fd, NULL, NULL);
821 gint editor_window_flag_set(gint n)
823 if (n < 0 || n >= GQVIEW_EDITOR_SLOTS ||
824 !editor_command[n] ||
825 strlen(editor_command[n]) == 0) return TRUE;
827 return (editor_command_parse(editor_command[n], NULL, NULL) & EDITOR_KEEP_FS);
831 const gchar *editor_get_error_str(gint flags)
833 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty.");
834 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax.");
835 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros.");
836 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type.");
837 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor.");
838 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status.");
839 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped.");
840 return _("Unknown error.");