Files
evolution/e-util/e-html-editor.c
Tomas Popela f60a5b6b58 Bug 743876 - Inline image lost on message send when sending through Outbox
The thing is that when we were sending the message through Outbox
Evolution generates one message that is used for sending and another one
for saving to Outbox. But while creating the first message the images
inside the view are processed to cid images, so for the second message
they were not there. To avoid that we will restore the images in the
view right after we get the HTML version of view's content.
2015-02-04 13:49:32 +01:00

1184 lines
33 KiB
C

/*
* e-html-editor.c
*
* Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU Lesser General Public
* License as published by the Free Software Foundation.
*
* 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
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <config.h>
#include <glib/gi18n-lib.h>
#include <camel/camel.h>
#include <enchant/enchant.h>
#include "e-html-editor.h"
#include "e-activity-bar.h"
#include "e-alert-bar.h"
#include "e-alert-dialog.h"
#include "e-alert-sink.h"
#include "e-html-editor-private.h"
#include "e-html-editor-utils.h"
#include "e-html-editor-selection.h"
#define E_HTML_EDITOR_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_HTML_EDITOR, EHTMLEditorPrivate))
/**
* EHTMLEditor:
*
* #EHTMLEditor provides GUI for manipulating with properties of #EHTMLEditorView and
* its #EHTMLEditorSelection - i.e. toolbars and actions.
*/
/* This controls how spelling suggestions are divided between the primary
* context menu and a secondary menu. The idea is to prevent the primary
* menu from growing too long.
*
* The constants below are used as follows:
*
* if TOTAL_SUGGESTIONS <= MAX_LEVEL1_SUGGETIONS:
* LEVEL1_SUGGESTIONS = TOTAL_SUGGESTIONS
* elif TOTAL_SUGGESTIONS - MAX_LEVEL1_SUGGESTIONS < MIN_LEVEL2_SUGGESTIONS:
* LEVEL1_SUGGESTIONS = TOTAL_SUGGESTIONS
* else
* LEVEL1_SUGGESTIONS = MAX_LEVEL1_SUGGETIONS
*
* LEVEL2_SUGGESTIONS = TOTAL_SUGGESTIONS - LEVEL1_SUGGESTIONS
*
* Note that MAX_LEVEL1_SUGGETIONS is not a hard maximum.
*/
#define MAX_LEVEL1_SUGGESTIONS 4
#define MIN_LEVEL2_SUGGESTIONS 3
enum {
PROP_0,
PROP_FILENAME
};
enum {
UPDATE_ACTIONS,
SPELL_LANGUAGES_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
/* Forward Declarations */
static void e_html_editor_alert_sink_init
(EAlertSinkInterface *interface);
G_DEFINE_TYPE_WITH_CODE (
EHTMLEditor,
e_html_editor,
GTK_TYPE_GRID,
G_IMPLEMENT_INTERFACE (
E_TYPE_ALERT_SINK,
e_html_editor_alert_sink_init))
/* Action callback for context menu spelling suggestions.
* XXX This should really be in e-html-editor-actions.c */
static void
action_context_spell_suggest_cb (GtkAction *action,
EHTMLEditor *editor)
{
EHTMLEditorView *view;
EHTMLEditorSelection *selection;
const gchar *word;
word = g_object_get_data (G_OBJECT (action), "word");
g_return_if_fail (word != NULL);
view = e_html_editor_get_view (editor);
selection = e_html_editor_view_get_selection (view);
e_html_editor_selection_replace_caret_word (selection, word);
}
static void
html_editor_inline_spelling_suggestions (EHTMLEditor *editor)
{
EHTMLEditorView *view;
EHTMLEditorSelection *selection;
WebKitSpellChecker *checker;
GtkActionGroup *action_group;
GtkUIManager *manager;
gchar **suggestions;
const gchar *path;
gchar *word;
guint count = 0;
guint length;
guint merge_id;
guint threshold;
gint ii;
view = e_html_editor_get_view (editor);
selection = e_html_editor_view_get_selection (view);
checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
word = e_html_editor_selection_get_caret_word (selection);
if (word == NULL || *word == '\0')
return;
suggestions = webkit_spell_checker_get_guesses_for_word (checker, word, NULL);
path = "/context-menu/context-spell-suggest/";
manager = e_html_editor_get_ui_manager (editor);
action_group = editor->priv->suggestion_actions;
merge_id = editor->priv->spell_suggestions_merge_id;
length = (suggestions != NULL) ? g_strv_length (suggestions) : 0;
/* Calculate how many suggestions to put directly in the
* context menu. The rest will go in a secondary menu. */
if (length <= MAX_LEVEL1_SUGGESTIONS) {
threshold = length;
} else if (length - MAX_LEVEL1_SUGGESTIONS < MIN_LEVEL2_SUGGESTIONS) {
threshold = length;
} else {
threshold = MAX_LEVEL1_SUGGESTIONS;
}
ii = 0;
for (ii = 0; suggestions && suggestions[ii]; ii++) {
gchar *suggestion = suggestions[ii];
gchar *action_name;
gchar *action_label;
GtkAction *action;
GtkWidget *child;
GSList *proxies;
/* Once we reach the threshold, put all subsequent
* spelling suggestions in a secondary menu. */
if (count == threshold)
path = "/context-menu/context-more-suggestions-menu/";
/* Action name just needs to be unique. */
action_name = g_strdup_printf ("suggest-%d", count++);
action_label = g_markup_printf_escaped (
"<b>%s</b>", suggestion);
action = gtk_action_new (
action_name, action_label, NULL, NULL);
g_object_set_data_full (
G_OBJECT (action), "word",
g_strdup (suggestion), g_free);
g_signal_connect (
action, "activate", G_CALLBACK (
action_context_spell_suggest_cb), editor);
gtk_action_group_add_action (action_group, action);
gtk_ui_manager_add_ui (
manager, merge_id, path,
action_name, action_name,
GTK_UI_MANAGER_AUTO, FALSE);
/* XXX GtkAction offers no support for Pango markup,
* so we have to manually set "use-markup" on the
* child of the proxy widget. */
gtk_ui_manager_ensure_update (manager);
proxies = gtk_action_get_proxies (action);
child = gtk_bin_get_child (proxies->data);
g_object_set (child, "use-markup", TRUE, NULL);
g_free (action_name);
g_free (action_label);
}
g_free (word);
g_strfreev (suggestions);
}
/* Helper for html_editor_update_actions() */
static void
html_editor_spell_checkers_foreach (EHTMLEditor *editor,
const gchar *language_code)
{
EHTMLEditorView *view;
EHTMLEditorSelection *selection;
ESpellChecker *spell_checker;
ESpellDictionary *dictionary;
GtkActionGroup *action_group;
GtkUIManager *manager;
GList *list, *link;
gchar *path;
gchar *word;
gint ii = 0;
guint merge_id;
view = e_html_editor_get_view (editor);
selection = e_html_editor_view_get_selection (view);
spell_checker = e_html_editor_view_get_spell_checker (view);
word = e_html_editor_selection_get_caret_word (selection);
if (word == NULL || *word == '\0')
return;
dictionary = e_spell_checker_ref_dictionary (
spell_checker, language_code);
if (dictionary != NULL) {
list = e_spell_dictionary_get_suggestions (
dictionary, word, -1);
g_object_unref (dictionary);
} else {
list = NULL;
}
manager = e_html_editor_get_ui_manager (editor);
action_group = editor->priv->suggestion_actions;
merge_id = editor->priv->spell_suggestions_merge_id;
path = g_strdup_printf (
"/context-menu/context-spell-suggest/"
"context-spell-suggest-%s-menu", language_code);
for (link = list; link != NULL; link = g_list_next (link)) {
gchar *suggestion = link->data;
gchar *action_name;
gchar *action_label;
GtkAction *action;
GtkWidget *child;
GSList *proxies;
/* Action name just needs to be unique. */
action_name = g_strdup_printf (
"suggest-%s-%d", language_code, ii);
action_label = g_markup_printf_escaped (
"<b>%s</b>", suggestion);
action = gtk_action_new (
action_name, action_label, NULL, NULL);
g_object_set_data_full (
G_OBJECT (action), "word",
g_strdup (suggestion), g_free);
g_signal_connect (
action, "activate", G_CALLBACK (
action_context_spell_suggest_cb), editor);
gtk_action_group_add_action (action_group, action);
gtk_ui_manager_add_ui (
manager, merge_id, path,
action_name, action_name,
GTK_UI_MANAGER_AUTO, FALSE);
/* XXX GtkAction offers no supports for Pango markup,
* so we have to manually set "use-markup" on the
* child of the proxy widget. */
gtk_ui_manager_ensure_update (manager);
proxies = gtk_action_get_proxies (action);
if (proxies && proxies->data) {
child = gtk_bin_get_child (proxies->data);
g_object_set (child, "use-markup", TRUE, NULL);
}
g_free (action_name);
g_free (action_label);
}
g_list_free_full (list, (GDestroyNotify) g_free);
g_free (path);
g_free (word);
}
static void
html_editor_update_actions (EHTMLEditor *editor,
GdkEventButton *event)
{
WebKitWebView *web_view;
WebKitSpellChecker *checker;
WebKitHitTestResult *hit_test;
WebKitHitTestResultContext context;
WebKitDOMNode *node;
EHTMLEditorSelection *selection;
EHTMLEditorView *view;
ESpellChecker *spell_checker;
GtkUIManager *manager;
GtkActionGroup *action_group;
GList *list;
gchar **languages;
guint ii, n_languages;
gboolean visible;
guint merge_id;
gint loc, len;
view = e_html_editor_get_view (editor);
spell_checker = e_html_editor_view_get_spell_checker (view);
web_view = WEBKIT_WEB_VIEW (view);
manager = e_html_editor_get_ui_manager (editor);
editor->priv->image = NULL;
editor->priv->table_cell = NULL;
/* Update context menu item visibility. */
hit_test = webkit_web_view_get_hit_test_result (web_view, event);
g_object_get (
G_OBJECT (hit_test),
"context", &context,
"inner-node", &node, NULL);
g_object_unref (hit_test);
visible = (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE);
gtk_action_set_visible (ACTION (CONTEXT_PROPERTIES_IMAGE), visible);
if (visible)
editor->priv->image = node;
visible = (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK);
gtk_action_set_visible (ACTION (CONTEXT_PROPERTIES_LINK), visible);
visible = (WEBKIT_DOM_IS_HTMLHR_ELEMENT (node));
gtk_action_set_visible (ACTION (CONTEXT_PROPERTIES_RULE), visible);
visible = (WEBKIT_DOM_IS_TEXT (node));
gtk_action_set_visible (ACTION (CONTEXT_PROPERTIES_TEXT), visible);
visible =
gtk_action_get_visible (ACTION (CONTEXT_PROPERTIES_IMAGE)) ||
gtk_action_get_visible (ACTION (CONTEXT_PROPERTIES_LINK)) ||
gtk_action_get_visible (ACTION (CONTEXT_PROPERTIES_TEXT));
gtk_action_set_visible (ACTION (CONTEXT_PROPERTIES_PARAGRAPH), visible);
/* Set to visible if any of these are true:
* - Selection is active and contains a link.
* - Cursor is on a link.
* - Cursor is on an image that has a URL or target.
*/
visible = (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) ||
(e_html_editor_dom_node_find_parent_element (node, "A") != NULL));
gtk_action_set_visible (ACTION (CONTEXT_REMOVE_LINK), visible);
visible = (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node) ||
(e_html_editor_dom_node_find_parent_element (node, "TD") != NULL) ||
(e_html_editor_dom_node_find_parent_element (node, "TH") != NULL));
gtk_action_set_visible (ACTION (CONTEXT_DELETE_CELL), visible);
gtk_action_set_visible (ACTION (CONTEXT_DELETE_COLUMN), visible);
gtk_action_set_visible (ACTION (CONTEXT_DELETE_ROW), visible);
gtk_action_set_visible (ACTION (CONTEXT_DELETE_TABLE), visible);
gtk_action_set_visible (ACTION (CONTEXT_INSERT_COLUMN_AFTER), visible);
gtk_action_set_visible (ACTION (CONTEXT_INSERT_COLUMN_BEFORE), visible);
gtk_action_set_visible (ACTION (CONTEXT_INSERT_ROW_ABOVE), visible);
gtk_action_set_visible (ACTION (CONTEXT_INSERT_ROW_BELOW), visible);
gtk_action_set_visible (ACTION (CONTEXT_INSERT_TABLE), visible);
gtk_action_set_visible (ACTION (CONTEXT_PROPERTIES_CELL), visible);
if (visible)
editor->priv->table_cell = node;
/* Note the |= (cursor must be in a table cell). */
visible |= (WEBKIT_DOM_IS_HTML_TABLE_ELEMENT (node) ||
(e_html_editor_dom_node_find_parent_element (node, "TABLE") != NULL));
gtk_action_set_visible (ACTION (CONTEXT_PROPERTIES_TABLE), visible);
/********************** Spell Check Suggestions **********************/
action_group = editor->priv->suggestion_actions;
/* Remove the old content from the context menu. */
merge_id = editor->priv->spell_suggestions_merge_id;
if (merge_id > 0) {
gtk_ui_manager_remove_ui (manager, merge_id);
editor->priv->spell_suggestions_merge_id = 0;
}
/* Clear the action group for spelling suggestions. */
list = gtk_action_group_list_actions (action_group);
while (list != NULL) {
GtkAction *action = list->data;
gtk_action_group_remove_action (action_group, action);
list = g_list_delete_link (list, list);
}
languages = e_spell_checker_list_active_languages (
spell_checker, &n_languages);
/* Decide if we should show spell checking items. */
checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
selection = e_html_editor_view_get_selection (view);
visible = FALSE;
if ((n_languages > 0) && e_html_editor_selection_has_text (selection)) {
gchar *word = e_html_editor_selection_get_caret_word (selection);
if (word && *word) {
webkit_spell_checker_check_spelling_of_string (
checker, word, &loc, &len);
visible = (loc > -1);
} else {
visible = FALSE;
}
g_free (word);
}
action_group = editor->priv->spell_check_actions;
gtk_action_group_set_visible (action_group, visible);
/* Exit early if spell checking items are invisible. */
if (!visible) {
g_strfreev (languages);
return;
}
merge_id = gtk_ui_manager_new_merge_id (manager);
editor->priv->spell_suggestions_merge_id = merge_id;
/* Handle a single active language as a special case. */
if (n_languages == 1) {
html_editor_inline_spelling_suggestions (editor);
g_strfreev (languages);
return;
}
/* Add actions and context menu content for active languages. */
for (ii = 0; ii < n_languages; ii++)
html_editor_spell_checkers_foreach (editor, languages[ii]);
g_strfreev (languages);
}
static void
html_editor_spell_languages_changed (EHTMLEditor *editor)
{
EHTMLEditorView *view;
ESpellChecker *spell_checker;
WebKitWebSettings *settings;
gchar *comma_separated;
gchar **languages;
view = e_html_editor_get_view (editor);
spell_checker = e_html_editor_view_get_spell_checker (view);
languages = e_spell_checker_list_active_languages (spell_checker, NULL);
comma_separated = g_strjoinv (",", languages);
g_strfreev (languages);
/* Set the languages for webview to highlight misspelled words */
settings = webkit_web_view_get_settings (
WEBKIT_WEB_VIEW (editor->priv->html_editor_view));
g_object_set (
G_OBJECT (settings),
"spell-checking-languages", comma_separated,
NULL);
if (editor->priv->spell_check_dialog != NULL)
e_html_editor_spell_check_dialog_update_dictionaries (
E_HTML_EDITOR_SPELL_CHECK_DIALOG (
editor->priv->spell_check_dialog));
if (*comma_separated)
e_html_editor_view_force_spell_check (editor->priv->html_editor_view);
else
e_html_editor_view_turn_spell_check_off (editor->priv->html_editor_view);
g_free (comma_separated);
}
static gboolean
html_editor_show_popup (EHTMLEditor *editor,
GdkEventButton *event,
gpointer user_data)
{
GtkWidget *menu;
menu = e_html_editor_get_managed_widget (editor, "/context-menu");
g_signal_emit (editor, signals[UPDATE_ACTIONS], 0, event);
if (event != NULL)
gtk_menu_popup (
GTK_MENU (menu), NULL, NULL, NULL,
user_data, event->button, event->time);
else
gtk_menu_popup (
GTK_MENU (menu), NULL, NULL, NULL,
user_data, 0, gtk_get_current_event_time ());
return TRUE;
}
static gchar *
html_editor_find_ui_file (const gchar *basename)
{
gchar *filename;
g_return_val_if_fail (basename != NULL, NULL);
/* Support running directly from the source tree. */
filename = g_build_filename (".", basename, NULL);
if (g_file_test (filename, G_FILE_TEST_EXISTS))
return filename;
g_free (filename);
/* XXX This is kinda broken. */
filename = g_build_filename (EVOLUTION_UIDIR, basename, NULL);
if (g_file_test (filename, G_FILE_TEST_EXISTS))
return filename;
g_free (filename);
g_critical ("Could not locate '%s'", basename);
return NULL;
}
static void
html_editor_parent_changed (GtkWidget *widget,
GtkWidget *previous_parent)
{
GtkWidget *top_level;
EHTMLEditor *editor = E_HTML_EDITOR (widget);
/* If he now have a window, then install our accelators to it */
top_level = gtk_widget_get_toplevel (widget);
if (GTK_IS_WINDOW (top_level)) {
gtk_window_add_accel_group (
GTK_WINDOW (top_level),
gtk_ui_manager_get_accel_group (editor->priv->manager));
}
}
static void
html_editor_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FILENAME:
e_html_editor_set_filename (
E_HTML_EDITOR (object),
g_value_get_string (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
html_editor_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FILENAME:
g_value_set_string (
value, e_html_editor_get_filename (
E_HTML_EDITOR (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
html_editor_constructed (GObject *object)
{
EHTMLEditor *editor = E_HTML_EDITOR (object);
EHTMLEditorPrivate *priv = editor->priv;
GtkIMMulticontext *im_context;
GtkWidget *widget;
GtkToolbar *toolbar;
GtkToolItem *tool_item;
/* Chain up to parent's method. */
G_OBJECT_CLASS (e_html_editor_parent_class)->constructed (object);
/* Construct the editing toolbars. */
widget = e_html_editor_get_managed_widget (editor, "/edit-toolbar");
gtk_widget_set_hexpand (widget, TRUE);
gtk_toolbar_set_style (GTK_TOOLBAR (widget), GTK_TOOLBAR_BOTH_HORIZ);
gtk_grid_attach (GTK_GRID (editor), widget, 0, 0, 1, 1);
priv->edit_toolbar = g_object_ref (widget);
gtk_widget_show (widget);
widget = e_html_editor_get_managed_widget (editor, "/html-toolbar");
gtk_widget_set_hexpand (widget, TRUE);
gtk_toolbar_set_style (GTK_TOOLBAR (widget), GTK_TOOLBAR_BOTH_HORIZ);
gtk_grid_attach (GTK_GRID (editor), widget, 0, 1, 1, 1);
priv->html_toolbar = g_object_ref (widget);
gtk_widget_show (widget);
/* Construct the activity bar. */
widget = e_activity_bar_new ();
gtk_widget_set_hexpand (widget, TRUE);
gtk_grid_attach (GTK_GRID (editor), widget, 0, 2, 1, 1);
priv->activity_bar = g_object_ref (widget);
/* Construct the alert bar for errors. */
widget = e_alert_bar_new ();
gtk_widget_set_hexpand (widget, TRUE);
gtk_grid_attach (GTK_GRID (editor), widget, 0, 3, 1, 1);
priv->alert_bar = g_object_ref (widget);
/* EAlertBar controls its own visibility. */
/* Construct the main editing area. */
widget = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (
GTK_SCROLLED_WINDOW (widget),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (
GTK_SCROLLED_WINDOW (widget), GTK_SHADOW_IN);
gtk_widget_set_hexpand (widget, TRUE);
gtk_widget_set_vexpand (widget, TRUE);
gtk_grid_attach (GTK_GRID (editor), widget, 0, 4, 1, 1);
priv->scrolled_window = g_object_ref (widget);
gtk_widget_show (widget);
widget = GTK_WIDGET (e_html_editor_get_view (editor));
gtk_container_add (GTK_CONTAINER (priv->scrolled_window), widget);
gtk_widget_show (widget);
g_signal_connect_swapped (
widget, "popup-event",
G_CALLBACK (html_editor_show_popup), editor);
/* Add some combo boxes to the "edit" toolbar. */
toolbar = GTK_TOOLBAR (priv->edit_toolbar);
tool_item = gtk_tool_item_new ();
widget = e_action_combo_box_new_with_action (
GTK_RADIO_ACTION (ACTION (STYLE_NORMAL)));
gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (widget), FALSE);
gtk_container_add (GTK_CONTAINER (tool_item), widget);
gtk_widget_set_tooltip_text (widget, _("Paragraph Style"));
gtk_toolbar_insert (toolbar, tool_item, 0);
priv->style_combo_box = g_object_ref (widget);
gtk_widget_show_all (GTK_WIDGET (tool_item));
tool_item = gtk_separator_tool_item_new ();
gtk_toolbar_insert (toolbar, tool_item, 0);
gtk_widget_show_all (GTK_WIDGET (tool_item));
tool_item = gtk_tool_item_new ();
widget = e_action_combo_box_new_with_action (
GTK_RADIO_ACTION (ACTION (MODE_HTML)));
gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (widget), FALSE);
gtk_container_add (GTK_CONTAINER (tool_item), widget);
gtk_widget_set_tooltip_text (widget, _("Editing Mode"));
gtk_toolbar_insert (toolbar, tool_item, 0);
priv->mode_combo_box = g_object_ref (widget);
gtk_widget_show_all (GTK_WIDGET (tool_item));
/* Add some combo boxes to the "html" toolbar. */
toolbar = GTK_TOOLBAR (priv->html_toolbar);
tool_item = gtk_tool_item_new ();
widget = e_color_combo_new ();
gtk_container_add (GTK_CONTAINER (tool_item), widget);
gtk_widget_set_tooltip_text (widget, _("Font Color"));
gtk_toolbar_insert (toolbar, tool_item, 0);
priv->color_combo_box = g_object_ref (widget);
gtk_widget_show_all (GTK_WIDGET (tool_item));
g_object_bind_property (
priv->color_combo_box, "current-color",
priv->selection, "font-color",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
g_object_bind_property (
priv->html_editor_view, "editable",
priv->color_combo_box, "sensitive",
G_BINDING_SYNC_CREATE);
tool_item = gtk_tool_item_new ();
widget = e_action_combo_box_new_with_action (
GTK_RADIO_ACTION (ACTION (SIZE_PLUS_ZERO)));
gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (widget), FALSE);
gtk_container_add (GTK_CONTAINER (tool_item), widget);
gtk_widget_set_tooltip_text (widget, _("Font Size"));
gtk_toolbar_insert (toolbar, tool_item, 0);
priv->size_combo_box = g_object_ref (widget);
gtk_widget_show_all (GTK_WIDGET (tool_item));
/* Add input methods to the context menu. */
widget = e_html_editor_get_managed_widget (
editor, "/context-menu/context-input-methods-menu");
widget = gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget));
g_object_get (
G_OBJECT (priv->html_editor_view), "im-context", &im_context, NULL);
gtk_im_multicontext_append_menuitems (
GTK_IM_MULTICONTEXT (im_context),
GTK_MENU_SHELL (widget));
}
static void
html_editor_dispose (GObject *object)
{
EHTMLEditorPrivate *priv;
priv = E_HTML_EDITOR_GET_PRIVATE (object);
g_clear_object (&priv->manager);
g_clear_object (&priv->core_actions);
g_clear_object (&priv->core_editor_actions);
g_clear_object (&priv->html_actions);
g_clear_object (&priv->context_actions);
g_clear_object (&priv->html_context_actions);
g_clear_object (&priv->language_actions);
g_clear_object (&priv->spell_check_actions);
g_clear_object (&priv->suggestion_actions);
g_clear_object (&priv->main_menu);
g_clear_object (&priv->main_toolbar);
g_clear_object (&priv->edit_toolbar);
g_clear_object (&priv->html_toolbar);
g_clear_object (&priv->activity_bar);
g_clear_object (&priv->alert_bar);
g_clear_object (&priv->edit_area);
g_clear_object (&priv->color_combo_box);
g_clear_object (&priv->mode_combo_box);
g_clear_object (&priv->size_combo_box);
g_clear_object (&priv->style_combo_box);
g_clear_object (&priv->scrolled_window);
g_clear_object (&priv->html_editor_view);
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_html_editor_parent_class)->dispose (object);
}
static void
html_editor_submit_alert (EAlertSink *alert_sink,
EAlert *alert)
{
EHTMLEditorPrivate *priv;
EAlertBar *alert_bar;
GtkWidget *toplevel;
GtkWidget *widget;
GtkWindow *parent;
priv = E_HTML_EDITOR_GET_PRIVATE (alert_sink);
switch (e_alert_get_message_type (alert)) {
case GTK_MESSAGE_INFO:
case GTK_MESSAGE_WARNING:
case GTK_MESSAGE_ERROR:
alert_bar = E_ALERT_BAR (priv->alert_bar);
e_alert_bar_add_alert (alert_bar, alert);
break;
default:
widget = GTK_WIDGET (alert_sink);
toplevel = gtk_widget_get_toplevel (widget);
if (GTK_IS_WINDOW (toplevel))
parent = GTK_WINDOW (toplevel);
else
parent = NULL;
widget = e_alert_dialog_new (parent, alert);
gtk_dialog_run (GTK_DIALOG (widget));
gtk_widget_destroy (widget);
}
}
static void
e_html_editor_class_init (EHTMLEditorClass *class)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
g_type_class_add_private (class, sizeof (EHTMLEditorPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = html_editor_set_property;
object_class->get_property = html_editor_get_property;
object_class->constructed = html_editor_constructed;
object_class->dispose = html_editor_dispose;
widget_class = GTK_WIDGET_CLASS (class);
widget_class->parent_set = html_editor_parent_changed;
class->update_actions = html_editor_update_actions;
class->spell_languages_changed = html_editor_spell_languages_changed;
g_object_class_install_property (
object_class,
PROP_FILENAME,
g_param_spec_string (
"filename",
NULL,
NULL,
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
signals[UPDATE_ACTIONS] = g_signal_new (
"update-actions",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EHTMLEditorClass, update_actions),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1,
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
signals[SPELL_LANGUAGES_CHANGED] = g_signal_new (
"spell-languages-changed",
G_OBJECT_CLASS_TYPE (class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EHTMLEditorClass, spell_languages_changed),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static void
e_html_editor_alert_sink_init (EAlertSinkInterface *interface)
{
interface->submit_alert = html_editor_submit_alert;
}
static void
e_html_editor_init (EHTMLEditor *editor)
{
EHTMLEditorPrivate *priv;
GtkWidget *widget;
gchar *filename;
GError *error = NULL;
editor->priv = E_HTML_EDITOR_GET_PRIVATE (editor);
priv = editor->priv;
priv->manager = gtk_ui_manager_new ();
priv->core_actions = gtk_action_group_new ("core");
priv->core_editor_actions = gtk_action_group_new ("core-editor");
priv->html_actions = gtk_action_group_new ("html");
priv->context_actions = gtk_action_group_new ("core-context");
priv->html_context_actions = gtk_action_group_new ("html-context");
priv->language_actions = gtk_action_group_new ("language");
priv->spell_check_actions = gtk_action_group_new ("spell-check");
priv->suggestion_actions = gtk_action_group_new ("suggestion");
priv->html_editor_view = g_object_ref_sink (e_html_editor_view_new ());
priv->selection = e_html_editor_view_get_selection (priv->html_editor_view);
filename = html_editor_find_ui_file ("e-html-editor-manager.ui");
if (!gtk_ui_manager_add_ui_from_file (priv->manager, filename, &error)) {
g_critical ("Couldn't load builder file: %s\n", error->message);
g_clear_error (&error);
}
g_free (filename);
editor_actions_init (editor);
priv->editor_layout_row = 2;
/* Tweak the main-toolbar style. */
widget = e_html_editor_get_managed_widget (editor, "/main-toolbar");
gtk_style_context_add_class (
gtk_widget_get_style_context (widget),
GTK_STYLE_CLASS_PRIMARY_TOOLBAR);
}
/**
* e_html_editor_new:
*
* Constructs a new #EHTMLEditor.
*
* Returns: A newly created widget. [transfer-full]
*/
GtkWidget *
e_html_editor_new (void)
{
return g_object_new (E_TYPE_HTML_EDITOR, NULL);
}
/**
* e_html_editor_get_view:
* @editor: an #EHTMLEditor
*
* Returns instance of #EHTMLEditorView used in the @editor.
*/
EHTMLEditorView *
e_html_editor_get_view (EHTMLEditor *editor)
{
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
return editor->priv->html_editor_view;
}
/**
* e_html_editor_get_ui_manager:
* @editor: an #EHTMLEditor
*
* Returns #GtkUIManager that manages all the actions in the @editor.
*/
GtkUIManager *
e_html_editor_get_ui_manager (EHTMLEditor *editor)
{
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
return editor->priv->manager;
}
/**
* e_html_editor_get_action:
* @editor: an #EHTMLEditor
* @action_name: name of action to lookup and return
*
* Returns: A #GtkAction matching @action_name or @NULL if no such action exists.
*/
GtkAction *
e_html_editor_get_action (EHTMLEditor *editor,
const gchar *action_name)
{
GtkUIManager *manager;
GtkAction *action = NULL;
GList *list;
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
g_return_val_if_fail (action_name != NULL, NULL);
manager = e_html_editor_get_ui_manager (editor);
list = gtk_ui_manager_get_action_groups (manager);
while (list != NULL && action == NULL) {
GtkActionGroup *action_group = list->data;
action = gtk_action_group_get_action (
action_group, action_name);
list = g_list_next (list);
}
g_return_val_if_fail (action != NULL, NULL);
return action;
}
/**
* e_html_editor_get_action_group:
* @editor: an #EHTMLEditor
* @group_name: name of action group to lookup and return
*
* Returns: A #GtkActionGroup matching @group_name or @NULL if not such action
* group exists.
*/
GtkActionGroup *
e_html_editor_get_action_group (EHTMLEditor *editor,
const gchar *group_name)
{
GtkUIManager *manager;
GList *list;
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
g_return_val_if_fail (group_name != NULL, NULL);
manager = e_html_editor_get_ui_manager (editor);
list = gtk_ui_manager_get_action_groups (manager);
while (list != NULL) {
GtkActionGroup *action_group = list->data;
const gchar *name;
name = gtk_action_group_get_name (action_group);
if (strcmp (name, group_name) == 0)
return action_group;
list = g_list_next (list);
}
return NULL;
}
GtkWidget *
e_html_editor_get_managed_widget (EHTMLEditor *editor,
const gchar *widget_path)
{
GtkUIManager *manager;
GtkWidget *widget;
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
g_return_val_if_fail (widget_path != NULL, NULL);
manager = e_html_editor_get_ui_manager (editor);
widget = gtk_ui_manager_get_widget (manager, widget_path);
g_return_val_if_fail (widget != NULL, NULL);
return widget;
}
GtkWidget *
e_html_editor_get_style_combo_box (EHTMLEditor *editor)
{
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
return editor->priv->style_combo_box;
}
/**
* e_html_editor_get_filename:
* @editor: an #EHTMLEditor
*
* Returns path and name of file to which content of the editor should be saved.
*/
const gchar *
e_html_editor_get_filename (EHTMLEditor *editor)
{
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
return editor->priv->filename;
}
/**
* e_html_editor_set_filename:
* @editor: an #EHTMLEditor
* @filename: Target file
*
* Sets file to which content of the editor should be saved (see
* e_html_editor_save()).
*/
void
e_html_editor_set_filename (EHTMLEditor *editor,
const gchar *filename)
{
g_return_if_fail (E_IS_HTML_EDITOR (editor));
if (g_strcmp0 (editor->priv->filename, filename) == 0)
return;
g_free (editor->priv->filename);
editor->priv->filename = g_strdup (filename);
g_object_notify (G_OBJECT (editor), "filename");
}
EActivityBar *
e_html_editor_get_activity_bar (EHTMLEditor *editor)
{
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
return E_ACTIVITY_BAR (editor->priv->activity_bar);
}
/**
* e_html_editor_new_activity:
* @editor: an #EHTMLEditor
*
* Creates and configures a new #EActivity so its progress is shown in
* the @editor. The #EActivity comes pre-loaded with a #CamelOperation.
*
* Returns: a new #EActivity for use with @editor
**/
EActivity *
e_html_editor_new_activity (EHTMLEditor *editor)
{
EActivity *activity;
EActivityBar *activity_bar;
GCancellable *cancellable;
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
activity = e_activity_new ();
e_activity_set_alert_sink (activity, E_ALERT_SINK (editor));
cancellable = camel_operation_new ();
e_activity_set_cancellable (activity, cancellable);
g_object_unref (cancellable);
activity_bar = E_ACTIVITY_BAR (editor->priv->activity_bar);
e_activity_bar_set_activity (activity_bar, activity);
return activity;
}
/**
* e_html_editor_pack_above:
* @editor: an #EHTMLEditor
* @child: a #GtkWidget
*
* Inserts @child right between the toolbars and the editor widget itself.
*/
void
e_html_editor_pack_above (EHTMLEditor *editor,
GtkWidget *child)
{
g_return_if_fail (E_IS_HTML_EDITOR (editor));
g_return_if_fail (GTK_IS_WIDGET (child));
gtk_grid_insert_row (GTK_GRID (editor), editor->priv->editor_layout_row);
gtk_grid_attach (GTK_GRID (editor), child, 0, editor->priv->editor_layout_row, 1, 1);
editor->priv->editor_layout_row++;
}
/**
* e_html_editor_save:
* @editor: an #EHTMLEditor
* @filename: file into which to save the content
* @as_html: whether the content should be saved as HTML or plain text
* @error:[out] a #GError
*
* Saves current content of the #EHTMLEditorView into given file. When @as_html
* is @FALSE, the content is first converted into plain text.
*
* Returns: @TRUE when content is succesfully saved, @FALSE otherwise.
*/
gboolean
e_html_editor_save (EHTMLEditor *editor,
const gchar *filename,
gboolean as_html,
GError **error)
{
GFile *file;
GFileOutputStream *stream;
gchar *content;
gsize written;
file = g_file_new_for_path (filename);
stream = g_file_replace (
file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error);
if ((error && *error) || !stream)
return FALSE;
if (as_html)
content = e_html_editor_view_get_text_html (
E_HTML_EDITOR_VIEW (editor), NULL, NULL);
else
content = e_html_editor_view_get_text_plain (
E_HTML_EDITOR_VIEW (editor));
if (!content || !*content) {
g_set_error (
error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to obtain content of editor");
return FALSE;
}
g_output_stream_write_all (
G_OUTPUT_STREAM (stream), content, strlen (content),
&written, NULL, error);
g_free (content);
g_object_unref (stream);
g_object_unref (file);
return TRUE;
}