/* * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Holger Macht * based on work by Sankar P * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #ifdef HAVE_SYS_WAIT_H # include #endif #include #include #define d(x) gboolean e_plugin_ui_init (GtkUIManager *manager, EMsgComposer *composer); GtkWidget * e_plugin_lib_get_configure_widget (EPlugin *epl); static void ee_editor_command_changed (GtkWidget *textbox); static void ee_editor_immediate_launch_changed (GtkWidget *checkbox); static gboolean editor_running (void); static gboolean key_press_cb (GtkWidget *widget, GdkEventKey *event, EMsgComposer *composer); /* used to track when the external editor is active */ static GThread *editor_thread; gint e_plugin_lib_enable (EPlugin *ep, gint enable); gint e_plugin_lib_enable (EPlugin *ep, gint enable) { return 0; } void ee_editor_command_changed (GtkWidget *textbox) { const gchar *editor; GSettings *settings; editor = gtk_entry_get_text (GTK_ENTRY (textbox)); d (printf ("\n\aeditor is : [%s] \n\a", editor)); /* GSettings access for every key-press. Sucky ? */ settings = g_settings_new ("org.gnome.evolution.plugin.external-editor"); g_settings_set_string (settings, "command", editor); g_object_unref (settings); } void ee_editor_immediate_launch_changed (GtkWidget *checkbox) { gboolean immediately; GSettings *settings; immediately = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbox)); d (printf ("\n\aimmediate launch is : [%d] \n\a", immediately)); settings = g_settings_new ("org.gnome.evolution.plugin.external-editor"); g_settings_set_boolean (settings, "launch-on-key-press", immediately); g_object_unref (settings); } GtkWidget * e_plugin_lib_get_configure_widget (EPlugin *epl) { GtkWidget *vbox, *textbox, *label, *help; GtkWidget *checkbox; GSettings *settings; gchar *editor; gboolean checked; vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10); textbox = gtk_entry_new (); label = gtk_label_new (_("Command to be executed to launch the editor: ")); help = gtk_label_new (_("For XEmacs use \"xemacs\"\nFor Vim use \"gvim -f\"")); settings = g_settings_new ("org.gnome.evolution.plugin.external-editor"); editor = g_settings_get_string (settings, "command"); if (editor) { gtk_entry_set_text (GTK_ENTRY (textbox), editor); g_free (editor); } checkbox = gtk_check_button_new_with_label ( _("Automatically launch when a new mail is edited")); checked = g_settings_get_boolean (settings, "launch-on-key-press"); if (checked) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbox), TRUE); g_object_unref (settings); gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), textbox, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), help, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), checkbox, FALSE, FALSE, 0); g_signal_connect ( textbox, "changed", G_CALLBACK (ee_editor_command_changed), textbox); g_signal_connect ( checkbox, "toggled", G_CALLBACK (ee_editor_immediate_launch_changed), checkbox); gtk_widget_show_all (vbox); return vbox; } static void enable_disable_composer (EMsgComposer *composer, gboolean enable) { GtkhtmlEditor *editor; GtkAction *action; GtkActionGroup *action_group; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); editor = GTKHTML_EDITOR (composer); if (enable) gtkhtml_editor_run_command (editor, "editable-on"); else gtkhtml_editor_run_command (editor, "editable-off"); action = GTKHTML_EDITOR_ACTION_EDIT_MENU (composer); gtk_action_set_sensitive (action, enable); action = GTKHTML_EDITOR_ACTION_FORMAT_MENU (composer); gtk_action_set_sensitive (action, enable); action = GTKHTML_EDITOR_ACTION_INSERT_MENU (composer); gtk_action_set_sensitive (action, enable); action_group = gtkhtml_editor_get_action_group (editor, "composer"); gtk_action_group_set_sensitive (action_group, enable); } static void enable_composer (EMsgComposer *composer) { enable_disable_composer (composer, TRUE); } static void disable_composer (EMsgComposer *composer) { enable_disable_composer (composer, FALSE); } /* needed because the new thread needs to call g_idle_add () */ static gboolean update_composer_text (GArray *array) { EMsgComposer *composer; gchar *text; composer = g_array_index (array, gpointer, 0); text = g_array_index (array, gpointer, 1); e_msg_composer_set_body_text (composer, text, FALSE); enable_composer (composer); g_free (text); return FALSE; } struct run_error_dialog_data { EMsgComposer *composer; const gchar *text; }; /* needed because the new thread needs to call g_idle_add () */ static gboolean run_error_dialog (struct run_error_dialog_data *data) { g_return_val_if_fail (data != NULL, FALSE); e_alert_run_dialog_for_args (GTK_WINDOW (data->composer), data->text, NULL); enable_composer (data->composer); g_free (data); return FALSE; } static gint numlines (const gchar *text, gint pos) { gint ctr = 0; gint lineno = 0; while (text && *text && ctr <= pos) { if (*text == '\n') lineno++; text++; ctr++; } if (lineno > 0) { lineno++; } return lineno; } static gboolean external_editor_running = FALSE; static GMutex external_editor_running_lock; static gpointer external_editor_thread (gpointer user_data) { EMsgComposer *composer = user_data; gchar *filename = NULL; gint status = 0; GSettings *settings; gchar *editor_cmd_line = NULL, *editor_cmd = NULL, *content; gint fd, position = -1, offset = -1; /* prefix temp files with evo so .*vimrc can be setup to recognize them */ fd = g_file_open_tmp ("evoXXXXXX", &filename, NULL); if (fd > 0) { gsize length = 0; close (fd); d (printf ("\n\aTemporary-file Name is : [%s] \n\a", filename)); /* Push the text (if there is one) from the composer to the file */ content = gtkhtml_editor_get_text_plain (GTKHTML_EDITOR (composer), &length); g_file_set_contents (filename, content, length, NULL); } else { struct run_error_dialog_data *data; data = g_new0 (struct run_error_dialog_data, 1); data->composer = composer; data->text = "org.gnome.evolution.plugins.external-editor:no-temp-file"; g_warning ("Temporary file fd is null"); /* run_error_dialog also calls enable_composer */ g_idle_add ((GSourceFunc) run_error_dialog, data); goto finished; } settings = g_settings_new ("org.gnome.evolution.plugin.external-editor"); editor_cmd = g_settings_get_string (settings, "command"); if (!editor_cmd) { if (!(editor_cmd = g_strdup (g_getenv ("EDITOR")))) /* Make gedit the default external editor, * if the default schemas are not installed * and no $EDITOR is set. */ editor_cmd = g_strdup ("gedit"); } g_object_unref (settings); if (g_strrstr (editor_cmd, "vim") != NULL && gtk_html_get_cursor_pos ( gtkhtml_editor_get_html ( GTKHTML_EDITOR (composer)), &position, &offset) && position >= 0 && offset >= 0) { gchar *tmp = editor_cmd; gint lineno; gboolean set_nofork; set_nofork = g_strrstr (editor_cmd, "gvim") != NULL; /* Increment 1 so that entering vim insert mode places you * in the same entry position you were at in the html. */ offset++; /* calculate the line number that the cursor is in */ lineno = numlines (content, position); editor_cmd = g_strdup_printf ( "%s \"+call cursor(%d,%d)\"%s%s", tmp, lineno, offset, set_nofork ? " " : "", set_nofork ? "--nofork" : ""); g_free (tmp); } g_free (content); editor_cmd_line = g_strconcat (editor_cmd, " ", filename, NULL); if (!g_spawn_command_line_sync (editor_cmd_line, NULL, NULL, &status, NULL)) { struct run_error_dialog_data *data; g_warning ("Unable to launch %s: ", editor_cmd_line); data = g_new0 (struct run_error_dialog_data, 1); data->composer = composer; data->text = "org.gnome.evolution.plugins.external-editor:editor-not-launchable"; /* run_error_dialog also calls enable_composer */ g_idle_add ((GSourceFunc) run_error_dialog, data); g_free (filename); g_free (editor_cmd_line); g_free (editor_cmd); goto finished; } g_free (editor_cmd_line); g_free (editor_cmd); #ifdef HAVE_SYS_WAIT_H if (WEXITSTATUS (status) != 0) { #else if (status) { #endif d (printf ("\n\nsome problem here with external editor\n\n")); g_idle_add ((GSourceFunc) enable_composer, composer); goto finished; } else { gchar *buf; if (g_file_get_contents (filename, &buf, NULL, NULL)) { gchar *htmltext; GArray *array; htmltext = camel_text_to_html ( buf, CAMEL_MIME_FILTER_TOHTML_PRE, 0); array = g_array_sized_new ( TRUE, TRUE, sizeof (gpointer), 2 * sizeof (gpointer)); array = g_array_append_val (array, composer); array = g_array_append_val (array, htmltext); g_idle_add ((GSourceFunc) update_composer_text, array); /* We no longer need that temporary file */ g_remove (filename); g_free (filename); } } finished: g_mutex_lock (&external_editor_running_lock); external_editor_running = FALSE; g_mutex_unlock (&external_editor_running_lock); return NULL; } static void launch_editor (GtkAction *action, EMsgComposer *composer) { d (printf ("\n\nexternal_editor plugin is launched \n\n")); if (editor_running ()) { d (printf ("not opening editor, because it's still running\n")); return; } disable_composer (composer); g_mutex_lock (&external_editor_running_lock); external_editor_running = TRUE; g_mutex_unlock (&external_editor_running_lock); editor_thread = g_thread_new ( NULL, external_editor_thread, composer); g_thread_unref (editor_thread); } static GtkActionEntry entries[] = { { "ExternalEditor", GTK_STOCK_EDIT, N_("Compose in External Editor"), "e", N_("Compose in External Editor"), G_CALLBACK (launch_editor) } }; static gboolean key_press_cb (GtkWidget *widget, GdkEventKey *event, EMsgComposer *composer) { GSettings *settings; gboolean immediately; /* we don't want to start the editor on any modifier keys */ switch (event->keyval) { case GDK_KEY_Alt_L: case GDK_KEY_Alt_R: case GDK_KEY_Super_L: case GDK_KEY_Super_R: case GDK_KEY_Control_L: case GDK_KEY_Control_R: return FALSE; default: break; } settings = g_settings_new ("org.gnome.evolution.plugin.external-editor"); immediately = g_settings_get_boolean (settings, "launch-on-key-press"); g_object_unref (settings); if (!immediately) return FALSE; launch_editor (NULL, composer); return TRUE; } static gboolean editor_running (void) { gboolean running; g_mutex_lock (&external_editor_running_lock); running = external_editor_running; g_mutex_unlock (&external_editor_running_lock); return running; } static gboolean delete_cb (GtkWidget *widget, EMsgComposer *composer) { if (editor_running ()) { e_alert_run_dialog_for_args ( NULL, "org.gnome.evolution.plugins." "external-editor:editor-still-running", NULL); return TRUE; } return FALSE; } gboolean e_plugin_ui_init (GtkUIManager *manager, EMsgComposer *composer) { GtkhtmlEditor *editor; EWebViewGtkHTML *web_view; editor = GTKHTML_EDITOR (composer); /* Add actions to the "composer" action group. */ gtk_action_group_add_actions ( gtkhtml_editor_get_action_group (editor, "composer"), entries, G_N_ELEMENTS (entries), composer); web_view = e_msg_composer_get_web_view (composer); g_signal_connect ( web_view, "key_press_event", G_CALLBACK (key_press_cb), composer); g_signal_connect ( web_view, "delete-event", G_CALLBACK (delete_cb), composer); return TRUE; }