Mercurial > geeqie.yaz
annotate src/editors.c @ 753:477f48ba28d8
rewritten utilops.h:
- better integration of external commands
- filter commands
author | nadvornik |
---|---|
date | Sat, 24 May 2008 22:44:18 +0000 |
parents | 8a8873e7a552 |
children | 7148e125bf23 |
rev | line source |
---|---|
9 | 1 /* |
196 | 2 * Geeqie |
123
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
3 * (C) 2006 John Ellis |
475 | 4 * Copyright (C) 2008 The Geeqie Team |
9 | 5 * |
6 * Author: John Ellis | |
7 * | |
8 * This software is released under the GNU General Public License (GNU GPL). | |
9 * Please read the included file COPYING for more information. | |
10 * This software comes with no warranty of any kind, use at your own risk! | |
11 */ | |
12 | |
13 | |
281 | 14 #include "main.h" |
9 | 15 #include "editors.h" |
16 | |
669 | 17 #include "filedata.h" |
18 #include "filefilter.h" | |
9 | 19 #include "utilops.h" |
20 #include "ui_fileops.h" | |
21 #include "ui_spinner.h" | |
22 #include "ui_utildlg.h" | |
23 | |
24 #include <errno.h> | |
25 | |
26 | |
27 #define EDITOR_WINDOW_WIDTH 500 | |
28 #define EDITOR_WINDOW_HEIGHT 300 | |
29 | |
30 | |
31 | |
32 typedef struct _EditorVerboseData EditorVerboseData; | |
33 struct _EditorVerboseData { | |
34 GenericDialog *gd; | |
35 GtkWidget *button_close; | |
36 GtkWidget *button_stop; | |
37 GtkWidget *text; | |
38 GtkWidget *progress; | |
39 GtkWidget *spinner; | |
140 | 40 }; |
41 | |
42 typedef struct _EditorData EditorData; | |
43 struct _EditorData { | |
44 gint flags; | |
45 GPid pid; | |
46 gchar *command_template; | |
47 GList *list; | |
9 | 48 gint count; |
49 gint total; | |
140 | 50 gboolean stopping; |
51 EditorVerboseData *vd; | |
52 EditorCallback callback; | |
53 gpointer data; | |
9 | 54 }; |
55 | |
56 | |
730 | 57 static Editor editor_slot_defaults[GQ_EDITOR_SLOTS] = { |
58 { N_("The Gimp"), "gimp-remote %{.cr2;.crw;.nef;.raw;*}f" }, | |
59 { N_("XV"), "xv %f" }, | |
60 { N_("Xpaint"), "xpaint %f" }, | |
61 { N_("UFraw"), "ufraw %{.cr2;.crw;.nef;.raw}p" }, | |
62 { N_("Add XMP sidecar"), "%vFILE=%{.cr2;.crw;.nef;.raw}p;XMP=`echo \"$FILE\"|sed -e 's|\\.[^.]*$|.xmp|'`; exiftool -tagsfromfile \"$FILE\" \"$XMP\"" }, | |
63 { NULL, NULL }, | |
64 { NULL, NULL }, | |
65 { NULL, NULL }, | |
66 { 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" }, | |
67 { 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" }, | |
134
9009856628f7
started implementation of external commands; external Delete should work
nadvornik
parents:
123
diff
changeset
|
68 /* special slots */ |
136 | 69 #if 1 |
70 /* for testing */ | |
730 | 71 { N_("External Copy command"), "%vset -x;cp %p %d" }, |
72 { N_("External Move command"), "%vset -x;mv %p %d" }, | |
73 { N_("External Rename command"), "%vset -x;mv %p %d" }, | |
74 { N_("External Delete command"), NULL }, | |
75 { N_("External New Folder command"), NULL }, | |
136 | 76 #else |
730 | 77 { N_("External Copy command"), NULL }, |
78 { N_("External Move command"), NULL }, | |
79 { N_("External Rename command"), NULL }, | |
80 { N_("External Delete command"), NULL }, | |
81 { N_("External New Folder command"), NULL }, | |
136 | 82 #endif |
9 | 83 }; |
84 | |
140 | 85 static void editor_verbose_window_progress(EditorData *ed, const gchar *text); |
86 static gint editor_command_next_start(EditorData *ed); | |
87 static gint editor_command_next_finish(EditorData *ed, gint status); | |
88 static gint editor_command_done(EditorData *ed); | |
9 | 89 |
90 /* | |
91 *----------------------------------------------------------------------------- | |
92 * external editor routines | |
93 *----------------------------------------------------------------------------- | |
94 */ | |
95 | |
96 void editor_reset_defaults(void) | |
97 { | |
98 gint i; | |
99 | |
283 | 100 for (i = 0; i < GQ_EDITOR_SLOTS; i++) |
9 | 101 { |
730 | 102 g_free(options->editor[i].name); |
103 options->editor[i].name = g_strdup(_(editor_slot_defaults[i].name)); | |
104 g_free(options->editor[i].command); | |
105 options->editor[i].command = g_strdup(editor_slot_defaults[i].command); | |
9 | 106 } |
107 } | |
108 | |
140 | 109 static void editor_verbose_data_free(EditorData *ed) |
110 { | |
111 if (!ed->vd) return; | |
112 g_free(ed->vd); | |
113 ed->vd = NULL; | |
114 } | |
115 | |
116 static void editor_data_free(EditorData *ed) | |
117 { | |
118 editor_verbose_data_free(ed); | |
119 g_free(ed->command_template); | |
120 g_free(ed); | |
121 } | |
122 | |
9 | 123 static void editor_verbose_window_close(GenericDialog *gd, gpointer data) |
124 { | |
140 | 125 EditorData *ed = data; |
9 | 126 |
127 generic_dialog_close(gd); | |
140 | 128 editor_verbose_data_free(ed); |
129 if (ed->pid == -1) editor_data_free(ed); /* the process has already terminated */ | |
9 | 130 } |
131 | |
132 static void editor_verbose_window_stop(GenericDialog *gd, gpointer data) | |
133 { | |
140 | 134 EditorData *ed = data; |
135 ed->stopping = TRUE; | |
136 ed->count = 0; | |
137 editor_verbose_window_progress(ed, _("stopping...")); | |
9 | 138 } |
139 | |
140 static void editor_verbose_window_enable_close(EditorVerboseData *vd) | |
141 { | |
142 vd->gd->cancel_cb = editor_verbose_window_close; | |
143 | |
144 spinner_set_interval(vd->spinner, -1); | |
145 gtk_widget_set_sensitive(vd->button_stop, FALSE); | |
146 gtk_widget_set_sensitive(vd->button_close, TRUE); | |
147 } | |
148 | |
140 | 149 static EditorVerboseData *editor_verbose_window(EditorData *ed, const gchar *text) |
9 | 150 { |
151 EditorVerboseData *vd; | |
152 GtkWidget *scrolled; | |
153 GtkWidget *hbox; | |
154 gchar *buf; | |
155 | |
156 vd = g_new0(EditorVerboseData, 1); | |
157 | |
254
9faf34f047b1
Make the wmclass value unique among the code by defining
zas_
parents:
238
diff
changeset
|
158 vd->gd = file_util_gen_dlg(_("Edit command results"), GQ_WMCLASS, "editor_results", |
9 | 159 NULL, FALSE, |
140 | 160 NULL, ed); |
9 | 161 buf = g_strdup_printf(_("Output of %s"), text); |
162 generic_dialog_add_message(vd->gd, NULL, buf, NULL); | |
163 g_free(buf); | |
164 vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL, | |
165 editor_verbose_window_stop, FALSE); | |
166 gtk_widget_set_sensitive(vd->button_stop, FALSE); | |
167 vd->button_close = generic_dialog_add_button(vd->gd, GTK_STOCK_CLOSE, NULL, | |
168 editor_verbose_window_close, TRUE); | |
169 gtk_widget_set_sensitive(vd->button_close, FALSE); | |
170 | |
171 scrolled = gtk_scrolled_window_new(NULL, NULL); | |
172 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN); | |
173 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), | |
174 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); | |
175 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), scrolled, TRUE, TRUE, 5); | |
176 gtk_widget_show(scrolled); | |
177 | |
178 vd->text = gtk_text_view_new(); | |
179 gtk_text_view_set_editable(GTK_TEXT_VIEW(vd->text), FALSE); | |
180 gtk_widget_set_size_request(vd->text, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT); | |
181 gtk_container_add(GTK_CONTAINER(scrolled), vd->text); | |
182 gtk_widget_show(vd->text); | |
183 | |
184 hbox = gtk_hbox_new(FALSE, 0); | |
185 gtk_box_pack_start(GTK_BOX(vd->gd->vbox), hbox, FALSE, FALSE, 0); | |
186 gtk_widget_show(hbox); | |
187 | |
188 vd->progress = gtk_progress_bar_new(); | |
189 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(vd->progress), 0.0); | |
190 gtk_box_pack_start(GTK_BOX(hbox), vd->progress, TRUE, TRUE, 0); | |
191 gtk_widget_show(vd->progress); | |
192 | |
193 vd->spinner = spinner_new(NULL, SPINNER_SPEED); | |
194 gtk_box_pack_start(GTK_BOX(hbox), vd->spinner, FALSE, FALSE, 0); | |
195 gtk_widget_show(vd->spinner); | |
442 | 196 |
9 | 197 gtk_widget_show(vd->gd->dialog); |
198 | |
140 | 199 ed->vd = vd; |
9 | 200 return vd; |
201 } | |
202 | |
203 static void editor_verbose_window_fill(EditorVerboseData *vd, gchar *text, gint len) | |
204 { | |
205 GtkTextBuffer *buffer; | |
206 GtkTextIter iter; | |
207 | |
208 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(vd->text)); | |
209 gtk_text_buffer_get_iter_at_offset(buffer, &iter, -1); | |
210 gtk_text_buffer_insert(buffer, &iter, text, len); | |
211 } | |
212 | |
140 | 213 static void editor_verbose_window_progress(EditorData *ed, const gchar *text) |
9 | 214 { |
140 | 215 if (!ed->vd) return; |
216 | |
217 if (ed->total) | |
9 | 218 { |
140 | 219 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ed->vd->progress), (double)ed->count / ed->total); |
9 | 220 } |
221 | |
140 | 222 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ed->vd->progress), (text) ? text : ""); |
9 | 223 } |
224 | |
225 static gboolean editor_verbose_io_cb(GIOChannel *source, GIOCondition condition, gpointer data) | |
226 { | |
140 | 227 EditorData *ed = data; |
9 | 228 gchar buf[512]; |
229 gsize count; | |
230 | |
140 | 231 if (condition & G_IO_IN) |
9 | 232 { |
140 | 233 while (g_io_channel_read_chars(source, buf, sizeof(buf), &count, NULL) == G_IO_STATUS_NORMAL) |
234 { | |
235 if (!g_utf8_validate(buf, count, NULL)) | |
9 | 236 { |
140 | 237 gchar *utf8; |
444 | 238 |
140 | 239 utf8 = g_locale_to_utf8(buf, count, NULL, NULL, NULL); |
240 if (utf8) | |
9 | 241 { |
140 | 242 editor_verbose_window_fill(ed->vd, utf8, -1); |
243 g_free(utf8); | |
9 | 244 } |
245 else | |
246 { | |
288
d1f74154463e
Replace occurences of Geeqie / geeqie by constants defined in main.h.
zas_
parents:
283
diff
changeset
|
247 editor_verbose_window_fill(ed->vd, "Error converting text to valid utf8\n", -1); |
9 | 248 } |
249 } | |
140 | 250 else |
251 { | |
252 editor_verbose_window_fill(ed->vd, buf, count); | |
253 } | |
254 } | |
9 | 255 } |
256 | |
140 | 257 if (condition & (G_IO_ERR | G_IO_HUP)) |
9 | 258 { |
140 | 259 g_io_channel_shutdown(source, TRUE, NULL); |
9 | 260 return FALSE; |
261 } | |
262 | |
263 return TRUE; | |
264 } | |
265 | |
138 | 266 typedef enum { |
267 PATH_FILE, | |
140 | 268 PATH_DEST |
138 | 269 } PathType; |
270 | |
271 | |
140 | 272 static gchar *editor_command_path_parse(const FileData *fd, PathType type, const gchar *extensions) |
123
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
273 { |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
274 GString *string; |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
275 gchar *pathl; |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
276 const gchar *p = NULL; |
123
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
277 |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
278 string = g_string_new(""); |
442 | 279 |
138 | 280 if (type == PATH_FILE) |
281 { | |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
282 GList *ext_list = filter_to_list(extensions); |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
283 GList *work = ext_list; |
442 | 284 |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
285 if (!work) |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
286 p = fd->path; |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
287 else |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
288 { |
516 | 289 while (work) |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
290 { |
444 | 291 GList *work2; |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
292 gchar *ext = work->data; |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
293 work = work->next; |
442 | 294 |
295 if (strcmp(ext, "*") == 0 || | |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
296 strcasecmp(ext, fd->extension) == 0) |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
297 { |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
298 p = fd->path; |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
299 break; |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
300 } |
442 | 301 |
444 | 302 work2 = fd->sidecar_files; |
516 | 303 while (work2) |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
304 { |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
305 FileData *sfd = work2->data; |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
306 work2 = work2->next; |
442 | 307 |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
308 if (strcasecmp(ext, sfd->extension) == 0) |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
309 { |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
310 p = sfd->path; |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
311 break; |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
312 } |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
313 } |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
314 if (p) break; |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
315 } |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
316 string_list_free(ext_list); |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
317 if (!p) return NULL; |
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
318 } |
138 | 319 } |
140 | 320 else if (type == PATH_DEST) |
138 | 321 { |
322 if (fd->change && fd->change->dest) | |
323 p = fd->change->dest; | |
324 else | |
325 p = ""; | |
326 } | |
444 | 327 |
123
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
328 while (*p != '\0') |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
329 { |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
330 /* must escape \, ", `, and $ to avoid problems, |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
331 * we assume system shell supports bash-like escaping |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
332 */ |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
333 if (strchr("\\\"`$", *p) != NULL) |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
334 { |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
335 string = g_string_append_c(string, '\\'); |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
336 } |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
337 string = g_string_append_c(string, *p); |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
338 p++; |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
339 } |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
340 |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
341 pathl = path_from_utf8(string->str); |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
342 g_string_free(string, TRUE); |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
343 |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
344 return pathl; |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
345 } |
3602a4aa7c71
Sat Dec 2 20:15:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
60
diff
changeset
|
346 |
9 | 347 |
348 /* | |
349 * The supported macros for editor commands: | |
350 * | |
351 * %f first occurence replaced by quoted sequence of filenames, command is run once. | |
352 * only one occurence of this macro is supported. | |
353 * ([ls %f] results in [ls "file1" "file2" ... "lastfile"]) | |
354 * %p command is run for each filename in turn, each instance replaced with single filename. | |
355 * multiple occurences of this macro is supported for complex shell commands. | |
356 * This macro will BLOCK THE APPLICATION until it completes, since command is run once | |
357 * for every file in syncronous order. To avoid blocking add the %v macro, below. | |
358 * ([ls %p] results in [ls "file1"], [ls "file2"] ... [ls "lastfile"]) | |
359 * none if no macro is supplied, the result is equivalent to "command %f" | |
360 * ([ls] results in [ls "file1" "file2" ... "lastfile"]) | |
361 * | |
362 * Only one of the macros %f or %p may be used in a given commmand. | |
363 * | |
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
364 * %v must be the first two characters[1] in a command, causes a window to display |
9 | 365 * showing the output of the command(s). |
366 * %V same as %v except in the case of %p only displays a window for multiple files, | |
367 * operating on a single file is suppresses the output dialog. | |
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
368 * |
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
369 * %w must be first two characters in a command, presence will disable full screen |
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
370 * from exiting upon invocation. |
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
371 * |
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
372 * |
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
373 * [1] Note: %v,%V may also be preceded by "%w". |
9 | 374 */ |
140 | 375 |
376 | |
377 gint editor_command_parse(const gchar *template, GList *list, gchar **output) | |
9 | 378 { |
140 | 379 gint flags = 0; |
380 const gchar *p = template; | |
381 GString *result = NULL; | |
382 gchar *extensions = NULL; | |
442 | 383 |
140 | 384 if (output) |
385 result = g_string_new(""); | |
386 | |
442 | 387 if (!template || template[0] == '\0') |
140 | 388 { |
389 flags |= EDITOR_ERROR_EMPTY; | |
390 goto err; | |
391 } | |
669 | 392 |
393 /* skip leading whitespaces if any */ | |
394 while (g_ascii_isspace(*p)) p++; | |
442 | 395 |
140 | 396 /* global flags */ |
397 while (*p == '%') | |
398 { | |
399 switch (*++p) | |
400 { | |
401 case 'w': | |
402 flags |= EDITOR_KEEP_FS; | |
403 p++; | |
404 break; | |
405 case 'v': | |
406 flags |= EDITOR_VERBOSE; | |
407 p++; | |
408 break; | |
409 case 'V': | |
410 flags |= EDITOR_VERBOSE_MULTI; | |
411 p++; | |
412 break; | |
669 | 413 default: |
414 flags |= EDITOR_ERROR_SYNTAX; | |
415 goto err; | |
140 | 416 } |
417 } | |
442 | 418 |
669 | 419 /* skip whitespaces if any */ |
420 while (g_ascii_isspace(*p)) p++; | |
421 | |
140 | 422 /* command */ |
442 | 423 |
140 | 424 while (*p) |
425 { | |
426 if (*p != '%') | |
427 { | |
428 if (output) result = g_string_append_c(result, *p); | |
429 } | |
430 else /* *p == '%' */ | |
431 { | |
432 extensions = NULL; | |
433 gchar *pathl = NULL; | |
9 | 434 |
140 | 435 p++; |
442 | 436 |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
437 /* for example "%f" or "%{.crw;.raw;.cr2}f" */ |
140 | 438 if (*p == '{') |
439 { | |
444 | 440 gchar *end; |
441 | |
140 | 442 p++; |
444 | 443 end = strchr(p, '}'); |
140 | 444 if (!end) |
445 { | |
446 flags |= EDITOR_ERROR_SYNTAX; | |
447 goto err; | |
448 } | |
442 | 449 |
140 | 450 extensions = g_strndup(p, end - p); |
451 p = end + 1; | |
452 } | |
442 | 453 |
454 switch (*p) | |
140 | 455 { |
456 case 'd': | |
457 flags |= EDITOR_DEST; | |
669 | 458 /* fall through */ |
140 | 459 case 'p': |
460 flags |= EDITOR_FOR_EACH; | |
461 if (flags & EDITOR_SINGLE_COMMAND) | |
462 { | |
463 flags |= EDITOR_ERROR_INCOMPATIBLE; | |
464 goto err; | |
465 } | |
466 if (output) | |
467 { | |
468 /* use the first file from the list */ | |
442 | 469 if (!list || !list->data) |
140 | 470 { |
471 flags |= EDITOR_ERROR_NO_FILE; | |
472 goto err; | |
473 } | |
669 | 474 pathl = editor_command_path_parse((FileData *)list->data, |
475 (flags & EDITOR_DEST) ? PATH_DEST : PATH_FILE, | |
476 extensions); | |
442 | 477 if (!pathl) |
140 | 478 { |
479 flags |= EDITOR_ERROR_NO_FILE; | |
480 goto err; | |
481 } | |
482 result = g_string_append_c(result, '"'); | |
483 result = g_string_append(result, pathl); | |
484 g_free(pathl); | |
485 result = g_string_append_c(result, '"'); | |
486 } | |
442 | 487 break; |
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
488 |
140 | 489 case 'f': |
490 flags |= EDITOR_SINGLE_COMMAND; | |
491 if (flags & (EDITOR_FOR_EACH | EDITOR_DEST)) | |
492 { | |
493 flags |= EDITOR_ERROR_INCOMPATIBLE; | |
494 goto err; | |
495 } | |
496 | |
497 if (output) | |
498 { | |
499 /* use whole list */ | |
500 GList *work = list; | |
501 gboolean ok = FALSE; | |
444 | 502 |
140 | 503 while (work) |
504 { | |
505 FileData *fd = work->data; | |
506 pathl = editor_command_path_parse(fd, PATH_FILE, extensions); | |
507 | |
508 if (pathl) | |
509 { | |
510 ok = TRUE; | |
511 if (work != list) g_string_append_c(result, ' '); | |
512 result = g_string_append_c(result, '"'); | |
513 result = g_string_append(result, pathl); | |
514 g_free(pathl); | |
515 result = g_string_append_c(result, '"'); | |
516 } | |
517 work = work->next; | |
518 } | |
442 | 519 if (!ok) |
140 | 520 { |
521 flags |= EDITOR_ERROR_NO_FILE; | |
522 goto err; | |
523 } | |
524 } | |
442 | 525 break; |
669 | 526 case '%': |
527 /* %% = % escaping */ | |
528 if (output) result = g_string_append_c(result, *p); | |
529 break; | |
140 | 530 default: |
531 flags |= EDITOR_ERROR_SYNTAX; | |
532 goto err; | |
533 } | |
534 if (extensions) g_free(extensions); | |
535 extensions = NULL; | |
536 } | |
537 p++; | |
9 | 538 } |
539 | |
140 | 540 if (output) *output = g_string_free(result, FALSE); |
541 return flags; | |
542 | |
442 | 543 |
140 | 544 err: |
442 | 545 if (output) |
9 | 546 { |
140 | 547 g_string_free(result, TRUE); |
548 *output = NULL; | |
549 } | |
550 if (extensions) g_free(extensions); | |
551 return flags; | |
552 } | |
553 | |
554 static void editor_child_exit_cb (GPid pid, gint status, gpointer data) | |
555 { | |
556 EditorData *ed = data; | |
557 g_spawn_close_pid(pid); | |
558 ed->pid = -1; | |
442 | 559 |
140 | 560 editor_command_next_finish(ed, status); |
561 } | |
562 | |
563 | |
564 static gint editor_command_one(const gchar *template, GList *list, EditorData *ed) | |
565 { | |
566 gchar *command; | |
567 FileData *fd = list->data; | |
568 GPid pid; | |
442 | 569 gint standard_output; |
570 gint standard_error; | |
140 | 571 gboolean ok; |
572 | |
573 ed->pid = -1; | |
574 ed->flags = editor_command_parse(template, list, &command); | |
575 | |
576 ok = !(ed->flags & EDITOR_ERROR_MASK); | |
577 | |
578 if (ok) | |
579 { | |
737
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
580 ok = (options->shell.path && *options->shell.path); |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
581 if (!ok) log_printf("ERROR: empty shell command\n"); |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
582 |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
583 if (ok) |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
584 { |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
585 ok = (access(options->shell.path, X_OK) == 0); |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
586 if (!ok) log_printf("ERROR: cannot execute shell command '%s'\n", options->shell.path); |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
587 } |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
588 |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
589 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC; |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
590 } |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
591 |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
592 if (ok) |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
593 { |
443 | 594 gchar *working_directory; |
595 gchar *args[4]; | |
737
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
596 guint n = 0; |
443 | 597 |
598 working_directory = remove_level_from_path(fd->path); | |
737
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
599 args[n++] = options->shell.path; |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
600 if (options->shell.options && *options->shell.options) |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
601 args[n++] = options->shell.options; |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
602 args[n++] = command; |
8a8873e7a552
Make shell command and its option rc file options instead of hardcoded strings.
zas_
parents:
731
diff
changeset
|
603 args[n] = NULL; |
443 | 604 |
442 | 605 ok = g_spawn_async_with_pipes(working_directory, args, NULL, |
140 | 606 G_SPAWN_DO_NOT_REAP_CHILD, /* GSpawnFlags */ |
442 | 607 NULL, NULL, |
608 &pid, | |
609 NULL, | |
610 ed->vd ? &standard_output : NULL, | |
611 ed->vd ? &standard_error : NULL, | |
140 | 612 NULL); |
443 | 613 |
614 g_free(working_directory); | |
442 | 615 |
140 | 616 if (!ok) ed->flags |= EDITOR_ERROR_CANT_EXEC; |
617 } | |
618 | |
442 | 619 if (ok) |
140 | 620 { |
621 g_child_watch_add(pid, editor_child_exit_cb, ed); | |
622 ed->pid = pid; | |
623 } | |
442 | 624 |
140 | 625 if (ed->vd) |
626 { | |
627 if (!ok) | |
9 | 628 { |
140 | 629 gchar *buf; |
630 | |
147
b2266996fa83
added possibility to specify prefered file type for external commands
nadvornik
parents:
140
diff
changeset
|
631 buf = g_strdup_printf(_("Failed to run command:\n%s\n"), template); |
140 | 632 editor_verbose_window_fill(ed->vd, buf, strlen(buf)); |
633 g_free(buf); | |
634 | |
635 } | |
442 | 636 else |
140 | 637 { |
638 GIOChannel *channel_output; | |
639 GIOChannel *channel_error; | |
444 | 640 |
140 | 641 channel_output = g_io_channel_unix_new(standard_output); |
642 g_io_channel_set_flags(channel_output, G_IO_FLAG_NONBLOCK, NULL); | |
643 | |
644 g_io_add_watch_full(channel_output, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP, | |
645 editor_verbose_io_cb, ed, NULL); | |
646 g_io_channel_unref(channel_output); | |
647 | |
648 channel_error = g_io_channel_unix_new(standard_error); | |
649 g_io_channel_set_flags(channel_error, G_IO_FLAG_NONBLOCK, NULL); | |
650 | |
651 g_io_add_watch_full(channel_error, G_PRIORITY_HIGH, G_IO_IN | G_IO_ERR | G_IO_HUP, | |
652 editor_verbose_io_cb, ed, NULL); | |
653 g_io_channel_unref(channel_error); | |
654 } | |
655 } | |
442 | 656 |
140 | 657 g_free(command); |
658 | |
659 return ed->flags & EDITOR_ERROR_MASK; | |
660 } | |
661 | |
662 static gint editor_command_next_start(EditorData *ed) | |
663 { | |
664 if (ed->vd) editor_verbose_window_fill(ed->vd, "\n", 1); | |
665 | |
666 if (ed->list && ed->count < ed->total) | |
667 { | |
668 FileData *fd; | |
669 gint error; | |
670 | |
671 fd = ed->list->data; | |
672 | |
673 if (ed->vd) | |
674 { | |
675 editor_verbose_window_progress(ed, (ed->flags & EDITOR_FOR_EACH) ? fd->path : _("running...")); | |
676 } | |
677 ed->count++; | |
678 | |
679 error = editor_command_one(ed->command_template, ed->list, ed); | |
680 if (!error && ed->vd) | |
681 { | |
682 gtk_widget_set_sensitive(ed->vd->button_stop, (ed->list != NULL) ); | |
683 if (ed->flags & EDITOR_FOR_EACH) | |
9 | 684 { |
140 | 685 editor_verbose_window_fill(ed->vd, fd->path, strlen(fd->path)); |
686 editor_verbose_window_fill(ed->vd, "\n", 1); | |
9 | 687 } |
688 } | |
140 | 689 |
442 | 690 if (!error) |
140 | 691 return 0; |
692 else | |
693 /* command was not started, call the finish immediately */ | |
694 return editor_command_next_finish(ed, 0); | |
695 } | |
442 | 696 |
140 | 697 /* everything is done */ |
237
404629011caa
Add missing return at the end of editor_command_next_start().
zas_
parents:
196
diff
changeset
|
698 return editor_command_done(ed); |
140 | 699 } |
700 | |
701 static gint editor_command_next_finish(EditorData *ed, gint status) | |
702 { | |
703 gint cont = ed->stopping ? EDITOR_CB_SKIP : EDITOR_CB_CONTINUE; | |
704 | |
705 if (status) | |
706 ed->flags |= EDITOR_ERROR_STATUS; | |
707 | |
708 if (ed->flags & EDITOR_FOR_EACH) | |
709 { | |
710 /* handle the first element from the list */ | |
711 GList *fd_element = ed->list; | |
444 | 712 |
140 | 713 ed->list = g_list_remove_link(ed->list, fd_element); |
714 if (ed->callback) | |
715 cont = ed->callback(ed->list ? ed : NULL, ed->flags, fd_element, ed->data); | |
716 filelist_free(fd_element); | |
9 | 717 } |
718 else | |
719 { | |
140 | 720 /* handle whole list */ |
721 if (ed->callback) | |
722 cont = ed->callback(NULL, ed->flags, ed->list, ed->data); | |
723 filelist_free(ed->list); | |
724 ed->list = NULL; | |
725 } | |
9 | 726 |
140 | 727 if (cont == EDITOR_CB_SUSPEND) |
728 return ed->flags & EDITOR_ERROR_MASK; | |
729 else if (cont == EDITOR_CB_SKIP) | |
730 return editor_command_done(ed); | |
731 else | |
732 return editor_command_next_start(ed); | |
733 } | |
9 | 734 |
140 | 735 static gint editor_command_done(EditorData *ed) |
736 { | |
737 gint flags; | |
9 | 738 |
140 | 739 if (ed->vd) |
740 { | |
444 | 741 const gchar *text; |
742 | |
140 | 743 if (ed->count == ed->total) |
9 | 744 { |
140 | 745 text = _("done"); |
9 | 746 } |
747 else | |
748 { | |
140 | 749 text = _("stopped by user"); |
9 | 750 } |
140 | 751 editor_verbose_window_progress(ed, text); |
752 editor_verbose_window_enable_close(ed->vd); | |
753 } | |
754 | |
755 /* free the not-handled items */ | |
756 if (ed->list) | |
757 { | |
758 ed->flags |= EDITOR_ERROR_SKIPPED; | |
759 if (ed->callback) ed->callback(NULL, ed->flags, ed->list, ed->data); | |
760 filelist_free(ed->list); | |
761 ed->list = NULL; | |
762 } | |
9 | 763 |
140 | 764 ed->count = 0; |
765 | |
766 flags = ed->flags & EDITOR_ERROR_MASK; | |
767 | |
768 if (!ed->vd) editor_data_free(ed); | |
769 | |
770 return flags; | |
771 } | |
772 | |
773 void editor_resume(gpointer ed) | |
774 { | |
775 editor_command_next_start(ed); | |
776 } | |
443 | 777 |
140 | 778 void editor_skip(gpointer ed) |
779 { | |
442 | 780 editor_command_done(ed); |
9 | 781 } |
782 | |
140 | 783 static gint editor_command_start(const gchar *template, const gchar *text, GList *list, EditorCallback cb, gpointer data) |
784 { | |
785 EditorData *ed; | |
786 gint flags = editor_command_parse(template, NULL, NULL); | |
442 | 787 |
140 | 788 if (flags & EDITOR_ERROR_MASK) return flags & EDITOR_ERROR_MASK; |
789 | |
790 ed = g_new0(EditorData, 1); | |
791 ed->list = filelist_copy(list); | |
792 ed->flags = flags; | |
793 ed->command_template = g_strdup(template); | |
794 ed->total = (flags & EDITOR_SINGLE_COMMAND) ? 1 : g_list_length(list); | |
795 ed->count = 0; | |
796 ed->stopping = FALSE; | |
797 ed->callback = cb; | |
798 ed->data = data; | |
442 | 799 |
140 | 800 if ((flags & EDITOR_VERBOSE_MULTI) && list && list->next) |
801 flags |= EDITOR_VERBOSE; | |
442 | 802 |
140 | 803 if (flags & EDITOR_VERBOSE) |
804 editor_verbose_window(ed, text); | |
442 | 805 |
806 editor_command_next_start(ed); | |
140 | 807 /* errors from editor_command_next_start will be handled via callback */ |
808 return flags & EDITOR_ERROR_MASK; | |
809 } | |
810 | |
444 | 811 static gint is_valid_editor_command(gint n) |
812 { | |
813 return (n >= 0 && n < GQ_EDITOR_SLOTS | |
730 | 814 && options->editor[n].command |
815 && strlen(options->editor[n].command) > 0); | |
444 | 816 } |
817 | |
140 | 818 gint start_editor_from_filelist_full(gint n, GList *list, EditorCallback cb, gpointer data) |
9 | 819 { |
820 gchar *command; | |
140 | 821 gint error; |
9 | 822 |
444 | 823 if (!list) return FALSE; |
824 if (!is_valid_editor_command(n)) return FALSE; | |
9 | 825 |
730 | 826 command = g_locale_from_utf8(options->editor[n].command, -1, NULL, NULL, NULL); |
827 error = editor_command_start(command, options->editor[n].name, list, cb, data); | |
9 | 828 g_free(command); |
669 | 829 |
670 | 830 if (n < GQ_EDITOR_GENERIC_SLOTS && (error & EDITOR_ERROR_MASK)) |
669 | 831 { |
670 | 832 gchar *text = g_strdup_printf(_("%s\n#%d \"%s\":\n%s"), editor_get_error_str(error), n+1, |
730 | 833 options->editor[n].name, options->editor[n].command); |
669 | 834 |
835 file_util_warning_dialog(_("Invalid editor command"), text, GTK_STOCK_DIALOG_ERROR, NULL); | |
836 g_free(text); | |
837 } | |
838 | |
140 | 839 return error; |
9 | 840 } |
841 | |
140 | 842 gint start_editor_from_filelist(gint n, GList *list) |
843 { | |
844 return start_editor_from_filelist_full(n, list, NULL, NULL); | |
845 } | |
846 | |
847 gint start_editor_from_file_full(gint n, FileData *fd, EditorCallback cb, gpointer data) | |
9 | 848 { |
849 GList *list; | |
140 | 850 gint error; |
9 | 851 |
138 | 852 if (!fd) return FALSE; |
9 | 853 |
138 | 854 list = g_list_append(NULL, fd); |
140 | 855 error = start_editor_from_filelist_full(n, list, cb, data); |
9 | 856 g_list_free(list); |
140 | 857 return error; |
9 | 858 } |
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
859 |
140 | 860 gint start_editor_from_file(gint n, FileData *fd) |
136 | 861 { |
140 | 862 return start_editor_from_file_full(n, fd, NULL, NULL); |
136 | 863 } |
864 | |
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
865 gint editor_window_flag_set(gint n) |
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
866 { |
444 | 867 if (!is_valid_editor_command(n)) return TRUE; |
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
868 |
730 | 869 return (editor_command_parse(options->editor[n].command, NULL, NULL) & EDITOR_KEEP_FS); |
60
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
870 } |
9c0c402b0ef3
Mon Jun 13 17:31:46 2005 John Ellis <johne@verizon.net>
gqview
parents:
9
diff
changeset
|
871 |
753 | 872 gint editor_is_filter(gint n) |
873 { | |
874 if (!is_valid_editor_command(n)) return FALSE; | |
875 | |
876 return (editor_command_parse(options->editor[n].command, NULL, NULL) & EDITOR_DEST); | |
877 } | |
878 | |
140 | 879 const gchar *editor_get_error_str(gint flags) |
880 { | |
881 if (flags & EDITOR_ERROR_EMPTY) return _("Editor template is empty."); | |
882 if (flags & EDITOR_ERROR_SYNTAX) return _("Editor template has incorrect syntax."); | |
883 if (flags & EDITOR_ERROR_INCOMPATIBLE) return _("Editor template uses incompatible macros."); | |
884 if (flags & EDITOR_ERROR_NO_FILE) return _("Can't find matching file type."); | |
885 if (flags & EDITOR_ERROR_CANT_EXEC) return _("Can't execute external editor."); | |
886 if (flags & EDITOR_ERROR_STATUS) return _("External editor returned error status."); | |
887 if (flags & EDITOR_ERROR_SKIPPED) return _("File was skipped."); | |
888 return _("Unknown error."); | |
889 } | |
731
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
890 |
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
891 const gchar *editor_get_name(gint n) |
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
892 { |
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
893 if (!is_valid_editor_command(n)) return NULL; |
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
894 |
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
895 if (options->editor[n].name && strlen(options->editor[n].name) > 0) |
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
896 return options->editor[n].name; |
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
897 |
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
898 return _("(unknown)"); |
fa8f7d7396cf
Introduce an helper function that returns the name of an editor.
zas_
parents:
730
diff
changeset
|
899 } |