No issues found
1 /*
2 *
3 * This program is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2 of the License, or (at your option) version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with the program; if not, see <http://www.gnu.org/licenses/>
15 *
16 *
17 * Authors:
18 * Holger Macht <hmacht@suse.de>
19 * based on work by Sankar P <psankar@novell.com>
20 *
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 *
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <mail/em-config.h>
30 #include <mail/em-composer-utils.h>
31 #include <libevolution-utils/e-alert-dialog.h>
32 #include <e-msg-composer.h>
33
34 #include <glib/gi18n-lib.h>
35 #include <glib/gstdio.h>
36 #include <gdk/gdkkeysyms.h>
37
38 #include <sys/stat.h>
39 #ifdef HAVE_SYS_WAIT_H
40 # include <sys/wait.h>
41 #endif
42
43 #include <stdlib.h>
44 #include <string.h>
45
46 #define d(x)
47
48 gboolean e_plugin_ui_init (GtkUIManager *manager,
49 EMsgComposer *composer);
50 GtkWidget * e_plugin_lib_get_configure_widget
51 (EPlugin *epl);
52 static void ee_editor_command_changed
53 (GtkWidget *textbox);
54 static void ee_editor_immediate_launch_changed
55 (GtkWidget *checkbox);
56 static void async_external_editor (EMsgComposer *composer);
57 static gboolean editor_running (void);
58 static gboolean key_press_cb (GtkWidget *widget,
59 GdkEventKey *event,
60 EMsgComposer *composer);
61
62 /* used to track when the external editor is active */
63 static GThread *editor_thread;
64
65 gint e_plugin_lib_enable (EPlugin *ep, gint enable);
66
67 gint
68 e_plugin_lib_enable (EPlugin *ep,
69 gint enable)
70 {
71 return 0;
72 }
73
74 void
75 ee_editor_command_changed (GtkWidget *textbox)
76 {
77 const gchar *editor;
78 GSettings *settings;
79
80 editor = gtk_entry_get_text (GTK_ENTRY (textbox));
81 d (printf ("\n\aeditor is : [%s] \n\a", editor));
82
83 /* GSettings access for every key-press. Sucky ? */
84 settings = g_settings_new ("org.gnome.evolution.plugin.external-editor");
85 g_settings_set_string (settings, "command", editor);
86 g_object_unref (settings);
87 }
88
89 void
90 ee_editor_immediate_launch_changed (GtkWidget *checkbox)
91 {
92 gboolean immediately;
93 GSettings *settings;
94
95 immediately = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbox));
96 d (printf ("\n\aimmediate launch is : [%d] \n\a", immediately));
97
98 settings = g_settings_new ("org.gnome.evolution.plugin.external-editor");
99 g_settings_set_boolean (settings, "launch-on-key-press", immediately);
100 g_object_unref (settings);
101 }
102
103 GtkWidget *
104 e_plugin_lib_get_configure_widget (EPlugin *epl)
105 {
106 GtkWidget *vbox, *textbox, *label, *help;
107 GtkWidget *checkbox;
108 GSettings *settings;
109 gchar *editor;
110 gboolean checked;
111
112 vbox = gtk_vbox_new (FALSE, 10);
113 textbox = gtk_entry_new ();
114 label = gtk_label_new (_("Command to be executed to launch the editor: "));
115 help = gtk_label_new (_("For XEmacs use \"xemacs\"\nFor Vim use \"gvim -f\""));
116 settings = g_settings_new ("org.gnome.evolution.plugin.external-editor");
117
118 editor = g_settings_get_string (settings, "command");
119 if (editor) {
120 gtk_entry_set_text (GTK_ENTRY (textbox), editor);
121 g_free (editor);
122 }
123
124 checkbox = gtk_check_button_new_with_label (
125 _("Automatically launch when a new mail is edited"));
126 checked = g_settings_get_boolean (settings, "launch-on-key-press");
127 if (checked)
128 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbox), TRUE);
129 g_object_unref (settings);
130
131 gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
132 gtk_box_pack_start (GTK_BOX (vbox), textbox, FALSE, FALSE, 0);
133 gtk_box_pack_start (GTK_BOX (vbox), help, FALSE, FALSE, 0);
134 gtk_box_pack_start (GTK_BOX (vbox), checkbox, FALSE, FALSE, 0);
135
136 g_signal_connect (
137 textbox, "changed",
138 G_CALLBACK (ee_editor_command_changed), textbox);
139
140 g_signal_connect (
141 checkbox, "toggled",
142 G_CALLBACK (ee_editor_immediate_launch_changed), checkbox);
143
144 gtk_widget_show_all (vbox);
145
146 return vbox;
147 }
148
149 static void
150 enable_disable_composer (EMsgComposer *composer,
151 gboolean enable)
152 {
153 GtkhtmlEditor *editor;
154 GtkAction *action;
155 GtkActionGroup *action_group;
156
157 g_return_if_fail (E_IS_MSG_COMPOSER (composer));
158
159 editor = GTKHTML_EDITOR (composer);
160
161 if (enable)
162 gtkhtml_editor_run_command (editor, "editable-on");
163 else
164 gtkhtml_editor_run_command (editor, "editable-off");
165
166 action = GTKHTML_EDITOR_ACTION_EDIT_MENU (composer);
167 gtk_action_set_sensitive (action, enable);
168
169 action = GTKHTML_EDITOR_ACTION_FORMAT_MENU (composer);
170 gtk_action_set_sensitive (action, enable);
171
172 action = GTKHTML_EDITOR_ACTION_INSERT_MENU (composer);
173 gtk_action_set_sensitive (action, enable);
174
175 action_group = gtkhtml_editor_get_action_group (editor, "composer");
176 gtk_action_group_set_sensitive (action_group, enable);
177 }
178
179 static void
180 enable_composer (EMsgComposer *composer)
181 {
182 enable_disable_composer (composer, TRUE);
183 }
184
185 static void
186 disable_composer (EMsgComposer *composer)
187 {
188 enable_disable_composer (composer, FALSE);
189 }
190
191 /* needed because the new thread needs to call g_idle_add () */
192 static gboolean
193 update_composer_text (GArray *array)
194 {
195 EMsgComposer *composer;
196 gchar *text;
197
198 composer = g_array_index (array, gpointer, 0);
199 text = g_array_index (array, gpointer, 1);
200
201 e_msg_composer_set_body_text (composer, text, FALSE);
202
203 enable_composer (composer);
204
205 g_free (text);
206
207 return FALSE;
208 }
209
210 struct run_error_dialog_data
211 {
212 EMsgComposer *composer;
213 const gchar *text;
214 };
215
216 /* needed because the new thread needs to call g_idle_add () */
217 static gboolean
218 run_error_dialog (struct run_error_dialog_data *data)
219 {
220 g_return_val_if_fail (data != NULL, FALSE);
221
222 e_alert_run_dialog_for_args (GTK_WINDOW (data->composer), data->text, NULL);
223 enable_composer (data->composer);
224
225 g_free (data);
226
227 return FALSE;
228 }
229
230 static gint
231 numlines (const gchar *text,
232 gint pos)
233 {
234 gint ctr = 0;
235 gint lineno = 0;
236
237 while (text && *text && ctr <= pos) {
238 if (*text == '\n')
239 lineno++;
240
241 text++;
242 ctr++;
243 }
244
245 if (lineno > 0) {
246 lineno++;
247 }
248
249 return lineno;
250 }
251
252 void
253 async_external_editor (EMsgComposer *composer)
254 {
255 gchar *filename = NULL;
256 gint status = 0;
257 GSettings *settings;
258 gchar *editor_cmd_line = NULL, *editor_cmd = NULL, *content;
259 gint fd, position = -1, offset = -1;
260
261 /* prefix temp files with evo so .*vimrc can be setup to recognize them */
262 fd = g_file_open_tmp ("evoXXXXXX", &filename, NULL);
263 if (fd > 0) {
264 gsize length = 0;
265
266 close (fd);
267 d (printf ("\n\aTemporary-file Name is : [%s] \n\a", filename));
268
269 /* Push the text (if there is one) from the composer to the file */
270 content = gtkhtml_editor_get_text_plain (GTKHTML_EDITOR (composer), &length);
271 g_file_set_contents (filename, content, length, NULL);
272 } else {
273 struct run_error_dialog_data *data;
274
275 data = g_new0 (struct run_error_dialog_data, 1);
276 data->composer = composer;
277 data->text = "org.gnome.evolution.plugins.external-editor:no-temp-file";
278
279 g_warning ("Temporary file fd is null");
280
281 /* run_error_dialog also calls enable_composer */
282 g_idle_add ((GSourceFunc) run_error_dialog, data);
283 return;
284 }
285
286 settings = g_settings_new ("org.gnome.evolution.plugin.external-editor");
287 editor_cmd = g_settings_get_string (settings, "command");
288 if (!editor_cmd) {
289 if (!(editor_cmd = g_strdup (g_getenv ("EDITOR"))))
290 /* Make gedit the default external editor,
291 * if the default schemas are not installed
292 * and no $EDITOR is set. */
293 editor_cmd = g_strdup ("gedit");
294 }
295 g_object_unref (settings);
296
297 if (g_strrstr (editor_cmd, "vim") != NULL
298 && gtk_html_get_cursor_pos (
299 gtkhtml_editor_get_html (
300 GTKHTML_EDITOR (composer)), &position, &offset)
301 && position >= 0 && offset >= 0) {
302 gchar *tmp = editor_cmd;
303 gint lineno;
304 gboolean set_nofork;
305
306 set_nofork = g_strrstr (editor_cmd, "gvim") != NULL;
307 /* Increment 1 so that entering vim insert mode places you
308 * in the same entry position you were at in the html. */
309 offset++;
310
311 /* calculate the line number that the cursor is in */
312 lineno = numlines (content, position);
313
314 editor_cmd = g_strdup_printf (
315 "%s \"+call cursor(%d,%d)\"%s%s",
316 tmp, lineno, offset,
317 set_nofork ? " " : "",
318 set_nofork ? "--nofork" : "");
319
320 g_free (tmp);
321 }
322
323 g_free (content);
324
325 editor_cmd_line = g_strconcat (editor_cmd, " ", filename, NULL);
326
327 if (!g_spawn_command_line_sync (editor_cmd_line, NULL, NULL, &status, NULL)) {
328 struct run_error_dialog_data *data;
329
330 g_warning ("Unable to launch %s: ", editor_cmd_line);
331
332 data = g_new0 (struct run_error_dialog_data, 1);
333 data->composer = composer;
334 data->text = "org.gnome.evolution.plugins.external-editor:editor-not-launchable";
335
336 /* run_error_dialog also calls enable_composer */
337 g_idle_add ((GSourceFunc) run_error_dialog, data);
338
339 g_free (filename);
340 g_free (editor_cmd_line);
341 g_free (editor_cmd);
342 return;
343 }
344 g_free (editor_cmd_line);
345 g_free (editor_cmd);
346
347 #ifdef HAVE_SYS_WAIT_H
348 if (WEXITSTATUS (status) != 0) {
349 #else
350 if (status) {
351 #endif
352 d (printf ("\n\nsome problem here with external editor\n\n"));
353 g_idle_add ((GSourceFunc) enable_composer, composer);
354 return;
355 } else {
356 gchar *buf;
357
358 if (g_file_get_contents (filename, &buf, NULL, NULL)) {
359 gchar *htmltext;
360 GArray *array;
361
362 htmltext = camel_text_to_html (buf, CAMEL_MIME_FILTER_TOHTML_PRE, 0);
363
364 array = g_array_sized_new (
365 TRUE, TRUE,
366 sizeof (gpointer), 2 * sizeof (gpointer));
367 array = g_array_append_val (array, composer);
368 array = g_array_append_val (array, htmltext);
369
370 g_idle_add ((GSourceFunc) update_composer_text, array);
371
372 /* We no longer need that temporary file */
373 g_remove (filename);
374 g_free (filename);
375 }
376 }
377 }
378
379 static void launch_editor (GtkAction *action, EMsgComposer *composer)
380 {
381 d (printf ("\n\nexternal_editor plugin is launched \n\n"));
382
383 if (editor_running ()) {
384 d (printf ("not opening editor, because it's still running\n"));
385 return;
386 }
387
388 disable_composer (composer);
389
390 editor_thread = g_thread_create (
391 (GThreadFunc) async_external_editor, composer, FALSE, NULL);
392 }
393
394 static GtkActionEntry entries[] = {
395 { "ExternalEditor",
396 GTK_STOCK_EDIT,
397 N_("Compose in External Editor"),
398 "<Shift><Control>e",
399 N_("Compose in External Editor"),
400 G_CALLBACK (launch_editor) }
401 };
402
403 static gboolean
404 key_press_cb (GtkWidget *widget,
405 GdkEventKey *event,
406 EMsgComposer *composer)
407 {
408 GSettings *settings;
409 gboolean immediately;
410
411 /* we don't want to start the editor on any modifier keys */
412 switch (event->keyval) {
413 case GDK_KEY_Alt_L:
414 case GDK_KEY_Alt_R:
415 case GDK_KEY_Super_L:
416 case GDK_KEY_Super_R:
417 case GDK_KEY_Control_L:
418 case GDK_KEY_Control_R:
419 return FALSE;
420 default:
421 break;
422 }
423
424 settings = g_settings_new ("org.gnome.evolution.plugin.external-editor");
425 immediately = g_settings_get_boolean (settings, "launch-on-key-press");
426 g_object_unref (settings);
427 if (!immediately)
428 return FALSE;
429
430 launch_editor (NULL, composer);
431
432 return TRUE;
433 }
434
435 static void
436 editor_running_thread_func (GThread *thread,
437 gpointer running)
438 {
439 if (thread == editor_thread)
440 *(gboolean*)running = TRUE;
441 }
442
443 /* Racy? */
444 static gboolean
445 editor_running (void)
446 {
447 gboolean running = FALSE;
448
449 g_thread_foreach ((GFunc) editor_running_thread_func, &running);
450
451 return running;
452 }
453
454 static gboolean
455 delete_cb (GtkWidget *widget,
456 EMsgComposer *composer)
457 {
458 if (editor_running ()) {
459 e_alert_run_dialog_for_args (
460 NULL, "org.gnome.evolution.plugins."
461 "external-editor:editor-still-running", NULL);
462 return TRUE;
463 }
464
465 return FALSE;
466 }
467
468 gboolean
469 e_plugin_ui_init (GtkUIManager *manager,
470 EMsgComposer *composer)
471 {
472 GtkhtmlEditor *editor;
473 EWebViewGtkHTML *web_view;
474
475 editor = GTKHTML_EDITOR (composer);
476
477 /* Add actions to the "composer" action group. */
478 gtk_action_group_add_actions (
479 gtkhtml_editor_get_action_group (editor, "composer"),
480 entries, G_N_ELEMENTS (entries), composer);
481
482 web_view = e_msg_composer_get_web_view (composer);
483
484 g_signal_connect (
485 web_view, "key_press_event",
486 G_CALLBACK (key_press_cb), composer);
487
488 g_signal_connect (
489 web_view, "delete-event",
490 G_CALLBACK (delete_cb), composer);
491
492 return TRUE;
493 }