aboutsummaryrefslogtreecommitdiffstats
path: root/e-util
diff options
context:
space:
mode:
authorTomas Popela <tpopela@redhat.com>2014-06-09 22:32:25 +0800
committerTomas Popela <tpopela@redhat.com>2014-06-09 22:32:25 +0800
commit8650fb139a9143f04615de74ff569bce3e0c4ce3 (patch)
tree89a41d08f179a5359b8eaee0c9344b8a5bf07cb3 /e-util
parent04b7c97275ae420dca43f3e65c2ef54d02f01bdd (diff)
downloadgsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar
gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.gz
gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.bz2
gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.lz
gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.xz
gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.tar.zst
gsoc2013-evolution-8650fb139a9143f04615de74ff569bce3e0c4ce3.zip
Bug 540362: [webkit-composer] Use webkit for composer
Merge wip/webkit-composer branch into master.
Diffstat (limited to 'e-util')
-rw-r--r--e-util/Makefile.am67
-rw-r--r--e-util/e-action-combo-box.c42
-rw-r--r--e-util/e-action-combo-box.h1
-rw-r--r--e-util/e-color-chooser-widget.c253
-rw-r--r--e-util/e-color-chooser-widget.h71
-rw-r--r--e-util/e-color-combo.c976
-rw-r--r--e-util/e-color-combo.h96
-rw-r--r--e-util/e-emoticon-action.c278
-rw-r--r--e-util/e-emoticon-action.h73
-rw-r--r--e-util/e-emoticon-chooser-menu.c184
-rw-r--r--e-util/e-emoticon-chooser-menu.h70
-rw-r--r--e-util/e-emoticon-chooser.c178
-rw-r--r--e-util/e-emoticon-chooser.h77
-rw-r--r--e-util/e-emoticon-tool-button.c695
-rw-r--r--e-util/e-emoticon-tool-button.h75
-rw-r--r--e-util/e-emoticon.c118
-rw-r--r--e-util/e-emoticon.h53
-rw-r--r--e-util/e-focus-tracker.c45
-rw-r--r--e-util/e-html-editor-actions.c2028
-rw-r--r--e-util/e-html-editor-actions.h155
-rw-r--r--e-util/e-html-editor-cell-dialog.c872
-rw-r--r--e-util/e-html-editor-cell-dialog.h72
-rw-r--r--e-util/e-html-editor-dialog.c248
-rw-r--r--e-util/e-html-editor-dialog.h74
-rw-r--r--e-util/e-html-editor-find-dialog.c224
-rw-r--r--e-util/e-html-editor-find-dialog.h73
-rw-r--r--e-util/e-html-editor-hrule-dialog.c421
-rw-r--r--e-util/e-html-editor-hrule-dialog.h70
-rw-r--r--e-util/e-html-editor-image-dialog.c703
-rw-r--r--e-util/e-html-editor-image-dialog.h73
-rw-r--r--e-util/e-html-editor-link-dialog.c390
-rw-r--r--e-util/e-html-editor-link-dialog.h70
-rw-r--r--e-util/e-html-editor-manager.ui181
-rw-r--r--e-util/e-html-editor-page-dialog.c513
-rw-r--r--e-util/e-html-editor-page-dialog.h70
-rw-r--r--e-util/e-html-editor-paragraph-dialog.c154
-rw-r--r--e-util/e-html-editor-paragraph-dialog.h71
-rw-r--r--e-util/e-html-editor-private.h103
-rw-r--r--e-util/e-html-editor-replace-dialog.c288
-rw-r--r--e-util/e-html-editor-replace-dialog.h71
-rw-r--r--e-util/e-html-editor-selection.c5576
-rw-r--r--e-util/e-html-editor-selection.h250
-rw-r--r--e-util/e-html-editor-spell-check-dialog.c710
-rw-r--r--e-util/e-html-editor-spell-check-dialog.h73
-rw-r--r--e-util/e-html-editor-table-dialog.c866
-rw-r--r--e-util/e-html-editor-table-dialog.h69
-rw-r--r--e-util/e-html-editor-text-dialog.c298
-rw-r--r--e-util/e-html-editor-text-dialog.h69
-rw-r--r--e-util/e-html-editor-utils.c116
-rw-r--r--e-util/e-html-editor-utils.h44
-rw-r--r--e-util/e-html-editor-view.c6303
-rw-r--r--e-util/e-html-editor-view.h164
-rw-r--r--e-util/e-html-editor.c1178
-rw-r--r--e-util/e-html-editor.h108
-rw-r--r--e-util/e-image-chooser-dialog.c223
-rw-r--r--e-util/e-image-chooser-dialog.h74
-rw-r--r--e-util/e-mail-signature-editor.c319
-rw-r--r--e-util/e-mail-signature-editor.h8
-rw-r--r--e-util/e-mail-signature-manager.c18
-rw-r--r--e-util/e-mail-signature-preview.c8
-rw-r--r--e-util/e-misc-utils.c44
-rw-r--r--e-util/e-misc-utils.h3
-rw-r--r--e-util/e-name-selector-entry.c6
-rw-r--r--e-util/e-spell-checker.c783
-rw-r--r--e-util/e-spell-checker.h95
-rw-r--r--e-util/e-spell-dictionary.c797
-rw-r--r--e-util/e-spell-dictionary.h99
-rw-r--r--e-util/e-spell-entry.c373
-rw-r--r--e-util/e-spell-entry.h7
-rw-r--r--e-util/e-util-enums.h221
-rw-r--r--e-util/e-util.h26
-rw-r--r--e-util/e-web-view-gtkhtml.c2352
-rw-r--r--e-util/e-web-view-gtkhtml.h209
-rw-r--r--e-util/e-web-view.c150
-rw-r--r--e-util/e-web-view.h13
-rw-r--r--e-util/test-html-editor.c497
76 files changed, 29457 insertions, 2968 deletions
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index 66cdc220e4..6d9499db27 100644
--- a/e-util/Makefile.am
+++ b/e-util/Makefile.am
@@ -40,6 +40,7 @@ errordir = $(privdatadir)/errors
@EVO_PLUGIN_RULE@
ui_DATA = \
+ e-html-editor-manager.ui \
e-send-options.ui \
e-table-config.ui \
e-timezone-dialog.ui \
@@ -60,6 +61,7 @@ noinst_PROGRAMS = \
test-category-completion \
test-contact-store \
test-dateedit \
+ test-html-editor \
test-mail-signatures \
test-name-selector \
test-preferences-window \
@@ -99,7 +101,7 @@ libevolution_util_la_CPPFLAGS = \
$(EVOLUTION_DATA_SERVER_CFLAGS) \
$(GNOME_PLATFORM_CFLAGS) \
$(GEO_CFLAGS) \
- $(GTKHTML_CFLAGS) \
+ $(ENCHANT_CFLAGS) \
$(GTKSPELL_CFLAGS) \
$(CODE_COVERAGE_CFLAGS) \
$(NULL)
@@ -165,6 +167,8 @@ evolution_util_include_HEADERS = \
e-client-cache.h \
e-client-combo-box.h \
e-client-selector.h \
+ e-color-chooser-widget.h \
+ e-color-combo.h \
e-config.h \
e-contact-store.h \
e-data-capture.h \
@@ -173,6 +177,11 @@ evolution_util_include_HEADERS = \
e-destination-store.h \
e-dialog-utils.h \
e-dialog-widgets.h \
+ e-emoticon-action.h \
+ e-emoticon-chooser-menu.h \
+ e-emoticon-chooser.h \
+ e-emoticon-tool-button.h \
+ e-emoticon.h \
e-event.h \
e-file-request.h \
e-file-utils.h \
@@ -187,9 +196,27 @@ evolution_util_include_HEADERS = \
e-filter-part.h \
e-filter-rule.h \
e-focus-tracker.h \
+ e-html-editor-actions.h \
+ e-html-editor-cell-dialog.h \
+ e-html-editor-dialog.h \
+ e-html-editor-find-dialog.h \
+ e-html-editor-hrule-dialog.h \
+ e-html-editor-image-dialog.h \
+ e-html-editor-link-dialog.h \
+ e-html-editor-page-dialog.h \
+ e-html-editor-paragraph-dialog.h \
+ e-html-editor-replace-dialog.h \
+ e-html-editor-selection.h \
+ e-html-editor-spell-check-dialog.h \
+ e-html-editor-table-dialog.h \
+ e-html-editor-text-dialog.h \
+ e-html-editor-utils.h \
+ e-html-editor-view.h \
+ e-html-editor.h \
e-html-utils.h \
e-icon-factory.h \
e-image-chooser.h \
+ e-image-chooser-dialog.h \
e-import-assistant.h \
e-import.h \
e-interval-chooser.h \
@@ -252,6 +279,8 @@ evolution_util_include_HEADERS = \
e-source-selector-dialog.h \
e-source-selector.h \
e-source-util.h \
+ e-spell-checker.h \
+ e-spell-dictionary.h \
e-spell-entry.h \
e-spell-text-view.h \
e-stock-request.h \
@@ -305,7 +334,6 @@ evolution_util_include_HEADERS = \
e-url-entry.h \
e-util-enums.h \
e-util-enumtypes.h \
- e-web-view-gtkhtml.h \
e-web-view-preview.h \
e-web-view.h \
e-widget-undo.h \
@@ -410,6 +438,8 @@ libevolution_util_la_SOURCES = \
e-client-cache.c \
e-client-combo-box.c \
e-client-selector.c \
+ e-color-chooser-widget.c \
+ e-color-combo.c \
e-config.c \
e-contact-store.c \
e-data-capture.c \
@@ -418,6 +448,11 @@ libevolution_util_la_SOURCES = \
e-destination-store.c \
e-dialog-utils.c \
e-dialog-widgets.c \
+ e-emoticon-action.c \
+ e-emoticon-chooser-menu.c \
+ e-emoticon-chooser.c \
+ e-emoticon-tool-button.c \
+ e-emoticon.c \
e-event.c \
e-file-request.c \
e-file-utils.c \
@@ -432,9 +467,28 @@ libevolution_util_la_SOURCES = \
e-filter-part.c \
e-filter-rule.c \
e-focus-tracker.c \
+ e-html-editor-actions.c \
+ e-html-editor-cell-dialog.c \
+ e-html-editor-dialog.c \
+ e-html-editor-find-dialog.c \
+ e-html-editor-hrule-dialog.c \
+ e-html-editor-image-dialog.c \
+ e-html-editor-link-dialog.c \
+ e-html-editor-page-dialog.c \
+ e-html-editor-paragraph-dialog.c \
+ e-html-editor-private.h \
+ e-html-editor-replace-dialog.c \
+ e-html-editor-selection.c \
+ e-html-editor-spell-check-dialog.c \
+ e-html-editor-table-dialog.c \
+ e-html-editor-text-dialog.c \
+ e-html-editor-utils.c \
+ e-html-editor-view.c \
+ e-html-editor.c \
e-html-utils.c \
e-icon-factory.c \
e-image-chooser.c \
+ e-image-chooser-dialog.c \
e-import-assistant.c \
e-import.c \
e-interval-chooser.c \
@@ -497,6 +551,8 @@ libevolution_util_la_SOURCES = \
e-source-selector-dialog.c \
e-source-selector.c \
e-source-util.c \
+ e-spell-checker.c \
+ e-spell-dictionary.c \
e-spell-entry.c \
e-spell-text-view.c \
e-stock-request.c \
@@ -547,7 +603,6 @@ libevolution_util_la_SOURCES = \
e-url-entry.c \
e-util-enumtypes.c \
e-util-private.h \
- e-web-view-gtkhtml.c \
e-web-view-preview.c \
e-web-view.c \
e-widget-undo.c \
@@ -590,7 +645,7 @@ libevolution_util_la_LIBADD = \
$(EVOLUTION_DATA_SERVER_LIBS) \
$(GNOME_PLATFORM_LIBS) \
$(GEO_LIBS) \
- $(GTKHTML_LIBS) \
+ $(ENCHANT_LIBS) \
$(GTKSPELL_LIBS) \
$(INTLLIBS) \
$(MATH_LIB) \
@@ -625,6 +680,10 @@ test_dateedit_CPPFLAGS = $(TEST_CPPFLAGS)
test_dateedit_SOURCES = test-dateedit.c
test_dateedit_LDADD = $(TEST_LDADD)
+test_html_editor_CPPFLAGS = $(TEST_CPPFLAGS)
+test_html_editor_SOURCES = test-html-editor.c
+test_html_editor_LDADD = $(TEST_LDADD)
+
test_mail_signatures_CPPFLAGS = $(TEST_CPPFLAGS)
test_mail_signatures_SOURCES = test-mail-signatures.c
test_mail_signatures_LDADD = $(TEST_LDADD)
diff --git a/e-util/e-action-combo-box.c b/e-util/e-action-combo-box.c
index 1b784b8ee8..33d678ab1e 100644
--- a/e-util/e-action-combo-box.c
+++ b/e-util/e-action-combo-box.c
@@ -104,10 +104,6 @@ action_combo_box_render_pixbuf (GtkCellLayout *layout,
gboolean visible;
gint width;
- /* Do any of the actions have an icon? */
- if (!combo_box->priv->group_has_icons)
- return;
-
gtk_tree_model_get (model, iter, COLUMN_ACTION, &action, -1);
/* A NULL action means the row is a separator. */
@@ -122,8 +118,12 @@ action_combo_box_render_pixbuf (GtkCellLayout *layout,
"visible", &visible,
NULL);
- /* Keep the pixbuf renderer a fixed size for proper alignment. */
- gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
+ /* If some action has an icon */
+ if (combo_box->priv->group_has_icons)
+ /* Keep the pixbuf renderer a fixed size for proper alignment. */
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
+ else
+ width = 0;
/* We can't set both "icon-name" and "stock-id" because setting
* one unsets the other. So pick the one that has a non-NULL
@@ -245,13 +245,25 @@ action_combo_box_update_model (EActionComboBox *combo_box)
GtkRadioAction *action = list->data;
GtkTreePath *path;
GtkTreeIter iter;
- gchar *icon_name;
- gchar *stock_id;
+ gchar *icon_name = NULL;
+ gchar *stock_id = NULL;
+ gboolean visible = FALSE;
gint value;
- g_object_get (
- action, "icon-name", &icon_name,
- "stock-id", &stock_id, NULL);
+ g_object_get (action,
+ "icon-name", &icon_name,
+ "stock-id", &stock_id,
+ "visible", &visible,
+ NULL);
+
+ if (!visible) {
+ g_free (icon_name);
+ g_free (stock_id);
+
+ list = g_slist_next (list);
+ continue;
+ }
+
combo_box->priv->group_has_icons |=
(icon_name != NULL || stock_id != NULL);
g_free (icon_name);
@@ -583,3 +595,11 @@ e_action_combo_box_add_separator_after (EActionComboBox *combo_box,
GTK_LIST_STORE (model), &iter, COLUMN_ACTION,
NULL, COLUMN_SORT, (gfloat) action_value + 0.5, -1);
}
+
+void
+e_action_combo_box_update_model (EActionComboBox *combo_box)
+{
+ g_return_if_fail (E_IS_ACTION_COMBO_BOX (combo_box));
+
+ action_combo_box_update_model (combo_box);
+}
diff --git a/e-util/e-action-combo-box.h b/e-util/e-action-combo-box.h
index f3a5a0b658..3a44ed730a 100644
--- a/e-util/e-action-combo-box.h
+++ b/e-util/e-action-combo-box.h
@@ -81,6 +81,7 @@ void e_action_combo_box_add_separator_before
void e_action_combo_box_add_separator_after
(EActionComboBox *combo_box,
gint action_value);
+void e_action_combo_box_update_model (EActionComboBox *combo_box);
G_END_DECLS
diff --git a/e-util/e-color-chooser-widget.c b/e-util/e-color-chooser-widget.c
new file mode 100644
index 0000000000..5761ebf2ff
--- /dev/null
+++ b/e-util/e-color-chooser-widget.c
@@ -0,0 +1,253 @@
+
+/* e-color-chooser-widget.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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-color-chooser-widget.h"
+
+#include <glib/gi18n-lib.h>
+
+#define E_COLOR_CHOOSER_WIDGET_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_COLOR_CHOOSER_WIDGET, EColorChooserWidgetPrivate))
+
+/**
+ * EColorChooserWidget:
+ *
+ * This widget wrapps around #GtkColorChooserWidget and allows the widget to be
+ * used as a delegate for #GtkComboBox for instance.
+ */
+
+struct _EColorChooserWidgetPrivate {
+ gboolean showing_editor;
+};
+
+enum {
+ SIGNAL_EDITOR_ACTIVATED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (
+ EColorChooserWidget,
+ e_color_chooser_widget,
+ GTK_TYPE_COLOR_CHOOSER_WIDGET);
+
+/* UGLY UGLY UGLY!
+ * GtkColorChooserWidget sends "color-activated" signal
+ * only when user double-clicks the color. This is totally stupid
+ * and since we want to use it in a combobox-like widget, we need
+ * to be notified upon single click (which by default only selects the color).
+ *
+ * Unfortunatelly the GtkColorSwatch widget, which handles the button-press
+ * event is a non-public widget embedded within the GtkColorChooserWidget,
+ * so we can't just subclass it and fix the behavior.
+ *
+ * Here we override button_press_event of the GtkColorSwatch and manually
+ * emit the 'activate' signal on single click. This is stupid, ugly and I
+ * want to punch someone for such a stupid design...
+ */
+static gboolean
+color_chooser_widget_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ if ((event->type == GDK_BUTTON_PRESS) &&
+ (event->button == GDK_BUTTON_PRIMARY)) {
+
+ g_signal_emit_by_name (widget, "activate");
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+color_chooser_widget_color_activated (GtkColorChooser *chooser,
+ GdkRGBA *color,
+ gpointer user_data)
+{
+ /* Because we are simulating the double-click by accepting only
+ * single click, the color in the swatch is actually not selected,
+ * so we must do it manually */
+ gtk_color_chooser_set_rgba (chooser, color);
+}
+
+static gboolean
+run_color_chooser_dialog (gpointer user_data)
+{
+ EColorChooserWidgetPrivate *priv;
+ GtkWidget *parent_window;
+ GtkWidget *parent_chooser;
+ GtkWidget *dialog;
+ GtkWidget *chooser;
+
+ parent_chooser = user_data;
+
+ g_object_set (
+ G_OBJECT (parent_chooser), "show-editor", FALSE, NULL);
+
+ parent_window = g_object_get_data (G_OBJECT (parent_chooser), "window");
+ if (!parent_window)
+ parent_window = gtk_widget_get_toplevel (parent_chooser);
+ dialog = gtk_dialog_new_with_buttons (
+ N_("Choose custom color"),
+ GTK_WINDOW (parent_window),
+ GTK_DIALOG_MODAL,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
+
+ chooser = gtk_color_chooser_widget_new ();
+ g_object_set (G_OBJECT (chooser), "show-editor", TRUE, NULL);
+ gtk_box_pack_start (
+ GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ chooser, TRUE, TRUE, 5);
+
+ gtk_widget_show_all (chooser);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
+ GdkRGBA color;
+
+ gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (chooser), &color);
+ gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (parent_chooser), &color);
+
+ g_signal_emit_by_name (parent_chooser, "color-activated", &color);
+ }
+
+ gtk_widget_destroy (dialog);
+
+ priv = E_COLOR_CHOOSER_WIDGET_GET_PRIVATE (parent_chooser);
+ priv->showing_editor = FALSE;
+
+ return FALSE;
+}
+
+static void
+color_chooser_show_editor_notify_cb (EColorChooserWidget *chooser,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ gboolean show_editor;
+
+ g_object_get (G_OBJECT (chooser), "show-editor", &show_editor, NULL);
+
+ /* Nothing to do here... */
+ if ((show_editor == FALSE) || (chooser->priv->showing_editor == TRUE))
+ return;
+
+ chooser->priv->showing_editor = TRUE;
+
+ /* Hide the editor - we don't want to display the single-color editor
+ * within this widget. We rather create a dialog window with the editor
+ * (we can't do it from this callback as Gtk would stop it in order to
+ * prevent endless recursion probably) */
+ g_idle_add (run_color_chooser_dialog, chooser);
+ g_signal_emit (chooser, signals[SIGNAL_EDITOR_ACTIVATED], 0);
+}
+
+void
+e_color_chooser_widget_class_init (EColorChooserWidgetClass *class)
+{
+ g_type_class_add_private (class, sizeof (EColorChooserWidgetPrivate));
+
+ signals[SIGNAL_EDITOR_ACTIVATED] = g_signal_new (
+ "editor-activated",
+ E_TYPE_COLOR_CHOOSER_WIDGET,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (EColorChooserWidgetClass, editor_activated),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+/* Recursively go through GtkContainers within the GtkColorChooserWidget
+ * and try to find GtkColorSwatch widget. */
+static GtkWidget *
+find_swatch (GtkContainer *container)
+{
+ GList *children, *child;
+
+ children = gtk_container_get_children (container);
+ for (child = children; child; child = g_list_next (child)) {
+ GtkWidget *widget = child->data;
+ GtkWidget *swatch;
+
+ if (GTK_IS_CONTAINER (widget)) {
+ swatch = find_swatch (GTK_CONTAINER (widget));
+
+ if (swatch != NULL) {
+ g_list_free (children);
+ return swatch;
+ }
+ }
+
+ if (g_strcmp0 (G_OBJECT_TYPE_NAME (widget), "GtkColorSwatch") == 0) {
+ g_list_free (children);
+ return widget;
+ }
+ }
+
+ g_list_free (children);
+
+ return NULL;
+}
+
+void
+e_color_chooser_widget_init (EColorChooserWidget *widget)
+{
+ GtkWidget *swatch;
+
+ widget->priv = E_COLOR_CHOOSER_WIDGET_GET_PRIVATE (widget);
+ widget->priv->showing_editor = FALSE;
+
+ swatch = find_swatch (GTK_CONTAINER (widget));
+
+ /* If swatch is NULL then GTK changed something and this widget
+ * becomes broken... */
+ g_return_if_fail (swatch != NULL);
+
+ if (swatch) {
+ GtkWidgetClass *swatch_class;
+ swatch_class = GTK_WIDGET_GET_CLASS (swatch);
+ swatch_class->button_press_event = color_chooser_widget_button_press_event;
+ }
+
+ g_signal_connect (
+ widget, "color-activated",
+ G_CALLBACK (color_chooser_widget_color_activated), NULL);
+
+ g_signal_connect (
+ widget, "notify::show-editor",
+ G_CALLBACK (color_chooser_show_editor_notify_cb), NULL);
+}
+
+GtkWidget *
+e_color_chooser_widget_new (void)
+{
+ return g_object_new (
+ E_TYPE_COLOR_CHOOSER_WIDGET,
+ "show-editor", FALSE,
+ "use-alpha", FALSE,
+ NULL);
+}
diff --git a/e-util/e-color-chooser-widget.h b/e-util/e-color-chooser-widget.h
new file mode 100644
index 0000000000..3b7c60de7f
--- /dev/null
+++ b/e-util/e-color-chooser-widget.h
@@ -0,0 +1,71 @@
+/* e-color-chooser-widget.h
+ *
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_COLOR_CHOOSER_WIDGET_H
+#define E_COLOR_CHOOSER_WIDGET_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_COLOR_CHOOSER_WIDGET \
+ (e_color_chooser_widget_get_type ())
+#define E_COLOR_CHOOSER_WIDGET(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_COLOR_CHOOSER_WIDGET, EColorChooserWidget))
+#define E_COLOR_CHOOSER_WIDGET_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_COLOR_CHOOSER_WIDGET, EColorChooserWidgetClass))
+#define E_IS_COLOR_CHOOSER_WIDGET(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_COLOR_CHOOSER_WIDGET))
+#define E_IS_COLOR_CHOOSER_WIDGET_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_COLOR_CHOOSER_WIDGET))
+#define E_COLOR_CHOOSER_WIDGET_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_COLOR_CHOOSER_WIDGET, EColorChooserWidgetClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EColorChooserWidget EColorChooserWidget;
+typedef struct _EColorChooserWidgetClass EColorChooserWidgetClass;
+typedef struct _EColorChooserWidgetPrivate EColorChooserWidgetPrivate;
+
+struct _EColorChooserWidget {
+ GtkColorChooserWidget parent;
+ EColorChooserWidgetPrivate *priv;
+};
+
+struct _EColorChooserWidgetClass {
+ GtkColorChooserWidgetClass parent_class;
+
+ void (*editor_activated) (GtkColorChooserWidget *chooser);
+};
+
+GType e_color_chooser_widget_get_type (void) G_GNUC_CONST;
+GtkWidget * e_color_chooser_widget_new (void);
+
+G_END_DECLS
+
+#endif /* E_COLOR_CHOOSER_WIDGET_H */
+
diff --git a/e-util/e-color-combo.c b/e-util/e-color-combo.c
new file mode 100644
index 0000000000..f2f46e4bec
--- /dev/null
+++ b/e-util/e-color-combo.c
@@ -0,0 +1,976 @@
+/* e-color-combo.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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-color-combo.h"
+#include "e-color-chooser-widget.h"
+
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+#include <cairo/cairo.h>
+#include <alloca.h>
+
+#define E_COLOR_COMBO_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_COLOR_COMBO, EColorComboPrivate))
+
+struct _EColorComboPrivate {
+ GtkWidget *color_frame; /* not referenced */
+ GtkWidget *arrow; /* not referenced */
+
+ GtkWidget *window;
+ GtkWidget *default_button; /* not referenced */
+ GtkWidget *chooser_widget; /* not referenced */
+
+ guint popup_shown : 1;
+ guint popup_in_progress : 1;
+
+ GdkRGBA *current_color;
+ GdkRGBA *default_color;
+ gint default_transparent: 1;
+
+ GList *palette;
+
+ GdkDevice *grab_keyboard;
+ GdkDevice *grab_mouse;
+};
+
+enum {
+ PROP_0,
+ PROP_CURRENT_COLOR,
+ PROP_DEFAULT_COLOR,
+ PROP_DEFAULT_LABEL,
+ PROP_DEFAULT_TRANSPARENT,
+ PROP_PALETTE,
+ PROP_POPUP_SHOWN
+};
+
+enum {
+ ACTIVATED,
+ POPUP,
+ POPDOWN,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+static GdkRGBA black = { 0, 0, 0, 1 };
+
+static struct {
+ const gchar *color;
+ const gchar *tooltip;
+} default_colors[] = {
+
+ { "#000000", N_("black") },
+ { "#993300", N_("light brown") },
+ { "#333300", N_("brown gold") },
+ { "#003300", N_("dark green #2") },
+ { "#003366", N_("navy") },
+ { "#000080", N_("dark blue") },
+ { "#333399", N_("purple #2") },
+ { "#333333", N_("very dark gray") },
+
+ { "#800000", N_("dark red") },
+ { "#FF6600", N_("red-orange") },
+ { "#808000", N_("gold") },
+ { "#008000", N_("dark green") },
+ { "#008080", N_("dull blue") },
+ { "#0000FF", N_("blue") },
+ { "#666699", N_("dull purple") },
+ { "#808080", N_("dark grey") },
+
+ { "#FF0000", N_("red") },
+ { "#FF9900", N_("orange") },
+ { "#99CC00", N_("lime") },
+ { "#339966", N_("dull green") },
+ { "#33CCCC", N_("dull blue #2") },
+ { "#3366FF", N_("sky blue #2") },
+ { "#800080", N_("purple") },
+ { "#969696", N_("gray") },
+
+ { "#FF00FF", N_("magenta") },
+ { "#FFCC00", N_("bright orange") },
+ { "#FFFF00", N_("yellow") },
+ { "#00FF00", N_("green") },
+ { "#00FFFF", N_("cyan") },
+ { "#00CCFF", N_("bright blue") },
+ { "#993366", N_("red purple") },
+ { "#C0C0C0", N_("light grey") },
+
+ { "#FF99CC", N_("pink") },
+ { "#FFCC99", N_("light orange") },
+ { "#FFFF99", N_("light yellow") },
+ { "#CCFFCC", N_("light green") },
+ { "#CCFFFF", N_("light cyan") },
+ { "#99CCFF", N_("light blue") },
+ { "#CC99FF", N_("light purple") },
+ { "#FFFFFF", N_("white") }
+};
+
+G_DEFINE_TYPE (
+ EColorCombo,
+ e_color_combo,
+ GTK_TYPE_BUTTON);
+
+static void
+color_combo_reposition_window (EColorCombo *combo)
+{
+ GdkScreen *screen;
+ GdkWindow *window;
+ GdkRectangle monitor;
+ GtkAllocation allocation;
+ gint monitor_num;
+ gint x, y, width, height;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (combo));
+ window = gtk_widget_get_window (GTK_WIDGET (combo));
+ monitor_num = gdk_screen_get_monitor_at_window (screen, window);
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ gdk_window_get_origin (window, &x, &y);
+
+ if (!gtk_widget_get_has_window (GTK_WIDGET (combo))) {
+ gtk_widget_get_allocation (GTK_WIDGET (combo), &allocation);
+ x += allocation.x;
+ y += allocation.y;
+ }
+
+ gtk_widget_get_allocation (combo->priv->window, &allocation);
+ width = allocation.width;
+ height = allocation.height;
+
+ x = CLAMP (x, monitor.x, monitor.x + monitor.width - width);
+ y = CLAMP (y, monitor.y, monitor.y + monitor.height - height);
+
+ gtk_window_move (GTK_WINDOW (combo->priv->window), x, y);
+}
+
+static void
+color_combo_popup (EColorCombo *combo)
+{
+ GdkWindow *window;
+ gboolean grab_status;
+ GdkDevice *device, *mouse, *keyboard;
+ guint32 activate_time;
+
+ device = gtk_get_current_event_device ();
+ g_return_if_fail (device != NULL);
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (combo)))
+ return;
+
+ if (combo->priv->popup_shown)
+ return;
+
+ activate_time = gtk_get_current_event_time ();
+ if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) {
+ keyboard = device;
+ mouse = gdk_device_get_associated_device (device);
+ } else {
+ keyboard = gdk_device_get_associated_device (device);
+ mouse = device;
+ }
+
+ /* Position the window over the button. */
+ color_combo_reposition_window (combo);
+
+ /* Show the pop-up. */
+ gtk_widget_show_all (combo->priv->window);
+ gtk_widget_grab_focus (combo->priv->window);
+
+ /* Try to grab the pointer and keyboard. */
+ window = gtk_widget_get_window (combo->priv->window);
+ grab_status =
+ (keyboard == NULL) ||
+ (gdk_device_grab (
+ keyboard, window,
+ GDK_OWNERSHIP_WINDOW, TRUE,
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
+ NULL, activate_time) == GDK_GRAB_SUCCESS);
+ if (grab_status) {
+ grab_status =
+ (mouse == NULL) ||
+ (gdk_device_grab (
+ mouse, window,
+ GDK_OWNERSHIP_WINDOW, TRUE,
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK,
+ NULL, activate_time) == GDK_GRAB_SUCCESS);
+ if (!grab_status && keyboard)
+ gdk_device_ungrab (keyboard, activate_time);
+ }
+
+ if (grab_status) {
+ gtk_device_grab_add (combo->priv->window, mouse, TRUE);
+ combo->priv->grab_keyboard = keyboard;
+ combo->priv->grab_mouse = mouse;
+ } else {
+ gtk_widget_hide (combo->priv->window);
+ }
+
+ /* Always make sure the editor-mode is OFF */
+ g_object_set (
+ G_OBJECT (combo->priv->chooser_widget),
+ "show-editor", FALSE, NULL);
+}
+
+static void
+color_combo_popdown (EColorCombo *combo)
+{
+ if (!gtk_widget_get_realized (GTK_WIDGET (combo)))
+ return;
+
+ if (!combo->priv->popup_shown)
+ return;
+
+ /* Hide the pop-up. */
+ gtk_device_grab_remove (combo->priv->window, combo->priv->grab_mouse);
+ gtk_widget_hide (combo->priv->window);
+
+ if (combo->priv->grab_keyboard)
+ gdk_device_ungrab (combo->priv->grab_keyboard, GDK_CURRENT_TIME);
+ if (combo->priv->grab_mouse)
+ gdk_device_ungrab (combo->priv->grab_mouse, GDK_CURRENT_TIME);
+
+ combo->priv->grab_keyboard = NULL;
+ combo->priv->grab_mouse = NULL;
+}
+
+static gboolean
+color_combo_window_button_press_event_cb (EColorCombo *combo,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GtkWidget *event_widget;
+
+ event_widget = gtk_get_event_widget ((GdkEvent *) event);
+
+ if (event_widget == combo->priv->window)
+ return TRUE;
+
+ if (combo->priv->popup_shown == TRUE)
+ return FALSE;
+
+ color_combo_popup (combo);
+
+ combo->priv->popup_in_progress = TRUE;
+
+ return TRUE;
+}
+
+static gboolean
+color_combo_window_button_release_event_cb (EColorCombo *combo,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ gboolean popup_in_progress;
+
+ popup_in_progress = combo->priv->popup_in_progress;
+ combo->priv->popup_in_progress = FALSE;
+
+ if (popup_in_progress)
+ return FALSE;
+
+ if (combo->priv->popup_shown)
+ goto popdown;
+
+ return FALSE;
+
+popdown:
+ color_combo_popdown (combo);
+
+ return TRUE;
+}
+
+static void
+color_combo_child_show_cb (EColorCombo *combo)
+{
+ combo->priv->popup_shown = TRUE;
+ g_object_notify (G_OBJECT (combo), "popup-shown");
+}
+
+static void
+color_combo_child_hide_cb (EColorCombo *combo)
+{
+ combo->priv->popup_shown = FALSE;
+ g_object_notify (G_OBJECT (combo), "popup-shown");
+}
+
+static void
+color_combo_get_preferred_width (GtkWidget *widget,
+ gint *min_width,
+ gint *natural_width)
+{
+ GtkWidgetClass *widget_class;
+
+ widget_class = GTK_WIDGET_CLASS (e_color_combo_parent_class);
+ widget_class->get_preferred_width (widget, min_width, natural_width);
+
+ /* Make sure the box with color sample is always visible */
+ if (min_width)
+ *min_width += 20;
+
+ if (natural_width)
+ *natural_width += 20;
+}
+
+static gboolean
+color_combo_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EColorCombo *combo = E_COLOR_COMBO (widget);
+ GdkWindow *window;
+ gint x, y, width, height;
+
+ window = gtk_widget_get_window (combo->priv->color_frame);
+ gdk_window_get_position (window, &x, &y);
+ /* Width - only width of the frame with color box */
+ width = gtk_widget_get_allocated_width (combo->priv->color_frame);
+
+ /* Height - height of the entire button (widget) */
+ height = gtk_widget_get_allocated_height (widget);
+
+ /* Check whether user clicked on the color frame - in such case
+ * apply the color immediatelly without displaying the popup widget */
+ if ((event->x_root >= x) && (event->x_root <= x + width) &&
+ (event->y_root >= y) && (event->y_root <= y + height)) {
+ GdkRGBA color;
+
+ e_color_combo_get_current_color (combo, &color);
+ g_signal_emit (combo, signals[ACTIVATED], 0, &color);
+
+ return TRUE;
+ }
+
+ /* Otherwise display the popup widget */
+ if (combo->priv->popup_shown) {
+ color_combo_popdown (combo);
+ } else {
+ color_combo_popup (combo);
+ }
+
+ return FALSE;
+}
+
+static void
+color_combo_swatch_color_changed (EColorCombo *combo,
+ GdkRGBA *color,
+ gpointer user_data)
+{
+ g_signal_emit (combo, signals[ACTIVATED], 0, color);
+
+ e_color_combo_set_current_color (combo, color);
+
+ color_combo_popdown (combo);
+}
+
+static void
+color_combo_draw_frame_cb (GtkWidget *widget,
+ cairo_t *cr,
+ gpointer user_data)
+{
+ EColorCombo *combo = user_data;
+ GdkRGBA rgba;
+ GtkAllocation allocation;
+ gint height, width;
+
+ e_color_combo_get_current_color (combo, &rgba);
+
+ gtk_widget_get_allocation (widget, &allocation);
+ width = allocation.width;
+ height = allocation.height;
+
+ cairo_rectangle (cr, 0, 0, width - 10, height);
+ cairo_set_source_rgb (cr, rgba.red, rgba.green, rgba.blue);
+ cairo_fill (cr);
+}
+
+static void
+color_combo_set_default_color_cb (EColorCombo *combo,
+ gpointer user_data)
+{
+ GdkRGBA color;
+
+ e_color_combo_get_default_color (combo, &color);
+ e_color_combo_set_current_color (combo, &color);
+
+ g_signal_emit (combo, signals[ACTIVATED], 0, &color);
+}
+
+static void
+color_combo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_COLOR:
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (object),
+ g_value_get_boxed (value));
+ return;
+
+ case PROP_DEFAULT_COLOR:
+ e_color_combo_set_default_color (
+ E_COLOR_COMBO (object),
+ g_value_get_boxed (value));
+ return;
+
+ case PROP_DEFAULT_LABEL:
+ e_color_combo_set_default_label (
+ E_COLOR_COMBO (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_DEFAULT_TRANSPARENT:
+ e_color_combo_set_default_transparent (
+ E_COLOR_COMBO (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_PALETTE:
+ e_color_combo_set_palette (
+ E_COLOR_COMBO (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_POPUP_SHOWN:
+ if (g_value_get_boolean (value))
+ e_color_combo_popup (
+ E_COLOR_COMBO (object));
+ else
+ e_color_combo_popdown (
+ E_COLOR_COMBO (object));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+color_combo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EColorComboPrivate *priv;
+ GdkRGBA color;
+
+ priv = E_COLOR_COMBO_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_CURRENT_COLOR:
+ e_color_combo_get_current_color (
+ E_COLOR_COMBO (object), &color);
+ g_value_set_boxed (value, &color);
+ return;
+
+ case PROP_DEFAULT_COLOR:
+ e_color_combo_get_default_color (
+ E_COLOR_COMBO (object), &color);
+ g_value_set_boxed (value, &color);
+ return;
+
+ case PROP_DEFAULT_LABEL:
+ g_value_set_string (
+ value, e_color_combo_get_default_label (
+ E_COLOR_COMBO (object)));
+ return;
+
+ case PROP_DEFAULT_TRANSPARENT:
+ g_value_set_boolean (
+ value,
+ e_color_combo_get_default_transparent (
+ E_COLOR_COMBO (object)));
+ return;
+
+ case PROP_PALETTE:
+ g_value_set_object (
+ value, e_color_combo_get_palette (
+ E_COLOR_COMBO (object)));
+ return;
+
+ case PROP_POPUP_SHOWN:
+ g_value_set_boolean (value, priv->popup_shown);
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+color_combo_dispose (GObject *object)
+{
+ EColorComboPrivate *priv;
+
+ priv = E_COLOR_COMBO_GET_PRIVATE (object);
+
+ if (priv->window != NULL) {
+ gtk_widget_destroy (priv->window);
+ priv->window = NULL;
+ }
+
+ if (priv->current_color != NULL) {
+ gdk_rgba_free (priv->current_color);
+ priv->current_color = NULL;
+ }
+
+ if (priv->default_color != NULL) {
+ gdk_rgba_free (priv->default_color);
+ priv->default_color = NULL;
+ }
+
+ g_list_free_full (priv->palette, (GDestroyNotify) gdk_rgba_free);
+ priv->palette = NULL;
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_color_combo_parent_class)->dispose (object);
+}
+
+static void
+e_color_combo_class_init (EColorComboClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EColorComboPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = color_combo_set_property;
+ object_class->get_property = color_combo_get_property;
+ object_class->dispose = color_combo_dispose;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->get_preferred_width = color_combo_get_preferred_width;
+ widget_class->button_press_event = color_combo_button_press_event_cb;
+
+ class->popup = color_combo_popup;
+ class->popdown = color_combo_popdown;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CURRENT_COLOR,
+ g_param_spec_boxed (
+ "current-color",
+ "Current color",
+ "The currently selected color",
+ GDK_TYPE_RGBA,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DEFAULT_COLOR,
+ g_param_spec_boxed (
+ "default-color",
+ "Default color",
+ "The color associated with the default button",
+ GDK_TYPE_RGBA,
+ G_PARAM_CONSTRUCT |
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DEFAULT_LABEL,
+ g_param_spec_string (
+ "default-label",
+ "Default label",
+ "The label for the default button",
+ _("Default"),
+ G_PARAM_CONSTRUCT |
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DEFAULT_TRANSPARENT,
+ g_param_spec_boolean (
+ "default-transparent",
+ "Default is transparent",
+ "Whether the default color is transparent",
+ FALSE,
+ G_PARAM_CONSTRUCT |
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PALETTE,
+ g_param_spec_pointer (
+ "palette",
+ "Color palette",
+ "Custom color palette",
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_POPUP_SHOWN,
+ g_param_spec_boolean (
+ "popup-shown",
+ "Popup shown",
+ "Whether the combo's dropdown is shown",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ signals[ACTIVATED] = g_signal_new (
+ "activated",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EColorComboClass, activated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[POPUP] = g_signal_new (
+ "popup",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EColorComboClass, popup),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[POPDOWN] = g_signal_new (
+ "popdown",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EColorComboClass, popdown),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_Down, GDK_MOD1_MASK, "popup", 0);
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_KP_Down, GDK_MOD1_MASK, "popup", 0);
+
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_Up, GDK_MOD1_MASK, "popdown", 0);
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_KP_Up, GDK_MOD1_MASK, "popdown", 0);
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_Escape, 0, "popdown", 0);
+}
+
+static void
+e_color_combo_init (EColorCombo *combo)
+{
+ GtkWidget *container;
+ GtkWidget *toplevel;
+ GtkWidget *widget;
+ GList *palette;
+ guint ii;
+
+ combo->priv = E_COLOR_COMBO_GET_PRIVATE (combo);
+
+ widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (combo), widget);
+
+ container = widget;
+
+ /* Build the combo button. */
+ widget = gtk_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ g_signal_connect (
+ widget, "draw",
+ G_CALLBACK (color_combo_draw_frame_cb), combo);
+ combo->priv->color_frame = widget; /* do not reference */
+
+ widget = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+
+ widget = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, TRUE, 0);
+ combo->priv->arrow = widget; /* do not reference */
+
+ /* Build the drop-down menu */
+ widget = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_container_set_border_width (GTK_CONTAINER (widget), 5);
+ gtk_window_set_resizable (GTK_WINDOW (widget), FALSE);
+ gtk_window_set_type_hint (
+ GTK_WINDOW (widget), GDK_WINDOW_TYPE_HINT_COMBO);
+ combo->priv->window = g_object_ref_sink (widget);
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (combo));
+ if (GTK_IS_WINDOW (toplevel)) {
+ gtk_window_group_add_window (
+ gtk_window_get_group (GTK_WINDOW (toplevel)),
+ GTK_WINDOW (widget));
+ gtk_window_set_transient_for (
+ GTK_WINDOW (widget), GTK_WINDOW (toplevel));
+ }
+
+ g_signal_connect_swapped (
+ widget, "show",
+ G_CALLBACK (color_combo_child_show_cb), combo);
+ g_signal_connect_swapped (
+ widget, "hide",
+ G_CALLBACK (color_combo_child_hide_cb), combo);
+ g_signal_connect_swapped (
+ widget, "button-press-event",
+ G_CALLBACK (color_combo_window_button_press_event_cb), combo);
+ g_signal_connect_swapped (
+ widget, "button-release-event",
+ G_CALLBACK (color_combo_window_button_release_event_cb), combo);
+
+ container = widget;
+
+ widget = gtk_grid_new ();
+ gtk_grid_set_row_spacing (GTK_GRID (widget), 5);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+
+ container = widget;
+
+ widget = gtk_button_new ();
+ gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 1, 1);
+ combo->priv->default_button = widget; /* do not reference */
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (color_combo_set_default_color_cb), combo);
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (color_combo_popdown), combo);
+
+ widget = e_color_chooser_widget_new ();
+ g_object_set_data (G_OBJECT (widget), "window", combo->priv->window);
+ gtk_grid_attach (GTK_GRID (container), widget, 0, 1, 1, 1);
+ combo->priv->chooser_widget = widget; /* do not reference */
+
+ g_signal_connect_swapped (
+ widget, "color-activated",
+ G_CALLBACK (color_combo_swatch_color_changed), combo);
+ g_signal_connect_swapped (
+ widget, "editor-activated",
+ G_CALLBACK (color_combo_popdown), combo);
+
+ palette = NULL;
+ for (ii = 0; ii < G_N_ELEMENTS (default_colors); ii++) {
+ GdkRGBA *color = g_new0 (GdkRGBA, 1);
+ gdk_rgba_parse (color, default_colors[ii].color);
+
+ palette = g_list_prepend (palette, color);
+ }
+ palette = g_list_reverse (palette);
+ e_color_combo_set_palette (combo, palette);
+ g_list_free_full (palette, (GDestroyNotify) g_free);
+
+ combo->priv->current_color = gdk_rgba_copy (&black);
+ combo->priv->default_color = gdk_rgba_copy (&black);
+}
+
+GtkWidget *
+e_color_combo_new (void)
+{
+ return g_object_new (E_TYPE_COLOR_COMBO, NULL);
+}
+
+GtkWidget *
+e_color_combo_new_defaults (GdkRGBA *default_color,
+ const gchar *default_label)
+{
+ g_return_val_if_fail (default_color != NULL, NULL);
+ g_return_val_if_fail (default_label != NULL, NULL);
+
+ return g_object_new (
+ E_TYPE_COLOR_COMBO,
+ "default-color", default_color,
+ "default-label", default_label,
+ NULL);
+}
+
+void
+e_color_combo_popup (EColorCombo *combo)
+{
+ g_return_if_fail (E_IS_COLOR_COMBO (combo));
+
+ g_signal_emit (combo, signals[POPUP], 0);
+}
+
+void
+e_color_combo_popdown (EColorCombo *combo)
+{
+ g_return_if_fail (E_IS_COLOR_COMBO (combo));
+
+ g_signal_emit (combo, signals[POPDOWN], 0);
+}
+
+void
+e_color_combo_get_current_color (EColorCombo *combo,
+ GdkRGBA *color)
+{
+ g_return_if_fail (E_IS_COLOR_COMBO (combo));
+ g_return_if_fail (color != NULL);
+
+ color->red = combo->priv->current_color->red;
+ color->green = combo->priv->current_color->green;
+ color->blue = combo->priv->current_color->blue;
+ color->alpha = combo->priv->current_color->alpha;
+}
+
+void
+e_color_combo_set_current_color (EColorCombo *combo,
+ const GdkRGBA *color)
+{
+ g_return_if_fail (E_IS_COLOR_COMBO (combo));
+
+ if (color == NULL)
+ color = &black;
+
+ if (combo->priv->current_color) {
+
+ if (gdk_rgba_equal (color, combo->priv->current_color)) {
+ return;
+ }
+
+ gdk_rgba_free (combo->priv->current_color);
+ }
+
+ combo->priv->current_color = gdk_rgba_copy (color);
+
+ gtk_color_chooser_set_rgba (
+ GTK_COLOR_CHOOSER (combo->priv->chooser_widget), color);
+ gtk_widget_queue_draw (combo->priv->color_frame);
+
+ g_object_notify (G_OBJECT (combo), "current-color");
+}
+
+void
+e_color_combo_get_default_color (EColorCombo *combo,
+ GdkRGBA *color)
+{
+ g_return_if_fail (E_IS_COLOR_COMBO (combo));
+ g_return_if_fail (color != NULL);
+
+ color->red = combo->priv->default_color->red;
+ color->green = combo->priv->default_color->green;
+ color->blue = combo->priv->default_color->blue;
+ color->alpha = combo->priv->default_color->alpha;
+}
+
+void
+e_color_combo_set_default_color (EColorCombo *combo,
+ const GdkRGBA *color)
+{
+ g_return_if_fail (E_IS_COLOR_COMBO (combo));
+
+ if (color == NULL)
+ color = &black;
+
+ if (combo->priv->default_color) {
+
+ if (gdk_rgba_equal (color, combo->priv->default_color)) {
+ return;
+ }
+
+ gdk_rgba_free (combo->priv->default_color);
+ }
+ combo->priv->default_color = gdk_rgba_copy (color);
+
+ gtk_color_chooser_set_rgba (
+ GTK_COLOR_CHOOSER (combo->priv->chooser_widget), color);
+
+ g_object_notify (G_OBJECT (combo), "default-color");
+}
+
+const gchar *
+e_color_combo_get_default_label (EColorCombo *combo)
+{
+ g_return_val_if_fail (E_IS_COLOR_COMBO (combo), NULL);
+
+ return gtk_button_get_label (GTK_BUTTON (combo->priv->default_button));
+}
+
+void
+e_color_combo_set_default_label (EColorCombo *combo,
+ const gchar *text)
+{
+ g_return_if_fail (E_IS_COLOR_COMBO (combo));
+
+ gtk_button_set_label (GTK_BUTTON (combo->priv->default_button), text);
+
+ g_object_notify (G_OBJECT (combo), "default-label");
+}
+
+gboolean
+e_color_combo_get_default_transparent (EColorCombo *combo)
+{
+ g_return_val_if_fail (E_IS_COLOR_COMBO (combo), FALSE);
+
+ return combo->priv->default_transparent;
+}
+
+void
+e_color_combo_set_default_transparent (EColorCombo *combo,
+ gboolean transparent)
+{
+ g_return_if_fail (E_IS_COLOR_COMBO (combo));
+
+ combo->priv->default_transparent = transparent;
+
+ g_object_notify (G_OBJECT (combo), "default-transparent");
+}
+
+GList *
+e_color_combo_get_palette (EColorCombo *combo)
+{
+ g_return_val_if_fail (E_IS_COLOR_COMBO (combo), NULL);
+
+ return g_list_copy (combo->priv->palette);
+}
+
+void
+e_color_combo_set_palette (EColorCombo *combo,
+ GList *palette)
+{
+ gint ii, count, colors_per_line;
+ GList *iter;
+ GdkRGBA *colors;
+
+ g_return_if_fail (E_IS_COLOR_COMBO (combo));
+
+ count = g_list_length (palette);
+ colors_per_line = (count % 10 == 0) ? 10 : 9;
+
+ colors = g_malloc_n (count, sizeof (GdkRGBA));
+ g_list_free_full (combo->priv->palette, (GDestroyNotify) gdk_rgba_free);
+ ii = 0;
+ combo->priv->palette = NULL;
+ for (iter = palette; iter; iter = g_list_next (iter)) {
+ combo->priv->palette = g_list_prepend (
+ combo->priv->palette, gdk_rgba_copy (iter->data));
+
+ colors[ii] = *((GdkRGBA *) iter->data);
+ ii++;
+ }
+ combo->priv->palette = g_list_reverse (combo->priv->palette);
+
+ gtk_color_chooser_add_palette (
+ GTK_COLOR_CHOOSER (combo->priv->chooser_widget),
+ GTK_ORIENTATION_HORIZONTAL, 0, 0, NULL);
+ gtk_color_chooser_add_palette (
+ GTK_COLOR_CHOOSER (combo->priv->chooser_widget),
+ GTK_ORIENTATION_HORIZONTAL, colors_per_line, count, colors);
+ g_free (colors);
+}
diff --git a/e-util/e-color-combo.h b/e-util/e-color-combo.h
new file mode 100644
index 0000000000..41f7fd1574
--- /dev/null
+++ b/e-util/e-color-combo.h
@@ -0,0 +1,96 @@
+/* e-color-combo.h
+ *
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_COLOR_COMBO_H
+#define E_COLOR_COMBO_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_COLOR_COMBO \
+ (e_color_combo_get_type ())
+#define E_COLOR_COMBO(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_COLOR_COMBO, EColorCombo))
+#define E_COLOR_COMBO_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_COLOR_COMBO, EColorComboClass))
+#define E_IS_COLOR_COMBO(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_COLOR_COMBO))
+#define E_IS_COLOR_COMBO_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_COLOR_COMBO))
+#define E_COLOR_COMBO_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_COLOR_COMBO, EColorComboClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EColorCombo EColorCombo;
+typedef struct _EColorComboClass EColorComboClass;
+typedef struct _EColorComboPrivate EColorComboPrivate;
+
+struct _EColorCombo {
+ GtkButton parent;
+ EColorComboPrivate *priv;
+};
+
+struct _EColorComboClass {
+ GtkButtonClass parent_class;
+
+ void (*popup) (EColorCombo *combo);
+ void (*popdown) (EColorCombo *combo);
+ void (*activated) (EColorCombo *combo,
+ GdkRGBA *color);
+};
+
+GType e_color_combo_get_type (void) G_GNUC_CONST;
+GtkWidget * e_color_combo_new (void);
+GtkWidget * e_color_combo_new_defaults (GdkRGBA *default_color,
+ const gchar *default_label);
+void e_color_combo_popup (EColorCombo *combo);
+void e_color_combo_popdown (EColorCombo *combo);
+void e_color_combo_get_current_color (EColorCombo *combo,
+ GdkRGBA *rgba);
+void e_color_combo_set_current_color (EColorCombo *combo,
+ const GdkRGBA *color);
+void e_color_combo_get_default_color (EColorCombo *combo,
+ GdkRGBA *color);
+void e_color_combo_set_default_color (EColorCombo *combo,
+ const GdkRGBA *default_color);
+const gchar * e_color_combo_get_default_label (EColorCombo *combo);
+void e_color_combo_set_default_label (EColorCombo *combo,
+ const gchar *text);
+gboolean e_color_combo_get_default_transparent
+ (EColorCombo *combo);
+void e_color_combo_set_default_transparent
+ (EColorCombo *combo,
+ gboolean transparent);
+GList * e_color_combo_get_palette (EColorCombo *combo);
+void e_color_combo_set_palette (EColorCombo *combo,
+ GList *palette);
+
+G_END_DECLS
+
+#endif /* E_COLOR_COMBO_H */
diff --git a/e-util/e-emoticon-action.c b/e-util/e-emoticon-action.c
new file mode 100644
index 0000000000..0850d33f49
--- /dev/null
+++ b/e-util/e-emoticon-action.c
@@ -0,0 +1,278 @@
+/*
+ * e-emoticon-action.c
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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 "e-emoticon-action.h"
+
+#include "e-emoticon-chooser.h"
+#include "e-emoticon-chooser-menu.h"
+#include "e-emoticon-tool-button.h"
+
+#define E_EMOTICON_ACTION_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_EMOTICON_ACTION, EEmoticonActionPrivate))
+
+struct _EEmoticonActionPrivate {
+ GList *choosers;
+ EEmoticonChooser *current_chooser;
+};
+
+enum {
+ PROP_0,
+ PROP_CURRENT_FACE
+};
+
+/* Forward Declarations */
+static void e_emoticon_action_interface_init
+ (EEmoticonChooserInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ EEmoticonAction,
+ e_emoticon_action,
+ GTK_TYPE_ACTION,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EMOTICON_CHOOSER,
+ e_emoticon_action_interface_init))
+
+static void
+emoticon_action_proxy_item_activated_cb (EEmoticonAction *action,
+ EEmoticonChooser *chooser)
+{
+ action->priv->current_chooser = chooser;
+
+ g_signal_emit_by_name (action, "item-activated");
+}
+
+static void
+emoticon_action_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_FACE:
+ e_emoticon_chooser_set_current_emoticon (
+ E_EMOTICON_CHOOSER (object),
+ g_value_get_boxed (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+emoticon_action_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_FACE:
+ g_value_set_boxed (
+ value, e_emoticon_chooser_get_current_emoticon (
+ E_EMOTICON_CHOOSER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+emoticon_action_finalize (GObject *object)
+{
+ EEmoticonActionPrivate *priv;
+
+ priv = E_EMOTICON_ACTION_GET_PRIVATE (object);
+
+ g_list_free (priv->choosers);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_emoticon_action_parent_class)->finalize (object);
+}
+
+static void
+emoticon_action_activate (GtkAction *action)
+{
+ EEmoticonActionPrivate *priv;
+
+ priv = E_EMOTICON_ACTION_GET_PRIVATE (action);
+
+ priv->current_chooser = NULL;
+}
+
+static GtkWidget *
+emoticon_action_create_menu_item (GtkAction *action)
+{
+ GtkWidget *item;
+ GtkWidget *menu;
+
+ item = gtk_image_menu_item_new ();
+ menu = gtk_action_create_menu (action);
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), menu);
+ gtk_widget_show (menu);
+
+ return item;
+}
+
+static GtkWidget *
+emoticon_action_create_tool_item (GtkAction *action)
+{
+ return GTK_WIDGET (e_emoticon_tool_button_new ());
+}
+
+static void
+emoticon_action_connect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ EEmoticonActionPrivate *priv;
+
+ priv = E_EMOTICON_ACTION_GET_PRIVATE (action);
+
+ if (!E_IS_EMOTICON_CHOOSER (proxy))
+ goto chainup;
+
+ if (g_list_find (priv->choosers, proxy) != NULL)
+ goto chainup;
+
+ g_signal_connect_swapped (
+ proxy, "item-activated",
+ G_CALLBACK (emoticon_action_proxy_item_activated_cb), action);
+
+chainup:
+ /* Chain up to parent's connect_proxy() method. */
+ GTK_ACTION_CLASS (e_emoticon_action_parent_class)->
+ connect_proxy (action, proxy);
+}
+
+static void
+emoticon_action_disconnect_proxy (GtkAction *action,
+ GtkWidget *proxy)
+{
+ EEmoticonActionPrivate *priv;
+
+ priv = E_EMOTICON_ACTION_GET_PRIVATE (action);
+
+ priv->choosers = g_list_remove (priv->choosers, proxy);
+
+ /* Chain up to parent's disconnect_proxy() method. */
+ GTK_ACTION_CLASS (e_emoticon_action_parent_class)->
+ disconnect_proxy (action, proxy);
+}
+
+static GtkWidget *
+emoticon_action_create_menu (GtkAction *action)
+{
+ EEmoticonActionPrivate *priv;
+ GtkWidget *widget;
+
+ priv = E_EMOTICON_ACTION_GET_PRIVATE (action);
+
+ widget = e_emoticon_chooser_menu_new ();
+
+ g_signal_connect_swapped (
+ widget, "item-activated",
+ G_CALLBACK (emoticon_action_proxy_item_activated_cb), action);
+
+ priv->choosers = g_list_prepend (priv->choosers, widget);
+
+ return widget;
+}
+
+static EEmoticon *
+emoticon_action_get_current_emoticon (EEmoticonChooser *chooser)
+{
+ EEmoticonActionPrivate *priv;
+ EEmoticon *emoticon = NULL;
+
+ priv = E_EMOTICON_ACTION_GET_PRIVATE (chooser);
+
+ if (priv->current_chooser != NULL)
+ emoticon = e_emoticon_chooser_get_current_emoticon (
+ priv->current_chooser);
+
+ return emoticon;
+}
+
+static void
+emoticon_action_set_current_emoticon (EEmoticonChooser *chooser,
+ EEmoticon *emoticon)
+{
+ EEmoticonActionPrivate *priv;
+ GList *iter;
+
+ priv = E_EMOTICON_ACTION_GET_PRIVATE (chooser);
+
+ for (iter = priv->choosers; iter != NULL; iter = iter->next) {
+ EEmoticonChooser *proxy_chooser = iter->data;
+
+ e_emoticon_chooser_set_current_emoticon (proxy_chooser, emoticon);
+ }
+}
+
+static void
+e_emoticon_action_class_init (EEmoticonActionClass *class)
+{
+ GObjectClass *object_class;
+ GtkActionClass *action_class;
+
+ g_type_class_add_private (class, sizeof (EEmoticonAction));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = emoticon_action_set_property;
+ object_class->get_property = emoticon_action_get_property;
+ object_class->finalize = emoticon_action_finalize;
+
+ action_class = GTK_ACTION_CLASS (class);
+ action_class->activate = emoticon_action_activate;
+ action_class->create_menu_item = emoticon_action_create_menu_item;
+ action_class->create_tool_item = emoticon_action_create_tool_item;
+ action_class->connect_proxy = emoticon_action_connect_proxy;
+ action_class->disconnect_proxy = emoticon_action_disconnect_proxy;
+ action_class->create_menu = emoticon_action_create_menu;
+
+ g_object_class_override_property (
+ object_class, PROP_CURRENT_FACE, "current-emoticon");
+}
+
+static void
+e_emoticon_action_interface_init (EEmoticonChooserInterface *interface)
+{
+ interface->get_current_emoticon = emoticon_action_get_current_emoticon;
+ interface->set_current_emoticon = emoticon_action_set_current_emoticon;
+}
+
+static void
+e_emoticon_action_init (EEmoticonAction *action)
+{
+ action->priv = E_EMOTICON_ACTION_GET_PRIVATE (action);
+}
+
+GtkAction *
+e_emoticon_action_new (const gchar *name,
+ const gchar *label,
+ const gchar *tooltip,
+ const gchar *stock_id)
+{
+ g_return_val_if_fail (name != NULL, NULL);
+
+ return g_object_new (
+ E_TYPE_EMOTICON_ACTION, "name", name, "label", label,
+ "tooltip", tooltip, "stock-id", stock_id, NULL);
+}
diff --git a/e-util/e-emoticon-action.h b/e-util/e-emoticon-action.h
new file mode 100644
index 0000000000..0e450e8750
--- /dev/null
+++ b/e-util/e-emoticon-action.h
@@ -0,0 +1,73 @@
+/*
+ * e-emoticon-action.h
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_EMOTICON_ACTION_H
+#define E_EMOTICON_ACTION_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_EMOTICON_ACTION \
+ (e_emoticon_action_get_type ())
+#define E_EMOTICON_ACTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_EMOTICON_ACTION, EEmoticonAction))
+#define E_EMOTICON_ACTION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_EMOTICON_ACTION, EEmoticonActionClass))
+#define E_IS_EMOTICON_ACTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_EMOTICON_ACTION))
+#define E_IS_EMOTICON_ACTION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_EMOTICON_ACTION))
+#define E_EMOTICON_ACTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_EMOTICON_ACTION, EEmoticonActionClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EEmoticonAction EEmoticonAction;
+typedef struct _EEmoticonActionClass EEmoticonActionClass;
+typedef struct _EEmoticonActionPrivate EEmoticonActionPrivate;
+
+struct _EEmoticonAction {
+ GtkAction parent;
+ EEmoticonActionPrivate *priv;
+};
+
+struct _EEmoticonActionClass {
+ GtkActionClass parent_class;
+};
+
+GType e_emoticon_action_get_type (void) G_GNUC_CONST;
+GtkAction * e_emoticon_action_new (const gchar *name,
+ const gchar *label,
+ const gchar *tooltip,
+ const gchar *stock_id);
+
+G_END_DECLS
+
+#endif /* E_EMOTICON_ACTION_H */
diff --git a/e-util/e-emoticon-chooser-menu.c b/e-util/e-emoticon-chooser-menu.c
new file mode 100644
index 0000000000..f2ed3376cf
--- /dev/null
+++ b/e-util/e-emoticon-chooser-menu.c
@@ -0,0 +1,184 @@
+/*
+ * e-emoticon-chooser-menu.c
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-emoticon-chooser-menu.h"
+#include "e-emoticon-chooser.h"
+
+#include <glib/gi18n-lib.h>
+
+enum {
+ PROP_0,
+ PROP_CURRENT_FACE
+};
+
+/* Forward Declarations */
+static void e_emoticon_chooser_menu_interface_init
+ (EEmoticonChooserInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ EEmoticonChooserMenu,
+ e_emoticon_chooser_menu,
+ GTK_TYPE_MENU,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EMOTICON_CHOOSER,
+ e_emoticon_chooser_menu_interface_init))
+
+static void
+emoticon_chooser_menu_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_FACE:
+ e_emoticon_chooser_set_current_emoticon (
+ E_EMOTICON_CHOOSER (object),
+ g_value_get_boxed (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+emoticon_chooser_menu_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_FACE:
+ g_value_set_boxed (
+ value,
+ e_emoticon_chooser_get_current_emoticon (
+ E_EMOTICON_CHOOSER (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static EEmoticon *
+emoticon_chooser_menu_get_current_emoticon (EEmoticonChooser *chooser)
+{
+ GtkWidget *item;
+
+ item = gtk_menu_get_active (GTK_MENU (chooser));
+ if (item == NULL)
+ return NULL;
+
+ return g_object_get_data (G_OBJECT (item), "emoticon");
+}
+
+static void
+emoticon_chooser_menu_set_current_emoticon (EEmoticonChooser *chooser,
+ EEmoticon *emoticon)
+{
+ GList *list, *iter;
+
+ list = gtk_container_get_children (GTK_CONTAINER (chooser));
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ GtkWidget *item = iter->data;
+ EEmoticon *candidate;
+
+ candidate = g_object_get_data (G_OBJECT (item), "emoticon");
+ if (candidate == NULL)
+ continue;
+
+ if (e_emoticon_equal (emoticon, candidate)) {
+ gtk_menu_shell_activate_item (
+ GTK_MENU_SHELL (chooser), item, TRUE);
+ break;
+ }
+ }
+
+ g_list_free (list);
+}
+
+static void
+e_emoticon_chooser_menu_class_init (EEmoticonChooserMenuClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = emoticon_chooser_menu_set_property;
+ object_class->get_property = emoticon_chooser_menu_get_property;
+
+ g_object_class_override_property (
+ object_class, PROP_CURRENT_FACE, "current-emoticon");
+}
+
+static void
+e_emoticon_chooser_menu_interface_init (EEmoticonChooserInterface *interface)
+{
+ interface->get_current_emoticon =
+ emoticon_chooser_menu_get_current_emoticon;
+ interface->set_current_emoticon =
+ emoticon_chooser_menu_set_current_emoticon;
+}
+
+static void
+e_emoticon_chooser_menu_init (EEmoticonChooserMenu *chooser_menu)
+{
+ EEmoticonChooser *chooser;
+ GList *list, *iter;
+
+ chooser = E_EMOTICON_CHOOSER (chooser_menu);
+ list = e_emoticon_chooser_get_items ();
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ EEmoticon *emoticon = iter->data;
+ GtkWidget *item;
+
+ /* To keep translated strings in subclasses */
+ item = gtk_image_menu_item_new_with_mnemonic (_(emoticon->label));
+ gtk_image_menu_item_set_image (
+ GTK_IMAGE_MENU_ITEM (item),
+ gtk_image_new_from_icon_name (
+ emoticon->icon_name, GTK_ICON_SIZE_MENU));
+ gtk_widget_show (item);
+
+ g_object_set_data_full (
+ G_OBJECT (item), "emoticon",
+ e_emoticon_copy (emoticon),
+ (GDestroyNotify) e_emoticon_free);
+
+ g_signal_connect_swapped (
+ item, "activate",
+ G_CALLBACK (e_emoticon_chooser_item_activated),
+ chooser);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (chooser_menu), item);
+ }
+
+ g_list_free (list);
+}
+
+GtkWidget *
+e_emoticon_chooser_menu_new (void)
+{
+ return g_object_new (E_TYPE_EMOTICON_CHOOSER_MENU, NULL);
+}
diff --git a/e-util/e-emoticon-chooser-menu.h b/e-util/e-emoticon-chooser-menu.h
new file mode 100644
index 0000000000..d4f99543bf
--- /dev/null
+++ b/e-util/e-emoticon-chooser-menu.h
@@ -0,0 +1,70 @@
+/*
+ * e-emoticon-chooser-menu.h
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_EMOTICON_CHOOSER_MENU_H
+#define E_EMOTICON_CHOOSER_MENU_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_EMOTICON_CHOOSER_MENU \
+ (e_emoticon_chooser_menu_get_type ())
+#define E_EMOTICON_CHOOSER_MENU(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_EMOTICON_CHOOSER_MENU, EEmoticonChooserMenu))
+#define E_EMOTICON_CHOOSER_MENU_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_EMOTICON_CHOOSER_MENU, EEmoticonChooserMenuClass))
+#define E_IS_EMOTICON_CHOOSER_MENU(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_EMOTICON_CHOOSER_MENU))
+#define E_IS_EMOTICON_CHOOSER_MENU_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_EMOTICON_CHOOSER_MENU))
+#define E_EMOTICON_CHOOSER_MENU_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_EMOTICON_CHOOSER_MENU, EEmoticonChooserMenuClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EEmoticonChooserMenu EEmoticonChooserMenu;
+typedef struct _EEmoticonChooserMenuClass EEmoticonChooserMenuClass;
+typedef struct _EEmoticonChooserMenuPrivate EEmoticonChooserMenuPrivate;
+
+struct _EEmoticonChooserMenu {
+ GtkMenu parent;
+};
+
+struct _EEmoticonChooserMenuClass {
+ GtkMenuClass parent_class;
+};
+
+GType e_emoticon_chooser_menu_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_emoticon_chooser_menu_new (void);
+
+G_END_DECLS
+
+#endif /* E_EMOTICON_CHOOSER_MENU_H */
diff --git a/e-util/e-emoticon-chooser.c b/e-util/e-emoticon-chooser.c
new file mode 100644
index 0000000000..44ce06ba55
--- /dev/null
+++ b/e-util/e-emoticon-chooser.c
@@ -0,0 +1,178 @@
+/*
+ * e-emoticon-chooser.c
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-emoticon-chooser.h"
+
+#include <glib/gi18n-lib.h>
+
+/* Constant version of EEMoticon. */
+typedef struct {
+ const gchar *label;
+ const gchar *icon_name;
+ const gchar *text_face;
+} ConstantEmoticon;
+
+static ConstantEmoticon available_emoticons[] = {
+ /* Translators: :-) */
+ { N_("_Smile"), "face-smile", ":-)" },
+ /* Translators: :-( */
+ { N_("S_ad"), "face-sad", ":-(" },
+ /* Translators: ;-) */
+ { N_("_Wink"), "face-wink", ";-)" },
+ /* Translators: :-P */
+ { N_("Ton_gue"), "face-raspberry", ":-P" },
+ /* Translators: :-)) */
+ { N_("Laug_h"), "face-laugh", ":-))" },
+ /* Translators: :-| */
+ { N_("_Plain"), "face-plain", ":-|" },
+ /* Translators: :-! */
+ { N_("Smi_rk"), "face-smirk", ":-!" },
+ /* Translators: :"-) */
+ { N_("_Embarrassed"), "face-embarrassed", ":\"-)" },
+ /* Translators: :-D */
+ { N_("_Big Smile"), "face-smile-big", ":-D" },
+ /* Translators: :-/ */
+ { N_("Uncer_tain"), "face-uncertain", ":-/" },
+ /* Translators: :-O */
+ { N_("S_urprise"), "face-surprise", ":-O" },
+ /* Translators: :-S */
+ { N_("W_orried"), "face-worried", ":-S" },
+ /* Translators: :-* */
+ { N_("_Kiss"), "face-kiss", ":-*" },
+ /* Translators: X-( */
+ { N_("A_ngry"), "face-angry", "X-(" },
+ /* Translators: B-) */
+ { N_("_Cool"), "face-cool", "B-)" },
+ /* Translators: O:-) */
+ { N_("Ange_l"), "face-angel", "O:-)" },
+ /* Translators: :'( */
+ { N_("Cr_ying"), "face-crying", ":'(" },
+ /* Translators: :-Q */
+ { N_("S_ick"), "face-sick", ":-Q" },
+ /* Translators: |-) */
+ { N_("Tire_d"), "face-tired", "|-)" },
+ /* Translators: >:-) */
+ { N_("De_vilish"), "face-devilish", ">:-)" },
+ /* Translators: :-(|) */
+ { N_("_Monkey"), "face-monkey", ":-(|)" }
+};
+
+enum {
+ ITEM_ACTIVATED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_INTERFACE (
+ EEmoticonChooser,
+ e_emoticon_chooser,
+ G_TYPE_OBJECT)
+
+static void
+e_emoticon_chooser_default_init (EEmoticonChooserInterface *interface)
+{
+ g_object_interface_install_property (
+ interface,
+ g_param_spec_boxed (
+ "current-emoticon",
+ "Current Emoticon",
+ "Currently selected emoticon",
+ E_TYPE_EMOTICON,
+ G_PARAM_READWRITE));
+
+ signals[ITEM_ACTIVATED] = g_signal_new (
+ "item-activated",
+ G_TYPE_FROM_INTERFACE (interface),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EEmoticonChooserInterface, item_activated),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+EEmoticon *
+e_emoticon_chooser_get_current_emoticon (EEmoticonChooser *chooser)
+{
+ EEmoticonChooserInterface *interface;
+
+ g_return_val_if_fail (E_IS_EMOTICON_CHOOSER (chooser), NULL);
+
+ interface = E_EMOTICON_CHOOSER_GET_INTERFACE (chooser);
+ g_return_val_if_fail (interface->get_current_emoticon != NULL, NULL);
+
+ return interface->get_current_emoticon (chooser);
+}
+
+void
+e_emoticon_chooser_set_current_emoticon (EEmoticonChooser *chooser,
+ EEmoticon *emoticon)
+{
+ EEmoticonChooserInterface *interface;
+
+ g_return_if_fail (E_IS_EMOTICON_CHOOSER (chooser));
+
+ interface = E_EMOTICON_CHOOSER_GET_INTERFACE (chooser);
+ g_return_if_fail (interface->set_current_emoticon != NULL);
+
+ interface->set_current_emoticon (chooser, emoticon);
+}
+
+void
+e_emoticon_chooser_item_activated (EEmoticonChooser *chooser)
+{
+ g_return_if_fail (E_IS_EMOTICON_CHOOSER (chooser));
+
+ g_signal_emit (chooser, signals[ITEM_ACTIVATED], 0);
+}
+
+GList *
+e_emoticon_chooser_get_items (void)
+{
+ GList *list = NULL;
+ gint ii;
+
+ for (ii = 0; ii < G_N_ELEMENTS (available_emoticons); ii++)
+ list = g_list_prepend (list, &available_emoticons[ii]);
+
+ return g_list_reverse (list);
+}
+
+const EEmoticon *
+e_emoticon_chooser_lookup_emoticon (const gchar *icon_name)
+{
+ gint ii;
+
+ g_return_val_if_fail (icon_name && *icon_name, NULL);
+
+ for (ii = 0; ii < G_N_ELEMENTS (available_emoticons); ii++) {
+ if (strcmp (available_emoticons[ii].icon_name, icon_name) == 0) {
+ return (const EEmoticon *) &available_emoticons[ii];
+ }
+ }
+
+ return NULL;
+}
+
diff --git a/e-util/e-emoticon-chooser.h b/e-util/e-emoticon-chooser.h
new file mode 100644
index 0000000000..14e899fd4e
--- /dev/null
+++ b/e-util/e-emoticon-chooser.h
@@ -0,0 +1,77 @@
+/*
+ * e-emoticon-chooser.h
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_EMOTICON_CHOOSER_H
+#define E_EMOTICON_CHOOSER_H
+
+#include <e-util/e-emoticon.h>
+
+/* Standard GObject macros */
+#define E_TYPE_EMOTICON_CHOOSER \
+ (e_emoticon_chooser_get_type ())
+#define E_EMOTICON_CHOOSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_EMOTICON_CHOOSER, EEmoticonChooser))
+#define E_IS_EMOTICON_CHOOSER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_EMOTICON_CHOOSER))
+#define E_EMOTICON_CHOOSER_GET_INTERFACE(obj) \
+ (G_TYPE_INSTANCE_GET_INTERFACE \
+ ((obj), E_TYPE_EMOTICON_CHOOSER, EEmoticonChooserInterface))
+
+G_BEGIN_DECLS
+
+typedef struct _EEmoticonChooser EEmoticonChooser;
+typedef struct _EEmoticonChooserInterface EEmoticonChooserInterface;
+
+struct _EEmoticonChooserInterface {
+ GTypeInterface parent_interface;
+
+ /* Methods */
+ EEmoticon * (*get_current_emoticon) (EEmoticonChooser *chooser);
+ void (*set_current_emoticon) (EEmoticonChooser *chooser,
+ EEmoticon *emoticon);
+
+ /* Signals */
+ void (*item_activated) (EEmoticonChooser *chooser);
+};
+
+GType e_emoticon_chooser_get_type (void) G_GNUC_CONST;
+EEmoticon * e_emoticon_chooser_get_current_emoticon
+ (EEmoticonChooser *chooser);
+void e_emoticon_chooser_set_current_emoticon
+ (EEmoticonChooser *chooser,
+ EEmoticon *emoticon);
+void e_emoticon_chooser_item_activated
+ (EEmoticonChooser *chooser);
+
+GList * e_emoticon_chooser_get_items (void);
+const EEmoticon *
+ e_emoticon_chooser_lookup_emoticon
+ (const gchar *icon_name);
+
+G_END_DECLS
+
+#endif /* E_EMOTICON_CHOOSER_H */
diff --git a/e-util/e-emoticon-tool-button.c b/e-util/e-emoticon-tool-button.c
new file mode 100644
index 0000000000..54f99c94b0
--- /dev/null
+++ b/e-util/e-emoticon-tool-button.c
@@ -0,0 +1,695 @@
+/*
+ * e-emoticon-tool-button.c
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-emoticon-tool-button.h"
+
+/* XXX The "button" aspects of this widget are based heavily on the
+ * GtkComboBox tree-view implementation. Consider splitting it
+ * into a reusable "button-with-an-empty-window" widget. */
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "e-emoticon-chooser.h"
+
+#define E_EMOTICON_TOOL_BUTTON_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButtonPrivate))
+
+/* XXX Should calculate this dynamically. */
+#define NUM_ROWS 7
+#define NUM_COLS 3
+
+enum {
+ PROP_0,
+ PROP_CURRENT_EMOTICON,
+ PROP_POPUP_SHOWN
+};
+
+enum {
+ POPUP,
+ POPDOWN,
+ LAST_SIGNAL
+};
+
+struct _EEmoticonToolButtonPrivate {
+ GtkWidget *active_button; /* not referenced */
+ GtkWidget *table;
+ GtkWidget *window;
+
+ guint popup_shown : 1;
+ guint popup_in_progress : 1;
+ GdkDevice *grab_keyboard;
+ GdkDevice *grab_mouse;
+};
+
+static guint signals[LAST_SIGNAL];
+
+/* Forward Declarations */
+static void e_emoticon_tool_button_interface_init
+ (EEmoticonChooserInterface *interface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ EEmoticonToolButton,
+ e_emoticon_tool_button,
+ GTK_TYPE_TOGGLE_TOOL_BUTTON,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EMOTICON_CHOOSER,
+ e_emoticon_tool_button_interface_init))
+
+/* XXX Copied from _gtk_toolbar_elide_underscores() */
+static gchar *
+emoticon_tool_button_elide_underscores (const gchar *original)
+{
+ gchar *q, *result;
+ const gchar *p, *end;
+ gsize len;
+ gboolean last_underscore;
+
+ if (!original)
+ return NULL;
+
+ len = strlen (original);
+ q = result = g_malloc (len + 1);
+ last_underscore = FALSE;
+
+ end = original + len;
+ for (p = original; p < end; p++) {
+ if (!last_underscore && *p == '_')
+ last_underscore = TRUE;
+ else {
+ last_underscore = FALSE;
+ if (original + 2 <= p && p + 1 <= end &&
+ p[-2] == '(' && p[-1] == '_' &&
+ p[0] != '_' && p[1] == ')') {
+ q--;
+ *q = '\0';
+ p++;
+ } else
+ *q++ = *p;
+ }
+ }
+
+ if (last_underscore)
+ *q++ = '_';
+
+ *q = '\0';
+
+ return result;
+}
+
+static void
+emoticon_tool_button_reposition_window (EEmoticonToolButton *button)
+{
+ GdkScreen *screen;
+ GdkWindow *window;
+ GdkRectangle monitor;
+ GtkAllocation allocation;
+ gint monitor_num;
+ gint x, y, width, height;
+
+ screen = gtk_widget_get_screen (GTK_WIDGET (button));
+ window = gtk_widget_get_window (GTK_WIDGET (button));
+ monitor_num = gdk_screen_get_monitor_at_window (screen, window);
+ gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+ gdk_window_get_origin (window, &x, &y);
+
+ if (!gtk_widget_get_has_window (GTK_WIDGET (button))) {
+ gtk_widget_get_allocation (GTK_WIDGET (button), &allocation);
+ x += allocation.x;
+ y += allocation.y;
+ }
+
+ gtk_widget_get_allocation (button->priv->window, &allocation);
+ width = allocation.width;
+ height = allocation.height;
+
+ x = CLAMP (x, monitor.x, monitor.x + monitor.width - width);
+ y = CLAMP (y, monitor.y, monitor.y + monitor.height - height);
+
+ gtk_window_move (GTK_WINDOW (button->priv->window), x, y);
+}
+
+static void
+emoticon_tool_button_emoticon_clicked_cb (EEmoticonToolButton *button,
+ GtkWidget *emoticon_button)
+{
+ button->priv->active_button = emoticon_button;
+ e_emoticon_tool_button_popdown (button);
+}
+
+static gboolean
+emoticon_tool_button_emoticon_release_event_cb (EEmoticonToolButton *button,
+ GdkEventButton *event,
+ GtkButton *emoticon_button)
+{
+ GtkStateType state;
+
+ state = gtk_widget_get_state (GTK_WIDGET (button));
+
+ if (state != GTK_STATE_NORMAL)
+ gtk_button_clicked (emoticon_button);
+
+ return FALSE;
+}
+
+static gboolean
+emoticon_tool_button_button_release_event_cb (EEmoticonToolButton *button,
+ GdkEventButton *event)
+{
+ GtkToggleToolButton *tool_button;
+ GtkWidget *event_widget;
+ gboolean popup_in_progress;
+
+ tool_button = GTK_TOGGLE_TOOL_BUTTON (button);
+ event_widget = gtk_get_event_widget ((GdkEvent *) event);
+
+ popup_in_progress = button->priv->popup_in_progress;
+ button->priv->popup_in_progress = FALSE;
+
+ if (event_widget != GTK_WIDGET (button))
+ goto popdown;
+
+ if (popup_in_progress)
+ return FALSE;
+
+ if (gtk_toggle_tool_button_get_active (tool_button))
+ goto popdown;
+
+ return FALSE;
+
+popdown:
+ e_emoticon_tool_button_popdown (button);
+
+ return TRUE;
+}
+
+static void
+emoticon_tool_button_child_show_cb (EEmoticonToolButton *button)
+{
+ button->priv->popup_shown = TRUE;
+ g_object_notify (G_OBJECT (button), "popup-shown");
+}
+
+static void
+emoticon_tool_button_child_hide_cb (EEmoticonToolButton *button)
+{
+ button->priv->popup_shown = FALSE;
+ g_object_notify (G_OBJECT (button), "popup-shown");
+}
+
+static gboolean
+emoticon_tool_button_child_key_press_event_cb (EEmoticonToolButton *button,
+ GdkEventKey *event)
+{
+ GtkWidget *window = button->priv->window;
+
+ if (!gtk_bindings_activate_event (G_OBJECT (window), event))
+ gtk_bindings_activate_event (G_OBJECT (button), event);
+
+ return TRUE;
+}
+
+static void
+emoticon_tool_button_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CURRENT_EMOTICON:
+ e_emoticon_chooser_set_current_emoticon (
+ E_EMOTICON_CHOOSER (object),
+ g_value_get_boxed (value));
+ return;
+
+ case PROP_POPUP_SHOWN:
+ if (g_value_get_boolean (value))
+ e_emoticon_tool_button_popup (
+ E_EMOTICON_TOOL_BUTTON (object));
+ else
+ e_emoticon_tool_button_popdown (
+ E_EMOTICON_TOOL_BUTTON (object));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+emoticon_tool_button_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EEmoticonToolButtonPrivate *priv;
+
+ priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_CURRENT_EMOTICON:
+ g_value_set_boxed (
+ value,
+ e_emoticon_chooser_get_current_emoticon (
+ E_EMOTICON_CHOOSER (object)));
+ return;
+
+ case PROP_POPUP_SHOWN:
+ g_value_set_boolean (value, priv->popup_shown);
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+emoticon_tool_button_dispose (GObject *object)
+{
+ EEmoticonToolButtonPrivate *priv;
+
+ priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (object);
+
+ if (priv->window != NULL) {
+ gtk_widget_destroy (priv->window);
+ priv->window = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_emoticon_tool_button_parent_class)->dispose (object);
+}
+
+static gboolean
+emoticon_tool_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ EEmoticonToolButton *button;
+ GtkToggleToolButton *toggle_button;
+ GtkWidget *event_widget;
+
+ button = E_EMOTICON_TOOL_BUTTON (widget);
+
+ event_widget = gtk_get_event_widget ((GdkEvent *) event);
+
+ if (event_widget == button->priv->window)
+ return TRUE;
+
+ if (event_widget != widget)
+ return FALSE;
+
+ toggle_button = GTK_TOGGLE_TOOL_BUTTON (widget);
+ if (gtk_toggle_tool_button_get_active (toggle_button))
+ return FALSE;
+
+ e_emoticon_tool_button_popup (button);
+
+ button->priv->popup_in_progress = TRUE;
+
+ return TRUE;
+}
+
+static void
+emoticon_tool_button_toggled (GtkToggleToolButton *button)
+{
+ if (gtk_toggle_tool_button_get_active (button))
+ e_emoticon_tool_button_popup (
+ E_EMOTICON_TOOL_BUTTON (button));
+ else
+ e_emoticon_tool_button_popdown (
+ E_EMOTICON_TOOL_BUTTON (button));
+}
+
+static void
+emoticon_tool_button_popup (EEmoticonToolButton *button)
+{
+ GtkToggleToolButton *tool_button;
+ GdkWindow *window;
+ gboolean grab_status;
+ GdkDevice *device, *mouse, *keyboard;
+ guint32 activate_time;
+
+ device = gtk_get_current_event_device ();
+ g_return_if_fail (device != NULL);
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (button)))
+ return;
+
+ if (button->priv->popup_shown)
+ return;
+
+ activate_time = gtk_get_current_event_time ();
+ if (gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) {
+ keyboard = device;
+ mouse = gdk_device_get_associated_device (device);
+ } else {
+ keyboard = gdk_device_get_associated_device (device);
+ mouse = device;
+ }
+
+ /* Position the window over the button. */
+ emoticon_tool_button_reposition_window (button);
+
+ /* Show the pop-up. */
+ gtk_widget_show (button->priv->window);
+ gtk_widget_grab_focus (button->priv->window);
+
+ /* Activate the tool button. */
+ tool_button = GTK_TOGGLE_TOOL_BUTTON (button);
+ gtk_toggle_tool_button_set_active (tool_button, TRUE);
+
+ /* Try to grab the pointer and keyboard. */
+ window = gtk_widget_get_window (button->priv->window);
+ grab_status = !keyboard ||
+ gdk_device_grab (
+ keyboard, window,
+ GDK_OWNERSHIP_WINDOW, TRUE,
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
+ NULL, activate_time) == GDK_GRAB_SUCCESS;
+ if (grab_status) {
+ grab_status = !mouse ||
+ gdk_device_grab (mouse, window,
+ GDK_OWNERSHIP_WINDOW, TRUE,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK,
+ NULL, activate_time) == GDK_GRAB_SUCCESS;
+ if (!grab_status && keyboard)
+ gdk_device_ungrab (keyboard, activate_time);
+ }
+
+ if (grab_status) {
+ gtk_device_grab_add (button->priv->window, mouse, TRUE);
+ button->priv->grab_keyboard = keyboard;
+ button->priv->grab_mouse = mouse;
+ } else {
+ gtk_widget_hide (button->priv->window);
+ }
+}
+
+static void
+emoticon_tool_button_popdown (EEmoticonToolButton *button)
+{
+ GtkToggleToolButton *tool_button;
+
+ if (!gtk_widget_get_realized (GTK_WIDGET (button)))
+ return;
+
+ if (!button->priv->popup_shown)
+ return;
+
+ /* Hide the pop-up. */
+ gtk_device_grab_remove (button->priv->window, button->priv->grab_mouse);
+ gtk_widget_hide (button->priv->window);
+
+ /* Deactivate the tool button. */
+ tool_button = GTK_TOGGLE_TOOL_BUTTON (button);
+ gtk_toggle_tool_button_set_active (tool_button, FALSE);
+
+ if (button->priv->grab_keyboard)
+ gdk_device_ungrab (button->priv->grab_keyboard, GDK_CURRENT_TIME);
+ if (button->priv->grab_mouse)
+ gdk_device_ungrab (button->priv->grab_mouse, GDK_CURRENT_TIME);
+
+ button->priv->grab_keyboard = NULL;
+ button->priv->grab_mouse = NULL;
+}
+
+static EEmoticon *
+emoticon_tool_button_get_current_emoticon (EEmoticonChooser *chooser)
+{
+ EEmoticonToolButtonPrivate *priv;
+
+ priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (chooser);
+
+ if (priv->active_button == NULL)
+ return NULL;
+
+ return g_object_get_data (G_OBJECT (priv->active_button), "emoticon");
+}
+
+static void
+emoticon_tool_button_set_current_emoticon (EEmoticonChooser *chooser,
+ EEmoticon *emoticon)
+{
+ EEmoticonToolButtonPrivate *priv;
+ GList *list, *iter;
+
+ priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (chooser);
+
+ list = gtk_container_get_children (GTK_CONTAINER (priv->table));
+
+ for (iter = list; iter != NULL; iter = iter->next) {
+ GtkWidget *item = iter->data;
+ EEmoticon *candidate;
+
+ candidate = g_object_get_data (G_OBJECT (item), "emoticon");
+ if (candidate == NULL)
+ continue;
+
+ if (e_emoticon_equal (emoticon, candidate)) {
+ gtk_button_clicked (GTK_BUTTON (item));
+ break;
+ }
+ }
+
+ g_list_free (list);
+}
+
+static void
+e_emoticon_tool_button_class_init (EEmoticonToolButtonClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+ GtkToggleToolButtonClass *toggle_tool_button_class;
+
+ g_type_class_add_private (class, sizeof (EEmoticonToolButtonPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = emoticon_tool_button_set_property;
+ object_class->get_property = emoticon_tool_button_get_property;
+ object_class->dispose = emoticon_tool_button_dispose;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->button_press_event = emoticon_tool_button_press_event;
+
+ toggle_tool_button_class = GTK_TOGGLE_TOOL_BUTTON_CLASS (class);
+ toggle_tool_button_class->toggled = emoticon_tool_button_toggled;
+
+ class->popup = emoticon_tool_button_popup;
+ class->popdown = emoticon_tool_button_popdown;
+
+ g_object_class_override_property (
+ object_class, PROP_CURRENT_EMOTICON, "current-emoticon");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_POPUP_SHOWN,
+ g_param_spec_boolean (
+ "popup-shown",
+ "Popup Shown",
+ "Whether the button's dropdown is shown",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ signals[POPUP] = g_signal_new (
+ "popup",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EEmoticonToolButtonClass, popup),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[POPDOWN] = g_signal_new (
+ "popdown",
+ G_OBJECT_CLASS_TYPE (class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EEmoticonToolButtonClass, popdown),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_Down, GDK_MOD1_MASK, "popup", 0);
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_KP_Down, GDK_MOD1_MASK, "popup", 0);
+
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_Up, GDK_MOD1_MASK, "popdown", 0);
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_KP_Up, GDK_MOD1_MASK, "popdown", 0);
+ gtk_binding_entry_add_signal (
+ gtk_binding_set_by_class (class),
+ GDK_KEY_Escape, 0, "popdown", 0);
+}
+
+static void
+e_emoticon_tool_button_interface_init (EEmoticonChooserInterface *interface)
+{
+ interface->get_current_emoticon =
+ emoticon_tool_button_get_current_emoticon;
+ interface->set_current_emoticon =
+ emoticon_tool_button_set_current_emoticon;
+}
+
+static void
+e_emoticon_tool_button_init (EEmoticonToolButton *button)
+{
+ EEmoticonChooser *chooser;
+ GtkWidget *toplevel;
+ GtkWidget *container;
+ GtkWidget *widget;
+ GtkWidget *window;
+ GList *list, *iter;
+ gint ii;
+
+ button->priv = E_EMOTICON_TOOL_BUTTON_GET_PRIVATE (button);
+
+ /* Build the pop-up window. */
+
+ window = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+ gtk_window_set_type_hint (
+ GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_COMBO);
+ button->priv->window = g_object_ref_sink (window);
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (button));
+ if (GTK_IS_WINDOW (toplevel)) {
+ gtk_window_group_add_window (
+ gtk_window_get_group (GTK_WINDOW (toplevel)),
+ GTK_WINDOW (window));
+ gtk_window_set_transient_for (
+ GTK_WINDOW (window), GTK_WINDOW (toplevel));
+ }
+
+ g_signal_connect_swapped (
+ window, "show",
+ G_CALLBACK (emoticon_tool_button_child_show_cb), button);
+ g_signal_connect_swapped (
+ window, "hide",
+ G_CALLBACK (emoticon_tool_button_child_hide_cb), button);
+ g_signal_connect_swapped (
+ window, "button-release-event",
+ G_CALLBACK (emoticon_tool_button_button_release_event_cb),
+ button);
+ g_signal_connect_swapped (
+ window, "key-press-event",
+ G_CALLBACK (emoticon_tool_button_child_key_press_event_cb),
+ button);
+
+ /* Build the pop-up window contents. */
+
+ widget = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_OUT);
+ gtk_container_add (GTK_CONTAINER (window), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = gtk_table_new (NUM_ROWS, NUM_COLS, TRUE);
+ gtk_table_set_row_spacings (GTK_TABLE (widget), 0);
+ gtk_table_set_col_spacings (GTK_TABLE (widget), 0);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ button->priv->table = g_object_ref (widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ chooser = E_EMOTICON_CHOOSER (button);
+ list = e_emoticon_chooser_get_items ();
+ g_assert (g_list_length (list) <= NUM_ROWS * NUM_COLS);
+
+ for (iter = list, ii = 0; iter != NULL; iter = iter->next, ii++) {
+ EEmoticon *emoticon = iter->data;
+ guint left = ii % NUM_COLS;
+ guint top = ii / NUM_COLS;
+ gchar *tooltip;
+
+ tooltip = emoticon_tool_button_elide_underscores (
+ gettext (emoticon->label));
+
+ widget = gtk_button_new ();
+ gtk_button_set_image (
+ GTK_BUTTON (widget),
+ gtk_image_new_from_icon_name (
+ emoticon->icon_name, GTK_ICON_SIZE_BUTTON));
+ gtk_button_set_relief (GTK_BUTTON (widget), GTK_RELIEF_NONE);
+ gtk_widget_set_tooltip_text (widget, tooltip);
+ gtk_widget_show (widget);
+
+ g_object_set_data_full (
+ G_OBJECT (widget), "emoticon",
+ e_emoticon_copy (emoticon),
+ (GDestroyNotify) e_emoticon_free);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (emoticon_tool_button_emoticon_clicked_cb),
+ button);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (e_emoticon_chooser_item_activated),
+ chooser);
+
+ g_signal_connect_swapped (
+ widget, "button-release-event",
+ G_CALLBACK (emoticon_tool_button_emoticon_release_event_cb),
+ button);
+
+ gtk_table_attach (
+ GTK_TABLE (container), widget,
+ left, left + 1, top, top + 1, 0, 0, 0, 0);
+
+ g_free (tooltip);
+ }
+
+ g_list_free (list);
+}
+
+GtkToolItem *
+e_emoticon_tool_button_new (void)
+{
+ return g_object_new (E_TYPE_EMOTICON_TOOL_BUTTON, NULL);
+}
+
+void
+e_emoticon_tool_button_popup (EEmoticonToolButton *button)
+{
+ g_return_if_fail (E_IS_EMOTICON_TOOL_BUTTON (button));
+
+ g_signal_emit (button, signals[POPUP], 0);
+}
+
+void
+e_emoticon_tool_button_popdown (EEmoticonToolButton *button)
+{
+ g_return_if_fail (E_IS_EMOTICON_TOOL_BUTTON (button));
+
+ g_signal_emit (button, signals[POPDOWN], 0);
+}
diff --git a/e-util/e-emoticon-tool-button.h b/e-util/e-emoticon-tool-button.h
new file mode 100644
index 0000000000..fc7dc8ef64
--- /dev/null
+++ b/e-util/e-emoticon-tool-button.h
@@ -0,0 +1,75 @@
+/*
+ * e-emoticon-tool-button.h
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_EMOTICON_TOOL_BUTTON_H
+#define E_EMOTICON_TOOL_BUTTON_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_EMOTICON_TOOL_BUTTON \
+ (e_emoticon_tool_button_get_type ())
+#define E_EMOTICON_TOOL_BUTTON(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButton))
+#define E_EMOTICON_TOOL_BUTTON_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButtonClass))
+#define E_IS_EMOTICON_TOOL_BUTTON(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_EMOTICON_TOOL_BUTTON))
+#define E_IS_EMOTICON_TOOL_BUTTON_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_EMOTICON_TOOL_BUTTON))
+#define E_EMOTICON_TOOL_BUTTON_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_EMOTICON_TOOL_BUTTON, EEmoticonToolButtonClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EEmoticonToolButton EEmoticonToolButton;
+typedef struct _EEmoticonToolButtonClass EEmoticonToolButtonClass;
+typedef struct _EEmoticonToolButtonPrivate EEmoticonToolButtonPrivate;
+
+struct _EEmoticonToolButton {
+ GtkToggleToolButton parent;
+ EEmoticonToolButtonPrivate *priv;
+};
+
+struct _EEmoticonToolButtonClass {
+ GtkToggleToolButtonClass parent_class;
+
+ void (*popup) (EEmoticonToolButton *button);
+ void (*popdown) (EEmoticonToolButton *button);
+};
+
+GType e_emoticon_tool_button_get_type (void) G_GNUC_CONST;
+GtkToolItem * e_emoticon_tool_button_new (void);
+void e_emoticon_tool_button_popup (EEmoticonToolButton *button);
+void e_emoticon_tool_button_popdown (EEmoticonToolButton *button);
+
+G_END_DECLS
+
+#endif /* E_EMOTICON_TOOL_BUTTON_H */
diff --git a/e-util/e-emoticon.c b/e-util/e-emoticon.c
new file mode 100644
index 0000000000..c543e52417
--- /dev/null
+++ b/e-util/e-emoticon.c
@@ -0,0 +1,118 @@
+/*
+ * e-emoticon.c
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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 "e-emoticon.h"
+
+#include <gtk/gtk.h>
+
+static EEmoticon *
+emoticon_copy (EEmoticon *emoticon)
+{
+ EEmoticon *copy;
+
+ copy = g_slice_new (EEmoticon);
+ copy->label = g_strdup (emoticon->label);
+ copy->icon_name = g_strdup (emoticon->icon_name);
+ copy->text_face = g_strdup (emoticon->text_face);
+
+ return copy;
+}
+
+static void
+emoticon_free (EEmoticon *emoticon)
+{
+ g_free (emoticon->label);
+ g_free (emoticon->icon_name);
+ g_free (emoticon->text_face);
+ g_slice_free (EEmoticon, emoticon);
+}
+
+GType
+e_emoticon_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0))
+ type = g_boxed_type_register_static (
+ "EEmoticon",
+ (GBoxedCopyFunc) emoticon_copy,
+ (GBoxedFreeFunc) emoticon_free);
+
+ return type;
+}
+
+gboolean
+e_emoticon_equal (EEmoticon *emoticon_a,
+ EEmoticon *emoticon_b)
+{
+ if (((emoticon_a == NULL) && (emoticon_b != NULL)) ||
+ ((emoticon_a != NULL) && (emoticon_b == NULL)))
+ return FALSE;
+
+ if (emoticon_a == emoticon_b)
+ return TRUE;
+
+ if (g_strcmp0 (emoticon_a->label, emoticon_b->label) != 0)
+ return FALSE;
+
+ if (g_strcmp0 (emoticon_a->icon_name, emoticon_b->icon_name) != 0)
+ return FALSE;
+
+ if (g_strcmp0 (emoticon_a->text_face, emoticon_b->text_face) != 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+EEmoticon *
+e_emoticon_copy (EEmoticon *emoticon)
+{
+ return g_boxed_copy (E_TYPE_EMOTICON, emoticon);
+}
+
+void
+e_emoticon_free (EEmoticon *emoticon)
+{
+ g_boxed_free (E_TYPE_EMOTICON, emoticon);
+}
+
+gchar *
+e_emoticon_get_uri (EEmoticon *emoticon)
+{
+ GtkIconInfo *icon_info;
+ GtkIconTheme *icon_theme;
+ const gchar *filename;
+ gchar *uri = NULL;
+
+ icon_theme = gtk_icon_theme_get_default ();
+ icon_info = gtk_icon_theme_lookup_icon (
+ icon_theme, emoticon->icon_name, 16, 0);
+ g_return_val_if_fail (icon_info != NULL, NULL);
+
+ filename = gtk_icon_info_get_filename (icon_info);
+ if (filename != NULL) {
+ uri = g_filename_to_uri (filename, NULL, NULL);
+ }
+ gtk_icon_info_free (icon_info);
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ return uri;
+}
diff --git a/e-util/e-emoticon.h b/e-util/e-emoticon.h
new file mode 100644
index 0000000000..66327ab788
--- /dev/null
+++ b/e-util/e-emoticon.h
@@ -0,0 +1,53 @@
+/*
+ * e-emoticon.h
+ *
+ * Copyright (C) 2008 Novell, Inc.
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_EMOTICON_H
+#define E_EMOTICON_H
+
+#include <glib-object.h>
+
+#define E_TYPE_EMOTICON \
+ (e_emoticon_get_type ())
+
+G_BEGIN_DECLS
+
+typedef struct _EEmoticon EEmoticon;
+
+struct _EEmoticon {
+ gchar *label;
+ gchar *icon_name;
+ gchar *text_face;
+};
+
+GType e_emoticon_get_type (void) G_GNUC_CONST;
+gboolean e_emoticon_equal (EEmoticon *emoticon_a,
+ EEmoticon *emoticon_b);
+EEmoticon * e_emoticon_copy (EEmoticon *emoticon);
+void e_emoticon_free (EEmoticon *emoticon);
+gchar * e_emoticon_get_uri (EEmoticon *emoticon);
+
+G_END_DECLS
+
+#endif /* E_EMOTICON_H */
diff --git a/e-util/e-focus-tracker.c b/e-util/e-focus-tracker.c
index 0879a16c1d..3d3daec9fb 100644
--- a/e-util/e-focus-tracker.c
+++ b/e-util/e-focus-tracker.c
@@ -28,6 +28,7 @@
#include "e-selectable.h"
#include "e-widget-undo.h"
+#include "e-html-editor-view.h"
#define E_FOCUS_TRACKER_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
@@ -255,6 +256,42 @@ focus_tracker_text_view_update_actions (EFocusTracker *focus_tracker,
}
static void
+focus_tracker_editor_update_actions (EFocusTracker *focus_tracker,
+ EHTMLEditorView *view,
+ GdkAtom *targets,
+ gint n_targets)
+{
+ GtkAction *action;
+ gboolean can_copy;
+ gboolean can_cut;
+ gboolean can_paste;
+
+ g_object_get (view,
+ "can-copy", &can_copy,
+ "can-cut", &can_cut,
+ "can-paste", &can_paste,
+ NULL);
+
+ action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
+ if (action != NULL) {
+ gtk_action_set_sensitive (action, can_cut);
+ gtk_action_set_tooltip (action, _("Cut the selection"));
+ }
+
+ action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
+ if (action != NULL) {
+ gtk_action_set_sensitive (action, can_copy);
+ gtk_action_set_tooltip (action, _("Copy the selection"));
+ }
+
+ action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
+ if (action != NULL) {
+ gtk_action_set_sensitive (action, can_paste);
+ gtk_action_set_tooltip (action, _("Paste the clipboard"));
+ }
+}
+
+static void
focus_tracker_selectable_update_actions (EFocusTracker *focus_tracker,
ESelectable *selectable,
GdkAtom *targets,
@@ -331,6 +368,11 @@ focus_tracker_targets_received_cb (GtkClipboard *clipboard,
focus_tracker, GTK_TEXT_VIEW (focus),
targets, n_targets);
+ else if (E_IS_HTML_EDITOR_VIEW (focus))
+ focus_tracker_editor_update_actions (
+ focus_tracker, E_HTML_EDITOR_VIEW (focus),
+ targets, n_targets);
+
g_object_unref (focus_tracker);
}
@@ -349,6 +391,9 @@ focus_tracker_set_focus_cb (GtkWindow *window,
if (GTK_IS_TEXT_VIEW (focus))
break;
+ if (E_IS_HTML_EDITOR_VIEW (focus))
+ break;
+
focus = gtk_widget_get_parent (focus);
}
diff --git a/e-util/e-html-editor-actions.c b/e-util/e-html-editor-actions.c
new file mode 100644
index 0000000000..6db3ad48da
--- /dev/null
+++ b/e-util/e-html-editor-actions.c
@@ -0,0 +1,2028 @@
+/* e-html-editor-actions.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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <enchant/enchant.h>
+
+#include "e-html-editor.h"
+#include "e-html-editor-private.h"
+#include "e-html-editor-actions.h"
+#include "e-html-editor-utils.h"
+#include "e-emoticon-action.h"
+#include "e-emoticon-chooser.h"
+#include "e-image-chooser-dialog.h"
+#include "e-spell-checker.h"
+
+static void
+insert_html_file_ready_cb (GFile *file,
+ GAsyncResult *result,
+ EHTMLEditor *editor)
+{
+ EHTMLEditorSelection *selection;
+ gchar *contents = NULL;
+ gsize length;
+ GError *error = NULL;
+
+ g_file_load_contents_finish (
+ file, result, &contents, &length, NULL, &error);
+ if (error != NULL) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (editor))),
+ 0, GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE, _("Failed to insert HTML file."));
+ gtk_message_dialog_format_secondary_text (
+ GTK_MESSAGE_DIALOG (dialog), "%s.", error->message);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ g_clear_error (&error);
+ g_object_unref (editor);
+ return;
+ }
+
+ selection = e_html_editor_view_get_selection (
+ e_html_editor_get_view (editor));
+ e_html_editor_selection_insert_html (selection, contents);
+ g_free (contents);
+
+ g_object_unref (editor);
+}
+
+static void
+insert_text_file_ready_cb (GFile *file,
+ GAsyncResult *result,
+ EHTMLEditor *editor)
+{
+ EHTMLEditorSelection *selection;
+ gchar *contents;
+ gsize length;
+ GError *error = NULL;
+
+ g_file_load_contents_finish (
+ file, result, &contents, &length, NULL, &error);
+ if (error != NULL) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (editor))),
+ 0, GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE, _("Failed to insert text file."));
+ gtk_message_dialog_format_secondary_text (
+ GTK_MESSAGE_DIALOG (dialog), "%s.", error->message);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ g_clear_error (&error);
+ g_object_unref (editor);
+ return;
+ }
+
+ selection = e_html_editor_view_get_selection (
+ e_html_editor_get_view (editor));
+ e_html_editor_selection_insert_text (selection, contents);
+ g_free (contents);
+
+ g_object_unref (editor);
+}
+
+static void
+editor_update_static_spell_actions (EHTMLEditor *editor)
+{
+ ESpellChecker *checker;
+ EHTMLEditorView *view;
+ guint count;
+
+ view = e_html_editor_get_view (editor);
+ checker = e_html_editor_view_get_spell_checker (view);
+
+ count = e_spell_checker_count_active_languages (checker);
+
+ gtk_action_set_visible (ACTION (CONTEXT_SPELL_ADD), count == 1);
+ gtk_action_set_visible (ACTION (CONTEXT_SPELL_ADD_MENU), count > 1);
+ gtk_action_set_visible (ACTION (CONTEXT_SPELL_IGNORE), count > 0);
+
+ gtk_action_set_sensitive (ACTION (SPELL_CHECK), count > 0);
+}
+
+/*****************************************************************************
+ * Action Callbacks
+ *****************************************************************************/
+
+static void
+action_context_delete_cell_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitDOMNode *sibling;
+ WebKitDOMElement *cell;
+
+ g_return_if_fail (editor->priv->table_cell != NULL);
+
+ cell = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TD");
+ if (!cell) {
+ cell = e_html_editor_dom_node_find_parent_element (
+ editor->priv->table_cell, "TH");
+ }
+ g_return_if_fail (cell != NULL);
+
+ sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (cell));
+ if (!sibling) {
+ sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (cell));
+ }
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (cell)),
+ WEBKIT_DOM_NODE (cell), NULL);
+
+ if (sibling) {
+ webkit_dom_html_table_cell_element_set_col_span (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (sibling),
+ webkit_dom_html_table_cell_element_get_col_span (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (sibling)) + 1);
+ }
+}
+
+static void
+action_context_delete_column_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitDOMElement *cell, *table;
+ WebKitDOMHTMLCollection *rows;
+ gulong index, length, ii;
+
+ g_return_if_fail (editor->priv->table_cell != NULL);
+
+ /* Find TD in which the selection starts */
+ cell = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TD");
+ if (!cell) {
+ cell = e_html_editor_dom_node_find_parent_element (
+ editor->priv->table_cell, "TH");
+ }
+ g_return_if_fail (cell != NULL);
+
+ table = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TABLE");
+ g_return_if_fail (table != NULL);
+
+ rows = webkit_dom_html_table_element_get_rows (
+ WEBKIT_DOM_HTML_TABLE_ELEMENT (table));
+ length = webkit_dom_html_collection_get_length (rows);
+
+ index = webkit_dom_html_table_cell_element_get_cell_index (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell));
+
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *row;
+
+ row = webkit_dom_html_collection_item (rows, ii);
+
+ webkit_dom_html_table_row_element_delete_cell (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index, NULL);
+ }
+}
+
+static void
+action_context_delete_row_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitDOMElement *row;
+
+ g_return_if_fail (editor->priv->table_cell != NULL);
+
+ row = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TR");
+ g_return_if_fail (row != NULL);
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row)),
+ WEBKIT_DOM_NODE (row), NULL);
+}
+
+static void
+action_context_delete_table_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitDOMElement *table;
+
+ g_return_if_fail (editor->priv->table_cell != NULL);
+
+ table = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TABLE");
+ g_return_if_fail (table != NULL);
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (table)),
+ WEBKIT_DOM_NODE (table), NULL);
+}
+
+static void
+action_context_insert_column_after_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitDOMElement *cell, *row;
+ gulong index;
+
+ g_return_if_fail (editor->priv->table_cell != NULL);
+
+ cell = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TD");
+ if (!cell) {
+ cell = e_html_editor_dom_node_find_parent_element (
+ editor->priv->table_cell, "TH");
+ }
+ g_return_if_fail (cell != NULL);
+
+ row = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TR");
+ g_return_if_fail (row != NULL);
+
+ /* Get the first row in the table */
+ row = WEBKIT_DOM_ELEMENT (
+ webkit_dom_node_get_first_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row))));
+
+ index = webkit_dom_html_table_cell_element_get_cell_index (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell));
+
+ while (row) {
+ webkit_dom_html_table_row_element_insert_cell (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index + 1, NULL);
+
+ row = WEBKIT_DOM_ELEMENT (
+ webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (row)));
+ }
+}
+
+static void
+action_context_insert_column_before_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitDOMElement *cell, *row;
+ gulong index;
+
+ g_return_if_fail (editor->priv->table_cell != NULL);
+
+ cell = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TD");
+ if (!cell) {
+ cell = e_html_editor_dom_node_find_parent_element (
+ editor->priv->table_cell, "TH");
+ }
+ g_return_if_fail (cell != NULL);
+
+ row = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (cell), "TR");
+ g_return_if_fail (row != NULL);
+
+ /* Get the first row in the table */
+ row = WEBKIT_DOM_ELEMENT (
+ webkit_dom_node_get_first_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (row))));
+
+ index = webkit_dom_html_table_cell_element_get_cell_index (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell));
+
+ while (row) {
+ webkit_dom_html_table_row_element_insert_cell (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), index - 1, NULL);
+
+ row = WEBKIT_DOM_ELEMENT (
+ webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (row)));
+ }
+}
+
+static void
+action_context_insert_row_above_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitDOMElement *row, *table;
+ WebKitDOMHTMLCollection *cells;
+ WebKitDOMHTMLElement *new_row;
+ gulong index, cell_count, ii;
+
+ g_return_if_fail (editor->priv->table_cell != NULL);
+
+ row = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TR");
+ g_return_if_fail (row != NULL);
+
+ table = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (row), "TABLE");
+ g_return_if_fail (table != NULL);
+
+ index = webkit_dom_html_table_row_element_get_row_index (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
+
+ new_row = webkit_dom_html_table_element_insert_row (
+ WEBKIT_DOM_HTML_TABLE_ELEMENT (table), index, NULL);
+
+ cells = webkit_dom_html_table_row_element_get_cells (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
+ cell_count = webkit_dom_html_collection_get_length (cells);
+ for (ii = 0; ii < cell_count; ii++) {
+ webkit_dom_html_table_row_element_insert_cell (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (new_row), -1, NULL);
+ }
+
+}
+
+static void
+action_context_insert_row_below_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitDOMElement *row, *table;
+ WebKitDOMHTMLCollection *cells;
+ WebKitDOMHTMLElement *new_row;
+ gulong index, cell_count, ii;
+
+ g_return_if_fail (editor->priv->table_cell != NULL);
+
+ row = e_html_editor_dom_node_find_parent_element (editor->priv->table_cell, "TR");
+ g_return_if_fail (row != NULL);
+
+ table = e_html_editor_dom_node_find_parent_element (WEBKIT_DOM_NODE (row), "TABLE");
+ g_return_if_fail (table != NULL);
+
+ index = webkit_dom_html_table_row_element_get_row_index (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
+
+ new_row = webkit_dom_html_table_element_insert_row (
+ WEBKIT_DOM_HTML_TABLE_ELEMENT (table), index + 1, NULL);
+
+ cells = webkit_dom_html_table_row_element_get_cells (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
+ cell_count = webkit_dom_html_collection_get_length (cells);
+ for (ii = 0; ii < cell_count; ii++) {
+ webkit_dom_html_table_row_element_insert_cell (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (new_row), -1, NULL);
+ }
+}
+
+static void
+action_context_remove_link_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+
+ e_html_editor_selection_unlink (selection);
+}
+
+static void
+action_context_spell_add_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ ESpellChecker *spell_checker;
+ EHTMLEditorSelection *selection;
+ gchar *word;
+
+ spell_checker = e_html_editor_view_get_spell_checker (
+ editor->priv->html_editor_view);
+ selection = e_html_editor_view_get_selection (editor->priv->html_editor_view);
+
+ word = e_html_editor_selection_get_caret_word (selection);
+ if (word && *word) {
+ e_spell_checker_learn_word (spell_checker, word);
+ }
+}
+
+static void
+action_context_spell_ignore_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ ESpellChecker *spell_checker;
+ EHTMLEditorSelection *selection;
+ gchar *word;
+
+ spell_checker = e_html_editor_view_get_spell_checker (
+ editor->priv->html_editor_view);
+ selection = e_html_editor_view_get_selection (editor->priv->html_editor_view);
+
+ word = e_html_editor_selection_get_caret_word (selection);
+ if (word && *word) {
+ e_spell_checker_ignore_word (spell_checker, word);
+ }
+}
+
+static void
+action_copy_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ webkit_web_view_copy_clipboard (
+ WEBKIT_WEB_VIEW (e_html_editor_get_view (editor)));
+}
+
+static void
+action_cut_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ webkit_web_view_cut_clipboard (
+ WEBKIT_WEB_VIEW (e_html_editor_get_view (editor)));
+}
+
+static void
+action_indent_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ e_html_editor_selection_indent (editor->priv->selection);
+}
+
+static void
+action_insert_emoticon_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ EHTMLEditorView *view;
+ EEmoticon *emoticon;
+
+ emoticon = e_emoticon_chooser_get_current_emoticon (
+ E_EMOTICON_CHOOSER (action));
+ g_return_if_fail (emoticon != NULL);
+
+ view = e_html_editor_get_view (editor);
+ e_html_editor_view_insert_smiley (view, emoticon);
+}
+
+static void
+action_insert_html_file_cb (GtkToggleAction *action,
+ EHTMLEditor *editor)
+{
+ GtkWidget *dialog;
+ GtkFileFilter *filter;
+
+ dialog = gtk_file_chooser_dialog_new (
+ _("Insert HTML File"), NULL,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, _("HTML file"));
+ gtk_file_filter_add_mime_type (filter, "text/html");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
+ GFile *file;
+
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+
+ /* XXX Need a way to cancel this. */
+ g_file_load_contents_async (
+ file, NULL, (GAsyncReadyCallback)
+ insert_html_file_ready_cb,
+ g_object_ref (editor));
+
+ g_object_unref (file);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+action_insert_image_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ GtkWidget *dialog;
+
+ dialog = e_image_chooser_dialog_new (_("Insert Image"), NULL);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+ gchar *uri;
+
+ uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dialog));
+
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+ e_html_editor_selection_insert_image (selection, uri);
+
+ g_free (uri);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+action_insert_link_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->link_dialog == NULL)
+ editor->priv->link_dialog =
+ e_html_editor_link_dialog_new (editor);
+
+ gtk_window_present (GTK_WINDOW (editor->priv->link_dialog));
+}
+
+static void
+action_insert_rule_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->hrule_dialog == NULL)
+ editor->priv->hrule_dialog =
+ e_html_editor_hrule_dialog_new (editor);
+
+ gtk_window_present (GTK_WINDOW (editor->priv->hrule_dialog));
+}
+
+static void
+action_insert_table_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->table_dialog == NULL)
+ editor->priv->table_dialog =
+ e_html_editor_table_dialog_new (editor);
+
+ gtk_window_present (GTK_WINDOW (editor->priv->table_dialog));
+}
+
+static void
+action_insert_text_file_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ GtkWidget *dialog;
+ GtkFileFilter *filter;
+
+ dialog = gtk_file_chooser_dialog_new (
+ _("Insert text file"), NULL,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, NULL);
+
+ filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (filter, _("Text file"));
+ gtk_file_filter_add_mime_type (filter, "text/plain");
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
+ GFile *file;
+
+ file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
+
+ /* XXX Need a way to cancel this. */
+ g_file_load_contents_async (
+ file, NULL, (GAsyncReadyCallback)
+ insert_text_file_ready_cb,
+ g_object_ref (editor));
+
+ g_object_unref (file);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+action_language_cb (GtkToggleAction *toggle_action,
+ EHTMLEditor *editor)
+{
+ ESpellChecker *checker;
+ EHTMLEditorView *view;
+ const gchar *language_code;
+ GtkAction *add_action;
+ gchar *action_name;
+ gboolean active;
+
+ view = e_html_editor_get_view (editor);
+ checker = e_html_editor_view_get_spell_checker (view);
+ language_code = gtk_action_get_name (GTK_ACTION (toggle_action));
+
+ active = gtk_toggle_action_get_active (toggle_action);
+ e_spell_checker_set_language_active (checker, language_code, active);
+
+ /* Update "Add Word To" context menu item visibility. */
+ action_name = g_strdup_printf ("context-spell-add-%s", language_code);
+ add_action = e_html_editor_get_action (editor, action_name);
+ gtk_action_set_visible (add_action, active);
+ g_free (action_name);
+
+ editor_update_static_spell_actions (editor);
+
+ g_signal_emit_by_name (editor, "spell-languages-changed");
+}
+
+static gboolean
+update_mode_combobox (gpointer data)
+{
+ EHTMLEditor *editor = data;
+ EHTMLEditorView *view;
+ GtkAction *action;
+ gboolean is_html;
+
+ if (!E_IS_HTML_EDITOR (editor))
+ return FALSE;
+
+ view = e_html_editor_get_view (editor);
+ is_html = e_html_editor_view_get_html_mode (view);
+
+ action = gtk_action_group_get_action (
+ editor->priv->core_actions, "mode-html");
+ gtk_radio_action_set_current_value (
+ GTK_RADIO_ACTION (action), (is_html ? 1 : 0));
+
+ return FALSE;
+}
+
+static void
+action_mode_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EHTMLEditor *editor)
+{
+ GtkActionGroup *action_group;
+ EHTMLEditorView *view;
+ GtkWidget *style_combo_box;
+ gboolean is_html;
+
+ view = e_html_editor_get_view (editor);
+ is_html = e_html_editor_view_get_html_mode (view);
+
+ /* This must be done from idle callback, because apparently we can change
+ * current value in callback of current value change */
+ g_idle_add (update_mode_combobox, editor);
+
+ action_group = editor->priv->html_actions;
+ gtk_action_group_set_sensitive (action_group, is_html);
+
+ action_group = editor->priv->html_context_actions;
+ gtk_action_group_set_visible (action_group, is_html);
+
+ gtk_widget_set_sensitive (editor->priv->color_combo_box, is_html);
+
+ if (is_html) {
+ gtk_widget_show (editor->priv->html_toolbar);
+ } else {
+ gtk_widget_hide (editor->priv->html_toolbar);
+ }
+
+ /* Certain paragraph styles are HTML-only. */
+ gtk_action_set_sensitive (ACTION (STYLE_H1), is_html);
+ gtk_action_set_visible (ACTION (STYLE_H1), is_html);
+ gtk_action_set_sensitive (ACTION (STYLE_H2), is_html);
+ gtk_action_set_visible (ACTION (STYLE_H2), is_html);
+ gtk_action_set_sensitive (ACTION (STYLE_H3), is_html);
+ gtk_action_set_visible (ACTION (STYLE_H3), is_html);
+ gtk_action_set_sensitive (ACTION (STYLE_H4), is_html);
+ gtk_action_set_visible (ACTION (STYLE_H4), is_html);
+ gtk_action_set_sensitive (ACTION (STYLE_H5), is_html);
+ gtk_action_set_visible (ACTION (STYLE_H5), is_html);
+ gtk_action_set_sensitive (ACTION (STYLE_H6), is_html);
+ gtk_action_set_visible (ACTION (STYLE_H6), is_html);
+ gtk_action_set_sensitive (ACTION (STYLE_ADDRESS), is_html);
+ gtk_action_set_visible (ACTION (STYLE_ADDRESS), is_html);
+
+ /* Hide them from the action combo box as well */
+ style_combo_box = e_html_editor_get_style_combo_box (editor);
+ e_action_combo_box_update_model (E_ACTION_COMBO_BOX (style_combo_box));
+}
+
+static void
+action_paste_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ EHTMLEditorView *view = e_html_editor_get_view (editor);
+
+ /* Paste only if WebView has focus */
+ if (gtk_widget_has_focus (GTK_WIDGET (view))) {
+ webkit_web_view_paste_clipboard (
+ WEBKIT_WEB_VIEW (view));
+
+ e_html_editor_view_force_spell_check (view);
+ }
+}
+
+static void
+action_paste_quote_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ e_html_editor_view_paste_clipboard_quoted (
+ e_html_editor_get_view (editor));
+
+ e_html_editor_view_force_spell_check (
+ e_html_editor_get_view (editor));
+}
+
+static void
+action_properties_cell_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->cell_dialog == NULL) {
+ editor->priv->cell_dialog =
+ e_html_editor_cell_dialog_new (editor);
+ }
+
+ e_html_editor_cell_dialog_show (
+ E_HTML_EDITOR_CELL_DIALOG (editor->priv->cell_dialog),
+ editor->priv->table_cell);
+}
+
+static void
+action_properties_image_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->image_dialog == NULL) {
+ editor->priv->image_dialog =
+ e_html_editor_image_dialog_new (editor);
+ }
+
+ e_html_editor_image_dialog_show (
+ E_HTML_EDITOR_IMAGE_DIALOG (editor->priv->image_dialog),
+ editor->priv->image);
+}
+
+static void
+action_properties_link_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->link_dialog == NULL) {
+ editor->priv->link_dialog =
+ e_html_editor_link_dialog_new (editor);
+ }
+
+ gtk_window_present (GTK_WINDOW (editor->priv->link_dialog));
+}
+
+static void
+action_properties_page_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->page_dialog == NULL) {
+ editor->priv->page_dialog =
+ e_html_editor_page_dialog_new (editor);
+ }
+
+ gtk_window_present (GTK_WINDOW (editor->priv->page_dialog));
+}
+
+static void
+action_properties_paragraph_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->paragraph_dialog == NULL) {
+ editor->priv->paragraph_dialog =
+ e_html_editor_paragraph_dialog_new (editor);
+ }
+
+ gtk_window_present (GTK_WINDOW (editor->priv->paragraph_dialog));
+}
+
+static void
+action_properties_rule_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->hrule_dialog == NULL) {
+ editor->priv->hrule_dialog =
+ e_html_editor_hrule_dialog_new (editor);
+ }
+
+ gtk_window_present (GTK_WINDOW (editor->priv->hrule_dialog));
+}
+
+static void
+action_properties_table_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->table_dialog == NULL) {
+ editor->priv->table_dialog =
+ e_html_editor_table_dialog_new (editor);
+ }
+
+ gtk_window_present (GTK_WINDOW (editor->priv->table_dialog));
+}
+
+static void
+action_properties_text_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->text_dialog == NULL) {
+ editor->priv->text_dialog =
+ e_html_editor_text_dialog_new (editor);
+ }
+
+ gtk_window_present (GTK_WINDOW (editor->priv->text_dialog));
+}
+
+static void
+action_redo_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ webkit_web_view_redo (
+ WEBKIT_WEB_VIEW (e_html_editor_get_view (editor)));
+}
+
+static void
+action_select_all_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ webkit_web_view_select_all (
+ WEBKIT_WEB_VIEW (e_html_editor_get_view (editor)));
+}
+
+static void
+action_show_find_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->find_dialog == NULL) {
+ editor->priv->find_dialog = e_html_editor_find_dialog_new (editor);
+ gtk_action_set_sensitive (ACTION (FIND_AGAIN), TRUE);
+ }
+
+ gtk_window_present (GTK_WINDOW (editor->priv->find_dialog));
+}
+
+static void
+action_find_again_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->find_dialog == NULL) {
+ return;
+ }
+
+ e_html_editor_find_dialog_find_next (
+ E_HTML_EDITOR_FIND_DIALOG (editor->priv->find_dialog));
+}
+
+static void
+action_show_replace_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->replace_dialog == NULL) {
+ editor->priv->replace_dialog =
+ e_html_editor_replace_dialog_new (editor);
+ }
+
+ gtk_window_present (GTK_WINDOW (editor->priv->replace_dialog));
+}
+
+static void
+action_spell_check_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ if (editor->priv->spell_check_dialog == NULL) {
+ editor->priv->spell_check_dialog =
+ e_html_editor_spell_check_dialog_new (editor);
+ }
+
+ gtk_window_present (GTK_WINDOW (editor->priv->spell_check_dialog));
+}
+
+static void
+action_undo_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ webkit_web_view_undo (
+ WEBKIT_WEB_VIEW (e_html_editor_get_view (editor)));
+}
+
+static void
+action_unindent_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ e_html_editor_selection_unindent (editor->priv->selection);
+}
+
+static void
+action_wrap_lines_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ e_html_editor_selection_wrap_lines (editor->priv->selection);
+}
+
+static void
+action_show_webkit_inspector_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitWebInspector *inspector;
+ EHTMLEditorView *view;
+
+ view = e_html_editor_get_view (editor);
+ inspector = webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (view));
+
+ webkit_web_inspector_show (inspector);
+}
+
+/*****************************************************************************
+ * Core Actions
+ *
+ * These actions are always enabled.
+ *****************************************************************************/
+
+static GtkActionEntry core_entries[] = {
+
+ { "copy",
+ GTK_STOCK_COPY,
+ N_("_Copy"),
+ "<Control>c",
+ N_("Copy selected text to the clipboard"),
+ G_CALLBACK (action_copy_cb) },
+
+ { "cut",
+ GTK_STOCK_CUT,
+ N_("Cu_t"),
+ "<Control>x",
+ N_("Cut selected text to the clipboard"),
+ G_CALLBACK (action_cut_cb) },
+
+ { "indent",
+ GTK_STOCK_INDENT,
+ N_("_Increase Indent"),
+ "<Control>bracketright",
+ N_("Increase Indent"),
+ G_CALLBACK (action_indent_cb) },
+
+ { "insert-html-file",
+ NULL,
+ N_("_HTML File..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_insert_html_file_cb) },
+
+ { "insert-text-file",
+ NULL,
+ N_("Te_xt File..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_insert_text_file_cb) },
+
+ { "paste",
+ GTK_STOCK_PASTE,
+ N_("_Paste"),
+ "<Control>v",
+ N_("Paste text from the clipboard"),
+ G_CALLBACK (action_paste_cb) },
+
+ { "paste-quote",
+ NULL,
+ N_("Paste _Quotation"),
+ "<Shift><Control>v",
+ NULL,
+ G_CALLBACK (action_paste_quote_cb) },
+
+ { "redo",
+ GTK_STOCK_REDO,
+ N_("_Redo"),
+ "<Shift><Control>z",
+ N_("Redo the last undone action"),
+ G_CALLBACK (action_redo_cb) },
+
+ { "select-all",
+ GTK_STOCK_SELECT_ALL,
+ N_("Select _All"),
+ "<Control>a",
+ NULL,
+ G_CALLBACK (action_select_all_cb) },
+
+ { "show-find",
+ GTK_STOCK_FIND,
+ N_("_Find..."),
+ "<Control>f",
+ N_("Search for text"),
+ G_CALLBACK (action_show_find_cb) },
+
+ { "find-again",
+ NULL,
+ N_("Find A_gain"),
+ "<Control>g",
+ NULL,
+ G_CALLBACK (action_find_again_cb) },
+
+ { "show-replace",
+ GTK_STOCK_FIND_AND_REPLACE,
+ N_("Re_place..."),
+ "<Control>h",
+ N_("Search for and replace text"),
+ G_CALLBACK (action_show_replace_cb) },
+
+ { "spell-check",
+ GTK_STOCK_SPELL_CHECK,
+ N_("Check _Spelling..."),
+ "F7",
+ NULL,
+ G_CALLBACK (action_spell_check_cb) },
+
+ { "undo",
+ GTK_STOCK_UNDO,
+ N_("_Undo"),
+ "<Control>z",
+ N_("Undo the last action"),
+ G_CALLBACK (action_undo_cb) },
+
+ { "unindent",
+ GTK_STOCK_UNINDENT,
+ N_("_Decrease Indent"),
+ "<Control>bracketleft",
+ N_("Decrease Indent"),
+ G_CALLBACK (action_unindent_cb) },
+
+ { "wrap-lines",
+ NULL,
+ N_("_Wrap Lines"),
+ "<Control>k",
+ NULL,
+ G_CALLBACK (action_wrap_lines_cb) },
+
+ { "webkit-inspector",
+ NULL,
+ N_("Open Inspector"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_show_webkit_inspector_cb) },
+
+ /* Menus */
+
+ { "edit-menu",
+ NULL,
+ N_("_Edit"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "file-menu",
+ NULL,
+ N_("_File"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "format-menu",
+ NULL,
+ N_("For_mat"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "paragraph-style-menu",
+ NULL,
+ N_("_Paragraph Style"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "insert-menu",
+ NULL,
+ N_("_Insert"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "justify-menu",
+ NULL,
+ N_("_Alignment"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "language-menu",
+ NULL,
+ N_("Current _Languages"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "view-menu",
+ NULL,
+ N_("_View"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static GtkRadioActionEntry core_justify_entries[] = {
+
+ { "justify-center",
+ GTK_STOCK_JUSTIFY_CENTER,
+ N_("_Center"),
+ "<Control>e",
+ N_("Center Alignment"),
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER },
+
+ { "justify-left",
+ GTK_STOCK_JUSTIFY_LEFT,
+ N_("_Left"),
+ "<Control>l",
+ N_("Left Alignment"),
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT },
+
+ { "justify-right",
+ GTK_STOCK_JUSTIFY_RIGHT,
+ N_("_Right"),
+ "<Control>r",
+ N_("Right Alignment"),
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT }
+};
+
+static GtkRadioActionEntry core_mode_entries[] = {
+
+ { "mode-html",
+ NULL,
+ N_("_HTML"),
+ NULL,
+ N_("HTML editing mode"),
+ TRUE }, /* e_html_editor_view_set_html_mode */
+
+ { "mode-plain",
+ NULL,
+ N_("Plain _Text"),
+ NULL,
+ N_("Plain text editing mode"),
+ FALSE } /* e_html_editor_view_set_html_mode */
+};
+
+static GtkRadioActionEntry core_style_entries[] = {
+
+ { "style-normal",
+ NULL,
+ N_("_Normal"),
+ "<Control>0",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH },
+
+ { "style-h1",
+ NULL,
+ N_("Header _1"),
+ "<Control>1",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1 },
+
+ { "style-h2",
+ NULL,
+ N_("Header _2"),
+ "<Control>2",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H2 },
+
+ { "style-h3",
+ NULL,
+ N_("Header _3"),
+ "<Control>3",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H3 },
+
+ { "style-h4",
+ NULL,
+ N_("Header _4"),
+ "<Control>4",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H4 },
+
+ { "style-h5",
+ NULL,
+ N_("Header _5"),
+ "<Control>5",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H5 },
+
+ { "style-h6",
+ NULL,
+ N_("Header _6"),
+ "<Control>6",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6 },
+
+ { "style-preformat",
+ NULL,
+ N_("_Preformatted"),
+ "<Control>7",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE },
+
+ { "style-address",
+ NULL,
+ N_("A_ddress"),
+ "<Control>8",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ADDRESS },
+
+ { "style-blockquote",
+ NULL,
+ N_("_Blockquote"),
+ "<Control>9",
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE },
+
+ { "style-list-bullet",
+ NULL,
+ N_("_Bulleted List"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST },
+
+ { "style-list-roman",
+ NULL,
+ N_("_Roman Numeral List"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN },
+
+ { "style-list-number",
+ NULL,
+ N_("Numbered _List"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST },
+
+ { "style-list-alpha",
+ NULL,
+ N_("_Alphabetical List"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA }
+};
+
+/*****************************************************************************
+ * Core Actions (HTML only)
+ *
+ * These actions are only enabled in HTML mode.
+ *****************************************************************************/
+
+static GtkActionEntry html_entries[] = {
+
+ { "insert-image",
+ "insert-image",
+ N_("_Image..."),
+ NULL,
+ N_("Insert Image"),
+ G_CALLBACK (action_insert_image_cb) },
+
+ { "insert-link",
+ "insert-link",
+ N_("_Link..."),
+ NULL,
+ N_("Insert Link"),
+ G_CALLBACK (action_insert_link_cb) },
+
+ { "insert-rule",
+ "stock_insert-rule",
+ /* Translators: 'Rule' here means a horizontal line in an HTML text */
+ N_("_Rule..."),
+ NULL,
+ /* Translators: 'Rule' here means a horizontal line in an HTML text */
+ N_("Insert Rule"),
+ G_CALLBACK (action_insert_rule_cb) },
+
+ { "insert-table",
+ "stock_insert-table",
+ N_("_Table..."),
+ NULL,
+ N_("Insert Table"),
+ G_CALLBACK (action_insert_table_cb) },
+
+ { "properties-cell",
+ NULL,
+ N_("_Cell..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_cell_cb) },
+
+ { "properties-image",
+ NULL,
+ N_("_Image..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_image_cb) },
+
+ { "properties-link",
+ NULL,
+ N_("_Link..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_link_cb) },
+
+ { "properties-page",
+ NULL,
+ N_("Pa_ge..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_page_cb) },
+
+ { "properties-rule",
+ NULL,
+ /* Translators: 'Rule' here means a horizontal line in an HTML text */
+ N_("_Rule..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_rule_cb) },
+
+ { "properties-table",
+ NULL,
+ N_("_Table..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_table_cb) },
+
+ /* Menus */
+
+ { "font-size-menu",
+ NULL,
+ N_("Font _Size"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "font-style-menu",
+ NULL,
+ N_("_Font Style"),
+ NULL,
+ NULL,
+ NULL },
+};
+
+static GtkToggleActionEntry html_toggle_entries[] = {
+
+ { "bold",
+ GTK_STOCK_BOLD,
+ N_("_Bold"),
+ "<Control>b",
+ N_("Bold"),
+ NULL,
+ FALSE },
+
+ { "italic",
+ GTK_STOCK_ITALIC,
+ N_("_Italic"),
+ "<Control>i",
+ N_("Italic"),
+ NULL,
+ FALSE },
+
+ { "monospaced",
+ "stock_text-monospaced",
+ N_("_Plain Text"),
+ "<Control>t",
+ N_("Plain Text"),
+ NULL,
+ FALSE },
+
+ { "strikethrough",
+ GTK_STOCK_STRIKETHROUGH,
+ N_("_Strikethrough"),
+ NULL,
+ N_("Strikethrough"),
+ NULL,
+ FALSE },
+
+ { "underline",
+ GTK_STOCK_UNDERLINE,
+ N_("_Underline"),
+ "<Control>u",
+ N_("Underline"),
+ NULL,
+ FALSE }
+};
+
+static GtkRadioActionEntry html_size_entries[] = {
+
+ { "size-minus-two",
+ NULL,
+ /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */
+ N_("-2"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_TINY },
+
+ { "size-minus-one",
+ NULL,
+ /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */
+ N_("-1"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_SMALL },
+
+ { "size-plus-zero",
+ NULL,
+ /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */
+ N_("+0"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL },
+
+ { "size-plus-one",
+ NULL,
+ /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */
+ N_("+1"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_BIG },
+
+ { "size-plus-two",
+ NULL,
+ /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */
+ N_("+2"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_BIGGER },
+
+ { "size-plus-three",
+ NULL,
+ /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */
+ N_("+3"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_LARGE },
+
+ { "size-plus-four",
+ NULL,
+ /* Translators: This is a font size level. It is shown on a tool bar. Please keep it as short as possible. */
+ N_("+4"),
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_VERY_LARGE }
+};
+
+/*****************************************************************************
+ * Context Menu Actions
+ *
+ * These require separate action entries so we can toggle their visiblity
+ * rather than their sensitivity as we do with main menu / toolbar actions.
+ * Note that some of these actions use the same callback function as their
+ * non-context sensitive counterparts.
+ *****************************************************************************/
+
+static GtkActionEntry context_entries[] = {
+
+ { "context-delete-cell",
+ NULL,
+ N_("Cell Contents"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_delete_cell_cb) },
+
+ { "context-delete-column",
+ NULL,
+ N_("Column"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_delete_column_cb) },
+
+ { "context-delete-row",
+ NULL,
+ N_("Row"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_delete_row_cb) },
+
+ { "context-delete-table",
+ NULL,
+ N_("Table"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_delete_table_cb) },
+
+ /* Menus */
+
+ { "context-delete-table-menu",
+ NULL,
+ /* Translators: Popup menu item caption, containing all the Delete options for a table */
+ N_("Table Delete"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "context-input-methods-menu",
+ NULL,
+ N_("Input Methods"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "context-insert-table-menu",
+ NULL,
+ /* Translators: Popup menu item caption, containing all the Insert options for a table */
+ N_("Table Insert"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "context-properties-menu",
+ NULL,
+ N_("Properties"),
+ NULL,
+ NULL,
+ NULL },
+};
+
+/*****************************************************************************
+ * Context Menu Actions (HTML only)
+ *
+ * These actions are never visible in plain-text mode. Note that some
+ * of them use the same callback function as their non-context sensitive
+ * counterparts.
+ *****************************************************************************/
+
+static GtkActionEntry html_context_entries[] = {
+
+ { "context-insert-column-after",
+ NULL,
+ N_("Column After"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_insert_column_after_cb) },
+
+ { "context-insert-column-before",
+ NULL,
+ N_("Column Before"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_insert_column_before_cb) },
+
+ { "context-insert-link",
+ NULL,
+ N_("Insert _Link"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_insert_link_cb) },
+
+ { "context-insert-row-above",
+ NULL,
+ N_("Row Above"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_insert_row_above_cb) },
+
+ { "context-insert-row-below",
+ NULL,
+ N_("Row Below"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_insert_row_below_cb) },
+
+ { "context-insert-table",
+ NULL,
+ N_("Table"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_insert_table_cb) },
+
+ { "context-properties-cell",
+ NULL,
+ N_("Cell..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_cell_cb) },
+
+ { "context-properties-image",
+ NULL,
+ N_("Image..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_image_cb) },
+
+ { "context-properties-link",
+ NULL,
+ N_("Link..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_link_cb) },
+
+ { "context-properties-page",
+ NULL,
+ N_("Page..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_page_cb) },
+
+ { "context-properties-paragraph",
+ NULL,
+ N_("Paragraph..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_paragraph_cb) },
+
+ { "context-properties-rule",
+ NULL,
+ /* Translators: 'Rule' here means a horizontal line in an HTML text */
+ N_("Rule..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_rule_cb) },
+
+ { "context-properties-table",
+ NULL,
+ N_("Table..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_table_cb) },
+
+ { "context-properties-text",
+ NULL,
+ N_("Text..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_properties_text_cb) },
+
+ { "context-remove-link",
+ NULL,
+ N_("Remove Link"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_remove_link_cb) }
+};
+
+/*****************************************************************************
+ * Context Menu Actions (spell checking only)
+ *
+ * These actions are only visible when the word underneath the cursor is
+ * misspelled.
+ *****************************************************************************/
+
+static GtkActionEntry spell_context_entries[] = {
+
+ { "context-spell-add",
+ NULL,
+ N_("Add Word to Dictionary"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_spell_add_cb) },
+
+ { "context-spell-ignore",
+ NULL,
+ N_("Ignore Misspelled Word"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_context_spell_ignore_cb) },
+
+ { "context-spell-add-menu",
+ NULL,
+ N_("Add Word To"),
+ NULL,
+ NULL,
+ NULL },
+
+ /* Menus */
+
+ { "context-more-suggestions-menu",
+ NULL,
+ N_("More Suggestions"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static void
+editor_actions_setup_languages_menu (EHTMLEditor *editor)
+{
+ ESpellChecker *checker;
+ EHTMLEditorView *view;
+ GtkUIManager *manager;
+ GtkActionGroup *action_group;
+ GList *list, *link;
+ guint merge_id;
+
+ manager = editor->priv->manager;
+ action_group = editor->priv->language_actions;
+ view = e_html_editor_get_view (editor);
+ checker = e_html_editor_view_get_spell_checker (view);
+ merge_id = gtk_ui_manager_new_merge_id (manager);
+
+ list = e_spell_checker_list_available_dicts (checker);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ ESpellDictionary *dictionary = link->data;
+ GtkToggleAction *action;
+ gboolean active;
+
+ action = gtk_toggle_action_new (
+ e_spell_dictionary_get_code (dictionary),
+ e_spell_dictionary_get_name (dictionary),
+ NULL, NULL);
+
+ /* Do this BEFORE connecting to the "toggled" signal.
+ * We're not prepared to invoke the signal handler yet.
+ * The "Add Word To" actions have not yet been added. */
+ active = e_spell_checker_get_language_active (
+ checker, e_spell_dictionary_get_code (dictionary));
+ gtk_toggle_action_set_active (action, active);
+
+ g_signal_connect (
+ action, "toggled",
+ G_CALLBACK (action_language_cb), editor);
+
+ gtk_action_group_add_action (
+ action_group, GTK_ACTION (action));
+
+ g_object_unref (action);
+
+ gtk_ui_manager_add_ui (
+ manager, merge_id,
+ "/main-menu/edit-menu/language-menu",
+ e_spell_dictionary_get_code (dictionary),
+ e_spell_dictionary_get_code (dictionary),
+ GTK_UI_MANAGER_AUTO, FALSE);
+ }
+
+ g_list_free (list);
+}
+
+static void
+editor_actions_setup_spell_check_menu (EHTMLEditor *editor)
+{
+ ESpellChecker *checker;
+ GtkUIManager *manager;
+ GtkActionGroup *action_group;
+ GList *available_dicts, *iter;
+ guint merge_id;
+
+ manager = editor->priv->manager;
+ action_group = editor->priv->spell_check_actions;;
+ checker = e_html_editor_view_get_spell_checker (editor->priv->html_editor_view);
+ available_dicts = e_spell_checker_list_available_dicts (checker);
+ merge_id = gtk_ui_manager_new_merge_id (manager);
+
+ for (iter = available_dicts; iter; iter = iter->next) {
+ ESpellDictionary *dictionary = iter->data;
+ GtkAction *action;
+ const gchar *code;
+ const gchar *name;
+ gchar *action_label;
+ gchar *action_name;
+
+ code = e_spell_dictionary_get_code (dictionary);
+ name = e_spell_dictionary_get_name (dictionary);
+
+ /* Add a suggestion menu. */
+ action_name = g_strdup_printf (
+ "context-spell-suggest-%s-menu", code);
+
+ action = gtk_action_new (action_name, name, NULL, NULL);
+ gtk_action_group_add_action (action_group, action);
+ g_object_unref (action);
+
+ gtk_ui_manager_add_ui (
+ manager, merge_id,
+ "/context-menu/context-spell-suggest",
+ action_name, action_name,
+ GTK_UI_MANAGER_MENU, FALSE);
+
+ g_free (action_name);
+
+ /* Add an item to the "Add Word To" menu. */
+ action_name = g_strdup_printf ("context-spell-add-%s", code);
+ /* Translators: %s will be replaced with the actual dictionary
+ * name, where a user can add a word to. This is part of an
+ * "Add Word To" submenu. */
+ action_label = g_strdup_printf (_("%s Dictionary"), name);
+
+ action = gtk_action_new (
+ action_name, action_label, NULL, NULL);
+
+ g_signal_connect (
+ action, "activate",
+ G_CALLBACK (action_context_spell_add_cb), editor);
+
+ /* Visibility is dependent on whether the
+ * corresponding language action is active. */
+ gtk_action_set_visible (action, FALSE);
+
+ gtk_action_group_add_action (action_group, action);
+
+ g_object_unref (action);
+
+ gtk_ui_manager_add_ui (
+ manager, merge_id,
+ "/context-menu/context-spell-add-menu",
+ action_name, action_name,
+ GTK_UI_MANAGER_AUTO, FALSE);
+
+ g_free (action_label);
+ g_free (action_name);
+ }
+
+ g_list_free (available_dicts);
+}
+
+void
+editor_actions_init (EHTMLEditor *editor)
+{
+ GtkAction *action;
+ GtkActionGroup *action_group;
+ GtkUIManager *manager;
+ const gchar *domain;
+ EHTMLEditorView *view;
+ GSettings *settings;
+
+ g_return_if_fail (E_IS_HTML_EDITOR (editor));
+
+ manager = e_html_editor_get_ui_manager (editor);
+ domain = GETTEXT_PACKAGE;
+ view = e_html_editor_get_view (editor);
+
+ /* Core Actions */
+ action_group = editor->priv->core_actions;
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_action_group_add_actions (
+ action_group, core_entries,
+ G_N_ELEMENTS (core_entries), editor);
+ gtk_action_group_add_radio_actions (
+ action_group, core_justify_entries,
+ G_N_ELEMENTS (core_justify_entries),
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT,
+ NULL, NULL);
+ gtk_action_group_add_radio_actions (
+ action_group, core_mode_entries,
+ G_N_ELEMENTS (core_mode_entries),
+ TRUE,
+ G_CALLBACK (action_mode_cb), editor);
+ gtk_action_group_add_radio_actions (
+ action_group, core_style_entries,
+ G_N_ELEMENTS (core_style_entries),
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH,
+ NULL, NULL);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ action = gtk_action_group_get_action (action_group, "mode-html");
+ g_object_bind_property (
+ view, "html-mode",
+ action, "current-value",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ /* Synchronize wiget mode with the buttons */
+ e_html_editor_view_set_html_mode (view, TRUE);
+
+ /* Face Action */
+ action = e_emoticon_action_new (
+ "insert-emoticon", _("_Emoticon"),
+ _("Insert Emoticon"), NULL);
+ g_object_set (action, "icon-name", "face-smile", NULL);
+ g_signal_connect (
+ action, "item-activated",
+ G_CALLBACK (action_insert_emoticon_cb), editor);
+ gtk_action_group_add_action (action_group, action);
+ g_object_unref (action);
+
+ /* Core Actions (HTML only) */
+ action_group = editor->priv->html_actions;
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_action_group_add_actions (
+ action_group, html_entries,
+ G_N_ELEMENTS (html_entries), editor);
+ gtk_action_group_add_toggle_actions (
+ action_group, html_toggle_entries,
+ G_N_ELEMENTS (html_toggle_entries), editor);
+ gtk_action_group_add_radio_actions (
+ action_group, html_size_entries,
+ G_N_ELEMENTS (html_size_entries),
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL,
+ NULL, NULL);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ /* Context Menu Actions */
+ action_group = editor->priv->context_actions;
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_action_group_add_actions (
+ action_group, context_entries,
+ G_N_ELEMENTS (context_entries), editor);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ /* Context Menu Actions (HTML only) */
+ action_group = editor->priv->html_context_actions;
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_action_group_add_actions (
+ action_group, html_context_entries,
+ G_N_ELEMENTS (html_context_entries), editor);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ /* Context Menu Actions (spell check only) */
+ action_group = editor->priv->spell_check_actions;
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_action_group_add_actions (
+ action_group, spell_context_entries,
+ G_N_ELEMENTS (spell_context_entries), editor);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ /* Language actions are generated dynamically. */
+ editor_actions_setup_languages_menu (editor);
+ action_group = editor->priv->language_actions;
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ /* Some spell check actions are generated dynamically. */
+ action_group = editor->priv->suggestion_actions;
+ editor_actions_setup_spell_check_menu (editor);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ /* Do this after all language actions are initialized. */
+ editor_update_static_spell_actions (editor);
+
+ /* Fine Tuning */
+
+ g_object_set (
+ G_OBJECT (ACTION (SHOW_FIND)),
+ "short-label", _("_Find"), NULL);
+ g_object_set (
+ G_OBJECT (ACTION (SHOW_REPLACE)),
+ "short-label", _("Re_place"), NULL);
+ g_object_set (
+ G_OBJECT (ACTION (INSERT_IMAGE)),
+ "short-label", _("_Image"), NULL);
+ g_object_set (
+ G_OBJECT (ACTION (INSERT_LINK)),
+ "short-label", _("_Link"), NULL);
+ g_object_set (
+ G_OBJECT (ACTION (INSERT_RULE)),
+ /* Translators: 'Rule' here means a horizontal line in an HTML text */
+ "short-label", _("_Rule"), NULL);
+ g_object_set (
+ G_OBJECT (ACTION (INSERT_TABLE)),
+ "short-label", _("_Table"), NULL);
+
+ gtk_action_set_sensitive (ACTION (UNINDENT), FALSE);
+ gtk_action_set_sensitive (ACTION (FIND_AGAIN), FALSE);
+ gtk_action_set_sensitive (ACTION (SPELL_CHECK), FALSE);
+
+ g_object_bind_property (
+ view, "can-redo",
+ ACTION (REDO), "sensitive",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (
+ view, "can-undo",
+ ACTION (UNDO), "sensitive",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (
+ view, "can-copy",
+ ACTION (COPY), "sensitive",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (
+ view, "can-cut",
+ ACTION (CUT), "sensitive",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (
+ view, "can-paste",
+ ACTION (PASTE), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ /* This is connected to JUSTIFY_LEFT action only, but
+ * it automatically applies on all actions in the group. */
+ g_object_bind_property (
+ editor->priv->selection, "alignment",
+ ACTION (JUSTIFY_LEFT), "current-value",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (
+ editor->priv->selection, "bold",
+ ACTION (BOLD), "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (
+ editor->priv->selection, "font-size",
+ ACTION (FONT_SIZE_GROUP), "current-value",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (
+ editor->priv->selection, "block-format",
+ ACTION (STYLE_NORMAL), "current-value",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (
+ editor->priv->selection, "indented",
+ ACTION (UNINDENT), "sensitive",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (
+ editor->priv->selection, "italic",
+ ACTION (ITALIC), "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (
+ editor->priv->selection, "monospaced",
+ ACTION (MONOSPACED), "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (
+ editor->priv->selection, "strikethrough",
+ ACTION (STRIKETHROUGH), "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_object_bind_property (
+ editor->priv->selection, "underline",
+ ACTION (UNDERLINE), "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+ /* Disable all actions and toolbars when editor is not editable */
+ g_object_bind_property (
+ view, "editable",
+ editor->priv->core_actions, "sensitive",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (
+ view, "editable",
+ editor->priv->html_actions, "sensitive",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (
+ view, "editable",
+ editor->priv->spell_check_actions, "sensitive",
+ G_BINDING_SYNC_CREATE);
+ g_object_bind_property (
+ view, "editable",
+ editor->priv->suggestion_actions, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ settings = g_settings_new ("org.gnome.evolution.mail");
+ gtk_action_set_visible (
+ ACTION (WEBKIT_INSPECTOR),
+ g_settings_get_boolean (settings, "composer-developer-mode"));
+ g_object_unref (settings);
+}
diff --git a/e-util/e-html-editor-actions.h b/e-util/e-html-editor-actions.h
new file mode 100644
index 0000000000..8999add9dc
--- /dev/null
+++ b/e-util/e-html-editor-actions.h
@@ -0,0 +1,155 @@
+/* e-html-editor-actions.h
+ *
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_ACTIONS_H
+#define E_HTML_EDITOR_ACTIONS_H
+
+#define E_HTML_EDITOR_ACTION(editor, name) \
+ (e_html_editor_get_action (E_HTML_EDITOR (editor), (name)))
+
+#define E_HTML_EDITOR_ACTION_BOLD(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "bold")
+#define E_HTML_EDITOR_ACTION_CONTEXT_DELETE_CELL(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-delete-cell")
+#define E_HTML_EDITOR_ACTION_CONTEXT_DELETE_COLUMN(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-delete-column")
+#define E_HTML_EDITOR_ACTION_CONTEXT_DELETE_ROW(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-delete-row")
+#define E_HTML_EDITOR_ACTION_CONTEXT_DELETE_TABLE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-delete-table")
+#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_COLUMN_AFTER(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-insert-column-after")
+#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_COLUMN_BEFORE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-insert-column-before")
+#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_ROW_ABOVE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-insert-row-above")
+#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_ROW_BELOW(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-insert-row-below")
+#define E_HTML_EDITOR_ACTION_CONTEXT_INSERT_TABLE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-insert-table")
+#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_CELL(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-properties-cell")
+#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_IMAGE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-properties-image")
+#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_LINK(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-properties-link")
+#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_PARAGRAPH(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-properties-paragraph")
+#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_RULE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-properties-rule")
+#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_TABLE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-properties-table")
+#define E_HTML_EDITOR_ACTION_CONTEXT_PROPERTIES_TEXT(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-properties-text")
+#define E_HTML_EDITOR_ACTION_CONTEXT_REMOVE_LINK(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-remove-link")
+#define E_HTML_EDITOR_ACTION_CONTEXT_SPELL_ADD(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-spell-add")
+#define E_HTML_EDITOR_ACTION_CONTEXT_SPELL_ADD_MENU(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-spell-add-menu")
+#define E_HTML_EDITOR_ACTION_CONTEXT_SPELL_IGNORE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "context-spell-ignore")
+#define E_HTML_EDITOR_ACTION_COPY(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "copy")
+#define E_HTML_EDITOR_ACTION_CUT(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "cut")
+#define E_HTML_EDITOR_ACTION_EDIT_MENU(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "edit-menu")
+#define E_HTML_EDITOR_ACTION_FIND_AGAIN(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "find-again")
+#define E_HTML_EDITOR_ACTION_FONT_SIZE_GROUP(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "size-plus-zero")
+#define E_HTML_EDITOR_ACTION_FORMAT_MENU(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "format-menu")
+#define E_HTML_EDITOR_ACTION_FORMAT_TEXT(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "format-text")
+#define E_HTML_EDITOR_ACTION_INSERT_IMAGE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "insert-image")
+#define E_HTML_EDITOR_ACTION_INSERT_LINK(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "insert-link")
+#define E_HTML_EDITOR_ACTION_INSERT_MENU(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "insert-menu")
+#define E_HTML_EDITOR_ACTION_INSERT_RULE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "insert-rule")
+#define E_HTML_EDITOR_ACTION_INSERT_TABLE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "insert-table")
+#define E_HTML_EDITOR_ACTION_ITALIC(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "italic")
+#define E_HTML_EDITOR_ACTION_JUSTIFY_CENTER(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "justify-center")
+#define E_HTML_EDITOR_ACTION_JUSTIFY_LEFT(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "justify-left")
+#define E_HTML_EDITOR_ACTION_JUSTIFY_RIGHT(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "justify-right")
+#define E_HTML_EDITOR_ACTION_MODE_HTML(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "mode-html")
+#define E_HTML_EDITOR_ACTION_MODE_PLAIN(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "mode-plain")
+#define E_HTML_EDITOR_ACTION_MONOSPACED(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "monospaced")
+#define E_HTML_EDITOR_ACTION_PASTE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "paste")
+#define E_HTML_EDITOR_ACTION_PROPERTIES_RULE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "properties-rule")
+#define E_HTML_EDITOR_ACTION_PROPERTIES_TABLE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "properties-table")
+#define E_HTML_EDITOR_ACTION_REDO(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "redo")
+#define E_HTML_EDITOR_ACTION_SHOW_FIND(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "show-find")
+#define E_HTML_EDITOR_ACTION_SHOW_REPLACE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "show-replace")
+#define E_HTML_EDITOR_ACTION_SIZE_PLUS_ZERO(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "size-plus-zero")
+#define E_HTML_EDITOR_ACTION_SPELL_CHECK(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "spell-check")
+#define E_HTML_EDITOR_ACTION_STRIKETHROUGH(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "strikethrough")
+#define E_HTML_EDITOR_ACTION_STYLE_ADDRESS(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "style-address")
+#define E_HTML_EDITOR_ACTION_STYLE_H1(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "style-h1")
+#define E_HTML_EDITOR_ACTION_STYLE_H2(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "style-h2")
+#define E_HTML_EDITOR_ACTION_STYLE_H3(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "style-h3")
+#define E_HTML_EDITOR_ACTION_STYLE_H4(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "style-h4")
+#define E_HTML_EDITOR_ACTION_STYLE_H5(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "style-h5")
+#define E_HTML_EDITOR_ACTION_STYLE_H6(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "style-h6")
+#define E_HTML_EDITOR_ACTION_STYLE_NORMAL(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "style-normal")
+#define E_HTML_EDITOR_ACTION_TEST_URL(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "test-url")
+#define E_HTML_EDITOR_ACTION_UNDERLINE(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "underline")
+#define E_HTML_EDITOR_ACTION_UNDO(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "undo")
+#define E_HTML_EDITOR_ACTION_UNINDENT(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "unindent")
+#define E_HTML_EDITOR_ACTION_WEBKIT_INSPECTOR(editor) \
+ E_HTML_EDITOR_ACTION ((editor), "webkit-inspector")
+
+#endif /* E_HTML_EDITOR_ACTIONS_H */
diff --git a/e-util/e-html-editor-cell-dialog.c b/e-util/e-html-editor-cell-dialog.c
new file mode 100644
index 0000000000..2b487288ae
--- /dev/null
+++ b/e-util/e-html-editor-cell-dialog.c
@@ -0,0 +1,872 @@
+/*
+ * e-html-editor-cell-dialog.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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-cell-dialog.h"
+
+#include <glib/gi18n-lib.h>
+#include <stdlib.h>
+
+#include "e-color-combo.h"
+#include "e-html-editor-utils.h"
+#include "e-image-chooser-dialog.h"
+#include "e-misc-utils.h"
+
+#define E_HTML_EDITOR_CELL_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_CELL_DIALOG, EHTMLEditorCellDialogPrivate))
+
+struct _EHTMLEditorCellDialogPrivate {
+ GtkWidget *scope_cell_button;
+ GtkWidget *scope_table_button;
+ GtkWidget *scope_row_button;
+ GtkWidget *scope_column_button;
+
+ GtkWidget *halign_combo;
+ GtkWidget *valign_combo;
+
+ GtkWidget *wrap_text_check;
+ GtkWidget *header_style_check;
+
+ GtkWidget *width_check;
+ GtkWidget *width_edit;
+ GtkWidget *width_units;
+
+ GtkWidget *row_span_edit;
+ GtkWidget *col_span_edit;
+
+ GtkWidget *background_color_picker;
+ GtkWidget *background_image_chooser;
+
+ WebKitDOMElement *cell;
+ guint scope;
+};
+
+enum {
+ SCOPE_CELL,
+ SCOPE_ROW,
+ SCOPE_COLUMN,
+ SCOPE_TABLE
+} DialogScope;
+
+static GdkRGBA white = { 1, 1, 1, 1 };
+
+typedef void (*DOMStrFunc) (WebKitDOMHTMLTableCellElement *cell, const gchar *val, gpointer user_data);
+typedef void (*DOMUlongFunc) (WebKitDOMHTMLTableCellElement *cell, gulong val, gpointer user_data);
+typedef void (*DOMBoolFunc) (WebKitDOMHTMLTableCellElement *cell, gboolean val, gpointer user_data);
+
+G_DEFINE_TYPE (
+ EHTMLEditorCellDialog,
+ e_html_editor_cell_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+static void
+call_cell_dom_func (WebKitDOMHTMLTableCellElement *cell,
+ gpointer func,
+ GValue *value,
+ gpointer user_data)
+{
+ if (G_VALUE_HOLDS_STRING (value)) {
+ DOMStrFunc f = func;
+ f (cell, g_value_get_string (value), user_data);
+ } else if (G_VALUE_HOLDS_ULONG (value)) {
+ DOMUlongFunc f = func;
+ f (cell, g_value_get_ulong (value), user_data);
+ } else if (G_VALUE_HOLDS_BOOLEAN (value)) {
+ DOMBoolFunc f = func;
+ f (cell, g_value_get_boolean (value), user_data);
+ }
+}
+
+static void
+for_each_cell_do (WebKitDOMElement *row,
+ gpointer func,
+ GValue *value,
+ gpointer user_data)
+{
+ WebKitDOMHTMLCollection *cells;
+ gulong ii, length;
+ cells = webkit_dom_html_table_row_element_get_cells (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
+ length = webkit_dom_html_collection_get_length (cells);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *cell;
+ cell = webkit_dom_html_collection_item (cells, ii);
+ if (!cell) {
+ continue;
+ }
+
+ call_cell_dom_func (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell), func, value, user_data);
+ }
+}
+
+static void
+html_editor_cell_dialog_set_attribute (EHTMLEditorCellDialog *dialog,
+ gpointer func,
+ GValue *value,
+ gpointer user_data)
+{
+ if (dialog->priv->scope == SCOPE_CELL) {
+
+ call_cell_dom_func (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell),
+ func, value, user_data);
+
+ } else if (dialog->priv->scope == SCOPE_COLUMN) {
+ gulong index, ii, length;
+ WebKitDOMElement *table;
+ WebKitDOMHTMLCollection *rows;
+
+ index = webkit_dom_html_table_cell_element_get_cell_index (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell));
+ table = e_html_editor_dom_node_find_parent_element (
+ WEBKIT_DOM_NODE (dialog->priv->cell), "TABLE");
+ if (!table) {
+ return;
+ }
+
+ rows = webkit_dom_html_table_element_get_rows (
+ WEBKIT_DOM_HTML_TABLE_ELEMENT (table));
+ length = webkit_dom_html_collection_get_length (rows);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *row, *cell;
+ WebKitDOMHTMLCollection *cells;
+
+ row = webkit_dom_html_collection_item (rows, ii);
+ cells = webkit_dom_html_table_row_element_get_cells (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
+ cell = webkit_dom_html_collection_item (cells, index);
+ if (!cell) {
+ continue;
+ }
+
+ call_cell_dom_func (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (cell),
+ func, value, user_data);
+ }
+
+ } else if (dialog->priv->scope == SCOPE_ROW) {
+ WebKitDOMElement *row;
+
+ row = e_html_editor_dom_node_find_parent_element (
+ WEBKIT_DOM_NODE (dialog->priv->cell), "TR");
+ if (!row) {
+ return;
+ }
+
+ for_each_cell_do (row, func, value, user_data);
+
+ } else if (dialog->priv->scope == SCOPE_TABLE) {
+ gulong ii, length;
+ WebKitDOMElement *table;
+ WebKitDOMHTMLCollection *rows;
+
+ table = e_html_editor_dom_node_find_parent_element (
+ WEBKIT_DOM_NODE (dialog->priv->cell), "TABLE");
+ if (!table) {
+ return;
+ }
+
+ rows = webkit_dom_html_table_element_get_rows (
+ WEBKIT_DOM_HTML_TABLE_ELEMENT (table));
+ length = webkit_dom_html_collection_get_length (rows);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *row;
+
+ row = webkit_dom_html_collection_item (rows, ii);
+ if (!row) {
+ continue;
+ }
+
+ for_each_cell_do (
+ WEBKIT_DOM_ELEMENT (row), func, value, user_data);
+ }
+ }
+}
+
+static void
+html_editor_cell_dialog_set_scope (EHTMLEditorCellDialog *dialog)
+{
+ if (gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->scope_cell_button))) {
+
+ dialog->priv->scope = SCOPE_CELL;
+
+ } else if (gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->scope_row_button))) {
+
+ dialog->priv->scope = SCOPE_ROW;
+
+ } else if (gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->scope_column_button))) {
+
+ dialog->priv->scope = SCOPE_COLUMN;
+
+ } else if (gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->scope_table_button))) {
+
+ dialog->priv->scope = SCOPE_TABLE;
+
+ }
+}
+
+static void
+html_editor_cell_dialog_set_valign (EHTMLEditorCellDialog *dialog)
+{
+ GValue val = { 0 };
+
+ g_value_init (&val, G_TYPE_STRING);
+ g_value_set_string (
+ &val,
+ gtk_combo_box_get_active_id (
+ GTK_COMBO_BOX (dialog->priv->valign_combo)));
+
+ html_editor_cell_dialog_set_attribute (
+ dialog, webkit_dom_html_table_cell_element_set_v_align, &val, NULL);
+
+ g_value_unset (&val);
+}
+
+static void
+html_editor_cell_dialog_set_halign (EHTMLEditorCellDialog *dialog)
+{
+ GValue val = { 0 };
+
+ g_value_init (&val, G_TYPE_STRING);
+ g_value_set_string (
+ &val,
+ gtk_combo_box_get_active_id (
+ GTK_COMBO_BOX (dialog->priv->halign_combo)));
+
+ html_editor_cell_dialog_set_attribute (
+ dialog, webkit_dom_html_table_cell_element_set_align, &val, NULL);
+
+ g_value_unset (&val);
+}
+
+static void
+html_editor_cell_dialog_set_wrap_text (EHTMLEditorCellDialog *dialog)
+{
+ GValue val = { 0 };
+
+ g_value_init (&val, G_TYPE_BOOLEAN);
+ g_value_set_boolean (
+ &val,
+ !gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->wrap_text_check)));
+
+ html_editor_cell_dialog_set_attribute (
+ dialog, webkit_dom_html_table_cell_element_set_no_wrap, &val, NULL);
+}
+
+static void
+cell_set_header_style (WebKitDOMHTMLTableCellElement *cell,
+ gboolean header_style,
+ gpointer user_data)
+{
+ EHTMLEditorCellDialog *dialog = user_data;
+ WebKitDOMDocument *document;
+ WebKitDOMNodeList *nodes;
+ WebKitDOMElement *new_cell;
+ gulong length, ii;
+ gchar *tagname;
+
+ document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (cell));
+ tagname = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (cell));
+
+ if (header_style && (g_ascii_strncasecmp (tagname, "TD", 2) == 0)) {
+
+ new_cell = webkit_dom_document_create_element (document, "TH", NULL);
+
+ } else if (!header_style && (g_ascii_strncasecmp (tagname, "TH", 2) == 0)) {
+
+ new_cell = webkit_dom_document_create_element (document, "TD", NULL);
+
+ } else {
+ g_free (tagname);
+ return;
+ }
+
+ /* Move all child nodes from cell to new_cell */
+ nodes = webkit_dom_node_get_child_nodes (WEBKIT_DOM_NODE (cell));
+ length = webkit_dom_node_list_get_length (nodes);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node;
+
+ node = webkit_dom_node_list_item (nodes, ii);
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (new_cell), node, NULL);
+ }
+
+ /* Insert new_cell before cell and remove cell */
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (cell)),
+ WEBKIT_DOM_NODE (new_cell),
+ WEBKIT_DOM_NODE (cell), NULL);
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (cell)),
+ WEBKIT_DOM_NODE (cell), NULL);
+
+ dialog->priv->cell = new_cell;
+
+ g_free (tagname);
+}
+
+static void
+html_editor_cell_dialog_set_header_style (EHTMLEditorCellDialog *dialog)
+{
+ GValue val = { 0 };
+
+ g_value_init (&val, G_TYPE_BOOLEAN);
+ g_value_set_boolean (
+ &val,
+ gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->header_style_check)));
+
+ html_editor_cell_dialog_set_attribute (
+ dialog, cell_set_header_style, &val, dialog);
+}
+
+static void
+html_editor_cell_dialog_set_width (EHTMLEditorCellDialog *dialog)
+{
+ GValue val = { 0 };
+ gchar *width;
+
+ if (!gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->width_check))) {
+
+ width = g_strdup ("auto");
+ } else {
+
+ width = g_strdup_printf (
+ "%d%s",
+ gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit)),
+ ((gtk_combo_box_get_active (
+ GTK_COMBO_BOX (dialog->priv->width_units)) == 0) ?
+ "px" : "%"));
+ }
+
+ g_value_init (&val, G_TYPE_STRING);
+ g_value_take_string (&val, width);
+ html_editor_cell_dialog_set_attribute (
+ dialog, webkit_dom_html_table_cell_element_set_width, &val, NULL);
+
+ g_free (width);
+}
+
+static void
+html_editor_cell_dialog_set_column_span (EHTMLEditorCellDialog *dialog)
+{
+ GValue val = { 0 };
+
+ g_value_init (&val, G_TYPE_ULONG);
+ g_value_set_ulong (
+ &val,
+ gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->col_span_edit)));
+
+ html_editor_cell_dialog_set_attribute (
+ dialog, webkit_dom_html_table_cell_element_set_col_span, &val, NULL);
+}
+
+static void
+html_editor_cell_dialog_set_row_span (EHTMLEditorCellDialog *dialog)
+{
+ GValue val = { 0 };
+
+ g_value_init (&val, G_TYPE_ULONG);
+ g_value_set_ulong (
+ &val,
+ gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->row_span_edit)));
+
+ html_editor_cell_dialog_set_attribute (
+ dialog, webkit_dom_html_table_cell_element_set_row_span, &val, NULL);
+}
+
+static void
+html_editor_cell_dialog_set_background_color (EHTMLEditorCellDialog *dialog)
+{
+ gchar *color;
+ GdkRGBA rgba;
+ GValue val = { 0 };
+
+ e_color_combo_get_current_color (
+ E_COLOR_COMBO (dialog->priv->background_color_picker), &rgba);
+ color = g_strdup_printf ("#%06x", e_rgba_to_value (&rgba));
+
+ g_value_init (&val, G_TYPE_STRING);
+ g_value_take_string (&val, color);
+
+ html_editor_cell_dialog_set_attribute (
+ dialog, webkit_dom_html_table_cell_element_set_bg_color, &val, NULL);
+
+ g_free (color);
+}
+
+static void
+cell_set_background_image (WebKitDOMHTMLTableCellElement *cell,
+ const gchar *uri,
+ gpointer user_data)
+{
+ if (!uri || !*uri) {
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (cell), "background");
+ } else {
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (cell), "background", uri, NULL);
+ }
+}
+
+static void
+html_editor_cell_dialog_set_background_image (EHTMLEditorCellDialog *dialog)
+{
+ const gchar *uri;
+ GValue val = { 0 };
+
+ uri = gtk_file_chooser_get_uri (
+ GTK_FILE_CHOOSER (dialog->priv->background_image_chooser));
+
+ g_value_init (&val, G_TYPE_STRING);
+ g_value_take_string (&val, (gchar *) uri);
+
+ html_editor_cell_dialog_set_attribute (
+ dialog, cell_set_background_image, &val, NULL);
+}
+
+static void
+html_editor_cell_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditorCellDialog *dialog;
+ gchar *tmp;
+ GdkRGBA color;
+
+ dialog = E_HTML_EDITOR_CELL_DIALOG (widget);
+
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->scope_cell_button), TRUE);
+
+ tmp = webkit_dom_html_table_cell_element_get_align (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell));
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->halign_combo),
+ (tmp && *tmp) ? tmp : "left");
+ g_free (tmp);
+
+ tmp = webkit_dom_html_table_cell_element_get_v_align (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell));
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->valign_combo),
+ (tmp && *tmp) ? tmp : "middle");
+ g_free (tmp);
+
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->wrap_text_check),
+ !webkit_dom_html_table_cell_element_get_no_wrap (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell)));
+
+ tmp = webkit_dom_element_get_tag_name (
+ WEBKIT_DOM_ELEMENT (dialog->priv->cell));
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->header_style_check),
+ (g_ascii_strncasecmp (tmp, "TH", 2) == 0));
+ g_free (tmp);
+
+ tmp = webkit_dom_html_table_cell_element_get_width (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell));
+ if (tmp && *tmp) {
+ gint val = atoi (tmp);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit), val);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->width_check), TRUE);
+ } else {
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit), 0);
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->width_check), FALSE);
+ }
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->width_units), "units-px");
+ g_free (tmp);
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->row_span_edit),
+ webkit_dom_html_table_cell_element_get_row_span (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell)));
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->col_span_edit),
+ webkit_dom_html_table_cell_element_get_col_span (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell)));
+
+ if (webkit_dom_element_has_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->cell), "background")) {
+ tmp = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->cell), "background");
+
+ gtk_file_chooser_set_uri (
+ GTK_FILE_CHOOSER (dialog->priv->background_image_chooser),
+ tmp);
+
+ g_free (tmp);
+ } else {
+ gtk_file_chooser_unselect_all (
+ GTK_FILE_CHOOSER (dialog->priv->background_image_chooser));
+ }
+
+ tmp = webkit_dom_html_table_cell_element_get_bg_color (
+ WEBKIT_DOM_HTML_TABLE_CELL_ELEMENT (dialog->priv->cell));
+ if (!tmp || *tmp) {
+ color = white;
+ }
+ if (gdk_rgba_parse (&color, tmp)) {
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->background_color_picker),
+ &color);
+ } else {
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->background_color_picker),
+ &white);
+ }
+ g_free (tmp);
+
+ GTK_WIDGET_CLASS (e_html_editor_cell_dialog_parent_class)->show (widget);
+}
+
+static void
+e_html_editor_cell_dialog_class_init (EHTMLEditorCellDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorCellDialogPrivate));
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_cell_dialog_show;
+}
+
+static void
+e_html_editor_cell_dialog_init (EHTMLEditorCellDialog *dialog)
+{
+ GtkGrid *main_layout, *grid;
+ GtkWidget *widget;
+ GtkFileFilter *file_filter;
+
+ dialog->priv = E_HTML_EDITOR_CELL_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ /* == Scope == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Scope</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Scope: cell */
+ widget = gtk_image_new_from_icon_name ("stock_select-cell", GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ widget = gtk_radio_button_new_with_mnemonic (NULL, _("C_ell"));
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ dialog->priv->scope_cell_button = widget;
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_cell_dialog_set_scope), dialog);
+
+ /* Scope: row */
+ widget = gtk_image_new_from_icon_name ("stock_select-row", GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (grid, widget, 2, 0, 1, 1);
+
+ widget = gtk_radio_button_new_with_mnemonic_from_widget (
+ GTK_RADIO_BUTTON (dialog->priv->scope_cell_button), _("_Row"));
+ gtk_grid_attach (grid, widget, 3, 0, 1, 1);
+ dialog->priv->scope_row_button = widget;
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_cell_dialog_set_scope), dialog);
+
+ /* Scope: table */
+ widget = gtk_image_new_from_icon_name ("stock_select-table", GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ widget = gtk_radio_button_new_with_mnemonic_from_widget (
+ GTK_RADIO_BUTTON (dialog->priv->scope_cell_button), _("_Table"));
+ gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+ dialog->priv->scope_table_button = widget;
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_cell_dialog_set_scope), dialog);
+
+ /* Scope: column */
+ widget = gtk_image_new_from_icon_name ("stock_select-column", GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (grid, widget, 2, 1, 1, 1);
+
+ widget = gtk_radio_button_new_with_mnemonic_from_widget (
+ GTK_RADIO_BUTTON (dialog->priv->scope_cell_button), _("Col_umn"));
+ gtk_grid_attach (grid, widget, 3, 1, 1, 1);
+ dialog->priv->scope_column_button = widget;
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_cell_dialog_set_scope), dialog);
+
+ /* == Alignment & Behavior == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Alignment &amp; Behavior</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 2, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Horizontal */
+ widget = gtk_combo_box_text_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "left", _("Left"));
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "center", _("Center"));
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "right", _("Right"));
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ dialog->priv->halign_combo = widget;
+
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_cell_dialog_set_halign), dialog);
+
+ widget = gtk_label_new_with_mnemonic (_("_Horizontal:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->halign_combo);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ /* Vertical */
+ widget = gtk_combo_box_text_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "top", _("Top"));
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "middle", _("Middle"));
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "bottom", _("Bottom"));
+ gtk_grid_attach (grid, widget, 3, 0, 1, 1);
+ dialog->priv->valign_combo = widget;
+
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_cell_dialog_set_valign), dialog);
+
+ widget = gtk_label_new_with_mnemonic (_("_Vertical:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->valign_combo);
+ gtk_grid_attach (grid, widget, 2, 0, 1, 1);
+
+ /* Wrap Text */
+ widget = gtk_check_button_new_with_mnemonic (_("_Wrap Text"));
+ dialog->priv->wrap_text_check = widget;
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_cell_dialog_set_wrap_text), dialog);
+
+ /* Header Style */
+ widget = gtk_check_button_new_with_mnemonic (_("_Header Style"));
+ dialog->priv->header_style_check = widget;
+
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_cell_dialog_set_header_style), dialog);
+
+ widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
+ gtk_box_pack_start (GTK_BOX (widget), dialog->priv->wrap_text_check, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (widget), dialog->priv->header_style_check, FALSE, FALSE, 0);
+ gtk_grid_attach (grid, widget, 0, 1, 4, 1);
+
+ /* == Layout == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Layout</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 4, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 5, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Width */
+ widget = gtk_check_button_new_with_mnemonic (_("_Width"));
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+ dialog->priv->width_check = widget;
+
+ widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ dialog->priv->width_edit = widget;
+
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_cell_dialog_set_width), dialog);
+ g_object_bind_property (
+ dialog->priv->width_check, "active",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "unit-px", "px");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "unit-percent", "%");
+ gtk_grid_attach (grid, widget, 2, 0, 1, 1);
+ dialog->priv->width_units = widget;
+
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_cell_dialog_set_width), dialog);
+ g_object_bind_property (
+ dialog->priv->width_check, "active",
+ widget, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ /* Row Span */
+ widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1);
+ gtk_grid_attach (grid, widget, 4, 0, 1, 1);
+ dialog->priv->row_span_edit = widget;
+
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_cell_dialog_set_row_span), dialog);
+
+ widget = gtk_label_new_with_mnemonic (_("Row S_pan:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->row_span_edit);
+ gtk_grid_attach (grid, widget, 3, 0, 1, 1);
+
+ /* Column Span */
+ widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1);
+ gtk_grid_attach (grid, widget, 4, 1, 1, 1);
+ dialog->priv->col_span_edit = widget;
+
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_cell_dialog_set_column_span), dialog);
+
+ widget = gtk_label_new_with_mnemonic (_("Co_lumn Span:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->col_span_edit);
+ gtk_grid_attach (grid, widget, 3, 1, 1, 1);
+
+ /* == Background == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Background</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 6, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 7, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Color */
+ widget = e_color_combo_new ();
+ e_color_combo_set_default_color (E_COLOR_COMBO (widget), &white);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "notify::current-color",
+ G_CALLBACK (html_editor_cell_dialog_set_background_color), dialog);
+ dialog->priv->background_color_picker = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("C_olor:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->background_color_picker);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ /* Image */
+ widget = e_image_chooser_dialog_new (
+ _("Choose Background Image"),
+ GTK_WINDOW (dialog));
+ dialog->priv->background_image_chooser = widget;
+
+ file_filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (file_filter, _("Images"));
+ gtk_file_filter_add_mime_type (file_filter, "image/*");
+
+ widget = gtk_file_chooser_button_new_with_dialog (
+ dialog->priv->background_image_chooser);
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget), file_filter);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+ g_signal_connect_swapped (
+ widget, "file-set",
+ G_CALLBACK (html_editor_cell_dialog_set_background_image), dialog);
+ dialog->priv->background_image_chooser = widget;
+
+ widget =gtk_label_new_with_mnemonic (_("_Image:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->background_image_chooser);
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_cell_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_CELL_DIALOG,
+ "editor", editor,
+ "title", N_("Cell Properties"),
+ NULL));
+}
+
+void
+e_html_editor_cell_dialog_show (EHTMLEditorCellDialog *dialog,
+ WebKitDOMNode *cell)
+{
+ EHTMLEditorCellDialogClass *class;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_CELL_DIALOG (dialog));
+ g_return_if_fail (cell != NULL);
+
+ dialog->priv->cell = e_html_editor_dom_node_find_parent_element (cell, "TD");
+ if (dialog->priv->cell == NULL) {
+ dialog->priv->cell =
+ e_html_editor_dom_node_find_parent_element (cell, "TH");
+ }
+
+ class = E_HTML_EDITOR_CELL_DIALOG_GET_CLASS (dialog);
+ GTK_WIDGET_CLASS (class)->show (GTK_WIDGET (dialog));
+}
+
diff --git a/e-util/e-html-editor-cell-dialog.h b/e-util/e-html-editor-cell-dialog.h
new file mode 100644
index 0000000000..3a7f60822f
--- /dev/null
+++ b/e-util/e-html-editor-cell-dialog.h
@@ -0,0 +1,72 @@
+/*
+ * e-html-editor-cell-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_CELL_DIALOG_H
+#define E_HTML_EDITOR_CELL_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_CELL_DIALOG \
+ (e_html_editor_cell_dialog_get_type ())
+#define E_HTML_EDITOR_CELL_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_CELL_DIALOG, EHTMLEditorCellDialog))
+#define E_HTML_EDITOR_CELL_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_CELL_DIALOG, EHTMLEditorCellDialogClass))
+#define E_IS_HTML_EDITOR_CELL_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_CELL_DIALOG))
+#define E_IS_HTML_EDITOR_CELL_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_CELL_DIALOG))
+#define E_HTML_EDITOR_CELL_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_CELL_DIALOG, EHTMLEditorCellDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorCellDialog EHTMLEditorCellDialog;
+typedef struct _EHTMLEditorCellDialogClass EHTMLEditorCellDialogClass;
+typedef struct _EHTMLEditorCellDialogPrivate EHTMLEditorCellDialogPrivate;
+
+struct _EHTMLEditorCellDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorCellDialogPrivate *priv;
+};
+
+struct _EHTMLEditorCellDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_cell_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_cell_dialog_new (EHTMLEditor *editor);
+void e_html_editor_cell_dialog_show (EHTMLEditorCellDialog *dialog,
+ WebKitDOMNode *cell);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_CELL_DIALOG_H */
diff --git a/e-util/e-html-editor-dialog.c b/e-util/e-html-editor-dialog.c
new file mode 100644
index 0000000000..5b43ebd316
--- /dev/null
+++ b/e-util/e-html-editor-dialog.c
@@ -0,0 +1,248 @@
+/*
+ * e-html-editor-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-dialog.h"
+
+#define E_HTML_EDITOR_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_DIALOG, EHTMLEditorDialogPrivate))
+
+struct _EHTMLEditorDialogPrivate {
+ EHTMLEditor *editor;
+
+ GtkBox *button_box;
+ GtkGrid *container;
+};
+
+enum {
+ PROP_0,
+ PROP_EDITOR,
+};
+
+G_DEFINE_ABSTRACT_TYPE (
+ EHTMLEditorDialog,
+ e_html_editor_dialog,
+ GTK_TYPE_WINDOW);
+
+static void
+html_editor_dialog_set_editor (EHTMLEditorDialog *dialog,
+ EHTMLEditor *editor)
+{
+ dialog->priv->editor = g_object_ref (editor);
+
+ g_object_notify (G_OBJECT (dialog), "editor");
+}
+
+static void
+html_editor_dialog_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_EDITOR:
+ g_value_set_object (
+ value,
+ e_html_editor_dialog_get_editor (
+ E_HTML_EDITOR_DIALOG (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+html_editor_dialog_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_EDITOR:
+ html_editor_dialog_set_editor (
+ E_HTML_EDITOR_DIALOG (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+html_editor_dialog_constructed (GObject *object)
+{
+ EHTMLEditorDialog *dialog = E_HTML_EDITOR_DIALOG (object);
+
+ /* Chain up to parent implementation first */
+ G_OBJECT_CLASS (e_html_editor_dialog_parent_class)->constructed (object);
+
+ gtk_window_set_transient_for (
+ GTK_WINDOW (dialog),
+ GTK_WINDOW (gtk_widget_get_toplevel (
+ GTK_WIDGET (dialog->priv->editor))));
+}
+
+static void
+html_editor_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditorDialogPrivate *priv;
+
+ priv = E_HTML_EDITOR_DIALOG_GET_PRIVATE (widget);
+
+ gtk_widget_show_all (GTK_WIDGET (priv->container));
+ gtk_widget_show_all (GTK_WIDGET (priv->button_box));
+
+ GTK_WIDGET_CLASS (e_html_editor_dialog_parent_class)->show (widget);
+}
+
+static void
+html_editor_dialog_dispose (GObject *object)
+{
+ EHTMLEditorDialogPrivate *priv;
+
+ priv = E_HTML_EDITOR_DIALOG_GET_PRIVATE (object);
+
+ g_clear_object (&priv->editor);
+
+ /* Chain up to parent's implementation */
+ G_OBJECT_CLASS (e_html_editor_dialog_parent_class)->dispose (object);
+}
+
+static void
+e_html_editor_dialog_class_init (EHTMLEditorDialogClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorDialogPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = html_editor_dialog_get_property;
+ object_class->set_property = html_editor_dialog_set_property;
+ object_class->dispose = html_editor_dialog_dispose;
+ object_class->constructed = html_editor_dialog_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_dialog_show;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_EDITOR,
+ g_param_spec_object (
+ "editor",
+ NULL,
+ NULL,
+ E_TYPE_HTML_EDITOR,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+}
+
+static gboolean
+key_press_event_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ if (event->keyval == GDK_KEY_Escape) {
+ gtk_widget_hide (widget);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+e_html_editor_dialog_init (EHTMLEditorDialog *dialog)
+{
+ GtkBox *main_layout;
+ GtkGrid *grid;
+ GtkWidget *widget, *button_box;
+
+ dialog->priv = E_HTML_EDITOR_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 5));
+ gtk_container_add (GTK_CONTAINER (dialog), GTK_WIDGET (main_layout));
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 10);
+ gtk_grid_set_column_spacing (grid, 10);
+ gtk_box_pack_start (main_layout, GTK_WIDGET (grid), TRUE, TRUE, 5);
+ dialog->priv->container = grid;
+
+ /* == Button box == */
+ widget = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (gtk_widget_hide), dialog);
+
+ button_box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (button_box), GTK_BUTTONBOX_END);
+ gtk_box_set_spacing (GTK_BOX (button_box), 5);
+ gtk_box_pack_start (main_layout, button_box, TRUE, TRUE, 5);
+ gtk_box_pack_start (GTK_BOX (button_box), widget, FALSE, FALSE, 5);
+ dialog->priv->button_box = GTK_BOX (button_box);
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+
+ g_object_set (
+ G_OBJECT (dialog),
+ "destroy-with-parent", TRUE,
+ "modal", TRUE,
+ "resizable", FALSE,
+ "window-position", GTK_WIN_POS_CENTER_ON_PARENT,
+ NULL);
+
+ /* Don't destroy the dialog when closed! */
+ g_signal_connect (
+ dialog, "delete-event",
+ G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+
+ g_signal_connect (
+ dialog, "key-press-event",
+ G_CALLBACK (key_press_event_cb), NULL);
+}
+
+EHTMLEditor *
+e_html_editor_dialog_get_editor (EHTMLEditorDialog *dialog)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_DIALOG (dialog), NULL);
+
+ return dialog->priv->editor;
+}
+
+GtkGrid *
+e_html_editor_dialog_get_container (EHTMLEditorDialog *dialog)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_DIALOG (dialog), NULL);
+
+ return dialog->priv->container;
+}
+
+GtkBox *
+e_html_editor_dialog_get_button_box (EHTMLEditorDialog *dialog)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_DIALOG (dialog), NULL);
+
+ return dialog->priv->button_box;
+}
+
diff --git a/e-util/e-html-editor-dialog.h b/e-util/e-html-editor-dialog.h
new file mode 100644
index 0000000000..37fc7a53c3
--- /dev/null
+++ b/e-util/e-html-editor-dialog.h
@@ -0,0 +1,74 @@
+/*
+ * e-html-editor-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_DIALOG_H
+#define E_HTML_EDITOR_DIALOG_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-html-editor.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_DIALOG \
+ (e_html_editor_dialog_get_type ())
+#define E_HTML_EDITOR_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_DIALOG, EHTMLEditorDialog))
+#define E_HTML_EDITOR_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_DIALOG, EHTMLEditorDialogClass))
+#define E_IS_HTML_EDITOR_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_DIALOG))
+#define E_IS_HTML_EDITOR_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_DIALOG))
+#define E_HTML_EDITOR_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_DIALOG, EHTMLEditorDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorDialog EHTMLEditorDialog;
+typedef struct _EHTMLEditorDialogClass EHTMLEditorDialogClass;
+typedef struct _EHTMLEditorDialogPrivate EHTMLEditorDialogPrivate;
+
+struct _EHTMLEditorDialog {
+ GtkWindow parent;
+ EHTMLEditorDialogPrivate *priv;
+};
+
+struct _EHTMLEditorDialogClass {
+ GtkWindowClass parent_class;
+};
+
+GType e_html_editor_dialog_get_type (void) G_GNUC_CONST;
+EHTMLEditor * e_html_editor_dialog_get_editor (EHTMLEditorDialog *dialog);
+GtkBox * e_html_editor_dialog_get_button_box
+ (EHTMLEditorDialog *dialog);
+GtkGrid * e_html_editor_dialog_get_container
+ (EHTMLEditorDialog *dialog);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_DIALOG_H */
diff --git a/e-util/e-html-editor-find-dialog.c b/e-util/e-html-editor-find-dialog.c
new file mode 100644
index 0000000000..9f44cf8dc5
--- /dev/null
+++ b/e-util/e-html-editor-find-dialog.c
@@ -0,0 +1,224 @@
+/*
+ * e-html-editor-find-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-find-dialog.h"
+
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+
+#define E_HTML_EDITOR_FIND_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_FIND_DIALOG, EHTMLEditorFindDialogPrivate))
+
+struct _EHTMLEditorFindDialogPrivate {
+ GtkWidget *entry;
+ GtkWidget *backwards;
+ GtkWidget *case_sensitive;
+ GtkWidget *wrap_search;
+
+ GtkWidget *find_button;
+
+ GtkWidget *result_label;
+};
+
+G_DEFINE_TYPE (
+ EHTMLEditorFindDialog,
+ e_html_editor_find_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+static void
+reset_dialog (EHTMLEditorFindDialog *dialog)
+{
+ gtk_widget_set_sensitive (dialog->priv->find_button, TRUE);
+ gtk_widget_hide (dialog->priv->result_label);
+}
+
+static void
+html_editor_find_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditorFindDialog *dialog = E_HTML_EDITOR_FIND_DIALOG (widget);
+
+ reset_dialog (dialog);
+ gtk_widget_grab_focus (dialog->priv->entry);
+
+ /* Chain up to parent's implementation */
+ GTK_WIDGET_CLASS (e_html_editor_find_dialog_parent_class)->show (widget);
+}
+
+static void
+html_editor_find_dialog_find_cb (EHTMLEditorFindDialog *dialog)
+{
+ gboolean found;
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ found = webkit_web_view_search_text (
+ WEBKIT_WEB_VIEW (view),
+ gtk_entry_get_text (
+ GTK_ENTRY (dialog->priv->entry)),
+ gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (
+ dialog->priv->case_sensitive)),
+ !gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (
+ dialog->priv->backwards)),
+ gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (
+ dialog->priv->wrap_search)));
+
+ gtk_widget_set_sensitive (dialog->priv->find_button, found);
+
+ /* We give focus to WebKit so that the selection is highlited.
+ * Without focus selection is not visible (at least with my default
+ * color scheme). The focus in fact is not given to WebKit, because
+ * this dialog is modal, but it satisfies it in a way that it paints
+ * the selection :) */
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+
+ if (!found) {
+ gtk_label_set_label (
+ GTK_LABEL (dialog->priv->result_label),
+ N_("No match found"));
+ gtk_widget_show (dialog->priv->result_label);
+ }
+}
+
+static gboolean
+entry_key_release_event (GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GdkEventKey *key = &event->key;
+ EHTMLEditorFindDialog *dialog = user_data;
+
+ if (key->keyval == GDK_KEY_Return) {
+ html_editor_find_dialog_find_cb (dialog);
+ return TRUE;
+ }
+
+ reset_dialog (dialog);
+ return FALSE;
+}
+
+static void
+e_html_editor_find_dialog_class_init (EHTMLEditorFindDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorFindDialogPrivate));
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_find_dialog_show;
+}
+
+static void
+e_html_editor_find_dialog_init (EHTMLEditorFindDialog *dialog)
+{
+ GtkGrid *main_layout;
+ GtkBox *box;
+ GtkWidget *widget;
+
+ dialog->priv = E_HTML_EDITOR_FIND_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ widget = gtk_entry_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+ dialog->priv->entry = widget;
+ g_signal_connect (
+ widget, "key-release-event",
+ G_CALLBACK (entry_key_release_event), dialog);
+
+ box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5));
+ gtk_grid_attach (main_layout, GTK_WIDGET (box), 0, 1, 1, 1);
+
+ widget = gtk_check_button_new_with_mnemonic (N_("Search _backwards"));
+ gtk_box_pack_start (box, widget, FALSE, FALSE, 0);
+ dialog->priv->backwards = widget;
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (reset_dialog), dialog);
+
+ widget = gtk_check_button_new_with_mnemonic (N_("Case _Sensitive"));
+ gtk_box_pack_start (box, widget, FALSE, FALSE, 0);
+ dialog->priv->case_sensitive = widget;
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (reset_dialog), dialog);
+
+ widget = gtk_check_button_new_with_mnemonic (N_("_Wrap Search"));
+ gtk_box_pack_start (box, widget, FALSE, FALSE, 0);
+ dialog->priv->wrap_search = widget;
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (reset_dialog), dialog);
+
+ box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5));
+ gtk_grid_attach (main_layout, GTK_WIDGET (box), 0, 2, 1, 1);
+
+ widget = gtk_label_new ("");
+ gtk_box_pack_start (box, widget, FALSE, FALSE, 0);
+ dialog->priv->result_label = widget;
+
+ widget = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_set_spacing (GTK_BOX (widget), 5);
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (widget), GTK_BUTTONBOX_END);
+ gtk_box_pack_end (box, widget, TRUE, TRUE, 0);
+ box = GTK_BOX (widget);
+
+ box = e_html_editor_dialog_get_button_box (E_HTML_EDITOR_DIALOG (dialog));
+ widget = gtk_button_new_from_stock (GTK_STOCK_FIND);
+ gtk_box_pack_start (box, widget, FALSE, FALSE, 5);
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_find_dialog_find_cb), dialog);
+ dialog->priv->find_button = widget;
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_find_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_FIND_DIALOG,
+ "editor", editor,
+ "icon-name", GTK_STOCK_FIND,
+ "title", N_("Find"),
+ NULL));
+}
+
+void
+e_html_editor_find_dialog_find_next (EHTMLEditorFindDialog *dialog)
+{
+ if (gtk_entry_get_text_length (GTK_ENTRY (dialog->priv->entry)) == 0) {
+ return;
+ }
+
+ html_editor_find_dialog_find_cb (dialog);
+}
diff --git a/e-util/e-html-editor-find-dialog.h b/e-util/e-html-editor-find-dialog.h
new file mode 100644
index 0000000000..60134f467f
--- /dev/null
+++ b/e-util/e-html-editor-find-dialog.h
@@ -0,0 +1,73 @@
+/*
+ * e-html-editor-find-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_FIND_DIALOG_H
+#define E_HTML_EDITOR_FIND_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_FIND_DIALOG \
+ (e_html_editor_find_dialog_get_type ())
+#define E_HTML_EDITOR_FIND_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_FIND_DIALOG, EHTMLEditorFindDialog))
+#define E_HTML_EDITOR_FIND_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_FIND_DIALOG, EHTMLEditorFindDialogClass))
+#define E_IS_HTML_EDITOR_FIND_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_FIND_DIALOG))
+#define E_IS_HTML_EDITOR_FIND_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_FIND_DIALOG))
+#define E_HTML_EDITOR_FIND_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_FIND_DIALOG, EHTMLEditorFindDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorFindDialog EHTMLEditorFindDialog;
+typedef struct _EHTMLEditorFindDialogClass EHTMLEditorFindDialogClass;
+typedef struct _EHTMLEditorFindDialogPrivate EHTMLEditorFindDialogPrivate;
+
+struct _EHTMLEditorFindDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorFindDialogPrivate *priv;
+};
+
+struct _EHTMLEditorFindDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_find_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_find_dialog_new (EHTMLEditor *editor);
+void e_html_editor_find_dialog_find_next
+ (EHTMLEditorFindDialog *dialog);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_FIND_DIALOG_H */
+
diff --git a/e-util/e-html-editor-hrule-dialog.c b/e-util/e-html-editor-hrule-dialog.c
new file mode 100644
index 0000000000..9ace655812
--- /dev/null
+++ b/e-util/e-html-editor-hrule-dialog.c
@@ -0,0 +1,421 @@
+/*
+ * e-html-editor-hrule-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-hrule-dialog.h"
+#include "e-html-editor-utils.h"
+#include "e-html-editor-view.h"
+
+#include <glib/gi18n-lib.h>
+#include <webkit/webkitdom.h>
+#include <stdlib.h>
+
+#define E_HTML_EDITOR_HRULE_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_HRULE_DIALOG, EHTMLEditorHRuleDialogPrivate))
+
+struct _EHTMLEditorHRuleDialogPrivate {
+ GtkWidget *width_edit;
+ GtkWidget *size_edit;
+ GtkWidget *unit_combo;
+
+ GtkWidget *alignment_combo;
+ GtkWidget *shaded_check;
+
+ WebKitDOMHTMLHRElement *hr_element;
+};
+
+G_DEFINE_TYPE (
+ EHTMLEditorHRuleDialog,
+ e_html_editor_hrule_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+static void
+html_editor_hrule_dialog_set_alignment (EHTMLEditorHRuleDialog *dialog)
+{
+ const gchar *alignment;
+
+ g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element));
+
+ alignment = gtk_combo_box_get_active_id (
+ GTK_COMBO_BOX (dialog->priv->alignment_combo));
+
+ webkit_dom_htmlhr_element_set_align (dialog->priv->hr_element, alignment);
+}
+
+static void
+html_editor_hrule_dialog_get_alignment (EHTMLEditorHRuleDialog *dialog)
+{
+ gchar *alignment;
+
+ g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element));
+
+ alignment = webkit_dom_htmlhr_element_get_align (dialog->priv->hr_element);
+
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->alignment_combo), alignment);
+ g_free (alignment);
+}
+
+static void
+html_editor_hrule_dialog_set_size (EHTMLEditorHRuleDialog *dialog)
+{
+ gchar *size;
+
+ g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element));
+
+ size = g_strdup_printf (
+ "%d",
+ (gint) gtk_spin_button_get_value (
+ GTK_SPIN_BUTTON (dialog->priv->size_edit)));
+
+ webkit_dom_htmlhr_element_set_size (dialog->priv->hr_element, size);
+
+ g_free (size);
+}
+
+static void
+html_editor_hrule_dialog_get_size (EHTMLEditorHRuleDialog *dialog)
+{
+ gchar *size;
+ gint size_int = 0;
+
+ g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element));
+
+ size = webkit_dom_htmlhr_element_get_size (dialog->priv->hr_element);
+ if (size && *size) {
+ size_int = atoi (size);
+ }
+
+ if (size_int == 0) {
+ size_int = 2;
+ }
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->size_edit), (gdouble) size_int);
+
+ g_free (size);
+}
+
+static void
+html_editor_hrule_dialog_set_width (EHTMLEditorHRuleDialog *dialog)
+{
+ gchar *width, *units;
+
+ g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element));
+
+ units = gtk_combo_box_text_get_active_text (
+ GTK_COMBO_BOX_TEXT (dialog->priv->unit_combo));
+ width = g_strdup_printf (
+ "%d%s",
+ (gint) gtk_spin_button_get_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit)),
+ units);
+
+ webkit_dom_htmlhr_element_set_width (dialog->priv->hr_element, width);
+
+ g_free (units);
+ g_free (width);
+}
+
+static void
+html_editor_hrule_dialog_get_width (EHTMLEditorHRuleDialog *dialog)
+{
+ gchar *width;
+ const gchar *units;
+ gint width_int = 0;
+
+ g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element));
+
+ width = webkit_dom_htmlhr_element_get_width (dialog->priv->hr_element);
+ if (width && *width) {
+ width_int = atoi (width);
+
+ if (strstr (width, "%") != NULL) {
+ units = "units-percent";
+ } else {
+ units = "units-px";
+ }
+ }
+
+ if (width_int == 0) {
+ width_int = 100;
+ units = "units-percent";
+ }
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit), (gdouble) width_int);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->unit_combo), units);
+
+ g_free (width);
+}
+
+static void
+html_editor_hrule_dialog_set_shading (EHTMLEditorHRuleDialog *dialog)
+{
+ gboolean no_shade;
+
+ g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element));
+
+ no_shade = !gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->shaded_check));
+
+ webkit_dom_htmlhr_element_set_no_shade (dialog->priv->hr_element, no_shade);
+}
+
+static void
+html_editor_hrule_dialog_get_shading (EHTMLEditorHRuleDialog *dialog)
+{
+ g_return_if_fail (WEBKIT_DOM_IS_HTMLHR_ELEMENT (dialog->priv->hr_element));
+
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->shaded_check),
+ !webkit_dom_htmlhr_element_get_no_shade (dialog->priv->hr_element));
+}
+
+static void
+html_editor_hrule_dialog_hide (GtkWidget *widget)
+{
+ EHTMLEditorHRuleDialogPrivate *priv;
+
+ priv = E_HTML_EDITOR_HRULE_DIALOG_GET_PRIVATE (widget);
+
+ priv->hr_element = NULL;
+
+ GTK_WIDGET_CLASS (e_html_editor_hrule_dialog_parent_class)->hide (widget);
+}
+
+static void
+html_editor_hrule_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditorHRuleDialog *dialog;
+ EHTMLEditor *editor;
+ EHTMLEditorSelection *editor_selection;
+ EHTMLEditorView *view;
+
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *selection;
+ WebKitDOMElement *rule;
+
+ dialog = E_HTML_EDITOR_HRULE_DIALOG (widget);
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ editor_selection = e_html_editor_view_get_selection (view);
+
+ document = webkit_web_view_get_dom_document (
+ WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ selection = webkit_dom_dom_window_get_selection (window);
+ if (webkit_dom_dom_selection_get_range_count (selection) < 1) {
+ GTK_WIDGET_CLASS (e_html_editor_hrule_dialog_parent_class)->show (widget);
+ return;
+ }
+
+ rule = e_html_editor_view_get_element_under_mouse_click (view);
+ if (!rule) {
+ WebKitDOMElement *caret, *parent, *element;
+
+ caret = e_html_editor_selection_save_caret_position (editor_selection);
+
+ parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (caret));
+ element = caret;
+
+ while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
+ element = parent;
+ parent = webkit_dom_node_get_parent_element (
+ WEBKIT_DOM_NODE (parent));
+ }
+
+ rule = webkit_dom_document_create_element (document, "HR", NULL);
+
+ /* Insert horizontal rule into body below the caret */
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (parent),
+ WEBKIT_DOM_NODE (rule),
+ webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)),
+ NULL);
+
+ e_html_editor_selection_clear_caret_position_marker (editor_selection);
+
+ dialog->priv->hr_element = WEBKIT_DOM_HTMLHR_ELEMENT (rule);
+
+ /* For new rule reset the values to default */
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit), 100.0);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->unit_combo), "units-percent");
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->size_edit), 2.0);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->alignment_combo), "left");
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->shaded_check), FALSE);
+
+ html_editor_hrule_dialog_set_alignment (dialog);
+ html_editor_hrule_dialog_set_size (dialog);
+ html_editor_hrule_dialog_set_alignment (dialog);
+ html_editor_hrule_dialog_set_shading (dialog);
+
+ e_html_editor_view_set_changed (view, TRUE);
+ } else {
+ dialog->priv->hr_element = WEBKIT_DOM_HTMLHR_ELEMENT (rule);
+
+ html_editor_hrule_dialog_get_alignment (dialog);
+ html_editor_hrule_dialog_get_size (dialog);
+ html_editor_hrule_dialog_get_width (dialog);
+ html_editor_hrule_dialog_get_shading (dialog);
+ }
+
+ /* Chain up to parent implementation */
+ GTK_WIDGET_CLASS (e_html_editor_hrule_dialog_parent_class)->show (widget);
+}
+
+static void
+e_html_editor_hrule_dialog_class_init (EHTMLEditorHRuleDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorHRuleDialogPrivate));
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_hrule_dialog_show;
+ widget_class->hide = html_editor_hrule_dialog_hide;
+}
+
+static void
+e_html_editor_hrule_dialog_init (EHTMLEditorHRuleDialog *dialog)
+{
+ GtkGrid *main_layout, *grid;
+ GtkWidget *widget;
+
+ dialog->priv = E_HTML_EDITOR_HRULE_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ /* == Size == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Size</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1);
+
+ /* Width */
+ widget = gtk_spin_button_new_with_range (0.0, 100.0, 1.0);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), 100);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_hrule_dialog_set_width), dialog);
+ dialog->priv->width_edit = widget;
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+
+ widget = gtk_label_new_with_mnemonic (_("_Width:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->size_edit);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-px", "px");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-percent", "%");
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "units-percent");
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_hrule_dialog_set_width), dialog);
+ dialog->priv->unit_combo = widget;
+ gtk_grid_attach (grid, widget, 2, 0, 1, 1);
+
+ /* Size */
+ widget = gtk_spin_button_new_with_range (0.0, 100.0, 1.0);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), 2);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_hrule_dialog_set_size), dialog);
+ dialog->priv->size_edit = widget;
+ gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+
+ widget = gtk_label_new_with_mnemonic (_("_Size:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->size_edit);
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ /* == Style == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Style</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 2, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1);
+
+ /* Alignment */
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget), "left", _("Left"));
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget), "center", _("Center"));
+ gtk_combo_box_text_append (
+ GTK_COMBO_BOX_TEXT (widget), "right", _("Right"));
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "left");
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_hrule_dialog_set_alignment), dialog);
+ dialog->priv->alignment_combo = widget;
+ gtk_grid_attach (grid, widget, 1, 0, 2, 1);
+
+ widget = gtk_label_new_with_mnemonic (_("_Alignment:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), widget);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ /* Shaded */
+ widget = gtk_check_button_new_with_mnemonic (_("S_haded"));
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_hrule_dialog_set_shading), dialog);
+ dialog->priv->shaded_check = widget;
+ gtk_grid_attach (grid, widget, 0, 1, 2, 1);
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_hrule_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_HRULE_DIALOG,
+ "editor", editor,
+ "title", _("Rule properties"),
+ NULL));
+}
diff --git a/e-util/e-html-editor-hrule-dialog.h b/e-util/e-html-editor-hrule-dialog.h
new file mode 100644
index 0000000000..876fc2515c
--- /dev/null
+++ b/e-util/e-html-editor-hrule-dialog.h
@@ -0,0 +1,70 @@
+/*
+ * e-html-editor-hrule-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_HRULE_DIALOG_H
+#define E_HTML_EDITOR_HRULE_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_HRULE_DIALOG \
+ (e_html_editor_hrule_dialog_get_type ())
+#define E_HTML_EDITOR_HRULE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_HRULE_DIALOG, EHTMLEditorHRuleDialog))
+#define E_HTML_EDITOR_HRULE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_HRULE_DIALOG, EHTMLEditorHRuleDialogClass))
+#define E_IS_HTML_EDITOR_HRULE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_HRULE_DIALOG))
+#define E_IS_HTML_EDITOR_HRULE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_HRULE_DIALOG))
+#define E_HTML_EDITOR_HRULE_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_HRULE_DIALOG, EHTMLEditorHRuleDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorHRuleDialog EHTMLEditorHRuleDialog;
+typedef struct _EHTMLEditorHRuleDialogClass EHTMLEditorHRuleDialogClass;
+typedef struct _EHTMLEditorHRuleDialogPrivate EHTMLEditorHRuleDialogPrivate;
+
+struct _EHTMLEditorHRuleDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorHRuleDialogPrivate *priv;
+};
+
+struct _EHTMLEditorHRuleDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_hrule_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_hrule_dialog_new (EHTMLEditor *editor);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_HRULE_DIALOG_H */
diff --git a/e-util/e-html-editor-image-dialog.c b/e-util/e-html-editor-image-dialog.c
new file mode 100644
index 0000000000..346de4419b
--- /dev/null
+++ b/e-util/e-html-editor-image-dialog.c
@@ -0,0 +1,703 @@
+/*
+ * e-html-editor-image-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-image-dialog.h"
+
+#include <stdlib.h>
+#include <glib/gi18n-lib.h>
+
+#include "e-html-editor-utils.h"
+#include "e-image-chooser-dialog.h"
+
+#define E_HTML_EDITOR_IMAGE_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_IMAGE_DIALOG, EHTMLEditorImageDialogPrivate))
+
+struct _EHTMLEditorImageDialogPrivate {
+ GtkWidget *file_chooser;
+ GtkWidget *description_edit;
+
+ GtkWidget *width_edit;
+ GtkWidget *width_units;
+ GtkWidget *height_edit;
+ GtkWidget *height_units;
+ GtkWidget *alignment;
+
+ GtkWidget *x_padding_edit;
+ GtkWidget *y_padding_edit;
+ GtkWidget *border_edit;
+
+ GtkWidget *url_edit;
+ GtkWidget *test_url_button;
+
+ WebKitDOMHTMLImageElement *image;
+};
+
+G_DEFINE_TYPE (
+ EHTMLEditorImageDialog,
+ e_html_editor_image_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+static void
+html_editor_image_dialog_set_src (EHTMLEditorImageDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorSelection *editor_selection;
+ EHTMLEditorView *view;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ editor_selection = e_html_editor_view_get_selection (view);
+
+ e_html_editor_selection_replace_image_src (
+ editor_selection,
+ WEBKIT_DOM_ELEMENT (dialog->priv->image),
+ gtk_file_chooser_get_uri (
+ GTK_FILE_CHOOSER (dialog->priv->file_chooser)));
+}
+
+static void
+html_editor_image_dialog_set_alt (EHTMLEditorImageDialog *dialog)
+{
+ webkit_dom_html_image_element_set_alt (
+ dialog->priv->image,
+ gtk_entry_get_text (GTK_ENTRY (dialog->priv->description_edit)));
+}
+
+static void
+html_editor_image_dialog_set_width (EHTMLEditorImageDialog *dialog)
+{
+ gint requested;
+ gulong natural;
+ gint width;
+
+ natural = webkit_dom_html_image_element_get_natural_width (
+ dialog->priv->image);
+ requested = gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit));
+
+ switch (gtk_combo_box_get_active (
+ GTK_COMBO_BOX (dialog->priv->width_units))) {
+
+ case 0: /* px */
+ width = requested;
+ break;
+
+ case 1: /* percent */
+ width = natural * requested * 0.01;
+ break;
+
+ case 2: /* follow */
+ width = natural;
+ break;
+
+ }
+
+ webkit_dom_html_image_element_set_width (dialog->priv->image, width);
+}
+
+static void
+html_editor_image_dialog_set_width_units (EHTMLEditorImageDialog *dialog)
+{
+ gint requested;
+ gulong natural;
+ gint width = 0;
+
+ natural = webkit_dom_html_image_element_get_natural_width (
+ dialog->priv->image);
+ requested = gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit));
+
+ switch (gtk_combo_box_get_active (
+ GTK_COMBO_BOX (dialog->priv->width_units))) {
+
+ case 0: /* px */
+ if (gtk_widget_is_sensitive (dialog->priv->width_edit)) {
+ width = requested * natural * 0.01;
+ } else {
+ width = natural;
+ }
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->image), "style");
+ gtk_widget_set_sensitive (dialog->priv->width_edit, TRUE);
+ break;
+
+ case 1: /* percent */
+ if (gtk_widget_is_sensitive (dialog->priv->width_edit)) {
+ width = (((gdouble) requested) / natural) * 100;
+ } else {
+ width = 100;
+ }
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->image), "style");
+ gtk_widget_set_sensitive (dialog->priv->width_edit, TRUE);
+ break;
+
+ case 2: /* follow */
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->image),
+ "style",
+ "width: auto;",
+ NULL);
+ gtk_widget_set_sensitive (dialog->priv->width_edit, FALSE);
+ break;
+ }
+
+ if (width != 0) {
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit), width);
+ }
+}
+
+static void
+html_editor_image_dialog_set_height (EHTMLEditorImageDialog *dialog)
+{
+ gint requested;
+ gulong natural;
+ gint height;
+
+ natural = webkit_dom_html_image_element_get_natural_height (
+ dialog->priv->image);
+ requested = gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->height_edit));
+
+ switch (gtk_combo_box_get_active (
+ GTK_COMBO_BOX (dialog->priv->height_units))) {
+
+ case 0: /* px */
+ height = requested;
+ break;
+
+ case 1: /* percent */
+ height = natural * requested * 0.01;
+ break;
+
+ case 2: /* follow */
+ height = natural;
+ break;
+
+ }
+
+ webkit_dom_html_image_element_set_height (dialog->priv->image, height);
+}
+
+static void
+html_editor_image_dialog_set_height_units (EHTMLEditorImageDialog *dialog)
+{
+ gint requested;
+ gulong natural;
+ gint height = -1;
+
+ natural = webkit_dom_html_image_element_get_natural_height (
+ dialog->priv->image);
+ requested = gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->height_edit));
+
+ switch (gtk_combo_box_get_active (
+ GTK_COMBO_BOX (dialog->priv->height_units))) {
+
+ case 0: /* px */
+ if (gtk_widget_is_sensitive (dialog->priv->height_edit)) {
+ height = requested * natural * 0.01;
+ } else {
+ height = natural;
+ }
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->image), "style");
+ gtk_widget_set_sensitive (dialog->priv->height_edit, TRUE);
+ break;
+
+ case 1: /* percent */
+ if (gtk_widget_is_sensitive (dialog->priv->height_edit)) {
+ height = (((gdouble) requested) / natural) * 100;
+ } else {
+ height = 100;
+ }
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->image), "style");
+ gtk_widget_set_sensitive (dialog->priv->height_edit, TRUE);
+ break;
+
+ case 2: /* follow */
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->image),
+ "style",
+ "height: auto;",
+ NULL);
+ gtk_widget_set_sensitive (dialog->priv->height_edit, FALSE);
+ break;
+ }
+
+ if (height != -1) {
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->height_edit), height);
+ }
+}
+
+static void
+html_editor_image_dialog_set_alignment (EHTMLEditorImageDialog *dialog)
+{
+ webkit_dom_html_image_element_set_align (
+ dialog->priv->image,
+ gtk_combo_box_get_active_id (
+ GTK_COMBO_BOX (dialog->priv->alignment)));
+}
+
+static void
+html_editor_image_dialog_set_x_padding (EHTMLEditorImageDialog *dialog)
+{
+ webkit_dom_html_image_element_set_hspace (
+ dialog->priv->image,
+ gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->x_padding_edit)));
+}
+
+static void
+html_editor_image_dialog_set_y_padding (EHTMLEditorImageDialog *dialog)
+{
+ webkit_dom_html_image_element_set_vspace (
+ dialog->priv->image,
+ gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->y_padding_edit)));
+}
+
+static void
+html_editor_image_dialog_set_border (EHTMLEditorImageDialog *dialog)
+{
+ gchar *val;
+
+ val = g_strdup_printf (
+ "%d", gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->border_edit)));
+
+ webkit_dom_html_image_element_set_border (dialog->priv->image, val);
+
+ g_free (val);
+}
+
+static void
+html_editor_image_dialog_set_url (EHTMLEditorImageDialog *dialog)
+{
+ WebKitDOMElement *link;
+ const gchar *url;
+
+ url = gtk_entry_get_text (GTK_ENTRY (dialog->priv->url_edit));
+ link = e_html_editor_dom_node_find_parent_element (
+ WEBKIT_DOM_NODE (dialog->priv->image), "A");
+
+ if (link) {
+ if (!url || !*url) {
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (link)),
+ WEBKIT_DOM_NODE (dialog->priv->image),
+ WEBKIT_DOM_NODE (link), NULL);
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (link)),
+ WEBKIT_DOM_NODE (link), NULL);
+ } else {
+ webkit_dom_html_anchor_element_set_href (
+ WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link), url);
+ }
+ } else {
+ if (url && *url) {
+ WebKitDOMDocument *document;
+
+ document = webkit_dom_node_get_owner_document (
+ WEBKIT_DOM_NODE (dialog->priv->image));
+ link = webkit_dom_document_create_element (
+ document, "A", NULL);
+
+ webkit_dom_html_anchor_element_set_href (
+ WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link), url);
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (dialog->priv->image)),
+ WEBKIT_DOM_NODE (link),
+ WEBKIT_DOM_NODE (dialog->priv->image), NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (link),
+ WEBKIT_DOM_NODE (dialog->priv->image), NULL);
+ }
+ }
+}
+
+static void
+html_editor_image_dialog_test_url (EHTMLEditorImageDialog *dialog)
+{
+ gtk_show_uri (
+ gtk_window_get_screen (GTK_WINDOW (dialog)),
+ gtk_entry_get_text (GTK_ENTRY (dialog->priv->url_edit)),
+ GDK_CURRENT_TIME,
+ NULL);
+}
+
+static void
+html_editor_image_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditorImageDialog *dialog;
+ WebKitDOMElement *link;
+ gchar *tmp;
+ glong val;
+
+ dialog = E_HTML_EDITOR_IMAGE_DIALOG (widget);
+
+ if (!dialog->priv->image) {
+ return;
+ }
+
+ tmp = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->image), "data-uri");
+ if (tmp && *tmp) {
+ gtk_file_chooser_set_uri (
+ GTK_FILE_CHOOSER (dialog->priv->file_chooser), tmp);
+ gtk_widget_set_sensitive (
+ GTK_WIDGET (dialog->priv->file_chooser), TRUE);
+ g_free (tmp);
+ } else {
+ gtk_file_chooser_set_uri (
+ GTK_FILE_CHOOSER (dialog->priv->file_chooser), "");
+ gtk_widget_set_sensitive (
+ GTK_WIDGET (dialog->priv->file_chooser), FALSE);
+ }
+
+ tmp = webkit_dom_html_image_element_get_alt (dialog->priv->image);
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->description_edit), tmp ? tmp : "");
+ g_free (tmp);
+
+ val = webkit_dom_html_image_element_get_width (dialog->priv->image);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit), val);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->width_units), "units-px");
+
+ val = webkit_dom_html_image_element_get_height (dialog->priv->image);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->height_edit), val);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->height_units), "units-px");
+
+ tmp = webkit_dom_html_image_element_get_border (dialog->priv->image);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->alignment),
+ (tmp && *tmp) ? tmp : "bottom");
+ g_free (tmp);
+
+ val = webkit_dom_html_image_element_get_hspace (dialog->priv->image);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->x_padding_edit), val);
+
+ val = webkit_dom_html_image_element_get_vspace (dialog->priv->image);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->y_padding_edit), val);
+
+ link = e_html_editor_dom_node_find_parent_element (
+ WEBKIT_DOM_NODE (dialog->priv->image), "A");
+ if (link) {
+ tmp = webkit_dom_html_anchor_element_get_href (
+ WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link));
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->url_edit), tmp);
+ g_free (tmp);
+ }
+
+ /* Chain up to parent implementation */
+ GTK_WIDGET_CLASS (e_html_editor_image_dialog_parent_class)->show (widget);
+}
+
+static void
+html_editor_image_dialog_hide (GtkWidget *widget)
+{
+ EHTMLEditorImageDialogPrivate *priv;
+
+ priv = E_HTML_EDITOR_IMAGE_DIALOG_GET_PRIVATE (widget);
+
+ priv->image = NULL;
+
+ GTK_WIDGET_CLASS (e_html_editor_image_dialog_parent_class)->hide (widget);
+}
+
+static void
+e_html_editor_image_dialog_class_init (EHTMLEditorImageDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorImageDialogPrivate));
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_image_dialog_show;
+ widget_class->hide = html_editor_image_dialog_hide;
+}
+
+static void
+e_html_editor_image_dialog_init (EHTMLEditorImageDialog *dialog)
+{
+ GtkGrid *main_layout, *grid;
+ GtkWidget *widget;
+ GtkFileFilter *file_filter;
+
+ dialog->priv = E_HTML_EDITOR_IMAGE_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ /* == General == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>General</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Source */
+ widget = e_image_chooser_dialog_new (
+ _("Choose Background Image"),
+ GTK_WINDOW (dialog));
+ gtk_file_chooser_set_action (
+ GTK_FILE_CHOOSER (widget), GTK_FILE_CHOOSER_ACTION_OPEN);
+
+ file_filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (file_filter, _("Images"));
+ gtk_file_filter_add_mime_type (file_filter, "image/*");
+
+ widget = gtk_file_chooser_button_new_with_dialog (widget);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "file-set",
+ G_CALLBACK (html_editor_image_dialog_set_src), dialog);
+ dialog->priv->file_chooser = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Source:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->file_chooser);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ /* Description */
+ widget = gtk_entry_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+ g_signal_connect_swapped (
+ widget, "notify::text",
+ G_CALLBACK (html_editor_image_dialog_set_alt), dialog);
+ dialog->priv->description_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Description:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->description_edit);
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ /* == Layout == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Layout</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 2, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Width */
+ widget = gtk_spin_button_new_with_range (1, G_MAXUINT, 1);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_image_dialog_set_width), dialog);
+ dialog->priv->width_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Width:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->width_edit);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-px", "px");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-percent", "%");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-follow", "follow");
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "units-px");
+ gtk_grid_attach (grid, widget, 2, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_image_dialog_set_width_units), dialog);
+ dialog->priv->width_units = widget;
+
+ /* Height */
+ widget = gtk_spin_button_new_with_range (1, G_MAXUINT, 1);
+ gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_image_dialog_set_height), dialog);
+ dialog->priv->height_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Height:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->height_edit);
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-px", "px");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-percent", "%");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-follow", "follow");
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "units-px");
+ gtk_grid_attach (grid, widget, 2, 1, 1, 1);
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_image_dialog_set_height_units), dialog);
+ dialog->priv->height_units = widget;
+
+ /* Alignment */
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "top", _("Top"));
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "middle", _("Middle"));
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "bottom", _("Bottom"));
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "bottom");
+ gtk_grid_attach (grid, widget, 1, 2, 1, 1);
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_image_dialog_set_alignment), dialog);
+ dialog->priv->alignment = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Alignment"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->alignment);
+ gtk_grid_attach (grid, widget, 0, 2, 1, 1);
+
+ /* X Padding */
+ widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1);
+ gtk_grid_attach (grid, widget, 5, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_image_dialog_set_x_padding), dialog);
+ dialog->priv->x_padding_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_X-Padding:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->x_padding_edit);
+ gtk_grid_attach (grid, widget, 4, 0, 1, 1);
+
+ widget = gtk_label_new ("px");
+ gtk_grid_attach (grid, widget, 6, 0, 1, 1);
+
+ /* Y Padding */
+ widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1);
+ gtk_grid_attach (grid, widget, 5, 1, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_image_dialog_set_y_padding), dialog);
+ dialog->priv->y_padding_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Y-Padding:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->y_padding_edit);
+ gtk_grid_attach (grid, widget, 4, 1, 1, 1);
+
+ widget = gtk_label_new ("px");
+ gtk_grid_attach (grid, widget, 6, 1, 1, 1);
+
+ /* Border */
+ widget = gtk_spin_button_new_with_range (0, G_MAXUINT, 1);
+ gtk_grid_attach (grid, widget, 5, 2, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_image_dialog_set_border), dialog);
+ dialog->priv->border_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Border:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->border_edit);
+ gtk_grid_attach (grid, widget, 4, 2, 1, 1);
+
+ widget = gtk_label_new ("px");
+ gtk_grid_attach (grid, widget, 6, 2, 1, 1);
+
+ /* == Layout == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Link</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 4, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 5, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ widget = gtk_entry_new ();
+ gtk_grid_attach (grid, widget, 1 ,0, 1, 1);
+ gtk_widget_set_hexpand (widget, TRUE);
+ g_signal_connect_swapped (
+ widget, "notify::text",
+ G_CALLBACK (html_editor_image_dialog_set_url), dialog);
+ dialog->priv->url_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_URL:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->url_edit);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ widget = gtk_button_new_with_mnemonic (_("_Test URL..."));
+ gtk_grid_attach (grid, widget, 2, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_image_dialog_test_url), dialog);
+ dialog->priv->test_url_button = widget;
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_image_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_IMAGE_DIALOG,
+ "editor", editor,
+ "title", N_("Image Properties"),
+ NULL));
+}
+
+void
+e_html_editor_image_dialog_show (EHTMLEditorImageDialog *dialog,
+ WebKitDOMNode *image)
+{
+ EHTMLEditorImageDialogClass *class;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_IMAGE_DIALOG (dialog));
+
+ if (image) {
+ dialog->priv->image = WEBKIT_DOM_HTML_IMAGE_ELEMENT (image);
+ } else {
+ dialog->priv->image = NULL;
+ }
+
+ class = E_HTML_EDITOR_IMAGE_DIALOG_GET_CLASS (dialog);
+ GTK_WIDGET_CLASS (class)->show (GTK_WIDGET (dialog));
+}
diff --git a/e-util/e-html-editor-image-dialog.h b/e-util/e-html-editor-image-dialog.h
new file mode 100644
index 0000000000..efdbaf963c
--- /dev/null
+++ b/e-util/e-html-editor-image-dialog.h
@@ -0,0 +1,73 @@
+/*
+ * e-html-editor-image-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_IMAGE_DIALOG_H
+#define E_HTML_EDITOR_IMAGE_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_IMAGE_DIALOG \
+ (e_html_editor_image_dialog_get_type ())
+#define E_HTML_EDITOR_IMAGE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_IMAGE_DIALOG, EHTMLEditorImageDialog))
+#define E_HTML_EDITOR_IMAGE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_IMAGE_DIALOG, EHTMLEditorImageDialogClass))
+#define E_IS_HTML_EDITOR_IMAGE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_IMAGE_DIALOG))
+#define E_IS_HTML_EDITOR_IMAGE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_IMAGE_DIALOG))
+#define E_HTML_EDITOR_IMAGE_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_IMAGE_DIALOG, EHTMLEditorImageDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorImageDialog EHTMLEditorImageDialog;
+typedef struct _EHTMLEditorImageDialogClass EHTMLEditorImageDialogClass;
+typedef struct _EHTMLEditorImageDialogPrivate EHTMLEditorImageDialogPrivate;
+
+struct _EHTMLEditorImageDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorImageDialogPrivate *priv;
+};
+
+struct _EHTMLEditorImageDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_image_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_image_dialog_new (EHTMLEditor *editor);
+void e_html_editor_image_dialog_show (EHTMLEditorImageDialog *dialog,
+ WebKitDOMNode *image);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_IMAGE_DIALOG_H */
+
diff --git a/e-util/e-html-editor-link-dialog.c b/e-util/e-html-editor-link-dialog.c
new file mode 100644
index 0000000000..0572d07c09
--- /dev/null
+++ b/e-util/e-html-editor-link-dialog.c
@@ -0,0 +1,390 @@
+/*
+ * e-html-editor-link-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-link-dialog.h"
+#include "e-html-editor-selection.h"
+#include "e-html-editor-utils.h"
+#include "e-html-editor-view.h"
+
+#include <glib/gi18n-lib.h>
+
+#define E_HTML_EDITOR_LINK_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_LINK_DIALOG, EHTMLEditorLinkDialogPrivate))
+
+G_DEFINE_TYPE (
+ EHTMLEditorLinkDialog,
+ e_html_editor_link_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+struct _EHTMLEditorLinkDialogPrivate {
+ GtkWidget *url_edit;
+ GtkWidget *label_edit;
+ GtkWidget *test_button;
+
+ GtkWidget *remove_link_button;
+ GtkWidget *ok_button;
+
+ gboolean label_autofill;
+};
+
+static void
+html_editor_link_dialog_test_link (EHTMLEditorLinkDialog *dialog)
+{
+ gtk_show_uri (
+ gtk_window_get_screen (GTK_WINDOW (dialog)),
+ gtk_entry_get_text (GTK_ENTRY (dialog->priv->url_edit)),
+ GDK_CURRENT_TIME,
+ NULL);
+}
+
+static void
+html_editor_link_dialog_url_changed (EHTMLEditorLinkDialog *dialog)
+{
+ if (dialog->priv->label_autofill &&
+ gtk_widget_is_sensitive (dialog->priv->label_edit)) {
+ const gchar *text;
+
+ text = gtk_entry_get_text (
+ GTK_ENTRY (dialog->priv->url_edit));
+ gtk_entry_set_text (
+ GTK_ENTRY (dialog->priv->label_edit), text);
+ }
+}
+
+static gboolean
+html_editor_link_dialog_description_changed (EHTMLEditorLinkDialog *dialog)
+{
+ const gchar *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (dialog->priv->label_edit));
+ dialog->priv->label_autofill = (*text == '\0');
+
+ return FALSE;
+}
+
+static void
+html_editor_link_dialog_remove_link (EHTMLEditorLinkDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+ e_html_editor_selection_unlink (selection);
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static void
+html_editor_link_dialog_ok (EHTMLEditorLinkDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *dom_selection;
+ WebKitDOMRange *range;
+ WebKitDOMElement *link;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+
+ if (!dom_selection ||
+ (webkit_dom_dom_selection_get_range_count (dom_selection) == 0)) {
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ return;
+ }
+
+ range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
+ link = e_html_editor_dom_node_find_parent_element (
+ webkit_dom_range_get_start_container (range, NULL), "A");
+ if (!link) {
+ if ((webkit_dom_range_get_start_container (range, NULL) !=
+ webkit_dom_range_get_end_container (range, NULL)) ||
+ (webkit_dom_range_get_start_offset (range, NULL) !=
+ webkit_dom_range_get_end_offset (range, NULL))) {
+
+ WebKitDOMDocumentFragment *fragment;
+ fragment = webkit_dom_range_extract_contents (range, NULL);
+ link = e_html_editor_dom_node_find_child_element (
+ WEBKIT_DOM_NODE (fragment), "A");
+ webkit_dom_range_insert_node (
+ range, WEBKIT_DOM_NODE (fragment), NULL);
+
+ webkit_dom_dom_selection_set_base_and_extent (
+ dom_selection,
+ webkit_dom_range_get_start_container (range, NULL),
+ webkit_dom_range_get_start_offset (range, NULL),
+ webkit_dom_range_get_end_container (range, NULL),
+ webkit_dom_range_get_end_offset (range, NULL),
+ NULL);
+ } else {
+ /* get element that was clicked on */
+ link = e_html_editor_view_get_element_under_mouse_click (view);
+ if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link))
+ link = NULL;
+ }
+ }
+
+ if (link) {
+ webkit_dom_html_anchor_element_set_href (
+ WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link),
+ gtk_entry_get_text (GTK_ENTRY (dialog->priv->url_edit)));
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (link),
+ gtk_entry_get_text (GTK_ENTRY (dialog->priv->label_edit)),
+ NULL);
+ } else {
+ gchar *text;
+
+ /* Check whether a text is selected or not */
+ text = webkit_dom_range_get_text (range);
+ if (text && *text) {
+ e_html_editor_selection_create_link (
+ selection,
+ gtk_entry_get_text (
+ GTK_ENTRY (dialog->priv->url_edit)));
+ } else {
+ gchar *html = g_strdup_printf (
+ "<a href=\"%s\">%s</a>",
+ gtk_entry_get_text (
+ GTK_ENTRY (dialog->priv->url_edit)),
+ gtk_entry_get_text (
+ GTK_ENTRY (dialog->priv->label_edit)));
+
+ e_html_editor_view_exec_command (
+ view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, html);
+
+ g_free (html);
+
+ }
+
+ g_free (text);
+ }
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+}
+
+static gboolean
+html_editor_link_dialog_entry_key_pressed (EHTMLEditorLinkDialog *dialog,
+ GdkEventKey *event)
+{
+ /* We can't do thins in key_released, because then you could not open
+ * this dialog from main menu by pressing enter on Insert->Link action */
+ if (event->keyval == GDK_KEY_Return) {
+ html_editor_link_dialog_ok (dialog);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+html_editor_link_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorLinkDialog *dialog;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *dom_selection;
+ WebKitDOMRange *range;
+ WebKitDOMElement *link;
+
+ dialog = E_HTML_EDITOR_LINK_DIALOG (widget);
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+
+ /* Reset to default values */
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->url_edit), "http://");
+ gtk_entry_set_text (GTK_ENTRY (dialog->priv->label_edit), "");
+ gtk_widget_set_sensitive (dialog->priv->label_edit, TRUE);
+ gtk_widget_set_sensitive (dialog->priv->remove_link_button, TRUE);
+ dialog->priv->label_autofill = TRUE;
+
+ /* No selection at all */
+ if (!dom_selection ||
+ webkit_dom_dom_selection_get_range_count (dom_selection) < 1) {
+ gtk_widget_set_sensitive (dialog->priv->remove_link_button, FALSE);
+ goto chainup;
+ }
+
+ range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
+ link = e_html_editor_dom_node_find_parent_element (
+ webkit_dom_range_get_start_container (range, NULL), "A");
+ if (!link) {
+ if ((webkit_dom_range_get_start_container (range, NULL) !=
+ webkit_dom_range_get_end_container (range, NULL)) ||
+ (webkit_dom_range_get_start_offset (range, NULL) !=
+ webkit_dom_range_get_end_offset (range, NULL))) {
+
+ WebKitDOMDocumentFragment *fragment;
+ fragment = webkit_dom_range_clone_contents (range, NULL);
+ link = e_html_editor_dom_node_find_child_element (
+ WEBKIT_DOM_NODE (fragment), "A");
+ } else {
+ /* get element that was clicked on */
+ link = e_html_editor_view_get_element_under_mouse_click (view);
+ if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link))
+ link = NULL;
+ }
+ }
+
+ if (link) {
+ gchar *href, *text;
+
+ href = webkit_dom_html_anchor_element_get_href (
+ WEBKIT_DOM_HTML_ANCHOR_ELEMENT (link));
+ text = webkit_dom_html_element_get_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (link));
+
+ gtk_entry_set_text (
+ GTK_ENTRY (dialog->priv->url_edit), href);
+ gtk_entry_set_text (
+ GTK_ENTRY (dialog->priv->label_edit), text);
+
+ g_free (text);
+ g_free (href);
+ } else {
+ gchar *text;
+
+ text = webkit_dom_range_get_text (range);
+ if (text && *text) {
+ gtk_entry_set_text (
+ GTK_ENTRY (dialog->priv->label_edit), text);
+ gtk_widget_set_sensitive (
+ dialog->priv->label_edit, FALSE);
+ gtk_widget_set_sensitive (
+ dialog->priv->remove_link_button, FALSE);
+ }
+ g_free (text);
+ }
+
+ chainup:
+ /* Chain up to parent implementation */
+ GTK_WIDGET_CLASS (e_html_editor_link_dialog_parent_class)->show (widget);
+}
+
+static void
+e_html_editor_link_dialog_class_init (EHTMLEditorLinkDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorLinkDialogPrivate));
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_link_dialog_show;
+}
+
+static void
+e_html_editor_link_dialog_init (EHTMLEditorLinkDialog *dialog)
+{
+ GtkGrid *main_layout;
+ GtkBox *button_box;
+ GtkWidget *widget;
+
+ dialog->priv = E_HTML_EDITOR_LINK_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ widget = gtk_entry_new ();
+ gtk_grid_attach (main_layout, widget, 1, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "notify::text",
+ G_CALLBACK (html_editor_link_dialog_url_changed), dialog);
+ g_signal_connect_swapped (
+ widget, "key-press-event",
+ G_CALLBACK (html_editor_link_dialog_entry_key_pressed), dialog);
+ dialog->priv->url_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_URL:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->url_edit);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+
+ widget = gtk_button_new_with_mnemonic (_("_Test URL..."));
+ gtk_grid_attach (main_layout, widget, 2, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_link_dialog_test_link), dialog);
+ dialog->priv->test_button = widget;
+
+ widget = gtk_entry_new ();
+ gtk_grid_attach (main_layout, widget, 1, 1, 2, 1);
+ g_signal_connect_swapped (
+ widget, "key-release-event",
+ G_CALLBACK (html_editor_link_dialog_description_changed), dialog);
+ g_signal_connect_swapped (
+ widget, "key-press-event",
+ G_CALLBACK (html_editor_link_dialog_entry_key_pressed), dialog);
+ dialog->priv->label_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Description:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->label_edit);
+ gtk_grid_attach (main_layout, widget, 0, 1, 1, 1);
+
+ button_box = e_html_editor_dialog_get_button_box (E_HTML_EDITOR_DIALOG (dialog));
+
+ widget = gtk_button_new_with_mnemonic (_("_Remove Link"));
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_link_dialog_remove_link), dialog);
+ gtk_box_pack_start (button_box, widget, FALSE, FALSE, 5);
+ dialog->priv->remove_link_button = widget;
+
+ widget = gtk_button_new_from_stock (GTK_STOCK_OK);
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_link_dialog_ok), dialog);
+ gtk_box_pack_end (button_box, widget, FALSE, FALSE, 5);
+ dialog->priv->ok_button = widget;
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_link_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_LINK_DIALOG,
+ "editor", editor,
+ "icon-name", "insert-link",
+ "title", N_("Link Properties"),
+ NULL));
+}
diff --git a/e-util/e-html-editor-link-dialog.h b/e-util/e-html-editor-link-dialog.h
new file mode 100644
index 0000000000..a1e426fe01
--- /dev/null
+++ b/e-util/e-html-editor-link-dialog.h
@@ -0,0 +1,70 @@
+/*
+ * e-html-editor-link-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_LINK_DIALOG_H
+#define E_HTML_EDITOR_LINK_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_LINK_DIALOG \
+ (e_html_editor_link_dialog_get_type ())
+#define E_HTML_EDITOR_LINK_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_LINK_DIALOG, EHTMLEditorLinkDialog))
+#define E_HTML_EDITOR_LINK_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_LINK_DIALOG, EHTMLEditorLinkDialogClass))
+#define E_IS_HTML_EDITOR_LINK_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_LINK_DIALOG))
+#define E_IS_HTML_EDITOR_LINK_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_LINK_DIALOG))
+#define E_HTML_EDITOR_LINK_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_LINK_DIALOG, EHTMLEditorLinkDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorLinkDialog EHTMLEditorLinkDialog;
+typedef struct _EHTMLEditorLinkDialogClass EHTMLEditorLinkDialogClass;
+typedef struct _EHTMLEditorLinkDialogPrivate EHTMLEditorLinkDialogPrivate;
+
+struct _EHTMLEditorLinkDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorLinkDialogPrivate *priv;
+};
+
+struct _EHTMLEditorLinkDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_link_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_link_dialog_new (EHTMLEditor *editor);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_LINK_DIALOG_H */
diff --git a/e-util/e-html-editor-manager.ui b/e-util/e-html-editor-manager.ui
new file mode 100644
index 0000000000..75f2c34b20
--- /dev/null
+++ b/e-util/e-html-editor-manager.ui
@@ -0,0 +1,181 @@
+<ui>
+ <menubar name='main-menu'>
+ <placeholder name='pre-file-menu'/>
+ <menu action='file-menu'/>
+ <placeholder name='pre-edit-menu'/>
+ <menu action='edit-menu'>
+ <placeholder name='edit-menu-top'/>
+ <menuitem action='undo'/>
+ <menuitem action='redo'/>
+ <separator/>
+ <menuitem action='cut'/>
+ <menuitem action='copy'/>
+ <menuitem action='paste'/>
+ <menuitem action='paste-quote'/>
+ <separator/>
+ <menuitem action='select-all'/>
+ <separator/>
+ <menuitem action='show-find'/>
+ <menuitem action='find-again'/>
+ <menuitem action='show-replace'/>
+ <separator/>
+ <placeholder name='pre-spell-check'/>
+ <menuitem action='spell-check'/>
+ <menu action='language-menu'/>
+ </menu>
+ <placeholder name='pre-insert-menu'>
+ <menu action='view-menu'>
+ <placeholder name='view-menu-top'/>
+ <menuitem action='webkit-inspector'/>
+ <separator/>
+ </menu>
+ </placeholder>
+ <menu action='insert-menu'>
+ <placeholder name='insert-menu-top'/>
+ <menuitem action='insert-image'/>
+ <menuitem action='insert-link'/>
+ <menuitem action='insert-rule'/>
+ <menuitem action='insert-table'/>
+ <menuitem action='insert-text-file'/>
+ <menuitem action='insert-html-file'/>
+ <menuitem action='insert-emoticon'/>
+ </menu>
+ <placeholder name='pre-format-menu'/>
+ <menu action='format-menu'>
+ <placeholder name='format-menu-top'/>
+ <menuitem action='mode-html'/>
+ <menuitem action='mode-plain'/>
+ <separator/>
+ <menu action='font-style-menu'>
+ <menuitem action='monospaced'/>
+ <separator/>
+ <menuitem action='bold'/>
+ <menuitem action='italic'/>
+ <menuitem action='underline'/>
+ <menuitem action='strikethrough'/>
+ </menu>
+ <menu action='font-size-menu'>
+ <menuitem action='size-minus-two'/>
+ <menuitem action='size-minus-one'/>
+ <menuitem action='size-plus-zero'/>
+ <menuitem action='size-plus-one'/>
+ <menuitem action='size-plus-two'/>
+ <menuitem action='size-plus-three'/>
+ <menuitem action='size-plus-four'/>
+ </menu>
+ <separator/>
+ <menu action='paragraph-style-menu'>
+ <menuitem action='style-normal'/>
+ <separator/>
+ <menuitem action='style-h1'/>
+ <menuitem action='style-h2'/>
+ <menuitem action='style-h3'/>
+ <menuitem action='style-h4'/>
+ <menuitem action='style-h5'/>
+ <menuitem action='style-h6'/>
+ <separator/>
+ <menuitem action='style-list-bullet'/>
+ <menuitem action='style-list-roman'/>
+ <menuitem action='style-list-number'/>
+ <menuitem action='style-list-alpha'/>
+ <separator/>
+ <menuitem action='style-address'/>
+ <menuitem action='style-preformat'/>
+ </menu>
+ <menu action='justify-menu'>
+ <menuitem action='justify-left'/>
+ <menuitem action='justify-center'/>
+ <menuitem action='justify-right'/>
+ </menu>
+ <separator/>
+ <menuitem action='indent'/>
+ <menuitem action='unindent'/>
+ <menuitem action='wrap-lines'/>
+ <separator/>
+ <menuitem action='properties-page'/>
+ </menu>
+ </menubar>
+ <toolbar name='main-toolbar'>
+ <placeholder name='pre-main-toolbar'/>
+ <toolitem action='undo'/>
+ <toolitem action='redo'/>
+ <separator/>
+ <toolitem action='cut'/>
+ <toolitem action='copy'/>
+ <toolitem action='paste'/>
+ <separator/>
+ <toolitem action='show-find'/>
+ <toolitem action='show-replace'/>
+ </toolbar>
+ <toolbar name='edit-toolbar'>
+ <separator/>
+ <toolitem action='justify-left'/>
+ <toolitem action='justify-center'/>
+ <toolitem action='justify-right'/>
+ <separator/>
+ <toolitem action='unindent'/>
+ <toolitem action='indent'/>
+ </toolbar>
+ <toolbar name='html-toolbar'>
+ <separator/>
+ <toolitem action='monospaced'/>
+ <toolitem action='bold'/>
+ <toolitem action='italic'/>
+ <toolitem action='underline'/>
+ <toolitem action='strikethrough'/>
+ <separator/>
+ <toolitem action='insert-image'/>
+ <toolitem action='insert-link'/>
+ <toolitem action='insert-rule'/>
+ <toolitem action='insert-table'/>
+ <toolitem action='insert-emoticon'/>
+ </toolbar>
+ <popup name='context-menu'>
+ <placeholder name='context-spell-suggest'/>
+ <menu action='context-more-suggestions-menu'/>
+ <separator/>
+ <menuitem action='context-spell-ignore'/>
+ <menu action='context-spell-add-menu'/>
+ <menuitem action='context-spell-add'/>
+ <separator/>
+ <menuitem action='undo'/>
+ <menuitem action='redo'/>
+ <separator/>
+ <menuitem action='cut'/>
+ <menuitem action='copy'/>
+ <menuitem action='paste'/>
+ <menuitem action='paste-quote'/>
+ <separator/>
+ <menuitem action='context-insert-link'/>
+ <menuitem action='context-remove-link'/>
+ <separator/>
+ <menu action='context-properties-menu'>
+ <menuitem action='context-properties-text'/>
+ <menuitem action='context-properties-link'/>
+ <menuitem action='context-properties-rule'/>
+ <menuitem action='context-properties-image'/>
+ <menuitem action='context-properties-paragraph'/>
+ <menuitem action='context-properties-cell'/>
+ <menuitem action='context-properties-table'/>
+ <menuitem action='context-properties-page'/>
+ </menu>
+ <separator/>
+ <menu action='context-insert-table-menu'>
+ <menuitem action='context-insert-table'/>
+ <separator/>
+ <menuitem action='context-insert-row-above'/>
+ <menuitem action='context-insert-row-below'/>
+ <separator/>
+ <menuitem action='context-insert-column-before'/>
+ <menuitem action='context-insert-column-after'/>
+ </menu>
+ <menu action='context-delete-table-menu'>
+ <menuitem action='context-delete-table'/>
+ <menuitem action='context-delete-row'/>
+ <menuitem action='context-delete-column'/>
+ <menuitem action='context-delete-cell'/>
+ </menu>
+ <separator/>
+ <menu action='context-input-methods-menu'/>
+ </popup>
+</ui>
diff --git a/e-util/e-html-editor-page-dialog.c b/e-util/e-html-editor-page-dialog.c
new file mode 100644
index 0000000000..53c8fce8c7
--- /dev/null
+++ b/e-util/e-html-editor-page-dialog.c
@@ -0,0 +1,513 @@
+/*
+ * e-html-editor-page-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-page-dialog.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "e-color-combo.h"
+#include "e-misc-utils.h"
+
+#define E_HTML_EDITOR_PAGE_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_PAGE_DIALOG, EHTMLEditorPageDialogPrivate))
+
+struct _EHTMLEditorPageDialogPrivate {
+ GtkWidget *text_color_picker;
+ GtkWidget *link_color_picker;
+ GtkWidget *background_color_picker;
+
+ GtkWidget *background_template_combo;
+ GtkWidget *background_image_filechooser;
+};
+
+typedef struct _Template {
+ const gchar *name;
+ const gchar *filename;
+ GdkRGBA text_color;
+ GdkRGBA link_color;
+ GdkRGBA background_color;
+ gint left_margin;
+} Template;
+
+static const Template templates[] = {
+
+ {
+ N_("None"),
+ NULL,
+ { 0.0, 0.0 , 0.0 , 1 },
+ { 0.3, 0.55, 0.85, 1 },
+ { 1.0, 1.0 , 1.0 , 1 },
+ 0
+ },
+
+ {
+ N_("Perforated Paper"),
+ "paper.png",
+ { 0.0, 0.0, 0.0, 1 },
+ { 0.0, 0.2, 0.4, 1 },
+ { 1.0, 1.0, 1.0, 0 },
+ 30
+ },
+
+ {
+ N_("Blue Ink"),
+ "texture.png",
+ { 0.1, 0.12, 0.56, 1 },
+ { 0.0, 0.0, 1.0, 1 },
+ { 1.0, 1.0, 1.0, 1 },
+ 0
+ },
+
+ {
+ N_("Paper"),
+ "rect.png",
+ { 0, 0, 0, 1 },
+ { 0, 0, 1, 1 },
+ { 1, 1, 1, 1 },
+ 0
+ },
+
+ {
+ N_("Ribbon"),
+ "ribbon.jpg",
+ { 0.0, 0.0, 0.0, 1 },
+ { 0.6, 0.2, 0.4, 1 },
+ { 1.0, 1.0, 1.0, 1 },
+ 70
+ },
+
+ {
+ N_("Midnight"),
+ "midnight-stars.jpg",
+ { 1.0, 1.0, 1.0, 1 },
+ { 1.0, 0.6, 0.0, 1 },
+ { 0.0, 0.0, 0.0, 1 },
+ 0
+ },
+
+ {
+ N_("Confidential"),
+ "confidential-stamp.jpg",
+ { 0.0, 0.0, 0.0, 1 },
+ { 0.0, 0.0, 1.0, 1 },
+ { 1.0, 1.0, 1.0, 1 },
+ 0
+ },
+
+ {
+ N_("Draft"),
+ "draft-stamp.jpg",
+ { 0.0, 0.0, 0.0, 1 },
+ { 0.0, 0.0, 1.0, 1 },
+ { 1.0, 1.0, 1.0, 1 },
+ 0
+ },
+
+ {
+ N_("Graph Paper"),
+ "draft-paper.png",
+ { 0.0 , 0.0 , 0.5, 1 },
+ { 0.88, 0.13, 0.14, 1 },
+ { 1.0 , 1.0 , 1.0 , 1 },
+ 0
+ }
+};
+
+G_DEFINE_TYPE (
+ EHTMLEditorPageDialog,
+ e_html_editor_page_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+static void
+html_editor_page_dialog_set_text_color (EHTMLEditorPageDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+ GdkRGBA rgba;
+ gchar *color;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+
+ e_color_combo_get_current_color (
+ E_COLOR_COMBO (dialog->priv->text_color_picker), &rgba);
+
+ color = g_strdup_printf ("#%06x", e_rgba_to_value (&rgba));
+ webkit_dom_html_body_element_set_text (
+ WEBKIT_DOM_HTML_BODY_ELEMENT (body), color);
+
+ g_free (color);
+}
+
+static void
+html_editor_page_dialog_set_link_color (EHTMLEditorPageDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+ GdkRGBA rgba;
+ gchar *color;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+
+ e_color_combo_get_current_color (
+ E_COLOR_COMBO (dialog->priv->link_color_picker), &rgba);
+
+ color = g_strdup_printf ("#%06x", e_rgba_to_value (&rgba));
+ webkit_dom_html_body_element_set_link (
+ WEBKIT_DOM_HTML_BODY_ELEMENT (body), color);
+
+ g_free (color);
+}
+
+static void
+html_editor_page_dialog_set_background_color (EHTMLEditorPageDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+ GdkRGBA rgba;
+ gchar *color;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+
+ e_color_combo_get_current_color (
+ E_COLOR_COMBO (dialog->priv->background_color_picker), &rgba);
+
+ color = g_strdup_printf ("#%06x", e_rgba_to_value (&rgba));
+
+ webkit_dom_html_body_element_set_bg_color (
+ WEBKIT_DOM_HTML_BODY_ELEMENT (body), color);
+
+ g_free (color);
+}
+
+static void
+html_editor_page_dialog_set_background_from_template (EHTMLEditorPageDialog *dialog)
+{
+ const Template *tmplt;
+
+ tmplt = &templates[
+ gtk_combo_box_get_active (
+ GTK_COMBO_BOX (dialog->priv->background_template_combo))];
+
+ /* Special case - 'none' template */
+ if (tmplt->filename == NULL) {
+ gtk_file_chooser_unselect_all (
+ GTK_FILE_CHOOSER (dialog->priv->background_image_filechooser));
+ } else {
+ gchar *filename;
+
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->text_color_picker),
+ &tmplt->text_color);
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->background_color_picker),
+ &tmplt->background_color);
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->link_color_picker),
+ &tmplt->link_color);
+
+ filename = g_build_filename (EVOLUTION_IMAGESDIR, tmplt->filename, NULL);
+
+ gtk_file_chooser_set_filename (
+ GTK_FILE_CHOOSER (dialog->priv->background_image_filechooser),
+ filename);
+ g_free (filename);
+ }
+
+}
+
+static void
+html_editor_page_dialog_set_background_image (EHTMLEditorPageDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+ gchar *uri;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+
+ uri = gtk_file_chooser_get_uri (
+ GTK_FILE_CHOOSER (
+ dialog->priv->background_image_filechooser));
+
+ webkit_dom_html_body_element_set_background (
+ WEBKIT_DOM_HTML_BODY_ELEMENT (body), uri ? uri : "");
+
+ g_free (uri);
+}
+
+static void
+html_editor_page_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorPageDialog *dialog;
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+ gchar *tmp;
+ GdkRGBA rgba;
+
+ dialog = E_HTML_EDITOR_PAGE_DIALOG (widget);
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+
+ tmp = webkit_dom_html_body_element_get_background (
+ WEBKIT_DOM_HTML_BODY_ELEMENT (body));
+ if (tmp && *tmp) {
+ gint ii;
+ gchar *fname = g_filename_from_uri (tmp, NULL, NULL);
+ for (ii = 0; ii < G_N_ELEMENTS (templates); ii++) {
+ const Template *tmplt = &templates[ii];
+
+ if (g_strcmp0 (tmplt->filename, fname) == 0) {
+ gtk_combo_box_set_active (
+ GTK_COMBO_BOX (dialog->priv->background_template_combo),
+ ii);
+ break;
+ }
+ }
+ g_free (fname);
+ } else {
+ gtk_combo_box_set_active (
+ GTK_COMBO_BOX (dialog->priv->background_template_combo), 0);
+ }
+ g_free (tmp);
+
+ tmp = webkit_dom_html_body_element_get_text (
+ WEBKIT_DOM_HTML_BODY_ELEMENT (body));
+ if (!tmp || !*tmp) {
+ GdkColor *color;
+ GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view));
+ color = &style->text[GTK_STATE_NORMAL];
+
+ rgba.alpha = 1;
+ rgba.red = ((gdouble) color->red) / G_MAXUINT16;
+ rgba.green = ((gdouble) color->green) / G_MAXUINT16;
+ rgba.blue = ((gdouble) color->blue) / G_MAXUINT16;
+ } else {
+ gdk_rgba_parse (&rgba, tmp);
+ }
+ g_free (tmp);
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->text_color_picker), &rgba);
+
+ tmp = webkit_dom_html_body_element_get_link (
+ WEBKIT_DOM_HTML_BODY_ELEMENT (body));
+ if (!tmp || !*tmp) {
+ GdkColor color;
+ gtk_widget_style_get (
+ GTK_WIDGET (view), "link-color", &color, NULL);
+
+ rgba.alpha = 1;
+ rgba.red = ((gdouble) color.red) / G_MAXUINT16;
+ rgba.green = ((gdouble) color.green) / G_MAXUINT16;
+ rgba.blue = ((gdouble) color.blue) / G_MAXUINT16;
+ } else {
+ gdk_rgba_parse (&rgba, tmp);
+ }
+ g_free (tmp);
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->link_color_picker), &rgba);
+
+ tmp = webkit_dom_html_body_element_get_bg_color (
+ WEBKIT_DOM_HTML_BODY_ELEMENT (body));
+ if (!tmp || !*tmp) {
+ GdkColor *color;
+ GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (view));
+ color = &style->base[GTK_STATE_NORMAL];
+
+ rgba.alpha = 1;
+ rgba.red = ((gdouble) color->red) / G_MAXUINT16;
+ rgba.green = ((gdouble) color->green) / G_MAXUINT16;
+ rgba.blue = ((gdouble) color->blue) / G_MAXUINT16;
+
+ } else {
+ gdk_rgba_parse (&rgba, tmp);
+ }
+ g_free (tmp);
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->background_color_picker), &rgba);
+
+ GTK_WIDGET_CLASS (e_html_editor_page_dialog_parent_class)->show (widget);
+}
+
+static void
+e_html_editor_page_dialog_class_init (EHTMLEditorPageDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorPageDialogPrivate));
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_page_dialog_show;
+}
+
+static void
+e_html_editor_page_dialog_init (EHTMLEditorPageDialog *dialog)
+{
+ GtkGrid *grid, *main_layout;
+ GtkWidget *widget;
+ gint ii;
+
+ dialog->priv = E_HTML_EDITOR_PAGE_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ /* == Colors == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Colors</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Text */
+ widget = e_color_combo_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ g_signal_connect_swapped (
+ widget, "notify::current-color",
+ G_CALLBACK (html_editor_page_dialog_set_text_color), dialog);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ dialog->priv->text_color_picker = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Text:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->text_color_picker);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ /* Link */
+ widget = e_color_combo_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ g_signal_connect_swapped (
+ widget, "notify::current-color",
+ G_CALLBACK (html_editor_page_dialog_set_link_color), dialog);
+ gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+ dialog->priv->link_color_picker = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Link:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->link_color_picker);
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ /* Background */
+ widget = e_color_combo_new ();
+ gtk_widget_set_hexpand (widget, TRUE);
+ g_signal_connect_swapped (
+ widget, "notify::current-color",
+ G_CALLBACK (html_editor_page_dialog_set_background_color), dialog);
+ gtk_grid_attach (grid, widget, 1, 2, 1, 1);
+ dialog->priv->background_color_picker = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Background:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->background_color_picker);
+ gtk_grid_attach (grid, widget, 0, 2, 1, 1);
+
+ /* == Background Image == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Background Image</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 2, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Template */
+ widget = gtk_combo_box_text_new ();
+ for (ii = 0; ii < G_N_ELEMENTS (templates); ii++) {
+ gtk_combo_box_text_append_text (
+ GTK_COMBO_BOX_TEXT (widget), templates[ii].name);
+ }
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_page_dialog_set_background_from_template), dialog);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ dialog->priv->background_template_combo = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Template:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->background_template_combo);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ /* Custom image */
+ widget = gtk_file_chooser_button_new (
+ _("Selection a file"), GTK_FILE_CHOOSER_ACTION_OPEN);
+ g_signal_connect_swapped (
+ widget, "selection-changed",
+ G_CALLBACK (html_editor_page_dialog_set_background_image), dialog);
+ gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+ dialog->priv->background_image_filechooser = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Custom:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->background_image_filechooser);
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_page_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_PAGE_DIALOG,
+ "editor", editor,
+ "title", N_("Page Properties"),
+ NULL));
+}
diff --git a/e-util/e-html-editor-page-dialog.h b/e-util/e-html-editor-page-dialog.h
new file mode 100644
index 0000000000..5678efc4b6
--- /dev/null
+++ b/e-util/e-html-editor-page-dialog.h
@@ -0,0 +1,70 @@
+/*
+ * e-html-editor-page-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_PAGE_DIALOG_H
+#define E_HTML_EDITOR_PAGE_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_PAGE_DIALOG \
+ (e_html_editor_page_dialog_get_type ())
+#define E_HTML_EDITOR_PAGE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_PAGE_DIALOG, EHTMLEditorPageDialog))
+#define E_HTML_EDITOR_PAGE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_PAGE_DIALOG, EHTMLEditorPageDialogClass))
+#define E_IS_HTML_EDITOR_PAGE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_PAGE_DIALOG))
+#define E_IS_HTML_EDITOR_PAGE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_PAGE_DIALOG))
+#define E_HTML_EDITOR_PAGE_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_PAGE_DIALOG, EHTMLEditorPageDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorPageDialog EHTMLEditorPageDialog;
+typedef struct _EHTMLEditorPageDialogClass EHTMLEditorPageDialogClass;
+typedef struct _EHTMLEditorPageDialogPrivate EHTMLEditorPageDialogPrivate;
+
+struct _EHTMLEditorPageDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorPageDialogPrivate *priv;
+};
+
+struct _EHTMLEditorPageDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_page_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_page_dialog_new (EHTMLEditor *editor);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_PAGE_DIALOG_H */
diff --git a/e-util/e-html-editor-paragraph-dialog.c b/e-util/e-html-editor-paragraph-dialog.c
new file mode 100644
index 0000000000..f0fce973a6
--- /dev/null
+++ b/e-util/e-html-editor-paragraph-dialog.c
@@ -0,0 +1,154 @@
+/*
+ * e-html-editor-paragraph-dialog.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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-paragraph-dialog.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "e-action-combo-box.h"
+
+#define E_HTML_EDITOR_PARAGRAPH_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG, EHTMLEditorParagraphDialogPrivate))
+
+G_DEFINE_TYPE (
+ EHTMLEditorParagraphDialog,
+ e_html_editor_paragraph_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+struct _EHTMLEditorParagraphDialogPrivate {
+ GtkWidget *style_combo;
+
+ GtkWidget *left_button;
+ GtkWidget *center_button;
+ GtkWidget *right_button;
+};
+
+static void
+html_editor_paragraph_dialog_constructed (GObject *object)
+{
+ GtkGrid *main_layout, *grid;
+ GtkWidget *widget;
+ EHTMLEditor *editor;
+ EHTMLEditorParagraphDialog *dialog;
+
+ dialog = E_HTML_EDITOR_PARAGRAPH_DIALOG (object);
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ /* == General == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>General</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Style */
+ widget = e_action_combo_box_new_with_action (
+ GTK_RADIO_ACTION (e_html_editor_get_action (editor, "style-normal")));
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ dialog->priv->style_combo = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Style:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->style_combo);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ /* == Alignment == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Alignment</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 2, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Left */
+ widget = gtk_toggle_button_new_with_label (GTK_STOCK_JUSTIFY_LEFT);
+ gtk_button_set_use_stock (GTK_BUTTON (widget), TRUE);
+ gtk_activatable_set_related_action (
+ GTK_ACTIVATABLE (widget),
+ e_html_editor_get_action (editor, "justify-left"));
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+ dialog->priv->left_button = widget;
+
+ /* Center */
+ widget = gtk_toggle_button_new_with_label (GTK_STOCK_JUSTIFY_CENTER);
+ gtk_button_set_use_stock (GTK_BUTTON (widget), TRUE);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ gtk_activatable_set_related_action (
+ GTK_ACTIVATABLE (widget),
+ e_html_editor_get_action (editor, "justify-center"));
+ dialog->priv->center_button = widget;
+
+ /* Right */
+ widget = gtk_toggle_button_new_with_label (GTK_STOCK_JUSTIFY_RIGHT);
+ gtk_button_set_use_stock (GTK_BUTTON (widget), TRUE);
+ gtk_grid_attach (grid, widget, 2, 0, 1, 1);
+ gtk_activatable_set_related_action (
+ GTK_ACTIVATABLE (widget),
+ e_html_editor_get_action (editor, "justify-right"));
+ dialog->priv->right_button = widget;
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+static void
+e_html_editor_paragraph_dialog_class_init (EHTMLEditorParagraphDialogClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (EHTMLEditorParagraphDialogPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructed = html_editor_paragraph_dialog_constructed;
+}
+
+static void
+e_html_editor_paragraph_dialog_init (EHTMLEditorParagraphDialog *dialog)
+{
+ dialog->priv = E_HTML_EDITOR_PARAGRAPH_DIALOG_GET_PRIVATE (dialog);
+}
+
+GtkWidget *
+e_html_editor_paragraph_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG,
+ "editor", editor,
+ "title", N_("Paragraph Properties"),
+ NULL));
+}
diff --git a/e-util/e-html-editor-paragraph-dialog.h b/e-util/e-html-editor-paragraph-dialog.h
new file mode 100644
index 0000000000..17855bd3c8
--- /dev/null
+++ b/e-util/e-html-editor-paragraph-dialog.h
@@ -0,0 +1,71 @@
+/*
+ * e-html-editor-paragraph-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_PARAGRAPH_DIALOG_H
+#define E_HTML_EDITOR_PARAGRAPH_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG \
+ (e_html_editor_paragraph_dialog_get_type ())
+#define E_HTML_EDITOR_PARAGRAPH_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG, EHTMLEditorParagraphDialog))
+#define E_HTML_EDITOR_PARAGRAPH_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG, EHTMLEditorParagraphDialogClass))
+#define E_IS_HTML_EDITOR_PARAGRAPH_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG))
+#define E_IS_HTML_EDITOR_PARAGRAPH_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG))
+#define E_HTML_EDITOR_PARAGRAPH_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_PARAGRAPH_DIALOG, EHTMLEditorParagraphDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorParagraphDialog EHTMLEditorParagraphDialog;
+typedef struct _EHTMLEditorParagraphDialogClass EHTMLEditorParagraphDialogClass;
+typedef struct _EHTMLEditorParagraphDialogPrivate EHTMLEditorParagraphDialogPrivate;
+
+struct _EHTMLEditorParagraphDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorParagraphDialogPrivate *priv;
+};
+
+struct _EHTMLEditorParagraphDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_paragraph_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_paragraph_dialog_new
+ (EHTMLEditor *editor);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_PARAGRAPH_DIALOG_H */
diff --git a/e-util/e-html-editor-private.h b/e-util/e-html-editor-private.h
new file mode 100644
index 0000000000..dc4658bd57
--- /dev/null
+++ b/e-util/e-html-editor-private.h
@@ -0,0 +1,103 @@
+/*
+ * e-html-editor-private.h
+ *
+ * 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.
+ */
+
+#ifndef E_HTML_EDITOR_PRIVATE_H
+#define E_HTML_EDITOR_PRIVATE_H
+
+#include <e-action-combo-box.h>
+#include <e-color-combo.h>
+#include <e-html-editor.h>
+#include <e-html-editor-actions.h>
+#include <e-html-editor-cell-dialog.h>
+#include <e-html-editor-find-dialog.h>
+#include <e-html-editor-hrule-dialog.h>
+#include <e-html-editor-image-dialog.h>
+#include <e-html-editor-link-dialog.h>
+#include <e-html-editor-page-dialog.h>
+#include <e-html-editor-paragraph-dialog.h>
+#include <e-html-editor-replace-dialog.h>
+#include <e-html-editor-spell-check-dialog.h>
+#include <e-html-editor-table-dialog.h>
+#include <e-html-editor-text-dialog.h>
+#include <e-html-editor-view.h>
+
+#ifdef HAVE_XFREE
+#include <X11/XF86keysym.h>
+#endif
+
+#define ACTION(name) (E_HTML_EDITOR_ACTION_##name (editor))
+#define WIDGET(name) (E_HTML_EDITOR_WIDGETS_##name (editor))
+
+G_BEGIN_DECLS
+
+struct _EHTMLEditorPrivate {
+ GtkUIManager *manager;
+ GtkActionGroup *core_actions;
+ GtkActionGroup *html_actions;
+ GtkActionGroup *context_actions;
+ GtkActionGroup *html_context_actions;
+ GtkActionGroup *language_actions;
+ GtkActionGroup *spell_check_actions;
+ GtkActionGroup *suggestion_actions;
+
+ GtkWidget *main_menu;
+ GtkWidget *main_toolbar;
+ GtkWidget *edit_toolbar;
+ GtkWidget *html_toolbar;
+ GtkWidget *activity_bar;
+ GtkWidget *alert_bar;
+ GtkWidget *edit_area;
+
+ GtkWidget *find_dialog;
+ GtkWidget *replace_dialog;
+ GtkWidget *link_dialog;
+ GtkWidget *hrule_dialog;
+ GtkWidget *table_dialog;
+ GtkWidget *page_dialog;
+ GtkWidget *image_dialog;
+ GtkWidget *text_dialog;
+ GtkWidget *paragraph_dialog;
+ GtkWidget *cell_dialog;
+ GtkWidget *spell_check_dialog;
+
+ GtkWidget *color_combo_box;
+ GtkWidget *mode_combo_box;
+ GtkWidget *size_combo_box;
+ GtkWidget *style_combo_box;
+ GtkWidget *scrolled_window;
+
+ EHTMLEditorView *html_editor_view;
+ EHTMLEditorSelection *selection;
+
+ gchar *filename;
+
+ guint spell_suggestions_merge_id;
+
+ WebKitDOMNode *image;
+ WebKitDOMNode *table_cell;
+
+ gint editor_layout_row;
+};
+
+void editor_actions_init (EHTMLEditor *editor);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_PRIVATE_H */
diff --git a/e-util/e-html-editor-replace-dialog.c b/e-util/e-html-editor-replace-dialog.c
new file mode 100644
index 0000000000..7addcdfdad
--- /dev/null
+++ b/e-util/e-html-editor-replace-dialog.c
@@ -0,0 +1,288 @@
+/*
+ * e-html-editor-replace-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-replace-dialog.h"
+
+#include <glib/gi18n-lib.h>
+
+#define E_HTML_EDITOR_REPLACE_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_REPLACE_DIALOG, EHTMLEditorReplaceDialogPrivate))
+
+G_DEFINE_TYPE (
+ EHTMLEditorReplaceDialog,
+ e_html_editor_replace_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+struct _EHTMLEditorReplaceDialogPrivate {
+ GtkWidget *search_entry;
+ GtkWidget *replace_entry;
+
+ GtkWidget *case_sensitive;
+ GtkWidget *backwards;
+ GtkWidget *wrap;
+
+ GtkWidget *result_label;
+
+ GtkWidget *skip_button;
+ GtkWidget *replace_button;
+ GtkWidget *replace_all_button;
+
+ EHTMLEditor *editor;
+};
+
+enum {
+ PROP_0,
+ PROP_EDITOR
+};
+
+static gboolean
+jump (EHTMLEditorReplaceDialog *dialog)
+{
+ EHTMLEditor *editor;
+ WebKitWebView *webview;
+ gboolean found;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ webview = WEBKIT_WEB_VIEW (
+ e_html_editor_get_view (editor));
+
+ found = webkit_web_view_search_text (
+ webview,
+ gtk_entry_get_text (GTK_ENTRY (dialog->priv->search_entry)),
+ gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->case_sensitive)),
+ !gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->backwards)),
+ gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->wrap)));
+
+ return found;
+}
+
+static void
+html_editor_replace_dialog_skip_cb (EHTMLEditorReplaceDialog *dialog)
+{
+ if (!jump (dialog)) {
+ gtk_label_set_label (
+ GTK_LABEL (dialog->priv->result_label),
+ N_("No match found"));
+ gtk_widget_show (dialog->priv->result_label);
+ } else {
+ gtk_widget_hide (dialog->priv->result_label);
+ }
+}
+
+static void
+html_editor_replace_dialog_replace_cb (EHTMLEditorReplaceDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+
+ /* Jump to next matching word */
+ if (!jump (dialog)) {
+ gtk_label_set_label (
+ GTK_LABEL (dialog->priv->result_label),
+ N_("No match found"));
+ gtk_widget_show (dialog->priv->result_label);
+ return;
+ } else {
+ gtk_widget_hide (dialog->priv->result_label);
+ }
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+
+ e_html_editor_selection_replace (
+ selection,
+ gtk_entry_get_text (GTK_ENTRY (dialog->priv->replace_entry)));
+}
+
+static void
+html_editor_replace_dialog_replace_all_cb (EHTMLEditorReplaceDialog *dialog)
+{
+ gint i = 0;
+ gchar *result;
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+ const gchar *replacement;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+ replacement = gtk_entry_get_text (GTK_ENTRY (dialog->priv->replace_entry));
+
+ while (jump (dialog)) {
+ e_html_editor_selection_replace (selection, replacement);
+ i++;
+
+ /* Jump behind the word */
+ e_html_editor_selection_move (
+ selection, TRUE, E_HTML_EDITOR_SELECTION_GRANULARITY_WORD);
+ }
+
+ result = g_strdup_printf (_("%d occurences replaced"), i);
+ gtk_label_set_label (GTK_LABEL (dialog->priv->result_label), result);
+ gtk_widget_show (dialog->priv->result_label);
+ g_free (result);
+}
+
+static void
+html_editor_replace_dialog_entry_changed (EHTMLEditorReplaceDialog *dialog)
+{
+ gboolean ready;
+ ready = ((gtk_entry_get_text_length (
+ GTK_ENTRY (dialog->priv->search_entry)) != 0) &&
+ (gtk_entry_get_text_length (
+ GTK_ENTRY (dialog->priv->replace_entry)) != 0));
+
+ gtk_widget_set_sensitive (dialog->priv->skip_button, ready);
+ gtk_widget_set_sensitive (dialog->priv->replace_button, ready);
+ gtk_widget_set_sensitive (dialog->priv->replace_all_button, ready);
+}
+
+static void
+html_editor_replace_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditorReplaceDialog *dialog = E_HTML_EDITOR_REPLACE_DIALOG (widget);
+
+ gtk_widget_grab_focus (dialog->priv->search_entry);
+ gtk_widget_hide (dialog->priv->result_label);
+
+ /* Chain up to parent implementation */
+ GTK_WIDGET_CLASS (e_html_editor_replace_dialog_parent_class)->show (widget);
+}
+
+static void
+e_html_editor_replace_dialog_class_init (EHTMLEditorReplaceDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorReplaceDialogPrivate));
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_replace_dialog_show;
+}
+
+static void
+e_html_editor_replace_dialog_init (EHTMLEditorReplaceDialog *dialog)
+{
+ GtkGrid *main_layout;
+ GtkWidget *widget, *layout;
+ GtkBox *button_box;
+
+ dialog->priv = E_HTML_EDITOR_REPLACE_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ widget = gtk_entry_new ();
+ gtk_grid_attach (main_layout, widget, 1, 0, 2, 1);
+ dialog->priv->search_entry = widget;
+ g_signal_connect_swapped (
+ widget, "notify::text-length",
+ G_CALLBACK (html_editor_replace_dialog_entry_changed), dialog);
+
+ widget = gtk_label_new_with_mnemonic (_("R_eplace:"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->search_entry);
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+
+ widget = gtk_entry_new ();
+ gtk_grid_attach (main_layout, widget, 1, 1, 2, 1);
+ dialog->priv->replace_entry = widget;
+ g_signal_connect_swapped (
+ widget, "notify::text-length",
+ G_CALLBACK (html_editor_replace_dialog_entry_changed), dialog);
+
+ widget = gtk_label_new_with_mnemonic (_("_With:"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->replace_entry);
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_grid_attach (main_layout, widget, 0, 1, 1, 1);
+
+ layout = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
+ gtk_grid_attach (main_layout, layout, 1, 2, 2, 1);
+
+ widget = gtk_check_button_new_with_mnemonic (_("Search _backwards"));
+ gtk_box_pack_start (GTK_BOX (layout), widget, FALSE, FALSE, 0);
+ dialog->priv->backwards = widget;
+
+ widget = gtk_check_button_new_with_mnemonic (_("_Case sensitive"));
+ gtk_box_pack_start (GTK_BOX (layout), widget, FALSE, FALSE, 0);
+ dialog->priv->case_sensitive = widget;
+
+ widget = gtk_check_button_new_with_mnemonic (_("Wra_p search"));
+ gtk_box_pack_start (GTK_BOX (layout), widget, FALSE, FALSE, 0);
+ dialog->priv->wrap = widget;
+
+ widget = gtk_label_new ("");
+ gtk_grid_attach (main_layout, widget, 0, 3, 2, 1);
+ dialog->priv->result_label = widget;
+
+ button_box = e_html_editor_dialog_get_button_box (E_HTML_EDITOR_DIALOG (dialog));
+
+ widget = gtk_button_new_with_mnemonic (_("_Skip"));
+ gtk_box_pack_start (button_box, widget, FALSE, FALSE, 5);
+ gtk_widget_set_sensitive (widget, FALSE);
+ dialog->priv->skip_button = widget;
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_replace_dialog_skip_cb), dialog);
+
+ widget = gtk_button_new_with_mnemonic (_("_Replace"));
+ gtk_box_pack_start (button_box, widget, FALSE, FALSE, 5);
+ gtk_widget_set_sensitive (widget, FALSE);
+ dialog->priv->replace_button = widget;
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_replace_dialog_replace_cb), dialog);
+
+ widget = gtk_button_new_with_mnemonic (_("Replace _All"));
+ gtk_box_pack_start (button_box, widget, FALSE, FALSE, 5);
+ gtk_widget_set_sensitive (widget, FALSE);
+ dialog->priv->replace_all_button = widget;
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_replace_dialog_replace_all_cb), dialog);
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_replace_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_REPLACE_DIALOG,
+ "editor", editor,
+ "icon-name", GTK_STOCK_FIND_AND_REPLACE,
+ "resizable", FALSE,
+ "title", N_("Replace"),
+ "transient-for", gtk_widget_get_toplevel (GTK_WIDGET (editor)),
+ "type", GTK_WINDOW_TOPLEVEL,
+ "window-position", GTK_WIN_POS_CENTER_ON_PARENT,
+ NULL));
+}
diff --git a/e-util/e-html-editor-replace-dialog.h b/e-util/e-html-editor-replace-dialog.h
new file mode 100644
index 0000000000..f253e428aa
--- /dev/null
+++ b/e-util/e-html-editor-replace-dialog.h
@@ -0,0 +1,71 @@
+/*
+ * e-html-editor-replace-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_REPLACE_DIALOG_H
+#define E_HTML_EDITOR_REPLACE_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_REPLACE_DIALOG \
+ (e_html_editor_replace_dialog_get_type ())
+#define E_HTML_EDITOR_REPLACE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_REPLACE_DIALOG, EHTMLEditorReplaceDialog))
+#define E_HTML_EDITOR_REPLACE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_REPLACE_DIALOG, EHTMLEditorReplaceDialogClass))
+#define E_IS_HTML_EDITOR_REPLACE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_REPLACE_DIALOG))
+#define E_IS_HTML_EDITOR_REPLACE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_REPLACE_DIALOG))
+#define E_HTML_EDITOR_REPLACE_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_REPLACE_DIALOG, EHTMLEditorReplaceDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorReplaceDialog EHTMLEditorReplaceDialog;
+typedef struct _EHTMLEditorReplaceDialogClass EHTMLEditorReplaceDialogClass;
+typedef struct _EHTMLEditorReplaceDialogPrivate EHTMLEditorReplaceDialogPrivate;
+
+struct _EHTMLEditorReplaceDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorReplaceDialogPrivate *priv;
+};
+
+struct _EHTMLEditorReplaceDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_replace_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_replace_dialog_new (EHTMLEditor *editor);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_REPLACE_DIALOG_H */
+
diff --git a/e-util/e-html-editor-selection.c b/e-util/e-html-editor-selection.c
new file mode 100644
index 0000000000..c109063f41
--- /dev/null
+++ b/e-util/e-html-editor-selection.c
@@ -0,0 +1,5576 @@
+/*
+ * e-html-editor-selection.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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-selection.h"
+#include "e-html-editor-view.h"
+#include "e-html-editor.h"
+#include "e-html-editor-utils.h"
+
+#include <e-util/e-util.h>
+
+#include <webkit/webkit.h>
+#include <webkit/webkitdom.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#define E_HTML_EDITOR_SELECTION_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_SELECTION, EHTMLEditorSelectionPrivate))
+
+#define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b"
+#define UNICODE_NBSP "\xc2\xa0"
+
+#define SPACES_PER_INDENTATION 4
+#define SPACES_PER_LIST_LEVEL 8
+
+/**
+ * EHTMLEditorSelection
+ *
+ * The #EHTMLEditorSelection object represents current position of the cursor
+ * with the editor or current text selection within the editor. To obtain
+ * valid #EHTMLEditorSelection, call e_html_editor_view_get_selection().
+ */
+
+struct _EHTMLEditorSelectionPrivate {
+
+ GWeakRef html_editor_view;
+ gulong selection_changed_handler_id;
+
+ gchar *text;
+
+ gboolean is_bold;
+ gboolean is_italic;
+ gboolean is_underline;
+ gboolean is_monospaced;
+ gboolean is_strikethrough;
+
+ gchar *background_color;
+ gchar *font_color;
+ gchar *font_family;
+
+ gulong selection_offset;
+
+ gint word_wrap_length;
+ guint font_size;
+
+ EHTMLEditorSelectionAlignment alignment;
+};
+
+enum {
+ PROP_0,
+ PROP_ALIGNMENT,
+ PROP_BACKGROUND_COLOR,
+ PROP_BLOCK_FORMAT,
+ PROP_BOLD,
+ PROP_HTML_EDITOR_VIEW,
+ PROP_FONT_COLOR,
+ PROP_FONT_NAME,
+ PROP_FONT_SIZE,
+ PROP_INDENTED,
+ PROP_ITALIC,
+ PROP_MONOSPACED,
+ PROP_STRIKETHROUGH,
+ PROP_SUBSCRIPT,
+ PROP_SUPERSCRIPT,
+ PROP_TEXT,
+ PROP_UNDERLINE
+};
+
+static const GdkRGBA black = { 0 };
+
+G_DEFINE_TYPE (
+ EHTMLEditorSelection,
+ e_html_editor_selection,
+ G_TYPE_OBJECT
+);
+
+static WebKitDOMRange *
+html_editor_selection_get_current_range (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ WebKitWebView *web_view;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *dom_selection;
+ WebKitDOMRange *range = NULL;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, NULL);
+
+ web_view = WEBKIT_WEB_VIEW (view);
+
+ document = webkit_web_view_get_dom_document (web_view);
+ window = webkit_dom_document_get_default_view (document);
+ if (!window)
+ goto exit;
+
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+ if (!WEBKIT_DOM_IS_DOM_SELECTION (dom_selection))
+ goto exit;
+
+ if (webkit_dom_dom_selection_get_range_count (dom_selection) < 1)
+ goto exit;
+
+ range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
+
+ exit:
+ g_object_unref (view);
+
+ return range;
+}
+
+static gboolean
+get_has_style (EHTMLEditorSelection *selection,
+ const gchar *style_tag)
+{
+ WebKitDOMNode *node;
+ WebKitDOMElement *element;
+ WebKitDOMRange *range;
+ gboolean result;
+ gint tag_len;
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return FALSE;
+
+ node = webkit_dom_range_get_start_container (range, NULL);
+ if (WEBKIT_DOM_IS_ELEMENT (node))
+ element = WEBKIT_DOM_ELEMENT (node);
+ else
+ element = webkit_dom_node_get_parent_element (node);
+
+ tag_len = strlen (style_tag);
+ result = FALSE;
+ while (!result && element) {
+ gchar *element_tag;
+ gboolean accept_citation = FALSE;
+
+ element_tag = webkit_dom_element_get_tag_name (element);
+
+ if (g_ascii_strncasecmp (style_tag, "citation", 8) == 0) {
+ accept_citation = TRUE;
+ result = ((strlen (element_tag) == 10 /* strlen ("blockquote") */) &&
+ (g_ascii_strncasecmp (element_tag, "blockquote", 10) == 0));
+ if (element_has_class (element, "-x-evo-indented"))
+ result = FALSE;
+ } else {
+ result = ((tag_len == strlen (element_tag)) &&
+ (g_ascii_strncasecmp (element_tag, style_tag, tag_len) == 0));
+ }
+
+ /* Special case: <blockquote type=cite> marks quotation, while
+ * just <blockquote> is used for indentation. If the <blockquote>
+ * has type=cite, then ignore it unless style_tag is "citation" */
+ if (result && g_ascii_strncasecmp (element_tag, "blockquote", 10) == 0) {
+ if (webkit_dom_element_has_attribute (element, "type")) {
+ gchar *type;
+ type = webkit_dom_element_get_attribute (element, "type");
+ if (!accept_citation && (g_ascii_strncasecmp (type, "cite", 4) == 0)) {
+ result = FALSE;
+ }
+ g_free (type);
+ } else {
+ if (accept_citation)
+ result = FALSE;
+ }
+ }
+
+ g_free (element_tag);
+
+ if (result)
+ break;
+
+ element = webkit_dom_node_get_parent_element (
+ WEBKIT_DOM_NODE (element));
+ }
+
+ return result;
+}
+
+static gchar *
+get_font_property (EHTMLEditorSelection *selection,
+ const gchar *font_property)
+{
+ WebKitDOMRange *range;
+ WebKitDOMNode *node;
+ WebKitDOMElement *element;
+ gchar *value;
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return NULL;
+
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+ element = e_html_editor_dom_node_find_parent_element (node, "FONT");
+ if (!element)
+ return NULL;
+
+ g_object_get (G_OBJECT (element), font_property, &value, NULL);
+
+ return value;
+}
+
+static void
+html_editor_selection_selection_changed_cb (WebKitWebView *webview,
+ EHTMLEditorSelection *selection)
+{
+ g_object_freeze_notify (G_OBJECT (selection));
+
+ g_object_notify (G_OBJECT (selection), "alignment");
+ g_object_notify (G_OBJECT (selection), "background-color");
+ g_object_notify (G_OBJECT (selection), "bold");
+ g_object_notify (G_OBJECT (selection), "font-name");
+ g_object_notify (G_OBJECT (selection), "font-size");
+ g_object_notify (G_OBJECT (selection), "font-color");
+ g_object_notify (G_OBJECT (selection), "block-format");
+ g_object_notify (G_OBJECT (selection), "indented");
+ g_object_notify (G_OBJECT (selection), "italic");
+ g_object_notify (G_OBJECT (selection), "monospaced");
+ g_object_notify (G_OBJECT (selection), "strikethrough");
+ g_object_notify (G_OBJECT (selection), "subscript");
+ g_object_notify (G_OBJECT (selection), "superscript");
+ g_object_notify (G_OBJECT (selection), "text");
+ g_object_notify (G_OBJECT (selection), "underline");
+
+ g_object_thaw_notify (G_OBJECT (selection));
+}
+
+void
+e_html_editor_selection_block_selection_changed (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_signal_handlers_block_by_func (
+ view, html_editor_selection_selection_changed_cb, selection);
+ g_object_unref (view);
+}
+
+void
+e_html_editor_selection_unblock_selection_changed (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_signal_handlers_unblock_by_func (
+ view, html_editor_selection_selection_changed_cb, selection);
+ g_object_unref (view);
+}
+
+static void
+html_editor_selection_set_html_editor_view (EHTMLEditorSelection *selection,
+ EHTMLEditorView *view)
+{
+ gulong handler_id;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
+
+ g_weak_ref_set (&selection->priv->html_editor_view, view);
+
+ handler_id = g_signal_connect (
+ view, "selection-changed",
+ G_CALLBACK (html_editor_selection_selection_changed_cb),
+ selection);
+
+ selection->priv->selection_changed_handler_id = handler_id;
+}
+
+static void
+html_editor_selection_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GdkRGBA rgba = { 0 };
+
+ switch (property_id) {
+ case PROP_ALIGNMENT:
+ g_value_set_int (
+ value,
+ e_html_editor_selection_get_alignment (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_BACKGROUND_COLOR:
+ g_value_set_string (
+ value,
+ e_html_editor_selection_get_background_color (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_BLOCK_FORMAT:
+ g_value_set_int (
+ value,
+ e_html_editor_selection_get_block_format (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_BOLD:
+ g_value_set_boolean (
+ value,
+ e_html_editor_selection_is_bold (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_HTML_EDITOR_VIEW:
+ g_value_take_object (
+ value,
+ e_html_editor_selection_ref_html_editor_view (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_FONT_COLOR:
+ e_html_editor_selection_get_font_color (
+ E_HTML_EDITOR_SELECTION (object), &rgba);
+ g_value_set_boxed (value, &rgba);
+ return;
+
+ case PROP_FONT_NAME:
+ g_value_set_string (
+ value,
+ e_html_editor_selection_get_font_name (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_FONT_SIZE:
+ g_value_set_int (
+ value,
+ e_html_editor_selection_get_font_size (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_INDENTED:
+ g_value_set_boolean (
+ value,
+ e_html_editor_selection_is_indented (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_ITALIC:
+ g_value_set_boolean (
+ value,
+ e_html_editor_selection_is_italic (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_MONOSPACED:
+ g_value_set_boolean (
+ value,
+ e_html_editor_selection_is_monospaced (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_STRIKETHROUGH:
+ g_value_set_boolean (
+ value,
+ e_html_editor_selection_is_strikethrough (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_SUBSCRIPT:
+ g_value_set_boolean (
+ value,
+ e_html_editor_selection_is_subscript (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_SUPERSCRIPT:
+ g_value_set_boolean (
+ value,
+ e_html_editor_selection_is_superscript (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+
+ case PROP_TEXT:
+ g_value_set_string (
+ value,
+ e_html_editor_selection_get_string (
+ E_HTML_EDITOR_SELECTION (object)));
+ break;
+
+ case PROP_UNDERLINE:
+ g_value_set_boolean (
+ value,
+ e_html_editor_selection_is_underline (
+ E_HTML_EDITOR_SELECTION (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+html_editor_selection_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ALIGNMENT:
+ e_html_editor_selection_set_alignment (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_BACKGROUND_COLOR:
+ e_html_editor_selection_set_background_color (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_BOLD:
+ e_html_editor_selection_set_bold (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_HTML_EDITOR_VIEW:
+ html_editor_selection_set_html_editor_view (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_FONT_COLOR:
+ e_html_editor_selection_set_font_color (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_boxed (value));
+ return;
+
+ case PROP_BLOCK_FORMAT:
+ e_html_editor_selection_set_block_format (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_FONT_NAME:
+ e_html_editor_selection_set_font_name (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_FONT_SIZE:
+ e_html_editor_selection_set_font_size (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_ITALIC:
+ e_html_editor_selection_set_italic (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_MONOSPACED:
+ e_html_editor_selection_set_monospaced (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_STRIKETHROUGH:
+ e_html_editor_selection_set_strikethrough (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_SUBSCRIPT:
+ e_html_editor_selection_set_subscript (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_SUPERSCRIPT:
+ e_html_editor_selection_set_superscript (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_UNDERLINE:
+ e_html_editor_selection_set_underline (
+ E_HTML_EDITOR_SELECTION (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+html_editor_selection_dispose (GObject *object)
+{
+ EHTMLEditorSelectionPrivate *priv;
+ EHTMLEditorView *view;
+
+ priv = E_HTML_EDITOR_SELECTION_GET_PRIVATE (object);
+
+ view = g_weak_ref_get (&priv->html_editor_view);
+ if (view != NULL) {
+ g_signal_handler_disconnect (
+ view, priv->selection_changed_handler_id);
+ priv->selection_changed_handler_id = 0;
+ g_object_unref (view);
+ }
+
+ g_weak_ref_set (&priv->html_editor_view, NULL);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_html_editor_selection_parent_class)->dispose (object);
+}
+
+static void
+html_editor_selection_finalize (GObject *object)
+{
+ EHTMLEditorSelection *selection = E_HTML_EDITOR_SELECTION (object);
+
+ g_free (selection->priv->text);
+ g_free (selection->priv->background_color);
+ g_free (selection->priv->font_color);
+ g_free (selection->priv->font_family);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_html_editor_selection_parent_class)->finalize (object);
+}
+
+static void
+e_html_editor_selection_class_init (EHTMLEditorSelectionClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorSelectionPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = html_editor_selection_get_property;
+ object_class->set_property = html_editor_selection_set_property;
+ object_class->dispose = html_editor_selection_dispose;
+ object_class->finalize = html_editor_selection_finalize;
+
+ /**
+ * EHTMLEditorSelectionalignment
+ *
+ * Holds alignment of current paragraph.
+ */
+ /* FIXME: Convert the enum to a proper type */
+ g_object_class_install_property (
+ object_class,
+ PROP_ALIGNMENT,
+ g_param_spec_int (
+ "alignment",
+ NULL,
+ NULL,
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT,
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT,
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT,
+ G_PARAM_READWRITE));
+
+ /**
+ * EHTMLEditorSelectionbackground-color
+ *
+ * Holds background color of current selection or at current cursor
+ * position.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_BACKGROUND_COLOR,
+ g_param_spec_string (
+ "background-color",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ /**
+ * EHTMLEditorSelectionblock-format
+ *
+ * Holds block format of current paragraph. See
+ * #EHTMLEditorSelectionBlockFormat for valid values.
+ */
+ /* FIXME Convert the EHTMLEditorSelectionBlockFormat
+ * enum to a proper type. */
+ g_object_class_install_property (
+ object_class,
+ PROP_BLOCK_FORMAT,
+ g_param_spec_int (
+ "block-format",
+ NULL,
+ NULL,
+ 0,
+ G_MAXINT,
+ 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionbold
+ *
+ * Holds whether current selection or text at current cursor position
+ * is bold.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_BOLD,
+ g_param_spec_boolean (
+ "bold",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HTML_EDITOR_VIEW,
+ g_param_spec_object (
+ "html-editor-view",
+ NULL,
+ NULL,
+ E_TYPE_HTML_EDITOR_VIEW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionfont-color
+ *
+ * Holds font color of current selection or at current cursor position.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_FONT_COLOR,
+ g_param_spec_boxed (
+ "font-color",
+ NULL,
+ NULL,
+ GDK_TYPE_RGBA,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionfont-name
+ *
+ * Holds name of font in current selection or at current cursor
+ * position.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_FONT_NAME,
+ g_param_spec_string (
+ "font-name",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionfont-size
+ *
+ * Holds point size of current selection or at current cursor position.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_FONT_SIZE,
+ g_param_spec_int (
+ "font-size",
+ NULL,
+ NULL,
+ 1,
+ 7,
+ 3,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionindented
+ *
+ * Holds whether current paragraph is indented. This does not include
+ * citations.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_INDENTED,
+ g_param_spec_boolean (
+ "indented",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionitalic
+ *
+ * Holds whether current selection or letter at current cursor position
+ * is italic.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_ITALIC,
+ g_param_spec_boolean (
+ "italic",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionmonospaced
+ *
+ * Holds whether current selection or letter at current cursor position
+ * is monospaced.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_MONOSPACED,
+ g_param_spec_boolean (
+ "monospaced",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionstrikethrough
+ *
+ * Holds whether current selection or letter at current cursor position
+ * is strikethrough.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_STRIKETHROUGH,
+ g_param_spec_boolean (
+ "strikethrough",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionsuperscript
+ *
+ * Holds whether current selection or letter at current cursor position
+ * is in superscript.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_SUPERSCRIPT,
+ g_param_spec_boolean (
+ "superscript",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionsubscript
+ *
+ * Holds whether current selection or letter at current cursor position
+ * is in subscript.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_SUBSCRIPT,
+ g_param_spec_boolean (
+ "subscript",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectiontext
+ *
+ * Holds always up-to-date text of current selection.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_TEXT,
+ g_param_spec_string (
+ "text",
+ NULL,
+ NULL,
+ NULL,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorSelectionunderline
+ *
+ * Holds whether current selection or letter at current cursor position
+ * is underlined.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_UNDERLINE,
+ g_param_spec_boolean (
+ "underline",
+ NULL,
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_html_editor_selection_init (EHTMLEditorSelection *selection)
+{
+ GSettings *g_settings;
+
+ selection->priv = E_HTML_EDITOR_SELECTION_GET_PRIVATE (selection);
+
+ g_settings = g_settings_new ("org.gnome.evolution.mail");
+ selection->priv->word_wrap_length =
+ g_settings_get_int (g_settings, "composer-word-wrap-length");
+ g_object_unref (g_settings);
+}
+
+gint
+e_html_editor_selection_get_word_wrap_length (EHTMLEditorSelection *selection)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), 72);
+
+ return selection->priv->word_wrap_length;
+}
+
+/**
+ * e_html_editor_selection_ref_html_editor_view:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns a new reference to @selection's #EHTMLEditorView. Unreference
+ * the #EHTMLEditorView with g_object_unref() when finished with it.
+ *
+ * Returns: an #EHTMLEditorView
+ **/
+EHTMLEditorView *
+e_html_editor_selection_ref_html_editor_view (EHTMLEditorSelection *selection)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
+
+ return g_weak_ref_get (&selection->priv->html_editor_view);
+}
+
+/**
+ * e_html_editor_selection_has_text:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current selection contains any text.
+ *
+ * Returns: @TRUE when current selection contains text, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_has_text (EHTMLEditorSelection *selection)
+{
+ WebKitDOMRange *range;
+ WebKitDOMNode *node;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ range = html_editor_selection_get_current_range (selection);
+
+ node = webkit_dom_range_get_start_container (range, NULL);
+ if (webkit_dom_node_get_node_type (node) == 3)
+ return TRUE;
+
+ node = webkit_dom_range_get_end_container (range, NULL);
+ if (webkit_dom_node_get_node_type (node) == 3)
+ return TRUE;
+
+ node = WEBKIT_DOM_NODE (webkit_dom_range_clone_contents (range, NULL));
+ while (node) {
+ if (webkit_dom_node_get_node_type (node) == 3)
+ return TRUE;
+
+ if (webkit_dom_node_has_child_nodes (node)) {
+ node = webkit_dom_node_get_first_child (node);
+ } else if (webkit_dom_node_get_next_sibling (node)) {
+ node = webkit_dom_node_get_next_sibling (node);
+ } else {
+ node = webkit_dom_node_get_parent_node (node);
+ if (node) {
+ node = webkit_dom_node_get_next_sibling (node);
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * e_html_editor_selection_get_caret_word:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns word under cursor.
+ *
+ * Returns: A newly allocated string with current caret word or @NULL when there
+ * is no text under cursor or when selection is active. [transfer-full].
+ */
+gchar *
+e_html_editor_selection_get_caret_word (EHTMLEditorSelection *selection)
+{
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
+
+ range = html_editor_selection_get_current_range (selection);
+
+ /* Don't operate on the visible selection */
+ range = webkit_dom_range_clone_range (range, NULL);
+ webkit_dom_range_expand (range, "word", NULL);
+
+ return webkit_dom_range_to_string (range, NULL);
+}
+
+/**
+ * e_html_editor_selection_replace_caret_word:
+ * @selection: an #EHTMLEditorSelection
+ * @replacement: a string to replace current caret word with
+ *
+ * Replaces current word under cursor with @replacement.
+ */
+void
+e_html_editor_selection_replace_caret_word (EHTMLEditorSelection *selection,
+ const gchar *replacement)
+{
+ EHTMLEditorView *view;
+ WebKitWebView *web_view;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *dom_selection;
+ WebKitDOMRange *range;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+ g_return_if_fail (replacement != NULL);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ web_view = WEBKIT_WEB_VIEW (view);
+
+ range = html_editor_selection_get_current_range (selection);
+ document = webkit_web_view_get_dom_document (web_view);
+ window = webkit_dom_document_get_default_view (document);
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+
+ webkit_dom_range_expand (range, "word", NULL);
+ webkit_dom_dom_selection_add_range (dom_selection, range);
+
+ e_html_editor_selection_insert_html (selection, replacement);
+
+ g_object_unref (view);
+}
+
+/**
+ * e_html_editor_selection_get_string:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns currently selected string.
+ *
+ * Returns: A pointer to content of current selection. The string is owned by
+ * #EHTMLEditorSelection and should not be free'd.
+ */
+const gchar *
+e_html_editor_selection_get_string (EHTMLEditorSelection *selection)
+{
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return NULL;
+
+ g_free (selection->priv->text);
+ selection->priv->text = webkit_dom_range_get_text (range);
+
+ return selection->priv->text;
+}
+
+/**
+ * e_html_editor_selection_replace:
+ * @selection: an #EHTMLEditorSelection
+ * @new_string: a string to replace current selection with
+ *
+ * Replaces currently selected text with @new_string.
+ */
+void
+e_html_editor_selection_replace (EHTMLEditorSelection *selection,
+ const gchar *new_string)
+{
+ EHTMLEditorView *view;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ e_html_editor_view_exec_command (
+ view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, new_string);
+
+ g_object_unref (view);
+}
+
+/**
+ * e_html_editor_selection_get_list_alignment_from_node:
+ * @node: #an WebKitDOMNode
+ *
+ * Returns alignment of given list.
+ *
+ * Returns: #EHTMLEditorSelectionAlignment
+ */
+EHTMLEditorSelectionAlignment
+e_html_editor_selection_get_list_alignment_from_node (WebKitDOMNode *node)
+{
+ if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-list-item-align-left"))
+ return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
+ if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-list-item-align-center"))
+ return E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER;
+ if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-list-item-align-right"))
+ return E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT;
+
+ return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
+}
+
+/**
+ * e_html_editor_selection_get_alignment:
+ * @selection: #an EHTMLEditorSelection
+ *
+ * Returns alignment of current paragraph
+ *
+ * Returns: #EHTMLEditorSelectionAlignment
+ */
+EHTMLEditorSelectionAlignment
+e_html_editor_selection_get_alignment (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorSelectionAlignment alignment;
+ EHTMLEditorView *view;
+ gchar *value;
+ WebKitDOMCSSStyleDeclaration *style;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMElement *element;
+ WebKitDOMNode *node;
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (
+ E_IS_HTML_EDITOR_SELECTION (selection),
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, FALSE);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ g_object_unref (view);
+ window = webkit_dom_document_get_default_view (document);
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
+
+ node = webkit_dom_range_get_start_container (range, NULL);
+ if (!node)
+ return E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
+
+ if (WEBKIT_DOM_IS_ELEMENT (node))
+ element = WEBKIT_DOM_ELEMENT (node);
+ else
+ element = webkit_dom_node_get_parent_element (node);
+
+ style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
+ value = webkit_dom_css_style_declaration_get_property_value (style, "text-align");
+
+ if (!value || !*value ||
+ (g_ascii_strncasecmp (value, "left", 4) == 0)) {
+ alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
+ } else if (g_ascii_strncasecmp (value, "center", 6) == 0) {
+ alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER;
+ } else if (g_ascii_strncasecmp (value, "right", 5) == 0) {
+ alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT;
+ } else {
+ alignment = E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT;
+ }
+
+ g_free (value);
+
+ return alignment;
+}
+
+static void
+set_ordered_list_type_to_element (WebKitDOMElement *list,
+ EHTMLEditorSelectionBlockFormat format)
+{
+ if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST)
+ webkit_dom_element_remove_attribute (list, "type");
+ else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA)
+ webkit_dom_element_set_attribute (list, "type", "A", NULL);
+ else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN)
+ webkit_dom_element_set_attribute (list, "type", "I", NULL);
+}
+
+static WebKitDOMElement *
+create_list_element (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document,
+ EHTMLEditorSelectionBlockFormat format,
+ gint level,
+ gboolean html_mode)
+{
+ WebKitDOMElement *list;
+ gint offset = -SPACES_PER_LIST_LEVEL;
+ gboolean inserting_unordered_list =
+ format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
+
+ list = webkit_dom_document_create_element (
+ document, inserting_unordered_list ? "UL" : "OL", NULL);
+
+ set_ordered_list_type_to_element (list, format);
+
+ if (level >= 0)
+ offset = (level + 1) * -SPACES_PER_LIST_LEVEL;
+
+ if (!html_mode)
+ e_html_editor_selection_set_paragraph_style (
+ selection, list, -1, offset, "");
+
+ return list;
+}
+
+static void
+remove_node (WebKitDOMNode *node)
+{
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node), node, NULL);
+}
+
+static void
+format_change_list_from_list (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document,
+ EHTMLEditorSelectionBlockFormat to,
+ gboolean html_mode)
+{
+ gboolean after_selection_end = FALSE;
+ WebKitDOMElement *selection_start_marker, *selection_end_marker, *new_list;
+ WebKitDOMNode *source_list, *source_list_clone, *current_list, *item;
+
+ selection_start_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-start-marker", NULL);
+ selection_end_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-end-marker", NULL);
+
+ if (!selection_start_marker || !selection_end_marker)
+ return;
+
+ new_list = create_list_element (selection, document, to, 0, html_mode);
+
+ /* Copy elements from previous block to list */
+ item = webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_start_marker));
+ source_list = webkit_dom_node_get_parent_node (item);
+ current_list = source_list;
+ source_list_clone = webkit_dom_node_clone_node (source_list, FALSE);
+
+ if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented"))
+ element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented");
+
+ while (item) {
+ WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item);
+
+ if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
+ webkit_dom_node_append_child (
+ after_selection_end ?
+ source_list_clone : WEBKIT_DOM_NODE (new_list),
+ WEBKIT_DOM_NODE (item),
+ NULL);
+ }
+
+ if (webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end_marker))) {
+ source_list_clone = webkit_dom_node_clone_node (current_list, FALSE);
+ after_selection_end = TRUE;
+ }
+
+ if (!next_item) {
+ if (after_selection_end)
+ break;
+ current_list = webkit_dom_node_get_next_sibling (current_list);
+ next_item = webkit_dom_node_get_first_child (current_list);
+ }
+ item = next_item;
+ }
+
+ if (webkit_dom_node_has_child_nodes (source_list_clone))
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (source_list),
+ WEBKIT_DOM_NODE (source_list_clone),
+ webkit_dom_node_get_next_sibling (source_list), NULL);
+ if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (new_list)))
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (source_list),
+ WEBKIT_DOM_NODE (new_list),
+ webkit_dom_node_get_next_sibling (source_list), NULL);
+ if (!webkit_dom_node_has_child_nodes (source_list))
+ remove_node (source_list);
+}
+
+/**
+ * e_html_editor_selection_set_alignment:
+ * @selection: an #EHTMLEditorSelection
+ * @alignment: an #EHTMLEditorSelectionAlignment value to apply
+ *
+ * Sets alignment of current paragraph to give @alignment.
+ */
+void
+e_html_editor_selection_set_alignment (EHTMLEditorSelection *selection,
+ EHTMLEditorSelectionAlignment alignment)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+ const gchar *class = "";
+ WebKitDOMDocument *document;
+ WebKitDOMElement *selection_start_marker;
+ WebKitDOMNode *parent;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (e_html_editor_selection_get_alignment (selection) == alignment)
+ return;
+
+ switch (alignment) {
+ case E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER:
+ command = E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_CENTER;
+ class = "-x-evo-list-item-align-center";
+ break;
+
+ case E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT:
+ command = E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_LEFT;
+ break;
+
+ case E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT:
+ command = E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_RIGHT;
+ class = "-x-evo-list-item-align-right";
+ break;
+ }
+
+ selection->priv->alignment = alignment;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ e_html_editor_selection_save (selection);
+
+ selection_start_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-start-marker", NULL);
+
+ if (!selection_start_marker) {
+ g_object_unref (view);
+ return;
+ }
+
+ parent = webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_start_marker));
+
+ if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (parent)) {
+ element_remove_class (
+ WEBKIT_DOM_ELEMENT (parent),
+ "-x-evo-list-item-align-center");
+ element_remove_class (
+ WEBKIT_DOM_ELEMENT (parent),
+ "-x-evo-list-item-align-right");
+
+ element_add_class (WEBKIT_DOM_ELEMENT (parent), class);
+ } else {
+ e_html_editor_view_exec_command (view, command, NULL);
+ }
+
+ e_html_editor_selection_restore (selection);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "alignment");
+}
+
+/**
+ * e_html_editor_selection_get_background_color:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns background color of currently selected text or letter at current
+ * cursor position.
+ *
+ * Returns: A string with code of current background color.
+ */
+const gchar *
+e_html_editor_selection_get_background_color (EHTMLEditorSelection *selection)
+{
+ WebKitDOMNode *ancestor;
+ WebKitDOMRange *range;
+ WebKitDOMCSSStyleDeclaration *css;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
+
+ range = html_editor_selection_get_current_range (selection);
+
+ ancestor = webkit_dom_range_get_common_ancestor_container (range, NULL);
+
+ css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (ancestor));
+ selection->priv->background_color =
+ webkit_dom_css_style_declaration_get_property_value (
+ css, "background-color");
+
+ return selection->priv->background_color;
+}
+
+/**
+ * e_html_editor_selection_set_background_color:
+ * @selection: an #EHTMLEditorSelection
+ * @color: code of new background color to set
+ *
+ * Changes background color of current selection or letter at current cursor
+ * position to @color.
+ */
+void
+e_html_editor_selection_set_background_color (EHTMLEditorSelection *selection,
+ const gchar *color)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+ g_return_if_fail (color != NULL && *color != '\0');
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_BACKGROUND_COLOR;
+ e_html_editor_view_exec_command (view, command, color);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "background-color");
+}
+
+static gint
+get_indentation_level (WebKitDOMElement *element)
+{
+ WebKitDOMElement *parent;
+ gint level = 0;
+
+ if (element_has_class (element, "-x-evo-indented"))
+ level++;
+
+ parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
+ /* Count level of indentation */
+ while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
+ if (element_has_class (parent, "-x-evo-indented"))
+ level++;
+
+ parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (parent));
+ }
+
+ return level;
+}
+
+static WebKitDOMNode *
+get_block_node (WebKitDOMRange *range)
+{
+ WebKitDOMNode *node;
+
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+ if (!WEBKIT_DOM_IS_ELEMENT (node))
+ node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node));
+
+ if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-temp-text-wrapper"))
+ node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node));
+
+ return node;
+}
+
+/**
+ * e_html_editor_selection_get_list_format_from_node:
+ * @node: an #WebKitDOMNode
+ *
+ * Returns block format of given list.
+ *
+ * Returns: #EHTMLEditorSelectionBlockFormat
+ */
+EHTMLEditorSelectionBlockFormat
+e_html_editor_selection_get_list_format_from_node (WebKitDOMNode *node)
+{
+ EHTMLEditorSelectionBlockFormat format =
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
+
+ if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node))
+ return -1;
+
+ if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node))
+ return format;
+
+ if (WEBKIT_DOM_IS_ELEMENT (node)) {
+ gchar *type_value = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (node), "type");
+
+ if (!type_value)
+ return E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST;
+
+ if (!*type_value)
+ format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST;
+ else if (g_ascii_strcasecmp (type_value, "A") == 0)
+ format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA;
+ else if (g_ascii_strcasecmp (type_value, "I") == 0)
+ format = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN;
+ g_free (type_value);
+ }
+
+ return -1;
+}
+
+/**
+ * e_html_editor_selection_get_block_format:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns block format of current paragraph.
+ *
+ * Returns: #EHTMLEditorSelectionBlockFormat
+ */
+EHTMLEditorSelectionBlockFormat
+e_html_editor_selection_get_block_format (EHTMLEditorSelection *selection)
+{
+ WebKitDOMNode *node;
+ WebKitDOMRange *range;
+ WebKitDOMElement *element;
+ EHTMLEditorSelectionBlockFormat result;
+
+ g_return_val_if_fail (
+ E_IS_HTML_EDITOR_SELECTION (selection),
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
+
+ node = webkit_dom_range_get_start_container (range, NULL);
+
+ if (e_html_editor_dom_node_find_parent_element (node, "UL")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
+ } else if ((element = e_html_editor_dom_node_find_parent_element (node, "OL")) != NULL) {
+ if (webkit_dom_element_has_attribute (element, "type")) {
+ gchar *type;
+
+ type = webkit_dom_element_get_attribute (element, "type");
+ if (type && ((*type == 'a') || (*type == 'A'))) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA;
+ } else if (type && ((*type == 'i') || (*type == 'I'))) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN;
+ } else {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST;
+ }
+
+ g_free (type);
+ } else {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST;
+ }
+ } else if (e_html_editor_dom_node_find_parent_element (node, "PRE")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE;
+ } else if (e_html_editor_dom_node_find_parent_element (node, "ADDRESS")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ADDRESS;
+ } else if (e_html_editor_dom_node_find_parent_element (node, "H1")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1;
+ } else if (e_html_editor_dom_node_find_parent_element (node, "H2")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H2;
+ } else if (e_html_editor_dom_node_find_parent_element (node, "H3")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H3;
+ } else if (e_html_editor_dom_node_find_parent_element (node, "H4")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H4;
+ } else if (e_html_editor_dom_node_find_parent_element (node, "H5")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H5;
+ } else if (e_html_editor_dom_node_find_parent_element (node, "H6")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6;
+ } else if ((element = e_html_editor_dom_node_find_parent_element (node, "BLOCKQUOTE")) != NULL) {
+ if (element_has_class (element, "-x-evo-indented"))
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
+ else {
+ WebKitDOMNode *block = get_block_node (range);
+
+ if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-paragraph"))
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
+ else
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE;
+ }
+ } else if (e_html_editor_dom_node_find_parent_element (node, "P")) {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
+ } else {
+ result = E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH;
+ }
+
+ return result;
+}
+
+static gboolean
+is_caret_position_node (WebKitDOMNode *node)
+{
+ return element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-position");
+}
+
+static void
+merge_list_into_list (WebKitDOMNode *from,
+ WebKitDOMNode *to,
+ gboolean insert_before)
+{
+ WebKitDOMNode *item;
+
+ if (!(to && from))
+ return;
+
+ while ((item = webkit_dom_node_get_first_child (from)) != NULL) {
+ if (insert_before)
+ webkit_dom_node_insert_before (
+ to, item, webkit_dom_node_get_last_child (to), NULL);
+ else
+ webkit_dom_node_append_child (to, item, NULL);
+ }
+
+ if (!webkit_dom_node_has_child_nodes (from))
+ remove_node (from);
+
+}
+
+static void
+merge_lists_if_possible (WebKitDOMNode *list)
+{
+ EHTMLEditorSelectionBlockFormat format, prev, next;
+ WebKitDOMNode *prev_sibling, *next_sibling;
+
+ prev_sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (list));
+ next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (list));
+
+ format = e_html_editor_selection_get_list_format_from_node (list),
+ prev = e_html_editor_selection_get_list_format_from_node (prev_sibling);
+ next = e_html_editor_selection_get_list_format_from_node (next_sibling);
+
+ if (format == prev && format != -1 && prev != -1)
+ merge_list_into_list (prev_sibling, list, TRUE);
+
+ if (format == next && format != -1 && next != -1)
+ merge_list_into_list (next_sibling, list, FALSE);
+}
+
+static void
+remove_wrapping (WebKitDOMElement *element)
+{
+ WebKitDOMNodeList *list;
+ gint ii, length;
+
+ list = webkit_dom_element_query_selector_all (
+ element, "br.-x-evo-wrap-br", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++)
+ remove_node (webkit_dom_node_list_item (list, ii));
+}
+
+static void
+remove_quoting (WebKitDOMElement *element)
+{
+ WebKitDOMNodeList *list;
+ gint ii, length;
+
+ list = webkit_dom_element_query_selector_all (
+ element, "span.-x-evo-quoted", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ remove_node (webkit_dom_node_list_item (list, ii));
+ }
+
+ list = webkit_dom_element_query_selector_all (
+ element, "span.-x-evo-temp-text-wrapper", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *nd = webkit_dom_node_list_item (list, ii);
+
+ while (webkit_dom_node_has_child_nodes (nd)) {
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (nd),
+ webkit_dom_node_get_first_child (nd),
+ nd,
+ NULL);
+ }
+
+ remove_node (nd);
+ }
+
+ webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
+}
+
+static gboolean
+node_is_list (WebKitDOMNode *node)
+{
+ return node && (
+ WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (node) ||
+ WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (node));
+}
+
+static gint
+get_citation_level (WebKitDOMNode *node)
+{
+ WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
+ gint level = 0;
+
+ while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
+ if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
+ webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "type"))
+ level++;
+
+ parent = webkit_dom_node_get_parent_node (parent);
+ }
+
+ return level;
+}
+
+static void
+format_change_block_to_block (EHTMLEditorSelection *selection,
+ EHTMLEditorSelectionBlockFormat format,
+ EHTMLEditorView *view,
+ const gchar *value,
+ WebKitDOMDocument *document)
+{
+ gboolean after_selection_end, quoted = FALSE;
+ WebKitDOMElement *selection_start_marker, *selection_end_marker, *element;
+ WebKitDOMNode *block, *next_block;
+
+ e_html_editor_selection_save (selection);
+ selection_start_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-start-marker", NULL);
+ selection_end_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-end-marker", NULL);
+ block = webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_start_marker));
+ if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-temp-text-wrapper")) {
+ block = webkit_dom_node_get_parent_node (block);
+ remove_wrapping (WEBKIT_DOM_ELEMENT (block));
+ remove_quoting (WEBKIT_DOM_ELEMENT (block));
+ quoted = TRUE;
+ }
+
+ /* Process all blocks that are in the selection one by one */
+ while (block) {
+ WebKitDOMNode *child;
+
+ after_selection_end = webkit_dom_node_contains (
+ block, WEBKIT_DOM_NODE (selection_end_marker));
+
+ next_block = webkit_dom_node_get_next_sibling (
+ WEBKIT_DOM_NODE (block));
+
+ if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH)
+ element = e_html_editor_selection_get_paragraph_element (
+ selection, document, -1, 0);
+ else
+ element = webkit_dom_document_create_element (
+ document, value, NULL);
+
+ while ((child = webkit_dom_node_get_first_child (block)))
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (element), child, NULL);
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (block),
+ WEBKIT_DOM_NODE (element),
+ block,
+ NULL);
+
+ remove_node (block);
+
+ block = next_block;
+
+ if (after_selection_end)
+ break;
+ }
+
+ if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH &&
+ !e_html_editor_view_get_html_mode (view)) {
+ gint citation_level, quote;
+
+ citation_level = get_citation_level (WEBKIT_DOM_NODE (element));
+ quote = citation_level ? citation_level + 1 : 0;
+
+ element = e_html_editor_selection_wrap_paragraph_length (
+ selection, element, selection->priv->word_wrap_length - quote);
+ }
+
+ if (quoted)
+ e_html_editor_view_quote_plain_text_element (view, element);
+
+ e_html_editor_selection_restore (selection);
+}
+
+static void
+remove_node_if_empty (WebKitDOMNode *node)
+{
+ if (!WEBKIT_DOM_IS_NODE (node))
+ return;
+
+ if (!webkit_dom_node_get_first_child (node)) {
+ remove_node (node);
+ } else {
+ gchar *text_content;
+
+ text_content = webkit_dom_node_get_text_content (node);
+ if (!text_content)
+ remove_node (node);
+
+ if (text_content && !*text_content)
+ remove_node (node);
+
+ g_free (text_content);
+ }
+}
+
+static void
+format_change_block_to_list (EHTMLEditorSelection *selection,
+ EHTMLEditorSelectionBlockFormat format,
+ EHTMLEditorView *view,
+ WebKitDOMDocument *document)
+{
+ gboolean after_selection_end;
+ gboolean html_mode = e_html_editor_view_get_html_mode (view);
+ WebKitDOMElement *selection_start_marker, *selection_end_marker, *item, *list;
+ WebKitDOMNode *block, *next_block;
+
+ e_html_editor_selection_save (selection);
+
+ selection_start_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-start-marker", NULL);
+ selection_end_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-end-marker", NULL);
+ block = webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_start_marker));
+
+ list = create_list_element (selection, document, format, 0, html_mode);
+
+ if (element_has_class (WEBKIT_DOM_ELEMENT (block), "-x-evo-temp-text-wrapper")) {
+ WebKitDOMElement *element;
+
+ block = webkit_dom_node_get_parent_node (block);
+
+ remove_wrapping (WEBKIT_DOM_ELEMENT (block));
+ remove_quoting (WEBKIT_DOM_ELEMENT (block));
+
+ e_html_editor_view_exec_command (
+ view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);
+
+ element = webkit_dom_document_query_selector (
+ document, "body>br", NULL);
+
+ webkit_dom_node_replace_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
+ WEBKIT_DOM_NODE (list),
+ WEBKIT_DOM_NODE (element),
+ NULL);
+
+ selection_start_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-start-marker", NULL);
+ selection_end_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-end-marker", NULL);
+ block = webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_start_marker));
+ } else
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (block),
+ WEBKIT_DOM_NODE (list),
+ block,
+ NULL);
+
+ /* Process all blocks that are in the selection one by one */
+ while (block) {
+ gboolean empty = FALSE;
+ gchar *content;
+ WebKitDOMNode *child;
+
+ after_selection_end = webkit_dom_node_contains (
+ block, WEBKIT_DOM_NODE (selection_end_marker));
+
+ next_block = webkit_dom_node_get_next_sibling (
+ WEBKIT_DOM_NODE (block));
+
+ item = webkit_dom_document_create_element (document, "LI", NULL);
+ content = webkit_dom_node_get_text_content (block);
+
+ empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
+ g_free (content);
+
+ /* We have to use again the hidden space to move caret into newly inserted list */
+ if (empty) {
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (item),
+ UNICODE_ZERO_WIDTH_SPACE,
+ NULL);
+ }
+
+ while ((child = webkit_dom_node_get_first_child (block)))
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (item), child, NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (list), WEBKIT_DOM_NODE (item), NULL);
+
+ remove_node (block);
+
+ block = next_block;
+
+ if (after_selection_end)
+ break;
+ }
+
+ merge_lists_if_possible (WEBKIT_DOM_NODE (list));
+
+ e_html_editor_view_force_spell_check (view);
+ e_html_editor_selection_restore (selection);
+}
+
+static void
+format_change_list_to_list (EHTMLEditorSelection *selection,
+ EHTMLEditorSelectionBlockFormat format,
+ WebKitDOMDocument *document,
+ gboolean html_mode)
+{
+ EHTMLEditorSelectionBlockFormat prev = 0, next = 0;
+ gboolean done = FALSE, indented = FALSE;
+ gboolean selection_starts_in_first_child, selection_ends_in_last_child;
+ WebKitDOMElement *selection_start_marker, *selection_end_marker;
+ WebKitDOMNode *prev_list, *current_list, *next_list;
+
+ e_html_editor_selection_save (selection);
+
+ selection_start_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-start-marker", NULL);
+ selection_end_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-end-marker", NULL);
+
+ current_list = webkit_dom_node_get_parent_node (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_start_marker)));
+
+ prev_list = webkit_dom_node_get_parent_node (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_start_marker)));
+
+ next_list = webkit_dom_node_get_parent_node (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_end_marker)));
+
+ selection_starts_in_first_child =
+ webkit_dom_node_contains (
+ webkit_dom_node_get_first_child (current_list),
+ WEBKIT_DOM_NODE (selection_start_marker));
+
+ selection_ends_in_last_child =
+ webkit_dom_node_contains (
+ webkit_dom_node_get_last_child (current_list),
+ WEBKIT_DOM_NODE (selection_end_marker));
+
+ indented = element_has_class (WEBKIT_DOM_ELEMENT (current_list), "-x-evo-indented");
+
+ if (!prev_list || !next_list || indented) {
+ format_change_list_from_list (selection, document, format, html_mode);
+ goto out;
+ }
+
+ if (webkit_dom_node_is_same_node (prev_list, next_list)) {
+ prev_list = webkit_dom_node_get_previous_sibling (
+ webkit_dom_node_get_parent_node (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_start_marker))));
+ next_list = webkit_dom_node_get_next_sibling (
+ webkit_dom_node_get_parent_node (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_end_marker))));
+ if (!prev_list || !next_list) {
+ format_change_list_from_list (selection, document, format, html_mode);
+ goto out;
+ }
+ }
+
+ prev = e_html_editor_selection_get_list_format_from_node (prev_list);
+ next = e_html_editor_selection_get_list_format_from_node (next_list);
+
+ if (format == prev && format != -1 && prev != -1) {
+ if (selection_starts_in_first_child && selection_ends_in_last_child) {
+ done = TRUE;
+ merge_list_into_list (current_list, prev_list, FALSE);
+ }
+ }
+
+ if (format == next && format != -1 && next != -1) {
+ if (selection_starts_in_first_child && selection_ends_in_last_child) {
+ done = TRUE;
+ merge_list_into_list (next_list, prev_list, FALSE);
+ }
+ }
+
+ if (done)
+ goto out;
+
+ format_change_list_from_list (selection, document, format, html_mode);
+out:
+ e_html_editor_selection_restore (selection);
+}
+
+static void
+format_change_list_to_block (EHTMLEditorSelection *selection,
+ EHTMLEditorSelectionBlockFormat format,
+ WebKitDOMDocument *document)
+{
+ gboolean after_end = FALSE;
+ WebKitDOMElement *selection_start, *element, *selection_end;
+ WebKitDOMNode *source_list, *next_item, *item, *source_list_clone;
+
+ e_html_editor_selection_save (selection);
+
+ selection_start = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-start-marker", NULL);
+ selection_end = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-end-marker", NULL);
+
+ item = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start));
+ source_list = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (item));
+ source_list_clone = webkit_dom_node_clone_node (source_list, FALSE);
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (source_list),
+ WEBKIT_DOM_NODE (source_list_clone),
+ webkit_dom_node_get_next_sibling (source_list),
+ NULL);
+
+ next_item = item;
+
+ /* Process all nodes that are in selection one by one */
+ while (next_item) {
+ WebKitDOMNode *tmp;
+
+ tmp = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (next_item));
+
+ if (!after_end) {
+ if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH)
+ element = e_html_editor_selection_get_paragraph_element (selection, document, -1, 0);
+ else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE)
+ element = webkit_dom_document_create_element (document, "PRE", NULL);
+ else if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE)
+ element = webkit_dom_document_create_element (document, "BLOCKQUOTE", NULL);
+ else
+ element = e_html_editor_selection_get_paragraph_element (selection, document, -1, 0);
+
+ after_end = webkit_dom_node_contains (next_item, WEBKIT_DOM_NODE (selection_end));
+
+ while (webkit_dom_node_get_first_child (next_item)) {
+ WebKitDOMNode *node = webkit_dom_node_get_first_child (next_item);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (element), node, NULL);
+ }
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (source_list),
+ WEBKIT_DOM_NODE (element),
+ source_list_clone,
+ NULL);
+
+ remove_node (next_item);
+
+ next_item = tmp;
+ } else {
+ webkit_dom_node_append_child (
+ source_list_clone, next_item, NULL);
+
+ next_item = tmp;
+ }
+ }
+
+ remove_node_if_empty (source_list_clone);
+ remove_node_if_empty (source_list);
+
+ e_html_editor_selection_restore (selection);
+}
+
+/**
+ * e_html_editor_selection_set_block_format:
+ * @selection: an #EHTMLEditorSelection
+ * @format: an #EHTMLEditorSelectionBlockFormat value
+ *
+ * Changes block format of current paragraph to @format.
+ */
+void
+e_html_editor_selection_set_block_format (EHTMLEditorSelection *selection,
+ EHTMLEditorSelectionBlockFormat format)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorSelectionBlockFormat current_format;
+ const gchar *value;
+ gboolean has_selection = FALSE;
+ gboolean from_list = FALSE, to_list = FALSE, html_mode;
+ WebKitDOMDocument *document;
+ WebKitDOMRange *range;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ current_format = e_html_editor_selection_get_block_format (selection);
+ if (current_format == format) {
+ return;
+ }
+
+ switch (format) {
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE:
+ value = "BLOCKQUOTE";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1:
+ value = "H1";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H2:
+ value = "H2";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H3:
+ value = "H3";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H4:
+ value = "H4";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H5:
+ value = "H5";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6:
+ value = "H6";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH:
+ value = "P";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE:
+ value = "PRE";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ADDRESS:
+ value = "ADDRESS";
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST:
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA:
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN:
+ to_list = TRUE;
+ value = NULL;
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST:
+ to_list = TRUE;
+ value = NULL;
+ break;
+ case E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_NONE:
+ default:
+ value = NULL;
+ break;
+ }
+
+ if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0)
+ has_selection = TRUE;
+
+ /* H1 - H6 have bold font by default */
+ if (format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1 &&
+ format <= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6)
+ selection->priv->is_bold = TRUE;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ html_mode = e_html_editor_view_get_html_mode (view);
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ from_list =
+ current_format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST;
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range) {
+ g_object_unref (view);
+ return;
+ }
+
+ if (from_list && to_list)
+ format_change_list_to_list (selection, format, document, html_mode);
+
+ if (!from_list && !to_list)
+ format_change_block_to_block (selection, format, view, value, document);
+
+ if (from_list && !to_list)
+ format_change_list_to_block (selection, format, document);
+
+ if (!from_list && to_list)
+ format_change_block_to_list (selection, format, view, document);
+
+ if (!has_selection)
+ e_html_editor_view_force_spell_check (view);
+
+ g_object_unref (view);
+
+ /* When changing the format we need to re-set the alignment */
+ e_html_editor_selection_set_alignment (selection, selection->priv->alignment);
+
+ g_object_notify (G_OBJECT (selection), "block-format");
+}
+
+/**
+ * e_html_editor_selection_get_font_color:
+ * @selection: an #EHTMLEditorSelection
+ * @rgba: a #GdkRGBA object to be set to current font color
+ *
+ * Sets @rgba to contain color of current text selection or letter at current
+ * cursor position.
+ */
+void
+e_html_editor_selection_get_font_color (EHTMLEditorSelection *selection,
+ GdkRGBA *rgba)
+{
+ gchar *color;
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") == 0) {
+ color = g_strdup (selection->priv->font_color);
+ } else {
+ color = get_font_property (selection, "color");
+ if (!color) {
+ *rgba = black;
+ return;
+ }
+ }
+
+ gdk_rgba_parse (rgba, color);
+ g_free (color);
+}
+
+/**
+ * e_html_editor_selection_set_font_color:
+ * @selection: an #EHTMLEditorSelection
+ * @rgba: a #GdkRGBA
+ *
+ * Sets font color of current selection or letter at current cursor position to
+ * color defined in @rgba.
+ */
+void
+e_html_editor_selection_set_font_color (EHTMLEditorSelection *selection,
+ const GdkRGBA *rgba)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+ guint32 rgba_value;
+ gchar *color;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (!rgba)
+ rgba = &black;
+
+ rgba_value = e_rgba_to_value ((GdkRGBA *) rgba);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_FORE_COLOR;
+ color = g_strdup_printf ("#%06x", rgba_value);
+ selection->priv->font_color = g_strdup (color);
+ e_html_editor_view_exec_command (view, command, color);
+ g_free (color);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "font-color");
+}
+
+/**
+ * e_html_editor_selection_get_font_name:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns name of font used in current selection or at letter at current cursor
+ * position.
+ *
+ * Returns: A string with font name. [transfer-none]
+ */
+const gchar *
+e_html_editor_selection_get_font_name (EHTMLEditorSelection *selection)
+{
+ WebKitDOMNode *node;
+ WebKitDOMRange *range;
+ WebKitDOMCSSStyleDeclaration *css;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
+
+ range = html_editor_selection_get_current_range (selection);
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+
+ g_free (selection->priv->font_family);
+ css = webkit_dom_element_get_style (WEBKIT_DOM_ELEMENT (node));
+ selection->priv->font_family =
+ webkit_dom_css_style_declaration_get_property_value (css, "fontFamily");
+
+ return selection->priv->font_family;
+}
+
+/**
+ * e_html_editor_selection_set_font_name:
+ * @selection: an #EHTMLEditorSelection
+ * @font_name: a font name to apply
+ *
+ * Sets font name of current selection or of letter at current cursor position
+ * to @font_name.
+ */
+void
+e_html_editor_selection_set_font_name (EHTMLEditorSelection *selection,
+ const gchar *font_name)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_FONT_NAME;
+ e_html_editor_view_exec_command (view, command, font_name);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "font-name");
+}
+
+/**
+ * e_editor_Selection_get_font_size:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns point size of current selection or of letter at current cursor position.
+ */
+ guint
+e_html_editor_selection_get_font_size (EHTMLEditorSelection *selection)
+{
+ gchar *size;
+ guint size_int;
+
+ g_return_val_if_fail (
+ E_IS_HTML_EDITOR_SELECTION (selection),
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL);
+
+ size = get_font_property (selection, "size");
+ if (!size)
+ return E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
+
+ size_int = atoi (size);
+ g_free (size);
+
+ if (size_int == 0)
+ return E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
+
+ return size_int;
+}
+
+/**
+ * e_html_editor_selection_set_font_size:
+ * @selection: an #EHTMLEditorSelection
+ * @font_size: point size to apply
+ *
+ * Sets font size of current selection or of letter at current cursor position
+ * to @font_size.
+ */
+void
+e_html_editor_selection_set_font_size (EHTMLEditorSelection *selection,
+ guint font_size)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+ gchar *size_str;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ selection->priv->font_size = font_size;
+ command = E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE;
+ size_str = g_strdup_printf ("%d", font_size);
+ e_html_editor_view_exec_command (view, command, size_str);
+ g_free (size_str);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "font-size");
+}
+
+/**
+ * e_html_editor_selection_is_citation:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current paragraph is a citation.
+ *
+ * Returns: @TRUE when current paragraph is a citation, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_is_citation (EHTMLEditorSelection *selection)
+{
+ gboolean ret_val;
+ gchar *value, *text_content;
+ WebKitDOMNode *node;
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return FALSE;
+
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+
+ if (WEBKIT_DOM_IS_TEXT (node))
+ return get_has_style (selection, "citation");
+
+ /* If we are changing the format of block we have to re-set bold property,
+ * otherwise it will be turned off because of no text in composer */
+ text_content = webkit_dom_node_get_text_content (node);
+ if (g_strcmp0 (text_content, "") == 0) {
+ g_free (text_content);
+ return FALSE;
+ }
+ g_free (text_content);
+
+ value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type");
+
+ /* citation == <blockquote type='cite'> */
+ if (g_strstr_len (value, -1, "cite"))
+ ret_val = TRUE;
+ else
+ ret_val = get_has_style (selection, "citation");
+
+ g_free (value);
+ return ret_val;
+}
+
+/**
+ * e_html_editor_selection_is_indented:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current paragraph is indented. This does not include
+ * citations. To check, whether paragraph is a citation, use
+ * e_html_editor_selection_is_citation().
+ *
+ * Returns: @TRUE when current paragraph is indented, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_is_indented (EHTMLEditorSelection *selection)
+{
+ WebKitDOMRange *range;
+ WebKitDOMNode *node;
+ WebKitDOMElement *element;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return FALSE;
+
+ node = webkit_dom_range_get_end_container (range, NULL);
+ if (!WEBKIT_DOM_IS_ELEMENT (node))
+ node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node));
+
+ element = webkit_dom_node_get_parent_element (node);
+
+ return element_has_class (element, "-x-evo-indented");
+}
+
+static gboolean
+is_in_html_mode (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view = e_html_editor_selection_ref_html_editor_view (selection);
+ gboolean ret_val;
+
+ g_return_val_if_fail (view != NULL, FALSE);
+
+ ret_val = e_html_editor_view_get_html_mode (view);
+
+ g_object_unref (view);
+
+ return ret_val;
+}
+
+static gint
+get_list_level (WebKitDOMNode *node)
+{
+ gint level = 0;
+
+ while (node && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node)) {
+ if (node_is_list (node))
+ level++;
+ node = webkit_dom_node_get_parent_node (node);
+ }
+
+ return level;
+}
+
+/**
+ * e_html_editor_selection_indent:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Indents current paragraph by one level.
+ */
+void
+e_html_editor_selection_indent (EHTMLEditorSelection *selection)
+{
+ gboolean has_selection;
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ has_selection = g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0;
+
+ if (!has_selection) {
+ WebKitDOMDocument *document;
+ WebKitDOMRange *range;
+ WebKitDOMNode *node, *clone;
+ WebKitDOMElement *element, *caret_position;
+ gint word_wrap_length = selection->priv->word_wrap_length;
+ gint level;
+ gint final_width = 0;
+
+ document = webkit_web_view_get_dom_document (
+ WEBKIT_WEB_VIEW (view));
+
+ caret_position = e_html_editor_selection_save_caret_position (selection);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range) {
+ g_object_unref (view);
+ return;
+ }
+
+ node = webkit_dom_range_get_end_container (range, NULL);
+ if (!WEBKIT_DOM_IS_ELEMENT (node))
+ node = WEBKIT_DOM_NODE (
+ webkit_dom_node_get_parent_element (node));
+
+ if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node)) {
+ gboolean html_mode = e_html_editor_view_get_html_mode (view);
+ WebKitDOMElement *list;
+ WebKitDOMNode *source_list = webkit_dom_node_get_parent_node (node);
+ EHTMLEditorSelectionBlockFormat format;
+
+ format = e_html_editor_selection_get_list_format_from_node (source_list);
+
+ list = create_list_element (
+ selection, document, format, get_list_level (node), html_mode);
+
+ element_add_class (list, "-x-evo-indented");
+ webkit_dom_node_insert_before (
+ source_list, WEBKIT_DOM_NODE (list), node, NULL);
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (list), node, NULL);
+ if (!webkit_dom_node_contains (node, WEBKIT_DOM_NODE (caret_position))) {
+ webkit_dom_node_append_child (
+ node, WEBKIT_DOM_NODE (caret_position), NULL);
+ }
+
+ merge_lists_if_possible (WEBKIT_DOM_NODE (list));
+
+ e_html_editor_selection_restore_caret_position (selection);
+
+ g_object_unref (view);
+ return;
+ }
+
+ level = get_indentation_level (WEBKIT_DOM_ELEMENT (node));
+
+ final_width = word_wrap_length - SPACES_PER_INDENTATION * (level + 1);
+ if (final_width < 10 && !is_in_html_mode (selection)) {
+ e_html_editor_selection_restore_caret_position (selection);
+ g_object_unref (view);
+ return;
+ }
+
+ element = webkit_dom_node_get_parent_element (node);
+ clone = webkit_dom_node_clone_node (node, TRUE);
+
+ /* Remove style and let the paragraph inherit it from parent */
+ if (element_has_class (WEBKIT_DOM_ELEMENT (clone), "-x-evo-paragraph"))
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (clone), "style");
+
+ element = e_html_editor_selection_get_indented_element (
+ selection, document, final_width);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (element),
+ clone,
+ NULL);
+
+ webkit_dom_node_replace_child (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (element),
+ node,
+ NULL);
+
+ e_html_editor_selection_restore_caret_position (selection);
+ } else {
+ command = E_HTML_EDITOR_VIEW_COMMAND_INDENT;
+ e_html_editor_selection_save (selection);
+ e_html_editor_view_exec_command (view, command, NULL);
+ }
+
+ e_html_editor_view_force_spell_check_for_current_paragraph (view);
+
+ if (has_selection)
+ e_html_editor_selection_restore (selection);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "indented");
+}
+
+static const gchar *
+get_css_alignment_value (EHTMLEditorSelectionAlignment alignment)
+{
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT)
+ return ""; /* Left is by default on ltr */
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER)
+ return "text-align: center;";
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT)
+ return "text-align: right;";
+
+ return "";
+}
+
+static void
+unindent_list (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document)
+{
+ gboolean after = FALSE;
+ WebKitDOMElement *new_list;
+ WebKitDOMNode *source_list, *source_list_clone, *current_list, *item;
+ WebKitDOMElement *selection_start_marker;
+ WebKitDOMElement *selection_end_marker;
+
+ selection_start_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-start-marker", NULL);
+ selection_end_marker = webkit_dom_document_query_selector (
+ document, "span#-x-evo-selection-end-marker", NULL);
+
+ if (!selection_start_marker || !selection_end_marker)
+ return;
+
+ /* Copy elements from previous block to list */
+ item = webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (selection_start_marker));
+ source_list = webkit_dom_node_get_parent_node (item);
+ new_list = WEBKIT_DOM_ELEMENT (
+ webkit_dom_node_clone_node (source_list, FALSE));
+ current_list = source_list;
+ source_list_clone = webkit_dom_node_clone_node (source_list, FALSE);
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (source_list),
+ WEBKIT_DOM_NODE (source_list_clone),
+ webkit_dom_node_get_next_sibling (source_list),
+ NULL);
+
+ if (element_has_class (WEBKIT_DOM_ELEMENT (source_list), "-x-evo-indented"))
+ element_add_class (WEBKIT_DOM_ELEMENT (new_list), "-x-evo-indented");
+
+ while (item) {
+ WebKitDOMNode *next_item = webkit_dom_node_get_next_sibling (item);
+
+ if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
+ if (after) {
+ webkit_dom_node_append_child (
+ source_list_clone, WEBKIT_DOM_NODE (item), NULL);
+ } else {
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (source_list),
+ item,
+ webkit_dom_node_get_next_sibling (source_list),
+ NULL);
+ }
+ }
+
+ if (webkit_dom_node_contains (item, WEBKIT_DOM_NODE (selection_end_marker)))
+ after = TRUE;
+
+ if (!next_item) {
+ if (after)
+ break;
+
+ current_list = webkit_dom_node_get_next_sibling (current_list);
+ next_item = webkit_dom_node_get_first_child (current_list);
+ }
+ item = next_item;
+ }
+
+ remove_node_if_empty (source_list_clone);
+ remove_node_if_empty (source_list);
+}
+/**
+ * e_html_editor_selection_unindent:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Unindents current paragraph by one level.
+ */
+void
+e_html_editor_selection_unindent (EHTMLEditorSelection *selection)
+{
+ gboolean has_selection;
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ has_selection = g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0;
+
+ if (!has_selection) {
+ EHTMLEditorSelectionAlignment alignment;
+ gboolean before_node = TRUE, reinsert_caret_position = FALSE;
+ const gchar *align_value;
+ gint word_wrap_length = selection->priv->word_wrap_length;
+ gint level, width;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *element;
+ WebKitDOMElement *prev_blockquote = NULL, *next_blockquote = NULL;
+ WebKitDOMNode *node, *clone, *node_clone, *caret_node;
+ WebKitDOMRange *range;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ e_html_editor_selection_save_caret_position (selection);
+
+ alignment = e_html_editor_selection_get_alignment (selection);
+ align_value = get_css_alignment_value (alignment);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range) {
+ g_object_unref (view);
+ return;
+ }
+
+ node = webkit_dom_range_get_end_container (range, NULL);
+ if (!WEBKIT_DOM_IS_ELEMENT (node))
+ node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node));
+
+ element = webkit_dom_node_get_parent_element (node);
+
+ if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node)) {
+ e_html_editor_selection_save (selection);
+ e_html_editor_selection_clear_caret_position_marker (selection);
+
+ unindent_list (selection, document);
+ e_html_editor_selection_restore (selection);
+ g_object_unref (view);
+ return;
+ }
+
+ if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (element))
+ return;
+
+ element_add_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-to-unindent");
+
+ level = get_indentation_level (element);
+ width = word_wrap_length - SPACES_PER_INDENTATION * level;
+ clone = WEBKIT_DOM_NODE (webkit_dom_node_clone_node (WEBKIT_DOM_NODE (element), TRUE));
+
+ /* Look if we have previous siblings, if so, we have to
+ * create new blockquote that will include them */
+ if (webkit_dom_node_get_previous_sibling (node))
+ prev_blockquote = e_html_editor_selection_get_indented_element (
+ selection, document, width);
+
+ /* Look if we have next siblings, if so, we have to
+ * create new blockquote that will include them */
+ if (webkit_dom_node_get_next_sibling (node))
+ next_blockquote = e_html_editor_selection_get_indented_element (
+ selection, document, width);
+
+ /* Copy nodes that are before / after the element that we want to unindent */
+ while (webkit_dom_node_has_child_nodes (clone)) {
+ WebKitDOMNode *child;
+
+ child = webkit_dom_node_get_first_child (clone);
+
+ if (is_caret_position_node (child)) {
+ reinsert_caret_position = TRUE;
+ caret_node = webkit_dom_node_clone_node (child, TRUE);
+ remove_node (child);
+ continue;
+ }
+
+ if (webkit_dom_node_is_equal_node (child, node)) {
+ before_node = FALSE;
+ node_clone = webkit_dom_node_clone_node (child, TRUE);
+ remove_node (child);
+ continue;
+ }
+
+ webkit_dom_node_append_child (
+ before_node ?
+ WEBKIT_DOM_NODE (prev_blockquote) :
+ WEBKIT_DOM_NODE (next_blockquote),
+ child,
+ NULL);
+
+ remove_node (child);
+ }
+
+ element_remove_class (WEBKIT_DOM_ELEMENT (node_clone), "-x-evo-to-unindent");
+
+ /* Insert blockqoute with nodes that were before the element that we want to unindent */
+ if (prev_blockquote) {
+ if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (prev_blockquote))) {
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
+ WEBKIT_DOM_NODE (prev_blockquote),
+ WEBKIT_DOM_NODE (element),
+ NULL);
+ }
+ }
+
+ /* Reinsert the caret position */
+ if (reinsert_caret_position) {
+ webkit_dom_node_insert_before (
+ node_clone,
+ caret_node,
+ webkit_dom_node_get_first_child (node_clone),
+ NULL);
+ }
+
+ if (level == 1 && element_has_class (WEBKIT_DOM_ELEMENT (node_clone), "-x-evo-paragraph"))
+ e_html_editor_selection_set_paragraph_style (
+ selection, WEBKIT_DOM_ELEMENT (node_clone), word_wrap_length, 0, align_value);
+
+ /* Insert the unindented element */
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
+ node_clone,
+ WEBKIT_DOM_NODE (element),
+ NULL);
+
+ /* Insert blockqoute with nodes that were after the element that we want to unindent */
+ if (next_blockquote) {
+ if (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (prev_blockquote))) {
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
+ WEBKIT_DOM_NODE (next_blockquote),
+ WEBKIT_DOM_NODE (element),
+ NULL);
+ }
+ }
+
+ /* Remove old blockquote */
+ remove_node (WEBKIT_DOM_NODE (element));
+
+ e_html_editor_selection_restore_caret_position (selection);
+ } else {
+ command = E_HTML_EDITOR_VIEW_COMMAND_OUTDENT;
+ e_html_editor_selection_save (selection);
+ e_html_editor_view_exec_command (view, command, NULL);
+ }
+
+ e_html_editor_view_force_spell_check_for_current_paragraph (view);
+
+ if (has_selection)
+ e_html_editor_selection_restore (selection);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "indented");
+}
+
+/**
+ * e_html_editor_selection_is_bold:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current selection or letter at current cursor position
+ * is bold.
+ *
+ * Returns @TRUE when selection is bold, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_is_bold (EHTMLEditorSelection *selection)
+{
+ gboolean ret_val;
+ gchar *value, *text_content;
+ EHTMLEditorView *view;
+ WebKitDOMCSSStyleDeclaration *style;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMNode *node;
+ WebKitDOMElement *element;
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, FALSE);
+
+ if (!e_html_editor_view_get_html_mode (view)) {
+ g_object_unref (view);
+ return FALSE;
+ }
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ g_object_unref (view);
+ window = webkit_dom_document_get_default_view (document);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return FALSE;
+
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+ /* If we are changing the format of block we have to re-set bold property,
+ * otherwise it will be turned off because of no text in composer */
+ text_content = webkit_dom_node_get_text_content (node);
+ if (g_strcmp0 (text_content, "") == 0) {
+ g_free (text_content);
+ return selection->priv->is_bold;
+ }
+ g_free (text_content);
+
+ if (WEBKIT_DOM_IS_ELEMENT (node))
+ element = WEBKIT_DOM_ELEMENT (node);
+ else
+ element = webkit_dom_node_get_parent_element (node);
+
+ style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
+ value = webkit_dom_css_style_declaration_get_property_value (style, "font-weight");
+
+ if (g_strstr_len (value, -1, "normal"))
+ ret_val = FALSE;
+ else
+ ret_val = TRUE;
+
+ g_free (value);
+ return ret_val;
+}
+
+/**
+ * e_html_editor_selection_set_bold:
+ * @selection: an #EHTMLEditorSelection
+ * @bold: @TRUE to enable bold, @FALSE to disable
+ *
+ * Toggles bold formatting of current selection or letter at current cursor
+ * position, depending on whether @bold is @TRUE or @FALSE.
+ */
+void
+e_html_editor_selection_set_bold (EHTMLEditorSelection *selection,
+ gboolean bold)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (e_html_editor_selection_is_bold (selection) == bold)
+ return;
+
+ selection->priv->is_bold = bold;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_BOLD;
+ e_html_editor_view_exec_command (view, command, NULL);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "bold");
+}
+
+/**
+ * e_html_editor_selection_is_italic:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current selection or letter at current cursor position
+ * is italic.
+ *
+ * Returns @TRUE when selection is italic, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_is_italic (EHTMLEditorSelection *selection)
+{
+ gboolean ret_val;
+ gchar *value, *text_content;
+ EHTMLEditorView *view;
+ WebKitDOMCSSStyleDeclaration *style;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMNode *node;
+ WebKitDOMElement *element;
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, FALSE);
+
+ if (!e_html_editor_view_get_html_mode (view)) {
+ g_object_unref (view);
+ return FALSE;
+ }
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ g_object_unref (view);
+ window = webkit_dom_document_get_default_view (document);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return FALSE;
+
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+ /* If we are changing the format of block we have to re-set italic property,
+ * otherwise it will be turned off because of no text in composer */
+ text_content = webkit_dom_node_get_text_content (node);
+ if (g_strcmp0 (text_content, "") == 0) {
+ g_free (text_content);
+ return selection->priv->is_italic;
+ }
+ g_free (text_content);
+
+ if (WEBKIT_DOM_IS_ELEMENT (node))
+ element = WEBKIT_DOM_ELEMENT (node);
+ else
+ element = webkit_dom_node_get_parent_element (node);
+
+ style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
+ value = webkit_dom_css_style_declaration_get_property_value (style, "font-style");
+
+ if (g_strstr_len (value, -1, "italic"))
+ ret_val = TRUE;
+ else
+ ret_val = FALSE;
+
+ g_free (value);
+ return ret_val;
+}
+
+/**
+ * e_html_editor_selection_set_italic:
+ * @selection: an #EHTMLEditorSelection
+ * @italic: @TRUE to enable italic, @FALSE to disable
+ *
+ * Toggles italic formatting of current selection or letter at current cursor
+ * position, depending on whether @italic is @TRUE or @FALSE.
+ */
+void
+e_html_editor_selection_set_italic (EHTMLEditorSelection *selection,
+ gboolean italic)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (e_html_editor_selection_is_italic (selection) == italic)
+ return;
+
+ selection->priv->is_italic = italic;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_ITALIC;
+ e_html_editor_view_exec_command (view, command, NULL);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "italic");
+}
+
+static gboolean
+is_monospaced_element (WebKitDOMElement *element)
+{
+ gchar *value;
+ gboolean ret_val = FALSE;
+
+ if (!element)
+ return FALSE;
+
+ if (!WEBKIT_DOM_IS_HTML_FONT_ELEMENT (element))
+ return FALSE;
+
+ value = webkit_dom_element_get_attribute (element, "face");
+
+ if (g_strcmp0 (value, "monospace") == 0)
+ ret_val = TRUE;
+
+ g_free (value);
+
+ return ret_val;
+}
+
+/**
+ * e_html_editor_selection_is_monospaced:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current selection or letter at current cursor position
+ * is monospaced.
+ *
+ * Returns @TRUE when selection is monospaced, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_is_monospaced (EHTMLEditorSelection *selection)
+{
+ gboolean ret_val;
+ gchar *value, *text_content;
+ EHTMLEditorView *view;
+ WebKitDOMCSSStyleDeclaration *style;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMNode *node;
+ WebKitDOMElement *element;
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, FALSE);
+
+ if (!e_html_editor_view_get_html_mode (view)) {
+ g_object_unref (view);
+ return FALSE;
+ }
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ g_object_unref (view);
+ window = webkit_dom_document_get_default_view (document);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return FALSE;
+
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+ /* If we are changing the format of block we have to re-set italic property,
+ * otherwise it will be turned off because of no text in composer */
+ text_content = webkit_dom_node_get_text_content (node);
+ if (g_strcmp0 (text_content, "") == 0) {
+ g_free (text_content);
+ return selection->priv->is_monospaced;
+ }
+ g_free (text_content);
+
+ if (WEBKIT_DOM_IS_ELEMENT (node))
+ element = WEBKIT_DOM_ELEMENT (node);
+ else
+ element = webkit_dom_node_get_parent_element (node);
+
+ style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
+ value = webkit_dom_css_style_declaration_get_property_value (style, "font-family");
+
+ if (g_strstr_len (value, -1, "monospace"))
+ ret_val = TRUE;
+ else
+ ret_val = FALSE;
+
+ g_free (value);
+ return ret_val;
+}
+
+static void
+move_caret_into_element (WebKitDOMDocument *document,
+ WebKitDOMElement *element)
+{
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *window_selection;
+ WebKitDOMRange *new_range;
+
+ if (!element)
+ return;
+
+ window = webkit_dom_document_get_default_view (document);
+ window_selection = webkit_dom_dom_window_get_selection (window);
+ new_range = webkit_dom_document_create_range (document);
+
+ webkit_dom_range_select_node_contents (
+ new_range, WEBKIT_DOM_NODE (element), NULL);
+ webkit_dom_range_collapse (new_range, FALSE, NULL);
+ webkit_dom_dom_selection_remove_all_ranges (window_selection);
+ webkit_dom_dom_selection_add_range (window_selection, new_range);
+}
+
+/**
+ * e_html_editor_selection_set_monospaced:
+ * @selection: an #EHTMLEditorSelection
+ * @monospaced: @TRUE to enable monospaced, @FALSE to disable
+ *
+ * Toggles monospaced formatting of current selection or letter at current cursor
+ * position, depending on whether @monospaced is @TRUE or @FALSE.
+ */
+void
+e_html_editor_selection_set_monospaced (EHTMLEditorSelection *selection,
+ gboolean monospaced)
+{
+ EHTMLEditorView *view;
+ WebKitWebView *web_view;
+ WebKitDOMDocument *document;
+ WebKitDOMRange *range;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *window_selection;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (e_html_editor_selection_is_monospaced (selection) == monospaced)
+ return;
+
+ selection->priv->is_monospaced = monospaced;
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ web_view = WEBKIT_WEB_VIEW (view);
+
+ document = webkit_web_view_get_dom_document (web_view);
+ window = webkit_dom_document_get_default_view (document);
+ window_selection = webkit_dom_dom_window_get_selection (window);
+
+ if (monospaced) {
+ gchar *font_size_str;
+ guint font_size;
+ WebKitDOMElement *monospace;
+
+ monospace = webkit_dom_document_create_element (
+ document, "font", NULL);
+ webkit_dom_element_set_attribute (
+ monospace, "face", "monospace", NULL);
+
+ font_size = selection->priv->font_size;
+ if (font_size == 0)
+ font_size = E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
+ font_size_str = g_strdup_printf ("%d", font_size);
+ webkit_dom_element_set_attribute (
+ monospace, "size", font_size_str, NULL);
+ g_free (font_size_str);
+
+ if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0) {
+ gchar *html, *outer_html;
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (monospace),
+ WEBKIT_DOM_NODE (
+ webkit_dom_range_clone_contents (range, NULL)),
+ NULL);
+
+ outer_html = webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (monospace));
+
+ html = g_strconcat (
+ /* Mark selection for restoration */
+ "<span id=\"-x-evo-selection-start-marker\"></span>",
+ outer_html,
+ "<span id=\"-x-evo-selection-end-marker\"></span>",
+ NULL),
+
+ e_html_editor_selection_insert_html (selection, html);
+
+ e_html_editor_selection_restore (selection);
+
+ g_free (html);
+ g_free (outer_html);
+ } else {
+ /* https://bugs.webkit.org/show_bug.cgi?id=15256 */
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (monospace),
+ UNICODE_ZERO_WIDTH_SPACE,
+ NULL);
+ webkit_dom_range_insert_node (
+ range, WEBKIT_DOM_NODE (monospace), NULL);
+
+ move_caret_into_element (document, monospace);
+ }
+ } else {
+ gboolean is_bold, is_italic, is_underline, is_strikethrough;
+ guint font_size;
+ WebKitDOMElement *tt_element;
+ WebKitDOMNode *node;
+
+ node = webkit_dom_range_get_end_container (range, NULL);
+ if (WEBKIT_DOM_IS_ELEMENT (node) &&
+ is_monospaced_element (WEBKIT_DOM_ELEMENT (node))) {
+ tt_element = WEBKIT_DOM_ELEMENT (node);
+ } else {
+ tt_element = e_html_editor_dom_node_find_parent_element (node, "FONT");
+
+ if (!is_monospaced_element (tt_element)) {
+ g_object_unref (view);
+ return;
+ }
+ }
+
+ /* Save current formatting */
+ is_bold = selection->priv->is_bold;
+ is_italic = selection->priv->is_italic;
+ is_underline = selection->priv->is_underline;
+ is_strikethrough = selection->priv->is_strikethrough;
+ font_size = selection->priv->font_size;
+ if (font_size == 0)
+ font_size = E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL;
+
+ if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") != 0) {
+ gchar *html, *outer_html, *inner_html, *beginning, *end;
+ gchar *start_position, *end_position, *font_size_str;
+ WebKitDOMElement *wrapper;
+
+ wrapper = webkit_dom_document_create_element (
+ document, "SPAN", NULL);
+ webkit_dom_element_set_id (wrapper, "-x-evo-remove-tt");
+ webkit_dom_range_surround_contents (
+ range, WEBKIT_DOM_NODE (wrapper), NULL);
+
+ html = webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (tt_element));
+
+ start_position = g_strstr_len (
+ html, -1, "<span id=\"-x-evo-remove-tt\"");
+ end_position = g_strstr_len (start_position, -1, "</span>");
+
+ beginning = g_utf8_substring (
+ html, 0, g_utf8_pointer_to_offset (html, start_position));
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (wrapper));
+ end = g_utf8_substring (
+ html,
+ g_utf8_pointer_to_offset (html, end_position) + 7,
+ g_utf8_strlen (html, -1)),
+
+ font_size_str = g_strdup_printf ("%d", font_size);
+
+ outer_html =
+ g_strconcat (
+ /* Beginning */
+ beginning,
+ /* End the previous FONT tag */
+ "</font>",
+ /* Mark selection for restoration */
+ "<span id=\"-x-evo-selection-start-marker\"></span>",
+ /* Inside will be the same */
+ inner_html,
+ "<span id=\"-x-evo-selection-end-marker\"></span>",
+ /* Start the new FONT element */
+ "<font face=\"monospace\" size=\"",
+ font_size_str,
+ "\">",
+ /* End - we have to start after </span> */
+ end,
+ NULL),
+
+ g_free (font_size_str);
+
+ webkit_dom_html_element_set_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (tt_element),
+ outer_html,
+ NULL);
+
+ e_html_editor_selection_restore (selection);
+
+ g_free (html);
+ g_free (outer_html);
+ g_free (inner_html);
+ g_free (beginning);
+ g_free (end);
+ } else {
+ WebKitDOMRange *new_range;
+ gchar *outer_html;
+ gchar *tmp;
+
+ webkit_dom_element_set_id (tt_element, "ev-tt");
+
+ outer_html = webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (tt_element));
+ tmp = g_strconcat (outer_html, UNICODE_ZERO_WIDTH_SPACE, NULL);
+ webkit_dom_html_element_set_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (tt_element),
+ tmp, NULL);
+
+ /* We need to get that element again */
+ tt_element = webkit_dom_document_get_element_by_id (
+ document, "ev-tt");
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (tt_element), "id");
+
+ new_range = webkit_dom_document_create_range (document);
+ webkit_dom_range_set_start_after (
+ new_range, WEBKIT_DOM_NODE (tt_element), NULL);
+ webkit_dom_range_set_end_after (
+ new_range, WEBKIT_DOM_NODE (tt_element), NULL);
+
+ webkit_dom_dom_selection_remove_all_ranges (
+ window_selection);
+ webkit_dom_dom_selection_add_range (
+ window_selection, new_range);
+
+ webkit_dom_dom_selection_modify (
+ window_selection, "move", "right", "character");
+
+ g_free (outer_html);
+ g_free (tmp);
+
+ e_html_editor_view_force_spell_check_for_current_paragraph (
+ view);
+ }
+
+ /* Re-set formatting */
+ if (is_bold)
+ e_html_editor_selection_set_bold (selection, TRUE);
+ if (is_italic)
+ e_html_editor_selection_set_italic (selection, TRUE);
+ if (is_underline)
+ e_html_editor_selection_set_underline (selection, TRUE);
+ if (is_strikethrough)
+ e_html_editor_selection_set_strikethrough (selection, TRUE);
+
+ e_html_editor_selection_set_font_size (selection, font_size);
+ }
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "monospaced");
+}
+
+/**
+ * e_html_editor_selection_is_strikethrough:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current selection or letter at current cursor position
+ * is striked through.
+ *
+ * Returns @TRUE when selection is striked through, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_is_strikethrough (EHTMLEditorSelection *selection)
+{
+ gboolean ret_val;
+ gchar *value, *text_content;
+ EHTMLEditorView *view;
+ WebKitDOMCSSStyleDeclaration *style;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMNode *node;
+ WebKitDOMElement *element;
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, FALSE);
+
+ if (!e_html_editor_view_get_html_mode (view)) {
+ g_object_unref (view);
+ return FALSE;
+ }
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ g_object_unref (view);
+ window = webkit_dom_document_get_default_view (document);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return FALSE;
+
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+ /* If we are changing the format of block we have to re-set strikethrough property,
+ * otherwise it will be turned off because of no text in composer */
+ text_content = webkit_dom_node_get_text_content (node);
+ if (g_strcmp0 (text_content, "") == 0) {
+ g_free (text_content);
+ return selection->priv->is_strikethrough;
+ }
+ g_free (text_content);
+
+ if (WEBKIT_DOM_IS_ELEMENT (node))
+ element = WEBKIT_DOM_ELEMENT (node);
+ else
+ element = webkit_dom_node_get_parent_element (node);
+
+ style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
+ value = webkit_dom_css_style_declaration_get_property_value (style, "text-decoration");
+
+ if (g_strstr_len (value, -1, "line-through"))
+ ret_val = TRUE;
+ else
+ ret_val = get_has_style (selection, "strike");
+
+ g_free (value);
+ return ret_val;
+}
+
+/**
+ * e_html_editor_selection_set_strikethrough:
+ * @selection: an #EHTMLEditorSelection
+ * @strikethrough: @TRUE to enable strikethrough, @FALSE to disable
+ *
+ * Toggles strike through formatting of current selection or letter at current
+ * cursor position, depending on whether @strikethrough is @TRUE or @FALSE.
+ */
+void
+e_html_editor_selection_set_strikethrough (EHTMLEditorSelection *selection,
+ gboolean strikethrough)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (e_html_editor_selection_is_strikethrough (selection) == strikethrough)
+ return;
+
+ selection->priv->is_strikethrough = strikethrough;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH;
+ e_html_editor_view_exec_command (view, command, NULL);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "strikethrough");
+}
+
+/**
+ * e_html_editor_selection_is_subscript:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current selection or letter at current cursor position
+ * is in subscript.
+ *
+ * Returns @TRUE when selection is in subscript, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_is_subscript (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ WebKitDOMNode *node;
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, FALSE);
+
+ if (!e_html_editor_view_get_html_mode (view)) {
+ g_object_unref (view);
+ return FALSE;
+ }
+
+ g_object_unref (view);
+
+ range = html_editor_selection_get_current_range (selection);
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+
+ while (node) {
+ gchar *tag_name;
+
+ tag_name = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (node));
+
+ if (g_ascii_strncasecmp (tag_name, "sub", 3) == 0) {
+ g_free (tag_name);
+ break;
+ }
+
+ g_free (tag_name);
+ node = webkit_dom_node_get_parent_node (node);
+ }
+
+ return (node != NULL);
+}
+
+/**
+ * e_html_editor_selection_set_subscript:
+ * @selection: an #EHTMLEditorSelection
+ * @subscript: @TRUE to enable subscript, @FALSE to disable
+ *
+ * Toggles subscript of current selection or letter at current cursor position,
+ * depending on whether @subscript is @TRUE or @FALSE.
+ */
+void
+e_html_editor_selection_set_subscript (EHTMLEditorSelection *selection,
+ gboolean subscript)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (e_html_editor_selection_is_subscript (selection) == subscript)
+ return;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_SUBSCRIPT;
+ e_html_editor_view_exec_command (view, command, NULL);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "subscript");
+}
+
+/**
+ * e_html_editor_selection_is_superscript:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current selection or letter at current cursor position
+ * is in superscript.
+ *
+ * Returns @TRUE when selection is in superscript, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_is_superscript (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ WebKitDOMNode *node;
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, FALSE);
+
+ if (!e_html_editor_view_get_html_mode (view)) {
+ g_object_unref (view);
+ return FALSE;
+ }
+
+ g_object_unref (view);
+
+ range = html_editor_selection_get_current_range (selection);
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+
+ while (node) {
+ gchar *tag_name;
+
+ tag_name = webkit_dom_element_get_tag_name (WEBKIT_DOM_ELEMENT (node));
+
+ if (g_ascii_strncasecmp (tag_name, "sup", 3) == 0) {
+ g_free (tag_name);
+ break;
+ }
+
+ g_free (tag_name);
+ node = webkit_dom_node_get_parent_node (node);
+ }
+
+ return (node != NULL);
+}
+
+/**
+ * e_html_editor_selection_set_superscript:
+ * @selection: an #EHTMLEditorSelection
+ * @superscript: @TRUE to enable superscript, @FALSE to disable
+ *
+ * Toggles superscript of current selection or letter at current cursor position,
+ * depending on whether @superscript is @TRUE or @FALSE.
+ */
+void
+e_html_editor_selection_set_superscript (EHTMLEditorSelection *selection,
+ gboolean superscript)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (e_html_editor_selection_is_superscript (selection) == superscript)
+ return;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_SUPERSCRIPT;
+ e_html_editor_view_exec_command (view, command, NULL);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "superscript");
+}
+
+/**
+ * e_html_editor_selection_is_underline:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Returns whether current selection or letter at current cursor position
+ * is underlined.
+ *
+ * Returns @TRUE when selection is underlined, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_selection_is_underline (EHTMLEditorSelection *selection)
+{
+ gboolean ret_val;
+ gchar *value, *text_content;
+ EHTMLEditorView *view;
+ WebKitDOMCSSStyleDeclaration *style;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMNode *node;
+ WebKitDOMElement *element;
+ WebKitDOMRange *range;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), FALSE);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, FALSE);
+
+ if (!e_html_editor_view_get_html_mode (view)) {
+ g_object_unref (view);
+ return FALSE;
+ }
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ g_object_unref (view);
+ window = webkit_dom_document_get_default_view (document);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return FALSE;
+
+ node = webkit_dom_range_get_common_ancestor_container (range, NULL);
+ /* If we are changing the format of block we have to re-set underline property,
+ * otherwise it will be turned off because of no text in composer */
+ text_content = webkit_dom_node_get_text_content (node);
+ if (g_strcmp0 (text_content, "") == 0) {
+ g_free (text_content);
+ return selection->priv->is_underline;
+ }
+ g_free (text_content);
+
+ if (WEBKIT_DOM_IS_ELEMENT (node))
+ element = WEBKIT_DOM_ELEMENT (node);
+ else
+ element = webkit_dom_node_get_parent_element (node);
+
+ style = webkit_dom_dom_window_get_computed_style (window, element, NULL);
+ value = webkit_dom_css_style_declaration_get_property_value (style, "text-decoration");
+
+ if (g_strstr_len (value, -1, "underline"))
+ ret_val = TRUE;
+ else
+ ret_val = get_has_style (selection, "u");
+
+ g_free (value);
+ return ret_val;
+}
+
+/**
+ * e_html_editor_selection_set_underline:
+ * @selection: an #EHTMLEditorSelection
+ * @underline: @TRUE to enable underline, @FALSE to disable
+ *
+ * Toggles underline formatting of current selection or letter at current
+ * cursor position, depending on whether @underline is @TRUE or @FALSE.
+ */
+void
+e_html_editor_selection_set_underline (EHTMLEditorSelection *selection,
+ gboolean underline)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ if (e_html_editor_selection_is_underline (selection) == underline)
+ return;
+
+ selection->priv->is_underline = underline;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE;
+ e_html_editor_view_exec_command (view, command, NULL);
+
+ g_object_unref (view);
+
+ g_object_notify (G_OBJECT (selection), "underline");
+}
+
+/**
+ * e_html_editor_selection_unlink:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Removes any links (&lt;A&gt; elements) from current selection or at current
+ * cursor position.
+ */
+void
+e_html_editor_selection_unlink (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *dom_selection;
+ WebKitDOMRange *range;
+ WebKitDOMElement *link;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+
+ range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
+ link = e_html_editor_dom_node_find_parent_element (
+ webkit_dom_range_get_start_container (range, NULL), "A");
+
+ if (!link) {
+ gchar *text;
+ /* get element that was clicked on */
+ link = e_html_editor_view_get_element_under_mouse_click (view);
+ if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (link))
+ link = NULL;
+
+ text = webkit_dom_html_element_get_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (link));
+ webkit_dom_html_element_set_outer_html (WEBKIT_DOM_HTML_ELEMENT (link), text, NULL);
+ g_free (text);
+ } else {
+ command = E_HTML_EDITOR_VIEW_COMMAND_UNLINK;
+ e_html_editor_view_exec_command (view, command, NULL);
+ }
+ g_object_unref (view);
+}
+
+/**
+ * e_html_editor_selection_create_link:
+ * @selection: an #EHTMLEditorSelection
+ * @uri: destination of the new link
+ *
+ * Converts current selection into a link pointing to @url.
+ */
+void
+e_html_editor_selection_create_link (EHTMLEditorSelection *selection,
+ const gchar *uri)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+ g_return_if_fail (uri != NULL && *uri != '\0');
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_CREATE_LINK;
+ e_html_editor_view_exec_command (view, command, uri);
+
+ g_object_unref (view);
+}
+
+/**
+ * e_html_editor_selection_insert_text:
+ * @selection: an #EHTMLEditorSelection
+ * @plain_text: text to insert
+ *
+ * Inserts @plain_text at current cursor position. When a text range is selected,
+ * it will be replaced by @plain_text.
+ */
+void
+e_html_editor_selection_insert_text (EHTMLEditorSelection *selection,
+ const gchar *plain_text)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+ g_return_if_fail (plain_text != NULL);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT;
+ e_html_editor_view_exec_command (view, command, plain_text);
+
+ g_object_unref (view);
+}
+
+/**
+ * e_html_editor_selection_insert_html:
+ * @selection: an #EHTMLEditorSelection
+ * @html_text: an HTML code to insert
+ *
+ * Insert @html_text into document at current cursor position. When a text range
+ * is selected, it will be replaced by @html_text.
+ */
+void
+e_html_editor_selection_insert_html (EHTMLEditorSelection *selection,
+ const gchar *html_text)
+{
+ EHTMLEditorView *view;
+ EHTMLEditorViewCommand command;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+ g_return_if_fail (html_text != NULL);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ command = E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML;
+ if (e_html_editor_view_get_html_mode (view)) {
+ e_html_editor_view_exec_command (view, command, html_text);
+ } else {
+ e_html_editor_view_convert_and_insert_html_to_plain_text (
+ view, html_text);
+ }
+
+ g_object_unref (view);
+}
+
+
+/************************* image_load_and_insert_async() *************************/
+
+typedef struct _LoadContext LoadContext;
+
+struct _LoadContext {
+ EHTMLEditorSelection *selection;
+ WebKitDOMElement *element;
+ GInputStream *input_stream;
+ GOutputStream *output_stream;
+ GFile *file;
+ GFileInfo *file_info;
+ goffset total_num_bytes;
+ gssize bytes_read;
+ const gchar *content_type;
+ const gchar *filename;
+ gchar buffer[4096];
+};
+
+/* Forward Declaration */
+static void
+image_load_stream_read_cb (GInputStream *input_stream,
+ GAsyncResult *result,
+ LoadContext *load_context);
+
+static LoadContext *
+image_load_context_new (EHTMLEditorSelection *selection)
+{
+ LoadContext *load_context;
+
+ load_context = g_slice_new0 (LoadContext);
+ load_context->selection = selection;
+
+ return load_context;
+}
+
+static void
+image_load_context_free (LoadContext *load_context)
+{
+ if (load_context->input_stream != NULL)
+ g_object_unref (load_context->input_stream);
+
+ if (load_context->output_stream != NULL)
+ g_object_unref (load_context->output_stream);
+
+ if (load_context->file_info != NULL)
+ g_object_unref (load_context->file_info);
+
+ if (load_context->file != NULL)
+ g_object_unref (load_context->file);
+
+ g_slice_free (LoadContext, load_context);
+}
+
+static void
+replace_base64_image_src (EHTMLEditorSelection *selection,
+ WebKitDOMElement *element,
+ const gchar *base64_content,
+ const gchar *filename,
+ const gchar *uri)
+{
+ EHTMLEditorView *view;
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ e_html_editor_view_set_changed (view, TRUE);
+ g_object_unref (view);
+
+ webkit_dom_html_image_element_set_src (
+ WEBKIT_DOM_HTML_IMAGE_ELEMENT (element),
+ base64_content);
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (element), "data-uri", uri, NULL);
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (element), "data-inline", "", NULL);
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (element), "data-name",
+ filename ? filename : "", NULL);
+}
+
+static void
+insert_base64_image (EHTMLEditorSelection *selection,
+ const gchar *base64_content,
+ const gchar *filename,
+ const gchar *uri)
+{
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *element, *caret_position, *resizable_wrapper;
+ WebKitDOMText *text;
+
+ caret_position = e_html_editor_selection_save_caret_position (selection);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ document = webkit_web_view_get_dom_document (
+ WEBKIT_WEB_VIEW (view));
+
+ e_html_editor_view_set_changed (view, TRUE);
+ g_object_unref (view);
+
+ resizable_wrapper =
+ webkit_dom_document_create_element (document, "span", NULL);
+ webkit_dom_element_set_attribute (
+ resizable_wrapper, "class", "-x-evo-resizable-wrapper", NULL);
+
+ element = webkit_dom_document_create_element (document, "img", NULL);
+ webkit_dom_html_image_element_set_src (
+ WEBKIT_DOM_HTML_IMAGE_ELEMENT (element),
+ base64_content);
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (element), "data-uri", uri, NULL);
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (element), "data-inline", "", NULL);
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (element), "data-name",
+ filename ? filename : "", NULL);
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (resizable_wrapper),
+ WEBKIT_DOM_NODE (element),
+ NULL);
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (caret_position)),
+ WEBKIT_DOM_NODE (resizable_wrapper),
+ WEBKIT_DOM_NODE (caret_position),
+ NULL);
+
+ /* We have to again use UNICODE_ZERO_WIDTH_SPACE character to restore
+ * caret on right position */
+ text = webkit_dom_document_create_text_node (
+ document, UNICODE_ZERO_WIDTH_SPACE);
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (caret_position)),
+ WEBKIT_DOM_NODE (text),
+ WEBKIT_DOM_NODE (caret_position),
+ NULL);
+
+ e_html_editor_selection_restore_caret_position (selection);
+}
+
+static void
+image_load_finish (LoadContext *load_context)
+{
+ EHTMLEditorSelection *selection;
+ GMemoryOutputStream *output_stream;
+ gchar *base64_encoded, *mime_type, *output, *uri;
+ gsize size;
+ gpointer data;
+
+ output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
+
+ selection = load_context->selection;
+
+ mime_type = g_content_type_get_mime_type (load_context->content_type);
+
+ data = g_memory_output_stream_get_data (output_stream);
+ size = g_memory_output_stream_get_data_size (output_stream);
+ uri = g_file_get_uri (load_context->file);
+
+ base64_encoded = g_base64_encode ((const guchar *) data, size);
+ output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
+ if (load_context->element)
+ replace_base64_image_src (
+ selection, load_context->element, output, load_context->filename, uri);
+ else
+ insert_base64_image (selection, output, load_context->filename, uri);
+
+ g_free (base64_encoded);
+ g_free (output);
+ g_free (mime_type);
+ g_free (uri);
+
+ image_load_context_free (load_context);
+}
+
+static void
+image_load_write_cb (GOutputStream *output_stream,
+ GAsyncResult *result,
+ LoadContext *load_context)
+{
+ GInputStream *input_stream;
+ gssize bytes_written;
+ GError *error = NULL;
+
+ bytes_written = g_output_stream_write_finish (
+ output_stream, result, &error);
+
+ if (error) {
+ image_load_context_free (load_context);
+ return;
+ }
+
+ input_stream = load_context->input_stream;
+
+ if (bytes_written < load_context->bytes_read) {
+ g_memmove (
+ load_context->buffer,
+ load_context->buffer + bytes_written,
+ load_context->bytes_read - bytes_written);
+ load_context->bytes_read -= bytes_written;
+
+ g_output_stream_write_async (
+ output_stream,
+ load_context->buffer,
+ load_context->bytes_read,
+ G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback) image_load_write_cb,
+ load_context);
+ } else
+ g_input_stream_read_async (
+ input_stream,
+ load_context->buffer,
+ sizeof (load_context->buffer),
+ G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback) image_load_stream_read_cb,
+ load_context);
+}
+
+static void
+image_load_stream_read_cb (GInputStream *input_stream,
+ GAsyncResult *result,
+ LoadContext *load_context)
+{
+ GOutputStream *output_stream;
+ gssize bytes_read;
+ GError *error = NULL;
+
+ bytes_read = g_input_stream_read_finish (
+ input_stream, result, &error);
+
+ if (error) {
+ image_load_context_free (load_context);
+ return;
+ }
+
+ if (bytes_read == 0) {
+ image_load_finish (load_context);
+ return;
+ }
+
+ output_stream = load_context->output_stream;
+ load_context->bytes_read = bytes_read;
+
+ g_output_stream_write_async (
+ output_stream,
+ load_context->buffer,
+ load_context->bytes_read,
+ G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback) image_load_write_cb,
+ load_context);
+}
+
+static void
+image_load_file_read_cb (GFile *file,
+ GAsyncResult *result,
+ LoadContext *load_context)
+{
+ GFileInputStream *input_stream;
+ GOutputStream *output_stream;
+ GError *error = NULL;
+
+ /* Input stream might be NULL, so don't use cast macro. */
+ input_stream = g_file_read_finish (file, result, &error);
+ load_context->input_stream = (GInputStream *) input_stream;
+
+ if (error) {
+ image_load_context_free (load_context);
+ return;
+ }
+
+ /* Load the contents into a GMemoryOutputStream. */
+ output_stream = g_memory_output_stream_new (
+ NULL, 0, g_realloc, g_free);
+
+ load_context->output_stream = output_stream;
+
+ g_input_stream_read_async (
+ load_context->input_stream,
+ load_context->buffer,
+ sizeof (load_context->buffer),
+ G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback) image_load_stream_read_cb,
+ load_context);
+}
+
+static void
+image_load_query_info_cb (GFile *file,
+ GAsyncResult *result,
+ LoadContext *load_context)
+{
+ GFileInfo *file_info;
+ GError *error = NULL;
+
+ file_info = g_file_query_info_finish (file, result, &error);
+ if (error) {
+ image_load_context_free (load_context);
+ return;
+ }
+
+ load_context->content_type = g_file_info_get_content_type (file_info);
+ load_context->total_num_bytes = g_file_info_get_size (file_info);
+ load_context->filename = g_file_info_get_name (file_info);
+
+ g_file_read_async (
+ file, G_PRIORITY_DEFAULT,
+ NULL, (GAsyncReadyCallback)
+ image_load_file_read_cb, load_context);
+}
+
+static void
+image_load_and_insert_async (EHTMLEditorSelection *selection,
+ WebKitDOMElement *element,
+ const gchar *uri)
+{
+ LoadContext *load_context;
+ GFile *file;
+
+ g_return_if_fail (uri && *uri);
+
+ file = g_file_new_for_uri (uri);
+ g_return_if_fail (file != NULL);
+
+ load_context = image_load_context_new (selection);
+ load_context->file = file;
+ load_context->element = element;
+
+ g_file_query_info_async (
+ file, "standard::*",
+ G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT,
+ NULL, (GAsyncReadyCallback)
+ image_load_query_info_cb, load_context);
+}
+
+/**
+ * e_html_editor_selection_insert_image:
+ * @selection: an #EHTMLEditorSelection
+ * @image_uri: an URI of the source image
+ *
+ * Inserts image at current cursor position using @image_uri as source. When a
+ * text range is selected, it will be replaced by the image.
+ */
+void
+e_html_editor_selection_insert_image (EHTMLEditorSelection *selection,
+ const gchar *image_uri)
+{
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+ g_return_if_fail (image_uri != NULL);
+
+ if (is_in_html_mode (selection)) {
+ if (strstr (image_uri, ";base64,")) {
+ if (g_str_has_prefix (image_uri, "data:"))
+ insert_base64_image (selection, image_uri, "", "");
+ if (strstr (image_uri, ";data")) {
+ const gchar *base64_data = strstr (image_uri, ";") + 1;
+ gchar *filename;
+ glong filename_length;
+
+ filename_length =
+ g_utf8_strlen (image_uri, -1) -
+ g_utf8_strlen (base64_data, -1) - 1;
+ filename = g_strndup (image_uri, filename_length);
+
+ insert_base64_image (selection, base64_data, filename, "");
+ g_free (filename);
+ }
+ } else
+ image_load_and_insert_async (selection, NULL, image_uri);
+ }
+}
+
+/**
+ * e_html_editor_selection_replace_image_src:
+ * @selection: an #EHTMLEditorSelection
+ * @image: #WebKitDOMElement representation of image
+ * @image_uri: an URI of the source image
+ *
+ * Replace the src attribute of the given @image with @image_uri.
+ */
+void
+e_html_editor_selection_replace_image_src (EHTMLEditorSelection *selection,
+ WebKitDOMElement *image,
+ const gchar *image_uri)
+{
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+ g_return_if_fail (image_uri != NULL);
+ g_return_if_fail (WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (image));
+
+ if (strstr (image_uri, ";base64,")) {
+ if (g_str_has_prefix (image_uri, "data:"))
+ replace_base64_image_src (
+ selection, image, image_uri, "", "");
+ if (strstr (image_uri, ";data")) {
+ const gchar *base64_data = strstr (image_uri, ";") + 1;
+ gchar *filename;
+ glong filename_length;
+
+ filename_length =
+ g_utf8_strlen (image_uri, -1) -
+ g_utf8_strlen (base64_data, -1) - 1;
+ filename = g_strndup (image_uri, filename_length);
+
+ replace_base64_image_src (
+ selection, image, base64_data, filename, "");
+ g_free (filename);
+ }
+ } else
+ image_load_and_insert_async (selection, image, image_uri);
+}
+
+/**
+ * e_html_editor_selection_clear_caret_position_marker:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Removes previously set caret position marker from composer.
+ */
+void
+e_html_editor_selection_clear_caret_position_marker (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *element;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ element = webkit_dom_document_get_element_by_id (document, "-x-evo-caret-position");
+
+ if (element)
+ remove_node (WEBKIT_DOM_NODE (element));
+
+ g_object_unref (view);
+}
+
+WebKitDOMNode *
+e_html_editor_selection_get_caret_position_node (WebKitDOMDocument *document)
+{
+ WebKitDOMElement *element;
+
+ element = webkit_dom_document_create_element (document, "SPAN", NULL);
+ webkit_dom_element_set_id (element, "-x-evo-caret-position");
+ webkit_dom_element_set_attribute (
+ element, "style", "color: red", NULL);
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (element), "*", NULL);
+
+ return WEBKIT_DOM_NODE (element);
+}
+
+/**
+ * e_html_editor_selection_save_caret_position:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Saves current caret position in composer.
+ *
+ * Returns: #WebKitDOMElement that was created on caret position
+ */
+WebKitDOMElement *
+e_html_editor_selection_save_caret_position (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMNode *split_node;
+ WebKitDOMNode *start_offset_node;
+ WebKitDOMNode *caret_node;
+ WebKitDOMRange *range;
+ gulong start_offset;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_val_if_fail (view != NULL, NULL);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ g_object_unref (view);
+
+ e_html_editor_selection_clear_caret_position_marker (selection);
+
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return NULL;
+
+ start_offset = webkit_dom_range_get_start_offset (range, NULL);
+ start_offset_node = webkit_dom_range_get_end_container (range, NULL);
+
+ caret_node = e_html_editor_selection_get_caret_position_node (document);
+
+ if (WEBKIT_DOM_IS_TEXT (start_offset_node) && start_offset != 0) {
+ WebKitDOMText *split_text;
+
+ split_text = webkit_dom_text_split_text (
+ WEBKIT_DOM_TEXT (start_offset_node),
+ start_offset, NULL);
+ split_node = WEBKIT_DOM_NODE (split_text);
+ } else {
+ split_node = start_offset_node;
+ }
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (start_offset_node),
+ caret_node,
+ split_node,
+ NULL);
+
+ return WEBKIT_DOM_ELEMENT (caret_node);
+}
+
+static void
+fix_quoting_nodes_after_caret_restoration (WebKitDOMDOMSelection *window_selection,
+ WebKitDOMNode *prev_sibling,
+ WebKitDOMNode *next_sibling)
+{
+ WebKitDOMNode *tmp_node;
+
+ if (!element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper"))
+ return;
+
+ webkit_dom_dom_selection_modify (
+ window_selection, "move", "forward", "character");
+ tmp_node = webkit_dom_node_get_next_sibling (
+ webkit_dom_node_get_first_child (prev_sibling));
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (prev_sibling),
+ tmp_node,
+ next_sibling,
+ NULL);
+
+ tmp_node = webkit_dom_node_get_first_child (prev_sibling);
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (prev_sibling),
+ tmp_node,
+ webkit_dom_node_get_previous_sibling (next_sibling),
+ NULL);
+
+ remove_node (prev_sibling);
+
+ webkit_dom_dom_selection_modify (
+ window_selection, "move", "backward", "character");
+}
+
+/**
+ * e_html_editor_selection_restore_caret_position:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Restores previously saved caret position in composer.
+ */
+void
+e_html_editor_selection_restore_caret_position (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *element;
+ gboolean fix_after_quoting;
+ gboolean swap_direction = FALSE;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ g_object_unref (view);
+
+ element = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-caret-position");
+ fix_after_quoting = element_has_class (element, "-x-evo-caret-quoting");
+
+ if (element) {
+ WebKitDOMDOMWindow *window;
+ WebKitDOMNode *parent_node;
+ WebKitDOMDOMSelection *window_selection;
+ WebKitDOMNode *prev_sibling;
+ WebKitDOMNode *next_sibling;
+
+ if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element)))
+ swap_direction = TRUE;
+
+ window = webkit_dom_document_get_default_view (document);
+ window_selection = webkit_dom_dom_window_get_selection (window);
+ parent_node = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
+ /* If parent is BODY element, we try to restore the position on the
+ * element that is next to us */
+ if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent_node)) {
+ /* Look if we have DIV on right */
+ next_sibling = webkit_dom_node_get_next_sibling (
+ WEBKIT_DOM_NODE (element));
+ if (!WEBKIT_DOM_IS_ELEMENT (next_sibling)) {
+ e_html_editor_selection_clear_caret_position_marker (selection);
+ return;
+ }
+
+ if (element_has_class (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-paragraph")) {
+ remove_node (WEBKIT_DOM_NODE (element));
+
+ move_caret_into_element (
+ document, WEBKIT_DOM_ELEMENT (next_sibling));
+
+ goto out;
+ }
+ }
+
+ move_caret_into_element (document, element);
+
+ if (fix_after_quoting) {
+ prev_sibling = webkit_dom_node_get_previous_sibling (
+ WEBKIT_DOM_NODE (element));
+ next_sibling = webkit_dom_node_get_next_sibling (
+ WEBKIT_DOM_NODE (element));
+ if (!next_sibling)
+ fix_after_quoting = FALSE;
+ }
+
+ remove_node (WEBKIT_DOM_NODE (element));
+
+ if (fix_after_quoting)
+ fix_quoting_nodes_after_caret_restoration (
+ window_selection, prev_sibling, next_sibling);
+ out:
+ /* FIXME If caret position is restored and afterwards the
+ * position is saved it is not on the place where it supposed
+ * to be (it is in the beginning of parent's element. It can
+ * be avoided by moving with the caret. */
+ if (swap_direction) {
+ webkit_dom_dom_selection_modify (
+ window_selection, "move", "forward", "character");
+ webkit_dom_dom_selection_modify (
+ window_selection, "move", "backward", "character");
+ } else {
+ webkit_dom_dom_selection_modify (
+ window_selection, "move", "backward", "character");
+ webkit_dom_dom_selection_modify (
+ window_selection, "move", "forward", "character");
+ }
+ }
+}
+
+static gint
+find_where_to_break_line (WebKitDOMNode *node,
+ gint max_len,
+ gint word_wrap_length)
+{
+ gchar *str, *text_start;
+ gunichar uc;
+ gint pos;
+ gint last_space = 0;
+ gint length;
+ gint ret_val = 0;
+ gchar* position;
+
+ text_start = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node));
+ length = g_utf8_strlen (text_start, -1);
+
+ pos = 1;
+ last_space = 0;
+ str = text_start;
+ do {
+ uc = g_utf8_get_char (str);
+ if (!uc) {
+ g_free (text_start);
+ if (pos <= max_len)
+ return pos;
+ else
+ return last_space;
+ }
+
+ /* If last_space is zero then the word is longer than
+ * word_wrap_length characters, so continue until we find
+ * a space */
+ if ((pos > max_len) && (last_space > 0)) {
+ if (last_space > word_wrap_length) {
+ g_free (text_start);
+ return last_space;
+ }
+
+ if (last_space > max_len) {
+ if (g_unichar_isspace (g_utf8_get_char (text_start)))
+ ret_val = 1;
+
+ g_free (text_start);
+ return ret_val;
+ }
+
+ if (last_space == max_len - 1) {
+ uc = g_utf8_get_char (str);
+ if (g_unichar_isspace (uc))
+ last_space++;
+ }
+
+ g_free (text_start);
+ return last_space;
+ }
+
+ if (g_unichar_isspace (uc))
+ last_space = pos;
+
+ pos += 1;
+ str = g_utf8_next_char (str);
+ } while (*str);
+
+ position = g_utf8_offset_to_pointer (text_start, max_len);
+
+ if (g_unichar_isspace (g_utf8_get_char (position))) {
+ ret_val = max_len + 1;
+ } else {
+ if (last_space < max_len) {
+ ret_val = last_space;
+ } else {
+ if (length > word_wrap_length)
+ ret_val = last_space;
+ else
+ ret_val = 0;
+ }
+ }
+
+ g_free (text_start);
+
+ return ret_val;
+}
+
+static WebKitDOMElement *
+wrap_lines (EHTMLEditorSelection *selection,
+ WebKitDOMNode *paragraph,
+ WebKitDOMDocument *document,
+ gboolean remove_all_br,
+ gint word_wrap_length)
+{
+ WebKitDOMNode *node, *start_node;
+ WebKitDOMNode *paragraph_clone;
+ WebKitDOMDocumentFragment *fragment;
+ WebKitDOMElement *element;
+ WebKitDOMNodeList *wrap_br;
+ gint len, ii, br_count;
+ gulong length_left;
+ glong paragraph_char_count;
+ gchar *text_content;
+
+ if (selection) {
+ paragraph_char_count = g_utf8_strlen (
+ e_html_editor_selection_get_string (selection), -1);
+
+ fragment = webkit_dom_range_clone_contents (
+ html_editor_selection_get_current_range (selection), NULL);
+
+ /* Select all BR elements or just ours that are used for wrapping.
+ * We are not removing user BR elements when this function is activated
+ * from Format->Wrap Lines action */
+ wrap_br = webkit_dom_document_fragment_query_selector_all (
+ fragment,
+ remove_all_br ? "br" : "br.-x-evo-wrap-br",
+ NULL);
+ } else {
+ WebKitDOMElement *caret_node;
+
+ if (!webkit_dom_node_has_child_nodes (paragraph))
+ return WEBKIT_DOM_ELEMENT (paragraph);
+
+ paragraph_clone = webkit_dom_node_clone_node (paragraph, TRUE);
+ caret_node = webkit_dom_element_query_selector (
+ WEBKIT_DOM_ELEMENT (paragraph_clone),
+ "span#-x-evo-caret-position", NULL);
+ text_content = webkit_dom_node_get_text_content (paragraph_clone);
+ paragraph_char_count = g_utf8_strlen (text_content, -1);
+ if (caret_node)
+ paragraph_char_count--;
+ g_free (text_content);
+
+ wrap_br = webkit_dom_element_query_selector_all (
+ WEBKIT_DOM_ELEMENT (paragraph_clone),
+ remove_all_br ? "br" : "br.-x-evo-wrap-br",
+ NULL);
+ }
+
+ /* And remove them */
+ br_count = webkit_dom_node_list_get_length (wrap_br);
+ for (ii = 0; ii < br_count; ii++)
+ remove_node (webkit_dom_node_list_item (wrap_br, ii));
+
+ if (selection)
+ node = WEBKIT_DOM_NODE (fragment);
+ else {
+ webkit_dom_node_normalize (paragraph_clone);
+ node = webkit_dom_node_get_first_child (paragraph_clone);
+ if (node) {
+ text_content = webkit_dom_node_get_text_content (node);
+ if (g_strcmp0 ("\n", text_content) == 0)
+ node = webkit_dom_node_get_next_sibling (node);
+ g_free (text_content);
+ }
+ }
+
+ start_node = node;
+ len = 0;
+ while (node) {
+ gint offset = 0;
+
+ if (WEBKIT_DOM_IS_TEXT (node)) {
+ const gchar *newline;
+ WebKitDOMNode *next_sibling;
+
+ /* If there is temporary hidden space we remove it */
+ text_content = webkit_dom_node_get_text_content (node);
+ if (strstr (text_content, UNICODE_ZERO_WIDTH_SPACE)) {
+ if (g_str_has_prefix (text_content, UNICODE_ZERO_WIDTH_SPACE))
+ webkit_dom_character_data_delete_data (
+ WEBKIT_DOM_CHARACTER_DATA (node),
+ 0,
+ 1,
+ NULL);
+ if (g_str_has_suffix (text_content, UNICODE_ZERO_WIDTH_SPACE))
+ webkit_dom_character_data_delete_data (
+ WEBKIT_DOM_CHARACTER_DATA (node),
+ g_utf8_strlen (text_content, -1) - 1,
+ 1,
+ NULL);
+ g_free (text_content);
+ text_content = webkit_dom_node_get_text_content (node);
+ }
+ newline = g_strstr_len (text_content, -1, "\n");
+
+ next_sibling = node;
+ while (newline) {
+ WebKitDOMElement *element;
+
+ next_sibling = WEBKIT_DOM_NODE (webkit_dom_text_split_text (
+ WEBKIT_DOM_TEXT (next_sibling),
+ g_utf8_pointer_to_offset (text_content, newline),
+ NULL));
+
+ if (!next_sibling)
+ break;
+
+ element = webkit_dom_document_create_element (
+ document, "BR", NULL);
+ element_add_class (element, "-x-evo-temp-wrap-text-br");
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (next_sibling),
+ WEBKIT_DOM_NODE (element),
+ next_sibling,
+ NULL);
+
+ g_free (text_content);
+
+ text_content = webkit_dom_node_get_text_content (next_sibling);
+ if (g_str_has_prefix (text_content, "\n")) {
+ webkit_dom_character_data_delete_data (
+ WEBKIT_DOM_CHARACTER_DATA (next_sibling), 0, 1, NULL);
+ g_free (text_content);
+ text_content =
+ webkit_dom_node_get_text_content (next_sibling);
+ }
+ newline = g_strstr_len (text_content, -1, "\n");
+ }
+ g_free (text_content);
+ } else {
+ /* If element is ANCHOR we wrap it separately */
+ if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node)) {
+ glong anchor_length;
+
+ text_content = webkit_dom_node_get_text_content (node);
+ anchor_length = g_utf8_strlen (text_content, -1);
+ if (len + anchor_length > word_wrap_length) {
+ element = webkit_dom_document_create_element (
+ document, "BR", NULL);
+ element_add_class (element, "-x-evo-wrap-br");
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (element),
+ node,
+ NULL);
+ len = anchor_length;
+ } else
+ len += anchor_length;
+
+ g_free (text_content);
+ node = webkit_dom_node_get_next_sibling (node);
+ continue;
+ }
+
+ if (is_caret_position_node (node)) {
+ node = webkit_dom_node_get_next_sibling (node);
+ continue;
+ }
+
+ /* When we are not removing user-entered BR elements (lines wrapped by user),
+ * we need to skip those elements */
+ if (!remove_all_br && WEBKIT_DOM_IS_HTMLBR_ELEMENT (node)) {
+ if (!element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) {
+ len = 0;
+ node = webkit_dom_node_get_next_sibling (node);
+ continue;
+ }
+ }
+ goto next_node;
+ }
+
+ /* If length of this node + what we already have is still less
+ * then word_wrap_length characters, then just join it and continue to next
+ * node */
+ length_left = webkit_dom_character_data_get_length (
+ WEBKIT_DOM_CHARACTER_DATA (node));
+
+ if ((length_left + len) < word_wrap_length) {
+ len += length_left;
+ goto next_node;
+ }
+
+ /* wrap until we have something */
+ while ((length_left + len) > word_wrap_length) {
+ /* Find where we can line-break the node so that it
+ * effectively fills the rest of current row */
+ offset = find_where_to_break_line (
+ node, word_wrap_length - len, word_wrap_length);
+
+ element = webkit_dom_document_create_element (document, "BR", NULL);
+ element_add_class (element, "-x-evo-wrap-br");
+
+ if (offset > 0 && offset <= word_wrap_length) {
+ if (offset != length_left)
+ webkit_dom_text_split_text (
+ WEBKIT_DOM_TEXT (node), offset, NULL);
+
+ if (webkit_dom_node_get_next_sibling (node)) {
+ gchar *nd_content;
+ WebKitDOMNode *nd = webkit_dom_node_get_next_sibling (node);
+
+ nd = webkit_dom_node_get_next_sibling (node);
+ nd_content = webkit_dom_node_get_text_content (nd);
+ if (nd_content && *nd_content) {
+ if (g_str_has_prefix (nd_content, " "))
+ webkit_dom_character_data_replace_data (
+ WEBKIT_DOM_CHARACTER_DATA (nd), 0, 1, "", NULL);
+ g_free (nd_content);
+ nd_content = webkit_dom_node_get_text_content (nd);
+ if (g_strcmp0 (nd_content, UNICODE_NBSP) == 0)
+ remove_node (nd);
+ g_free (nd_content);
+ }
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (element),
+ nd,
+ NULL);
+ } else {
+ webkit_dom_node_append_child (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (element),
+ NULL);
+ }
+ } else if (offset > word_wrap_length) {
+ if (offset != length_left)
+ webkit_dom_text_split_text (
+ WEBKIT_DOM_TEXT (node), offset + 1, NULL);
+
+ if (webkit_dom_node_get_next_sibling (node)) {
+ gchar *nd_content;
+ WebKitDOMNode *nd = webkit_dom_node_get_next_sibling (node);
+
+ nd = webkit_dom_node_get_next_sibling (node);
+ nd_content = webkit_dom_node_get_text_content (nd);
+ if (nd_content && *nd_content) {
+ if (g_str_has_prefix (nd_content, " "))
+ webkit_dom_character_data_replace_data (
+ WEBKIT_DOM_CHARACTER_DATA (nd), 0, 1, "", NULL);
+ g_free (nd_content);
+ nd_content = webkit_dom_node_get_text_content (nd);
+ if (g_strcmp0 (nd_content, UNICODE_NBSP) == 0)
+ remove_node (nd);
+ g_free (nd_content);
+ }
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (element),
+ nd,
+ NULL);
+ } else {
+ webkit_dom_node_append_child (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (element),
+ NULL);
+ }
+ len = 0;
+ break;
+ } else {
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (element),
+ node,
+ NULL);
+ }
+ length_left = webkit_dom_character_data_get_length (
+ WEBKIT_DOM_CHARACTER_DATA (node));
+
+ len = 0;
+ }
+ len += length_left - offset;
+ next_node:
+ if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (node))
+ len = 0;
+
+ /* Move to next node */
+ if (webkit_dom_node_has_child_nodes (node)) {
+ node = webkit_dom_node_get_first_child (node);
+ } else if (webkit_dom_node_get_next_sibling (node)) {
+ node = webkit_dom_node_get_next_sibling (node);
+ } else {
+ if (webkit_dom_node_is_equal_node (node, start_node))
+ break;
+
+ node = webkit_dom_node_get_parent_node (node);
+ if (node)
+ node = webkit_dom_node_get_next_sibling (node);
+ }
+ }
+
+ if (selection) {
+ gchar *html;
+
+ /* Create a wrapper DIV and put the processed content into it */
+ element = webkit_dom_document_create_element (document, "DIV", NULL);
+ element_add_class (element, "-x-evo-paragraph");
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (element),
+ WEBKIT_DOM_NODE (start_node),
+ NULL);
+
+ webkit_dom_node_normalize (WEBKIT_DOM_NODE (element));
+ /* Get HTML code of the processed content */
+ html = webkit_dom_html_element_get_inner_html (WEBKIT_DOM_HTML_ELEMENT (element));
+
+ /* Overwrite the current selection be the processed content */
+ e_html_editor_selection_insert_html (selection, html);
+
+ g_free (html);
+
+ return NULL;
+ } else {
+ webkit_dom_node_normalize (paragraph_clone);
+
+ /* Replace paragraph with wrapped one */
+ webkit_dom_node_replace_child (
+ webkit_dom_node_get_parent_node (paragraph),
+ paragraph_clone,
+ paragraph,
+ NULL);
+
+ return WEBKIT_DOM_ELEMENT (paragraph_clone);
+ }
+}
+
+void
+e_html_editor_selection_set_indented_style (EHTMLEditorSelection *selection,
+ WebKitDOMElement *element,
+ gint width)
+{
+ EHTMLEditorSelectionAlignment alignment;
+ gchar *style;
+ const gchar *align_value;
+ gint word_wrap_length = (width == -1) ? selection->priv->word_wrap_length : width;
+ gint start = 0, end = 0;
+
+ alignment = e_html_editor_selection_get_alignment (selection);
+ align_value = get_css_alignment_value (alignment);
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT)
+ start = SPACES_PER_INDENTATION;
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
+ start = SPACES_PER_INDENTATION;
+ }
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
+ start = 0;
+ end = SPACES_PER_INDENTATION;
+ }
+
+ webkit_dom_element_set_class_name (element, "-x-evo-indented");
+ /* We don't want vertical space bellow and above blockquote inserted by
+ * WebKit's User Agent Stylesheet. We have to override it through style attribute. */
+ if (is_in_html_mode (selection))
+ style = g_strdup_printf (
+ "-webkit-margin-start: %dch; -webkit-margin-end : %dch; %s",
+ start, end, align_value);
+ else
+ style = g_strdup_printf (
+ "-webkit-margin-start: %dch; -webkit-margin-end : %dch; "
+ "word-wrap: normal; width: %dch; %s",
+ start, end, word_wrap_length, align_value);
+
+ webkit_dom_element_set_attribute (element, "style", style, NULL);
+ g_free (style);
+}
+
+WebKitDOMElement *
+e_html_editor_selection_get_indented_element (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document,
+ gint width)
+{
+ WebKitDOMElement *element;
+
+ element = webkit_dom_document_create_element (document, "BLOCKQUOTE", NULL);
+ e_html_editor_selection_set_indented_style (selection, element, width);
+
+ return element;
+}
+
+void
+e_html_editor_selection_set_paragraph_style (EHTMLEditorSelection *selection,
+ WebKitDOMElement *element,
+ gint width,
+ gint offset,
+ const gchar *style_to_add)
+{
+ EHTMLEditorSelectionAlignment alignment;
+ const gchar *align_value = NULL;
+ char *style = NULL;
+ gint word_wrap_length = (width == -1) ? selection->priv->word_wrap_length : width;
+
+ alignment = e_html_editor_selection_get_alignment (selection);
+ align_value = get_css_alignment_value (alignment);
+
+ element_add_class (element, "-x-evo-paragraph");
+ if (!is_in_html_mode (selection)) {
+ style = g_strdup_printf (
+ "width: %dch; word-wrap: normal; %s %s",
+ (word_wrap_length + offset), align_value, style_to_add);
+ } else {
+ if (*align_value || *style_to_add)
+ style = g_strdup_printf (
+ "%s %s", align_value, style_to_add);
+ }
+ if (style) {
+ webkit_dom_element_set_attribute (element, "style", style, NULL);
+ g_free (style);
+ }
+}
+
+WebKitDOMElement *
+e_html_editor_selection_get_paragraph_element (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document,
+ gint width,
+ gint offset)
+{
+ WebKitDOMElement *element;
+
+ element = webkit_dom_document_create_element (document, "DIV", NULL);
+ e_html_editor_selection_set_paragraph_style (selection, element, width, offset, "");
+
+ return element;
+}
+
+WebKitDOMElement *
+e_html_editor_selection_put_node_into_paragraph (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document,
+ WebKitDOMNode *node,
+ WebKitDOMNode *caret_position)
+{
+ WebKitDOMRange *range;
+ WebKitDOMElement *container;
+
+ range = webkit_dom_document_create_range (document);
+ container = e_html_editor_selection_get_paragraph_element (selection, document, -1, 0);
+ webkit_dom_range_select_node (range, node, NULL);
+ webkit_dom_range_surround_contents (range, WEBKIT_DOM_NODE (container), NULL);
+ /* We have to move caret position inside this container */
+ webkit_dom_node_append_child (WEBKIT_DOM_NODE (container), caret_position, NULL);
+
+ return container;
+}
+
+/**
+ * e_html_editor_selection_wrap_lines:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Wraps all lines in current selection to be 71 characters long.
+ */
+void
+e_html_editor_selection_wrap_lines (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ WebKitDOMRange *range;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *active_paragraph, *caret;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ g_object_unref (view);
+
+ caret = e_html_editor_selection_save_caret_position (selection);
+ if (g_strcmp0 (e_html_editor_selection_get_string (selection), "") == 0) {
+ WebKitDOMNode *end_container;
+ WebKitDOMNode *parent;
+ WebKitDOMNode *paragraph;
+ gchar *text_content;
+
+ /* We need to save caret position and restore it after
+ * wrapping the selection, but we need to save it before we
+ * start to modify selection */
+ range = html_editor_selection_get_current_range (selection);
+ if (!range)
+ return;
+
+ end_container = webkit_dom_range_get_common_ancestor_container (range, NULL);
+
+ /* Wrap only text surrounded in DIV and P tags */
+ parent = webkit_dom_node_get_parent_node(end_container);
+ if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) ||
+ WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (parent)) {
+ element_add_class (
+ WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph");
+ paragraph = parent;
+ } else {
+ WebKitDOMElement *parent_div =
+ e_html_editor_dom_node_find_parent_element (parent, "DIV");
+
+ if (element_has_class (parent_div, "-x-evo-paragraph")) {
+ paragraph = WEBKIT_DOM_NODE (parent_div);
+ } else {
+ if (!caret)
+ return;
+
+ /* We try to select previous sibling */
+ paragraph = webkit_dom_node_get_previous_sibling (
+ WEBKIT_DOM_NODE (caret));
+ if (paragraph) {
+ /* When there is just text without container
+ * we have to surround it with paragraph div */
+ if (WEBKIT_DOM_IS_TEXT (paragraph))
+ paragraph = WEBKIT_DOM_NODE (
+ e_html_editor_selection_put_node_into_paragraph (
+ selection, document, paragraph,
+ WEBKIT_DOM_NODE (caret)));
+ } else {
+ /* When some weird element is selected, return */
+ e_html_editor_selection_clear_caret_position_marker (selection);
+ return;
+ }
+ }
+ }
+
+ if (!paragraph)
+ return;
+
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (paragraph), "style");
+ webkit_dom_element_set_id (
+ WEBKIT_DOM_ELEMENT (paragraph), "-x-evo-active-paragraph");
+
+ text_content = webkit_dom_node_get_text_content (paragraph);
+ /* If there is hidden space character in the beginning we remove it */
+ if (strstr (text_content, UNICODE_ZERO_WIDTH_SPACE)) {
+ if (g_str_has_prefix (text_content, UNICODE_ZERO_WIDTH_SPACE)) {
+ WebKitDOMNode *node;
+
+ node = webkit_dom_node_get_first_child (paragraph);
+
+ webkit_dom_character_data_delete_data (
+ WEBKIT_DOM_CHARACTER_DATA (node),
+ 0,
+ 1,
+ NULL);
+ }
+ if (g_str_has_suffix (text_content, UNICODE_ZERO_WIDTH_SPACE)) {
+ WebKitDOMNode *node;
+
+ node = webkit_dom_node_get_last_child (paragraph);
+
+ webkit_dom_character_data_delete_data (
+ WEBKIT_DOM_CHARACTER_DATA (node),
+ g_utf8_strlen (text_content, -1) -1,
+ 1,
+ NULL);
+ }
+ }
+ g_free (text_content);
+
+ wrap_lines (
+ NULL, paragraph, document, FALSE,
+ selection->priv->word_wrap_length);
+
+ } else {
+ e_html_editor_selection_save_caret_position (selection);
+ /* If we have selection -> wrap it */
+ wrap_lines (
+ selection, NULL, document, FALSE,
+ selection->priv->word_wrap_length);
+ }
+
+ active_paragraph = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-active-paragraph");
+ /* We have to move caret on position where it was before modifying the text */
+ e_html_editor_selection_restore_caret_position (selection);
+
+ /* Set paragraph as non-active */
+ if (active_paragraph)
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (active_paragraph), "id");
+}
+
+WebKitDOMElement *
+e_html_editor_selection_wrap_paragraph_length (EHTMLEditorSelection *selection,
+ WebKitDOMElement *paragraph,
+ gint length)
+{
+ WebKitDOMDocument *document;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
+ g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL);
+ g_return_val_if_fail (length > 10, NULL);
+
+ document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (paragraph));
+
+ return wrap_lines (
+ NULL, WEBKIT_DOM_NODE (paragraph), document, FALSE, length);
+}
+
+void
+e_html_editor_selection_wrap_paragraphs_in_document (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document)
+{
+ WebKitDOMNodeList *list;
+ gint ii, length;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ list = webkit_dom_document_query_selector_all (
+ document, "div.-x-evo-paragraph:not(#-x-evo-input-start)", NULL);
+
+ length = webkit_dom_node_list_get_length (list);
+
+ for (ii = 0; ii < length; ii++) {
+ gint quote, citation_level;
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+
+ citation_level = get_citation_level (node);
+ quote = citation_level ? citation_level + 1 : 0;
+
+ if (node_is_list (node)) {
+ WebKitDOMNode *item = webkit_dom_node_get_first_child (node);
+
+ while (item && WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
+ e_html_editor_selection_wrap_paragraph_length (
+ selection,
+ WEBKIT_DOM_ELEMENT (item),
+ selection->priv->word_wrap_length - quote);
+ item = webkit_dom_node_get_next_sibling (item);
+ }
+ } else {
+ e_html_editor_selection_wrap_paragraph_length (
+ selection,
+ WEBKIT_DOM_ELEMENT (node),
+ selection->priv->word_wrap_length - quote);
+ }
+ }
+}
+
+WebKitDOMElement *
+e_html_editor_selection_wrap_paragraph (EHTMLEditorSelection *selection,
+ WebKitDOMElement *paragraph)
+{
+ gint indentation_level, citation_level, quote;
+ gint final_width, word_wrap_length, offset = 0;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_SELECTION (selection), NULL);
+ g_return_val_if_fail (WEBKIT_DOM_IS_ELEMENT (paragraph), NULL);
+
+ word_wrap_length = selection->priv->word_wrap_length;
+ indentation_level = get_indentation_level (paragraph);
+ citation_level = get_citation_level (WEBKIT_DOM_NODE (paragraph));
+
+ if (node_is_list (WEBKIT_DOM_NODE (paragraph)) ||
+ WEBKIT_DOM_IS_HTMLLI_ELEMENT (paragraph)) {
+
+ gint list_level = get_list_level (WEBKIT_DOM_NODE (paragraph));
+ indentation_level = 0;
+
+ if (list_level > 0)
+ offset = list_level * -SPACES_PER_LIST_LEVEL;
+ else
+ offset = -SPACES_PER_LIST_LEVEL;
+ }
+
+ quote = citation_level ? citation_level + 1 : 0;
+ quote *= 2;
+
+ final_width = word_wrap_length - quote + offset;
+ final_width -= SPACES_PER_INDENTATION * indentation_level;
+
+ return e_html_editor_selection_wrap_paragraph_length (
+ selection, WEBKIT_DOM_ELEMENT (paragraph), final_width);
+}
+
+/**
+ * e_html_editor_selection_save:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Saves current cursor position or current selection range. The selection can
+ * be later restored by calling e_html_editor_selection_restore().
+ *
+ * Note that calling e_html_editor_selection_save() overwrites previously saved
+ * position.
+ *
+ * Note that this method inserts special markings into the HTML code that are
+ * used to later restore the selection. It can happen that by deleting some
+ * segments of the document some of the markings are deleted too. In that case
+ * restoring the selection by e_html_editor_selection_restore() can fail. Also by
+ * moving text segments (Cut & Paste) can result in moving the markings
+ * elsewhere, thus e_html_editor_selection_restore() will restore the selection
+ * incorrectly.
+ *
+ * It is recommended to use this method only when you are not planning to make
+ * bigger changes to content or structure of the document (formatting changes
+ * are usually OK).
+ */
+void
+e_html_editor_selection_save (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ WebKitWebView *web_view;
+ WebKitDOMDocument *document;
+ WebKitDOMRange *range;
+ WebKitDOMNode *container;
+ WebKitDOMElement *marker;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ web_view = WEBKIT_WEB_VIEW (view);
+
+ document = webkit_web_view_get_dom_document (web_view);
+
+ /* First remove all markers (if present) */
+ marker = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-selection-start-marker");
+ if (marker != NULL)
+ remove_node (WEBKIT_DOM_NODE (marker));
+
+ marker = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-selection-end-marker");
+ if (marker != NULL)
+ remove_node (WEBKIT_DOM_NODE (marker));
+
+ range = html_editor_selection_get_current_range (selection);
+
+ if (range != NULL) {
+ WebKitDOMNode *marker_node;
+ WebKitDOMNode *parent_node;
+ WebKitDOMNode *split_node;
+ glong offset;
+
+ marker = webkit_dom_document_create_element (
+ document, "SPAN", NULL);
+ webkit_dom_element_set_id (
+ marker, "-x-evo-selection-start-marker");
+
+ container = webkit_dom_range_get_start_container (range, NULL);
+ offset = webkit_dom_range_get_start_offset (range, NULL);
+
+ if (WEBKIT_DOM_IS_TEXT (container)) {
+ WebKitDOMText *split_text;
+
+ split_text = webkit_dom_text_split_text (
+ WEBKIT_DOM_TEXT (container), offset, NULL);
+ split_node = WEBKIT_DOM_NODE (split_text);
+ } else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (container)) {
+ webkit_dom_node_insert_before (
+ container,
+ WEBKIT_DOM_NODE (marker),
+ webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (container)),
+ NULL);
+
+ goto end_marker;
+ } else {
+ if (!webkit_dom_node_get_previous_sibling (container)) {
+ split_node = webkit_dom_node_get_parent_node (
+ container);
+ } else if (!webkit_dom_node_get_next_sibling (container)) {
+ split_node = webkit_dom_node_get_parent_node (
+ container);
+ split_node = webkit_dom_node_get_next_sibling (
+ split_node);
+ } else
+ split_node = container;
+ }
+
+ if (!split_node) {
+ webkit_dom_node_insert_before (
+ container,
+ WEBKIT_DOM_NODE (marker),
+ webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (container)),
+ NULL);
+ } else {
+ marker_node = WEBKIT_DOM_NODE (marker);
+ parent_node = webkit_dom_node_get_parent_node (split_node);
+
+ webkit_dom_node_insert_before (
+ parent_node, marker_node, split_node, NULL);
+ }
+ end_marker:
+ marker = webkit_dom_document_create_element (
+ document, "SPAN", NULL);
+ webkit_dom_element_set_id (
+ marker, "-x-evo-selection-end-marker");
+
+ container = webkit_dom_range_get_end_container (range, NULL);
+ offset = webkit_dom_range_get_end_offset (range, NULL);
+
+ if (WEBKIT_DOM_IS_TEXT (container) && offset != 0) {
+ WebKitDOMText *split_text;
+
+ split_text = webkit_dom_text_split_text (
+ WEBKIT_DOM_TEXT (container), offset, NULL);
+ split_node = WEBKIT_DOM_NODE (split_text);
+ } else if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (container)) {
+ webkit_dom_node_append_child (
+ container, WEBKIT_DOM_NODE (marker), NULL);
+ g_object_unref (view);
+ return;
+ } else {
+ if (!webkit_dom_node_get_previous_sibling (container)) {
+ split_node = webkit_dom_node_get_parent_node (
+ container);
+ } else if (!webkit_dom_node_get_next_sibling (container)) {
+ split_node = webkit_dom_node_get_parent_node (
+ container);
+ split_node = webkit_dom_node_get_next_sibling (
+ split_node);
+ } else
+ split_node = container;
+ }
+
+ marker_node = WEBKIT_DOM_NODE (marker);
+
+ if (split_node) {
+ parent_node = webkit_dom_node_get_parent_node (split_node);
+
+ webkit_dom_node_insert_before (
+ parent_node, marker_node, split_node, NULL);
+ } else {
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (container),
+ marker_node,
+ NULL);
+ }
+ }
+
+ g_object_unref (view);
+}
+
+/**
+ * e_html_editor_selection_restore:
+ * @selection: an #EHTMLEditorSelection
+ *
+ * Restores cursor position or selection range that was saved by
+ * e_html_editor_selection_save().
+ *
+ * Note that calling this function without calling e_html_editor_selection_save()
+ * before is a programming error and the behavior is undefined.
+ */
+void
+e_html_editor_selection_restore (EHTMLEditorSelection *selection)
+{
+ EHTMLEditorView *view;
+ WebKitWebView *web_view;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *dom_selection;
+ WebKitDOMRange *range;
+ WebKitDOMElement *marker;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ web_view = WEBKIT_WEB_VIEW (view);
+
+ document = webkit_web_view_get_dom_document (web_view);
+ window = webkit_dom_document_get_default_view (document);
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+ range = webkit_dom_document_create_range (document);
+
+ if (range != NULL) {
+ marker = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-selection-start-marker");
+ if (!marker) {
+ marker = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-selection-end-marker");
+ if (marker)
+ remove_node (WEBKIT_DOM_NODE (marker));
+ return;
+ }
+
+ webkit_dom_range_set_start_after (
+ range, WEBKIT_DOM_NODE (marker), NULL);
+ remove_node (WEBKIT_DOM_NODE (marker));
+
+ marker = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-selection-end-marker");
+ if (!marker) {
+ marker = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-selection-start-marker");
+ if (marker)
+ remove_node (WEBKIT_DOM_NODE (marker));
+ return;
+ }
+
+ webkit_dom_range_set_end_before (
+ range, WEBKIT_DOM_NODE (marker), NULL);
+ remove_node (WEBKIT_DOM_NODE (marker));
+
+ webkit_dom_dom_selection_remove_all_ranges (dom_selection);
+ webkit_dom_dom_selection_add_range (dom_selection, range);
+ }
+
+ g_object_unref (view);
+}
+
+static void
+html_editor_selection_modify (EHTMLEditorSelection *selection,
+ const gchar *alter,
+ gboolean forward,
+ EHTMLEditorSelectionGranularity granularity)
+{
+ EHTMLEditorView *view;
+ WebKitWebView *web_view;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *dom_selection;
+ const gchar *granularity_str;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SELECTION (selection));
+
+ view = e_html_editor_selection_ref_html_editor_view (selection);
+ g_return_if_fail (view != NULL);
+
+ web_view = WEBKIT_WEB_VIEW (view);
+
+ document = webkit_web_view_get_dom_document (web_view);
+ window = webkit_dom_document_get_default_view (document);
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+
+ switch (granularity) {
+ case E_HTML_EDITOR_SELECTION_GRANULARITY_CHARACTER:
+ granularity_str = "character";
+ break;
+ case E_HTML_EDITOR_SELECTION_GRANULARITY_WORD:
+ granularity_str = "word";
+ break;
+ }
+
+ webkit_dom_dom_selection_modify (
+ dom_selection, alter,
+ forward ? "forward" : "backward",
+ granularity_str);
+
+ g_object_unref (view);
+}
+
+/**
+ * e_html_editor_selection_extend:
+ * @selection: an #EHTMLEditorSelection
+ * @forward: whether to extend selection forward or backward
+ * @granularity: granularity of the extension
+ *
+ * Extends current selection in given direction by given granularity.
+ */
+void
+e_html_editor_selection_extend (EHTMLEditorSelection *selection,
+ gboolean forward,
+ EHTMLEditorSelectionGranularity granularity)
+{
+ html_editor_selection_modify (selection, "extend", forward, granularity);
+}
+
+/**
+ * e_html_editor_selection_move:
+ * @selection: an #EHTMLEditorSelection
+ * @forward: whether to move the selection forward or backward
+ * @granularity: granularity of the movement
+ *
+ * Moves current selection in given direction by given granularity
+ */
+void
+e_html_editor_selection_move (EHTMLEditorSelection *selection,
+ gboolean forward,
+ EHTMLEditorSelectionGranularity granularity)
+{
+ html_editor_selection_modify (selection, "move", forward, granularity);
+}
+
+void
+e_html_editor_selection_scroll_to_caret (EHTMLEditorSelection *selection)
+{
+ WebKitDOMElement *caret;
+
+ caret = e_html_editor_selection_save_caret_position (selection);
+
+ webkit_dom_element_scroll_into_view (caret, TRUE);
+
+ e_html_editor_selection_clear_caret_position_marker (selection);
+}
diff --git a/e-util/e-html-editor-selection.h b/e-util/e-html-editor-selection.h
new file mode 100644
index 0000000000..c0d2d1817f
--- /dev/null
+++ b/e-util/e-html-editor-selection.h
@@ -0,0 +1,250 @@
+/*
+ * e-html-editor-selection.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_SELECTION_H
+#define E_HTML_EDITOR_SELECTION_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-util-enums.h>
+#include <webkit/webkit.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_SELECTION \
+ (e_html_editor_selection_get_type ())
+#define E_HTML_EDITOR_SELECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_SELECTION, EHTMLEditorSelection))
+#define E_HTML_EDITOR_SELECTION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_SELECTION, EHTMLEditorSelectionClass))
+#define E_IS_HTML_EDITOR_SELECTION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_SELECTION))
+#define E_IS_HTML_EDITOR_SELECTION_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_SELECTION))
+#define E_HTML_EDITOR_SELECTION_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_SELECTION, EHTMLEditorSelectionClass))
+
+G_BEGIN_DECLS
+
+struct _EHTMLEditorView;
+
+typedef struct _EHTMLEditorSelection EHTMLEditorSelection;
+typedef struct _EHTMLEditorSelectionClass EHTMLEditorSelectionClass;
+typedef struct _EHTMLEditorSelectionPrivate EHTMLEditorSelectionPrivate;
+
+struct _EHTMLEditorSelection {
+ GObject parent;
+ EHTMLEditorSelectionPrivate *priv;
+};
+
+struct _EHTMLEditorSelectionClass {
+ GObjectClass parent_class;
+};
+
+GType e_html_editor_selection_get_type
+ (void) G_GNUC_CONST;
+struct _EHTMLEditorView *
+ e_html_editor_selection_ref_html_editor_view
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_block_selection_changed
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_unblock_selection_changed
+ (EHTMLEditorSelection *selection);
+gint e_html_editor_selection_get_word_wrap_length
+ (EHTMLEditorSelection *selection);
+gboolean e_html_editor_selection_has_text
+ (EHTMLEditorSelection *selection);
+gchar * e_html_editor_selection_get_caret_word
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_replace_caret_word
+ (EHTMLEditorSelection *selection,
+ const gchar *replacement);
+EHTMLEditorSelectionAlignment
+ e_html_editor_selection_get_alignment
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_alignment
+ (EHTMLEditorSelection *selection,
+ EHTMLEditorSelectionAlignment alignment);
+const gchar * e_html_editor_selection_get_background_color
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_background_color
+ (EHTMLEditorSelection *selection,
+ const gchar *color);
+void e_html_editor_selection_get_font_color
+ (EHTMLEditorSelection *selection,
+ GdkRGBA *rgba);
+void e_html_editor_selection_set_font_color
+ (EHTMLEditorSelection *selection,
+ const GdkRGBA *rgba);
+const gchar * e_html_editor_selection_get_font_name
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_font_name
+ (EHTMLEditorSelection *selection,
+ const gchar *font_name);
+guint e_html_editor_selection_get_font_size
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_font_size
+ (EHTMLEditorSelection *selection,
+ guint font_size);
+EHTMLEditorSelectionBlockFormat
+ e_html_editor_selection_get_block_format
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_block_format
+ (EHTMLEditorSelection *selection,
+ EHTMLEditorSelectionBlockFormat format);
+gboolean e_html_editor_selection_is_citation
+ (EHTMLEditorSelection *selection);
+gboolean e_html_editor_selection_is_indented
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_indent (EHTMLEditorSelection *selection);
+void e_html_editor_selection_unindent
+ (EHTMLEditorSelection *selection);
+gboolean e_html_editor_selection_is_bold (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_bold
+ (EHTMLEditorSelection *selection,
+ gboolean bold);
+gboolean e_html_editor_selection_is_italic
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_italic
+ (EHTMLEditorSelection *selection,
+ gboolean italic);
+gboolean e_html_editor_selection_is_monospaced
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_monospaced
+ (EHTMLEditorSelection *selection,
+ gboolean monospaced);
+gboolean e_html_editor_selection_is_strikethrough
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_strikethrough
+ (EHTMLEditorSelection *selection,
+ gboolean strikethrough);
+gboolean e_html_editor_selection_is_superscript
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_superscript
+ (EHTMLEditorSelection *selection,
+ gboolean superscript);
+gboolean e_html_editor_selection_is_subscript
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_subscript
+ (EHTMLEditorSelection *selection,
+ gboolean subscript);
+gboolean e_html_editor_selection_is_underline
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_underline
+ (EHTMLEditorSelection *selection,
+ gboolean underline);
+void e_html_editor_selection_unlink (EHTMLEditorSelection *selection);
+void e_html_editor_selection_create_link
+ (EHTMLEditorSelection *selection,
+ const gchar *uri);
+const gchar * e_html_editor_selection_get_string
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_replace (EHTMLEditorSelection *selection,
+ const gchar *new_string);
+void e_html_editor_selection_insert_html
+ (EHTMLEditorSelection *selection,
+ const gchar *html_text);
+void e_html_editor_selection_replace_image_src
+ (EHTMLEditorSelection *selection,
+ WebKitDOMElement *image,
+ const gchar *image_uri);
+void e_html_editor_selection_insert_image
+ (EHTMLEditorSelection *selection,
+ const gchar *image_uri);
+void e_html_editor_selection_insert_text
+ (EHTMLEditorSelection *selection,
+ const gchar *plain_text);
+void e_html_editor_selection_clear_caret_position_marker
+ (EHTMLEditorSelection *selection);
+WebKitDOMNode *
+ e_html_editor_selection_get_caret_position_node
+ (WebKitDOMDocument *document);
+WebKitDOMElement *
+ e_html_editor_selection_save_caret_position
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_restore_caret_position
+ (EHTMLEditorSelection *selection);
+void e_html_editor_selection_set_indented_style
+ (EHTMLEditorSelection *selection,
+ WebKitDOMElement *element,
+ gint width);
+WebKitDOMElement *
+ e_html_editor_selection_get_indented_element
+ (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document,
+ gint width);
+void e_html_editor_selection_set_paragraph_style
+ (EHTMLEditorSelection *selection,
+ WebKitDOMElement *element,
+ gint width,
+ gint offset,
+ const gchar *style_to_add);
+WebKitDOMElement *
+ e_html_editor_selection_get_paragraph_element
+ (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document,
+ gint width,
+ gint offset);
+WebKitDOMElement *
+ e_html_editor_selection_put_node_into_paragraph
+ (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document,
+ WebKitDOMNode *node,
+ WebKitDOMNode *caret_position);
+void e_html_editor_selection_wrap_lines
+ (EHTMLEditorSelection *selection);
+WebKitDOMElement *
+ e_html_editor_selection_wrap_paragraph_length
+ (EHTMLEditorSelection *selection,
+ WebKitDOMElement *paragraph,
+ gint length);
+void e_html_editor_selection_wrap_paragraphs_in_document
+ (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document);
+WebKitDOMElement *
+ e_html_editor_selection_wrap_paragraph
+ (EHTMLEditorSelection *selection,
+ WebKitDOMElement *paragraph);
+void e_html_editor_selection_save (EHTMLEditorSelection *selection);
+void e_html_editor_selection_restore (EHTMLEditorSelection *selection);
+void e_html_editor_selection_move (EHTMLEditorSelection *selection,
+ gboolean forward,
+ EHTMLEditorSelectionGranularity granularity);
+void e_html_editor_selection_extend (EHTMLEditorSelection *selection,
+ gboolean forward,
+ EHTMLEditorSelectionGranularity granularity);
+void e_html_editor_selection_scroll_to_caret
+ (EHTMLEditorSelection *selection);
+EHTMLEditorSelectionBlockFormat
+ e_html_editor_selection_get_list_format_from_node
+ (WebKitDOMNode *node);
+EHTMLEditorSelectionAlignment
+ e_html_editor_selection_get_list_alignment_from_node
+ (WebKitDOMNode *node);
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_SELECTION_H */
diff --git a/e-util/e-html-editor-spell-check-dialog.c b/e-util/e-html-editor-spell-check-dialog.c
new file mode 100644
index 0000000000..b399dfabd8
--- /dev/null
+++ b/e-util/e-html-editor-spell-check-dialog.c
@@ -0,0 +1,710 @@
+/*
+ * e-html-editor-spell-dialog.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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-spell-check-dialog.h"
+
+#include <glib/gi18n-lib.h>
+#include <enchant/enchant.h>
+
+#include "e-html-editor-view.h"
+#include "e-spell-checker.h"
+#include "e-spell-dictionary.h"
+
+#define E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, EHTMLEditorSpellCheckDialogPrivate))
+
+struct _EHTMLEditorSpellCheckDialogPrivate {
+ GtkWidget *add_word_button;
+ GtkWidget *back_button;
+ GtkWidget *dictionary_combo;
+ GtkWidget *ignore_button;
+ GtkWidget *replace_button;
+ GtkWidget *replace_all_button;
+ GtkWidget *skip_button;
+ GtkWidget *suggestion_label;
+ GtkWidget *tree_view;
+
+ WebKitDOMDOMSelection *selection;
+
+ gchar *word;
+ ESpellDictionary *current_dict;
+};
+
+enum {
+ COLUMN_NAME,
+ COLUMN_DICTIONARY,
+ NUM_COLUMNS
+};
+
+G_DEFINE_TYPE (
+ EHTMLEditorSpellCheckDialog,
+ e_html_editor_spell_check_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG)
+
+static void
+html_editor_spell_check_dialog_set_word (EHTMLEditorSpellCheckDialog *dialog,
+ const gchar *word)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ GtkTreeView *tree_view;
+ GtkListStore *store;
+ gchar *markup;
+ GList *list, *link;
+
+ if (word == NULL)
+ return;
+
+ if (dialog->priv->word != word) {
+ g_free (dialog->priv->word);
+ dialog->priv->word = g_strdup (word);
+ }
+
+ markup = g_strdup_printf (_("<b>Suggestions for '%s'</b>"), word);
+ gtk_label_set_markup (
+ GTK_LABEL (dialog->priv->suggestion_label), markup);
+ g_free (markup);
+
+ tree_view = GTK_TREE_VIEW (dialog->priv->tree_view);
+ store = GTK_LIST_STORE (gtk_tree_view_get_model (tree_view));
+ gtk_list_store_clear (store);
+
+ list = e_spell_dictionary_get_suggestions (
+ dialog->priv->current_dict, word, -1);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ GtkTreeIter iter;
+ gchar *suggestion = link->data;
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, 0, suggestion, -1);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) g_free);
+
+ /* We give focus to WebKit so that the currently selected word
+ * is highlited. Without focus selection is not visible (at
+ * least with my default color scheme). The focus in fact is not
+ * given to WebKit, because this dialog is modal, but it satisfies
+ * it in a way that it paints the selection :) */
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static gboolean
+select_next_word (EHTMLEditorSpellCheckDialog *dialog)
+{
+ WebKitDOMNode *anchor, *focus;
+ gulong anchor_offset, focus_offset;
+
+ anchor = webkit_dom_dom_selection_get_anchor_node (dialog->priv->selection);
+ anchor_offset = webkit_dom_dom_selection_get_anchor_offset (dialog->priv->selection);
+
+ focus = webkit_dom_dom_selection_get_focus_node (dialog->priv->selection);
+ focus_offset = webkit_dom_dom_selection_get_focus_offset (dialog->priv->selection);
+
+ /* Jump _behind_ next word */
+ webkit_dom_dom_selection_modify (
+ dialog->priv->selection, "move", "forward", "word");
+ /* Jump before the word */
+ webkit_dom_dom_selection_modify (
+ dialog->priv->selection, "move", "backward", "word");
+ /* Select it */
+ webkit_dom_dom_selection_modify (
+ dialog->priv->selection, "extend", "forward", "word");
+
+ /* If the selection didn't change, then we have most probably
+ * reached the end of document - return FALSE */
+ return !((anchor == webkit_dom_dom_selection_get_anchor_node (
+ dialog->priv->selection)) &&
+ (anchor_offset == webkit_dom_dom_selection_get_anchor_offset (
+ dialog->priv->selection)) &&
+ (focus == webkit_dom_dom_selection_get_focus_node (
+ dialog->priv->selection)) &&
+ (focus_offset == webkit_dom_dom_selection_get_focus_offset (
+ dialog->priv->selection)));
+}
+
+static gboolean
+html_editor_spell_check_dialog_next (EHTMLEditorSpellCheckDialog *dialog)
+{
+ WebKitDOMNode *start = NULL, *end = NULL;
+ gulong start_offset, end_offset;
+
+ if (dialog->priv->word == NULL) {
+ webkit_dom_dom_selection_modify (
+ dialog->priv->selection, "move", "left", "documentboundary");
+ } else {
+ /* Remember last selected word */
+ start = webkit_dom_dom_selection_get_anchor_node (
+ dialog->priv->selection);
+ end = webkit_dom_dom_selection_get_focus_node (
+ dialog->priv->selection);
+ start_offset = webkit_dom_dom_selection_get_anchor_offset (
+ dialog->priv->selection);
+ end_offset = webkit_dom_dom_selection_get_focus_offset (
+ dialog->priv->selection);
+ }
+
+ while (select_next_word (dialog)) {
+ WebKitDOMRange *range;
+ WebKitSpellChecker *checker;
+ gint loc, len;
+ gchar *word;
+
+ range = webkit_dom_dom_selection_get_range_at (
+ dialog->priv->selection, 0, NULL);
+ word = webkit_dom_range_get_text (range);
+
+ checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
+ webkit_spell_checker_check_spelling_of_string (
+ checker, word, &loc, &len);
+
+ /* Found misspelled word! */
+ if (loc != -1) {
+ html_editor_spell_check_dialog_set_word (dialog, word);
+ g_free (word);
+ return TRUE;
+ }
+
+ g_free (word);
+ }
+
+ /* Restore the selection to contain the last misspelled word. This is
+ * reached only when we reach the end of the document */
+ if (start && end) {
+ webkit_dom_dom_selection_set_base_and_extent (
+ dialog->priv->selection, start, start_offset,
+ end, end_offset, NULL);
+ }
+
+ /* Close the dialog */
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ return FALSE;
+}
+
+static gboolean
+select_previous_word (EHTMLEditorSpellCheckDialog *dialog)
+{
+ WebKitDOMNode *old_anchor_node;
+ WebKitDOMNode *new_anchor_node;
+ gulong old_anchor_offset;
+ gulong new_anchor_offset;
+
+ old_anchor_node = webkit_dom_dom_selection_get_anchor_node (
+ dialog->priv->selection);
+ old_anchor_offset = webkit_dom_dom_selection_get_anchor_offset (
+ dialog->priv->selection);
+
+ /* Jump on the beginning of current word */
+ webkit_dom_dom_selection_modify (
+ dialog->priv->selection, "move", "backward", "word");
+ /* Jump before previous word */
+ webkit_dom_dom_selection_modify (
+ dialog->priv->selection, "move", "backward", "word");
+ /* Select it */
+ webkit_dom_dom_selection_modify (
+ dialog->priv->selection, "extend", "forward", "word");
+
+ /* If the selection start didn't change, then we have most probably
+ * reached the beginnig of document. Return FALSE */
+
+ new_anchor_node = webkit_dom_dom_selection_get_anchor_node (
+ dialog->priv->selection);
+ new_anchor_offset = webkit_dom_dom_selection_get_anchor_offset (
+ dialog->priv->selection);
+
+ return (new_anchor_node != old_anchor_node) ||
+ (new_anchor_offset != old_anchor_offset);
+}
+
+static gboolean
+html_editor_spell_check_dialog_prev (EHTMLEditorSpellCheckDialog *dialog)
+{
+ WebKitDOMNode *start = NULL, *end = NULL;
+ gulong start_offset, end_offset;
+
+ if (dialog->priv->word == NULL) {
+ webkit_dom_dom_selection_modify (
+ dialog->priv->selection,
+ "move", "right", "documentboundary");
+ webkit_dom_dom_selection_modify (
+ dialog->priv->selection,
+ "extend", "backward", "word");
+ } else {
+ /* Remember last selected word */
+ start = webkit_dom_dom_selection_get_anchor_node (
+ dialog->priv->selection);
+ end = webkit_dom_dom_selection_get_focus_node (
+ dialog->priv->selection);
+ start_offset = webkit_dom_dom_selection_get_anchor_offset (
+ dialog->priv->selection);
+ end_offset = webkit_dom_dom_selection_get_focus_offset (
+ dialog->priv->selection);
+ }
+
+ while (select_previous_word (dialog)) {
+ WebKitDOMRange *range;
+ WebKitSpellChecker *checker;
+ gint loc, len;
+ gchar *word;
+
+ range = webkit_dom_dom_selection_get_range_at (
+ dialog->priv->selection, 0, NULL);
+ word = webkit_dom_range_get_text (range);
+
+ checker = WEBKIT_SPELL_CHECKER (webkit_get_text_checker ());
+ webkit_spell_checker_check_spelling_of_string (
+ checker, word, &loc, &len);
+
+ /* Found misspelled word! */
+ if (loc != -1) {
+ html_editor_spell_check_dialog_set_word (dialog, word);
+ g_free (word);
+ return TRUE;
+ }
+
+ g_free (word);
+ }
+
+ /* Restore the selection to contain the last misspelled word. This is
+ * reached only when we reach the beginning of the document */
+ if (start && end) {
+ webkit_dom_dom_selection_set_base_and_extent (
+ dialog->priv->selection, start, start_offset,
+ end, end_offset, NULL);
+ }
+
+ /* Close the dialog */
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ return FALSE;
+}
+
+static void
+html_editor_spell_check_dialog_replace (EHTMLEditorSpellCheckDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *editor_selection;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ gchar *replacement;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ editor_selection = e_html_editor_view_get_selection (view);
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (dialog->priv->tree_view));
+ gtk_tree_selection_get_selected (selection, &model, &iter);
+ gtk_tree_model_get (model, &iter, 0, &replacement, -1);
+
+ e_html_editor_selection_insert_html (
+ editor_selection, replacement);
+
+ g_free (replacement);
+ html_editor_spell_check_dialog_next (dialog);
+}
+
+static void
+html_editor_spell_check_dialog_replace_all (EHTMLEditorSpellCheckDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *editor_selection;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ gchar *replacement;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ editor_selection = e_html_editor_view_get_selection (view);
+
+ selection = gtk_tree_view_get_selection (
+ GTK_TREE_VIEW (dialog->priv->tree_view));
+ gtk_tree_selection_get_selected (selection, &model, &iter);
+ gtk_tree_model_get (model, &iter, 0, &replacement, -1);
+
+ /* Repeatedly search for 'word', then replace selection by
+ * 'replacement'. Repeat until there's at least one occurence of
+ * 'word' in the document */
+ while (webkit_web_view_search_text (
+ WEBKIT_WEB_VIEW (view), dialog->priv->word,
+ FALSE, TRUE, TRUE)) {
+
+ e_html_editor_selection_insert_html (
+ editor_selection, replacement);
+ }
+
+ g_free (replacement);
+ html_editor_spell_check_dialog_next (dialog);
+}
+
+static void
+html_editor_spell_check_dialog_ignore (EHTMLEditorSpellCheckDialog *dialog)
+{
+ if (dialog->priv->word == NULL)
+ return;
+
+ e_spell_dictionary_ignore_word (
+ dialog->priv->current_dict, dialog->priv->word, -1);
+
+ html_editor_spell_check_dialog_next (dialog);
+}
+
+static void
+html_editor_spell_check_dialog_learn (EHTMLEditorSpellCheckDialog *dialog)
+{
+ if (dialog->priv->word == NULL)
+ return;
+
+ e_spell_dictionary_learn_word (
+ dialog->priv->current_dict, dialog->priv->word, -1);
+
+ html_editor_spell_check_dialog_next (dialog);
+}
+
+static void
+html_editor_spell_check_dialog_set_dictionary (EHTMLEditorSpellCheckDialog *dialog)
+{
+ GtkComboBox *combo_box;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ ESpellDictionary *dictionary;
+
+ combo_box = GTK_COMBO_BOX (dialog->priv->dictionary_combo);
+ gtk_combo_box_get_active_iter (combo_box, &iter);
+ model = gtk_combo_box_get_model (combo_box);
+
+ gtk_tree_model_get (model, &iter, 1, &dictionary, -1);
+
+ dialog->priv->current_dict = dictionary;
+
+ /* Update suggestions */
+ html_editor_spell_check_dialog_set_word (dialog, dialog->priv->word);
+}
+
+static void
+html_editor_spell_check_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSpellCheckDialog *dialog;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+
+ dialog = E_HTML_EDITOR_SPELL_CHECK_DIALOG (widget);
+
+ g_free (dialog->priv->word);
+ dialog->priv->word = NULL;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ dialog->priv->selection = webkit_dom_dom_window_get_selection (window);
+
+ /* Select the first word or quit */
+ if (html_editor_spell_check_dialog_next (dialog)) {
+ GTK_WIDGET_CLASS (e_html_editor_spell_check_dialog_parent_class)->
+ show (widget);
+ }
+}
+
+static void
+html_editor_spell_check_dialog_finalize (GObject *object)
+{
+ EHTMLEditorSpellCheckDialogPrivate *priv;
+
+ priv = E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_PRIVATE (object);
+
+ g_free (priv->word);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_html_editor_spell_check_dialog_parent_class)->
+ finalize (object);
+}
+
+static void
+html_editor_spell_check_dialog_constructed (GObject *object)
+{
+ EHTMLEditorSpellCheckDialog *dialog;
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_html_editor_spell_check_dialog_parent_class)->
+ constructed (object);
+
+ dialog = E_HTML_EDITOR_SPELL_CHECK_DIALOG (object);
+ e_html_editor_spell_check_dialog_update_dictionaries (dialog);
+}
+
+static void
+e_html_editor_spell_check_dialog_class_init (EHTMLEditorSpellCheckDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (EHTMLEditorSpellCheckDialogPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = html_editor_spell_check_dialog_finalize;
+ object_class->constructed = html_editor_spell_check_dialog_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_spell_check_dialog_show;
+}
+
+static void
+e_html_editor_spell_check_dialog_init (EHTMLEditorSpellCheckDialog *dialog)
+{
+ GtkWidget *widget;
+ GtkGrid *main_layout;
+ GtkListStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+
+ dialog->priv = E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ /* == Suggestions == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Suggestions</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 0, 2, 1);
+ dialog->priv->suggestion_label = widget;
+
+ /* Tree view */
+ widget = gtk_tree_view_new ();
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (widget), FALSE);
+ gtk_widget_set_vexpand (widget, TRUE);
+ gtk_widget_set_hexpand (widget, TRUE);
+ dialog->priv->tree_view = widget;
+
+ /* Column */
+ column = gtk_tree_view_column_new_with_attributes (
+ "", gtk_cell_renderer_text_new (), "text", 0, NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (widget), column);
+
+ /* Store */
+ store = gtk_list_store_new (1, G_TYPE_STRING);
+ gtk_tree_view_set_model (
+ GTK_TREE_VIEW (widget), GTK_TREE_MODEL (store));
+
+ /* Scrolled Window */
+ widget = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_size_request (widget, 150, -1);
+ 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_ETCHED_IN);
+ gtk_container_add (GTK_CONTAINER (widget), dialog->priv->tree_view);
+ gtk_grid_attach (main_layout, widget, 0, 1, 1, 5);
+
+ /* Replace */
+ widget = gtk_button_new_with_mnemonic (_("Replace"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget),
+ gtk_image_new_from_stock (
+ GTK_STOCK_CONVERT, GTK_ICON_SIZE_BUTTON));
+ gtk_grid_attach (main_layout, widget, 1, 1, 1, 1);
+ dialog->priv->replace_button = widget;
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_spell_check_dialog_replace), dialog);
+
+ /* Replace All */
+ widget = gtk_button_new_with_mnemonic (_("Replace All"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget),
+ gtk_image_new_from_stock (
+ GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON));
+ gtk_grid_attach (main_layout, widget, 1, 2, 1, 1);
+ dialog->priv->replace_all_button = widget;
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_spell_check_dialog_replace_all), dialog);
+
+ /* Ignore */
+ widget = gtk_button_new_with_mnemonic (_("Ignore"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget),
+ gtk_image_new_from_stock (
+ GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON));
+ gtk_grid_attach (main_layout, widget, 1, 3, 1, 1);
+ dialog->priv->ignore_button = widget;
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_spell_check_dialog_ignore), dialog);
+
+ /* Skip */
+ widget = gtk_button_new_with_mnemonic (_("Skip"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget),
+ gtk_image_new_from_stock (
+ GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_BUTTON));
+ gtk_grid_attach (main_layout, widget, 1, 4, 1, 1);
+ dialog->priv->skip_button = widget;
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_spell_check_dialog_next), dialog);
+
+ /* Back */
+ widget = gtk_button_new_with_mnemonic (_("Back"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget),
+ gtk_image_new_from_stock (
+ GTK_STOCK_GO_BACK, GTK_ICON_SIZE_BUTTON));
+ gtk_grid_attach (main_layout, widget, 1, 5, 1, 1);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_spell_check_dialog_prev), dialog);
+
+ /* Dictionary label */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Dictionary</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0);
+ gtk_grid_attach (main_layout, widget, 0, 6, 2, 1);
+
+ /* Dictionaries combo */
+ widget = gtk_combo_box_new ();
+ gtk_grid_attach (main_layout, widget, 0, 7, 1, 1);
+ dialog->priv->dictionary_combo = widget;
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (
+ GTK_CELL_LAYOUT (widget), renderer, TRUE);
+ gtk_cell_layout_add_attribute (
+ GTK_CELL_LAYOUT (widget), renderer, "text", 0);
+
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_spell_check_dialog_set_dictionary), dialog);
+
+ /* Add Word button */
+ widget = gtk_button_new_with_mnemonic (_("Add Word"));
+ gtk_button_set_image (
+ GTK_BUTTON (widget),
+ gtk_image_new_from_stock (
+ GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON));
+ gtk_grid_attach (main_layout, widget, 1, 7, 1, 1);
+ dialog->priv->add_word_button = widget;
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (html_editor_spell_check_dialog_learn), dialog);
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_spell_check_dialog_new (EHTMLEditor *editor)
+{
+ return g_object_new (
+ E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG,
+ "editor", editor,
+ "title", N_("Spell Checking"),
+ NULL);
+}
+
+void
+e_html_editor_spell_check_dialog_update_dictionaries (EHTMLEditorSpellCheckDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ ESpellChecker *spell_checker;
+ GtkComboBox *combo_box;
+ GtkListStore *store;
+ GQueue queue = G_QUEUE_INIT;
+ gchar **languages;
+ guint n_languages = 0;
+ guint ii;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG (dialog));
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ 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, &n_languages);
+ for (ii = 0; ii < n_languages; ii++) {
+ ESpellDictionary *dictionary;
+
+ dictionary = e_spell_checker_ref_dictionary (
+ spell_checker, languages[ii]);
+ if (dictionary != NULL)
+ g_queue_push_tail (&queue, dictionary);
+ else
+ g_warning (
+ "%s: No '%s' dictionary found",
+ G_STRFUNC, languages[ii]);
+ }
+ g_strfreev (languages);
+
+ /* Populate a list store for the combo box. */
+ store = gtk_list_store_new (
+ NUM_COLUMNS,
+ G_TYPE_STRING, /* COLUMN_NAME */
+ E_TYPE_SPELL_DICTIONARY); /* COLUMN_DICTIONARY */
+
+ while (!g_queue_is_empty (&queue)) {
+ ESpellDictionary *dictionary;
+ GtkTreeIter iter;
+ const gchar *name;
+
+ dictionary = g_queue_pop_head (&queue);
+ name = e_spell_dictionary_get_name (dictionary);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (
+ store, &iter,
+ COLUMN_NAME, name,
+ COLUMN_DICTIONARY, dictionary,
+ -1);
+
+ g_object_unref (dictionary);
+ }
+
+ /* FIXME Try to restore selection. */
+ combo_box = GTK_COMBO_BOX (dialog->priv->dictionary_combo);
+ gtk_combo_box_set_model (combo_box, GTK_TREE_MODEL (store));
+ gtk_combo_box_set_active (combo_box, 0);
+
+ g_object_unref (store);
+}
+
diff --git a/e-util/e-html-editor-spell-check-dialog.h b/e-util/e-html-editor-spell-check-dialog.h
new file mode 100644
index 0000000000..4191cd98ad
--- /dev/null
+++ b/e-util/e-html-editor-spell-check-dialog.h
@@ -0,0 +1,73 @@
+/*
+ * e-html-editor-spell-check-dialog.h
+ *
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_SPELL_CHECK_DIALOG_H
+#define E_HTML_EDITOR_SPELL_CHECK_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG \
+ (e_html_editor_spell_check_dialog_get_type ())
+#define E_HTML_EDITOR_SPELL_CHECK_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, EHTMLEditorSpellCheckDialog))
+#define E_HTML_EDITOR_SPELL_CHECK_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, EHTMLEditorSpellCheckDialogClass))
+#define E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG))
+#define E_IS_HTML_EDITOR_SPELL_CHECK_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG))
+#define E_HTML_EDITOR_SPELL_CHECK_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_SPELL_CHECK_DIALOG, EHTMLEditorSpellCheckDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorSpellCheckDialog EHTMLEditorSpellCheckDialog;
+typedef struct _EHTMLEditorSpellCheckDialogClass EHTMLEditorSpellCheckDialogClass;
+typedef struct _EHTMLEditorSpellCheckDialogPrivate EHTMLEditorSpellCheckDialogPrivate;
+
+struct _EHTMLEditorSpellCheckDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorSpellCheckDialogPrivate *priv;
+};
+
+struct _EHTMLEditorSpellCheckDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_spell_check_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_spell_check_dialog_new
+ (EHTMLEditor *editor);
+void e_html_editor_spell_check_dialog_update_dictionaries
+ (EHTMLEditorSpellCheckDialog *dialog);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_SPELL_CHECK_DIALOG_H */
diff --git a/e-util/e-html-editor-table-dialog.c b/e-util/e-html-editor-table-dialog.c
new file mode 100644
index 0000000000..467d2a62e8
--- /dev/null
+++ b/e-util/e-html-editor-table-dialog.c
@@ -0,0 +1,866 @@
+/*
+ * e-html-editor-table-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-table-dialog.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "e-color-combo.h"
+#include "e-html-editor-utils.h"
+#include "e-image-chooser-dialog.h"
+#include "e-misc-utils.h"
+
+#define E_HTML_EDITOR_TABLE_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_TABLE_DIALOG, EHTMLEditorTableDialogPrivate))
+
+struct _EHTMLEditorTableDialogPrivate {
+ GtkWidget *rows_edit;
+ GtkWidget *columns_edit;
+
+ GtkWidget *width_edit;
+ GtkWidget *width_units;
+ GtkWidget *width_check;
+
+ GtkWidget *spacing_edit;
+ GtkWidget *padding_edit;
+ GtkWidget *border_edit;
+
+ GtkWidget *alignment_combo;
+
+ GtkWidget *background_color_button;
+ GtkWidget *background_image_button;
+ GtkWidget *image_chooser_dialog;
+
+ WebKitDOMHTMLTableElement *table_element;
+};
+
+static GdkRGBA white = { 1, 1, 1, 1 };
+
+G_DEFINE_TYPE (
+ EHTMLEditorTableDialog,
+ e_html_editor_table_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+static WebKitDOMElement *
+html_editor_table_dialog_create_table (EHTMLEditorTableDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorSelection *editor_selection;
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *table, *br, *caret, *parent, *element;
+ gint i;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ editor_selection = e_html_editor_view_get_selection (view);
+
+ document = webkit_web_view_get_dom_document (
+ WEBKIT_WEB_VIEW (view));
+
+ /* Default 3x3 table */
+ table = webkit_dom_document_create_element (document, "TABLE", NULL);
+ for (i = 0; i < 3; i++) {
+ WebKitDOMHTMLElement *row;
+ gint j;
+
+ row = webkit_dom_html_table_element_insert_row (
+ WEBKIT_DOM_HTML_TABLE_ELEMENT (table), -1, NULL);
+
+ for (j = 0; j < 3; j++) {
+ webkit_dom_html_table_row_element_insert_cell (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row), -1, NULL);
+ }
+ }
+
+ caret = e_html_editor_selection_save_caret_position (editor_selection);
+
+ parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (caret));
+ element = caret;
+
+ while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
+ element = parent;
+ parent = webkit_dom_node_get_parent_element (
+ WEBKIT_DOM_NODE (parent));
+ }
+
+ br = webkit_dom_document_create_element (document, "BR", NULL);
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (parent),
+ WEBKIT_DOM_NODE (br),
+ webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)),
+ NULL);
+
+ /* Insert the table into body below the caret */
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (parent),
+ WEBKIT_DOM_NODE (table),
+ webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)),
+ NULL);
+
+ e_html_editor_selection_clear_caret_position_marker (editor_selection);
+
+ e_html_editor_view_set_changed (view, TRUE);
+
+ return table;
+}
+
+static void
+html_editor_table_dialog_set_row_count (EHTMLEditorTableDialog *dialog)
+{
+ WebKitDOMHTMLCollection *rows;
+ gulong ii, current_count, expected_count;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ rows = webkit_dom_html_table_element_get_rows (dialog->priv->table_element);
+ current_count = webkit_dom_html_collection_get_length (rows);
+ expected_count = gtk_spin_button_get_value (
+ GTK_SPIN_BUTTON (dialog->priv->rows_edit));
+
+ if (current_count < expected_count) {
+ for (ii = 0; ii < expected_count - current_count; ii++) {
+ webkit_dom_html_table_element_insert_row (
+ dialog->priv->table_element, -1, NULL);
+ }
+ } else if (current_count > expected_count) {
+ for (ii = 0; ii < current_count - expected_count; ii++) {
+ webkit_dom_html_table_element_delete_row (
+ dialog->priv->table_element, -1, NULL);
+ }
+ }
+}
+
+static void
+html_editor_table_dialog_get_row_count (EHTMLEditorTableDialog *dialog)
+{
+ WebKitDOMHTMLCollection *rows;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ rows = webkit_dom_html_table_element_get_rows (dialog->priv->table_element);
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->rows_edit),
+ webkit_dom_html_collection_get_length (rows));
+}
+
+static void
+html_editor_table_dialog_set_column_count (EHTMLEditorTableDialog *dialog)
+{
+ WebKitDOMHTMLCollection *rows;
+ gulong ii, row_count, expected_columns;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ rows = webkit_dom_html_table_element_get_rows (dialog->priv->table_element);
+ row_count = webkit_dom_html_collection_get_length (rows);
+ expected_columns = gtk_spin_button_get_value (
+ GTK_SPIN_BUTTON (dialog->priv->columns_edit));
+
+ for (ii = 0; ii < row_count; ii++) {
+ WebKitDOMHTMLTableRowElement *row;
+ WebKitDOMHTMLCollection *cells;
+ gulong jj, current_columns;
+
+ row = WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (
+ webkit_dom_html_collection_item (rows, ii));
+
+ cells = webkit_dom_html_table_row_element_get_cells (row);
+ current_columns = webkit_dom_html_collection_get_length (cells);
+
+ if (current_columns < expected_columns) {
+ for (jj = 0; jj < expected_columns - current_columns; jj++) {
+ webkit_dom_html_table_row_element_insert_cell (
+ row, -1, NULL);
+ }
+ } else if (expected_columns < current_columns) {
+ for (jj = 0; jj < current_columns - expected_columns; jj++) {
+ webkit_dom_html_table_row_element_delete_cell (
+ row, -1, NULL);
+ }
+ }
+ }
+}
+
+static void
+html_editor_table_dialog_get_column_count (EHTMLEditorTableDialog *dialog)
+{
+ WebKitDOMHTMLCollection *rows, *columns;
+ WebKitDOMNode *row;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ rows = webkit_dom_html_table_element_get_rows (dialog->priv->table_element);
+ row = webkit_dom_html_collection_item (rows, 0);
+
+ columns = webkit_dom_html_table_row_element_get_cells (
+ WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (row));
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->columns_edit),
+ webkit_dom_html_collection_get_length (columns));
+}
+
+static void
+html_editor_table_dialog_set_width (EHTMLEditorTableDialog *dialog)
+{
+ gchar *width;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ if (gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->width_check))) {
+ gchar *units;
+
+ units = gtk_combo_box_text_get_active_text (
+ GTK_COMBO_BOX_TEXT (dialog->priv->width_units));
+ width = g_strdup_printf (
+ "%d%s",
+ gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit)),
+ units);
+ g_free (units);
+
+ gtk_widget_set_sensitive (dialog->priv->width_edit, TRUE);
+ gtk_widget_set_sensitive (dialog->priv->width_units, TRUE);
+ } else {
+ width = g_strdup ("auto");
+
+ gtk_widget_set_sensitive (dialog->priv->width_edit, FALSE);
+ gtk_widget_set_sensitive (dialog->priv->width_units, FALSE);
+ }
+
+ webkit_dom_html_table_element_set_width (
+ dialog->priv->table_element, width);
+ g_free (width);
+}
+
+static void
+html_editor_table_dialog_get_width (EHTMLEditorTableDialog *dialog)
+{
+ gchar *width;
+
+ width = webkit_dom_html_table_element_get_width (dialog->priv->table_element);
+ if (!width || !*width || g_ascii_strncasecmp (width, "auto", 4) == 0) {
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->width_check), FALSE);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit), 100);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->width_units), "units-percent");
+ } else {
+ gint width_int = atoi (width);
+
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->width_check), TRUE);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit), width_int);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->width_units),
+ ((strstr (width, "%") == NULL) ?
+ "units-px" : "units-percent"));
+ }
+ g_free (width);
+}
+
+static void
+html_editor_table_dialog_set_alignment (EHTMLEditorTableDialog *dialog)
+{
+ g_return_if_fail (dialog->priv->table_element);
+
+ webkit_dom_html_table_element_set_align (
+ dialog->priv->table_element,
+ gtk_combo_box_get_active_id (
+ GTK_COMBO_BOX (dialog->priv->alignment_combo)));
+}
+
+static void
+html_editor_table_dialog_get_alignment (EHTMLEditorTableDialog *dialog)
+{
+ gchar *alignment;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ alignment = webkit_dom_html_table_element_get_align (
+ dialog->priv->table_element);
+
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->alignment_combo), alignment);
+
+ g_free (alignment);
+}
+
+static void
+html_editor_table_dialog_set_padding (EHTMLEditorTableDialog *dialog)
+{
+ gchar *padding;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ padding = g_strdup_printf (
+ "%d",
+ gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->padding_edit)));
+
+ webkit_dom_html_table_element_set_cell_padding (
+ dialog->priv->table_element, padding);
+
+ g_free (padding);
+}
+
+static void
+html_editor_table_dialog_get_padding (EHTMLEditorTableDialog *dialog)
+{
+ gchar *padding;
+ gint padding_int;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ padding = webkit_dom_html_table_element_get_cell_padding (
+ dialog->priv->table_element);
+ if (!padding || !*padding) {
+ padding_int = 0;
+ } else {
+ padding_int = atoi (padding);
+ }
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->padding_edit), padding_int);
+
+ g_free (padding);
+}
+
+static void
+html_editor_table_dialog_set_spacing (EHTMLEditorTableDialog *dialog)
+{
+ gchar *spacing;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ spacing = g_strdup_printf (
+ "%d",
+ gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->spacing_edit)));
+
+ webkit_dom_html_table_element_set_cell_spacing (
+ dialog->priv->table_element, spacing);
+
+ g_free (spacing);
+}
+
+static void
+html_editor_table_dialog_get_spacing (EHTMLEditorTableDialog *dialog)
+{
+ gchar *spacing;
+ gint spacing_int;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ spacing = webkit_dom_html_table_element_get_cell_spacing (
+ dialog->priv->table_element);
+ if (!spacing || !*spacing) {
+ spacing_int = 0;
+ } else {
+ spacing_int = atoi (spacing);
+ }
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->spacing_edit), spacing_int);
+
+ g_free (spacing);
+}
+
+static void
+html_editor_table_dialog_set_border (EHTMLEditorTableDialog *dialog)
+{
+ gchar *border;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ border = g_strdup_printf (
+ "%d",
+ gtk_spin_button_get_value_as_int (
+ GTK_SPIN_BUTTON (dialog->priv->border_edit)));
+
+ webkit_dom_html_table_element_set_border (
+ dialog->priv->table_element, border);
+
+ g_free (border);
+}
+
+static void
+html_editor_table_dialog_get_border (EHTMLEditorTableDialog *dialog)
+{
+ gchar *border;
+ gint border_int;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ border = webkit_dom_html_table_element_get_border (
+ dialog->priv->table_element);
+ if (!border || !*border) {
+ border_int = 0;
+ } else {
+ border_int = atoi (border);
+ }
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->border_edit), border_int);
+
+ g_free (border);
+}
+
+static void
+html_editor_table_dialog_set_background_color (EHTMLEditorTableDialog *dialog)
+{
+ gchar *color;
+ GdkRGBA rgba;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ e_color_combo_get_current_color (
+ E_COLOR_COMBO (dialog->priv->background_color_button), &rgba);
+ color = g_strdup_printf (
+ "#%06x", e_rgba_to_value (&rgba));
+
+ webkit_dom_html_table_element_set_bg_color (
+ dialog->priv->table_element, color);
+
+ g_free (color);
+}
+
+static void
+html_editor_table_dialog_get_background_color (EHTMLEditorTableDialog *dialog)
+{
+ gchar *color;
+ GdkRGBA rgba;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ color = webkit_dom_html_table_element_get_bg_color (
+ dialog->priv->table_element);
+
+ gdk_rgba_parse (&rgba, color);
+
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->background_color_button), &rgba);
+
+ g_free (color);
+}
+
+static void
+html_editor_table_dialog_set_background_image (EHTMLEditorTableDialog *dialog)
+{
+ const gchar *filename;
+
+ g_return_if_fail (dialog->priv->table_element);
+
+ filename = gtk_file_chooser_get_filename (
+ GTK_FILE_CHOOSER (dialog->priv->background_image_button));
+
+ if (filename) {
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->table_element),
+ "background", filename, NULL);
+ } else {
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->table_element),
+ "background");
+ }
+}
+
+static void
+html_editor_table_dialog_get_background_image (EHTMLEditorTableDialog *dialog)
+{
+ g_return_if_fail (dialog->priv->table_element);
+
+ if (!webkit_dom_element_has_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->table_element), "background")) {
+
+ gtk_file_chooser_unselect_all (
+ GTK_FILE_CHOOSER (dialog->priv->background_image_button));
+ return;
+ } else {
+ gchar *background;
+
+ background = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (dialog->priv->table_element), "background");
+
+ gtk_file_chooser_set_filename (
+ GTK_FILE_CHOOSER (dialog->priv->background_image_button),
+ background);
+
+ g_free (background);
+ }
+}
+
+static void
+html_editor_table_dialog_get_values (EHTMLEditorTableDialog *dialog)
+{
+ html_editor_table_dialog_get_row_count (dialog);
+ html_editor_table_dialog_get_column_count (dialog);
+ html_editor_table_dialog_get_width (dialog);
+ html_editor_table_dialog_get_alignment (dialog);
+ html_editor_table_dialog_get_spacing (dialog);
+ html_editor_table_dialog_get_padding (dialog);
+ html_editor_table_dialog_get_border (dialog);
+ html_editor_table_dialog_get_background_color (dialog);
+ html_editor_table_dialog_get_background_image (dialog);
+}
+
+static void
+html_editor_table_dialog_reset_values (EHTMLEditorTableDialog *dialog)
+{
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->rows_edit), 3);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->columns_edit), 3);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->alignment_combo), "left");
+
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->width_check), TRUE);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->width_edit), 100);
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (dialog->priv->width_units), "units-percent");
+
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->spacing_edit), 2);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->padding_edit), 1);
+ gtk_spin_button_set_value (
+ GTK_SPIN_BUTTON (dialog->priv->border_edit), 1);
+
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->background_color_button), &white);
+ gtk_file_chooser_unselect_all (
+ GTK_FILE_CHOOSER (dialog->priv->background_image_button));
+
+ html_editor_table_dialog_set_row_count (dialog);
+ html_editor_table_dialog_set_column_count (dialog);
+ html_editor_table_dialog_set_width (dialog);
+ html_editor_table_dialog_set_alignment (dialog);
+ html_editor_table_dialog_set_spacing (dialog);
+ html_editor_table_dialog_set_padding (dialog);
+ html_editor_table_dialog_set_border (dialog);
+ html_editor_table_dialog_set_background_color (dialog);
+ html_editor_table_dialog_set_background_image (dialog);
+}
+
+static void
+html_editor_table_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditorTableDialog *dialog;
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *selection;
+
+ dialog = E_HTML_EDITOR_TABLE_DIALOG (widget);
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ selection = webkit_dom_dom_window_get_selection (window);
+ if (selection && (webkit_dom_dom_selection_get_range_count (selection) > 0)) {
+ WebKitDOMElement *table;
+ WebKitDOMRange *range;
+
+ range = webkit_dom_dom_selection_get_range_at (selection, 0, NULL);
+ table = e_html_editor_dom_node_find_parent_element (
+ webkit_dom_range_get_start_container (range, NULL), "TABLE");
+
+ if (!table) {
+ dialog->priv->table_element = WEBKIT_DOM_HTML_TABLE_ELEMENT (
+ html_editor_table_dialog_create_table (dialog));
+ html_editor_table_dialog_reset_values (dialog);
+ } else {
+ dialog->priv->table_element =
+ WEBKIT_DOM_HTML_TABLE_ELEMENT (table);
+ html_editor_table_dialog_get_values (dialog);
+ }
+ }
+
+ /* Chain up to parent implementation */
+ GTK_WIDGET_CLASS (e_html_editor_table_dialog_parent_class)->show (widget);
+}
+
+static void
+html_editor_table_dialog_hide (GtkWidget *widget)
+{
+ EHTMLEditorTableDialogPrivate *priv;
+
+ priv = E_HTML_EDITOR_TABLE_DIALOG_GET_PRIVATE (widget);
+
+ priv->table_element = NULL;
+
+ GTK_WIDGET_CLASS (e_html_editor_table_dialog_parent_class)->hide (widget);
+}
+
+static void
+e_html_editor_table_dialog_class_init (EHTMLEditorTableDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorTableDialogPrivate));
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_table_dialog_show;
+ widget_class->hide = html_editor_table_dialog_hide;
+}
+
+static void
+e_html_editor_table_dialog_init (EHTMLEditorTableDialog *dialog)
+{
+ GtkGrid *main_layout, *grid;
+ GtkWidget *widget;
+ GtkFileFilter *file_filter;
+
+ dialog->priv = E_HTML_EDITOR_TABLE_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ /* == General == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>General</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 1, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Rows */
+ widget = gtk_image_new_from_icon_name ("stock_select-row", GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ widget = gtk_spin_button_new_with_range (1, G_MAXINT, 1);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0);
+ gtk_grid_attach (grid, widget, 2, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_table_dialog_set_row_count), dialog);
+ dialog->priv->rows_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Rows:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->rows_edit);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+
+ /* Columns */
+ widget = gtk_image_new_from_icon_name ("stock_select-column", GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (grid, widget, 3, 0, 1, 1);
+
+ widget = gtk_spin_button_new_with_range (1, G_MAXINT, 1);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0);
+ gtk_grid_attach (grid, widget, 5, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_table_dialog_set_column_count), dialog);
+ dialog->priv->columns_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("C_olumns:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->columns_edit);
+ gtk_grid_attach (grid, widget, 4, 0, 1, 1);
+
+ /* == Layout == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Layout</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 2, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 3, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Width */
+ widget = gtk_check_button_new_with_mnemonic (_("_Width:"));
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_table_dialog_set_width), dialog);
+ dialog->priv->width_check = widget;
+
+ widget = gtk_spin_button_new_with_range (1, 100, 1);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_table_dialog_set_width), dialog);
+ dialog->priv->width_edit = widget;
+
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-px", "px");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "units-percent", "%");
+ gtk_grid_attach (grid, widget, 2, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_table_dialog_set_width), dialog);
+ dialog->priv->width_units = widget;
+
+ /* Spacing */
+ widget = gtk_spin_button_new_with_range (0, G_MAXINT, 1);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0);
+ gtk_grid_attach (grid, widget, 5, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_table_dialog_set_spacing), dialog);
+ dialog->priv->spacing_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Spacing:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->spacing_edit);
+ gtk_grid_attach (grid, widget, 4, 0, 1, 1);
+
+ widget = gtk_label_new ("px");
+ gtk_grid_attach (grid, widget, 6, 0, 1, 1);
+
+ /* Padding */
+ widget = gtk_spin_button_new_with_range (0, G_MAXINT, 1);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0);
+ gtk_grid_attach (grid, widget, 5, 1, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_table_dialog_set_padding), dialog);
+ dialog->priv->padding_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Padding:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->padding_edit);
+ gtk_grid_attach (grid, widget, 4, 1, 1, 1);
+
+ widget = gtk_label_new ("px");
+ gtk_grid_attach (grid, widget, 6, 1, 1, 1);
+
+ /* Border */
+ widget = gtk_spin_button_new_with_range (0, G_MAXINT, 1);
+ gtk_spin_button_set_digits (GTK_SPIN_BUTTON (widget), 0);
+ gtk_grid_attach (grid, widget, 5, 2, 1, 1);
+ g_signal_connect_swapped (
+ widget, "value-changed",
+ G_CALLBACK (html_editor_table_dialog_set_border), dialog);
+ dialog->priv->border_edit = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Border:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->border_edit);
+ gtk_grid_attach (grid, widget, 4, 2, 1, 1);
+
+ widget = gtk_label_new ("px");
+ gtk_grid_attach (grid, widget, 6, 2, 1, 1);
+
+ /* Alignment */
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "left", _("Left"));
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "center", _("Center"));
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "right", _("Right"));
+ gtk_grid_attach (grid, widget, 1, 1, 2, 1);
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_table_dialog_set_alignment), dialog);
+ dialog->priv->alignment_combo = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Alignment:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->alignment_combo);
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ /* == Background == */
+ widget = gtk_label_new ("");
+ gtk_label_set_markup (GTK_LABEL (widget), _("<b>Background</b>"));
+ gtk_misc_set_alignment (GTK_MISC (widget), 0, 0.5);
+ gtk_grid_attach (main_layout, widget, 0, 4, 1, 1);
+
+ grid = GTK_GRID (gtk_grid_new ());
+ gtk_grid_set_row_spacing (grid, 5);
+ gtk_grid_set_column_spacing (grid, 5);
+ gtk_grid_attach (main_layout, GTK_WIDGET (grid), 0, 5, 1, 1);
+ gtk_widget_set_margin_left (GTK_WIDGET (grid), 10);
+
+ /* Color */
+ widget = e_color_combo_new ();
+ e_color_combo_set_default_color (E_COLOR_COMBO (widget), &white);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_grid_attach (grid, widget, 1, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "notify::current-color",
+ G_CALLBACK (html_editor_table_dialog_set_background_color), dialog);
+ dialog->priv->background_color_button = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Color:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->background_color_button);
+ gtk_grid_attach (grid, widget, 0, 0, 1, 1);
+
+ /* Image */
+ widget = e_image_chooser_dialog_new (
+ _("Choose Background Image"),
+ GTK_WINDOW (dialog));
+ dialog->priv->image_chooser_dialog = widget;
+
+ file_filter = gtk_file_filter_new ();
+ gtk_file_filter_set_name (file_filter, _("Images"));
+ gtk_file_filter_add_mime_type (file_filter, "image/*");
+
+ widget = gtk_file_chooser_button_new_with_dialog (
+ dialog->priv->image_chooser_dialog);
+ gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (widget), file_filter);
+ gtk_widget_set_hexpand (widget, TRUE);
+ gtk_grid_attach (grid, widget, 1, 1, 1, 1);
+ g_signal_connect_swapped (
+ widget, "file-set",
+ G_CALLBACK (html_editor_table_dialog_set_background_image), dialog);
+ dialog->priv->background_image_button = widget;
+
+ widget =gtk_label_new_with_mnemonic (_("Image:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (
+ GTK_LABEL (widget), dialog->priv->background_image_button);
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_table_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_TABLE_DIALOG,
+ "editor", editor,
+ "title", N_("Table Properties"),
+ NULL));
+}
+
diff --git a/e-util/e-html-editor-table-dialog.h b/e-util/e-html-editor-table-dialog.h
new file mode 100644
index 0000000000..70c790d22d
--- /dev/null
+++ b/e-util/e-html-editor-table-dialog.h
@@ -0,0 +1,69 @@
+/*
+ * e-html-editor-table-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_TABLE_DIALOG_H
+#define E_HTML_EDITOR_TABLE_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_TABLE_DIALOG \
+ (e_html_editor_table_dialog_get_type ())
+#define E_HTML_EDITOR_TABLE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_TABLE_DIALOG, EHTMLEditorTableDialog))
+#define E_HTML_EDITOR_TABLE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_TABLE_DIALOG, EHTMLEditorTableDialogClass))
+#define E_IS_HTML_EDITOR_TABLE_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_TABLE_DIALOG))
+#define E_IS_HTML_EDITOR_TABLE_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_TABLE_DIALOG))
+#define E_HTML_EDITOR_TABLE_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_TABLE_DIALOG, EHTMLEditorTableDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorTableDialog EHTMLEditorTableDialog;
+typedef struct _EHTMLEditorTableDialogClass EHTMLEditorTableDialogClass;
+typedef struct _EHTMLEditorTableDialogPrivate EHTMLEditorTableDialogPrivate;
+
+struct _EHTMLEditorTableDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorTableDialogPrivate *priv;
+};
+
+struct _EHTMLEditorTableDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_table_dialog_get_type (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_table_dialog_new (EHTMLEditor *editor);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_TABLE_DIALOG_H */
diff --git a/e-util/e-html-editor-text-dialog.c b/e-util/e-html-editor-text-dialog.c
new file mode 100644
index 0000000000..2db792c70a
--- /dev/null
+++ b/e-util/e-html-editor-text-dialog.c
@@ -0,0 +1,298 @@
+/*
+ * e-html-editor-text-dialog.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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-text-dialog.h"
+
+#include <glib/gi18n-lib.h>
+
+#include "e-color-combo.h"
+
+#define E_HTML_EDITOR_TEXT_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_TEXT_DIALOG, EHTMLEditorTextDialogPrivate))
+
+struct _EHTMLEditorTextDialogPrivate {
+ GtkWidget *bold_check;
+ GtkWidget *italic_check;
+ GtkWidget *underline_check;
+ GtkWidget *strikethrough_check;
+
+ GtkWidget *color_check;
+ GtkWidget *size_check;
+};
+
+G_DEFINE_TYPE (
+ EHTMLEditorTextDialog,
+ e_html_editor_text_dialog,
+ E_TYPE_HTML_EDITOR_DIALOG);
+
+static void
+html_editor_text_dialog_set_bold (EHTMLEditorTextDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+
+ e_html_editor_selection_set_bold (
+ selection,
+ gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->bold_check)));
+}
+
+static void
+html_editor_text_dialog_set_italic (EHTMLEditorTextDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+
+ e_html_editor_selection_set_italic (
+ selection,
+ gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->italic_check)));
+}
+
+static void
+html_editor_text_dialog_set_underline (EHTMLEditorTextDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+
+ e_html_editor_selection_set_underline (
+ selection,
+ gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->underline_check)));
+}
+
+static void
+html_editor_text_dialog_set_strikethrough (EHTMLEditorTextDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+
+ e_html_editor_selection_set_strikethrough (
+ selection,
+ gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->strikethrough_check)));
+}
+
+static void
+html_editor_text_dialog_set_color (EHTMLEditorTextDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+ GdkRGBA rgba;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+
+ e_color_combo_get_current_color (
+ E_COLOR_COMBO (dialog->priv->color_check), &rgba);
+ e_html_editor_selection_set_font_color (selection, &rgba);
+}
+
+static void
+html_editor_text_dialog_set_size (EHTMLEditorTextDialog *dialog)
+{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+ gint size;
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+ size = gtk_combo_box_get_active (GTK_COMBO_BOX (dialog->priv->size_check));
+
+ e_html_editor_selection_set_font_size (selection, size + 1);
+}
+
+static void
+html_editor_text_dialog_show (GtkWidget *widget)
+{
+ EHTMLEditorTextDialog *dialog;
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+ GdkRGBA rgba;
+
+ dialog = E_HTML_EDITOR_TEXT_DIALOG (widget);
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ view = e_html_editor_get_view (editor);
+ selection = e_html_editor_view_get_selection (view);
+
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->bold_check),
+ e_html_editor_selection_is_bold (selection));
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->italic_check),
+ e_html_editor_selection_is_italic (selection));
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->underline_check),
+ e_html_editor_selection_is_underline (selection));
+ gtk_toggle_button_set_active (
+ GTK_TOGGLE_BUTTON (dialog->priv->strikethrough_check),
+ e_html_editor_selection_is_strikethrough (selection));
+
+ gtk_combo_box_set_active (
+ GTK_COMBO_BOX (dialog->priv->size_check),
+ e_html_editor_selection_get_font_size (selection));
+
+ e_html_editor_selection_get_font_color (selection, &rgba);
+ e_color_combo_set_current_color (
+ E_COLOR_COMBO (dialog->priv->color_check), &rgba);
+
+ GTK_WIDGET_CLASS (e_html_editor_text_dialog_parent_class)->show (widget);
+}
+
+static void
+e_html_editor_text_dialog_class_init (EHTMLEditorTextDialogClass *class)
+{
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorTextDialogPrivate));
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->show = html_editor_text_dialog_show;
+}
+
+static void
+e_html_editor_text_dialog_init (EHTMLEditorTextDialog *dialog)
+{
+ GtkGrid *main_layout;
+ GtkWidget *widget;
+
+ dialog->priv = E_HTML_EDITOR_TEXT_DIALOG_GET_PRIVATE (dialog);
+
+ main_layout = e_html_editor_dialog_get_container (E_HTML_EDITOR_DIALOG (dialog));
+
+ /* Bold */
+ widget = gtk_image_new_from_stock (GTK_STOCK_BOLD, GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (main_layout, widget, 0, 0, 1, 1);
+
+ widget = gtk_check_button_new_with_mnemonic (_("_Bold"));
+ gtk_grid_attach (main_layout, widget, 1, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_text_dialog_set_bold), dialog);
+ dialog->priv->bold_check = widget;
+
+ /* Italic */
+ widget = gtk_image_new_from_stock (GTK_STOCK_ITALIC, GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (main_layout, widget, 0, 1, 1, 1);
+
+ widget = gtk_check_button_new_with_mnemonic (_("_Italic"));
+ gtk_grid_attach (main_layout, widget, 1, 1, 1, 1);
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_text_dialog_set_italic), dialog);
+ dialog->priv->italic_check = widget;
+
+ /* Underline */
+ widget = gtk_image_new_from_stock (GTK_STOCK_UNDERLINE, GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (main_layout, widget, 0, 2, 1, 1);
+
+ widget = gtk_check_button_new_with_mnemonic (_("_Underline"));
+ gtk_grid_attach (main_layout, widget, 1, 2, 1, 1);
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_text_dialog_set_underline), dialog);
+ dialog->priv->underline_check = widget;
+
+ widget = gtk_image_new_from_stock (GTK_STOCK_STRIKETHROUGH, GTK_ICON_SIZE_BUTTON);
+ gtk_grid_attach (main_layout, widget, 0, 3, 1, 1);
+
+ widget = gtk_check_button_new_with_mnemonic (_("_Strikethrough"));
+ gtk_grid_attach (main_layout, widget, 1, 3, 1, 1);
+ g_signal_connect_swapped (
+ widget, "toggled",
+ G_CALLBACK (html_editor_text_dialog_set_strikethrough), dialog);
+ dialog->priv->strikethrough_check = widget;
+
+ /* Color */
+ widget = e_color_combo_new ();
+ gtk_grid_attach (main_layout, widget, 3, 0, 1, 1);
+ g_signal_connect_swapped (
+ widget, "notify::current-color",
+ G_CALLBACK (html_editor_text_dialog_set_color), dialog);
+ dialog->priv->color_check = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("_Color:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->color_check);
+ gtk_grid_attach (main_layout, widget, 2, 0, 1, 1);
+
+ /* Size */
+ widget = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "minus-two", "-2");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "minus-one", "-1");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "minus-zero", "0");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "plus-one", "+1");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "plus-two", "+2");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "plus-three", "+3");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "plus-four", "+4");
+ gtk_grid_attach (main_layout, widget, 3, 1, 1, 1);
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (html_editor_text_dialog_set_size), dialog);
+ dialog->priv->size_check = widget;
+
+ widget = gtk_label_new_with_mnemonic (_("Si_ze:"));
+ gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_RIGHT);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), dialog->priv->size_check);
+ gtk_grid_attach (main_layout, widget, 2, 1, 1, 1);
+
+ gtk_widget_show_all (GTK_WIDGET (main_layout));
+}
+
+GtkWidget *
+e_html_editor_text_dialog_new (EHTMLEditor *editor)
+{
+ return GTK_WIDGET (
+ g_object_new (
+ E_TYPE_HTML_EDITOR_TEXT_DIALOG,
+ "editor", editor,
+ "title", N_("Text Properties"),
+ NULL));
+}
diff --git a/e-util/e-html-editor-text-dialog.h b/e-util/e-html-editor-text-dialog.h
new file mode 100644
index 0000000000..006b780009
--- /dev/null
+++ b/e-util/e-html-editor-text-dialog.h
@@ -0,0 +1,69 @@
+/*
+ * e-html-editor-text-dialog.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_TEXT_DIALOG_H
+#define E_HTML_EDITOR_TEXT_DIALOG_H
+
+#include <e-util/e-html-editor-dialog.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_TEXT_DIALOG \
+ (e_html_editor_text_dialog_get_type ())
+#define E_HTML_EDITOR_TEXT_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_TEXT_DIALOG, EHTMLEditorTextDialog))
+#define E_HTML_EDITOR_TEXT_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_TEXT_DIALOG, EHTMLEditorTextDialogClass))
+#define E_IS_HTML_EDITOR_TEXT_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_TEXT_DIALOG))
+#define E_IS_HTML_EDITOR_TEXT_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_TEXT_DIALOG))
+#define E_HTML_EDITOR_TEXT_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_TEXT_DIALOG, EHTMLEditorTextDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorTextDialog EHTMLEditorTextDialog;
+typedef struct _EHTMLEditorTextDialogClass EHTMLEditorTextDialogClass;
+typedef struct _EHTMLEditorTextDialogPrivate EHTMLEditorTextDialogPrivate;
+
+struct _EHTMLEditorTextDialog {
+ EHTMLEditorDialog parent;
+ EHTMLEditorTextDialogPrivate *priv;
+};
+
+struct _EHTMLEditorTextDialogClass {
+ EHTMLEditorDialogClass parent_class;
+};
+
+GType e_html_editor_text_dialog_get_type (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_text_dialog_new (EHTMLEditor *editor);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_TEXT_DIALOG_H */
diff --git a/e-util/e-html-editor-utils.c b/e-util/e-html-editor-utils.c
new file mode 100644
index 0000000000..2807ea94b0
--- /dev/null
+++ b/e-util/e-html-editor-utils.c
@@ -0,0 +1,116 @@
+/*
+ * e-html-editor-utils.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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-utils.h"
+#include <string.h>
+
+/**
+ * e_html_editor_dom_node_find_parent_element:
+ * @node: Start node
+ * @tagname: Tag name of element to search
+ *
+ * Recursively searches for first occurance of element with given @tagname
+ * that is parent of given @node.
+ *
+ * Returns: A #WebKitDOMElement with @tagname representing parent of @node or
+ * @NULL when @node has no parent with given @tagname. When @node matches @tagname,
+ * then the @node is returned.
+ */
+WebKitDOMElement *
+e_html_editor_dom_node_find_parent_element (WebKitDOMNode *node,
+ const gchar *tagname)
+{
+ gint taglen = strlen (tagname);
+
+ while (node) {
+
+ if (WEBKIT_DOM_IS_ELEMENT (node)) {
+ gchar *node_tagname;
+
+ node_tagname = webkit_dom_element_get_tag_name (
+ WEBKIT_DOM_ELEMENT (node));
+
+ if (node_tagname &&
+ (strlen (node_tagname) == taglen) &&
+ (g_ascii_strncasecmp (node_tagname, tagname, taglen) == 0)) {
+ g_free (node_tagname);
+ return WEBKIT_DOM_ELEMENT (node);
+ }
+
+ g_free (node_tagname);
+ }
+
+ node = WEBKIT_DOM_NODE (webkit_dom_node_get_parent_element (node));
+ }
+
+ return NULL;
+}
+
+/**
+ * e_html_editor_dom_node_find_child_element:
+ * @node: Start node
+ * @tagname: Tag name of element to search.
+ *
+ * Recursively searches for first occurence of element with given @tagname that
+ * is a child of @node.
+ *
+ * Returns: A #WebKitDOMElement with @tagname representing a child of @node or
+ * @NULL when @node has no child with given @tagname. When @node matches @tagname,
+ * then the @node is returned.
+ */
+WebKitDOMElement *
+e_html_editor_dom_node_find_child_element (WebKitDOMNode *node,
+ const gchar *tagname)
+{
+ WebKitDOMNode *start_node = node;
+ gint taglen = strlen (tagname);
+
+ do {
+ if (WEBKIT_DOM_IS_ELEMENT (node)) {
+ gchar *node_tagname;
+
+ node_tagname = webkit_dom_element_get_tag_name (
+ WEBKIT_DOM_ELEMENT (node));
+
+ if (node_tagname &&
+ (strlen (node_tagname) == taglen) &&
+ (g_ascii_strncasecmp (node_tagname, tagname, taglen) == 0)) {
+ g_free (node_tagname);
+ return WEBKIT_DOM_ELEMENT (node);
+ }
+
+ g_free (node_tagname);
+ }
+
+ if (webkit_dom_node_has_child_nodes (node)) {
+ node = webkit_dom_node_get_first_child (node);
+ } else if (webkit_dom_node_get_next_sibling (node)) {
+ node = webkit_dom_node_get_next_sibling (node);
+ } else {
+ node = webkit_dom_node_get_parent_node (node);
+ }
+ } while (!webkit_dom_node_is_same_node (node, start_node));
+
+ return NULL;
+}
diff --git a/e-util/e-html-editor-utils.h b/e-util/e-html-editor-utils.h
new file mode 100644
index 0000000000..7331a87709
--- /dev/null
+++ b/e-util/e-html-editor-utils.h
@@ -0,0 +1,44 @@
+/*
+ * e-html-editor-utils.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_UTILS_H
+#define E_HTML_EDITOR_UTILS_H
+
+#include <webkit/webkitdom.h>
+
+G_BEGIN_DECLS
+
+WebKitDOMElement *
+ e_html_editor_dom_node_find_parent_element
+ (WebKitDOMNode *node,
+ const gchar *tagname);
+
+WebKitDOMElement *
+ e_html_editor_dom_node_find_child_element
+ (WebKitDOMNode *node,
+ const gchar *tagname);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_UTILS_H */
diff --git a/e-util/e-html-editor-view.c b/e-util/e-html-editor-view.c
new file mode 100644
index 0000000000..21cadb3a5e
--- /dev/null
+++ b/e-util/e-html-editor-view.c
@@ -0,0 +1,6303 @@
+/*
+ * e-html-editor-view.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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-html-editor-view.h"
+#include "e-html-editor.h"
+#include "e-emoticon-chooser.h"
+
+#include <e-util/e-util.h>
+#include <e-util/e-marshal.h>
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+
+#define E_HTML_EDITOR_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_HTML_EDITOR_VIEW, EHTMLEditorViewPrivate))
+
+#define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b"
+
+#define URL_PATTERN \
+ "((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[\\-;:&=\\+\\$,\\w]+@)?" \
+ "[A-Za-z0-9\\.\\-]+|(?:www\\.|[\\-;:&=\\+\\$,\\w]+@)" \
+ "[A-Za-z0-9\\.\\-]+)((?:\\/[\\+~%\\/\\.\\w\\-]*)?\\?" \
+ "?(?:[\\-\\+=&;%@\\.\\w]*)#?(?:[\\.\\!\\/\\\\w]*))?)"
+
+#define URL_PATTERN_SPACE URL_PATTERN "\\s"
+
+#define QUOTE_SYMBOL ">"
+
+/* Keep synchronized with the same value in EHTMLEditorSelection */
+#define SPACES_PER_LIST_LEVEL 8
+
+/**
+ * EHTMLEditorView:
+ *
+ * The #EHTMLEditorView is a WebKit-based rich text editor. The view itself
+ * only provides means to configure global behavior of the editor. To work
+ * with the actual content, current cursor position or current selection,
+ * use #EHTMLEditorSelection object.
+ */
+
+struct _EHTMLEditorViewPrivate {
+ gint changed : 1;
+ gint inline_spelling : 1;
+ gint magic_links : 1;
+ gint magic_smileys : 1;
+ gint can_copy : 1;
+ gint can_cut : 1;
+ gint can_paste : 1;
+ gint can_redo : 1;
+ gint can_undo : 1;
+ gint reload_in_progress : 1;
+ gint html_mode : 1;
+
+ EHTMLEditorSelection *selection;
+
+ WebKitDOMElement *element_under_mouse;
+
+ GHashTable *inline_images;
+
+ GSettings *font_settings;
+ GSettings *aliasing_settings;
+
+ gboolean convertor_insert;
+
+ WebKitWebView *convertor_web_view;
+};
+
+enum {
+ PROP_0,
+ PROP_CAN_COPY,
+ PROP_CAN_CUT,
+ PROP_CAN_PASTE,
+ PROP_CAN_REDO,
+ PROP_CAN_UNDO,
+ PROP_CHANGED,
+ PROP_HTML_MODE,
+ PROP_INLINE_SPELLING,
+ PROP_MAGIC_LINKS,
+ PROP_MAGIC_SMILEYS,
+ PROP_SPELL_CHECKER
+};
+
+enum {
+ POPUP_EVENT,
+ PASTE_PRIMARY_CLIPBOARD,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static CamelDataCache *emd_global_http_cache = NULL;
+
+G_DEFINE_TYPE_WITH_CODE (
+ EHTMLEditorView,
+ e_html_editor_view,
+ WEBKIT_TYPE_WEB_VIEW,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EXTENSIBLE, NULL))
+
+static WebKitDOMRange *
+html_editor_view_get_dom_range (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *selection;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ selection = webkit_dom_dom_window_get_selection (window);
+
+ if (webkit_dom_dom_selection_get_range_count (selection) < 1) {
+ return NULL;
+ }
+
+ return webkit_dom_dom_selection_get_range_at (selection, 0, NULL);
+}
+
+static void
+html_editor_view_user_changed_contents_cb (EHTMLEditorView *view,
+ gpointer user_data)
+{
+ WebKitWebView *web_view;
+ gboolean can_redo, can_undo;
+
+ web_view = WEBKIT_WEB_VIEW (view);
+
+ e_html_editor_view_set_changed (view, TRUE);
+
+ can_redo = webkit_web_view_can_redo (web_view);
+ if (view->priv->can_redo != can_redo) {
+ view->priv->can_redo = can_redo;
+ g_object_notify (G_OBJECT (view), "can-redo");
+ }
+
+ can_undo = webkit_web_view_can_undo (web_view);
+ if (view->priv->can_undo != can_undo) {
+ view->priv->can_undo = can_undo;
+ g_object_notify (G_OBJECT (view), "can-undo");
+ }
+}
+
+static void
+html_editor_view_selection_changed_cb (EHTMLEditorView *view,
+ gpointer user_data)
+{
+ WebKitWebView *web_view;
+ gboolean can_copy, can_cut, can_paste;
+
+ web_view = WEBKIT_WEB_VIEW (view);
+
+ /* When the webview is being (re)loaded, the document is in an
+ * inconsistant state and there is no selection, so don't propagate
+ * the signal further to EHTMLEditorSelection and others and wait until
+ * the load is finished. */
+ if (view->priv->reload_in_progress) {
+ g_signal_stop_emission_by_name (view, "selection-changed");
+ return;
+ }
+
+ can_copy = webkit_web_view_can_copy_clipboard (web_view);
+ if (view->priv->can_copy != can_copy) {
+ view->priv->can_copy = can_copy;
+ g_object_notify (G_OBJECT (view), "can-copy");
+ }
+
+ can_cut = webkit_web_view_can_cut_clipboard (web_view);
+ if (view->priv->can_cut != can_cut) {
+ view->priv->can_cut = can_cut;
+ g_object_notify (G_OBJECT (view), "can-cut");
+ }
+
+ can_paste = webkit_web_view_can_paste_clipboard (web_view);
+ if (view->priv->can_paste != can_paste) {
+ view->priv->can_paste = can_paste;
+ g_object_notify (G_OBJECT (view), "can-paste");
+ }
+}
+
+static gboolean
+html_editor_view_should_show_delete_interface_for_element (EHTMLEditorView *view,
+ WebKitDOMHTMLElement *element)
+{
+ return FALSE;
+}
+
+void
+e_html_editor_view_force_spell_check_for_current_paragraph (EHTMLEditorView *view)
+{
+ EHTMLEditorSelection *selection;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMSelection *dom_selection;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMElement *caret, *parent, *element;
+ WebKitDOMRange *end_range, *actual;
+ WebKitDOMText *text;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+
+ element = webkit_dom_document_query_selector (
+ document, "body[spellcheck=true]", NULL);
+
+ if (!element)
+ return;
+
+ selection = e_html_editor_view_get_selection (view);
+ caret = e_html_editor_selection_save_caret_position (selection);
+
+ /* Block callbacks of selection-changed signal as we don't want to
+ * recount all the block format things in EHTMLEditorSelection and here as well
+ * when we are moving with caret */
+ g_signal_handlers_block_by_func (
+ view, html_editor_view_selection_changed_cb, NULL);
+ e_html_editor_selection_block_selection_changed (selection);
+
+ parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (caret));
+ element = caret;
+
+ while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
+ element = parent;
+ parent = webkit_dom_node_get_parent_element (
+ WEBKIT_DOM_NODE (parent));
+ }
+
+ /* Append some text on the end of the element */
+ text = webkit_dom_document_create_text_node (document, "-x-evo-end");
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (text), NULL);
+
+ /* Create range that's pointing on the end of this text */
+ end_range = webkit_dom_document_create_range (document);
+ webkit_dom_range_select_node_contents (
+ end_range, WEBKIT_DOM_NODE (text), NULL);
+ webkit_dom_range_collapse (end_range, FALSE, NULL);
+
+ /* Move on the beginning of the paragraph */
+ actual = webkit_dom_document_create_range (document);
+ webkit_dom_range_select_node_contents (
+ actual, WEBKIT_DOM_NODE (element), NULL);
+ webkit_dom_range_collapse (actual, TRUE, NULL);
+ webkit_dom_dom_selection_remove_all_ranges (dom_selection);
+ webkit_dom_dom_selection_add_range (dom_selection, actual);
+
+ /* Go through all words to spellcheck them. To avoid this we have to wait for
+ * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */
+ actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
+ /* We are moving forward word by word until we hit the text on the end of
+ * the paragraph that we previously inserted there */
+ while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) {
+ webkit_dom_dom_selection_modify (
+ dom_selection, "move", "forward", "word");
+ actual = webkit_dom_dom_selection_get_range_at (
+ dom_selection, 0, NULL);
+ }
+
+ /* Remove the text that we inserted on the end of the paragraph */
+ webkit_dom_node_remove_child (
+ WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (text), NULL);
+
+ /* Unblock the callbacks */
+ g_signal_handlers_unblock_by_func (
+ view, html_editor_view_selection_changed_cb, NULL);
+ e_html_editor_selection_unblock_selection_changed (selection);
+
+ e_html_editor_selection_restore_caret_position (selection);
+}
+
+static void
+move_caret_into_element (WebKitDOMDocument *document,
+ WebKitDOMElement *element)
+{
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *window_selection;
+ WebKitDOMRange *new_range;
+
+ if (!element)
+ return;
+
+ window = webkit_dom_document_get_default_view (document);
+ window_selection = webkit_dom_dom_window_get_selection (window);
+ new_range = webkit_dom_document_create_range (document);
+
+ webkit_dom_range_select_node_contents (
+ new_range, WEBKIT_DOM_NODE (element), NULL);
+ webkit_dom_range_collapse (new_range, FALSE, NULL);
+ webkit_dom_dom_selection_remove_all_ranges (window_selection);
+ webkit_dom_dom_selection_add_range (window_selection, new_range);
+}
+
+static void
+refresh_spell_check (EHTMLEditorView *view,
+ gboolean enable_spell_check)
+{
+ EHTMLEditorSelection *selection;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMSelection *dom_selection;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMHTMLElement *body;
+ WebKitDOMRange *end_range, *actual;
+ WebKitDOMText *text;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+
+ /* Enable/Disable spellcheck in composer */
+ body = webkit_dom_document_get_body (document);
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (body),
+ "spellcheck",
+ enable_spell_check ? "true" : "false",
+ NULL);
+
+ selection = e_html_editor_view_get_selection (view);
+ e_html_editor_selection_save_caret_position (selection);
+
+ /* Sometimes the web view is not event focused, so we have to move caret
+ * into body */
+ if (!webkit_dom_document_get_element_by_id (document, "-x-evo-caret-position")) {
+ move_caret_into_element (
+ document,
+ WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)));
+ e_html_editor_selection_save_caret_position (selection);
+ }
+
+ /* Block callbacks of selection-changed signal as we don't want to
+ * recount all the block format things in EHTMLEditorSelection and here as well
+ * when we are moving with caret */
+ g_signal_handlers_block_by_func (
+ view, html_editor_view_selection_changed_cb, NULL);
+ e_html_editor_selection_block_selection_changed (selection);
+
+ /* Append some text on the end of the body */
+ text = webkit_dom_document_create_text_node (document, "-x-evo-end");
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL);
+
+ /* Create range that's pointing on the end of this text */
+ end_range = webkit_dom_document_create_range (document);
+ webkit_dom_range_select_node_contents (
+ end_range, WEBKIT_DOM_NODE (text), NULL);
+ webkit_dom_range_collapse (end_range, FALSE, NULL);
+
+ /* Move on the beginning of the document */
+ webkit_dom_dom_selection_modify (
+ dom_selection, "move", "backward", "documentboundary");
+
+ /* Go through all words to spellcheck them. To avoid this we have to wait for
+ * http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */
+ actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
+ /* We are moving forward word by word until we hit the text on the end of
+ * the body that we previously inserted there */
+ while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) {
+ webkit_dom_dom_selection_modify (
+ dom_selection, "move", "forward", "word");
+ actual = webkit_dom_dom_selection_get_range_at (
+ dom_selection, 0, NULL);
+ }
+
+ /* Remove the text that we inserted on the end of the body */
+ webkit_dom_node_remove_child (
+ WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL);
+
+ /* Unblock the callbacks */
+ g_signal_handlers_unblock_by_func (
+ view, html_editor_view_selection_changed_cb, NULL);
+ e_html_editor_selection_unblock_selection_changed (selection);
+
+ e_html_editor_selection_restore_caret_position (selection);
+}
+
+void
+e_html_editor_view_turn_spell_check_off (EHTMLEditorView *view)
+{
+ refresh_spell_check (view, FALSE);
+}
+
+void
+e_html_editor_view_force_spell_check (EHTMLEditorView *view)
+{
+ refresh_spell_check (view, TRUE);
+}
+
+static void
+body_input_event_cb (WebKitDOMElement *element,
+ WebKitDOMEvent *event,
+ EHTMLEditorView *view)
+{
+ WebKitDOMNode *node;
+ WebKitDOMRange *range = html_editor_view_get_dom_range (view);
+
+ e_html_editor_view_set_changed (view, TRUE);
+
+ node = webkit_dom_range_get_end_container (range, NULL);
+
+ /* After toggling monospaced format, we are using UNICODE_ZERO_WIDTH_SPACE
+ * to move caret into right space. When this callback is called it is not
+ * necassary anymore so remove it */
+ if (e_html_editor_view_get_html_mode (view)) {
+ WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node);
+
+ if (parent) {
+ WebKitDOMNode *prev_sibling;
+
+ prev_sibling = webkit_dom_node_get_previous_sibling (
+ WEBKIT_DOM_NODE (parent));
+
+ if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) {
+ gchar *text = webkit_dom_node_get_text_content (
+ prev_sibling);
+
+ if (g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0) {
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (
+ prev_sibling),
+ prev_sibling,
+ NULL);
+ }
+ g_free (text);
+ }
+
+ }
+ }
+
+ /* If text before caret includes UNICODE_ZERO_WIDTH_SPACE character, remove it */
+ if (WEBKIT_DOM_IS_TEXT (node)) {
+ gchar *text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node));
+ glong length = g_utf8_strlen (text, -1);
+ WebKitDOMNode *parent;
+
+ /* We have to preserve empty paragraphs with just UNICODE_ZERO_WIDTH_SPACE
+ * character as when we will remove it it will collapse */
+ if (length > 1) {
+ if (g_str_has_prefix (text, UNICODE_ZERO_WIDTH_SPACE))
+ webkit_dom_character_data_replace_data (
+ WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
+ else if (g_str_has_suffix (text, UNICODE_ZERO_WIDTH_SPACE))
+ webkit_dom_character_data_replace_data (
+ WEBKIT_DOM_CHARACTER_DATA (node), length - 1, 1, "", NULL);
+ }
+ g_free (text);
+
+ parent = webkit_dom_node_get_parent_node (node);
+ if ((WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (parent) ||
+ WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent)) &&
+ !element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph")) {
+ if (e_html_editor_view_get_html_mode (view)) {
+ element_add_class (
+ WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph");
+ } else {
+ e_html_editor_selection_set_paragraph_style (
+ e_html_editor_view_get_selection (view),
+ WEBKIT_DOM_ELEMENT (parent),
+ -1, 0, "");
+ }
+ }
+
+ /* When new smiley is added we have to use UNICODE_HIDDEN_SPACE to set the
+ * caret position to right place. It is removed when user starts typing. But
+ * when the user will press left arrow he will move the caret into
+ * smiley wrapper. If he will start to write there we have to move the written
+ * text out of the wrapper and move caret to right place */
+ if (WEBKIT_DOM_IS_ELEMENT (parent) &&
+ element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-wrapper")) {
+ WebKitDOMDocument *document;
+
+ document = webkit_web_view_get_dom_document (
+ WEBKIT_WEB_VIEW (view));
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (parent),
+ e_html_editor_selection_get_caret_position_node (
+ document),
+ webkit_dom_node_get_next_sibling (parent),
+ NULL);
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (parent),
+ node,
+ webkit_dom_node_get_next_sibling (parent),
+ NULL);
+ e_html_editor_selection_restore_caret_position (
+ e_html_editor_view_get_selection (view));
+ }
+ }
+}
+
+static void
+set_base64_to_element_attribute (EHTMLEditorView *view,
+ WebKitDOMElement *element,
+ const gchar *attribute)
+{
+ gchar *attribute_value;
+ const gchar *base64_src;
+
+ attribute_value = webkit_dom_element_get_attribute (element, attribute);
+
+ if ((base64_src = g_hash_table_lookup (view->priv->inline_images, attribute_value)) != NULL) {
+ const gchar *base64_data = strstr (base64_src, ";") + 1;
+ gchar *name;
+ glong name_length;
+
+ name_length =
+ g_utf8_strlen (base64_src, -1) -
+ g_utf8_strlen (base64_data, -1) - 1;
+ name = g_strndup (base64_src, name_length);
+
+ webkit_dom_element_set_attribute (element, "data-inline", "", NULL);
+ webkit_dom_element_set_attribute (element, "data-name", name, NULL);
+ webkit_dom_element_set_attribute (element, attribute, base64_data, NULL);
+
+ g_free (name);
+ }
+}
+
+static void
+change_cid_images_src_to_base64 (EHTMLEditorView *view)
+{
+ gint ii, length;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *document_element;
+ WebKitDOMNamedNodeMap *attributes;
+ WebKitDOMNodeList *list;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ document_element = webkit_dom_document_get_document_element (document);
+
+ list = webkit_dom_document_query_selector_all (document, "img[src^=\"cid:\"]", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+
+ set_base64_to_element_attribute (view, WEBKIT_DOM_ELEMENT (node), "src");
+ }
+
+ /* Namespaces */
+ attributes = webkit_dom_element_get_attributes (document_element);
+ length = webkit_dom_named_node_map_get_length (attributes);
+ for (ii = 0; ii < length; ii++) {
+ gchar *name;
+ WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
+
+ name = webkit_dom_node_get_local_name (node);
+
+ if (g_str_has_prefix (name, "xmlns:")) {
+ const gchar *ns = name + 6;
+ gchar *attribute_ns = g_strconcat (ns, ":src", NULL);
+ gchar *selector = g_strconcat ("img[", ns, "\\:src^=\"cid:\"]", NULL);
+ gint ns_length, jj;
+
+ list = webkit_dom_document_query_selector_all (
+ document, selector, NULL);
+ ns_length = webkit_dom_node_list_get_length (list);
+ for (jj = 0; jj < ns_length; jj++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);
+
+ set_base64_to_element_attribute (
+ view, WEBKIT_DOM_ELEMENT (node), attribute_ns);
+ }
+
+ g_free (attribute_ns);
+ g_free (selector);
+ }
+ g_free (name);
+ }
+
+ list = webkit_dom_document_query_selector_all (
+ document, "[background^=\"cid:\"]", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+
+ set_base64_to_element_attribute (
+ view, WEBKIT_DOM_ELEMENT (node), "background");
+ }
+ g_hash_table_remove_all (view->priv->inline_images);
+}
+
+/* For purpose of this function see e-mail-formatter-quote.c */
+static void
+put_body_in_citation (WebKitDOMDocument *document)
+{
+ WebKitDOMElement *cite_body = webkit_dom_document_query_selector (
+ document, "span.-x-evo-cite-body", NULL);
+
+ if (cite_body) {
+ WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
+ gchar *inner_html, *with_citation;
+
+ webkit_dom_node_remove_child (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (cite_body),
+ NULL);
+
+ inner_html = webkit_dom_html_element_get_inner_html (body);
+ with_citation = g_strconcat (
+ "<blockquote type=\"cite\" id=\"-x-evo-main-cite\">",
+ inner_html, "</span>", NULL);
+ webkit_dom_html_element_set_inner_html (body, with_citation, NULL);
+ g_free (inner_html);
+ g_free (with_citation);
+ }
+}
+
+/* For purpose of this function see e-mail-formatter-quote.c */
+static void
+move_elements_to_body (WebKitDOMDocument *document)
+{
+ WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
+ WebKitDOMNodeList *list;
+ gint ii;
+
+ list = webkit_dom_document_query_selector_all (
+ document, "span.-x-evo-to-body", NULL);
+ for (ii = webkit_dom_node_list_get_length (list) - 1; ii >= 0; ii--) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+
+ while (webkit_dom_node_has_child_nodes (node)) {
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (body),
+ webkit_dom_node_get_first_child (node),
+ webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (body)),
+ NULL);
+ }
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (node),
+ NULL);
+ }
+}
+
+static void
+repair_gmail_blockquotes (WebKitDOMDocument *document)
+{
+ WebKitDOMNodeList *list;
+ gint ii, length;
+
+ list = webkit_dom_document_query_selector_all (
+ document, "blockquote.gmail_quote", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+
+ webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class");
+ webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "style");
+ webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "type", "cite", NULL);
+ }
+}
+
+static void
+html_editor_view_load_status_changed (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+ WebKitLoadStatus status;
+
+ status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view));
+ if (status != WEBKIT_LOAD_FINISHED)
+ return;
+
+ view->priv->reload_in_progress = FALSE;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+
+ webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (body), "style");
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-message", "", NULL);
+
+ put_body_in_citation (document);
+ move_elements_to_body (document);
+ repair_gmail_blockquotes (document);
+
+ /* Register on input event that is called when the content (body) is modified */
+ webkit_dom_event_target_add_event_listener (
+ WEBKIT_DOM_EVENT_TARGET (body),
+ "input",
+ G_CALLBACK (body_input_event_cb),
+ FALSE,
+ view);
+
+ if (view->priv->html_mode)
+ change_cid_images_src_to_base64 (view);
+}
+
+/* Based on original use_pictograms() from GtkHTML */
+static const gchar *emoticons_chars =
+ /* 0 */ "DO)(|/PQ*!"
+ /* 10 */ "S\0:-\0:\0:-\0"
+ /* 20 */ ":\0:;=-\"\0:;"
+ /* 30 */ "B\"|\0:-'\0:X"
+ /* 40 */ "\0:\0:-\0:\0:-"
+ /* 50 */ "\0:\0:-\0:\0:-"
+ /* 60 */ "\0:\0:\0:-\0:\0"
+ /* 70 */ ":-\0:\0:-\0:\0";
+static gint emoticons_states[] = {
+ /* 0 */ 12, 17, 22, 34, 43, 48, 53, 58, 65, 70,
+ /* 10 */ 75, 0, -15, 15, 0, -15, 0, -17, 20, 0,
+ /* 20 */ -17, 0, -14, -20, -14, 28, 63, 0, -14, -20,
+ /* 30 */ -3, 63, -18, 0, -12, 38, 41, 0, -12, -2,
+ /* 40 */ 0, -4, 0, -10, 46, 0, -10, 0, -19, 51,
+ /* 50 */ 0, -19, 0, -11, 56, 0, -11, 0, -13, 61,
+ /* 60 */ 0, -13, 0, -6, 0, 68, -7, 0, -7, 0,
+ /* 70 */ -16, 73, 0, -16, 0, -21, 78, 0, -21, 0 };
+static const gchar *emoticons_icon_names[] = {
+ "face-angel",
+ "face-angry",
+ "face-cool",
+ "face-crying",
+ "face-devilish",
+ "face-embarrassed",
+ "face-kiss",
+ "face-laugh", /* not used */
+ "face-monkey", /* not used */
+ "face-plain",
+ "face-raspberry",
+ "face-sad",
+ "face-sick",
+ "face-smile",
+ "face-smile-big",
+ "face-smirk",
+ "face-surprise",
+ "face-tired",
+ "face-uncertain",
+ "face-wink",
+ "face-worried"
+};
+
+static void
+html_editor_view_check_magic_links (EHTMLEditorView *view,
+ WebKitDOMRange *range,
+ gboolean include_space_by_user,
+ GdkEventKey *event)
+{
+ gchar *node_text;
+ gchar **urls;
+ GRegex *regex = NULL;
+ GMatchInfo *match_info;
+ gint start_pos_url, end_pos_url;
+ WebKitDOMNode *node;
+ gboolean include_space = FALSE;
+ gboolean return_pressed = FALSE;
+
+ if (event != NULL) {
+ if ((event->keyval == GDK_KEY_Return) ||
+ (event->keyval == GDK_KEY_Linefeed) ||
+ (event->keyval == GDK_KEY_KP_Enter)) {
+
+ return_pressed = TRUE;
+ }
+
+ if (event->keyval == GDK_KEY_space)
+ include_space = TRUE;
+ } else {
+ include_space = include_space_by_user;
+ }
+
+ node = webkit_dom_range_get_end_container (range, NULL);
+
+ if (return_pressed)
+ node = webkit_dom_node_get_previous_sibling (node);
+
+ if (!node)
+ return;
+
+ if (!WEBKIT_DOM_IS_TEXT (node)) {
+ if (webkit_dom_node_has_child_nodes (node))
+ node = webkit_dom_node_get_first_child (node);
+ if (!WEBKIT_DOM_IS_TEXT (node))
+ return;
+ }
+
+ node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
+ if (!node_text || !(*node_text) || !g_utf8_validate (node_text, -1, NULL))
+ return;
+
+ regex = g_regex_new (include_space ? URL_PATTERN_SPACE : URL_PATTERN, 0, 0, NULL);
+
+ if (!regex) {
+ g_free (node_text);
+ return;
+ }
+
+ g_regex_match_all (regex, node_text, G_REGEX_MATCH_NOTEMPTY, &match_info);
+ urls = g_match_info_fetch_all (match_info);
+
+ if (urls) {
+ gchar *final_url, *url_end_raw;
+ glong url_start, url_end, url_length;
+ WebKitDOMDocument *document;
+ WebKitDOMNode *url_text_node_clone;
+ WebKitDOMText *url_text_node;
+ WebKitDOMElement *anchor;
+ const gchar* url_text;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ if (!return_pressed)
+ e_html_editor_selection_save_caret_position (
+ e_html_editor_view_get_selection (view));
+
+ g_match_info_fetch_pos (match_info, 0, &start_pos_url, &end_pos_url);
+
+ /* Get start and end position of url in node's text because positions
+ * that we get from g_match_info_fetch_pos are not UTF-8 aware */
+ url_end_raw = g_strndup(node_text, end_pos_url);
+ url_end = g_utf8_strlen (url_end_raw, -1);
+
+ url_length = g_utf8_strlen (urls[0], -1);
+ url_start = url_end - url_length;
+
+ webkit_dom_text_split_text (
+ WEBKIT_DOM_TEXT (node),
+ include_space ? url_end - 1 : url_end,
+ NULL);
+
+ url_text_node = webkit_dom_text_split_text (
+ WEBKIT_DOM_TEXT (node), url_start, NULL);
+ url_text_node_clone = webkit_dom_node_clone_node (
+ WEBKIT_DOM_NODE (url_text_node), TRUE);
+ url_text = webkit_dom_text_get_whole_text (
+ WEBKIT_DOM_TEXT (url_text_node_clone));
+
+ final_url = g_strconcat (
+ g_str_has_prefix (url_text, "www") ? "http://" : "", url_text, NULL);
+
+ /* Create and prepare new anchor element */
+ anchor = webkit_dom_document_create_element (document, "A", NULL);
+
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (anchor),
+ url_text,
+ NULL);
+
+ webkit_dom_html_anchor_element_set_href (
+ WEBKIT_DOM_HTML_ANCHOR_ELEMENT (anchor),
+ final_url);
+
+ /* Insert new anchor element into document */
+ webkit_dom_node_replace_child (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (anchor),
+ WEBKIT_DOM_NODE (url_text_node),
+ NULL);
+
+ if (!return_pressed)
+ e_html_editor_selection_restore_caret_position (
+ e_html_editor_view_get_selection (view));
+
+ g_free (url_end_raw);
+ g_free (final_url);
+ } else {
+ WebKitDOMElement *parent;
+ WebKitDOMNode *prev_sibling;
+ gchar *href, *text, *url;
+ gint diff;
+ const char* text_to_append;
+ gboolean appending_to_link = FALSE;
+
+ parent = webkit_dom_node_get_parent_element (node);
+ prev_sibling = webkit_dom_node_get_previous_sibling (node);
+
+ /* If previous sibling is ANCHOR and actual text node is not beginning with
+ * space => we're appending to link */
+ if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) {
+ text_to_append = webkit_dom_node_get_text_content (node);
+ if (g_strcmp0 (text_to_append, "") != 0 &&
+ !g_unichar_isspace (g_utf8_get_char (text_to_append))) {
+
+ appending_to_link = TRUE;
+ parent = WEBKIT_DOM_ELEMENT (prev_sibling);
+ }
+ }
+
+ /* If parent is ANCHOR => we're editing the link */
+ if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) && !appending_to_link) {
+ g_match_info_free (match_info);
+ g_regex_unref (regex);
+ g_free (node_text);
+ return;
+ }
+
+ /* edit only if href and description are the same */
+ href = webkit_dom_html_anchor_element_get_href (
+ WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent));
+
+ if (appending_to_link) {
+ gchar *inner_text;
+
+ inner_text =
+ webkit_dom_html_element_get_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (parent)),
+
+ text = g_strconcat (inner_text, text_to_append, NULL);
+ g_free (inner_text);
+ } else
+ text = webkit_dom_html_element_get_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (parent));
+
+ if (strstr (href, "://") && !strstr (text, "://")) {
+ url = strstr (href, "://") + 3;
+ diff = strlen (text) - strlen (url);
+
+ if (text [strlen (text) - 1] != '/')
+ diff++;
+
+ if ((g_strcmp0 (url, text) != 0 && ABS (diff) == 1) || appending_to_link) {
+ gchar *inner_html, *protocol, *new_href;
+
+ protocol = g_strndup (href, strstr (href, "://") - href + 3);
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (parent));
+ new_href = g_strconcat (
+ protocol, inner_html, appending_to_link ? text_to_append : "", NULL);
+
+ webkit_dom_html_anchor_element_set_href (
+ WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent),
+ new_href);
+
+ if (appending_to_link) {
+ gchar *tmp;
+
+ tmp = g_strconcat (inner_html, text_to_append, NULL);
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (parent),
+ tmp,
+ NULL);
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node),
+ node, NULL);
+
+ g_free (tmp);
+ }
+
+ g_free (new_href);
+ g_free (protocol);
+ g_free (inner_html);
+ }
+ } else {
+ diff = strlen (text) - strlen (href);
+ if (text [strlen (text) - 1] != '/')
+ diff++;
+
+ if ((g_strcmp0 (href, text) != 0 && ABS (diff) == 1) || appending_to_link) {
+ gchar *inner_html;
+ gchar *new_href;
+
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (parent));
+ new_href = g_strconcat (
+ inner_html,
+ appending_to_link ? text_to_append : "",
+ NULL);
+
+ webkit_dom_html_anchor_element_set_href (
+ WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent),
+ new_href);
+
+ if (appending_to_link) {
+ gchar *tmp;
+
+ tmp = g_strconcat (inner_html, text_to_append, NULL);
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (parent),
+ tmp,
+ NULL);
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node),
+ node, NULL);
+
+ g_free (tmp);
+ }
+
+ g_free (new_href);
+ g_free (inner_html);
+ }
+
+ }
+ g_free (text);
+ g_free (href);
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (regex);
+ g_free (node_text);
+}
+
+typedef struct _LoadContext LoadContext;
+
+struct _LoadContext {
+ EHTMLEditorView *view;
+ gchar *content_type;
+ gchar *name;
+ EEmoticon *emoticon;
+};
+
+static LoadContext *
+emoticon_load_context_new (EHTMLEditorView *view,
+ EEmoticon *emoticon)
+{
+ LoadContext *load_context;
+
+ load_context = g_slice_new0 (LoadContext);
+ load_context->view = view;
+ load_context->emoticon = emoticon;
+
+ return load_context;
+}
+
+static void
+emoticon_load_context_free (LoadContext *load_context)
+{
+ g_free (load_context->content_type);
+ g_free (load_context->name);
+ g_slice_free (LoadContext, load_context);
+}
+
+static void
+emoticon_read_async_cb (GFile *file,
+ GAsyncResult *result,
+ LoadContext *load_context)
+{
+ EHTMLEditorView *view = load_context->view;
+ EEmoticon *emoticon = load_context->emoticon;
+ GError *error = NULL;
+ gchar *html, *node_text = NULL, *mime_type;
+ gchar *base64_encoded, *output, *data;
+ const gchar *emoticon_start;
+ GFileInputStream *input_stream;
+ GOutputStream *output_stream;
+ gssize size;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *span, *caret_position;
+ WebKitDOMNode *node;
+ WebKitDOMRange *range;
+
+ input_stream = g_file_read_finish (file, result, &error);
+ g_return_if_fail (!error && input_stream);
+
+ output_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+
+ size = g_output_stream_splice (
+ output_stream, G_INPUT_STREAM (input_stream),
+ G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error);
+
+ if (error || (size == -1))
+ goto out;
+
+ caret_position = e_html_editor_selection_save_caret_position (
+ e_html_editor_view_get_selection (view));
+
+ if (caret_position) {
+ WebKitDOMNode *parent;
+
+ parent = webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (caret_position));
+
+ /* Situation when caret is restored in body and not in paragraph */
+ if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
+ caret_position = WEBKIT_DOM_ELEMENT (
+ webkit_dom_node_remove_child (
+ WEBKIT_DOM_NODE (parent),
+ WEBKIT_DOM_NODE (caret_position),
+ NULL));
+
+ caret_position = WEBKIT_DOM_ELEMENT (
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (parent)),
+ WEBKIT_DOM_NODE (caret_position),
+ webkit_dom_node_get_first_child (
+ webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (parent))),
+ NULL));
+ }
+ }
+
+ mime_type = g_content_type_get_mime_type (load_context->content_type);
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ range = html_editor_view_get_dom_range (view);
+ node = webkit_dom_range_get_end_container (range, NULL);
+ if (WEBKIT_DOM_IS_TEXT (node))
+ node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
+ span = webkit_dom_document_create_element (document, "SPAN", NULL);
+
+ data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (output_stream));
+
+ base64_encoded = g_base64_encode ((const guchar *) data, size);
+ output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
+
+ /* Insert span with image representation and another one with text
+ * represetation and hide/show them dependant on active composer mode */
+ /* &#8203 == UNICODE_ZERO_WIDTH_SPACE */
+ html = g_strdup_printf (
+ "<span class=\"-x-evo-smiley-wrapper -x-evo-resizable-wrapper\">"
+ "<img src=\"%s\" alt=\"%s\" x-evo-smiley=\"%s\" "
+ "class=\"-x-evo-smiley-img\" data-inline data-name=\"%s\"/>"
+ "<span class=\"-x-evo-smiley-text\" style=\"display: none;\">%s"
+ "</span></span>&#8203;",
+ output, emoticon ? emoticon->text_face : "", emoticon->icon_name,
+ load_context->name, emoticon ? emoticon->text_face : "");
+
+ span = WEBKIT_DOM_ELEMENT (
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (caret_position)),
+ WEBKIT_DOM_NODE (span),
+ WEBKIT_DOM_NODE (caret_position),
+ NULL));
+
+ webkit_dom_html_element_set_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (span), html, NULL);
+
+ if (node_text) {
+ emoticon_start = g_utf8_strrchr (
+ node_text, -1, g_utf8_get_char (emoticon->text_face));
+ if (emoticon_start) {
+ webkit_dom_character_data_delete_data (
+ WEBKIT_DOM_CHARACTER_DATA (node),
+ g_utf8_strlen (node_text, -1) - strlen (emoticon_start),
+ strlen (emoticon->text_face),
+ NULL);
+ }
+ }
+
+ e_html_editor_selection_restore_caret_position (
+ e_html_editor_view_get_selection (view));
+
+ e_html_editor_view_set_changed (view, TRUE);
+
+ g_free (html);
+ g_free (node_text);
+ g_free (base64_encoded);
+ g_free (output);
+ g_free (mime_type);
+ g_object_unref (output_stream);
+ out:
+ emoticon_load_context_free (load_context);
+}
+
+static void
+emoticon_query_info_async_cb (GFile *file,
+ GAsyncResult *result,
+ LoadContext *load_context)
+{
+ GError *error = NULL;
+ GFileInfo *info;
+
+ info = g_file_query_info_finish (file, result, &error);
+ g_return_if_fail (!error && info);
+
+ load_context->content_type = g_strdup (g_file_info_get_content_type (info));
+ load_context->name = g_strdup (g_file_info_get_name (info));
+
+ g_file_read_async (
+ file, G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback) emoticon_read_async_cb, load_context);
+
+ g_object_unref (info);
+}
+
+void
+e_html_editor_view_insert_smiley (EHTMLEditorView *view,
+ EEmoticon *emoticon)
+{
+ GFile *file;
+ gchar *filename_uri;
+ LoadContext *load_context;
+
+ filename_uri = e_emoticon_get_uri (emoticon);
+ g_return_if_fail (filename_uri != NULL);
+
+ load_context = emoticon_load_context_new (view, emoticon);
+
+ file = g_file_new_for_uri (filename_uri);
+ g_file_query_info_async (
+ file, "standard::*", G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback) emoticon_query_info_async_cb, load_context);
+
+ g_free (filename_uri);
+ g_object_unref (file);
+}
+
+static void
+html_editor_view_check_magic_smileys (EHTMLEditorView *view,
+ WebKitDOMRange *range)
+{
+ gint pos;
+ gint state;
+ gint relative;
+ gint start;
+ gchar *node_text;
+ gunichar uc;
+ WebKitDOMNode *node;
+
+ node = webkit_dom_range_get_end_container (range, NULL);
+ if (!WEBKIT_DOM_IS_TEXT (node))
+ return;
+
+ node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
+ if (node_text == NULL)
+ return;
+
+ start = webkit_dom_range_get_end_offset (range, NULL) - 1;
+ pos = start;
+ state = 0;
+ while (pos >= 0) {
+ uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos));
+ relative = 0;
+ while (emoticons_chars[state + relative]) {
+ if (emoticons_chars[state + relative] == uc)
+ break;
+ relative++;
+ }
+ state = emoticons_states[state + relative];
+ /* 0 .. not found, -n .. found n-th */
+ if (state <= 0)
+ break;
+ pos--;
+ }
+
+ /* Special case needed to recognize angel and devilish. */
+ if (pos > 0 && state == -14) {
+ uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1));
+ if (uc == 'O') {
+ state = -1;
+ pos--;
+ } else if (uc == '>') {
+ state = -5;
+ pos--;
+ }
+ }
+
+ if (state < 0) {
+ const EEmoticon *emoticon;
+
+ if (pos > 0) {
+ uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1));
+ if (!g_unichar_isspace (uc)) {
+ g_free (node_text);
+ return;
+ }
+ }
+
+ emoticon = (e_emoticon_chooser_lookup_emoticon (
+ emoticons_icon_names[-state - 1]));
+ e_html_editor_view_insert_smiley (view, (EEmoticon *) emoticon);
+ }
+
+ g_free (node_text);
+}
+
+static void
+html_editor_view_set_links_active (EHTMLEditorView *view,
+ gboolean active)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMElement *style;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ if (active) {
+ style = webkit_dom_document_get_element_by_id (
+ document, "--evolution-editor-style-a");
+ if (style) {
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (style)),
+ WEBKIT_DOM_NODE (style), NULL);
+ }
+ } else {
+ WebKitDOMHTMLHeadElement *head;
+ head = webkit_dom_document_get_head (document);
+
+ style = webkit_dom_document_create_element (document, "STYLE", NULL);
+ webkit_dom_element_set_id (style, "--evolution-editor-style-a");
+ webkit_dom_html_element_set_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (style), "a { cursor: text; }", NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style), NULL);
+ }
+}
+
+static void
+clipboard_text_received (GtkClipboard *clipboard,
+ const gchar *text,
+ EHTMLEditorView *view)
+{
+ EHTMLEditorSelection *selection;
+ gchar *escaped_text;
+ WebKitDOMDocument *document;
+ WebKitDOMDOMWindow *window;
+ WebKitDOMDOMSelection *dom_selection;
+ WebKitDOMElement *blockquote, *element;
+ WebKitDOMNode *node;
+ WebKitDOMRange *range;
+
+ if (!text || !*text)
+ return;
+
+ selection = e_html_editor_view_get_selection (view);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ window = webkit_dom_document_get_default_view (document);
+ dom_selection = webkit_dom_dom_window_get_selection (window);
+
+ /* This is a trick to escape any HTML characters (like <, > or &).
+ * <textarea> automatically replaces all these unsafe characters
+ * by &lt;, &gt; etc. */
+ element = webkit_dom_document_create_element (document, "textarea", NULL);
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (element), text, NULL);
+ escaped_text = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (element));
+
+ element = webkit_dom_document_create_element (document, "pre", NULL);
+
+ webkit_dom_html_element_set_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (element), escaped_text, NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (element),
+ e_html_editor_selection_get_caret_position_node (document),
+ NULL);
+
+ blockquote = webkit_dom_document_create_element (document, "blockquote", NULL);
+ webkit_dom_element_set_attribute (blockquote, "type", "cite", NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (blockquote), WEBKIT_DOM_NODE (element), NULL);
+
+ if (!e_html_editor_view_get_html_mode (view))
+ e_html_editor_view_quote_plain_text_element (view, element);
+
+ range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
+ node = webkit_dom_range_get_end_container (range, NULL);
+
+ webkit_dom_node_append_child (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (blockquote),
+ NULL);
+
+ e_html_editor_selection_restore_caret_position (selection);
+
+ e_html_editor_view_force_spell_check_for_current_paragraph (view);
+
+ g_free (escaped_text);
+}
+
+static void
+html_editor_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CHANGED:
+ e_html_editor_view_set_changed (
+ E_HTML_EDITOR_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_HTML_MODE:
+ e_html_editor_view_set_html_mode (
+ E_HTML_EDITOR_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_INLINE_SPELLING:
+ e_html_editor_view_set_inline_spelling (
+ E_HTML_EDITOR_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_MAGIC_LINKS:
+ e_html_editor_view_set_magic_links (
+ E_HTML_EDITOR_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_MAGIC_SMILEYS:
+ e_html_editor_view_set_magic_smileys (
+ E_HTML_EDITOR_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+html_editor_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CAN_COPY:
+ g_value_set_boolean (
+ value, webkit_web_view_can_copy_clipboard (
+ WEBKIT_WEB_VIEW (object)));
+ return;
+
+ case PROP_CAN_CUT:
+ g_value_set_boolean (
+ value, webkit_web_view_can_cut_clipboard (
+ WEBKIT_WEB_VIEW (object)));
+ return;
+
+ case PROP_CAN_PASTE:
+ g_value_set_boolean (
+ value, webkit_web_view_can_paste_clipboard (
+ WEBKIT_WEB_VIEW (object)));
+ return;
+
+ case PROP_CAN_REDO:
+ g_value_set_boolean (
+ value, webkit_web_view_can_redo (
+ WEBKIT_WEB_VIEW (object)));
+ return;
+
+ case PROP_CAN_UNDO:
+ g_value_set_boolean (
+ value, webkit_web_view_can_undo (
+ WEBKIT_WEB_VIEW (object)));
+ return;
+
+ case PROP_CHANGED:
+ g_value_set_boolean (
+ value, e_html_editor_view_get_changed (
+ E_HTML_EDITOR_VIEW (object)));
+ return;
+
+ case PROP_HTML_MODE:
+ g_value_set_boolean (
+ value, e_html_editor_view_get_html_mode (
+ E_HTML_EDITOR_VIEW (object)));
+ return;
+
+ case PROP_INLINE_SPELLING:
+ g_value_set_boolean (
+ value, e_html_editor_view_get_inline_spelling (
+ E_HTML_EDITOR_VIEW (object)));
+ return;
+
+ case PROP_MAGIC_LINKS:
+ g_value_set_boolean (
+ value, e_html_editor_view_get_magic_links (
+ E_HTML_EDITOR_VIEW (object)));
+ return;
+
+ case PROP_MAGIC_SMILEYS:
+ g_value_set_boolean (
+ value, e_html_editor_view_get_magic_smileys (
+ E_HTML_EDITOR_VIEW (object)));
+ return;
+
+ case PROP_SPELL_CHECKER:
+ g_value_set_object (
+ value, e_html_editor_view_get_spell_checker (
+ E_HTML_EDITOR_VIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+html_editor_view_dispose (GObject *object)
+{
+ EHTMLEditorViewPrivate *priv;
+
+ priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (object);
+
+ g_clear_object (&priv->selection);
+
+ if (priv->convertor_web_view != NULL) {
+ g_object_unref (priv->convertor_web_view);
+ priv->convertor_web_view = NULL;
+ }
+
+ if (priv->aliasing_settings != NULL) {
+ g_signal_handlers_disconnect_matched (
+ priv->aliasing_settings, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, object);
+ g_object_unref (priv->aliasing_settings);
+ priv->aliasing_settings = NULL;
+ }
+
+ if (priv->font_settings != NULL) {
+ g_signal_handlers_disconnect_matched (
+ priv->font_settings, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, object);
+ g_object_unref (priv->font_settings);
+ priv->font_settings = NULL;
+ }
+
+ g_hash_table_remove_all (priv->inline_images);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_html_editor_view_parent_class)->dispose (object);
+}
+
+static void
+html_editor_view_finalize (GObject *object)
+{
+ EHTMLEditorViewPrivate *priv;
+
+ priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (object);
+
+ g_hash_table_destroy (priv->inline_images);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_html_editor_view_parent_class)->finalize (object);
+}
+
+static void
+html_editor_view_constructed (GObject *object)
+{
+ e_extensible_load_extensions (E_EXTENSIBLE (object));
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_html_editor_view_parent_class)->constructed (object);
+}
+
+static void
+html_editor_view_save_element_under_mouse_click (GtkWidget *widget)
+{
+ gint x, y;
+ GdkDeviceManager *device_manager;
+ GdkDevice *pointer;
+ EHTMLEditorView *view;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *element;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_VIEW (widget));
+
+ device_manager = gdk_display_get_device_manager (
+ gtk_widget_get_display (GTK_WIDGET (widget)));
+ pointer = gdk_device_manager_get_client_pointer (device_manager);
+ gdk_window_get_device_position (
+ gtk_widget_get_window (GTK_WIDGET (widget)), pointer, &x, &y, NULL);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));
+ element = webkit_dom_document_element_from_point (document, x, y);
+
+ view = E_HTML_EDITOR_VIEW (widget);
+ view->priv->element_under_mouse = element;
+}
+
+static gboolean
+html_editor_view_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ gboolean event_handled;
+
+ if (event->button == 2) {
+ /* Middle click paste */
+ g_signal_emit (widget, signals[PASTE_PRIMARY_CLIPBOARD], 0);
+ event_handled = TRUE;
+ } else if (event->button == 3) {
+ html_editor_view_save_element_under_mouse_click (widget);
+ g_signal_emit (
+ widget, signals[POPUP_EVENT],
+ 0, event, &event_handled);
+ } else {
+ event_handled = FALSE;
+ }
+
+ if (event_handled)
+ return TRUE;
+
+ /* Chain up to parent's button_press_event() method. */
+ return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
+ button_press_event (widget, event);
+}
+
+static gboolean
+html_editor_view_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ WebKitWebView *webview;
+ WebKitHitTestResult *hit_test;
+ WebKitHitTestResultContext context;
+ gchar *uri;
+
+ webview = WEBKIT_WEB_VIEW (widget);
+ hit_test = webkit_web_view_get_hit_test_result (webview, event);
+
+ g_object_get (
+ hit_test,
+ "context", &context,
+ "link-uri", &uri,
+ NULL);
+
+ g_object_unref (hit_test);
+
+ /* Left click on a link */
+ if ((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) &&
+ (event->button == 1)) {
+
+ /* Ctrl + Left Click on link opens it, otherwise ignore the
+ * click completely */
+ if (event->state & GDK_CONTROL_MASK) {
+ GtkWidget *toplevel;
+ GdkScreen *screen;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
+ gtk_show_uri (screen, uri, event->time, NULL);
+ g_free (uri);
+ }
+
+ return TRUE;
+ }
+
+ g_free (uri);
+
+ /* Chain up to parent's button_release_event() method. */
+ return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
+ button_release_event (widget, event);
+}
+
+static gboolean
+insert_new_line_into_citation (EHTMLEditorView *view)
+{
+ EHTMLEditorSelection *selection;
+ gboolean html_mode, ret_val;
+
+ html_mode = e_html_editor_view_get_html_mode (view);
+ selection = e_html_editor_view_get_selection (view);
+
+ ret_val = e_html_editor_view_exec_command (
+ view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);
+
+ if (ret_val && !html_mode) {
+ WebKitDOMElement *element;
+ WebKitDOMDocument *document;
+ WebKitDOMNode *next_sibling;
+
+ document = webkit_web_view_get_dom_document (
+ WEBKIT_WEB_VIEW (view));
+
+ element = webkit_dom_document_query_selector (
+ document, "body>br", NULL);
+
+ next_sibling = webkit_dom_node_get_next_sibling (
+ WEBKIT_DOM_NODE (element));
+
+ if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_sibling)) {
+ /* Quote content */
+ next_sibling = WEBKIT_DOM_NODE (
+ e_html_editor_view_quote_plain_text_element (
+ view, WEBKIT_DOM_ELEMENT (next_sibling)));
+ /* Renew spellcheck */
+ e_html_editor_view_force_spell_check (view);
+ /* Insert caret node on right position */
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (element)),
+ e_html_editor_selection_get_caret_position_node (
+ document),
+ WEBKIT_DOM_NODE (element),
+ NULL);
+ /* Restore caret position */
+ e_html_editor_selection_restore_caret_position (
+ selection);
+ }
+ }
+
+ return ret_val;
+}
+
+static gboolean
+prevent_from_deleting_last_element_in_body (EHTMLEditorView *view)
+{
+ gboolean ret_val = FALSE;
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+ WebKitDOMNodeList *list;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+
+ list = webkit_dom_node_get_child_nodes (WEBKIT_DOM_NODE (body));
+
+ if (webkit_dom_node_list_get_length (list) <= 1) {
+ gchar *content;
+
+ content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (body));
+
+ if (!*content)
+ ret_val = TRUE;
+
+ g_free (content);
+
+ if (webkit_dom_element_query_selector (WEBKIT_DOM_ELEMENT (body), "img", NULL))
+ ret_val = FALSE;
+ }
+
+ return ret_val;
+}
+
+static gboolean
+html_editor_view_key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ EHTMLEditorView *view = E_HTML_EDITOR_VIEW (widget);
+
+ if (event->keyval == GDK_KEY_Tab)
+ return e_html_editor_view_exec_command (
+ view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, "\t");
+
+ if ((event->keyval == GDK_KEY_Control_L) ||
+ (event->keyval == GDK_KEY_Control_R)) {
+
+ html_editor_view_set_links_active (view, TRUE);
+ }
+
+ if ((event->keyval == GDK_KEY_Return) ||
+ (event->keyval == GDK_KEY_KP_Enter)) {
+ EHTMLEditorSelection *selection;
+
+ selection = e_html_editor_view_get_selection (view);
+ /* When user presses ENTER in a citation block, WebKit does
+ * not break the citation automatically, so we need to use
+ * the special command to do it. */
+ if (e_html_editor_selection_is_citation (selection))
+ return insert_new_line_into_citation (view);
+ }
+
+ /* BackSpace in indented block decrease indent level by one */
+ if (event->keyval == GDK_KEY_BackSpace) {
+ EHTMLEditorSelection *selection;
+
+ selection = e_html_editor_view_get_selection (view);
+ if (e_html_editor_selection_is_indented (selection)) {
+ WebKitDOMElement *caret;
+
+ caret = e_html_editor_selection_save_caret_position (selection);
+
+ if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (caret))) {
+ e_html_editor_selection_clear_caret_position_marker (selection);
+ e_html_editor_selection_unindent (selection);
+ return TRUE;
+ } else
+ e_html_editor_selection_clear_caret_position_marker (selection);
+ }
+
+ if (prevent_from_deleting_last_element_in_body (view))
+ return TRUE;
+ }
+
+ /* Chain up to parent's key_press_event() method. */
+ return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
+ key_press_event (widget, event);
+}
+
+static void
+mark_node_as_paragraph_after_ending_list (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document)
+{
+ gint ii, length;
+ WebKitDOMNodeList *list;
+
+ /* When pressing Enter on empty line in the list WebKit will end that
+ * list and inserts <div><br></div> so mark it for wrapping */
+ list = webkit_dom_document_query_selector_all (
+ document, "body > div:not(.-x-evo-paragraph) > br", NULL);
+
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node = webkit_dom_node_get_parent_node (
+ webkit_dom_node_list_item (list, ii));
+
+ e_html_editor_selection_set_paragraph_style (
+ selection, WEBKIT_DOM_ELEMENT (node), -1, 0, "");
+ }
+}
+
+static gboolean
+surround_text_with_paragraph_if_needed (EHTMLEditorSelection *selection,
+ WebKitDOMDocument *document,
+ WebKitDOMNode *node)
+{
+ WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node);
+ WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (node);
+ WebKitDOMElement *element;
+
+ /* All text in composer has to be written in div elements, so if
+ * we are writing something straight to the body, surround it with
+ * paragraph */
+ if (WEBKIT_DOM_IS_TEXT (node) &&
+ WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node))) {
+ element = e_html_editor_selection_put_node_into_paragraph (
+ selection,
+ document,
+ node,
+ e_html_editor_selection_get_caret_position_node (document));
+
+ if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling)) {
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (next_sibling),
+ next_sibling,
+ NULL);
+ }
+
+ /* Tab character */
+ if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
+ element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "Apple-tab-span")) {
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (element),
+ prev_sibling,
+ webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (element)),
+ NULL);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+html_editor_view_key_release_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMRange *range;
+ EHTMLEditorView *view;
+ EHTMLEditorSelection *selection;
+
+ view = E_HTML_EDITOR_VIEW (widget);
+ range = html_editor_view_get_dom_range (view);
+ selection = e_html_editor_view_get_selection (view);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (widget));
+
+ if (view->priv->magic_smileys &&
+ view->priv->html_mode) {
+ html_editor_view_check_magic_smileys (view, range);
+ }
+
+ if ((event->keyval == GDK_KEY_Return) ||
+ (event->keyval == GDK_KEY_Linefeed) ||
+ (event->keyval == GDK_KEY_KP_Enter) ||
+ (event->keyval == GDK_KEY_space)) {
+
+ html_editor_view_check_magic_links (view, range, FALSE, event);
+
+ mark_node_as_paragraph_after_ending_list (selection, document);
+ } else {
+ WebKitDOMNode *node;
+
+ node = webkit_dom_range_get_end_container (range, NULL);
+
+ if (surround_text_with_paragraph_if_needed (selection, document, node)) {
+ e_html_editor_selection_restore_caret_position (selection);
+ node = webkit_dom_range_get_end_container (range, NULL);
+ range = html_editor_view_get_dom_range (view);
+ }
+
+ if (WEBKIT_DOM_IS_TEXT (node)) {
+ gchar *text;
+
+ text = webkit_dom_node_get_text_content (node);
+
+ if (g_strcmp0 (text, "") != 0 && !g_unichar_isspace (g_utf8_get_char (text))) {
+ WebKitDOMNode *prev_sibling;
+
+ prev_sibling = webkit_dom_node_get_previous_sibling (node);
+
+ if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling))
+ html_editor_view_check_magic_links (view, range, FALSE, event);
+ }
+ g_free (text);
+ }
+ }
+
+ if ((event->keyval == GDK_KEY_Control_L) ||
+ (event->keyval == GDK_KEY_Control_R)) {
+
+ html_editor_view_set_links_active (view, FALSE);
+ }
+
+ /* Chain up to parent's key_release_event() method. */
+ return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
+ key_release_event (widget, event);
+}
+
+static void
+html_editor_view_paste_clipboard_quoted (EHTMLEditorView *view)
+{
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_clipboard_get_for_display (
+ gdk_display_get_default (),
+ GDK_SELECTION_CLIPBOARD);
+
+ gtk_clipboard_request_text (
+ clipboard,
+ (GtkClipboardTextReceivedFunc) clipboard_text_received,
+ view);
+}
+
+static gboolean
+html_editor_view_image_exists_in_cache (const gchar *image_uri)
+{
+ gchar *filename;
+ gchar *hash;
+ gboolean exists = FALSE;
+
+ g_return_val_if_fail (emd_global_http_cache != NULL, FALSE);
+
+ hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, image_uri, -1);
+ filename = camel_data_cache_get_filename (
+ emd_global_http_cache, "http", hash);
+
+ if (filename != NULL) {
+ exists = g_file_test (filename, G_FILE_TEST_EXISTS);
+ g_free (filename);
+ }
+
+ g_free (hash);
+
+ return exists;
+}
+
+static gchar *
+html_editor_view_redirect_uri (EHTMLEditorView *view,
+ const gchar *uri)
+{
+ EImageLoadingPolicy image_policy;
+ GSettings *settings;
+ gboolean uri_is_http;
+
+ uri_is_http =
+ g_str_has_prefix (uri, "http:") ||
+ g_str_has_prefix (uri, "https:") ||
+ g_str_has_prefix (uri, "evo-http:") ||
+ g_str_has_prefix (uri, "evo-https:");
+
+ /* Redirect http(s) request to evo-http(s) protocol.
+ * See EMailRequest for further details about this. */
+ if (uri_is_http) {
+ gchar *new_uri;
+ SoupURI *soup_uri;
+ gboolean image_exists;
+
+ /* Check Evolution's cache */
+ image_exists = html_editor_view_image_exists_in_cache (uri);
+
+ settings = g_settings_new ("org.gnome.evolution.mail");
+ image_policy = g_settings_get_enum (settings, "image-loading-policy");
+ g_object_unref (settings);
+ /* If the URI is not cached and we are not allowed to load it
+ * then redirect to invalid URI, so that webkit would display
+ * a native placeholder for it. */
+ if (!image_exists && (image_policy == E_IMAGE_LOADING_POLICY_NEVER)) {
+ return g_strdup ("about:blank");
+ }
+
+ new_uri = g_strconcat ("evo-", uri, NULL);
+ soup_uri = soup_uri_new (new_uri);
+ g_free (new_uri);
+
+ new_uri = soup_uri_to_string (soup_uri, FALSE);
+
+ soup_uri_free (soup_uri);
+
+ return new_uri;
+ }
+
+ return g_strdup (uri);
+}
+
+static void
+html_editor_view_resource_requested (WebKitWebView *web_view,
+ WebKitWebFrame *frame,
+ WebKitWebResource *resource,
+ WebKitNetworkRequest *request,
+ WebKitNetworkResponse *response,
+ gpointer user_data)
+{
+ const gchar *original_uri;
+
+ original_uri = webkit_network_request_get_uri (request);
+
+ if (original_uri != NULL) {
+ gchar *redirected_uri;
+
+ redirected_uri = html_editor_view_redirect_uri (
+ E_HTML_EDITOR_VIEW (web_view), original_uri);
+
+ webkit_network_request_set_uri (request, redirected_uri);
+
+ g_free (redirected_uri);
+ }
+}
+
+static void
+e_html_editor_view_class_init (EHTMLEditorViewClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EHTMLEditorViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = html_editor_view_get_property;
+ object_class->set_property = html_editor_view_set_property;
+ object_class->dispose = html_editor_view_dispose;
+ object_class->finalize = html_editor_view_finalize;
+ object_class->constructed = html_editor_view_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->button_press_event = html_editor_view_button_press_event;
+ widget_class->button_release_event = html_editor_view_button_release_event;
+ widget_class->key_press_event = html_editor_view_key_press_event;
+ widget_class->key_release_event = html_editor_view_key_release_event;
+
+ class->paste_clipboard_quoted = html_editor_view_paste_clipboard_quoted;
+
+ /**
+ * EHTMLEditorView:can-copy
+ *
+ * Determines whether it's possible to copy to clipboard. The action
+ * is usually disabled when there is no selection to copy.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CAN_COPY,
+ g_param_spec_boolean (
+ "can-copy",
+ "Can Copy",
+ NULL,
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:can-cut
+ *
+ * Determines whether it's possible to cut to clipboard. The action
+ * is usually disabled when there is no selection to cut.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CAN_CUT,
+ g_param_spec_boolean (
+ "can-cut",
+ "Can Cut",
+ NULL,
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:can-paste
+ *
+ * Determines whether it's possible to paste from clipboard. The action
+ * is usually disabled when there is no valid content in clipboard to
+ * paste.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CAN_PASTE,
+ g_param_spec_boolean (
+ "can-paste",
+ "Can Paste",
+ NULL,
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:can-redo
+ *
+ * Determines whether it's possible to redo previous action. The action
+ * is usually disabled when there is no action to redo.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CAN_REDO,
+ g_param_spec_boolean (
+ "can-redo",
+ "Can Redo",
+ NULL,
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:can-undo
+ *
+ * Determines whether it's possible to undo last action. The action
+ * is usually disabled when there is no previous action to undo.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CAN_UNDO,
+ g_param_spec_boolean (
+ "can-undo",
+ "Can Undo",
+ NULL,
+ FALSE,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:changed
+ *
+ * Determines whether document has been modified
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_CHANGED,
+ g_param_spec_boolean (
+ "changed",
+ _("Changed property"),
+ _("Whether editor changed"),
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:html-mode
+ *
+ * Determines whether HTML or plain text mode is enabled.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_HTML_MODE,
+ g_param_spec_boolean (
+ "html-mode",
+ "HTML Mode",
+ "Edit HTML or plain text",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView::inline-spelling
+ *
+ * Determines whether automatic spellchecking is enabled.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_INLINE_SPELLING,
+ g_param_spec_boolean (
+ "inline-spelling",
+ "Inline Spelling",
+ "Check your spelling as you type",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:magic-links
+ *
+ * Determines whether automatic conversion of text links into
+ * HTML links is enabled.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_MAGIC_LINKS,
+ g_param_spec_boolean (
+ "magic-links",
+ "Magic Links",
+ "Make URIs clickable as you type",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:magic-smileys
+ *
+ * Determines whether automatic conversion of text smileys into
+ * images is enabled.
+ */
+ g_object_class_install_property (
+ object_class,
+ PROP_MAGIC_SMILEYS,
+ g_param_spec_boolean (
+ "magic-smileys",
+ "Magic Smileys",
+ "Convert emoticons to images as you type",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:spell-checker:
+ *
+ * The #ESpellChecker used for spell checking.
+ **/
+ g_object_class_install_property (
+ object_class,
+ PROP_SPELL_CHECKER,
+ g_param_spec_object (
+ "spell-checker",
+ "Spell Checker",
+ "The spell checker",
+ E_TYPE_SPELL_CHECKER,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * EHTMLEditorView:popup-event
+ *
+ * Emitted whenever a context menu is requested.
+ */
+ signals[POPUP_EVENT] = g_signal_new (
+ "popup-event",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EHTMLEditorViewClass, popup_event),
+ g_signal_accumulator_true_handled, NULL,
+ e_marshal_BOOLEAN__BOXED,
+ G_TYPE_BOOLEAN, 1,
+ GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+ /**
+ * EHTMLEditorView:paste-primary-clipboad
+ *
+ * Emitted when user presses middle button on EHTMLEditorView
+ */
+ signals[PASTE_PRIMARY_CLIPBOARD] = g_signal_new (
+ "paste-primary-clipboard",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EHTMLEditorViewClass, paste_primary_clipboard),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static GString *
+replace_string_spaces_with_nbsp_in_prefix (const gchar *text)
+{
+ GString *str;
+ gint counter = 0;
+
+ g_return_val_if_fail (text != NULL, NULL);
+
+ str = g_string_new ("");
+
+ while (g_str_has_prefix (text + counter, " ")) {
+ g_string_append (str, "&nbsp;");
+
+ counter++;
+ }
+
+ g_string_append (str, text + counter);
+
+ return str;
+}
+
+/* This parses the HTML code (that contains just text, &nbsp; and BR elements)
+ * into paragraphs.
+ * HTML code in that format we can get by taking innerText from some element,
+ * setting it to another one and finally getting innerHTML from it */
+static void
+parse_html_into_paragraphs (EHTMLEditorView *view,
+ WebKitDOMDocument *document,
+ WebKitDOMElement *blockquote,
+ const gchar *html,
+ gboolean use_pre)
+{
+ const gchar *prev_br, *next_br;
+ gchar *inner_html;
+ gint citation_level = 0;
+ GString *start, *end;
+ gboolean ignore_next_br = FALSE;
+
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (blockquote), "", NULL);
+
+ prev_br = html;
+ next_br = strstr (prev_br, "<br>");
+
+ while (next_br) {
+ gboolean local_ignore_next_br = ignore_next_br;
+ const gchar *citation = NULL, *citation_end = NULL;
+ const gchar *rest = NULL, *with_br = NULL;
+ gchar *to_insert = NULL;
+ WebKitDOMElement *paragraph;
+
+ to_insert = g_utf8_substring (
+ prev_br, 0, g_utf8_pointer_to_offset (prev_br, next_br));
+
+ with_br = strstr (to_insert, "<br>");
+
+ ignore_next_br = FALSE;
+
+ citation = strstr (to_insert, "##CITATION_");
+ if (citation) {
+ if (strstr (to_insert, "##CITATION_START##"))
+ citation_level++;
+ else
+ citation_level--;
+
+ citation_end = strstr (citation + 2, "##");
+ if (citation_end)
+ rest = citation_end + 2;
+ } else {
+ rest = with_br ?
+ to_insert + 4 + (with_br - to_insert) : to_insert;
+ }
+
+ if (use_pre) {
+ paragraph = webkit_dom_document_create_element (
+ document, "pre", NULL);
+ } else {
+ paragraph = e_html_editor_selection_get_paragraph_element (
+ e_html_editor_view_get_selection (view),
+ document, -1, citation_level);
+ }
+
+ if (with_br && !*rest && !citation &&!local_ignore_next_br) {
+ WebKitDOMNode *paragraph_clone;
+
+ paragraph_clone = webkit_dom_node_clone_node (
+ WEBKIT_DOM_NODE (paragraph), TRUE);
+
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (paragraph_clone),
+ "&nbsp;",
+ NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (blockquote),
+ paragraph_clone,
+ NULL);
+ }
+
+ if (citation) {
+ WebKitDOMText *text;
+ gchar *citation_mark;
+
+ citation_mark = g_utf8_substring (
+ citation, 0,
+ g_utf8_pointer_to_offset (
+ citation, citation_end + 2));
+
+ text = webkit_dom_document_create_text_node (
+ document, citation_mark);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (blockquote),
+ WEBKIT_DOM_NODE (text),
+ NULL);
+
+ g_free (citation_mark);
+ }
+
+ if (rest && *rest){
+ GString *space_to_nbsp;
+
+ space_to_nbsp = replace_string_spaces_with_nbsp_in_prefix (rest);
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (paragraph),
+ space_to_nbsp->str,
+ NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (blockquote),
+ WEBKIT_DOM_NODE (paragraph),
+ NULL);
+ g_string_free (space_to_nbsp, TRUE);
+ }
+
+ if (citation_end)
+ ignore_next_br = TRUE;
+
+ prev_br = next_br;
+ next_br = strstr (prev_br + 4, "<br>");
+ g_free (to_insert);
+ }
+
+ if (g_utf8_strlen (prev_br, -1) > 0) {
+ WebKitDOMElement *paragraph;
+
+ if (use_pre) {
+ paragraph = webkit_dom_document_create_element (
+ document, "pre", NULL);
+ } else {
+ paragraph = e_html_editor_selection_get_paragraph_element (
+ e_html_editor_view_get_selection (view),
+ document, -1, citation_level);
+ }
+
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (paragraph),
+ g_str_has_prefix (prev_br, "<br>") ? prev_br + 4 : prev_br,
+ NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (blockquote),
+ WEBKIT_DOM_NODE (paragraph),
+ NULL);
+ }
+
+ /* Replace text markers with actual HTML blockquotes */
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (blockquote));
+ start = e_str_replace_string (
+ inner_html, "##CITATION_START##","<blockquote type=\"cite\">");
+ end = e_str_replace_string (
+ start->str, "##CITATION_END##", "</blockquote>");
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (blockquote), end->str, NULL);
+
+ g_free (inner_html);
+ g_string_free (start, TRUE);
+ g_string_free (end, TRUE);
+}
+
+static void
+mark_citation (WebKitDOMElement *citation)
+{
+ gchar *inner_html, *surrounded;
+
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (citation));
+
+ surrounded = g_strconcat (
+ "<span>##CITATION_START##</span>", inner_html,
+ "<span>##CITATION_END##</span>", NULL);
+
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (citation), surrounded, NULL);
+
+ element_add_class (citation, "marked");
+
+ g_free (inner_html);
+ g_free (surrounded);
+}
+
+static gint
+create_text_markers_for_citations_in_document (WebKitDOMDocument *document)
+{
+ gint count = 0;
+ WebKitDOMElement *citation;
+
+ citation = webkit_dom_document_query_selector (
+ document, "blockquote[type=cite]:not(.marked)", NULL);
+
+ while (citation) {
+ mark_citation (citation);
+ count ++;
+
+ citation = webkit_dom_document_query_selector (
+ document, "blockquote[type=cite]:not(.marked)", NULL);
+ }
+
+ return count;
+}
+
+static gint
+create_text_markers_for_citations_in_element (WebKitDOMElement *element)
+{
+ gint count = 0;
+ WebKitDOMElement *citation;
+
+ citation = webkit_dom_element_query_selector (
+ element, "blockquote[type=cite]:not(.marked)", NULL);
+
+ while (citation) {
+ mark_citation (citation);
+ count ++;
+
+ citation = webkit_dom_element_query_selector (
+ element, "blockquote[type=cite]:not(.marked)", NULL);
+ }
+
+ return count;
+}
+
+static void
+html_editor_view_process_document_from_convertor (EHTMLEditorView *view,
+ WebKitDOMDocument *document_convertor)
+{
+ EHTMLEditorSelection *selection = e_html_editor_view_get_selection (view);
+ gboolean start_bottom;
+ gchar *inner_text, *inner_html;
+ gint ii;
+ GSettings *settings;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *paragraph, *new_blockquote, *top_signature;
+ WebKitDOMElement *cite_body, *signature;
+ WebKitDOMHTMLElement *body, *body_convertor;
+ WebKitDOMNodeList *list;
+
+ settings = g_settings_new ("org.gnome.evolution.mail");
+ start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom");
+ g_object_unref (settings);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+ body_convertor = webkit_dom_document_get_body (document_convertor);
+
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-converted", "", NULL);
+
+ paragraph = webkit_dom_document_get_element_by_id (document, "-x-evo-input-start");
+ if (!paragraph) {
+ paragraph = e_html_editor_selection_get_paragraph_element (
+ selection, document, -1, 0);
+ webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
+ webkit_dom_html_element_set_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (paragraph), UNICODE_ZERO_WIDTH_SPACE, NULL);
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (webkit_dom_document_get_body (document)),
+ WEBKIT_DOM_NODE (paragraph),
+ NULL);
+ }
+
+ list = webkit_dom_document_query_selector_all (
+ document_convertor, "span.-x-evo-to-body", NULL);
+ for (ii = webkit_dom_node_list_get_length (list) - 1; ii >= 0; ii--) {
+ WebKitDOMNode *node;
+
+ node = webkit_dom_node_list_item (list, ii);
+ while (webkit_dom_node_has_child_nodes (node)) {
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (body),
+ webkit_dom_node_clone_node (
+ webkit_dom_node_get_first_child (node), TRUE),
+ webkit_dom_node_get_next_sibling (
+ WEBKIT_DOM_NODE (paragraph)),
+ NULL);
+
+ webkit_dom_node_remove_child (
+ node, webkit_dom_node_get_first_child (node), NULL);
+ }
+
+ webkit_dom_node_remove_child (
+ WEBKIT_DOM_NODE (body_convertor),
+ WEBKIT_DOM_NODE (node),
+ NULL);
+ }
+
+ repair_gmail_blockquotes (document_convertor);
+
+ create_text_markers_for_citations_in_document (document_convertor);
+
+ /* Get innertText from convertor */
+ inner_text = webkit_dom_html_element_get_inner_text (body_convertor);
+
+ cite_body = webkit_dom_document_query_selector (
+ document_convertor, "span.-x-evo-cite-body", NULL);
+
+ top_signature = webkit_dom_document_query_selector (
+ document, ".-x-evo-top-signature", NULL);
+ signature = webkit_dom_document_query_selector (
+ document, "span.-x-evo-signature", NULL);
+
+ if (cite_body) {
+ if (!(top_signature && start_bottom))
+ e_html_editor_selection_save_caret_position (selection);
+ } else {
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (paragraph),
+ WEBKIT_DOM_NODE (
+ e_html_editor_selection_get_caret_position_node (
+ document)),
+ NULL);
+ }
+
+ new_blockquote = webkit_dom_document_create_element (
+ document, "blockquote", NULL);
+ webkit_dom_element_set_attribute (
+ new_blockquote, "type", "cite", NULL);
+
+ webkit_dom_html_element_set_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (new_blockquote), inner_text, NULL);
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (new_blockquote));
+
+ if (cite_body) {
+ webkit_dom_element_set_attribute (
+ new_blockquote, "id", "-x-evo-main-cite", NULL);
+
+ parse_html_into_paragraphs (
+ view, document, new_blockquote, inner_html, FALSE);
+
+ if (start_bottom) {
+ if (signature) {
+ WebKitDOMNode *parent =
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (signature));
+ if (top_signature) {
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (new_blockquote),
+ NULL);
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (paragraph),
+ NULL);
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (paragraph),
+ e_html_editor_selection_get_caret_position_node (
+ document),
+ NULL);
+ } else {
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (new_blockquote),
+ WEBKIT_DOM_NODE (parent),
+ NULL);
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (paragraph),
+ webkit_dom_node_get_next_sibling (
+ WEBKIT_DOM_NODE (new_blockquote)),
+ NULL);
+ }
+ } else {
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (new_blockquote),
+ NULL);
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (paragraph),
+ webkit_dom_node_get_next_sibling (
+ WEBKIT_DOM_NODE (new_blockquote)),
+ NULL);
+ }
+ } else {
+ if (signature) {
+ WebKitDOMNode *parent =
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (signature));
+
+ if (top_signature) {
+ WebKitDOMElement *br;
+
+ br = webkit_dom_document_create_element (
+ document, "BR", NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (new_blockquote),
+ NULL);
+ /* Insert NL after signature */
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (br),
+ webkit_dom_node_get_next_sibling (
+ WEBKIT_DOM_NODE (paragraph)),
+ NULL);
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (parent),
+ WEBKIT_DOM_NODE (br),
+ NULL);
+ } else
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (new_blockquote),
+ WEBKIT_DOM_NODE (parent),
+ NULL);
+ } else {
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (new_blockquote),
+ NULL);
+ }
+ }
+ } else {
+ WebKitDOMNode *signature_clone, *first_child;
+
+ if (signature) {
+ signature_clone = webkit_dom_node_clone_node (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (signature)),
+ TRUE);
+ }
+
+ parse_html_into_paragraphs (
+ view, document, WEBKIT_DOM_ELEMENT (body),
+ inner_html, FALSE);
+
+ if (signature) {
+ if (!top_signature) {
+ signature_clone = webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (body),
+ signature_clone,
+ NULL);
+ } else {
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (body),
+ signature_clone,
+ webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (body)),
+ NULL);
+ }
+ }
+
+ first_child = webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (body));
+
+ webkit_dom_node_insert_before (
+ first_child,
+ e_html_editor_selection_get_caret_position_node (
+ document),
+ webkit_dom_node_get_first_child (first_child),
+ NULL);
+ }
+
+ if (!e_html_editor_view_get_html_mode (view))
+ e_html_editor_selection_wrap_paragraphs_in_document (
+ selection, document);
+ if (webkit_dom_document_query_selector (document, "blockquote[type=cite]", NULL))
+ body = WEBKIT_DOM_HTML_ELEMENT (
+ e_html_editor_view_quote_plain_text (view));
+
+ e_html_editor_selection_restore_caret_position (selection);
+ e_html_editor_view_force_spell_check (view);
+
+ /* Register on input event that is called when the content (body) is modified */
+ webkit_dom_event_target_add_event_listener (
+ WEBKIT_DOM_EVENT_TARGET (body),
+ "input",
+ G_CALLBACK (body_input_event_cb),
+ FALSE,
+ view);
+
+ g_free (inner_html);
+ g_free (inner_text);
+}
+
+static void
+html_editor_view_insert_converted_html_into_selection (EHTMLEditorView *view,
+ WebKitDOMDocument *document_convertor)
+{
+ gchar *inner_text, *inner_html;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *element;
+ WebKitDOMHTMLElement *convertor_body;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ convertor_body = webkit_dom_document_get_body (document_convertor);
+
+ inner_text = webkit_dom_html_element_get_inner_text (convertor_body);
+ element = webkit_dom_document_create_element (document, "div", NULL);
+ webkit_dom_html_element_set_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (element), inner_text, NULL);
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (element));
+
+ parse_html_into_paragraphs (
+ view, document, element, inner_html, FALSE);
+
+ g_free (inner_html);
+
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (element));
+
+ e_html_editor_view_exec_command (
+ view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, inner_html);
+
+ e_html_editor_view_force_spell_check (view);
+
+ g_free (inner_html);
+ g_free (inner_text);
+}
+
+static void
+html_plain_text_convertor_load_status_changed (WebKitWebView *web_view,
+ GParamSpec *pspec,
+ EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document_convertor;
+
+ if (webkit_web_view_get_load_status (web_view) != WEBKIT_LOAD_FINISHED)
+ return;
+
+ document_convertor = webkit_web_view_get_dom_document (web_view);
+
+ if (view->priv->convertor_insert)
+ html_editor_view_insert_converted_html_into_selection (
+ view, document_convertor);
+ else
+ html_editor_view_process_document_from_convertor (
+ view, document_convertor);
+}
+
+static void
+e_html_editor_view_init (EHTMLEditorView *view)
+{
+ WebKitWebSettings *settings;
+ GSettings *g_settings;
+ GSettingsSchema *settings_schema;
+ ESpellChecker *checker;
+ gchar **languages;
+ gchar *comma_separated;
+ const gchar *user_cache_dir;
+
+ view->priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (view);
+
+ webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), TRUE);
+ settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
+
+ g_object_set (
+ G_OBJECT (settings),
+ "enable-developer-extras", TRUE,
+ "enable-dom-paste", TRUE,
+ "enable-file-access-from-file-uris", TRUE,
+ "enable-plugins", FALSE,
+ "enable-scripts", FALSE,
+ "enable-spell-checking", TRUE,
+ "respect-image-orientation", TRUE,
+ NULL);
+
+ webkit_web_view_set_settings (WEBKIT_WEB_VIEW (view), settings);
+
+ /* Override the spell-checker, use our own */
+ checker = e_spell_checker_new ();
+ webkit_set_text_checker (G_OBJECT (checker));
+ g_object_unref (checker);
+
+ /* Don't use CSS when possible to preserve compatibility with older
+ * versions of Evolution or other MUAs */
+ e_html_editor_view_exec_command (
+ view, E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS, "false");
+
+ g_signal_connect (
+ view, "user-changed-contents",
+ G_CALLBACK (html_editor_view_user_changed_contents_cb), NULL);
+ g_signal_connect (
+ view, "selection-changed",
+ G_CALLBACK (html_editor_view_selection_changed_cb), NULL);
+ g_signal_connect (
+ view, "should-show-delete-interface-for-element",
+ G_CALLBACK (html_editor_view_should_show_delete_interface_for_element), NULL);
+ g_signal_connect (
+ view, "resource-request-starting",
+ G_CALLBACK (html_editor_view_resource_requested), NULL);
+ g_signal_connect (
+ view, "notify::load-status",
+ G_CALLBACK (html_editor_view_load_status_changed), NULL);
+
+ view->priv->selection = g_object_new (
+ E_TYPE_HTML_EDITOR_SELECTION,
+ "html-editor-view", view,
+ NULL);
+
+ g_settings = g_settings_new ("org.gnome.desktop.interface");
+ g_signal_connect_swapped (
+ g_settings, "changed::font-name",
+ G_CALLBACK (e_html_editor_view_update_fonts), view);
+ g_signal_connect_swapped (
+ g_settings, "changed::monospace-font-name",
+ G_CALLBACK (e_html_editor_view_update_fonts), view);
+ view->priv->font_settings = g_settings;
+
+ /* This schema is optional. Use if available. */
+ settings_schema = g_settings_schema_source_lookup (
+ g_settings_schema_source_get_default (),
+ "org.gnome.settings-daemon.plugins.xsettings", FALSE);
+ if (settings_schema != NULL) {
+ g_settings = g_settings_new ("org.gnome.settings-daemon.plugins.xsettings");
+ g_signal_connect_swapped (
+ settings, "changed::antialiasing",
+ G_CALLBACK (e_html_editor_view_update_fonts), view);
+ view->priv->aliasing_settings = g_settings;
+ }
+
+ view->priv->inline_images = g_hash_table_new_full (
+ g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ e_html_editor_view_update_fonts (view);
+
+ /* Give spell check languages to WebKit */
+ languages = e_spell_checker_list_active_languages (checker, NULL);
+ comma_separated = g_strjoinv (",", languages);
+ g_strfreev (languages);
+
+ g_object_set (
+ G_OBJECT (settings),
+ "spell-checking-languages", comma_separated,
+ NULL);
+
+ g_free (comma_separated);
+
+ view->priv->convertor_insert = FALSE;
+
+ view->priv->convertor_web_view =
+ g_object_ref_sink (WEBKIT_WEB_VIEW (webkit_web_view_new ()));
+ settings = webkit_web_view_get_settings (view->priv->convertor_web_view);
+
+ g_object_set (
+ G_OBJECT (settings),
+ "enable-scripts", FALSE,
+ "enable-plugins", FALSE,
+ NULL);
+
+ g_signal_connect (
+ view->priv->convertor_web_view, "notify::load-status",
+ G_CALLBACK (html_plain_text_convertor_load_status_changed), view);
+
+ /* Make WebKit think we are displaying a local file, so that it
+ * does not block loading resources from file:// protocol */
+ webkit_web_view_load_string (
+ WEBKIT_WEB_VIEW (view), "", "text/html", "UTF-8", "file://");
+
+ html_editor_view_set_links_active (view, FALSE);
+
+ if (emd_global_http_cache == NULL) {
+ user_cache_dir = e_get_user_cache_dir ();
+ emd_global_http_cache = camel_data_cache_new (user_cache_dir, NULL);
+
+ /* cache expiry - 2 hour access, 1 day max */
+ camel_data_cache_set_expire_age (
+ emd_global_http_cache, 24 * 60 * 60);
+ camel_data_cache_set_expire_access (
+ emd_global_http_cache, 2 * 60 * 60);
+ }
+}
+
+/**
+ * e_html_editor_view_new:
+ *
+ * Returns a new instance of the editor.
+ *
+ * Returns: A newly created #EHTMLEditorView. [transfer-full]
+ */
+EHTMLEditorView *
+e_html_editor_view_new (void)
+{
+ return g_object_new (E_TYPE_HTML_EDITOR_VIEW, NULL);
+}
+
+/**
+ * e_html_editor_view_get_selection:
+ * @view: an #EHTMLEditorView
+ *
+ * Returns an #EHTMLEditorSelection object which represents current selection or
+ * cursor position within the editor document. The #EHTMLEditorSelection allows
+ * programmer to manipulate with formatting, selection, styles etc.
+ *
+ * Returns: An always valid #EHTMLEditorSelection object. The object is owned by
+ * the @view and should never be free'd.
+ */
+EHTMLEditorSelection *
+e_html_editor_view_get_selection (EHTMLEditorView *view)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), NULL);
+
+ return view->priv->selection;
+}
+
+/**
+ * e_html_editor_view_exec_command:
+ * @view: an #EHTMLEditorView
+ * @command: an #EHTMLEditorViewCommand to execute
+ * @value: value of the command (or @NULL if the command does not require value)
+ *
+ * The function will fail when @value is @NULL or empty but the current @command
+ * requires a value to be passed. The @value is ignored when the @command does
+ * not expect any value.
+ *
+ * Returns: @TRUE when the command was succesfully executed, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_view_exec_command (EHTMLEditorView *view,
+ EHTMLEditorViewCommand command,
+ const gchar *value)
+{
+ WebKitDOMDocument *document;
+ const gchar *cmd_str = 0;
+ gboolean has_value;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
+
+#define CHECK_COMMAND(cmd,str,val) case cmd:\
+ if (val) {\
+ g_return_val_if_fail (value && *value, FALSE);\
+ }\
+ has_value = val; \
+ cmd_str = str;\
+ break;
+
+ switch (command) {
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_BACKGROUND_COLOR, "BackColor", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_BOLD, "Bold", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_COPY, "Copy", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_CREATE_LINK, "CreateLink", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_CUT, "Cut", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR, "DefaultParagraphSeparator", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_DELETE, "Delete", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FIND_STRING, "FindString", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_NAME, "FontName", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE, "FontSize", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE_DELTA, "FontSizeDelta", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORE_COLOR, "ForeColor", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORMAT_BLOCK, "FormatBlock", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORWARD_DELETE, "ForwardDelete", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_HILITE_COLOR, "HiliteColor", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INDENT, "Indent", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_HORIZONTAL_RULE, "InsertHorizontalRule", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, "InsertHTML", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_IMAGE, "InsertImage", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_LINE_BREAK, "InsertLineBreak", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, "InsertNewlineInQuotedContent", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_ORDERED_LIST, "InsertOrderedList", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_PARAGRAPH, "InsertParagraph", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, "InsertText", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_UNORDERED_LIST, "InsertUnorderedList", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_ITALIC, "Italic", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_CENTER, "JustifyCenter", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_FULL, "JustifyFull", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_LEFT, "JustifyLeft", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_NONE, "JustifyNone", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_RIGHT, "JustifyRight", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_OUTDENT, "Outdent", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE, "Paste", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE_AND_MATCH_STYLE, "PasteAndMatchStyle", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE_AS_PLAIN_TEXT, "PasteAsPlainText", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PRINT, "Print", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_REDO, "Redo", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_REMOVE_FORMAT, "RemoveFormat", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SELECT_ALL, "SelectAll", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH, "Strikethrough", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS, "StyleWithCSS", TRUE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SUBSCRIPT, "Subscript", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SUPERSCRIPT, "Superscript", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_TRANSPOSE, "Transpose", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE, "Underline", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNDO, "Undo", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNLINK, "Unlink", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNSELECT, "Unselect", FALSE)
+ CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_USE_CSS, "UseCSS", TRUE)
+ }
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ return webkit_dom_document_exec_command (
+ document, cmd_str, FALSE, has_value ? value : "" );
+}
+
+/**
+ * e_html_editor_view_get_changed:
+ * @view: an #EHTMLEditorView
+ *
+ * Whether content of the editor has been changed.
+ *
+ * Returns: @TRUE when document was changed, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_view_get_changed (EHTMLEditorView *view)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
+
+ return view->priv->changed;
+}
+
+/**
+ * e_html_editor_view_set_changed:
+ * @view: an #EHTMLEditorView
+ * @changed: whether document has been changed or not
+ *
+ * Sets whether document has been changed or not. The editor is tracking changes
+ * automatically, but sometimes it's necessary to change the dirty flag to force
+ * "Save changes" dialog for example.
+ */
+void
+e_html_editor_view_set_changed (EHTMLEditorView *view,
+ gboolean changed)
+{
+ g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
+
+ if (view->priv->changed == changed)
+ return;
+
+ view->priv->changed = changed;
+
+ g_object_notify (G_OBJECT (view), "changed");
+}
+
+static gboolean
+is_citation_node (WebKitDOMNode *node)
+{
+ char *value;
+
+ if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
+ return FALSE;
+
+ value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type");
+
+ /* citation == <blockquote type='cite'> */
+ if (g_strcmp0 (value, "cite") == 0) {
+ g_free (value);
+ return TRUE;
+ } else {
+ g_free (value);
+ return FALSE;
+ }
+}
+
+static gchar *
+get_quotation_for_level (gint quote_level)
+{
+ gint ii;
+ GString *output = g_string_new ("");
+
+ for (ii = 0; ii < quote_level; ii++) {
+ g_string_append (output, "<span class=\"quote_character\">");
+ g_string_append (output, QUOTE_SYMBOL);
+ g_string_append (output, " ");
+ g_string_append (output, "</span>");
+ }
+
+ return g_string_free (output, FALSE);
+}
+
+static void
+insert_quote_symbols (WebKitDOMHTMLElement *element,
+ gint quote_level,
+ gboolean skip_first,
+ gboolean insert_newline)
+{
+ gchar *text;
+ gint ii;
+ GString *output;
+ gchar *quotation;
+
+ if (!WEBKIT_DOM_IS_HTML_ELEMENT (element))
+ return;
+
+ text = webkit_dom_html_element_get_inner_html (element);
+ output = g_string_new ("");
+ quotation = get_quotation_for_level (quote_level);
+
+ if (g_strcmp0 (text, "\n") == 0) {
+ g_string_append (output, "<span class=\"-x-evo-quoted\">");
+ g_string_append (output, quotation);
+ g_string_append (output, "</span>");
+ g_string_append (output, "\n");
+ } else {
+ gchar **lines;
+
+ lines = g_strsplit (text, "\n", 0);
+
+ for (ii = 0; lines[ii]; ii++) {
+ if (ii == 0 && skip_first) {
+ if (g_strv_length (lines) == 1) {
+ g_strfreev (lines);
+ goto exit;
+ }
+ g_string_append (output, lines[ii]);
+ g_string_append (output, "\n");
+ }
+
+ g_string_append (output, "<span class=\"-x-evo-quoted\">");
+ g_string_append (output, quotation);
+ g_string_append (output, "</span>");
+
+ /* Insert line of text */
+ g_string_append (output, lines[ii]);
+ if ((ii == g_strv_length (lines) - 1) &&
+ !g_str_has_suffix (text, "\n") && !insert_newline) {
+ /* If we are on last line and node's text doesn't
+ * end with \n, don't insert it */
+ break;
+ }
+ g_string_append (output, "\n");
+ }
+
+ g_strfreev (lines);
+ }
+
+ webkit_dom_html_element_set_inner_html (element, output->str, NULL);
+ exit:
+ g_free (quotation);
+ g_free (text);
+ g_string_free (output, TRUE);
+}
+
+static void
+quote_node (WebKitDOMDocument *document,
+ WebKitDOMNode *node,
+ gint quote_level)
+{
+ gboolean skip_first = FALSE;
+ gboolean insert_newline = FALSE;
+ gboolean is_html_node = FALSE;
+ WebKitDOMElement *wrapper;
+ WebKitDOMNode *node_clone, *prev_sibling, *next_sibling;
+
+ /* Don't quote when we are not in citation */
+ if (quote_level == 0)
+ return;
+
+ if (WEBKIT_DOM_IS_COMMENT (node))
+ return;
+
+ if (WEBKIT_DOM_IS_HTML_ELEMENT (node)) {
+ insert_quote_symbols (
+ WEBKIT_DOM_HTML_ELEMENT (node), quote_level, FALSE, FALSE);
+ return;
+ }
+
+ prev_sibling = webkit_dom_node_get_previous_sibling (node);
+ next_sibling = webkit_dom_node_get_next_sibling (node);
+
+ is_html_node =
+ !WEBKIT_DOM_IS_COMMENT (prev_sibling) && (
+ WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling) ||
+ element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "b") ||
+ element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "i") ||
+ element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "u"));
+
+ if (prev_sibling && is_html_node)
+ skip_first = TRUE;
+
+ /* Skip the BR between first blockquote and pre */
+ if (quote_level == 1 && next_sibling && WEBKIT_DOM_IS_HTML_PRE_ELEMENT (next_sibling))
+ return;
+
+ if (next_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling) &&
+ WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (webkit_dom_node_get_next_sibling (next_sibling))) {
+ insert_newline = TRUE;
+ }
+
+ /* Do temporary wrapper */
+ wrapper = webkit_dom_document_create_element (document, "SPAN", NULL);
+ webkit_dom_element_set_class_name (wrapper, "-x-evo-temp-text-wrapper");
+
+ node_clone = webkit_dom_node_clone_node (node, TRUE);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (wrapper),
+ node_clone,
+ NULL);
+
+ insert_quote_symbols (
+ WEBKIT_DOM_HTML_ELEMENT (wrapper),
+ quote_level,
+ skip_first,
+ insert_newline);
+
+ webkit_dom_node_replace_child (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (wrapper),
+ node,
+ NULL);
+}
+
+static void
+insert_quote_symbols_before_node (WebKitDOMDocument *document,
+ WebKitDOMNode *node,
+ gint quote_level,
+ gboolean is_html_node)
+{
+ gchar *quotation;
+ WebKitDOMElement *element;
+
+ quotation = get_quotation_for_level (quote_level);
+ element = webkit_dom_document_create_element (document, "SPAN", NULL);
+ element_add_class (element, "-x-evo-quoted");
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (element), quotation, NULL);
+
+ if (is_html_node) {
+ WebKitDOMElement *new_br;
+
+ new_br = webkit_dom_document_create_element (document, "br", NULL);
+ element_add_class (new_br, "-x-evo-temp-br");
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (new_br),
+ node,
+ NULL);
+ }
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (node),
+ WEBKIT_DOM_NODE (element),
+ node,
+ NULL);
+
+ if (is_html_node) {
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node),
+ node,
+ NULL);
+ }
+
+ g_free (quotation);
+}
+
+static void
+quote_plain_text_recursive (WebKitDOMDocument *document,
+ WebKitDOMNode *node,
+ WebKitDOMNode *start_node,
+ gint quote_level)
+{
+ gboolean skip_node = FALSE;
+ gboolean move_next = FALSE;
+ gboolean suppress_next = FALSE;
+ gboolean is_html_node = FALSE;
+ WebKitDOMNode *next_sibling, *prev_sibling;
+
+ node = webkit_dom_node_get_first_child (node);
+
+ while (node) {
+ skip_node = FALSE;
+ move_next = FALSE;
+ is_html_node = FALSE;
+
+ if (WEBKIT_DOM_IS_COMMENT (node))
+ goto next_node;
+
+ prev_sibling = webkit_dom_node_get_previous_sibling (node);
+ next_sibling = webkit_dom_node_get_next_sibling (node);
+
+ if (WEBKIT_DOM_IS_TEXT (node)) {
+ /* Start quoting after we are in blockquote */
+ if (quote_level > 0 && !suppress_next) {
+ /* When quoting text node, we are wrappering it and
+ * afterwards replacing it with that wrapper, thus asking
+ * for next_sibling after quoting will return NULL bacause
+ * that node don't exist anymore */
+ quote_node (document, node, quote_level);
+ node = next_sibling;
+ skip_node = TRUE;
+ } else
+ suppress_next = FALSE;
+
+ goto next_node;
+ }
+
+ if (!(WEBKIT_DOM_IS_ELEMENT (node) || WEBKIT_DOM_IS_HTML_ELEMENT (node)))
+ goto next_node;
+
+ if (element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-position")) {
+ if (quote_level > 0)
+ element_add_class (
+ WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-quoting");
+
+ move_next = TRUE;
+ suppress_next = TRUE;
+ goto next_node;
+ }
+
+ if (element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-selection-start-marker") ||
+ element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-selection-end-marker")) {
+ move_next = TRUE;
+ suppress_next = TRUE;
+ goto next_node;
+ }
+
+ if (WEBKIT_DOM_IS_HTML_META_ELEMENT (node)) {
+ goto next_node;
+ }
+ if (WEBKIT_DOM_IS_HTML_STYLE_ELEMENT (node)) {
+ move_next = TRUE;
+ goto next_node;
+ }
+ if (WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (node)) {
+ move_next = TRUE;
+ goto next_node;
+ }
+
+ if (webkit_dom_element_get_child_element_count (WEBKIT_DOM_ELEMENT (node)) != 0)
+ goto with_children;
+
+ /* Even in plain text mode we can have some basic html element
+ * like anchor and others. When Forwaring e-mail as Quoted EMFormat
+ * generates header that contatains <b> tags (bold font).
+ * We have to treat these elements separately to avoid
+ * modifications of theirs inner texts */
+ is_html_node =
+ WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) ||
+ element_has_tag (WEBKIT_DOM_ELEMENT (node), "b") ||
+ element_has_tag (WEBKIT_DOM_ELEMENT (node), "i") ||
+ element_has_tag (WEBKIT_DOM_ELEMENT (node), "u");
+
+ if (is_html_node) {
+ if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling))
+ insert_quote_symbols_before_node (
+ document, prev_sibling, quote_level, TRUE);
+
+ move_next = TRUE;
+ goto next_node;
+ }
+
+ /* If element doesn't have children, we can quote it */
+ if (is_citation_node (node)) {
+ /* Citation with just text inside */
+ quote_node (document, node, quote_level + 1);
+ /* Set citation as quoted */
+ element_add_class (
+ WEBKIT_DOM_ELEMENT (node),
+ "-x-evo-plaintext-quoted");
+
+ move_next = TRUE;
+ goto next_node;
+ }
+
+ if (!WEBKIT_DOM_IS_HTMLBR_ELEMENT (node))
+ goto not_br;
+
+ if (!prev_sibling) {
+ WebKitDOMNode *parent;
+
+ parent = webkit_dom_node_get_parent_node (node);
+
+ /* BR in the beginning of the citation */
+ if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent))
+ insert_quote_symbols_before_node (
+ document, node, quote_level, FALSE);
+ }
+
+ if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
+ WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (next_sibling) &&
+ element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper")) {
+ /* Situation when anchors are alone on line */
+ gchar *text_content;
+
+ text_content = webkit_dom_node_get_text_content (prev_sibling);
+
+ if (g_str_has_suffix (text_content, "\n")) {
+ insert_quote_symbols_before_node (
+ document, node, quote_level, FALSE);
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node),
+ node,
+ NULL);
+ g_free (text_content);
+ node = next_sibling;
+ skip_node = TRUE;
+ goto next_node;
+ }
+ g_free (text_content);
+ }
+
+ if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling)) {
+ gchar *quotation, *content;
+
+ quotation = get_quotation_for_level (quote_level);
+
+ content = g_strconcat (
+ "<span class=\"-x-evo-quoted\">",
+ quotation,
+ "</span><br class=\"-x-evo-temp-br\">",
+ NULL);
+
+ webkit_dom_html_element_set_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (node),
+ content,
+ NULL);
+
+ g_free (content);
+ g_free (quotation);
+
+ node = next_sibling;
+ skip_node = TRUE;
+ goto next_node;
+ }
+
+ if (!prev_sibling && !next_sibling) {
+ WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
+
+ if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent)) {
+ insert_quote_symbols_before_node (
+ document, node, quote_level, FALSE);
+ }
+ }
+
+ if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
+ element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper")) {
+ gchar *text_content;
+
+ text_content = webkit_dom_node_get_text_content (prev_sibling);
+ if (g_strcmp0 (text_content, "") == 0)
+ insert_quote_symbols_before_node (
+ document, node, quote_level, FALSE);
+
+ g_free (text_content);
+ }
+
+ if (is_citation_node (prev_sibling)) {
+ insert_quote_symbols_before_node (
+ document, node, quote_level, FALSE);
+ }
+ not_br:
+ if (g_strcmp0 (webkit_dom_node_get_text_content (node), "") == 0) {
+ move_next = TRUE;
+ goto next_node;
+ }
+
+ quote_node (document, node, quote_level);
+
+ move_next = TRUE;
+ goto next_node;
+
+ with_children:
+ if (is_citation_node (node)) {
+ /* Go deeper and increase level */
+ quote_plain_text_recursive (
+ document, node, start_node, quote_level + 1);
+ /* set citation as quoted */
+ element_add_class (
+ WEBKIT_DOM_ELEMENT (node),
+ "-x-evo-plaintext-quoted");
+ move_next = TRUE;
+ } else {
+ quote_plain_text_recursive (
+ document, node, start_node, quote_level);
+ move_next = TRUE;
+ }
+ next_node:
+ if (!skip_node) {
+ /* Move to next node */
+ if (!move_next && webkit_dom_node_has_child_nodes (node)) {
+ node = webkit_dom_node_get_first_child (node);
+ } else if (webkit_dom_node_get_next_sibling (node)) {
+ node = webkit_dom_node_get_next_sibling (node);
+ } else {
+ return;
+ }
+ }
+ }
+}
+
+static gint
+get_citation_level (WebKitDOMNode *node,
+ gboolean set_plaintext_quoted)
+{
+ WebKitDOMNode *parent = node;
+ gint level = 0;
+
+ while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
+ if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
+ webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "type")) {
+ level++;
+
+ if (set_plaintext_quoted) {
+ element_add_class (
+ WEBKIT_DOM_ELEMENT (parent),
+ "-x-evo-plaintext-quoted");
+ }
+ }
+
+ parent = webkit_dom_node_get_parent_node (parent);
+ }
+
+ return level;
+}
+
+WebKitDOMElement *
+e_html_editor_view_quote_plain_text_element (EHTMLEditorView *view,
+ WebKitDOMElement *element)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMNode *element_clone;
+ WebKitDOMNodeList *list;
+ gint ii, length, level;
+
+ document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
+
+ element_clone = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (element), TRUE);
+ level = get_citation_level (WEBKIT_DOM_NODE (element), TRUE);
+
+ /* Remove old quote characters if the exists */
+ list = webkit_dom_element_query_selector_all (
+ WEBKIT_DOM_ELEMENT (element_clone), "span.-x-evo-quoted", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node), node, NULL);
+ }
+
+ quote_plain_text_recursive (
+ document, element_clone, element_clone, level);
+
+ /* Replace old element with one, that is quoted */
+ webkit_dom_node_replace_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
+ element_clone,
+ WEBKIT_DOM_NODE (element),
+ NULL);
+
+ return WEBKIT_DOM_ELEMENT (element_clone);
+}
+
+/**
+ * e_html_editor_view_quote_plain_text:
+ * @view: an #EHTMLEditorView
+ *
+ * Quote text inside citation blockquotes in plain text mode.
+ *
+ * As this function is cloning and replacing all citation blockquotes keep on
+ * mind that any pointers to nodes inside these blockquotes will be invalidated.
+ */
+WebKitDOMElement *
+e_html_editor_view_quote_plain_text (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+ WebKitDOMNode *body_clone;
+ WebKitDOMNamedNodeMap *attributes;
+ WebKitDOMNodeList *list;
+ WebKitDOMElement *element;
+ gint ii, length;
+ gulong attributes_length;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ /* Check if the document is already quoted */
+ element = webkit_dom_document_query_selector (
+ document, ".-x-evo-plaintext-quoted", NULL);
+ if (element)
+ return NULL;
+
+ body = webkit_dom_document_get_body (document);
+ body_clone = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), TRUE);
+
+ /* Clean unwanted spaces before and after blockquotes */
+ list = webkit_dom_element_query_selector_all (
+ WEBKIT_DOM_ELEMENT (body_clone), "blockquote[type|=cite]", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *blockquote = webkit_dom_node_list_item (list, ii);
+ WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (blockquote);
+ WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (blockquote);
+
+ if (prev_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling)) {
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (prev_sibling),
+ prev_sibling,
+ NULL);
+ }
+ if (next_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling)) {
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (next_sibling),
+ next_sibling,
+ NULL);
+ }
+ if (webkit_dom_node_has_child_nodes (blockquote)) {
+ WebKitDOMNode *child = webkit_dom_node_get_first_child (blockquote);
+ if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)) {
+ webkit_dom_node_remove_child (
+ blockquote,
+ child,
+ NULL);
+ }
+ }
+ }
+
+ quote_plain_text_recursive (document, body_clone, body_clone, 0);
+
+ /* Copy attributes */
+ attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
+ attributes_length = webkit_dom_named_node_map_get_length (attributes);
+ for (ii = 0; ii < attributes_length; ii++) {
+ gchar *name, *value;
+ WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
+
+ name = webkit_dom_node_get_local_name (node);
+ value = webkit_dom_node_get_node_value (node);
+
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (body_clone), name, value, NULL);
+
+ g_free (name);
+ g_free (value);
+ }
+
+ /* Replace old BODY with one, that is quoted */
+ webkit_dom_node_replace_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (body)),
+ body_clone,
+ WEBKIT_DOM_NODE (body),
+ NULL);
+
+ return WEBKIT_DOM_ELEMENT (body_clone);
+}
+
+/**
+ * e_html_editor_view_dequote_plain_text:
+ * @view: an #EHTMLEditorView
+ *
+ * Dequote already quoted plain text in editor.
+ * Editor have to be quoted with e_html_editor_view_quote_plain_text otherwise
+ * it's not working.
+ */
+void
+e_html_editor_view_dequote_plain_text (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMNodeList *paragraphs;
+ gint length, ii;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ paragraphs = webkit_dom_document_query_selector_all (
+ document, "blockquote.-x-evo-plaintext-quoted", NULL);
+ length = webkit_dom_node_list_get_length (paragraphs);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNodeList *list;
+ WebKitDOMElement *element;
+ gint jj, list_length;
+
+ element = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (paragraphs, ii));
+
+ if (is_citation_node (WEBKIT_DOM_NODE (element))) {
+ element_remove_class (element, "-x-evo-plaintext-quoted");
+
+ list = webkit_dom_element_query_selector_all (
+ element, "span.-x-evo-quoted", NULL);
+ list_length = webkit_dom_node_list_get_length (list);
+ for (jj = 0; jj < list_length; jj++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node),
+ node,
+ NULL);
+ }
+ list = webkit_dom_element_query_selector_all (
+ element, "span.-x-evo-temp-text-wrapper", NULL);
+ list_length = webkit_dom_node_list_get_length (list);
+ for (jj = 0; jj < list_length; jj++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);
+
+ webkit_dom_node_replace_child (
+ webkit_dom_node_get_parent_node (node),
+ webkit_dom_node_get_first_child (node),
+ node,
+ NULL);
+ }
+ }
+ }
+}
+
+/**
+ * e_html_editor_view_get_html_mode:
+ * @view: an #EHTMLEditorView
+ *
+ * Whether the editor is in HTML mode or plain text mode. In HTML mode,
+ * more formatting options are avilable an the email is sent as
+ * multipart/alternative.
+ *
+ * Returns: @TRUE when HTML mode is enabled, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_view_get_html_mode (EHTMLEditorView *view)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
+
+ return view->priv->html_mode;
+}
+
+static gint
+get_indentation_level (WebKitDOMElement *element)
+{
+ WebKitDOMElement *parent;
+ gint level = 1;
+
+ parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
+ /* Count level of indentation */
+ while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
+ if (element_has_class (parent, "-x-evo-indented"))
+ level++;
+
+ parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (parent));
+ }
+
+ return level;
+}
+
+static void
+process_blockquote (WebKitDOMElement *blockquote)
+{
+ WebKitDOMNodeList *list;
+ int jj, length;
+
+ /* First replace wrappers */
+ list = webkit_dom_element_query_selector_all (
+ blockquote, "span.-x-evo-temp-text-wrapper", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (jj = 0; jj < length; jj++) {
+ WebKitDOMNode *quoted_node;
+ gchar *text_content, *tmp = NULL;
+
+ quoted_node = webkit_dom_node_list_item (list, jj);
+ text_content = webkit_dom_node_get_text_content (quoted_node);
+ if (webkit_dom_node_get_previous_sibling (quoted_node)) {
+ tmp = g_strconcat ("<br>", text_content, NULL);
+ g_free (text_content);
+ text_content = tmp;
+ }
+
+ webkit_dom_html_element_set_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);
+
+ g_free (text_content);
+ }
+
+ /* Afterwards replace quote nodes with symbols */
+ list = webkit_dom_element_query_selector_all (
+ blockquote, "span.-x-evo-quoted", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (jj = 0; jj < length; jj++) {
+ WebKitDOMNode *quoted_node;
+ gchar *text_content, *tmp = NULL;
+
+ quoted_node = webkit_dom_node_list_item (list, jj);
+ text_content = webkit_dom_node_get_text_content (quoted_node);
+ if (webkit_dom_node_get_previous_sibling (quoted_node)) {
+ tmp = g_strconcat ("<br>", text_content, NULL);
+ g_free (text_content);
+ text_content = tmp;
+ }
+
+ webkit_dom_html_element_set_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);
+
+ g_free (text_content);
+ }
+
+ if (element_has_class (blockquote, "-x-evo-indented")) {
+ WebKitDOMNode *child;
+ gchar *spaces;
+
+ spaces = g_strnfill (4 * get_indentation_level (blockquote), ' ');
+
+ child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (blockquote));
+ while (child) {
+ /* If next sibling is indented blockqoute skip it,
+ * it will be processed afterwards */
+ if (WEBKIT_DOM_IS_ELEMENT (child) &&
+ element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-indented"))
+ child = webkit_dom_node_get_next_sibling (child);
+
+ if (WEBKIT_DOM_IS_TEXT (child)) {
+ gchar *text_content;
+ gchar *indented_text;
+
+ text_content = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (child));
+ indented_text = g_strconcat (spaces, text_content, NULL);
+
+ webkit_dom_text_replace_whole_text (
+ WEBKIT_DOM_TEXT (child),
+ indented_text,
+ NULL);
+
+ g_free (text_content);
+ g_free (indented_text);
+ }
+
+ if (!child)
+ break;
+
+ /* Move to next node */
+ if (webkit_dom_node_has_child_nodes (child))
+ child = webkit_dom_node_get_first_child (child);
+ else if (webkit_dom_node_get_next_sibling (child))
+ child = webkit_dom_node_get_next_sibling (child);
+ else {
+ if (webkit_dom_node_is_equal_node (WEBKIT_DOM_NODE (blockquote), child))
+ break;
+
+ child = webkit_dom_node_get_parent_node (child);
+ if (child)
+ child = webkit_dom_node_get_next_sibling (child);
+ }
+ }
+ g_free (spaces);
+
+ webkit_dom_element_remove_attribute (blockquote, "style");
+ }
+}
+
+/* Taken from GtkHTML */
+static gchar *
+get_alpha_value (gint value,
+ gboolean lower)
+{
+ GString *str;
+ gchar *rv;
+ gint add = lower ? 'a' : 'A';
+
+ str = g_string_new (". ");
+
+ do {
+ g_string_prepend_c (str, ((value - 1) % 26) + add);
+ value = (value - 1) / 26;
+ } while (value);
+
+ rv = str->str;
+ g_string_free (str, FALSE);
+
+ return rv;
+}
+
+/* Taken from GtkHTML */
+static gchar *
+get_roman_value (gint value,
+ gboolean lower)
+{
+ GString *str;
+ const gchar *base = "IVXLCDM";
+ gchar *rv;
+ gint b, r, add = lower ? 'a' - 'A' : 0;
+
+ if (value > 3999)
+ return g_strdup ("?. ");
+
+ str = g_string_new (". ");
+
+ for (b = 0; value > 0 && b < 7 - 1; b += 2, value /= 10) {
+ r = value % 10;
+ if (r != 0) {
+ if (r < 4) {
+ for (; r; r--)
+ g_string_prepend_c (str, base[b] + add);
+ } else if (r == 4) {
+ g_string_prepend_c (str, base[b + 1] + add);
+ g_string_prepend_c (str, base[b] + add);
+ } else if (r == 5) {
+ g_string_prepend_c (str, base[b + 1] + add);
+ } else if (r < 9) {
+ for (; r > 5; r--)
+ g_string_prepend_c (str, base[b] + add);
+ g_string_prepend_c (str, base[b + 1] + add);
+ } else if (r == 9) {
+ g_string_prepend_c (str, base[b + 2] + add);
+ g_string_prepend_c (str, base[b] + add);
+ }
+ }
+ }
+
+ rv = str->str;
+ g_string_free (str, FALSE);
+
+ return rv;
+}
+
+static void
+process_list_to_plain_text (EHTMLEditorView *view,
+ WebKitDOMElement *element,
+ gint level,
+ GString *output)
+{
+ EHTMLEditorSelectionBlockFormat format;
+ EHTMLEditorSelectionAlignment alignment;
+ gint counter = 1;
+ gchar *indent_per_level = g_strnfill (SPACES_PER_LIST_LEVEL, ' ');
+ WebKitDOMNode *item;
+ gint word_wrap_length = e_html_editor_selection_get_word_wrap_length (
+ e_html_editor_view_get_selection (view));
+
+ format = e_html_editor_selection_get_list_format_from_node (
+ WEBKIT_DOM_NODE (element));
+
+ /* Process list items to plain text */
+ item = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element));
+ while (item) {
+ if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (item))
+ g_string_append (output, "\n");
+
+ if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
+ gchar *space, *item_str = NULL;
+ gint ii = 0;
+ WebKitDOMElement *wrapped;
+ GString *item_value = g_string_new ("");
+
+ alignment = e_html_editor_selection_get_list_alignment_from_node (
+ WEBKIT_DOM_NODE (item));
+
+ wrapped = webkit_dom_element_query_selector (
+ WEBKIT_DOM_ELEMENT (item), ".-x-evo-wrap-br", NULL);
+ /* Wrapped text */
+ if (wrapped) {
+ WebKitDOMNode *node = webkit_dom_node_get_first_child (item);
+ GString *line = g_string_new ("");
+ while (node) {
+ if (WEBKIT_DOM_IS_TEXT (node)) {
+ /* append text from line */
+ gchar *text_content;
+ text_content = webkit_dom_node_get_text_content (node);
+ g_string_append (line, text_content);
+ g_free (text_content);
+ }
+ if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (node) &&
+ element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) {
+ /* put spaces before line characters -> wordwraplength - indentation */
+ g_string_append (line, "\n");
+ /* put spaces before line characters -> wordwraplength - indentation */
+ for (ii = 0; ii < level; ii++)
+ g_string_append (line, indent_per_level);
+ g_string_append (item_value, line->str);
+ g_string_erase (line, 0, -1);
+ }
+ node = webkit_dom_node_get_next_sibling (node);
+ }
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT)
+ g_string_append (item_value, line->str);
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
+ gchar *fill = NULL;
+ gint fill_length;
+
+ fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
+ fill_length -= ii * SPACES_PER_LIST_LEVEL;
+ fill_length /= 2;
+
+ if (fill_length < 0)
+ fill_length = 0;
+
+ fill = g_strnfill (fill_length, ' ');
+
+ g_string_append (item_value, fill);
+ g_string_append (item_value, line->str);
+ g_free (fill);
+ }
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
+ gchar *fill = NULL;
+ gint fill_length;
+
+ fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
+ fill_length -= ii * SPACES_PER_LIST_LEVEL;
+
+ if (fill_length < 0)
+ fill_length = 0;
+
+ fill = g_strnfill (fill_length, ' ');
+
+ g_string_append (item_value, fill);
+ g_string_append (item_value, line->str);
+ g_free (fill);
+ }
+ g_string_free (line, TRUE);
+ /* that same here */
+ } else {
+ gchar *text_content =
+ webkit_dom_node_get_text_content (item);
+ g_string_append (item_value, text_content);
+ g_free (text_content);
+ }
+
+ if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST) {
+ space = g_strnfill (SPACES_PER_LIST_LEVEL - 2, ' ');
+ item_str = g_strdup_printf (
+ "%s* %s", space, item_value->str);
+ g_free (space);
+ }
+
+ if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) {
+ gint length = 1, tmp = counter;
+
+ while ((tmp = tmp / 10) > 1)
+ length++;
+
+ if (tmp == 1)
+ length++;
+
+ space = g_strnfill (SPACES_PER_LIST_LEVEL - 2 - length, ' ');
+ item_str = g_strdup_printf (
+ "%s%d. %s", space, counter, item_value->str);
+ g_free (space);
+ }
+
+ if (format > E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) {
+ gchar *value;
+
+ if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA)
+ value = get_alpha_value (counter, FALSE);
+ else
+ value = get_roman_value (counter, FALSE);
+
+ /* Value already containes dot and space */
+ space = g_strnfill (SPACES_PER_LIST_LEVEL - strlen (value), ' ');
+ item_str = g_strdup_printf (
+ "%s%s%s", space, value, item_value->str);
+ g_free (space);
+ g_free (value);
+ }
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT) {
+ for (ii = 0; ii < level - 1; ii++) {
+ g_string_append (output, indent_per_level);
+ }
+ g_string_append (output, item_str);
+ }
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
+ if (!wrapped) {
+ gchar *fill = NULL;
+ gint fill_length;
+
+ fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
+ fill_length -= ii * SPACES_PER_LIST_LEVEL;
+
+ if (fill_length < 0)
+ fill_length = 0;
+
+ if (g_str_has_suffix (item_str, " "))
+ fill_length++;
+
+ fill = g_strnfill (fill_length, ' ');
+
+ g_string_append (output, fill);
+ g_free (fill);
+ }
+ if (g_str_has_suffix (item_str, " "))
+ g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
+ else
+ g_string_append (output, item_str);
+ }
+
+ if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
+ if (!wrapped) {
+ gchar *fill = NULL;
+ gint fill_length = 0;
+
+ for (ii = 0; ii < level - 1; ii++)
+ g_string_append (output, indent_per_level);
+
+ fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
+ fill_length -= ii * SPACES_PER_LIST_LEVEL;
+ fill_length /= 2;
+
+ if (fill_length < 0)
+ fill_length = 0;
+
+ if (g_str_has_suffix (item_str, " "))
+ fill_length++;
+
+ fill = g_strnfill (fill_length, ' ');
+
+ g_string_append (output, fill);
+ g_free (fill);
+ }
+ if (g_str_has_suffix (item_str, " "))
+ g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
+ else
+ g_string_append (output, item_str);
+ }
+
+ counter++;
+ item = webkit_dom_node_get_next_sibling (item);
+ if (item)
+ g_string_append (output, "\n");
+
+ g_free (item_str);
+ g_string_free (item_value, TRUE);
+ } else if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (item) ||
+ WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (item)) {
+ process_list_to_plain_text (
+ view, WEBKIT_DOM_ELEMENT (item), level + 1, output);
+ item = webkit_dom_node_get_next_sibling (item);
+ } else {
+ item = webkit_dom_node_get_next_sibling (item);
+ }
+ }
+
+ if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)))
+ g_string_append (output, "\n");
+
+ g_free (indent_per_level);
+}
+
+static void
+remove_base_attributes (WebKitDOMElement *element)
+{
+ webkit_dom_element_remove_attribute (element, "class");
+ webkit_dom_element_remove_attribute (element, "id");
+ webkit_dom_element_remove_attribute (element, "name");
+}
+
+static void
+remove_evolution_attributes (WebKitDOMElement *element)
+{
+ webkit_dom_element_remove_attribute (element, "x-evo-smiley");
+ webkit_dom_element_remove_attribute (element, "data-converted");
+ webkit_dom_element_remove_attribute (element, "data-edit-as-new");
+ webkit_dom_element_remove_attribute (element, "data-inline");
+ webkit_dom_element_remove_attribute (element, "data-uri");
+ webkit_dom_element_remove_attribute (element, "data-message");
+ webkit_dom_element_remove_attribute (element, "data-name");
+ webkit_dom_element_remove_attribute (element, "data-new-message");
+ webkit_dom_element_remove_attribute (element, "spellcheck");
+}
+/*
+static void
+remove_style_attributes (WebKitDOMElement *element)
+{
+ webkit_dom_element_remove_attribute (element, "bgcolor");
+ webkit_dom_element_remove_attribute (element, "background");
+ webkit_dom_element_remove_attribute (element, "style");
+}
+*/
+static void
+process_elements (EHTMLEditorView *view,
+ WebKitDOMNode *node,
+ gboolean to_html,
+ gboolean changing_mode,
+ gboolean to_plain_text,
+ GString *buffer)
+{
+ WebKitDOMNodeList *nodes;
+ gulong ii, length;
+ gchar *content;
+ gboolean skip_nl = FALSE;
+
+ if (to_plain_text && !buffer)
+ return;
+
+ if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node)) {
+ if (changing_mode && to_plain_text) {
+ WebKitDOMNamedNodeMap *attributes;
+ gulong attributes_length;
+
+ /* Copy attributes */
+ g_string_append (buffer, "<html><head></head><body ");
+ attributes = webkit_dom_element_get_attributes (
+ WEBKIT_DOM_ELEMENT (node));
+ attributes_length =
+ webkit_dom_named_node_map_get_length (attributes);
+
+ for (ii = 0; ii < attributes_length; ii++) {
+ gchar *name, *value;
+ WebKitDOMNode *node =
+ webkit_dom_named_node_map_item (
+ attributes, ii);
+
+ name = webkit_dom_node_get_local_name (node);
+ value = webkit_dom_node_get_node_value (node);
+
+ g_string_append (buffer, name);
+ g_string_append (buffer, "=\"");
+ g_string_append (buffer, value);
+ g_string_append (buffer, "\" ");
+
+ g_free (name);
+ g_free (value);
+ }
+ g_string_append (buffer, ">");
+ }
+ if (to_html)
+ remove_evolution_attributes (WEBKIT_DOM_ELEMENT (node));
+ }
+
+ nodes = webkit_dom_node_get_child_nodes (node);
+ length = webkit_dom_node_list_get_length (nodes);
+
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *child;
+ gboolean skip_node = FALSE;
+
+ child = webkit_dom_node_list_item (nodes, ii);
+
+ if (WEBKIT_DOM_IS_TEXT (child)) {
+ gchar *content, *tmp;
+ GRegex *regex;
+
+ content = webkit_dom_node_get_text_content (child);
+ /* Replace tabs with 4 whitespaces, otherwise they got
+ * replaced by single whitespace */
+ if (strstr (content, "\x9")) {
+ regex = g_regex_new ("\x9", 0, 0, NULL);
+ tmp = g_regex_replace (
+ regex, content, -1, 0, " ", 0, NULL);
+ webkit_dom_node_set_text_content (child, tmp, NULL);
+ g_free (content);
+ g_free (tmp);
+ content = webkit_dom_node_get_text_content (child);
+ g_regex_unref (regex);
+ }
+
+ if (strstr (content, UNICODE_ZERO_WIDTH_SPACE)) {
+ regex = g_regex_new (UNICODE_ZERO_WIDTH_SPACE, 0, 0, NULL);
+ tmp = g_regex_replace (
+ regex, content, -1, 0, "", 0, NULL);
+ webkit_dom_node_set_text_content (child, tmp, NULL);
+ g_free (tmp);
+ g_free (content);
+ content = webkit_dom_node_get_text_content (child);
+ g_regex_unref (regex);
+ }
+
+ if (to_plain_text && !changing_mode) {
+ gchar *style;
+ const gchar *css_align;
+
+ style = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (node), "style");
+
+ if ((css_align = strstr (style, "text-align: "))) {
+ gchar *align;
+ gchar *content_with_align;
+ gint length;
+ gint word_wrap_length =
+ e_html_editor_selection_get_word_wrap_length (
+ e_html_editor_view_get_selection (view));
+
+ if (!g_str_has_prefix (css_align + 12, "left")) {
+ if (g_str_has_prefix (css_align + 12, "center"))
+ length = (word_wrap_length - g_utf8_strlen (content, -1)) / 2;
+ else
+ length = word_wrap_length - g_utf8_strlen (content, -1);
+
+ if (g_str_has_suffix (content, " ")) {
+ char *tmp;
+
+ length++;
+ align = g_strnfill (length, ' ');
+
+ tmp = g_strndup (content, g_utf8_strlen (content, -1) -1);
+
+ content_with_align = g_strconcat (
+ align, tmp, NULL);
+ g_free (tmp);
+ } else {
+ align = g_strnfill (length, ' ');
+
+ content_with_align = g_strconcat (
+ align, content, NULL);
+ }
+
+ g_free (content);
+ g_free (align);
+ content = content_with_align;
+ }
+ }
+
+ g_free (style);
+ }
+
+ if (to_plain_text || changing_mode)
+ g_string_append (buffer, content);
+
+ g_free (content);
+
+ goto next;
+ }
+
+ if (WEBKIT_DOM_IS_COMMENT (child) || !WEBKIT_DOM_IS_ELEMENT (child))
+ goto next;
+
+ /* Leave caret position untouched */
+ if (element_has_id (WEBKIT_DOM_ELEMENT (child), "-x-evo-caret-position")) {
+ if (changing_mode && to_plain_text) {
+ content = webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (child));
+ g_string_append (buffer, content);
+ g_free (content);
+ }
+ if (to_html) {
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (child),
+ child,
+ NULL);
+ }
+
+ skip_node = TRUE;
+ }
+
+ if (element_has_class (WEBKIT_DOM_ELEMENT (child), "Apple-tab-span")) {
+ if (!changing_mode && to_plain_text) {
+ gchar *content, *tmp;
+ GRegex *regex;
+
+ content = webkit_dom_node_get_text_content (child);
+ /* Replace tabs with 4 whitespaces, otherwise they got
+ * replaced by single whitespace */
+ if (strstr (content, "\x9")) {
+ regex = g_regex_new ("\x9", 0, 0, NULL);
+ tmp = g_regex_replace (
+ regex, content, -1, 0, " ", 0, NULL);
+ g_string_append (buffer, tmp);
+ g_free (tmp);
+ content = webkit_dom_node_get_text_content (child);
+ g_regex_unref (regex);
+ }
+ }
+ if (to_html) {
+ element_remove_class (
+ WEBKIT_DOM_ELEMENT (child),
+ "Applet-tab-span");
+ }
+
+ skip_node = TRUE;
+ }
+
+ /* Leave blockquotes as they are */
+ if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (child)) {
+ if (changing_mode && to_plain_text) {
+ content = webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (child));
+ g_string_append (buffer, content);
+ g_free (content);
+ skip_node = TRUE;
+ } else {
+ if (!changing_mode && to_plain_text) {
+ if (get_citation_level (child, FALSE) == 0) {
+ gchar *value;
+ value = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (child), "type");
+ if (value && g_strcmp0 (value, "cite") == 0) {
+ g_string_append (buffer, "\n");
+ }
+ g_free (value);
+ }
+ }
+ process_blockquote (WEBKIT_DOM_ELEMENT (child));
+ }
+ }
+
+ if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (child) ||
+ WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (child)) {
+ if (to_plain_text) {
+ if (changing_mode) {
+ content = webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (child));
+ g_string_append (buffer, content);
+ g_free (content);
+ } else {
+ process_list_to_plain_text (
+ view, WEBKIT_DOM_ELEMENT (child), 1, buffer);
+ }
+ skip_node = TRUE;
+ goto next;
+ }
+ }
+
+ if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-resizable-wrapper") &&
+ !element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
+ WebKitDOMNode *image =
+ webkit_dom_node_get_first_child (child);
+
+ if (to_html && WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (image)) {
+ remove_evolution_attributes (
+ WEBKIT_DOM_ELEMENT (image));
+
+ webkit_dom_node_replace_child (
+ node, image, child, NULL);
+ }
+
+ skip_node = TRUE;
+ }
+
+ /* Leave paragraphs as they are */
+ if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-paragraph")) {
+ if (changing_mode && to_plain_text) {
+ content = webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (child));
+ g_string_append (buffer, content);
+ g_free (content);
+ skip_node = TRUE;
+ }
+ if (to_html) {
+ remove_base_attributes (WEBKIT_DOM_ELEMENT (child));
+ remove_evolution_attributes (WEBKIT_DOM_ELEMENT (child));
+ }
+ }
+
+ /* Signature */
+ if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child)) {
+ WebKitDOMNode *first_child;
+
+ first_child = webkit_dom_node_get_first_child (child);
+ if (WEBKIT_DOM_IS_ELEMENT (first_child)) {
+ if (element_has_class (
+ WEBKIT_DOM_ELEMENT (first_child),
+ "-x-evo-signature")) {
+
+ if (to_html) {
+ remove_base_attributes (
+ WEBKIT_DOM_ELEMENT (first_child));
+ remove_evolution_attributes (
+ WEBKIT_DOM_ELEMENT (first_child));
+ }
+ if (to_plain_text && !changing_mode) {
+ g_string_append (buffer, "\n");
+ content = webkit_dom_html_element_get_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (first_child));
+ g_string_append (buffer, content);
+ g_free (content);
+ skip_nl = TRUE;
+ }
+ skip_node = TRUE;
+ }
+ }
+ }
+
+ /* Replace smileys with their text representation */
+ if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
+ if (to_plain_text && !changing_mode) {
+ WebKitDOMNode *text_version;
+
+ text_version = webkit_dom_node_get_last_child (child);
+ content = webkit_dom_html_element_get_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (text_version));
+ g_string_append (buffer, content);
+ g_free (content);
+ skip_node = TRUE;
+ }
+ if (to_html) {
+ WebKitDOMElement *img;
+
+ img = WEBKIT_DOM_ELEMENT (
+ webkit_dom_node_get_first_child (child)),
+
+ remove_evolution_attributes (img);
+ remove_base_attributes (img);
+
+ webkit_dom_node_insert_before (
+ webkit_dom_node_get_parent_node (child),
+ WEBKIT_DOM_NODE (img),
+ child,
+ NULL);
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (child),
+ child,
+ NULL);
+ skip_node = TRUE;
+ }
+ }
+
+ /* Leave PRE elements untouched */
+ if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (child)) {
+ if (changing_mode && to_plain_text) {
+ content = webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (child));
+ g_string_append (buffer, content);
+ g_free (content);
+ skip_node = TRUE;
+ }
+ if (to_html)
+ remove_evolution_attributes (WEBKIT_DOM_ELEMENT (child));
+ }
+
+ if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)) {
+ if (to_plain_text) {
+ /* Insert new line when we hit BR element */
+ g_string_append (buffer, changing_mode ? "<br>" : "\n");
+ }
+ }
+ next:
+ if (webkit_dom_node_has_child_nodes (child) && !skip_node)
+ process_elements (
+ view, child, to_html, changing_mode, to_plain_text, buffer);
+ }
+
+ if (to_plain_text && (
+ WEBKIT_DOM_IS_HTML_DIV_ELEMENT (node) ||
+ WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (node) ||
+ WEBKIT_DOM_IS_HTML_PRE_ELEMENT (node) ||
+ WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))) {
+
+ gboolean add_br = TRUE;
+ WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node);
+
+ /* If we don't have next sibling (last element in body) or next element is
+ * signature we are not adding the BR element */
+ if (!next_sibling)
+ add_br = FALSE;
+
+ if (element_has_class (webkit_dom_node_get_parent_element (node), "-x-evo-indented"))
+ add_br = FALSE;
+
+ if (next_sibling && WEBKIT_DOM_IS_HTML_DIV_ELEMENT (next_sibling)) {
+ if (webkit_dom_element_query_selector (
+ WEBKIT_DOM_ELEMENT (next_sibling),
+ "span.-x-evo-signature", NULL)) {
+
+ add_br = FALSE;
+ }
+ }
+
+ content = webkit_dom_node_get_text_content (node);
+ if (add_br && g_utf8_strlen (content, -1) > 0 && !skip_nl)
+ g_string_append (buffer, changing_mode ? "<br>" : "\n");
+
+ g_free (content);
+ }
+
+}
+
+static void
+remove_wrapping (EHTMLEditorView *view)
+{
+ gint length;
+ gint ii;
+ WebKitDOMDocument *document;
+ WebKitDOMNodeList *list;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ list = webkit_dom_document_query_selector_all (document, "br.-x-evo-wrap-br", NULL);
+
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (node), node, NULL);
+ }
+}
+
+static void
+remove_images_in_element (EHTMLEditorView *view,
+ WebKitDOMElement *element,
+ gboolean html_mode)
+{
+ gint length, ii;
+ WebKitDOMNodeList *images;
+
+ images = webkit_dom_element_query_selector_all (
+ element, "img:not(.-x-evo-smiley-img)", NULL);
+
+ length = webkit_dom_node_list_get_length (images);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *img = webkit_dom_node_list_item (images, ii);
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (img), img, NULL);
+ }
+}
+
+static void
+remove_images (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ remove_images_in_element (
+ view,
+ WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)),
+ view->priv->html_mode);
+}
+
+static void
+toggle_smileys (EHTMLEditorView *view)
+{
+ gboolean html_mode;
+ gint length;
+ gint ii;
+ WebKitDOMDocument *document;
+ WebKitDOMNodeList *smileys;
+
+ html_mode = e_html_editor_view_get_html_mode (view);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ smileys = webkit_dom_document_query_selector_all (
+ document, "img.-x-evo-smiley-img", NULL);
+
+ length = webkit_dom_node_list_get_length (smileys);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *img = webkit_dom_node_list_item (smileys, ii);
+ WebKitDOMNode *text = webkit_dom_node_get_next_sibling (img);
+ WebKitDOMElement *parent = webkit_dom_node_get_parent_element (img);
+
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (html_mode ? text : img),
+ "style",
+ "display: none",
+ NULL);
+
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (html_mode ? img : text), "style");
+
+ if (html_mode)
+ element_add_class (parent, "-x-evo-resizable-wrapper");
+ else
+ element_remove_class (parent, "-x-evo-resizable-wrapper");
+ }
+}
+
+static void
+toggle_paragraphs_style_in_element (EHTMLEditorView *view,
+ WebKitDOMElement *element,
+ gboolean html_mode)
+{
+ EHTMLEditorSelection *selection;
+ gint ii, length;
+ WebKitDOMNodeList *paragraphs;
+
+ html_mode = e_html_editor_view_get_html_mode (view);
+ selection = e_html_editor_view_get_selection (view);
+
+ paragraphs = webkit_dom_element_query_selector_all (
+ element, ".-x-evo-paragraph", NULL);
+
+ length = webkit_dom_node_list_get_length (paragraphs);
+
+ for (ii = 0; ii < length; ii++) {
+ gchar *style;
+ const gchar *css_align;
+ WebKitDOMNode *node = webkit_dom_node_list_item (paragraphs, ii);
+
+ if (html_mode) {
+ style = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (node), "style");
+
+ if ((css_align = strstr (style, "text-align: "))) {
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (node),
+ "style",
+ g_str_has_prefix (css_align + 12, "center") ?
+ "text-align: center" :
+ "text-align: right",
+ NULL);
+ } else {
+ /* In HTML mode the paragraphs don't have width limit */
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (node), "style");
+ }
+ g_free (style);
+ } else {
+ WebKitDOMNode *parent;
+
+ parent = webkit_dom_node_get_parent_node (node);
+ /* If the paragraph is inside indented paragraph don't set
+ * the style as it will be inherited */
+ if (!element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented")) {
+ style = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (node), "style");
+
+ if ((css_align = strstr (style, "text-align: "))) {
+ const gchar *style_to_add;
+
+ style_to_add = g_str_has_prefix (
+ css_align + 12, "center") ?
+ "text-align: center;" :
+ "text-align: right;";
+
+ /* In HTML mode the paragraphs have width limit */
+ e_html_editor_selection_set_paragraph_style (
+ selection, WEBKIT_DOM_ELEMENT (node),
+ -1, 0, style_to_add);
+ }
+ g_free (style);
+ }
+ }
+ }
+}
+
+static void
+toggle_paragraphs_style (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ toggle_paragraphs_style_in_element (
+ view,
+ WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)),
+ view->priv->html_mode);
+}
+
+static gchar *
+process_content_for_saving_as_draft (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+ WebKitDOMElement *element, *document_element;
+ gchar *content;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+ element = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-caret-position");
+
+ if (element)
+ webkit_dom_element_set_attribute (
+ element, "style", "display: none; color: red;", NULL);
+
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-evo-draft", "", NULL);
+
+ document_element = webkit_dom_document_get_document_element (document);
+ content = webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (document_element));
+
+ webkit_dom_element_remove_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-evo-draft");
+
+ if (element)
+ webkit_dom_element_set_attribute (
+ element, "style", "color: red;", NULL);
+
+ return content;
+}
+
+static gchar *
+process_content_for_mode_change (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMNode *body;
+ GString *plain_text;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
+
+ plain_text = g_string_sized_new (1024);
+
+ process_elements (view, body, FALSE, TRUE, TRUE, plain_text);
+
+ g_string_append (plain_text, "</body></html>");
+
+ return g_string_free (plain_text, FALSE);
+}
+
+static void
+convert_element_from_html_to_plain_text (EHTMLEditorView *view,
+ WebKitDOMElement *element,
+ gboolean *wrap,
+ gboolean *quote)
+{
+ EHTMLEditorSelection *selection;
+ gint blockquotes_count;
+ gchar *inner_text, *inner_html;
+ gboolean restore = TRUE;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *top_signature, *signature, *blockquote, *main_blockquote;
+ WebKitDOMNode *signature_clone, *from;
+
+ selection = e_html_editor_view_get_selection (view);
+
+ document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
+
+ top_signature = webkit_dom_element_query_selector (
+ element, ".-x-evo-top-signature", NULL);
+ signature = webkit_dom_element_query_selector (
+ element, "span.-x-evo-signature", NULL);
+ main_blockquote = webkit_dom_element_query_selector (
+ element, "#-x-evo-main-cite", NULL);
+
+ blockquote = webkit_dom_document_create_element (
+ document, "blockquote", NULL);
+
+ if (main_blockquote) {
+ WebKitDOMElement *input_start;
+
+ webkit_dom_element_set_attribute (
+ blockquote, "type", "cite", NULL);
+
+ input_start = webkit_dom_element_query_selector (
+ element, "#-x-evo-input-start", NULL);
+
+ restore = input_start ? TRUE : FALSE;
+
+ if (input_start) {
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (input_start),
+ e_html_editor_selection_get_caret_position_node (
+ document),
+ NULL);
+ }
+ from = WEBKIT_DOM_NODE (main_blockquote);
+ } else {
+ if (signature) {
+ signature_clone = webkit_dom_node_clone_node (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (signature)),
+ TRUE);
+
+ webkit_dom_node_remove_child (
+ WEBKIT_DOM_NODE (element),
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (signature)),
+ NULL);
+ }
+ from = WEBKIT_DOM_NODE (element);
+ }
+
+ blockquotes_count = create_text_markers_for_citations_in_element (
+ WEBKIT_DOM_ELEMENT (from));
+
+ inner_text = webkit_dom_html_element_get_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (from));
+
+ webkit_dom_html_element_set_inner_text (
+ WEBKIT_DOM_HTML_ELEMENT (blockquote), inner_text, NULL);
+
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (blockquote));
+
+ parse_html_into_paragraphs (
+ view, document,
+ main_blockquote ? blockquote : WEBKIT_DOM_ELEMENT (element),
+ inner_html,
+ FALSE);
+
+ if (main_blockquote) {
+ webkit_dom_node_replace_child (
+ webkit_dom_node_get_parent_node (
+ WEBKIT_DOM_NODE (main_blockquote)),
+ WEBKIT_DOM_NODE (blockquote),
+ WEBKIT_DOM_NODE (main_blockquote),
+ NULL);
+
+ remove_evolution_attributes (WEBKIT_DOM_ELEMENT (element));
+ *wrap = TRUE;
+ } else {
+ WebKitDOMNode *first_child;
+
+ if (signature) {
+ if (!top_signature) {
+ signature_clone = webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (element),
+ signature_clone,
+ NULL);
+ } else {
+ webkit_dom_node_insert_before (
+ WEBKIT_DOM_NODE (element),
+ signature_clone,
+ webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (element)),
+ NULL);
+ }
+ }
+
+ first_child = webkit_dom_node_get_first_child (
+ WEBKIT_DOM_NODE (element));
+
+ if (!webkit_dom_node_has_child_nodes (first_child)) {
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (first_child),
+ UNICODE_ZERO_WIDTH_SPACE,
+ NULL);
+ }
+ webkit_dom_node_insert_before (
+ first_child,
+ e_html_editor_selection_get_caret_position_node (
+ document),
+ webkit_dom_node_get_first_child (first_child),
+ NULL);
+
+ *wrap = TRUE;
+ }
+
+ *quote = main_blockquote || blockquotes_count > 0;
+
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (element), "data-converted", "", NULL);
+
+ g_free (inner_text);
+ g_free (inner_html);
+
+ if (restore)
+ e_html_editor_selection_restore_caret_position (selection);
+}
+
+static gchar *
+process_content_for_plain_text (EHTMLEditorView *view)
+{
+ gboolean converted, wrap = FALSE, quote = FALSE, clean = FALSE;
+ gint length, ii;
+ GString *plain_text;
+ WebKitDOMDocument *document;
+ WebKitDOMNode *body, *source;
+ WebKitDOMNodeList *paragraphs;
+
+ plain_text = g_string_sized_new (1024);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
+ converted = webkit_dom_element_has_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-converted");
+ source = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), TRUE);
+
+ /* If composer is in HTML mode we have to move the content to plain version */
+ if (view->priv->html_mode) {
+ if (converted) {
+ toggle_paragraphs_style_in_element (
+ view, WEBKIT_DOM_ELEMENT (source), FALSE);
+ remove_images_in_element (
+ view, WEBKIT_DOM_ELEMENT (source), FALSE);
+ } else {
+ gchar *inner_html;
+ WebKitDOMElement *div;
+
+ inner_html = webkit_dom_html_element_get_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (body));
+
+ div = webkit_dom_document_create_element (
+ document, "div", NULL);
+
+ webkit_dom_html_element_set_inner_html (
+ WEBKIT_DOM_HTML_ELEMENT (div), inner_html, NULL);
+
+ webkit_dom_node_append_child (
+ WEBKIT_DOM_NODE (body),
+ WEBKIT_DOM_NODE (div),
+ NULL);
+
+ paragraphs = webkit_dom_element_query_selector_all (
+ div, "#-x-evo-input-start", NULL);
+
+ length = webkit_dom_node_list_get_length (paragraphs);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *paragraph;
+
+ paragraph = webkit_dom_node_list_item (paragraphs, ii);
+
+ webkit_dom_element_set_id (
+ WEBKIT_DOM_ELEMENT (paragraph), "");
+ }
+
+ convert_element_from_html_to_plain_text (
+ view, div, &wrap, &quote);
+
+ source = WEBKIT_DOM_NODE (div);
+
+ clean = TRUE;
+ }
+ }
+
+ paragraphs = webkit_dom_element_query_selector_all (
+ WEBKIT_DOM_ELEMENT (source), ".-x-evo-paragraph", NULL);
+
+ length = webkit_dom_node_list_get_length (paragraphs);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *paragraph;
+
+ paragraph = webkit_dom_node_list_item (paragraphs, ii);
+
+ if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (paragraph) ||
+ WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (paragraph)) {
+ WebKitDOMNode *item = webkit_dom_node_get_first_child (paragraph);
+
+ while (item) {
+ WebKitDOMNode *next_item =
+ webkit_dom_node_get_next_sibling (item);
+
+ if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
+ e_html_editor_selection_wrap_paragraph (
+ e_html_editor_view_get_selection (view),
+ WEBKIT_DOM_ELEMENT (item));
+ }
+ item = next_item;
+ }
+ } else {
+ e_html_editor_selection_wrap_paragraph (
+ e_html_editor_view_get_selection (view),
+ WEBKIT_DOM_ELEMENT (paragraph));
+ }
+ }
+
+ paragraphs = webkit_dom_element_query_selector_all (
+ WEBKIT_DOM_ELEMENT (source),
+ "span[id^=\"-x-evo-selection-\"], span#-x-evo-caret-position",
+ NULL);
+
+ length = webkit_dom_node_list_get_length (paragraphs);
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (paragraphs, ii);
+ WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
+
+ webkit_dom_node_remove_child (parent, node, NULL);
+ webkit_dom_node_normalize (parent);
+ }
+
+ if (view->priv->html_mode || quote)
+ quote_plain_text_recursive (document, source, source, 0);
+
+ process_elements (view, source, FALSE, FALSE, TRUE, plain_text);
+
+ if (clean)
+ webkit_dom_node_remove_child (
+ WEBKIT_DOM_NODE (body), source, NULL);
+
+ /* Return text content between <body> and </body> */
+ return g_string_free (plain_text, FALSE);
+}
+
+static gchar *
+process_content_for_html (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMNode *body;
+ WebKitDOMElement *element;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
+ process_elements (view, body, TRUE, FALSE, FALSE, NULL);
+ element = webkit_dom_document_get_document_element (document);
+ return webkit_dom_html_element_get_outer_html (
+ WEBKIT_DOM_HTML_ELEMENT (element));
+}
+
+static gboolean
+show_lose_formatting_dialog (EHTMLEditorView *view)
+{
+ gint result;
+ GtkWidget *toplevel, *dialog;
+ GtkWindow *parent = NULL;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
+
+ if (GTK_IS_WINDOW (toplevel))
+ parent = GTK_WINDOW (toplevel);
+
+ dialog = gtk_message_dialog_new (
+ parent,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_NONE,
+ _("Turning HTML mode off will cause the text "
+ "to lose all formatting. Do you want to continue?"));
+ gtk_dialog_add_buttons (
+ GTK_DIALOG (dialog),
+ _("_Don't lose formatting"), GTK_RESPONSE_CANCEL,
+ _("_Lose formatting"), GTK_RESPONSE_OK,
+ NULL);
+
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (result != GTK_RESPONSE_OK) {
+ gtk_widget_destroy (dialog);
+ /* Nothing has changed, but notify anyway */
+ g_object_notify (G_OBJECT (view), "html-mode");
+ return FALSE;
+ }
+
+ gtk_widget_destroy (dialog);
+
+ return TRUE;
+}
+
+static void
+clear_attributes (WebKitDOMDocument *document)
+{
+ gint length, ii;
+ WebKitDOMNamedNodeMap *attributes;
+ WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
+ WebKitDOMHTMLHeadElement *head = webkit_dom_document_get_head (document);
+ WebKitDOMElement *document_element =
+ webkit_dom_document_get_document_element (document);
+
+ /* Remove all attributes from HTML element */
+ attributes = webkit_dom_element_get_attributes (document_element);
+ length = webkit_dom_named_node_map_get_length (attributes);
+ for (ii = length - 1; ii >= 0; ii--) {
+ WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
+
+ webkit_dom_element_remove_attribute_node (
+ document_element, WEBKIT_DOM_ATTR (node), NULL);
+ }
+
+ /* Remove everything from HEAD element */
+ while (webkit_dom_node_has_child_nodes (WEBKIT_DOM_NODE (head))) {
+ webkit_dom_node_remove_child (
+ WEBKIT_DOM_NODE (head),
+ webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (head)),
+ NULL);
+ }
+
+ /* Remove non Evolution attributes from BODY element */
+ attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
+ length = webkit_dom_named_node_map_get_length (attributes);
+ for (ii = length - 1; ii >= 0; ii--) {
+ gchar *name;
+ WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
+
+ name = webkit_dom_node_get_local_name (node);
+
+ if (!g_str_has_prefix (name, "data-") ||
+ g_str_has_prefix (name, "data-inline") ||
+ g_str_has_prefix (name, "data-name")) {
+ webkit_dom_element_remove_attribute_node (
+ WEBKIT_DOM_ELEMENT (body),
+ WEBKIT_DOM_ATTR (node),
+ NULL);
+ }
+
+ g_free (name);
+ }
+}
+
+static void
+convert_when_changing_composer_mode (EHTMLEditorView *view)
+{
+ EHTMLEditorSelection *selection;
+ gboolean quote = FALSE, wrap = FALSE;
+ WebKitDOMDocument *document;
+ WebKitDOMHTMLElement *body;
+
+ selection = e_html_editor_view_get_selection (view);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+
+ convert_element_from_html_to_plain_text (
+ view, WEBKIT_DOM_ELEMENT (body), &wrap, &quote);
+
+ if (wrap)
+ e_html_editor_selection_wrap_paragraphs_in_document (selection, document);
+
+ if (quote) {
+ e_html_editor_selection_save_caret_position (selection);
+ body = WEBKIT_DOM_HTML_ELEMENT (e_html_editor_view_quote_plain_text (view));
+ e_html_editor_selection_restore_caret_position (selection);
+ }
+
+ clear_attributes (document);
+
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-converted", "", NULL);
+
+ /* Update fonts - in plain text we only want monospace */
+ e_html_editor_view_update_fonts (view);
+
+ e_html_editor_view_force_spell_check (view);
+}
+
+/**
+ * e_html_editor_view_set_html_mode:
+ * @view: an #EHTMLEditorView
+ * @html_mode: @TRUE to enable HTML mode, @FALSE to enable plain text mode
+ *
+ * When switching from HTML to plain text mode, user will be prompted whether
+ * he/she really wants to switch the mode and lose all formatting. When user
+ * declines, the property is not changed. When they accept, the all formatting
+ * is lost.
+ */
+void
+e_html_editor_view_set_html_mode (EHTMLEditorView *view,
+ gboolean html_mode)
+{
+ EHTMLEditorSelection *selection;
+ gboolean is_from_new_message, converted, edit_as_new, message, convert;
+ gboolean reply, hide;
+ WebKitDOMElement *blockquote;
+ WebKitDOMHTMLElement *body;
+ WebKitDOMDocument *document;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
+
+ selection = e_html_editor_view_get_selection (view);
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ body = webkit_dom_document_get_body (document);
+
+ is_from_new_message = webkit_dom_element_has_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-new-message");
+ converted = webkit_dom_element_has_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-converted");
+ edit_as_new = webkit_dom_element_has_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-edit-as-new");
+ message = webkit_dom_element_has_attribute (
+ WEBKIT_DOM_ELEMENT (body), "data-message");
+
+ reply = !is_from_new_message && !edit_as_new && message;
+ hide = !reply && !converted;
+
+ convert = message && ((!hide && reply && !converted) || (edit_as_new && !converted));
+
+ /* If toggling from HTML to plain text mode, ask user first */
+ if (convert && view->priv->html_mode && !html_mode) {
+ if (!show_lose_formatting_dialog (view))
+ return;
+
+ view->priv->html_mode = html_mode;
+
+ convert_when_changing_composer_mode (view);
+
+ goto out;
+ }
+
+ if (html_mode == view->priv->html_mode)
+ return;
+
+ view->priv->html_mode = html_mode;
+
+ /* Update fonts - in plain text we only want monospace */
+ e_html_editor_view_update_fonts (view);
+
+ blockquote = webkit_dom_document_query_selector (
+ document, "blockquote[type|=cite]", NULL);
+
+ if (view->priv->html_mode) {
+ if (blockquote)
+ e_html_editor_view_dequote_plain_text (view);
+
+ toggle_paragraphs_style (view);
+ toggle_smileys (view);
+ remove_wrapping (view);
+ } else {
+ gchar *plain;
+
+ /* Save caret position -> it will be restored in e-composer-private.c */
+ e_html_editor_selection_save_caret_position (selection);
+
+ if (blockquote) {
+ e_html_editor_selection_wrap_paragraphs_in_document (
+ selection, document);
+ e_html_editor_view_quote_plain_text (view);
+ }
+
+ toggle_paragraphs_style (view);
+ toggle_smileys (view);
+ remove_images (view);
+
+ plain = process_content_for_mode_change (view);
+
+ if (*plain)
+ webkit_web_view_load_string (
+ WEBKIT_WEB_VIEW (view), plain, NULL, NULL, "file://");
+
+ g_free (plain);
+ }
+
+ out:
+ g_object_notify (G_OBJECT (view), "html-mode");
+}
+
+/**
+ * e_html_editor_view_get_inline_spelling:
+ * @view: an #EHTMLEditorView
+ *
+ * Returns whether automatic spellchecking is enabled or not. When enabled,
+ * editor will perform spellchecking as user is typing. Otherwise spellcheck
+ * has to be run manually from menu.
+ *
+ * Returns: @TRUE when automatic spellchecking is enabled, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_view_get_inline_spelling (EHTMLEditorView *view)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
+
+ return view->priv->inline_spelling;
+}
+
+/**
+ * e_html_editor_view_set_inline_spelling:
+ * @view: an #EHTMLEditorView
+ * @inline_spelling: @TRUE to enable automatic spellchecking, @FALSE otherwise
+ *
+ * Enables or disables automatic spellchecking.
+ */
+void
+e_html_editor_view_set_inline_spelling (EHTMLEditorView *view,
+ gboolean inline_spelling)
+{
+ g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
+
+ if (view->priv->inline_spelling == inline_spelling)
+ return;
+
+ view->priv->inline_spelling = inline_spelling;
+
+ g_object_notify (G_OBJECT (view), "inline-spelling");
+}
+
+/**
+ * e_html_editor_view_get_magic_links:
+ * @view: an #EHTMLEditorView
+ *
+ * Returns whether automatic links conversion is enabled. When enabled, the editor
+ * will automatically convert any HTTP links into clickable HTML links.
+ *
+ * Returns: @TRUE when magic links are enabled, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_view_get_magic_links (EHTMLEditorView *view)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
+
+ return view->priv->magic_links;
+}
+
+/**
+ * e_html_editor_view_set_magic_links:
+ * @view: an #EHTMLEditorView
+ * @magic_links: @TRUE to enable magic links, @FALSE to disable them
+ *
+ * Enables or disables automatic links conversion.
+ */
+void
+e_html_editor_view_set_magic_links (EHTMLEditorView *view,
+ gboolean magic_links)
+{
+ g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
+
+ if (view->priv->magic_links == magic_links)
+ return;
+
+ view->priv->magic_links = magic_links;
+
+ g_object_notify (G_OBJECT (view), "magic-links");
+}
+
+/**
+ * e_html_editor_view_get_magic_smileys:
+ * @view: an #EHTMLEditorView
+ *
+ * Returns whether automatic conversion of smileys is enabled or disabled. When
+ * enabled, the editor will automatically convert text smileys ( :-), ;-),...)
+ * into images.
+ *
+ * Returns: @TRUE when magic smileys are enabled, @FALSE otherwise.
+ */
+gboolean
+e_html_editor_view_get_magic_smileys (EHTMLEditorView *view)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
+
+ return view->priv->magic_smileys;
+}
+
+/**
+ * e_html_editor_view_set_magic_smileys:
+ * @view: an #EHTMLEditorView
+ * @magic_smileys: @TRUE to enable magic smileys, @FALSE to disable them
+ *
+ * Enables or disables magic smileys.
+ */
+void
+e_html_editor_view_set_magic_smileys (EHTMLEditorView *view,
+ gboolean magic_smileys)
+{
+ g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
+
+ if (view->priv->magic_smileys == magic_smileys)
+ return;
+
+ view->priv->magic_smileys = magic_smileys;
+
+ g_object_notify (G_OBJECT (view), "magic-smileys");
+}
+
+/**
+ * e_html_editor_view_get_spell_checker:
+ * @view: an #EHTMLEditorView
+ *
+ * Returns an #ESpellChecker object that is used to perform spellchecking.
+ *
+ * Returns: An always-valid #ESpellChecker object
+ */
+ESpellChecker *
+e_html_editor_view_get_spell_checker (EHTMLEditorView *view)
+{
+ return E_SPELL_CHECKER (webkit_get_text_checker ());
+}
+
+/**
+ * e_html_editor_view_get_text_html:
+ * @view: an #EHTMLEditorView:
+ *
+ * Returns processed HTML content of the editor document (with elements attributes
+ * used in Evolution composer)
+ *
+ * Returns: A newly allocated string
+ */
+gchar *
+e_html_editor_view_get_text_html (EHTMLEditorView *view)
+{
+ return process_content_for_html (view);
+}
+
+/**
+ * e_html_editor_view_get_text_html_for_drafts:
+ * @view: an #EHTMLEditorView:
+ *
+ * Returns HTML content of the editor document (without elements attributes
+ * used in Evolution composer)
+ *
+ * Returns: A newly allocated string
+ */
+gchar *
+e_html_editor_view_get_text_html_for_drafts (EHTMLEditorView *view)
+{
+ return process_content_for_saving_as_draft (view);
+}
+
+/**
+ * e_html_editor_view_get_text_plain:
+ * @view: an #EHTMLEditorView
+ *
+ * Returns plain text content of the @view. The algorithm removes any
+ * formatting or styles from the document and keeps only the text and line
+ * breaks.
+ *
+ * Returns: A newly allocated string with plain text content of the document.
+ */
+gchar *
+e_html_editor_view_get_text_plain (EHTMLEditorView *view)
+{
+ return process_content_for_plain_text (view);
+}
+
+static void
+convert_and_load_html_to_plain_text (EHTMLEditorView *view,
+ const gchar *html)
+{
+ view->priv->convertor_insert = FALSE;
+
+ webkit_web_view_load_string (
+ view->priv->convertor_web_view, html, NULL, NULL, "file://");
+}
+
+void
+e_html_editor_view_convert_and_insert_html_to_plain_text (EHTMLEditorView *view,
+ const gchar *html)
+{
+ view->priv->convertor_insert = TRUE;
+
+ webkit_web_view_load_string (
+ view->priv->convertor_web_view, html, NULL, NULL, "file://");
+}
+
+/**
+ * e_html_editor_view_set_text_html:
+ * @view: an #EHTMLEditorView
+ * @text: HTML code to load into the editor
+ *
+ * Loads given @text into the editor, destroying any content already present.
+ */
+void
+e_html_editor_view_set_text_html (EHTMLEditorView *view,
+ const gchar *text)
+{
+ view->priv->reload_in_progress = TRUE;
+
+ /* Only convert messages that are in HTML */
+ if (!view->priv->html_mode && *text && !strstr (text, "data-evo-draft")) {
+ if (strstr (text, "<!-- text/html -->")) {
+ if (!show_lose_formatting_dialog (view)) {
+ e_html_editor_view_set_html_mode (view, TRUE);
+ webkit_web_view_load_string (
+ WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
+ return;
+ }
+ convert_and_load_html_to_plain_text (view, text);
+ } else {
+ convert_and_load_html_to_plain_text (view, text);
+ }
+ } else {
+ webkit_web_view_load_string (
+ WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
+ }
+}
+
+/**
+ * e_html_editor_view_set_text_plain:
+ * @view: an #EHTMLEditorView
+ * @text: A plain text to load into the editor
+ *
+ * Loads given @text into the editor, destryoing any content already present.
+ */
+void
+e_html_editor_view_set_text_plain (EHTMLEditorView *view,
+ const gchar *text)
+{
+ view->priv->reload_in_progress = TRUE;
+
+ webkit_web_view_load_string (
+ WEBKIT_WEB_VIEW (view), text, "text/plain", NULL, "file://");
+}
+
+/**
+ * e_html_editor_view_paste_clipboard_quoted:
+ * @view: an #EHTMLEditorView
+ *
+ * Pastes current content of clipboard into the editor as quoted text
+ */
+void
+e_html_editor_view_paste_clipboard_quoted (EHTMLEditorView *view)
+{
+ EHTMLEditorViewClass *class;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
+
+ class = E_HTML_EDITOR_VIEW_GET_CLASS (view);
+ g_return_if_fail (class->paste_clipboard_quoted != NULL);
+
+ class->paste_clipboard_quoted (view);
+}
+
+void
+e_html_editor_view_embed_styles (EHTMLEditorView *view)
+{
+ WebKitWebSettings *settings;
+ WebKitDOMDocument *document;
+ WebKitDOMElement *sheet;
+ gchar *stylesheet_uri;
+ gchar *stylesheet_content;
+ const gchar *stylesheet;
+ gsize length;
+
+ settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+
+ g_object_get (
+ G_OBJECT (settings),
+ "user-stylesheet-uri", &stylesheet_uri,
+ NULL);
+
+ stylesheet = strstr (stylesheet_uri, ",");
+ stylesheet_content = (gchar *) g_base64_decode (stylesheet, &length);
+ g_free (stylesheet_uri);
+
+ if (length == 0) {
+ g_free (stylesheet_content);
+ return;
+ }
+
+ e_web_view_create_and_add_css_style_sheet (document, "-x-evo-composer-sheet");
+
+ sheet = webkit_dom_document_get_element_by_id (document, "-x-evo-composer-sheet");
+ webkit_dom_element_set_attribute (
+ sheet,
+ "type",
+ "text/css",
+ NULL);
+
+ webkit_dom_html_element_set_inner_html (WEBKIT_DOM_HTML_ELEMENT (sheet), stylesheet_content, NULL);
+
+ g_free (stylesheet_content);
+}
+
+void
+e_html_editor_view_remove_embed_styles (EHTMLEditorView *view)
+{
+ WebKitDOMDocument *document;
+ WebKitDOMElement *sheet;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ sheet = webkit_dom_document_get_element_by_id (
+ document, "-x-evo-composer-sheet");
+
+ webkit_dom_node_remove_child (
+ webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (sheet)),
+ WEBKIT_DOM_NODE (sheet),
+ NULL);
+}
+
+static const gchar *
+citation_color_level_1 (void)
+{
+ return "rgb(114,159,207)"; /* Sky Blue 1 */
+}
+
+static const gchar *
+citation_color_level_2 (void)
+{
+ return "rgb(173,127,168)"; /* Plum 1 */
+}
+
+static const gchar *
+citation_color_level_3 (void)
+{
+ return "rgb(138,226,52)"; /* Chameleon 1 */
+}
+
+static const gchar *
+citation_color_level_4 (void)
+{
+ return "rgb(252,175,62)"; /* Orange 1 */
+}
+
+static const gchar *
+citation_color_level_5 (void)
+{
+ return "rgb(233,185,110)"; /* Chocolate 1 */
+}
+
+/**
+ * e_html_editor_view_update_fonts:
+ * @view: an #EHTMLEditorView
+ *
+ * Forces the editor to reload font settings from WebKitWebSettings and apply
+ * it on the content of the editor document.
+ */
+void
+e_html_editor_view_update_fonts (EHTMLEditorView *view)
+{
+ GString *stylesheet;
+ gchar *base64;
+ gchar *aa = NULL;
+ WebKitWebSettings *settings;
+ PangoFontDescription *ms, *vw;
+ const gchar *styles[] = { "normal", "oblique", "italic" };
+ const gchar *smoothing = NULL;
+ GtkStyleContext *context;
+ GdkColor *link = NULL;
+ GdkColor *visited = NULL;
+ gchar *font;
+
+ font = g_settings_get_string (
+ view->priv->font_settings,
+ "monospace-font-name");
+ ms = pango_font_description_from_string (
+ font ? font : "monospace 10");
+ g_free (font);
+
+ if (view->priv->html_mode) {
+ font = g_settings_get_string (
+ view->priv->font_settings,
+ "font-name");
+ vw = pango_font_description_from_string (
+ font ? font : "serif 10");
+ g_free (font);
+ } else {
+ /* When in plain text mode, force monospace font */
+ vw = pango_font_description_copy (ms);
+ }
+
+ stylesheet = g_string_new ("");
+ g_string_append_printf (
+ stylesheet,
+ "body {\n"
+ " font-family: '%s';\n"
+ " font-size: %dpt;\n"
+ " font-weight: %d;\n"
+ " font-style: %s;\n",
+ pango_font_description_get_family (vw),
+ pango_font_description_get_size (vw) / PANGO_SCALE,
+ pango_font_description_get_weight (vw),
+ styles[pango_font_description_get_style (vw)]);
+
+ if (view->priv->aliasing_settings != NULL)
+ aa = g_settings_get_string (
+ view->priv->aliasing_settings, "antialiasing");
+
+ if (g_strcmp0 (aa, "none") == 0)
+ smoothing = "none";
+ else if (g_strcmp0 (aa, "grayscale") == 0)
+ smoothing = "antialiased";
+ else if (g_strcmp0 (aa, "rgba") == 0)
+ smoothing = "subpixel-antialiased";
+
+ if (smoothing != NULL)
+ g_string_append_printf (
+ stylesheet,
+ " -webkit-font-smoothing: %s;\n",
+ smoothing);
+
+ g_free (aa);
+
+ g_string_append (stylesheet, "}\n");
+
+ g_string_append_printf (
+ stylesheet,
+ "pre,code,.pre {\n"
+ " font-family: '%s';\n"
+ " font-size: %dpt;\n"
+ " font-weight: %d;\n"
+ " font-style: %s;\n"
+ "}",
+ pango_font_description_get_family (ms),
+ pango_font_description_get_size (ms) / PANGO_SCALE,
+ pango_font_description_get_weight (ms),
+ styles[pango_font_description_get_style (ms)]);
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (view));
+ gtk_style_context_get_style (
+ context,
+ "link-color", &link,
+ "visited-link-color", &visited,
+ NULL);
+
+ if (link == NULL) {
+ link = g_slice_new0 (GdkColor);
+ link->blue = G_MAXINT16;
+ }
+
+ if (visited == NULL) {
+ visited = g_slice_new0 (GdkColor);
+ visited->red = G_MAXINT16;
+ }
+
+ g_string_append_printf (
+ stylesheet,
+ "a {\n"
+ " color: #%06x;\n"
+ "}\n"
+ "a:visited {\n"
+ " color: #%06x;\n"
+ "}\n",
+ e_color_to_value (link),
+ e_color_to_value (visited));
+
+ /* See bug #689777 for details */
+ g_string_append (
+ stylesheet,
+ "p,pre,code,address {\n"
+ " margin: 0;\n"
+ "}\n"
+ "h1,h2,h3,h4,h5,h6 {\n"
+ " margin-top: 0.2em;\n"
+ " margin-bottom: 0.2em;\n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ "img "
+ "{\n"
+ " height: inherit; \n"
+ " width: inherit; \n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ "span.-x-evo-resizable-wrapper "
+ "{\n"
+ " resize: both; \n"
+ " overflow: hidden; \n"
+ " display: inline-block; \n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ "span.-x-evo-resizable-wrapper:hover "
+ "{\n"
+ " outline: 1px dashed red; \n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ "ul,ol "
+ "{\n"
+ " -webkit-padding-start: 7ch; \n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ ".-x-evo-list-item-alignt-left "
+ "{\n"
+ " text-align: left; \n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ ".-x-evo-list-item-align-center "
+ "{\n"
+ " text-align: center; \n"
+ " -webkit-padding-start: 0ch; \n"
+ " margin-left: -3ch; \n"
+ " margin-right: 1ch; \n"
+ " list-style-position: inside; \n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ ".-x-evo-list-item-align-right "
+ "{\n"
+ " text-align: right; \n"
+ " -webkit-padding-start: 0ch; \n"
+ " margin-left: -3ch; \n"
+ " margin-right: 1ch; \n"
+ " list-style-position: inside; \n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ "ol,ul "
+ "{\n"
+ " -webkit-margin-before: 0em; \n"
+ " -webkit-margin-after: 0em; \n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ "blockquote "
+ "{\n"
+ " -webkit-margin-before: 0em; \n"
+ " -webkit-margin-after: 0em; \n"
+ "}\n");
+
+ g_string_append (
+ stylesheet,
+ "blockquote[type=cite] "
+ "{\n"
+ " padding: 0.0ex 0ex;\n"
+ " margin: 0ex;\n"
+ " -webkit-margin-start: 0em; \n"
+ " -webkit-margin-end : 0em; \n"
+ " color: #737373 !important;\n"
+ "}\n");
+
+ g_string_append_printf (
+ stylesheet,
+ ".quote_character "
+ "{\n"
+ " color: %s;\n"
+ "}\n",
+ citation_color_level_1 ());
+
+ g_string_append_printf (
+ stylesheet,
+ ".quote_character+"
+ ".quote_character"
+ "{\n"
+ " color: %s;\n"
+ "}\n",
+ citation_color_level_2 ());
+
+ g_string_append_printf (
+ stylesheet,
+ ".quote_character+"
+ ".quote_character+"
+ ".quote_character"
+ "{\n"
+ " color: %s;\n"
+ "}\n",
+ citation_color_level_3 ());
+
+ g_string_append_printf (
+ stylesheet,
+ ".quote_character+"
+ ".quote_character+"
+ ".quote_character+"
+ ".quote_character"
+ "{\n"
+ " color: %s;\n"
+ "}\n",
+ citation_color_level_4 ());
+
+ g_string_append_printf (
+ stylesheet,
+ ".quote_character+"
+ ".quote_character+"
+ ".quote_character+"
+ ".quote_character+"
+ ".quote_character"
+ "{\n"
+ " color: %s;\n"
+ "}\n",
+ citation_color_level_5 ());
+
+ g_string_append (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " padding: 0.4ex 1ex;\n"
+ " margin: 1ex;\n"
+ " border-width: 0px 2px 0px 2px;\n"
+ " border-style: none solid none solid;\n"
+ " border-radius: 2px;\n"
+ "}\n");
+
+ /* Block quote border colors are borrowed from Thunderbird. */
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ "}\n",
+ citation_color_level_1 ());
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ "}\n",
+ citation_color_level_2 ());
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ "}\n",
+ citation_color_level_3 ());
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ "}\n",
+ citation_color_level_4 ());
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ "}\n",
+ citation_color_level_5 ());
+
+ gdk_color_free (link);
+ gdk_color_free (visited);
+
+ base64 = g_base64_encode ((guchar *) stylesheet->str, stylesheet->len);
+ g_string_free (stylesheet, TRUE);
+
+ stylesheet = g_string_new ("data:text/css;charset=utf-8;base64,");
+ g_string_append (stylesheet, base64);
+ g_free (base64);
+
+ settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
+ g_object_set (
+ G_OBJECT (settings),
+ "default-font-size", pango_font_description_get_size (vw) / PANGO_SCALE,
+ "default-font-family", pango_font_description_get_family (vw),
+ "monospace-font-family", pango_font_description_get_family (ms),
+ "default-monospace-font-size", (pango_font_description_get_size (ms) / PANGO_SCALE),
+ "user-stylesheet-uri", stylesheet->str,
+ NULL);
+
+ g_string_free (stylesheet, TRUE);
+
+ pango_font_description_free (ms);
+ pango_font_description_free (vw);
+}
+
+/**
+ * e_html_editor_view_get_element_under_mouse_click:
+ * @view: an #EHTMLEditorView
+ *
+ * Returns DOM element, that was clicked on.
+ *
+ * Returns: DOM element on that was clicked.
+ */
+WebKitDOMElement *
+e_html_editor_view_get_element_under_mouse_click (EHTMLEditorView *view)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), NULL);
+
+ return view->priv->element_under_mouse;
+}
+
+/**
+ * e_html_editor_view_check_magic_links
+ * @view: an #EHTMLEditorView
+ * @include_space: If TRUE the pattern for link expects space on end
+ *
+ * Check if actual selection in given editor is link. If so, it is surrounded
+ * with ANCHOR element.
+ */
+void
+e_html_editor_view_check_magic_links (EHTMLEditorView *view,
+ gboolean include_space)
+{
+ WebKitDOMRange *range;
+
+ g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
+
+ range = html_editor_view_get_dom_range (view);
+ html_editor_view_check_magic_links (view, range, include_space, NULL);
+}
+
+static CamelMimePart *
+e_html_editor_view_add_inline_image_from_element (EHTMLEditorView *view,
+ WebKitDOMElement *element,
+ const gchar *attribute)
+{
+ CamelStream *stream;
+ CamelDataWrapper *wrapper;
+ CamelMimePart *part = NULL;
+ gsize decoded_size;
+ gssize size;
+ gchar *mime_type = NULL;
+ gchar *element_src, *cid, *name;
+ const gchar *base64_encoded_data;
+ guchar *base64_decoded_data;
+
+ if (!WEBKIT_DOM_IS_ELEMENT (element)) {
+ return NULL;
+ }
+
+ element_src = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (element), attribute);
+
+ base64_encoded_data = strstr (element_src, ";base64,");
+ if (!base64_encoded_data)
+ goto out;
+
+ mime_type = g_strndup (
+ element_src + 5,
+ base64_encoded_data - (strstr (element_src, "data:") + 5));
+
+ /* Move to actual data */
+ base64_encoded_data += 8;
+
+ base64_decoded_data = g_base64_decode (base64_encoded_data, &decoded_size);
+
+ stream = camel_stream_mem_new ();
+ size = camel_stream_write (
+ stream, (gchar *) base64_decoded_data, decoded_size, NULL, NULL);
+
+ if (size == -1)
+ goto out;
+
+ wrapper = camel_data_wrapper_new ();
+ camel_data_wrapper_construct_from_stream_sync (
+ wrapper, stream, NULL, NULL);
+ g_object_unref (stream);
+
+ camel_data_wrapper_set_mime_type (wrapper, mime_type);
+
+ part = camel_mime_part_new ();
+ camel_medium_set_content (CAMEL_MEDIUM (part), wrapper);
+ g_object_unref (wrapper);
+
+ cid = camel_header_msgid_generate ();
+ camel_mime_part_set_content_id (part, cid);
+ name = webkit_dom_element_get_attribute (element, "data-name");
+ camel_mime_part_set_filename (part, name);
+ g_free (name);
+ camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64);
+out:
+ g_free (mime_type);
+ g_free (element_src);
+ g_free (base64_decoded_data);
+
+ return part;
+}
+
+GList *
+e_html_editor_view_get_parts_for_inline_images (EHTMLEditorView *view)
+{
+ GHashTable *added;
+ GList *parts = NULL;
+ gint length, ii;
+ WebKitDOMDocument *document;
+ WebKitDOMNodeList *list;
+
+ document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
+ list = webkit_dom_document_query_selector_all (document, "img[data-inline]", NULL);
+
+ length = webkit_dom_node_list_get_length (list);
+ if (length == 0)
+ return parts;
+
+ added = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
+ for (ii = 0; ii < length; ii++) {
+ CamelMimePart *part;
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+ gchar *src = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (node), "src");
+
+ if (!g_hash_table_lookup (added, src)) {
+ part = e_html_editor_view_add_inline_image_from_element (
+ view, WEBKIT_DOM_ELEMENT (node), "src");
+ parts = g_list_append (parts, part);
+ g_hash_table_insert (
+ added, src, (gpointer) camel_mime_part_get_content_id (part));
+ }
+ g_free (src);
+ }
+
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+ const gchar *id;
+ gchar *src = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (node), "src");
+
+ if ((id = g_hash_table_lookup (added, src)) != NULL) {
+ gchar *cid = g_strdup_printf ("cid:%s", id);
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (node), "src", cid, NULL);
+ g_free (cid);
+ }
+ g_free (src);
+ }
+
+ list = webkit_dom_document_query_selector_all (
+ document, "[data-inline][background]", NULL);
+ length = webkit_dom_node_list_get_length (list);
+ for (ii = 0; ii < length; ii++) {
+ CamelMimePart *part;
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+ gchar *src = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (node), "background");
+
+ if (!g_hash_table_lookup (added, src)) {
+ part = e_html_editor_view_add_inline_image_from_element (
+ view, WEBKIT_DOM_ELEMENT (node), "background");
+ if (part) {
+ parts = g_list_append (parts, part);
+ g_hash_table_insert (
+ added, src,
+ (gpointer) camel_mime_part_get_content_id (part));
+ }
+ }
+ g_free (src);
+ }
+
+ for (ii = 0; ii < length; ii++) {
+ WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
+ gchar *src = webkit_dom_element_get_attribute (
+ WEBKIT_DOM_ELEMENT (node), "background");
+ const gchar *id;
+
+ if ((id = g_hash_table_lookup (added, src)) != NULL) {
+ gchar *cid = g_strdup_printf ("cid:%s", id);
+ webkit_dom_element_set_attribute (
+ WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
+ g_free (cid);
+ }
+ g_free (src);
+ }
+
+ g_hash_table_destroy (added);
+
+ return parts;
+}
+
+/**
+ * e_html_editor_view_add_inline_image_from_mime_part:
+ * @composer: a composer object
+ * @part: a CamelMimePart containing image data
+ *
+ * This adds the mime part @part to @composer as an inline image.
+ **/
+void
+e_html_editor_view_add_inline_image_from_mime_part (EHTMLEditorView *view,
+ CamelMimePart *part)
+{
+ CamelDataWrapper *dw;
+ CamelStream *stream;
+ GByteArray *byte_array;
+ gchar *src, *base64_encoded, *mime_type, *cid_src;
+ const gchar *cid, *name;
+
+ stream = camel_stream_mem_new ();
+ dw = camel_medium_get_content (CAMEL_MEDIUM (part));
+ g_return_if_fail (dw);
+
+ mime_type = camel_data_wrapper_get_mime_type (dw);
+ camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL);
+ camel_stream_close (stream, NULL, NULL);
+
+ byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (stream));
+
+ if (!byte_array->data)
+ return;
+
+ base64_encoded = g_base64_encode ((const guchar *) byte_array->data, byte_array->len);
+
+ name = camel_mime_part_get_filename (part);
+ /* Insert file name before new src */
+ src = g_strconcat (name, ";data:", mime_type, ";base64,", base64_encoded, NULL);
+
+ cid = camel_mime_part_get_content_id (part);
+ if (!cid) {
+ camel_mime_part_set_content_id (part, NULL);
+ cid = camel_mime_part_get_content_id (part);
+ }
+ cid_src = g_strdup_printf ("cid:%s", cid);
+
+ g_hash_table_insert (view->priv->inline_images, cid_src, src);
+
+ g_free (base64_encoded);
+ g_free (mime_type);
+ g_object_unref (stream);
+}
diff --git a/e-util/e-html-editor-view.h b/e-util/e-html-editor-view.h
new file mode 100644
index 0000000000..e40b15b375
--- /dev/null
+++ b/e-util/e-html-editor-view.h
@@ -0,0 +1,164 @@
+/*
+ * e-html-editor-view.h
+ *
+ * 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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_VIEW_H
+#define E_HTML_EDITOR_VIEW_H
+
+#include <webkit/webkit.h>
+
+#include <camel/camel.h>
+
+#include <e-util/e-html-editor-selection.h>
+#include <e-util/e-emoticon.h>
+#include <e-util/e-spell-checker.h>
+#include <e-util/e-util-enums.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR_VIEW \
+ (e_html_editor_view_get_type ())
+#define E_HTML_EDITOR_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR_VIEW, EHTMLEditorView))
+#define E_HTML_EDITOR_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR_VIEW, EHTMLEditorViewClass))
+#define E_IS_HTML_EDITOR_VIEW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR_VIEW))
+#define E_IS_HTML_EDITOR_VIEW_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR_VIEW))
+#define E_HTML_EDITOR_VIEW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR_VIEW, EHTMLEditorViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditorView EHTMLEditorView;
+typedef struct _EHTMLEditorViewClass EHTMLEditorViewClass;
+typedef struct _EHTMLEditorViewPrivate EHTMLEditorViewPrivate;
+
+struct _EHTMLEditorView {
+ WebKitWebView parent;
+ EHTMLEditorViewPrivate *priv;
+};
+
+struct _EHTMLEditorViewClass {
+ WebKitWebViewClass parent_class;
+
+ void (*paste_clipboard_quoted)
+ (EHTMLEditorView *view);
+ gboolean (*popup_event) (EHTMLEditorView *view,
+ GdkEventButton *event);
+ void (*paste_primary_clipboard)
+ (EHTMLEditorView *view);
+};
+
+GType e_html_editor_view_get_type (void) G_GNUC_CONST;
+EHTMLEditorView *
+ e_html_editor_view_new (void);
+EHTMLEditorSelection *
+ e_html_editor_view_get_selection
+ (EHTMLEditorView *view);
+gboolean e_html_editor_view_exec_command (EHTMLEditorView *view,
+ EHTMLEditorViewCommand command,
+ const gchar *value);
+gboolean e_html_editor_view_get_changed (EHTMLEditorView *view);
+void e_html_editor_view_set_changed (EHTMLEditorView *view,
+ gboolean changed);
+gboolean e_html_editor_view_get_html_mode
+ (EHTMLEditorView *view);
+void e_html_editor_view_set_html_mode
+ (EHTMLEditorView *view,
+ gboolean html_mode);
+gboolean e_html_editor_view_get_inline_spelling
+ (EHTMLEditorView *view);
+void e_html_editor_view_set_inline_spelling
+ (EHTMLEditorView *view,
+ gboolean inline_spelling);
+gboolean e_html_editor_view_get_magic_links
+ (EHTMLEditorView *view);
+void e_html_editor_view_set_magic_links
+ (EHTMLEditorView *view,
+ gboolean magic_links);
+void e_html_editor_view_insert_smiley
+ (EHTMLEditorView *view,
+ EEmoticon *emoticon);
+gboolean e_html_editor_view_get_magic_smileys
+ (EHTMLEditorView *view);
+void e_html_editor_view_set_magic_smileys
+ (EHTMLEditorView *view,
+ gboolean magic_smileys);
+ESpellChecker * e_html_editor_view_get_spell_checker
+ (EHTMLEditorView *view);
+gchar * e_html_editor_view_get_text_html
+ (EHTMLEditorView *view);
+gchar * e_html_editor_view_get_text_html_for_drafts
+ (EHTMLEditorView *view);
+gchar * e_html_editor_view_get_text_plain
+ (EHTMLEditorView *view);
+void e_html_editor_view_convert_and_insert_html_to_plain_text
+ (EHTMLEditorView *view,
+ const gchar *html);
+void e_html_editor_view_set_text_html
+ (EHTMLEditorView *view,
+ const gchar *text);
+void e_html_editor_view_set_text_plain
+ (EHTMLEditorView *view,
+ const gchar *text);
+void e_html_editor_view_paste_clipboard_quoted
+ (EHTMLEditorView *view);
+void e_html_editor_view_embed_styles (EHTMLEditorView *view);
+void e_html_editor_view_remove_embed_styles
+ (EHTMLEditorView *view);
+void e_html_editor_view_update_fonts (EHTMLEditorView *view);
+WebKitDOMElement *
+ e_html_editor_view_get_element_under_mouse_click
+ (EHTMLEditorView *view);
+void e_html_editor_view_check_magic_links
+ (EHTMLEditorView *view,
+ gboolean while_typing);
+WebKitDOMElement *
+ e_html_editor_view_quote_plain_text_element
+ (EHTMLEditorView *view,
+ WebKitDOMElement *element);
+WebKitDOMElement *
+ e_html_editor_view_quote_plain_text
+ (EHTMLEditorView *view);
+void e_html_editor_view_dequote_plain_text
+ (EHTMLEditorView *view);
+void e_html_editor_view_turn_spell_check_off
+ (EHTMLEditorView *view);
+void e_html_editor_view_force_spell_check_for_current_paragraph
+ (EHTMLEditorView *view);
+void e_html_editor_view_force_spell_check
+ (EHTMLEditorView *view);
+void e_html_editor_view_add_inline_image_from_mime_part
+ (EHTMLEditorView *view,
+ CamelMimePart *part);
+GList * e_html_editor_view_get_parts_for_inline_images
+ (EHTMLEditorView *view);
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_VIEW_H */
diff --git a/e-util/e-html-editor.c b/e-util/e-html-editor.c
new file mode 100644
index 0000000000..aeae5373b0
--- /dev/null
+++ b/e-util/e-html-editor.c
@@ -0,0 +1,1178 @@
+/*
+ * 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_node_get_node_type (node) == 3);
+ 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;
+
+ /* 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->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->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));
+ 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;
+}
+
diff --git a/e-util/e-html-editor.h b/e-util/e-html-editor.h
new file mode 100644
index 0000000000..5618cc8e05
--- /dev/null
+++ b/e-util/e-html-editor.h
@@ -0,0 +1,108 @@
+/*
+ * e-html-editor.h
+ *
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_HTML_EDITOR_H
+#define E_HTML_EDITOR_H
+
+#include <gtk/gtk.h>
+#include <e-util/e-activity.h>
+#include <e-util/e-activity-bar.h>
+#include <e-util/e-html-editor-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_HTML_EDITOR \
+ (e_html_editor_get_type ())
+#define E_HTML_EDITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_HTML_EDITOR, EHTMLEditor))
+#define E_HTML_EDITOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_HTML_EDITOR, EHTMLEditorClass))
+#define E_IS_HTML_EDITOR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_HTML_EDITOR))
+#define E_IS_HTML_EDITOR_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_HTML_EDITOR))
+#define E_HTML_EDITOR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_HTML_EDITOR, EHTMLEditorClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EHTMLEditor EHTMLEditor;
+typedef struct _EHTMLEditorClass EHTMLEditorClass;
+typedef struct _EHTMLEditorPrivate EHTMLEditorPrivate;
+
+struct _EHTMLEditor {
+ GtkGrid parent;
+ EHTMLEditorPrivate *priv;
+};
+
+struct _EHTMLEditorClass {
+ GtkGridClass parent_class;
+
+ void (*update_actions) (EHTMLEditor *editor,
+ GdkEventButton *event);
+ void (*spell_languages_changed)
+ (EHTMLEditor *editor);
+};
+
+GType e_html_editor_get_type (void) G_GNUC_CONST;
+GtkWidget * e_html_editor_new (void);
+EHTMLEditorView *
+ e_html_editor_get_view (EHTMLEditor *editor);
+GtkBuilder * e_html_editor_get_builder (EHTMLEditor *editor);
+GtkUIManager * e_html_editor_get_ui_manager (EHTMLEditor *editor);
+GtkAction * e_html_editor_get_action (EHTMLEditor *editor,
+ const gchar *action_name);
+GtkActionGroup *e_html_editor_get_action_group (EHTMLEditor *editor,
+ const gchar *group_name);
+GtkWidget * e_html_editor_get_widget (EHTMLEditor *editor,
+ const gchar *widget_name);
+GtkWidget * e_html_editor_get_managed_widget
+ (EHTMLEditor *editor,
+ const gchar *widget_path);
+GtkWidget * e_html_editor_get_style_combo_box
+ (EHTMLEditor *editor);
+const gchar * e_html_editor_get_filename (EHTMLEditor *editor);
+void e_html_editor_set_filename (EHTMLEditor *editor,
+ const gchar *filename);
+EActivityBar * e_html_editor_get_activity_bar (EHTMLEditor *editor);
+EActivity * e_html_editor_new_activity (EHTMLEditor *editor);
+void e_html_editor_pack_above (EHTMLEditor *editor,
+ GtkWidget *child);
+
+/*****************************************************************************
+ * High-Level Editing Interface
+ *****************************************************************************/
+
+gboolean e_html_editor_save (EHTMLEditor *editor,
+ const gchar *filename,
+ gboolean as_html,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* E_HTML_EDITOR_H */
diff --git a/e-util/e-image-chooser-dialog.c b/e-util/e-image-chooser-dialog.c
new file mode 100644
index 0000000000..73a6c202cb
--- /dev/null
+++ b/e-util/e-image-chooser-dialog.c
@@ -0,0 +1,223 @@
+/*
+ * e-image-chooser-dialog.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 "e-image-chooser-dialog.h"
+
+#define E_IMAGE_CHOOSER_DIALOG_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_IMAGE_CHOOSER_DIALOG, EImageChooserDialogPrivate))
+
+#define PREVIEW_WIDTH 256
+#define PREVIEW_HEIGHT 256
+
+typedef struct _Context Context;
+
+struct _EImageChooserDialogPrivate {
+ GCancellable *cancellable;
+};
+
+struct _Context {
+ GtkFileChooser *file_chooser;
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (
+ EImageChooserDialog,
+ e_image_chooser_dialog,
+ GTK_TYPE_FILE_CHOOSER_DIALOG)
+
+static void
+context_free (Context *context)
+{
+ g_object_unref (context->file_chooser);
+ g_object_unref (context->cancellable);
+
+ g_slice_free (Context, context);
+}
+
+static void
+image_chooser_dialog_read_cb (GFile *preview_file,
+ GAsyncResult *result,
+ Context *context)
+{
+ GdkPixbuf *pixbuf;
+ GtkWidget *preview_widget;
+ GFileInputStream *input_stream;
+
+ input_stream = g_file_read_finish (preview_file, result, NULL);
+
+ /* FIXME Handle errors better, but remember
+ * to ignore G_IO_ERROR_CANCELLED. */
+ if (input_stream == NULL)
+ goto exit;
+
+ /* XXX This blocks, but GDK-PixBuf offers no asynchronous
+ * alternative and I don't want to deal with making GDK
+ * calls from threads and all the crap that goes with it. */
+ pixbuf = gdk_pixbuf_new_from_stream_at_scale (
+ G_INPUT_STREAM (input_stream),
+ PREVIEW_WIDTH, PREVIEW_HEIGHT, TRUE,
+ context->cancellable, NULL);
+
+ preview_widget = gtk_file_chooser_get_preview_widget (
+ context->file_chooser);
+
+ gtk_file_chooser_set_preview_widget_active (
+ context->file_chooser, pixbuf != NULL);
+
+ gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf);
+
+ if (pixbuf != NULL)
+ g_object_unref (pixbuf);
+
+ g_object_unref (input_stream);
+
+exit:
+ context_free (context);
+}
+
+static void
+image_chooser_dialog_update_preview (GtkFileChooser *file_chooser)
+{
+ EImageChooserDialogPrivate *priv;
+ GtkWidget *preview_widget;
+ GFile *preview_file;
+ Context *context;
+
+ priv = E_IMAGE_CHOOSER_DIALOG_GET_PRIVATE (file_chooser);
+ preview_file = gtk_file_chooser_get_preview_file (file_chooser);
+ preview_widget = gtk_file_chooser_get_preview_widget (file_chooser);
+
+ if (priv->cancellable != NULL) {
+ g_cancellable_cancel (priv->cancellable);
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ }
+
+ gtk_image_clear (GTK_IMAGE (preview_widget));
+ gtk_file_chooser_set_preview_widget_active (file_chooser, FALSE);
+
+ if (preview_file == NULL)
+ return;
+
+ priv->cancellable = g_cancellable_new ();
+
+ context = g_slice_new0 (Context);
+ context->file_chooser = g_object_ref (file_chooser);
+ context->cancellable = g_object_ref (priv->cancellable);
+
+ g_file_read_async (
+ preview_file, G_PRIORITY_LOW,
+ priv->cancellable, (GAsyncReadyCallback)
+ image_chooser_dialog_read_cb, context);
+
+ g_object_unref (preview_file);
+}
+
+static void
+image_chooser_dialog_dispose (GObject *object)
+{
+ EImageChooserDialogPrivate *priv;
+
+ priv = E_IMAGE_CHOOSER_DIALOG_GET_PRIVATE (object);
+
+ if (priv->cancellable != NULL) {
+ g_cancellable_cancel (priv->cancellable);
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_image_chooser_dialog_parent_class)->dispose (object);
+}
+
+static void
+image_chooser_dialog_constructed (GObject *object)
+{
+ GtkFileChooser *file_chooser;
+ GtkFileFilter *file_filter;
+
+ file_chooser = GTK_FILE_CHOOSER (object);
+ gtk_file_chooser_set_local_only (file_chooser, FALSE);
+
+ gtk_dialog_add_button (
+ GTK_DIALOG (file_chooser),
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button (
+ GTK_DIALOG (file_chooser),
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_default_response (
+ GTK_DIALOG (file_chooser), GTK_RESPONSE_ACCEPT);
+
+ file_filter = gtk_file_filter_new ();
+ gtk_file_filter_add_pixbuf_formats (file_filter);
+ gtk_file_chooser_set_filter (file_chooser, file_filter);
+
+ gtk_file_chooser_set_preview_widget (file_chooser, gtk_image_new ());
+}
+
+static void
+e_image_chooser_dialog_class_init (EImageChooserDialogClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (
+ class, sizeof (EImageChooserDialogPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = image_chooser_dialog_dispose;
+ object_class->constructed = image_chooser_dialog_constructed;
+}
+
+static void
+e_image_chooser_dialog_init (EImageChooserDialog *dialog)
+{
+ dialog->priv = E_IMAGE_CHOOSER_DIALOG_GET_PRIVATE (dialog);
+
+ g_signal_connect (
+ dialog, "update-preview",
+ G_CALLBACK (image_chooser_dialog_update_preview), NULL);
+}
+
+GtkWidget *
+e_image_chooser_dialog_new (const gchar *title,
+ GtkWindow *parent)
+{
+ return g_object_new (
+ E_TYPE_IMAGE_CHOOSER_DIALOG,
+ "action", GTK_FILE_CHOOSER_ACTION_OPEN,
+ "title", title,
+ "transient-for", parent, NULL);
+}
+
+GFile *
+e_image_chooser_dialog_run (EImageChooserDialog *dialog)
+{
+ GtkFileChooser *file_chooser;
+
+ g_return_val_if_fail (E_IS_IMAGE_CHOOSER_DIALOG (dialog), NULL);
+
+ file_chooser = GTK_FILE_CHOOSER (dialog);
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT)
+ return NULL;
+
+ return gtk_file_chooser_get_file (file_chooser);
+}
diff --git a/e-util/e-image-chooser-dialog.h b/e-util/e-image-chooser-dialog.h
new file mode 100644
index 0000000000..967fddfbad
--- /dev/null
+++ b/e-util/e-image-chooser-dialog.h
@@ -0,0 +1,74 @@
+/*
+ * e-image-chooser-dialog.h
+ *
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_IMAGE_CHOOSER_DIALOG_H
+#define E_IMAGE_CHOOSER_DIALOG_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_IMAGE_CHOOSER_DIALOG \
+ (e_image_chooser_dialog_get_type ())
+#define E_IMAGE_CHOOSER_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_IMAGE_CHOOSER_DIALOG, EImageChooserDialog))
+#define E_IMAGE_CHOOSER_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_IMAGE_CHOOSER_DIALOG, EImageChooserDialogClass))
+#define E_IS_IMAGE_CHOOSER_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_IMAGE_CHOOSER_DIALOG))
+#define E_IS_IMAGE_CHOOSER_DIALOG_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_IMAGE_CHOOSER_DIALOG))
+#define E_IMAGE_CHOOSER_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_IMAGE_CHOOSER_DIALOG, EImageChooserDialogClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EImageChooserDialog EImageChooserDialog;
+typedef struct _EImageChooserDialogClass EImageChooserDialogClass;
+typedef struct _EImageChooserDialogPrivate EImageChooserDialogPrivate;
+
+struct _EImageChooserDialog {
+ GtkFileChooserDialog parent;
+ EImageChooserDialogPrivate *priv;
+};
+
+struct _EImageChooserDialogClass {
+ GtkFileChooserDialogClass parent_class;
+};
+
+GType e_image_chooser_dialog_get_type
+ (void) G_GNUC_CONST;
+GtkWidget * e_image_chooser_dialog_new
+ (const gchar *title,
+ GtkWindow *parent);
+GFile * e_image_chooser_dialog_run
+ (EImageChooserDialog *dialog);
+
+G_END_DECLS
+
+#endif /* E_IMAGE_CHOOSER_DIALOG_H */
diff --git a/e-util/e-mail-signature-editor.c b/e-util/e-mail-signature-editor.c
index 05c783d448..b5c87635f1 100644
--- a/e-util/e-mail-signature-editor.c
+++ b/e-util/e-mail-signature-editor.c
@@ -17,13 +17,13 @@
#include "e-mail-signature-editor.h"
+#include <config.h>
#include <string.h>
#include <glib/gi18n.h>
-#include "e-alert-bar.h"
#include "e-alert-dialog.h"
#include "e-alert-sink.h"
-#include "e-web-view-gtkhtml.h"
+#include "e-alert-bar.h"
#define E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
@@ -32,6 +32,7 @@
typedef struct _AsyncContext AsyncContext;
struct _EMailSignatureEditorPrivate {
+ EHTMLEditor *editor;
GtkActionGroup *action_group;
EFocusTracker *focus_tracker;
GCancellable *cancellable;
@@ -40,7 +41,6 @@ struct _EMailSignatureEditorPrivate {
gchar *original_name;
GtkWidget *entry; /* not referenced */
- GtkWidget *alert_bar; /* not referenced */
};
struct _AsyncContext {
@@ -52,6 +52,7 @@ struct _AsyncContext {
enum {
PROP_0,
+ PROP_EDITOR,
PROP_FOCUS_TRACKER,
PROP_REGISTRY,
PROP_SOURCE
@@ -75,17 +76,10 @@ static const gchar *ui =
" </toolbar>\n"
"</ui>";
-/* Forward Declarations */
-static void e_mail_signature_editor_alert_sink_init
- (EAlertSinkInterface *iface);
-
-G_DEFINE_TYPE_WITH_CODE (
+G_DEFINE_TYPE (
EMailSignatureEditor,
e_mail_signature_editor,
- GTKHTML_TYPE_EDITOR,
- G_IMPLEMENT_INTERFACE (
- E_TYPE_ALERT_SINK,
- e_mail_signature_editor_alert_sink_init))
+ GTK_TYPE_WINDOW)
static void
async_context_free (AsyncContext *async_context)
@@ -106,8 +100,10 @@ mail_signature_editor_loaded_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
ESource *source;
- EMailSignatureEditor *editor;
+ EMailSignatureEditor *window;
ESourceMailSignature *extension;
const gchar *extension_name;
const gchar *mime_type;
@@ -116,7 +112,7 @@ mail_signature_editor_loaded_cb (GObject *object,
GError *error = NULL;
source = E_SOURCE (object);
- editor = E_MAIL_SIGNATURE_EDITOR (user_data);
+ window = E_MAIL_SIGNATURE_EDITOR (user_data);
e_source_mail_signature_load_finish (
source, result, &contents, NULL, &error);
@@ -124,17 +120,17 @@ mail_signature_editor_loaded_cb (GObject *object,
/* Ignore cancellations. */
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
g_warn_if_fail (contents == NULL);
- g_object_unref (editor);
+ g_object_unref (window);
g_error_free (error);
return;
} else if (error != NULL) {
g_warn_if_fail (contents == NULL);
e_alert_submit (
- E_ALERT_SINK (editor),
+ E_ALERT_SINK (window),
"widgets:no-load-signature",
error->message, NULL);
- g_object_unref (editor);
+ g_object_unref (window);
g_error_free (error);
return;
}
@@ -147,30 +143,18 @@ mail_signature_editor_loaded_cb (GObject *object,
mime_type = e_source_mail_signature_get_mime_type (extension);
is_html = (g_strcmp0 (mime_type, "text/html") == 0);
- gtkhtml_editor_set_html_mode (GTKHTML_EDITOR (editor), is_html);
+ editor = e_mail_signature_editor_get_editor (window);
+ view = e_html_editor_get_view (editor);
+ e_html_editor_view_set_html_mode (view, is_html);
- if (is_html) {
- gtkhtml_editor_insert_html (
- GTKHTML_EDITOR (editor), contents);
- } else {
- gtkhtml_editor_insert_text (
- GTKHTML_EDITOR (editor), contents);
-
- gtkhtml_editor_run_command (
- GTKHTML_EDITOR (editor), "cursor-position-save");
- gtkhtml_editor_run_command (
- GTKHTML_EDITOR (editor), "select-all");
- gtkhtml_editor_run_command (
- GTKHTML_EDITOR (editor), "style-pre");
- gtkhtml_editor_run_command (
- GTKHTML_EDITOR (editor), "unselect-all");
- gtkhtml_editor_run_command (
- GTKHTML_EDITOR (editor), "cursor-position-restore");
- }
+ if (is_html)
+ e_html_editor_view_set_text_html (view, contents);
+ else
+ e_html_editor_view_set_text_plain (view, contents);
g_free (contents);
- g_object_unref (editor);
+ g_object_unref (window);
}
static gboolean
@@ -189,28 +173,33 @@ mail_signature_editor_delete_event_cb (EMailSignatureEditor *editor,
static void
action_close_cb (GtkAction *action,
- EMailSignatureEditor *editor)
+ EMailSignatureEditor *window)
{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
gboolean something_changed = FALSE;
const gchar *original_name;
const gchar *signature_name;
- original_name = editor->priv->original_name;
- signature_name = gtk_entry_get_text (GTK_ENTRY (editor->priv->entry));
+ original_name = window->priv->original_name;
+ signature_name = gtk_entry_get_text (GTK_ENTRY (window->priv->entry));
+
+ editor = e_mail_signature_editor_get_editor (window);
+ view = e_html_editor_get_view (editor);
- something_changed |= gtkhtml_editor_has_undo (GTKHTML_EDITOR (editor));
+ something_changed |= webkit_web_view_can_undo (WEBKIT_WEB_VIEW (view));
something_changed |= (strcmp (signature_name, original_name) != 0);
if (something_changed) {
gint response;
response = e_alert_run_dialog_for_args (
- GTK_WINDOW (editor),
+ GTK_WINDOW (window),
"widgets:ask-signature-changed", NULL);
if (response == GTK_RESPONSE_YES) {
GtkActionGroup *action_group;
- action_group = editor->priv->action_group;
+ action_group = window->priv->action_group;
action = gtk_action_group_get_action (
action_group, "save-and-close");
gtk_action_activate (action);
@@ -219,7 +208,7 @@ action_close_cb (GtkAction *action,
return;
}
- gtk_widget_destroy (GTK_WIDGET (editor));
+ gtk_widget_destroy (GTK_WIDGET (window));
}
static void
@@ -292,8 +281,7 @@ action_save_and_close_cb (GtkAction *action,
/* Only make sure that the 'source-changed' is called,
* thus the preview of the signature is updated on save.
* It is not called when only signature body is changed
- * (and ESource properties are left unchanged).
- */
+ * (and ESource properties are left unchanged). */
g_signal_emit_by_name (registry, "source-changed", source);
gtk_widget_destroy (GTK_WIDGET (editor));
@@ -397,6 +385,13 @@ mail_signature_editor_get_property (GObject *object,
GParamSpec *pspec)
{
switch (property_id) {
+ case PROP_EDITOR:
+ g_value_set_object (
+ value,
+ e_mail_signature_editor_get_editor (
+ E_MAIL_SIGNATURE_EDITOR (object)));
+ return;
+
case PROP_FOCUS_TRACKER:
g_value_set_object (
value,
@@ -429,6 +424,11 @@ mail_signature_editor_dispose (GObject *object)
priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (object);
+ if (priv->editor != NULL) {
+ g_object_unref (priv->editor);
+ priv->editor = NULL;
+ }
+
if (priv->action_group != NULL) {
g_object_unref (priv->action_group);
priv->action_group = NULL;
@@ -477,16 +477,18 @@ mail_signature_editor_finalize (GObject *object)
static void
mail_signature_editor_constructed (GObject *object)
{
- EMailSignatureEditor *editor;
+ EMailSignatureEditor *window;
GtkActionGroup *action_group;
EFocusTracker *focus_tracker;
- GtkhtmlEditor *gtkhtml_editor;
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
GtkUIManager *ui_manager;
GDBusObject *dbus_object;
ESource *source;
GtkAction *action;
GtkWidget *container;
GtkWidget *widget;
+ GtkWidget *hbox;
const gchar *display_name;
GError *error = NULL;
@@ -494,10 +496,11 @@ mail_signature_editor_constructed (GObject *object)
G_OBJECT_CLASS (e_mail_signature_editor_parent_class)->
constructed (object);
- editor = E_MAIL_SIGNATURE_EDITOR (object);
+ window = E_MAIL_SIGNATURE_EDITOR (object);
+ editor = e_mail_signature_editor_get_editor (window);
+ view = e_html_editor_get_view (editor);
- gtkhtml_editor = GTKHTML_EDITOR (editor);
- ui_manager = gtkhtml_editor_get_ui_manager (gtkhtml_editor);
+ ui_manager = e_html_editor_get_ui_manager (editor);
/* Because we are loading from a hard-coded string, there is
* no chance of I/O errors. Failure here implies a malformed
@@ -511,103 +514,102 @@ mail_signature_editor_constructed (GObject *object)
action_group, GETTEXT_PACKAGE);
gtk_action_group_add_actions (
action_group, entries,
- G_N_ELEMENTS (entries), editor);
+ G_N_ELEMENTS (entries), window);
gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
- editor->priv->action_group = g_object_ref (action_group);
+ window->priv->action_group = g_object_ref (action_group);
/* Hide page properties because it is not inherited in the mail. */
- action = gtkhtml_editor_get_action (gtkhtml_editor, "properties-page");
+ action = e_html_editor_get_action (editor, "properties-page");
gtk_action_set_visible (action, FALSE);
- action = gtkhtml_editor_get_action (
- gtkhtml_editor, "context-properties-page");
+ action = e_html_editor_get_action (editor, "context-properties-page");
gtk_action_set_visible (action, FALSE);
gtk_ui_manager_ensure_update (ui_manager);
- gtk_window_set_title (GTK_WINDOW (editor), _("Edit Signature"));
+ gtk_window_set_title (GTK_WINDOW (window), _("Edit Signature"));
+ gtk_window_set_default_size (GTK_WINDOW (window), 600, 440);
- /* Construct the signature name entry. */
+ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (window), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ /* Construct the main menu and toolbar. */
+
+ widget = e_html_editor_get_managed_widget (editor, "/main-menu");
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = e_html_editor_get_managed_widget (editor, "/main-toolbar");
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
- container = gtkhtml_editor->vbox;
+ /* Construct the signature name entry. */
widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_container_set_border_width (GTK_CONTAINER (widget), 6);
gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
- /* Position 2 should be between the main and style toolbars. */
- gtk_box_reorder_child (GTK_BOX (container), widget, 2);
gtk_widget_show (widget);
- container = widget;
+ hbox = widget;
widget = gtk_entry_new ();
- gtk_box_pack_end (GTK_BOX (container), widget, TRUE, TRUE, 0);
- editor->priv->entry = widget; /* not referenced */
+ gtk_box_pack_end (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
+ window->priv->entry = widget; /* not referenced */
gtk_widget_show (widget);
widget = gtk_label_new_with_mnemonic (_("_Signature Name:"));
- gtk_label_set_mnemonic_widget (GTK_LABEL (widget), editor->priv->entry);
- gtk_box_pack_end (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), window->priv->entry);
+ gtk_box_pack_end (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
gtk_widget_show (widget);
- g_signal_connect (
- editor, "delete-event",
- G_CALLBACK (mail_signature_editor_delete_event_cb), NULL);
-
- /* Construct the alert bar for errors. */
+ /* Construct the main editing area. */
- container = gtkhtml_editor->vbox;
-
- widget = e_alert_bar_new ();
- gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
- /* Position 5 should be between the style toolbar and editing area. */
- gtk_box_reorder_child (GTK_BOX (container), widget, 5);
- editor->priv->alert_bar = widget; /* not referenced */
- /* EAlertBar controls its own visibility. */
+ widget = GTK_WIDGET (editor);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
- /* Configure an EFocusTracker to manage selection actions.
- *
- * XXX GtkhtmlEditor does not manage its own selection actions,
- * which is technically a bug but works in our favor here
- * because it won't cause any conflicts with EFocusTracker. */
+ g_signal_connect (
+ window, "delete-event",
+ G_CALLBACK (mail_signature_editor_delete_event_cb), NULL);
- focus_tracker = e_focus_tracker_new (GTK_WINDOW (editor));
+ /* Configure an EFocusTracker to manage selection actions. */
+ focus_tracker = e_focus_tracker_new (GTK_WINDOW (window));
- action = gtkhtml_editor_get_action (gtkhtml_editor, "cut");
+ action = e_html_editor_get_action (editor, "cut");
e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
- action = gtkhtml_editor_get_action (gtkhtml_editor, "copy");
+ action = e_html_editor_get_action (editor, "copy");
e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
- action = gtkhtml_editor_get_action (gtkhtml_editor, "paste");
+ action = e_html_editor_get_action (editor, "paste");
e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
- action = gtkhtml_editor_get_action (gtkhtml_editor, "select-all");
+ action = e_html_editor_get_action (editor, "select-all");
e_focus_tracker_set_select_all_action (focus_tracker, action);
- editor->priv->focus_tracker = focus_tracker;
+ window->priv->focus_tracker = focus_tracker;
- source = e_mail_signature_editor_get_source (editor);
+ source = e_mail_signature_editor_get_source (window);
display_name = e_source_get_display_name (source);
if (display_name == NULL || *display_name == '\0')
display_name = _("Unnamed");
/* Set the entry text before we grab focus. */
- g_free (editor->priv->original_name);
- editor->priv->original_name = g_strdup (display_name);
- gtk_entry_set_text (GTK_ENTRY (editor->priv->entry), display_name);
+ g_free (window->priv->original_name);
+ window->priv->original_name = g_strdup (display_name);
+ gtk_entry_set_text (GTK_ENTRY (window->priv->entry), display_name);
/* Set the focus appropriately. If this is a new signature, draw
* the user's attention to the signature name entry. Otherwise go
* straight to the editing area. */
if (source == NULL) {
- gtk_widget_grab_focus (editor->priv->entry);
+ gtk_widget_grab_focus (window->priv->entry);
} else {
- GtkHTML *html;
-
- html = gtkhtml_editor_get_html (gtkhtml_editor);
- gtk_widget_grab_focus (GTK_WIDGET (html));
+ gtk_widget_grab_focus (GTK_WIDGET (view));
}
/* Load file content only for an existing signature.
@@ -623,72 +625,19 @@ mail_signature_editor_constructed (GObject *object)
G_PRIORITY_DEFAULT,
cancellable,
mail_signature_editor_loaded_cb,
- g_object_ref (editor));
+ g_object_ref (window));
- g_warn_if_fail (editor->priv->cancellable == NULL);
- editor->priv->cancellable = cancellable;
+ g_warn_if_fail (window->priv->cancellable == NULL);
+ window->priv->cancellable = cancellable;
g_object_unref (dbus_object);
}
}
static void
-mail_signature_editor_cut_clipboard (GtkhtmlEditor *editor)
-{
- /* Do nothing. EFocusTracker handles this. */
-}
-
-static void
-mail_signature_editor_copy_clipboard (GtkhtmlEditor *editor)
-{
- /* Do nothing. EFocusTracker handles this. */
-}
-
-static void
-mail_signature_editor_paste_clipboard (GtkhtmlEditor *editor)
-{
- /* Do nothing. EFocusTracker handles this. */
-}
-
-static void
-mail_signature_editor_select_all (GtkhtmlEditor *editor)
-{
- /* Do nothing. EFocusTracker handles this. */
-}
-
-static void
-mail_signature_editor_submit_alert (EAlertSink *alert_sink,
- EAlert *alert)
-{
- EMailSignatureEditorPrivate *priv;
- EAlertBar *alert_bar;
- GtkWidget *dialog;
- GtkWindow *parent;
-
- priv = E_MAIL_SIGNATURE_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:
- parent = GTK_WINDOW (alert_sink);
- dialog = e_alert_dialog_new (parent, alert);
- gtk_dialog_run (GTK_DIALOG (dialog));
- gtk_widget_destroy (dialog);
- break;
- }
-}
-
-static void
e_mail_signature_editor_class_init (EMailSignatureEditorClass *class)
{
GObjectClass *object_class;
- GtkhtmlEditorClass *editor_class;
g_type_class_add_private (class, sizeof (EMailSignatureEditorPrivate));
@@ -699,11 +648,16 @@ e_mail_signature_editor_class_init (EMailSignatureEditorClass *class)
object_class->finalize = mail_signature_editor_finalize;
object_class->constructed = mail_signature_editor_constructed;
- editor_class = GTKHTML_EDITOR_CLASS (class);
- editor_class->cut_clipboard = mail_signature_editor_cut_clipboard;
- editor_class->copy_clipboard = mail_signature_editor_copy_clipboard;
- editor_class->paste_clipboard = mail_signature_editor_paste_clipboard;
- editor_class->select_all = mail_signature_editor_select_all;
+ g_object_class_install_property (
+ object_class,
+ PROP_EDITOR,
+ g_param_spec_object (
+ "editor",
+ NULL,
+ NULL,
+ E_TYPE_HTML_EDITOR,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
@@ -742,15 +696,11 @@ e_mail_signature_editor_class_init (EMailSignatureEditorClass *class)
}
static void
-e_mail_signature_editor_alert_sink_init (EAlertSinkInterface *iface)
-{
- iface->submit_alert = mail_signature_editor_submit_alert;
-}
-
-static void
e_mail_signature_editor_init (EMailSignatureEditor *editor)
{
editor->priv = E_MAIL_SIGNATURE_EDITOR_GET_PRIVATE (editor);
+
+ editor->priv->editor = g_object_ref_sink (e_html_editor_new ());
}
GtkWidget *
@@ -764,11 +714,18 @@ e_mail_signature_editor_new (ESourceRegistry *registry,
return g_object_new (
E_TYPE_MAIL_SIGNATURE_EDITOR,
- "html", e_web_view_gtkhtml_new (),
"registry", registry,
"source", source, NULL);
}
+EHTMLEditor *
+e_mail_signature_editor_get_editor (EMailSignatureEditor *editor)
+{
+ g_return_val_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor), NULL);
+
+ return editor->priv->editor;
+}
+
EFocusTracker *
e_mail_signature_editor_get_focus_tracker (EMailSignatureEditor *editor)
{
@@ -851,7 +808,7 @@ mail_signature_editor_commit_cb (GObject *object,
}
void
-e_mail_signature_editor_commit (EMailSignatureEditor *editor,
+e_mail_signature_editor_commit (EMailSignatureEditor *window,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
@@ -864,23 +821,23 @@ e_mail_signature_editor_commit (EMailSignatureEditor *editor,
const gchar *extension_name;
const gchar *mime_type;
gchar *contents;
- gboolean is_html;
- gsize length;
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
- g_return_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (editor));
+ g_return_if_fail (E_IS_MAIL_SIGNATURE_EDITOR (window));
- registry = e_mail_signature_editor_get_registry (editor);
- source = e_mail_signature_editor_get_source (editor);
- is_html = gtkhtml_editor_get_html_mode (GTKHTML_EDITOR (editor));
+ registry = e_mail_signature_editor_get_registry (window);
+ source = e_mail_signature_editor_get_source (window);
+
+ editor = e_mail_signature_editor_get_editor (window);
+ view = e_html_editor_get_view (editor);
- if (is_html) {
+ if (e_html_editor_view_get_html_mode (view)) {
mime_type = "text/html";
- contents = gtkhtml_editor_get_text_html (
- GTKHTML_EDITOR (editor), &length);
+ contents = e_html_editor_view_get_text_html (view);
} else {
mime_type = "text/plain";
- contents = gtkhtml_editor_get_text_plain (
- GTKHTML_EDITOR (editor), &length);
+ contents = e_html_editor_view_get_text_plain (view);
}
extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
@@ -890,13 +847,13 @@ e_mail_signature_editor_commit (EMailSignatureEditor *editor,
async_context = g_slice_new0 (AsyncContext);
async_context->source = g_object_ref (source);
async_context->contents = contents; /* takes ownership */
- async_context->length = length;
+ async_context->length = strlen (contents);
if (G_IS_CANCELLABLE (cancellable))
async_context->cancellable = g_object_ref (cancellable);
simple = g_simple_async_result_new (
- G_OBJECT (editor), callback, user_data,
+ G_OBJECT (window), callback, user_data,
e_mail_signature_editor_commit);
g_simple_async_result_set_op_res_gpointer (
diff --git a/e-util/e-mail-signature-editor.h b/e-util/e-mail-signature-editor.h
index 7de6841647..1b8622d5d1 100644
--- a/e-util/e-mail-signature-editor.h
+++ b/e-util/e-mail-signature-editor.h
@@ -22,9 +22,9 @@
#ifndef E_MAIL_SIGNATURE_EDITOR_H
#define E_MAIL_SIGNATURE_EDITOR_H
-#include <gtkhtml-editor.h>
#include <libedataserver/libedataserver.h>
+#include <e-util/e-html-editor.h>
#include <e-util/e-focus-tracker.h>
/* Standard GObject macros */
@@ -53,18 +53,20 @@ typedef struct _EMailSignatureEditorClass EMailSignatureEditorClass;
typedef struct _EMailSignatureEditorPrivate EMailSignatureEditorPrivate;
struct _EMailSignatureEditor {
- GtkhtmlEditor parent;
+ GtkWindow parent;
EMailSignatureEditorPrivate *priv;
};
struct _EMailSignatureEditorClass {
- GtkhtmlEditorClass parent_class;
+ GtkWindowClass parent_class;
};
GType e_mail_signature_editor_get_type
(void) G_GNUC_CONST;
GtkWidget * e_mail_signature_editor_new (ESourceRegistry *registry,
ESource *source);
+EHTMLEditor * e_mail_signature_editor_get_editor
+ (EMailSignatureEditor *editor);
EFocusTracker * e_mail_signature_editor_get_focus_tracker
(EMailSignatureEditor *editor);
ESourceRegistry *
diff --git a/e-util/e-mail-signature-manager.c b/e-util/e-mail-signature-manager.c
index 7f75cb7540..fb34c7eb4c 100644
--- a/e-util/e-mail-signature-manager.c
+++ b/e-util/e-mail-signature-manager.c
@@ -398,15 +398,22 @@ mail_signature_manager_constructed (GObject *object)
static void
mail_signature_manager_add_signature (EMailSignatureManager *manager)
{
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
ESourceRegistry *registry;
- GtkWidget *editor;
+ GtkWidget *widget;
registry = e_mail_signature_manager_get_registry (manager);
- editor = e_mail_signature_editor_new (registry, NULL);
- gtkhtml_editor_set_html_mode (
- GTKHTML_EDITOR (editor), manager->priv->prefer_html);
- mail_signature_manager_emit_editor_created (manager, editor);
+ widget = e_mail_signature_editor_new (registry, NULL);
+
+ editor = e_mail_signature_editor_get_editor (
+ E_MAIL_SIGNATURE_EDITOR (widget));
+ view = e_html_editor_get_view (editor);
+ e_html_editor_view_set_html_mode (
+ view, manager->priv->prefer_html);
+
+ mail_signature_manager_emit_editor_created (manager, widget);
gtk_widget_grab_focus (manager->priv->tree_view);
}
@@ -435,6 +442,7 @@ mail_signature_manager_editor_created (EMailSignatureManager *manager,
gtk_window_set_transient_for (GTK_WINDOW (editor), parent);
gtk_window_set_position (GTK_WINDOW (editor), position);
+ gtk_widget_set_size_request (GTK_WIDGET (editor), 450, 300);
gtk_widget_show (GTK_WIDGET (editor));
}
diff --git a/e-util/e-mail-signature-preview.c b/e-util/e-mail-signature-preview.c
index c2eeaa6472..ecf64191b6 100644
--- a/e-util/e-mail-signature-preview.c
+++ b/e-util/e-mail-signature-preview.c
@@ -145,12 +145,16 @@ mail_signature_preview_load_cb (ESource *source,
mime_type = e_source_mail_signature_get_mime_type (extension);
if (g_strcmp0 (mime_type, "text/html") == 0) {
- e_web_view_load_string (E_WEB_VIEW (preview), contents);
+ webkit_web_view_load_string (
+ WEBKIT_WEB_VIEW (preview), contents,
+ "text/html", "UTF-8", "file:///");
} else {
gchar *string;
string = g_markup_printf_escaped ("<pre>%s</pre>", contents);
- e_web_view_load_string (E_WEB_VIEW (preview), string);
+ webkit_web_view_load_string (
+ WEBKIT_WEB_VIEW (preview), string,
+ "text/html", "UTF-8", "file:///");
g_free (string);
}
diff --git a/e-util/e-misc-utils.c b/e-util/e-misc-utils.c
index 510dad39b2..88f6da9599 100644
--- a/e-util/e-misc-utils.c
+++ b/e-util/e-misc-utils.c
@@ -1089,6 +1089,50 @@ e_str_without_underscores (const gchar *string)
return new_string;
}
+/**
+ * e_str_replace_string
+ * @text: the string to replace
+ * @before: the string to be replaced
+ * @after: the string to replaced with
+ *
+ * Replaces every occurrence of the string @before with the string @after in
+ * the string @text and returns a #GString with result that should be freed
+ * with g_string_free().
+ *
+ * Returns: a newly-allocated #GString
+ */
+GString *
+e_str_replace_string (const gchar *text,
+ const gchar *before,
+ const gchar *after)
+{
+ const gchar *p, *next;
+ GString *str;
+ gint find_len;
+
+ g_return_val_if_fail (text != NULL, NULL);
+ g_return_val_if_fail (before != NULL, NULL);
+ g_return_val_if_fail (*before, NULL);
+
+ find_len = strlen (before);
+ str = g_string_new ("");
+
+ p = text;
+ while (next = strstr (p, before), next) {
+ if (p < next)
+ g_string_append_len (str, p, next - p);
+
+ if (after && *after)
+ g_string_append (str, after);
+
+ p = next + find_len;
+ }
+
+ g_string_append (str, p);
+
+ return str;
+}
+
gint
e_str_compare (gconstpointer x,
gconstpointer y)
diff --git a/e-util/e-misc-utils.h b/e-util/e-misc-utils.h
index ebc860b91e..5810490b4f 100644
--- a/e-util/e-misc-utils.h
+++ b/e-util/e-misc-utils.h
@@ -99,6 +99,9 @@ gchar * e_ascii_dtostr (gchar *buffer,
gdouble d);
gchar * e_str_without_underscores (const gchar *string);
+GString * e_str_replace_string (const gchar *text,
+ const gchar *find,
+ const gchar *replace);
gint e_str_compare (gconstpointer x,
gconstpointer y);
gint e_str_case_compare (gconstpointer x,
diff --git a/e-util/e-name-selector-entry.c b/e-util/e-name-selector-entry.c
index a50c886e9c..5a6519c39b 100644
--- a/e-util/e-name-selector-entry.c
+++ b/e-util/e-name-selector-entry.c
@@ -511,10 +511,10 @@ is_quoted_at (const gchar *string,
gunichar c = g_utf8_get_char (p);
if (c == '"')
- quoted = ~quoted;
+ quoted = !quoted;
}
- return quoted ? TRUE : FALSE;
+ return quoted;
}
static gint
@@ -530,7 +530,7 @@ get_index_at_position (const gchar *string,
gunichar c = g_utf8_get_char (p);
if (c == '"')
- quoted = ~quoted;
+ quoted = !quoted;
else if (c == ',' && !quoted)
n++;
}
diff --git a/e-util/e-spell-checker.c b/e-util/e-spell-checker.c
new file mode 100644
index 0000000000..c781672103
--- /dev/null
+++ b/e-util/e-spell-checker.c
@@ -0,0 +1,783 @@
+/*
+ * e-spell-checker.c
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-spell-checker.h"
+#include "e-spell-dictionary.h"
+
+#include <libebackend/libebackend.h>
+#include <webkit/webkitspellchecker.h>
+#include <pango/pango.h>
+#include <gtk/gtk.h>
+#include <string.h>
+
+#define E_SPELL_CHECKER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_SPELL_CHECKER, ESpellCheckerPrivate))
+
+#define MAX_SUGGESTIONS 10
+
+struct _ESpellCheckerPrivate {
+ EnchantBroker *broker;
+ GHashTable *active_dictionaries;
+ GHashTable *dictionaries_cache;
+ gboolean dictionaries_loaded;
+
+ /* We retain ownership of the EnchantDict's since they
+ * have to be freed through enchant_broker_free_dict()
+ * and we also own the EnchantBroker. */
+ GHashTable *enchant_dicts;
+};
+
+enum {
+ PROP_0,
+ PROP_ACTIVE_LANGUAGES
+};
+
+/* Forward Declarations */
+static void e_spell_checker_init_webkit_checker
+ (WebKitSpellCheckerInterface *interface);
+
+G_DEFINE_TYPE_EXTENDED (
+ ESpellChecker,
+ e_spell_checker,
+ G_TYPE_OBJECT,
+ 0,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EXTENSIBLE, NULL)
+ G_IMPLEMENT_INTERFACE (
+ WEBKIT_TYPE_SPELL_CHECKER,
+ e_spell_checker_init_webkit_checker))
+
+/**
+ * ESpellChecker:
+ *
+ * #ESpellChecker represents a spellchecker in Evolution. It can be used as a
+ * provider for dictionaries. It also implements #WebKitSpellCheckerInterface,
+ * so it can be set as a default spell-checker to WebKit editors
+ */
+
+static gboolean
+spell_checker_enchant_dicts_foreach_cb (gpointer key,
+ gpointer value,
+ gpointer user_data)
+{
+ EnchantDict *enchant_dict = value;
+ EnchantBroker *enchant_broker = user_data;
+
+ enchant_broker_free_dict (enchant_broker, enchant_dict);
+
+ return TRUE;
+}
+
+static void
+wksc_check_spelling (WebKitSpellChecker *webkit_checker,
+ const gchar *word,
+ gint *misspelling_location,
+ gint *misspelling_length)
+{
+ ESpellChecker *checker = E_SPELL_CHECKER (webkit_checker);
+ GHashTable *active_dictionaries;
+ PangoLanguage *language;
+ PangoLogAttr *attrs;
+ gint length, ii;
+
+ active_dictionaries = checker->priv->active_dictionaries;
+ if (g_hash_table_size (active_dictionaries) == 0)
+ return;
+
+ length = g_utf8_strlen (word, -1);
+
+ language = pango_language_get_default ();
+ attrs = g_new (PangoLogAttr, length + 1);
+
+ pango_get_log_attrs (word, -1, -1, language, attrs, length + 1);
+
+ for (ii = 0; ii < length + 1; ii++) {
+ /* We go through each character until we find an is_word_start,
+ * then we get into an inner loop to find the is_word_end
+ * corresponding */
+ if (attrs[ii].is_word_start) {
+ gboolean word_recognized;
+ gint start = ii;
+ gint end = ii;
+ gint word_length;
+ gchar *cstart;
+ gint bytes;
+ gchar *new_word;
+
+ while (attrs[end].is_word_end < 1)
+ end++;
+
+ word_length = end - start;
+ /* Set the iterator to be at the current word
+ * end, so we don't check characters twice. */
+ ii = end;
+
+ cstart = g_utf8_offset_to_pointer (word, start);
+ bytes = g_utf8_offset_to_pointer (word, end) - cstart;
+ new_word = g_new0 (gchar, bytes + 1);
+
+ g_utf8_strncpy (new_word, cstart, word_length);
+
+ word_recognized = e_spell_checker_check_word (
+ checker, new_word, strlen (new_word));
+
+ if (word_recognized) {
+ if (misspelling_location != NULL)
+ *misspelling_location = -1;
+ if (misspelling_length != NULL)
+ *misspelling_length = 0;
+ } else {
+ if (misspelling_location != NULL)
+ *misspelling_location = start;
+ if (misspelling_length != NULL)
+ *misspelling_length = word_length;
+ }
+
+ g_free (new_word);
+ }
+ }
+
+ g_free (attrs);
+}
+
+static gchar **
+wksc_get_guesses (WebKitSpellChecker *webkit_checker,
+ const gchar *word,
+ const gchar *context)
+{
+ ESpellChecker *checker = E_SPELL_CHECKER (webkit_checker);
+ GHashTable *active_dictionaries;
+ GList *list, *link;
+ gchar ** guesses;
+ gint ii = 0;
+
+ guesses = g_new0 (gchar *, MAX_SUGGESTIONS + 1);
+
+ active_dictionaries = checker->priv->active_dictionaries;
+ list = g_hash_table_get_keys (active_dictionaries);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ ESpellDictionary *dictionary;
+ GList *suggestions;
+
+ dictionary = E_SPELL_DICTIONARY (link->data);
+ suggestions = e_spell_dictionary_get_suggestions (
+ dictionary, word, -1);
+
+ while (suggestions != NULL && ii < MAX_SUGGESTIONS) {
+ guesses[ii++] = suggestions->data;
+ suggestions->data = NULL;
+
+ suggestions = g_list_delete_link (
+ suggestions, suggestions);
+ }
+
+ g_list_free_full (suggestions, (GDestroyNotify) g_free);
+
+ if (ii >= MAX_SUGGESTIONS)
+ break;
+ }
+
+ g_list_free (list);
+
+ return guesses;
+}
+
+static gchar *
+wksc_get_autocorrect_suggestions (WebKitSpellChecker *webkit_checker,
+ const gchar *word)
+{
+ /* Not supported/needed */
+ return NULL;
+}
+
+static void
+spell_checker_learn_word (WebKitSpellChecker *webkit_checker,
+ const gchar *word)
+{
+ /* Carefully, this will add the word to all active dictionaries! */
+
+ ESpellChecker *checker;
+ GHashTable *active_dictionaries;
+ GList *list, *link;
+
+ checker = E_SPELL_CHECKER (webkit_checker);
+ active_dictionaries = checker->priv->active_dictionaries;
+ list = g_hash_table_get_keys (active_dictionaries);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ ESpellDictionary *dictionary;
+
+ dictionary = E_SPELL_DICTIONARY (link->data);
+ e_spell_dictionary_learn_word (dictionary, word, -1);
+ }
+
+ g_list_free (list);
+}
+
+static void
+spell_checker_ignore_word (WebKitSpellChecker *webkit_checker,
+ const gchar *word)
+{
+ /* Carefully, this will add the word to all active dictionaries */
+
+ ESpellChecker *checker;
+ GHashTable *active_dictionaries;
+ GList *list, *link;
+
+ checker = E_SPELL_CHECKER (webkit_checker);
+ active_dictionaries = checker->priv->active_dictionaries;
+ list = g_hash_table_get_keys (active_dictionaries);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ ESpellDictionary *dictionary;
+
+ dictionary = E_SPELL_DICTIONARY (link->data);
+ e_spell_dictionary_ignore_word (dictionary, word, -1);
+ }
+
+ g_list_free (list);
+}
+
+static void
+wksc_update_languages (WebKitSpellChecker *webkit_checker,
+ const gchar *languages)
+{
+ ESpellChecker *checker;
+ GHashTable *active_dictionaries;
+ GQueue queue = G_QUEUE_INIT;
+
+ checker = E_SPELL_CHECKER (webkit_checker);
+ active_dictionaries = checker->priv->active_dictionaries;
+
+ if (languages != NULL) {
+ gchar **langs;
+ gint ii;
+
+ langs = g_strsplit (languages, ",", -1);
+ for (ii = 0; langs[ii] != NULL; ii++) {
+ ESpellDictionary *dictionary;
+
+ dictionary = e_spell_checker_ref_dictionary (
+ checker, langs[ii]);
+ if (dictionary != NULL)
+ g_queue_push_tail (&queue, dictionary);
+ }
+ g_strfreev (langs);
+ } else {
+ ESpellDictionary *dictionary;
+ PangoLanguage *pango_language;
+ const gchar *language;
+
+ pango_language = gtk_get_default_language ();
+ language = pango_language_to_string (pango_language);
+ dictionary = e_spell_checker_ref_dictionary (checker, language);
+
+ if (dictionary == NULL) {
+ GList *list;
+
+ list = e_spell_checker_list_available_dicts (checker);
+ if (list != NULL) {
+ dictionary = g_object_ref (list->data);
+ g_list_free (list);
+ }
+ }
+
+ if (dictionary != NULL)
+ g_queue_push_tail (&queue, dictionary);
+ }
+
+ g_hash_table_remove_all (active_dictionaries);
+
+ while (!g_queue_is_empty (&queue)) {
+ ESpellDictionary *dictionary;
+
+ dictionary = g_queue_pop_head (&queue);
+ g_hash_table_add (active_dictionaries, dictionary);
+ }
+
+ g_object_notify (G_OBJECT (checker), "active-languages");
+}
+
+static void
+spell_checker_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_ACTIVE_LANGUAGES:
+ g_value_take_boxed (
+ value,
+ e_spell_checker_list_active_languages (
+ E_SPELL_CHECKER (object), NULL));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+spell_checker_dispose (GObject *object)
+{
+ ESpellCheckerPrivate *priv;
+
+ priv = E_SPELL_CHECKER_GET_PRIVATE (object);
+
+ g_hash_table_remove_all (priv->active_dictionaries);
+ g_hash_table_remove_all (priv->dictionaries_cache);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_spell_checker_parent_class)->dispose (object);
+}
+
+static void
+spell_checker_finalize (GObject *object)
+{
+ ESpellCheckerPrivate *priv;
+
+ priv = E_SPELL_CHECKER_GET_PRIVATE (object);
+
+ /* Freeing EnchantDicts requires help from EnchantBroker. */
+ g_hash_table_foreach_remove (
+ priv->enchant_dicts,
+ spell_checker_enchant_dicts_foreach_cb,
+ priv->broker);
+ g_hash_table_destroy (priv->enchant_dicts);
+
+ enchant_broker_free (priv->broker);
+
+ g_hash_table_destroy (priv->active_dictionaries);
+ g_hash_table_destroy (priv->dictionaries_cache);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_spell_checker_parent_class)->finalize (object);
+}
+
+static void
+spell_checker_constructed (GObject *object)
+{
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_spell_checker_parent_class)->constructed (object);
+
+ e_extensible_load_extensions (E_EXTENSIBLE (object));
+}
+
+static void
+e_spell_checker_class_init (ESpellCheckerClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (ESpellCheckerPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->get_property = spell_checker_get_property;
+ object_class->dispose = spell_checker_dispose;
+ object_class->finalize = spell_checker_finalize;
+ object_class->constructed = spell_checker_constructed;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_ACTIVE_LANGUAGES,
+ g_param_spec_boxed (
+ "active-languages",
+ "Active Languages",
+ "Active spell check language codes",
+ G_TYPE_STRV,
+ G_PARAM_READABLE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_spell_checker_init_webkit_checker (WebKitSpellCheckerInterface *interface)
+{
+ interface->check_spelling_of_string = wksc_check_spelling;
+ interface->get_autocorrect_suggestions_for_misspelled_word =
+ wksc_get_autocorrect_suggestions;
+ interface->get_guesses_for_word = wksc_get_guesses;
+ interface->ignore_word = spell_checker_ignore_word;
+ interface->learn_word = spell_checker_learn_word;
+ interface->update_spell_checking_languages = wksc_update_languages;
+}
+
+static void
+e_spell_checker_init (ESpellChecker *checker)
+{
+ GHashTable *active_dictionaries;
+ GHashTable *dictionaries_cache;
+ GHashTable *enchant_dicts;
+
+ active_dictionaries = g_hash_table_new_full (
+ (GHashFunc) e_spell_dictionary_hash,
+ (GEqualFunc) e_spell_dictionary_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) NULL);
+
+ dictionaries_cache = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) NULL,
+ (GDestroyNotify) g_object_unref);
+
+ enchant_dicts = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) NULL);
+
+ checker->priv = E_SPELL_CHECKER_GET_PRIVATE (checker);
+
+ checker->priv->broker = enchant_broker_init ();
+ checker->priv->active_dictionaries = active_dictionaries;
+ checker->priv->dictionaries_cache = dictionaries_cache;
+ checker->priv->enchant_dicts = enchant_dicts;
+}
+
+/**
+ * e_spell_checker_new:
+ *
+ * Creates a new #ESpellChecker instance.
+ *
+ * Returns: a new #ESpellChecker
+ **/
+ESpellChecker *
+e_spell_checker_new (void)
+{
+ return g_object_new (E_TYPE_SPELL_CHECKER, NULL);
+}
+
+static void
+list_enchant_dicts (const gchar * const lang_tag,
+ const gchar * const provider_name,
+ const gchar * const provider_desc,
+ const gchar * const provider_file,
+ gpointer user_data)
+{
+ ESpellChecker *checker = user_data;
+ EnchantDict *enchant_dict;
+
+ enchant_dict = enchant_broker_request_dict (
+ checker->priv->broker, lang_tag);
+ if (enchant_dict != NULL) {
+ ESpellDictionary *dictionary;
+ const gchar *code;
+
+ /* Note that we retain ownership of the EnchantDict.
+ * Since EnchantDict is not reference counted, we're
+ * merely loaning the pointer to ESpellDictionary. */
+ dictionary = e_spell_dictionary_new (checker, enchant_dict);
+ code = e_spell_dictionary_get_code (dictionary);
+
+ g_hash_table_insert (
+ checker->priv->dictionaries_cache,
+ (gpointer) code, dictionary);
+
+ g_hash_table_insert (
+ checker->priv->enchant_dicts,
+ g_strdup (code), enchant_dict);
+ }
+}
+
+/**
+ * e_spell_checker_list_available_dicts:
+ * @checker: An #ESpellChecker
+ *
+ * Returns list of all dictionaries available to the actual
+ * spell-checking backend.
+ *
+ * Returns: new copy of #GList of #ESpellDictionary. The dictionaries are
+ * owned by the @checker and should not be free'd. The list should be freed
+ * using g_list_free() when not neede anymore. [transfer-list]
+ */
+GList *
+e_spell_checker_list_available_dicts (ESpellChecker *checker)
+{
+ GList *list;
+
+ g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), NULL);
+
+ if (!checker->priv->dictionaries_loaded) {
+ enchant_broker_list_dicts (
+ checker->priv->broker,
+ list_enchant_dicts, checker);
+ checker->priv->dictionaries_loaded = TRUE;
+ }
+
+ list = g_hash_table_get_values (checker->priv->dictionaries_cache);
+
+ return g_list_sort (list, (GCompareFunc) e_spell_dictionary_compare);
+}
+
+/**
+ * e_spell_checker_ref_dictionary:
+ * @checker: an #ESpellChecker
+ * @language_code: (allow-none): language code of a dictionary, or %NULL
+ *
+ * Tries to find an #ESpellDictionary for given @language_code.
+ * If @language_code is %NULL, the function will return a default
+ * #ESpellDictionary.
+ *
+ * Returns: an #ESpellDictionary for @language_code
+ */
+ESpellDictionary *
+e_spell_checker_ref_dictionary (ESpellChecker *checker,
+ const gchar *language_code)
+{
+ ESpellDictionary *dictionary;
+ GList *list;
+
+ g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), NULL);
+
+ /* If the cache has not yet been initialized, do so - we will need
+ * it anyway, Otherwise is this call very cheap */
+ list = e_spell_checker_list_available_dicts (checker);
+
+ if (language_code == NULL) {
+ dictionary = (list != NULL) ? list->data : NULL;
+ } else {
+ dictionary = g_hash_table_lookup (
+ checker->priv->dictionaries_cache,
+ language_code);
+ }
+
+ if (dictionary != NULL)
+ g_object_ref (dictionary);
+
+ g_list_free (list);
+
+ return dictionary;
+}
+
+/**
+ * e_spell_checker_get_enchant_dict:
+ * @checker: an #ESpellChecker
+ * @language_code: language code of a dictionary, or %NULL
+ *
+ * Returns the #EnchantDict for @language_code, or %NULL if there is none.
+ *
+ * Returns: the #EnchantDict for @language_code, or %NULL
+ **/
+EnchantDict *
+e_spell_checker_get_enchant_dict (ESpellChecker *checker,
+ const gchar *language_code)
+{
+ g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), NULL);
+ g_return_val_if_fail (language_code != NULL, NULL);
+
+ return g_hash_table_lookup (
+ checker->priv->enchant_dicts, language_code);
+}
+
+gboolean
+e_spell_checker_get_language_active (ESpellChecker *checker,
+ const gchar *language_code)
+{
+ ESpellDictionary *dictionary;
+ GHashTable *active_dictionaries;
+ gboolean active;
+
+ g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), FALSE);
+ g_return_val_if_fail (language_code != NULL, FALSE);
+
+ dictionary = e_spell_checker_ref_dictionary (checker, language_code);
+ g_return_val_if_fail (dictionary != NULL, FALSE);
+
+ active_dictionaries = checker->priv->active_dictionaries;
+ active = g_hash_table_contains (active_dictionaries, dictionary);
+
+ g_object_unref (dictionary);
+
+ return active;
+}
+
+void
+e_spell_checker_set_language_active (ESpellChecker *checker,
+ const gchar *language_code,
+ gboolean active)
+{
+ ESpellDictionary *dictionary;
+ GHashTable *active_dictionaries;
+ gboolean is_active;
+
+ g_return_if_fail (E_IS_SPELL_CHECKER (checker));
+ g_return_if_fail (language_code != NULL);
+
+ dictionary = e_spell_checker_ref_dictionary (checker, language_code);
+ g_return_if_fail (dictionary != NULL);
+
+ active_dictionaries = checker->priv->active_dictionaries;
+ is_active = g_hash_table_contains (active_dictionaries, dictionary);
+
+ if (active && !is_active) {
+ g_object_ref (dictionary);
+ g_hash_table_add (active_dictionaries, dictionary);
+ g_object_notify (G_OBJECT (checker), "active-languages");
+ } else if (!active && is_active) {
+ g_hash_table_remove (active_dictionaries, dictionary);
+ g_object_notify (G_OBJECT (checker), "active-languages");
+ }
+
+ g_object_unref (dictionary);
+}
+
+/**
+ * e_spell_checker_list_active_languages:
+ * @checker: an #ESpellChecker
+ * @n_languages: return location for the number of active languages, or %NULL
+ *
+ * Returns a %NULL-terminated array of language codes actively being used
+ * for spell checking. Free the returned array with g_strfreev().
+ *
+ * Returns: a %NULL-teriminated array of language codes
+ **/
+gchar **
+e_spell_checker_list_active_languages (ESpellChecker *checker,
+ guint *n_languages)
+{
+ GHashTable *active_dictionaries;
+ GList *list, *link;
+ gchar **active_languages;
+ guint size;
+ gint ii = 0;
+
+ g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), NULL);
+
+ active_dictionaries = checker->priv->active_dictionaries;
+ list = g_hash_table_get_keys (active_dictionaries);
+ size = g_hash_table_size (active_dictionaries);
+
+ active_languages = g_new0 (gchar *, size + 1);
+
+ list = g_list_sort (list, (GCompareFunc) e_spell_dictionary_compare);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ ESpellDictionary *dictionary;
+ const gchar *language_code;
+
+ dictionary = E_SPELL_DICTIONARY (link->data);
+ language_code = e_spell_dictionary_get_code (dictionary);
+ active_languages[ii++] = g_strdup (language_code);
+ }
+
+ if (n_languages != NULL)
+ *n_languages = size;
+
+ g_list_free (list);
+
+ return active_languages;
+}
+
+/**
+ * e_spell_checker_count_active_languages:
+ * @checker: an #ESpellChecker
+ *
+ * Returns the number of languages actively being used for spell checking.
+ *
+ * Returns: number of active spell checking languages
+ **/
+guint
+e_spell_checker_count_active_languages (ESpellChecker *checker)
+{
+ g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), 0);
+
+ return g_hash_table_size (checker->priv->active_dictionaries);
+}
+
+/**
+ * e_spell_checker_check_word:
+ * @checker: an #SpellChecker
+ * @word: a word to spell-check
+ * @length: length of @word in bytes or -1 when %NULL-terminated
+ *
+ * Calls e_spell_dictionary_check_word() on all active dictionaries in
+ * @checker, and returns %TRUE if @word is recognized by any of them.
+ *
+ * Returns: %TRUE if @word is recognized, %FALSE otherwise
+ **/
+gboolean
+e_spell_checker_check_word (ESpellChecker *checker,
+ const gchar *word,
+ gsize length)
+{
+ GList *list, *link;
+ gboolean recognized = FALSE;
+
+ g_return_val_if_fail (E_IS_SPELL_CHECKER (checker), TRUE);
+ g_return_val_if_fail (word != NULL && *word != '\0', TRUE);
+
+ list = g_hash_table_get_keys (checker->priv->active_dictionaries);
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ ESpellDictionary *dictionary;
+
+ dictionary = E_SPELL_DICTIONARY (link->data);
+ if (e_spell_dictionary_check_word (dictionary, word, length)) {
+ recognized = TRUE;
+ break;
+ }
+ }
+
+ g_list_free (list);
+
+ return recognized;
+}
+
+/**
+ * e_spell_checker_ignore_word:
+ * @checker: an #ESpellChecker
+ * @word: word to ignore for the rest of session
+ *
+ * Calls e_spell_dictionary_ignore_word() on all active dictionaries in
+ * the @checker.
+ */
+void
+e_spell_checker_ignore_word (ESpellChecker *checker,
+ const gchar *word)
+{
+ WebKitSpellCheckerInterface *interface;
+
+ g_return_if_fail (E_IS_SPELL_CHECKER (checker));
+
+ interface = WEBKIT_SPELL_CHECKER_GET_IFACE (checker);
+ interface->ignore_word (WEBKIT_SPELL_CHECKER (checker), word);
+}
+
+/**
+ * e_spell_checker_learn_word:
+ * @checker: an #ESpellChecker
+ * @word: word to learn
+ *
+ * Calls e_spell_dictionary_learn_word() on all active dictionaries in
+ * the @checker.
+ */
+void
+e_spell_checker_learn_word (ESpellChecker *checker,
+ const gchar *word)
+{
+ WebKitSpellCheckerInterface *interface;
+
+ g_return_if_fail (E_IS_SPELL_CHECKER (checker));
+
+ interface = WEBKIT_SPELL_CHECKER_GET_IFACE (checker);
+ interface->learn_word (WEBKIT_SPELL_CHECKER (checker), word);
+}
diff --git a/e-util/e-spell-checker.h b/e-util/e-spell-checker.h
new file mode 100644
index 0000000000..48303d66a9
--- /dev/null
+++ b/e-util/e-spell-checker.h
@@ -0,0 +1,95 @@
+/*
+ * e-spell-checker.h
+ *
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SPELL_CHECKER_H
+#define E_SPELL_CHECKER_H
+
+#include <glib-object.h>
+#include <e-util/e-spell-dictionary.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SPELL_CHECKER \
+ (e_spell_checker_get_type ())
+#define E_SPELL_CHECKER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_SPELL_CHECKER, ESpellChecker))
+#define E_SPELL_CHECKER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_SPELL_CHECKER, ESpellCheckerClass))
+#define E_IS_SPELL_CHECKER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_SPELL_CHECKER))
+#define E_IS_SPELL_CHECKER_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_SPELL_CHECKER))
+#define E_SPELL_CHECKER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_SPELL_CHECKER, ESpellCheckerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESpellChecker ESpellChecker;
+typedef struct _ESpellCheckerPrivate ESpellCheckerPrivate;
+typedef struct _ESpellCheckerClass ESpellCheckerClass;
+
+struct _ESpellChecker {
+ GObject parent;
+ ESpellCheckerPrivate *priv;
+};
+
+struct _ESpellCheckerClass {
+ GObjectClass parent_class;
+};
+
+GType e_spell_checker_get_type (void) G_GNUC_CONST;
+ESpellChecker * e_spell_checker_new (void);
+GList * e_spell_checker_list_available_dicts
+ (ESpellChecker *checker);
+ESpellDictionary *
+ e_spell_checker_ref_dictionary (ESpellChecker *checker,
+ const gchar *language_code);
+EnchantDict * e_spell_checker_get_enchant_dict
+ (ESpellChecker *checker,
+ const gchar *language_code);
+gboolean e_spell_checker_get_language_active
+ (ESpellChecker *checker,
+ const gchar *language_code);
+void e_spell_checker_set_language_active
+ (ESpellChecker *checker,
+ const gchar *language_code,
+ gboolean active);
+gchar ** e_spell_checker_list_active_languages
+ (ESpellChecker *checker,
+ guint *n_languages);
+guint e_spell_checker_count_active_languages
+ (ESpellChecker *checker);
+gboolean e_spell_checker_check_word (ESpellChecker *checker,
+ const gchar *word,
+ gsize length);
+void e_spell_checker_learn_word (ESpellChecker *checker,
+ const gchar *word);
+void e_spell_checker_ignore_word (ESpellChecker *checker,
+ const gchar *word);
+
+G_END_DECLS
+
+#endif /* E_SPELL_CHECKER_H */
diff --git a/e-util/e-spell-dictionary.c b/e-util/e-spell-dictionary.c
new file mode 100644
index 0000000000..e6e06b7d9a
--- /dev/null
+++ b/e-util/e-spell-dictionary.c
@@ -0,0 +1,797 @@
+/*
+ * e-spell-dictionary.c
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-spell-dictionary.h"
+#include "e-spell-checker.h"
+
+#include <glib/gi18n-lib.h>
+#include <string.h>
+
+#define E_SPELL_DICTIONARY_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_SPELL_DICTIONARY, ESpellDictionaryPrivate))
+
+/**
+ * ESpellDictionary:
+ *
+ * The #ESpellDictionary is a wrapper around #EnchantDict.
+ */
+
+enum {
+ PROP_0,
+ PROP_SPELL_CHECKER
+};
+
+struct _ESpellDictionaryPrivate {
+ GWeakRef spell_checker;
+
+ gchar *name;
+ gchar *code;
+ gchar *collate_key;
+};
+
+#define ISO_639_DOMAIN "iso_639"
+#define ISO_3166_DOMAIN "iso_3166"
+
+static GHashTable *iso_639_table = NULL;
+static GHashTable *iso_3166_table = NULL;
+
+G_DEFINE_TYPE (
+ ESpellDictionary,
+ e_spell_dictionary,
+ G_TYPE_OBJECT);
+
+#ifdef HAVE_ISO_CODES
+
+#define ISOCODESLOCALEDIR ISO_CODES_PREFIX "/share/locale"
+
+#ifdef G_OS_WIN32
+#ifdef DATADIR
+#undef DATADIR
+#endif
+#include <shlobj.h>
+static HMODULE hmodule;
+
+BOOL WINAPI
+DllMain (HINSTANCE hinstDLL,
+ DWORD fdwReason,
+ LPVOID lpvReserved);
+
+BOOL WINAPI
+DllMain (HINSTANCE hinstDLL,
+ DWORD fdwReason,
+ LPVOID lpvReserved)
+{
+ switch (fdwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ hmodule = hinstDLL;
+ break;
+ }
+
+ return TRUE;
+}
+
+static gchar *
+_get_iso_codes_prefix (void)
+{
+ static gchar retval[1000];
+ static gint beenhere = 0;
+ gchar *temp_dir = 0;
+
+ if (beenhere)
+ return retval;
+
+ if (!(temp_dir = g_win32_get_package_installation_directory_of_module ((gpointer) hmodule))) {
+ strcpy (retval, ISO_CODES_PREFIX);
+ return retval;
+ }
+
+ strcpy (retval, temp_dir);
+ g_free (temp_dir);
+ beenhere = 1;
+ return retval;
+}
+
+static gchar *
+_get_isocodeslocaledir (void)
+{
+ static gchar retval[1000];
+ static gint beenhere = 0;
+
+ if (beenhere)
+ return retval;
+
+ strcpy (retval, _get_iso_codes_prefix ());
+ strcat (retval, "\\share\\locale" );
+ beenhere = 1;
+ return retval;
+}
+
+#undef ISO_CODES_PREFIX
+#define ISO_CODES_PREFIX _get_iso_codes_prefix ()
+
+#undef ISOCODESLOCALEDIR
+#define ISOCODESLOCALEDIR _get_isocodeslocaledir ()
+
+#endif
+
+static void
+iso_639_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer data,
+ GError **error)
+{
+ GHashTable *hash_table = data;
+ const gchar *iso_639_1_code = NULL;
+ const gchar *iso_639_2_code = NULL;
+ const gchar *name = NULL;
+ const gchar *code = NULL;
+ gint ii;
+
+ if (g_strcmp0 (element_name, "iso_639_entry") != 0) {
+ return;
+ }
+
+ for (ii = 0; attribute_names[ii] != NULL; ii++) {
+ if (strcmp (attribute_names[ii], "name") == 0)
+ name = attribute_values[ii];
+ else if (strcmp (attribute_names[ii], "iso_639_1_code") == 0)
+ iso_639_1_code = attribute_values[ii];
+ else if (strcmp (attribute_names[ii], "iso_639_2T_code") == 0)
+ iso_639_2_code = attribute_values[ii];
+ }
+
+ code = (iso_639_1_code != NULL) ? iso_639_1_code : iso_639_2_code;
+
+ if (code != NULL && *code != '\0' && name != NULL && *name != '\0')
+ g_hash_table_insert (
+ hash_table, g_strdup (code),
+ g_strdup (dgettext (ISO_639_DOMAIN, name)));
+}
+
+static void
+iso_3166_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer data,
+ GError **error)
+{
+ GHashTable *hash_table = data;
+ const gchar *name = NULL;
+ const gchar *code = NULL;
+ gint ii;
+
+ if (strcmp (element_name, "iso_3166_entry") != 0)
+ return;
+
+ for (ii = 0; attribute_names[ii] != NULL; ii++) {
+ if (strcmp (attribute_names[ii], "name") == 0)
+ name = attribute_values[ii];
+ else if (strcmp (attribute_names[ii], "alpha_2_code") == 0)
+ code = attribute_values[ii];
+ }
+
+ if (code != NULL && *code != '\0' && name != NULL && *name != '\0')
+ g_hash_table_insert (
+ hash_table, g_ascii_strdown (code, -1),
+ g_strdup (dgettext (ISO_3166_DOMAIN, name)));
+}
+
+static GMarkupParser iso_639_parser = {
+ iso_639_start_element,
+ NULL, NULL, NULL, NULL
+};
+
+static GMarkupParser iso_3166_parser = {
+ iso_3166_start_element,
+ NULL, NULL, NULL, NULL
+};
+
+static void
+iso_codes_parse (const GMarkupParser *parser,
+ const gchar *basename,
+ GHashTable *hash_table)
+{
+ GMappedFile *mapped_file;
+ gchar *filename;
+ GError *error = NULL;
+
+ filename = g_build_filename (
+ ISO_CODES_PREFIX, "share", "xml",
+ "iso-codes", basename, NULL);
+ mapped_file = g_mapped_file_new (filename, FALSE, &error);
+ g_free (filename);
+
+ if (mapped_file != NULL) {
+ GMarkupParseContext *context;
+ const gchar *contents;
+ gsize length;
+
+ context = g_markup_parse_context_new (
+ parser, 0, hash_table, NULL);
+ contents = g_mapped_file_get_contents (mapped_file);
+ length = g_mapped_file_get_length (mapped_file);
+ g_markup_parse_context_parse (
+ context, contents, length, &error);
+ g_markup_parse_context_free (context);
+#if GLIB_CHECK_VERSION(2,21,3)
+ g_mapped_file_unref (mapped_file);
+#else
+ g_mapped_file_free (mapped_file);
+#endif
+ }
+
+ if (error != NULL) {
+ g_warning ("%s: %s", basename, error->message);
+ g_error_free (error);
+ }
+}
+
+#endif /* HAVE_ISO_CODES */
+
+struct _enchant_dict_description_data {
+ gchar *language_tag;
+ gchar *dict_name;
+};
+
+static void
+describe_dictionary (const gchar *language_tag,
+ const gchar *provider_name,
+ const gchar *provider_desc,
+ const gchar *provider_file,
+ gpointer user_data)
+{
+ struct _enchant_dict_description_data *data = user_data;
+ const gchar *iso_639_name;
+ const gchar *iso_3166_name;
+ gchar *language_name;
+ gchar *lowercase;
+ gchar **tokens;
+
+ /* Split language code into lowercase tokens. */
+ lowercase = g_ascii_strdown (language_tag, -1);
+ tokens = g_strsplit (lowercase, "_", -1);
+ g_free (lowercase);
+
+ g_return_if_fail (tokens != NULL);
+
+ iso_639_name = g_hash_table_lookup (iso_639_table, tokens[0]);
+
+ if (iso_639_name == NULL) {
+ language_name = g_strdup_printf (
+ /* Translators: %s is the language ISO code. */
+ C_("language", "Unknown (%s)"), language_tag);
+ goto exit;
+ }
+
+ if (g_strv_length (tokens) < 2) {
+ language_name = g_strdup (iso_639_name);
+ goto exit;
+ }
+
+ iso_3166_name = g_hash_table_lookup (iso_3166_table, tokens[1]);
+
+ if (iso_3166_name != NULL)
+ language_name = g_strdup_printf (
+ /* Translators: The first %s is the language name, and the
+ * second is the country name. Example: "French (France)" */
+ C_("language", "%s (%s)"), iso_639_name, iso_3166_name);
+ else
+ language_name = g_strdup_printf (
+ /* Translators: The first %s is the language name, and the
+ * second is the country name. Example: "French (France)" */
+ C_("language", "%s (%s)"), iso_639_name, tokens[1]);
+
+exit:
+ g_strfreev (tokens);
+
+ data->language_tag = g_strdup (language_tag);
+ data->dict_name = language_name;
+}
+
+static void
+spell_dictionary_set_enchant_dict (ESpellDictionary *dictionary,
+ EnchantDict *enchant_dict)
+{
+ struct _enchant_dict_description_data data;
+
+ enchant_dict_describe (enchant_dict, describe_dictionary, &data);
+
+ dictionary->priv->code = data.language_tag;
+ dictionary->priv->name = data.dict_name;
+ dictionary->priv->collate_key = g_utf8_collate_key (data.dict_name, -1);
+}
+
+static void
+spell_dictionary_set_spell_checker (ESpellDictionary *dictionary,
+ ESpellChecker *spell_checker)
+{
+ g_return_if_fail (E_IS_SPELL_CHECKER (spell_checker));
+
+ g_weak_ref_set (&dictionary->priv->spell_checker, spell_checker);
+}
+
+static void
+spell_dictionary_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SPELL_CHECKER:
+ spell_dictionary_set_spell_checker (
+ E_SPELL_DICTIONARY (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+spell_dictionary_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_SPELL_CHECKER:
+ g_value_take_object (
+ value,
+ e_spell_dictionary_ref_spell_checker (
+ E_SPELL_DICTIONARY (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+spell_dictionary_dispose (GObject *object)
+{
+ ESpellDictionaryPrivate *priv;
+
+ priv = E_SPELL_DICTIONARY_GET_PRIVATE (object);
+
+ g_weak_ref_set (&priv->spell_checker, NULL);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_spell_dictionary_parent_class)->dispose (object);
+}
+
+static void
+spell_dictionary_finalize (GObject *object)
+{
+ ESpellDictionaryPrivate *priv;
+
+ priv = E_SPELL_DICTIONARY_GET_PRIVATE (object);
+
+ g_free (priv->name);
+ g_free (priv->code);
+ g_free (priv->collate_key);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_spell_dictionary_parent_class)->finalize (object);
+}
+
+static void
+e_spell_dictionary_class_init (ESpellDictionaryClass *class)
+{
+ GObjectClass *object_class;
+
+ g_type_class_add_private (class, sizeof (ESpellDictionaryPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->set_property = spell_dictionary_set_property;
+ object_class->get_property = spell_dictionary_get_property;
+ object_class->dispose = spell_dictionary_dispose;
+ object_class->finalize = spell_dictionary_finalize;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SPELL_CHECKER,
+ g_param_spec_object (
+ "spell-checker",
+ NULL,
+ "Parent spell checker",
+ E_TYPE_SPELL_CHECKER,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+e_spell_dictionary_init (ESpellDictionary *dictionary)
+{
+ dictionary->priv = E_SPELL_DICTIONARY_GET_PRIVATE (dictionary);
+
+ if (!iso_639_table && !iso_3166_table) {
+#if defined (ENABLE_NLS) && defined (HAVE_ISO_CODES)
+ bindtextdomain (ISO_639_DOMAIN, ISOCODESLOCALEDIR);
+ bind_textdomain_codeset (ISO_639_DOMAIN, "UTF-8");
+
+ bindtextdomain (ISO_3166_DOMAIN, ISOCODESLOCALEDIR);
+ bind_textdomain_codeset (ISO_3166_DOMAIN, "UTF-8");
+#endif /* ENABLE_NLS && HAVE_ISO_CODES */
+
+ iso_639_table = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ iso_3166_table = g_hash_table_new_full (
+ (GHashFunc) g_str_hash,
+ (GEqualFunc) g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+#ifdef HAVE_ISO_CODES
+ iso_codes_parse (
+ &iso_639_parser, "iso_639.xml", iso_639_table);
+ iso_codes_parse (
+ &iso_3166_parser, "iso_3166.xml", iso_3166_table);
+#endif /* HAVE_ISO_CODES */
+ }
+}
+
+ESpellDictionary *
+e_spell_dictionary_new (ESpellChecker *spell_checker,
+ EnchantDict *enchant_dict)
+{
+ ESpellDictionary *dictionary;
+
+ g_return_val_if_fail (E_IS_SPELL_CHECKER (spell_checker), NULL);
+ g_return_val_if_fail (enchant_dict != NULL, NULL);
+
+ dictionary = g_object_new (
+ E_TYPE_SPELL_DICTIONARY,
+ "spell-checker", spell_checker, NULL);
+
+ /* Since EnchantDict is not reference counted, ESpellChecker
+ * is loaning us the EnchantDict pointer. We do not own it. */
+ spell_dictionary_set_enchant_dict (dictionary, enchant_dict);
+
+ return dictionary;
+}
+
+/**
+ * e_spell_dictionary_hash:
+ * @dictionary: an #ESpellDictionary
+ *
+ * Generates a hash value for @dictionary based on its ISO code.
+ * This function is intended for easily hashing an #ESpellDictionary
+ * to add to a #GHashTable or similar data structure.
+ *
+ * Returns: a hash value for @dictionary
+ **/
+guint
+e_spell_dictionary_hash (ESpellDictionary *dictionary)
+{
+ const gchar *code;
+
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), 0);
+
+ code = e_spell_dictionary_get_code (dictionary);
+
+ return g_str_hash (code);
+}
+
+/**
+ * e_spell_dictionary_equal:
+ * @dictionary1: an #ESpellDictionary
+ * @dictionary2: another #ESpellDictionary
+ *
+ * Checks two #ESpellDictionary instances for equality based on their
+ * ISO codes.
+ *
+ * Returns: %TRUE if @dictionary1 and @dictionary2 are equal
+ **/
+gboolean
+e_spell_dictionary_equal (ESpellDictionary *dictionary1,
+ ESpellDictionary *dictionary2)
+{
+ const gchar *code1, *code2;
+
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary1), FALSE);
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary2), FALSE);
+
+ if (dictionary1 == dictionary2)
+ return TRUE;
+
+ code1 = e_spell_dictionary_get_code (dictionary1);
+ code2 = e_spell_dictionary_get_code (dictionary2);
+
+ return g_str_equal (code1, code2);
+}
+
+/**
+ * e_spell_dictionary_compare:
+ * @dictionary1: an #ESpellDictionary
+ * @dictionary2: another #ESpellDictionary
+ *
+ * Compares @dictionary1 and @dictionary2 by their display names for
+ * the purpose of lexicographical sorting. Use this function where a
+ * #GCompareFunc callback is required, such as g_list_sort().
+ *
+ * Returns: 0 if the names match,
+ * a negative value if @dictionary1 < @dictionary2,
+ * or a positive value of @dictionary1 > @dictionary2
+ **/
+gint
+e_spell_dictionary_compare (ESpellDictionary *dictionary1,
+ ESpellDictionary *dictionary2)
+{
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary1), 0);
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary2), 0);
+
+ return strcmp (
+ dictionary1->priv->collate_key,
+ dictionary2->priv->collate_key);
+}
+
+/**
+ * e_spell_dictionary_get_name:
+ * @dictionary: an #ESpellDictionary
+ *
+ * Returns the display name of the dictionary (for example
+ * "English (British)")
+ *
+ * Returns: the display name of the @dictionary
+ */
+const gchar *
+e_spell_dictionary_get_name (ESpellDictionary *dictionary)
+{
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), NULL);
+
+ return dictionary->priv->name;
+}
+
+/**
+ * e_spell_dictionary_get_code:
+ * @dictionary: an #ESpellDictionary
+ *
+ * Returns the ISO code of the spell-checking language for
+ * @dictionary (for example "en_US").
+ *
+ * Returns: the language code of the @dictionary
+ */
+const gchar *
+e_spell_dictionary_get_code (ESpellDictionary *dictionary)
+{
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), NULL);
+
+ return dictionary->priv->code;
+}
+
+/**
+ * e_spell_dictionary_ref_spell_checker:
+ * @dictionary: an #ESpellDictionary
+ *
+ * Returns a new reference to the #ESpellChecker which owns the dictionary.
+ * Unreference the #ESpellChecker with g_object_unref() when finished with it.
+ *
+ * Returns: an #ESpellChecker
+ **/
+ESpellChecker *
+e_spell_dictionary_ref_spell_checker (ESpellDictionary *dictionary)
+{
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), NULL);
+
+ return g_weak_ref_get (&dictionary->priv->spell_checker);
+}
+
+/**
+ * e_spell_dictionary_check_word:
+ * @dictionary: an #ESpellDictionary
+ * @word: a word to spell-check
+ * @length: length of @word in bytes or -1 when %NULL-terminated
+ *
+ * Tries to lookup the @word in the @dictionary to check whether
+ * it's spelled correctly or not.
+ *
+ * Returns: %TRUE if @word is recognized, %FALSE otherwise
+ */
+gboolean
+e_spell_dictionary_check_word (ESpellDictionary *dictionary,
+ const gchar *word,
+ gsize length)
+{
+ ESpellChecker *spell_checker;
+ EnchantDict *enchant_dict;
+ gboolean recognized;
+
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), TRUE);
+ g_return_val_if_fail (word != NULL && *word != '\0', TRUE);
+
+ spell_checker = e_spell_dictionary_ref_spell_checker (dictionary);
+ g_return_val_if_fail (spell_checker != NULL, TRUE);
+
+ enchant_dict = e_spell_checker_get_enchant_dict (
+ spell_checker, e_spell_dictionary_get_code (dictionary));
+ g_return_val_if_fail (enchant_dict != NULL, TRUE);
+
+ recognized = (enchant_dict_check (enchant_dict, word, length) == 0);
+
+ g_object_unref (spell_checker);
+
+ return recognized;
+}
+
+/**
+ * e_spell_dictionary_learn_word:
+ * @dictionary: an #ESpellDictionary
+ * @word: a word to add to @dictionary
+ * @length: length of @word in bytes or -1 when %NULL-terminated
+ *
+ * Permanently adds @word to @dictionary so that next time calling
+ * e_spell_dictionary_check() on the @word will return %TRUE.
+ */
+void
+e_spell_dictionary_learn_word (ESpellDictionary *dictionary,
+ const gchar *word,
+ gsize length)
+{
+ ESpellChecker *spell_checker;
+ EnchantDict *enchant_dict;
+
+ g_return_if_fail (E_IS_SPELL_DICTIONARY (dictionary));
+ g_return_if_fail (word != NULL && *word != '\0');
+
+ spell_checker = e_spell_dictionary_ref_spell_checker (dictionary);
+ g_return_if_fail (spell_checker != NULL);
+
+ enchant_dict = e_spell_checker_get_enchant_dict (
+ spell_checker, e_spell_dictionary_get_code (dictionary));
+ g_return_if_fail (enchant_dict != NULL);
+
+ enchant_dict_add_to_personal (enchant_dict, word, length);
+
+ g_object_unref (spell_checker);
+}
+
+/**
+ * e_spell_dictionary_ignore_word:
+ * @dictionary: an #ESpellDictionary
+ * @word: a word to add to ignore list
+ * @length: length of @word in bytes or -1 when %NULL-terminated
+ *
+ * Adds @word to temporary ignore list of the @dictionary, so that
+ * e_spell_dictionary_check() on the @word will return %TRUE. The
+ * list is cleared when the dictionary is freed.
+ */
+void
+e_spell_dictionary_ignore_word (ESpellDictionary *dictionary,
+ const gchar *word,
+ gsize length)
+{
+ ESpellChecker *spell_checker;
+ EnchantDict *enchant_dict;
+
+ g_return_if_fail (E_IS_SPELL_DICTIONARY (dictionary));
+ g_return_if_fail (word != NULL && *word != '\0');
+
+ spell_checker = e_spell_dictionary_ref_spell_checker (dictionary);
+ g_return_if_fail (spell_checker != NULL);
+
+ enchant_dict = e_spell_checker_get_enchant_dict (
+ spell_checker, e_spell_dictionary_get_code (dictionary));
+ g_return_if_fail (enchant_dict != NULL);
+
+ enchant_dict_add_to_session (enchant_dict, word, length);
+
+ g_object_unref (spell_checker);
+}
+
+/**
+ * e_spell_dictionary_get_suggestions:
+ * @dictionary: an #ESpellDictionary
+ * @word: a word to which to find suggestions
+ * @length: length of @word in bytes or -1 when %NULL-terminated
+ *
+ * Provides list of alternative spellings of @word.
+ *
+ * Free the returned spelling suggestions with g_free(), and the list
+ * itself with g_list_free(). An easy way to free the list properly in
+ * one step is as follows:
+ *
+ * |[
+ * g_list_free_full (list, (GDestroyNotify) g_free);
+ * ]|
+ *
+ * Returns: a list of spelling suggestions for @word
+ */
+GList *
+e_spell_dictionary_get_suggestions (ESpellDictionary *dictionary,
+ const gchar *word,
+ gsize length)
+{
+ ESpellChecker *spell_checker;
+ EnchantDict *enchant_dict;
+ GList *list = NULL;
+ gchar **suggestions;
+ gsize ii, count = 0;
+
+ g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), NULL);
+ g_return_val_if_fail (word != NULL && *word != '\0', NULL);
+
+ spell_checker = e_spell_dictionary_ref_spell_checker (dictionary);
+ g_return_val_if_fail (spell_checker != NULL, NULL);
+
+ enchant_dict = e_spell_checker_get_enchant_dict (
+ spell_checker, e_spell_dictionary_get_code (dictionary));
+ g_return_val_if_fail (enchant_dict != NULL, NULL);
+
+ suggestions = enchant_dict_suggest (enchant_dict, word, length, &count);
+ for (ii = 0; ii < count; ii++)
+ list = g_list_prepend (list, g_strdup (suggestions[ii]));
+ enchant_dict_free_suggestions (enchant_dict, suggestions);
+
+ g_object_unref (spell_checker);
+
+ return g_list_reverse (list);
+}
+
+/**
+ * e_spell_dictionary_add_correction
+ * @dictionary: an #ESpellDictionary
+ * @misspelled: a misspelled word
+ * @misspelled_length: length of @misspelled in bytes or -1 when
+ * %NULL-terminated
+ * @correction: the corrected word
+ * @correction_length: length of @correction in bytes or -1 when
+ * %NULL-terminated
+ *
+ * Learns a new @correction of @misspelled word.
+ */
+void
+e_spell_dictionary_store_correction (ESpellDictionary *dictionary,
+ const gchar *misspelled,
+ gsize misspelled_length,
+ const gchar *correction,
+ gsize correction_length)
+{
+ ESpellChecker *spell_checker;
+ EnchantDict *enchant_dict;
+
+ g_return_if_fail (E_IS_SPELL_DICTIONARY (dictionary));
+ g_return_if_fail (misspelled != NULL && *misspelled != '\0');
+ g_return_if_fail (correction != NULL && *correction != '\0');
+
+ spell_checker = e_spell_dictionary_ref_spell_checker (dictionary);
+ g_return_if_fail (spell_checker != NULL);
+
+ enchant_dict = e_spell_checker_get_enchant_dict (
+ spell_checker, e_spell_dictionary_get_code (dictionary));
+ g_return_if_fail (enchant_dict != NULL);
+
+ enchant_dict_store_replacement (
+ enchant_dict,
+ misspelled, misspelled_length,
+ correction, correction_length);
+
+ g_object_unref (spell_checker);
+}
+
diff --git a/e-util/e-spell-dictionary.h b/e-util/e-spell-dictionary.h
new file mode 100644
index 0000000000..f36bfb4704
--- /dev/null
+++ b/e-util/e-spell-dictionary.h
@@ -0,0 +1,99 @@
+/*
+ * e-spell-dictionary.h
+ *
+ * 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.
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_SPELL_DICTIONARY_H
+#define E_SPELL_DICTIONARY_H
+
+#include <glib-object.h>
+#include <enchant/enchant.h>
+
+/* Standard GObject macros */
+#define E_TYPE_SPELL_DICTIONARY \
+ (e_spell_dictionary_get_type ())
+#define E_SPELL_DICTIONARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_SPELL_DICTIONARY, ESpellDictionary))
+#define E_SPELL_DICTIONARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_SPELL_DICTIONARY, ESpellDictionaryClass))
+#define E_IS_SPELL_DICTIONARY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_SPELL_DICTIONARY))
+#define E_IS_SPELL_DICTIONARY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_SPELL_DICTIONARY))
+#define E_SPELL_DICTIONARY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_SPELL_DICTIONARY, ESpellDictionaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ESpellDictionary ESpellDictionary;
+typedef struct _ESpellDictionaryPrivate ESpellDictionaryPrivate;
+typedef struct _ESpellDictionaryClass ESpellDictionaryClass;
+typedef struct _ESpellChecker ESpellChecker;
+
+struct _ESpellDictionary {
+ GObject parent;
+ ESpellDictionaryPrivate *priv;
+};
+
+struct _ESpellDictionaryClass {
+ GObjectClass parent_class;
+};
+
+GType e_spell_dictionary_get_type (void) G_GNUC_CONST;
+ESpellDictionary *
+ e_spell_dictionary_new (ESpellChecker *spell_checker,
+ EnchantDict *enchant_dict);
+guint e_spell_dictionary_hash (ESpellDictionary *dictionary);
+gboolean e_spell_dictionary_equal (ESpellDictionary *dictionary1,
+ ESpellDictionary *dictionary2);
+gint e_spell_dictionary_compare (ESpellDictionary *dictionary1,
+ ESpellDictionary *dictionary2);
+const gchar * e_spell_dictionary_get_name (ESpellDictionary *dictionary);
+const gchar * e_spell_dictionary_get_code (ESpellDictionary *dictionary);
+ESpellChecker * e_spell_dictionary_ref_spell_checker
+ (ESpellDictionary *dictionary);
+gboolean e_spell_dictionary_check_word (ESpellDictionary *dictionary,
+ const gchar *word,
+ gsize length);
+void e_spell_dictionary_learn_word (ESpellDictionary *dictionary,
+ const gchar *word,
+ gsize length);
+void e_spell_dictionary_ignore_word (ESpellDictionary *dictionary,
+ const gchar *word,
+ gsize length);
+GList * e_spell_dictionary_get_suggestions
+ (ESpellDictionary *dictionary,
+ const gchar *word,
+ gsize length);
+void e_spell_dictionary_store_correction
+ (ESpellDictionary *dictionary,
+ const gchar *misspelled,
+ gsize misspelled_length,
+ const gchar *correction,
+ gsize correction_length);
+
+G_END_DECLS
+
+#endif /* E_SPELL_DICTIONARY_H */
diff --git a/e-util/e-spell-entry.c b/e-util/e-spell-entry.c
index 75c7a6a9c8..4993612a7a 100644
--- a/e-util/e-spell-entry.c
+++ b/e-util/e-spell-entry.c
@@ -23,8 +23,7 @@
#include <libebackend/libebackend.h>
-#include <editor/gtkhtml-spell-language.h>
-#include <editor/gtkhtml-spell-checker.h>
+#include <e-util/e-spell-checker.h>
#include "e-misc-utils.h"
#include "e-spell-entry.h"
@@ -37,18 +36,20 @@ struct _ESpellEntryPrivate {
PangoAttrList *attr_list;
gint mark_character;
gint entry_scroll_offset;
- GSettings *settings;
gboolean custom_checkers;
gboolean checking_enabled;
- GSList *checkers;
gchar **words;
gint *word_starts;
gint *word_ends;
+
+ ESpellChecker *spell_checker;
+ guint active_languages_handler_id;
};
enum {
PROP_0,
- PROP_CHECKING_ENABLED
+ PROP_CHECKING_ENABLED,
+ PROP_SPELL_CHECKER
};
G_DEFINE_TYPE_WITH_CODE (
@@ -76,16 +77,12 @@ word_misspelled (ESpellEntry *entry,
g_strlcpy (word, text + start, end - start + 1);
if (g_unichar_isalpha (*word)) {
- GSList *li;
+ ESpellChecker *spell_checker;
gssize wlen = strlen (word);
- for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
- GtkhtmlSpellChecker *checker = li->data;
- if (gtkhtml_spell_checker_check_word (checker, word, wlen)) {
- result = FALSE;
- break;
- }
- }
+ spell_checker = e_spell_entry_get_spell_checker (entry);
+ if (e_spell_checker_check_word (spell_checker, word, wlen))
+ result = FALSE;
}
g_free (word);
@@ -160,8 +157,13 @@ spell_entry_recheck_all (ESpellEntry *entry)
pango_attr_list_unref (entry->priv->attr_list);
entry->priv->attr_list = pango_attr_list_new ();
- if (e_spell_entry_get_checking_enabled (entry))
- check_words = (entry->priv->checkers != NULL);
+ if (e_spell_entry_get_checking_enabled (entry)) {
+ ESpellChecker *spell_checker;
+
+ spell_checker = e_spell_entry_get_spell_checker (entry);
+ if (e_spell_checker_count_active_languages (spell_checker) > 0)
+ check_words = TRUE;
+ }
if (check_words) {
/* Loop through words */
@@ -269,15 +271,15 @@ add_to_dictionary (GtkWidget *menuitem,
{
gchar *word;
gint start, end;
- GtkhtmlSpellChecker *checker;
+ ESpellDictionary *dict;
get_word_extents_from_position (
entry, &start, &end, entry->priv->mark_character);
word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
- checker = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
- if (checker != NULL)
- gtkhtml_spell_checker_add_word (checker, word, -1);
+ dict = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
+ if (dict != NULL)
+ e_spell_dictionary_learn_word (dict, word, -1);
g_free (word);
@@ -300,18 +302,16 @@ static void
ignore_all (GtkWidget *menuitem,
ESpellEntry *entry)
{
+ ESpellChecker *spell_checker;
gchar *word;
gint start, end;
- GSList *li;
get_word_extents_from_position (
entry, &start, &end, entry->priv->mark_character);
word = gtk_editable_get_chars (GTK_EDITABLE (entry), start, end);
- for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
- GtkhtmlSpellChecker *checker = li->data;
- gtkhtml_spell_checker_add_word_to_session (checker, word, -1);
- }
+ spell_checker = e_spell_entry_get_spell_checker (entry);
+ e_spell_checker_ignore_word (spell_checker, word);
g_free (word);
@@ -338,7 +338,7 @@ replace_word (GtkWidget *menuitem,
const gchar *newword;
gint start, end;
gint cursor;
- GtkhtmlSpellChecker *checker;
+ ESpellDictionary *dict;
get_word_extents_from_position (
entry, &start, &end, entry->priv->mark_character);
@@ -359,11 +359,11 @@ replace_word (GtkWidget *menuitem,
GTK_EDITABLE (entry), newword, strlen (newword), &start);
gtk_editable_set_position (GTK_EDITABLE (entry), cursor);
- checker = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
+ dict = g_object_get_data (G_OBJECT (menuitem), "spell-entry-checker");
- if (checker != NULL)
- gtkhtml_spell_checker_store_replacement (
- checker, oldword, -1, newword, -1);
+ if (dict != NULL)
+ e_spell_dictionary_store_correction (
+ dict, oldword, -1, newword, -1);
g_free (oldword);
}
@@ -371,13 +371,13 @@ replace_word (GtkWidget *menuitem,
static void
build_suggestion_menu (ESpellEntry *entry,
GtkWidget *menu,
- GtkhtmlSpellChecker *checker,
+ ESpellDictionary *dict,
const gchar *word)
{
GtkWidget *mi;
GList *suggestions, *iter;
- suggestions = gtkhtml_spell_checker_get_suggestions (checker, word, -1);
+ suggestions = e_spell_dictionary_get_suggestions (dict, word, -1);
if (suggestions == NULL) {
/* no suggestions. Put something in the menu anyway... */
@@ -414,7 +414,7 @@ build_suggestion_menu (ESpellEntry *entry,
}
mi = gtk_menu_item_new_with_label (iter->data);
- g_object_set_data (G_OBJECT (mi), "spell-entry-checker", checker);
+ g_object_set_data (G_OBJECT (mi), "spell-entry-checker", dict);
g_signal_connect (mi, "activate", G_CALLBACK (replace_word), entry);
gtk_widget_show (mi);
gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
@@ -428,35 +428,49 @@ static GtkWidget *
build_spelling_menu (ESpellEntry *entry,
const gchar *word)
{
- GtkhtmlSpellChecker *checker;
+ ESpellChecker *spell_checker;
+ ESpellDictionary *dict;
GtkWidget *topmenu, *mi;
+ GQueue queue = G_QUEUE_INIT;
+ gchar **active_languages;
+ guint ii, n_active_languages;
gchar *label;
topmenu = gtk_menu_new ();
- if (entry->priv->checkers == NULL)
- return topmenu;
+ spell_checker = e_spell_entry_get_spell_checker (entry);
+
+ active_languages = e_spell_checker_list_active_languages (
+ spell_checker, &n_active_languages);
+ for (ii = 0; ii < n_active_languages; ii++) {
+ dict = e_spell_checker_ref_dictionary (
+ spell_checker, active_languages[ii]);
+ if (dict != NULL)
+ g_queue_push_tail (&queue, dict);
+ }
+ g_strfreev (active_languages);
+
+ if (g_queue_is_empty (&queue))
+ goto exit;
/* Suggestions */
- if (entry->priv->checkers->next == NULL) {
- checker = entry->priv->checkers->data;
- build_suggestion_menu (entry, topmenu, checker, word);
+ if (n_active_languages == 1) {
+ dict = g_queue_peek_head (&queue);
+ build_suggestion_menu (entry, topmenu, dict, word);
} else {
- GSList *li;
GtkWidget *menu;
- const gchar *lang_name;
+ GList *list, *link;
- for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
- const GtkhtmlSpellLanguage *language;
+ list = g_queue_peek_head_link (&queue);
- checker = li->data;
- language = gtkhtml_spell_checker_get_language (checker);
- if (language == NULL)
- continue;
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ const gchar *lang_name;
- lang_name = gtkhtml_spell_language_get_name (language);
+ dict = E_SPELL_DICTIONARY (link->data);
+
+ lang_name = e_spell_dictionary_get_name (dict);
if (lang_name == NULL)
- lang_name = gtkhtml_spell_language_get_code (language);
+ lang_name = e_spell_dictionary_get_code (dict);
if (lang_name == NULL)
lang_name = "???";
@@ -466,7 +480,7 @@ build_spelling_menu (ESpellEntry *entry,
gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
menu = gtk_menu_new ();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
- build_suggestion_menu (entry, menu, checker, word);
+ build_suggestion_menu (entry, menu, dict, word);
}
}
@@ -484,36 +498,34 @@ build_spelling_menu (ESpellEntry *entry,
GTK_IMAGE_MENU_ITEM (mi),
gtk_image_new_from_icon_name ("list-add", GTK_ICON_SIZE_MENU));
- if (entry->priv->checkers->next == NULL) {
- checker = entry->priv->checkers->data;
- g_object_set_data (G_OBJECT (mi), "spell-entry-checker", checker);
+ if (n_active_languages == 1) {
+ dict = g_queue_peek_head (&queue);
+ g_object_set_data (G_OBJECT (mi), "spell-entry-checker", dict);
g_signal_connect (
mi, "activate",
G_CALLBACK (add_to_dictionary), entry);
} else {
- GSList *li;
GtkWidget *menu, *submi;
- const gchar *lang_name;
+ GList *list, *link;
menu = gtk_menu_new ();
gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
- for (li = entry->priv->checkers; li; li = g_slist_next (li)) {
- const GtkhtmlSpellLanguage *language;
+ list = g_queue_peek_head_link (&queue);
- checker = li->data;
- language = gtkhtml_spell_checker_get_language (checker);
- if (language == NULL)
- continue;
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ const gchar *lang_name;
+
+ dict = E_SPELL_DICTIONARY (link->data);
- lang_name = gtkhtml_spell_language_get_name (language);
+ lang_name = e_spell_dictionary_get_name (dict);
if (lang_name == NULL)
- lang_name = gtkhtml_spell_language_get_code (language);
+ lang_name = e_spell_dictionary_get_code (dict);
if (lang_name == NULL)
lang_name = "???";
submi = gtk_menu_item_new_with_label (lang_name);
- g_object_set_data (G_OBJECT (submi), "spell-entry-checker", checker);
+ g_object_set_data (G_OBJECT (submi), "spell-entry-checker", dict);
g_signal_connect (
submi, "activate",
G_CALLBACK (add_to_dictionary), entry);
@@ -535,6 +547,10 @@ build_spelling_menu (ESpellEntry *entry,
gtk_widget_show_all (mi);
gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+exit:
+ while (!g_queue_is_empty (&queue))
+ g_object_unref (g_queue_pop_head (&queue));
+
return topmenu;
}
@@ -580,10 +596,12 @@ spell_entry_populate_popup (ESpellEntry *entry,
GtkMenu *menu,
gpointer data)
{
+ ESpellChecker *spell_checker;
gint start, end;
gchar *word;
- if (entry->priv->checkers == NULL)
+ spell_checker = e_spell_entry_get_spell_checker (entry);
+ if (e_spell_checker_count_active_languages (spell_checker) == 0)
return;
get_word_extents_from_position (
@@ -606,8 +624,10 @@ static void
spell_entry_changed (GtkEditable *editable)
{
ESpellEntry *entry = E_SPELL_ENTRY (editable);
+ ESpellChecker *spell_checker;
- if (entry->priv->checkers == NULL)
+ spell_checker = e_spell_entry_get_spell_checker (entry);
+ if (e_spell_checker_count_active_languages (spell_checker) == 0)
return;
if (entry->priv->words != NULL) {
@@ -633,70 +653,6 @@ spell_entry_notify_scroll_offset (ESpellEntry *spell_entry)
&spell_entry->priv->entry_scroll_offset, NULL);
}
-static GList *
-spell_entry_load_spell_languages (void)
-{
- GSettings *settings;
- GList *spell_languages = NULL;
- gchar **strv;
- gint ii;
-
- /* Ask GSettings for a list of spell check language codes. */
- settings = g_settings_new ("org.gnome.evolution.mail");
- strv = g_settings_get_strv (settings, "composer-spell-languages");
- g_object_unref (settings);
-
- /* Convert the codes to spell language structs. */
- for (ii = 0; strv[ii] != NULL; ii++) {
- gchar *language_code = strv[ii];
- const GtkhtmlSpellLanguage *language;
-
- language = gtkhtml_spell_language_lookup (language_code);
- if (language != NULL)
- spell_languages = g_list_prepend (
- spell_languages, (gpointer) language);
- }
-
- g_strfreev (strv);
-
- spell_languages = g_list_reverse (spell_languages);
-
- /* Pick a default spell language if it came back empty. */
- if (spell_languages == NULL) {
- const GtkhtmlSpellLanguage *language;
-
- language = gtkhtml_spell_language_lookup (NULL);
-
- if (language) {
- spell_languages = g_list_prepend (
- spell_languages, (gpointer) language);
- }
- }
-
- return spell_languages;
-}
-
-static void
-spell_entry_settings_changed (ESpellEntry *spell_entry,
- const gchar *key)
-{
- GList *languages;
-
- g_return_if_fail (spell_entry != NULL);
-
- if (spell_entry->priv->custom_checkers)
- return;
-
- if (key && !g_str_equal (key, "composer-spell-languages"))
- return;
-
- languages = spell_entry_load_spell_languages ();
- e_spell_entry_set_languages (spell_entry, languages);
- g_list_free (languages);
-
- spell_entry->priv->custom_checkers = FALSE;
-}
-
static gint
spell_entry_find_position (ESpellEntry *spell_entry,
gint x)
@@ -722,6 +678,15 @@ spell_entry_find_position (ESpellEntry *spell_entry,
}
static void
+spell_entry_active_languages_cb (ESpellChecker *spell_checker,
+ GParamSpec *pspec,
+ ESpellEntry *spell_entry)
+{
+ if (gtk_widget_get_realized (GTK_WIDGET (spell_entry)))
+ spell_entry_recheck_all (spell_entry);
+}
+
+static void
spell_entry_set_property (GObject *object,
guint property_id,
const GValue *value,
@@ -733,6 +698,12 @@ spell_entry_set_property (GObject *object,
E_SPELL_ENTRY (object),
g_value_get_boolean (value));
return;
+
+ case PROP_SPELL_CHECKER:
+ e_spell_entry_set_spell_checker (
+ E_SPELL_ENTRY (object),
+ g_value_get_object (value));
+ return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -751,6 +722,13 @@ spell_entry_get_property (GObject *object,
e_spell_entry_get_checking_enabled (
E_SPELL_ENTRY (object)));
return;
+
+ case PROP_SPELL_CHECKER:
+ g_value_set_object (
+ value,
+ e_spell_entry_get_spell_checker (
+ E_SPELL_ENTRY (object)));
+ return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -763,10 +741,14 @@ spell_entry_dispose (GObject *object)
priv = E_SPELL_ENTRY_GET_PRIVATE (object);
- g_slist_free_full (priv->checkers, (GDestroyNotify) g_object_unref);
- priv->checkers = NULL;
+ if (priv->active_languages_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->spell_checker,
+ priv->active_languages_handler_id);
+ priv->active_languages_handler_id = 0;
+ }
- g_clear_object (&priv->settings);
+ g_clear_object (&priv->spell_checker);
if (priv->attr_list != NULL) {
pango_attr_list_unref (priv->attr_list);
@@ -795,9 +777,22 @@ spell_entry_finalize (GObject *object)
static void
spell_entry_constructed (GObject *object)
{
+ ESpellEntry *spell_entry;
+ ESpellChecker *spell_checker;
+
+ spell_entry = E_SPELL_ENTRY (object);
+
/* Chain up to parent's constructed() method. */
G_OBJECT_CLASS (e_spell_entry_parent_class)->constructed (object);
+ /* Install a default spell checker if there is not one already. */
+ spell_checker = e_spell_entry_get_spell_checker (spell_entry);
+ if (spell_checker == NULL) {
+ spell_checker = e_spell_checker_new ();
+ e_spell_entry_set_spell_checker (spell_entry, spell_checker);
+ g_object_unref (spell_checker);
+ }
+
e_extensible_load_extensions (E_EXTENSIBLE (object));
}
@@ -860,6 +855,17 @@ e_spell_entry_class_init (ESpellEntryClass *class)
TRUE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SPELL_CHECKER,
+ g_param_spec_object (
+ "spell-checker",
+ "Spell Checker",
+ "The spell checker object",
+ E_TYPE_SPELL_CHECKER,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
}
static void
@@ -867,7 +873,6 @@ e_spell_entry_init (ESpellEntry *spell_entry)
{
spell_entry->priv = E_SPELL_ENTRY_GET_PRIVATE (spell_entry);
spell_entry->priv->attr_list = pango_attr_list_new ();
- spell_entry->priv->checkers = NULL;
spell_entry->priv->checking_enabled = TRUE;
g_signal_connect (
@@ -882,15 +887,6 @@ e_spell_entry_init (ESpellEntry *spell_entry)
e_signal_connect_notify (
spell_entry, "notify::scroll-offset",
G_CALLBACK (spell_entry_notify_scroll_offset), NULL);
-
- /* listen for languages changes */
- spell_entry->priv->settings = g_settings_new ("org.gnome.evolution.mail");
- g_signal_connect_swapped (
- spell_entry->priv->settings, "changed",
- G_CALLBACK (spell_entry_settings_changed), spell_entry);
-
- /* load current settings */
- spell_entry_settings_changed (spell_entry, NULL);
}
GtkWidget *
@@ -899,36 +895,6 @@ e_spell_entry_new (void)
return g_object_new (E_TYPE_SPELL_ENTRY, NULL);
}
-/* 'languages' consists of 'const GtkhtmlSpellLanguage *' */
-void
-e_spell_entry_set_languages (ESpellEntry *spell_entry,
- GList *languages)
-{
- GList *iter;
-
- g_return_if_fail (spell_entry != NULL);
-
- spell_entry->priv->custom_checkers = TRUE;
-
- if (spell_entry->priv->checkers)
- g_slist_free_full (spell_entry->priv->checkers, g_object_unref);
- spell_entry->priv->checkers = NULL;
-
- for (iter = languages; iter; iter = g_list_next (iter)) {
- const GtkhtmlSpellLanguage *language = iter->data;
-
- if (language)
- spell_entry->priv->checkers = g_slist_prepend (
- spell_entry->priv->checkers,
- gtkhtml_spell_checker_new (language));
- }
-
- spell_entry->priv->checkers = g_slist_reverse (spell_entry->priv->checkers);
-
- if (gtk_widget_get_realized (GTK_WIDGET (spell_entry)))
- spell_entry_recheck_all (spell_entry);
-}
-
gboolean
e_spell_entry_get_checking_enabled (ESpellEntry *spell_entry)
{
@@ -951,3 +917,66 @@ e_spell_entry_set_checking_enabled (ESpellEntry *spell_entry,
g_object_notify (G_OBJECT (spell_entry), "checking-enabled");
}
+
+/**
+ * e_spell_entry_get_spell_checker:
+ * @spell_entry: an #ESpellEntry
+ *
+ * Returns the #ESpellChecker being used for spell checking. By default,
+ * #ESpellEntry creates its own #ESpellChecker, but this can be overridden
+ * through e_spell_entry_set_spell_checker().
+ *
+ * Returns: an #ESpellChecker
+ **/
+ESpellChecker *
+e_spell_entry_get_spell_checker (ESpellEntry *spell_entry)
+{
+ g_return_val_if_fail (E_IS_SPELL_ENTRY (spell_entry), NULL);
+
+ return spell_entry->priv->spell_checker;
+}
+
+/**
+ * e_spell_entry_set_spell_checker:
+ * @spell_entry: an #ESpellEntry
+ * @spell_checker: an #ESpellChecker
+ *
+ * Sets the #ESpellChecker to use for spell checking. By default,
+ * #ESpellEntry creates its own #ESpellChecker. This function can be
+ * useful for sharing an #ESpellChecker across multiple spell-checking
+ * widgets, so the active spell checking languages stay synchronized.
+ **/
+void
+e_spell_entry_set_spell_checker (ESpellEntry *spell_entry,
+ ESpellChecker *spell_checker)
+{
+ gulong handler_id;
+
+ g_return_if_fail (E_IS_SPELL_ENTRY (spell_entry));
+ g_return_if_fail (E_IS_SPELL_CHECKER (spell_checker));
+
+ if (spell_checker == spell_entry->priv->spell_checker)
+ return;
+
+ if (spell_entry->priv->spell_checker != NULL) {
+ g_signal_handler_disconnect (
+ spell_entry->priv->spell_checker,
+ spell_entry->priv->active_languages_handler_id);
+ g_object_unref (spell_entry->priv->spell_checker);
+ }
+
+ spell_entry->priv->spell_checker = g_object_ref (spell_checker);
+
+ handler_id = g_signal_connect (
+ spell_checker, "notify::active-languages",
+ G_CALLBACK (spell_entry_active_languages_cb),
+ spell_entry);
+
+ spell_entry->priv->active_languages_handler_id = handler_id;
+
+ g_object_notify (G_OBJECT (spell_entry), "spell-checker");
+
+ if (gtk_widget_get_realized (GTK_WIDGET (spell_entry)))
+ spell_entry_recheck_all (spell_entry);
+}
+
diff --git a/e-util/e-spell-entry.h b/e-util/e-spell-entry.h
index ed23cb0453..a07d68f177 100644
--- a/e-util/e-spell-entry.h
+++ b/e-util/e-spell-entry.h
@@ -24,6 +24,8 @@
#include <gtk/gtk.h>
+#include <e-util/e-util.h>
+
/* Standard GObject macros */
#define E_TYPE_SPELL_ENTRY \
(e_spell_entry_get_type ())
@@ -60,13 +62,14 @@ struct _ESpellEntryClass {
GType e_spell_entry_get_type (void) G_GNUC_CONST;
GtkWidget * e_spell_entry_new (void);
-void e_spell_entry_set_languages (ESpellEntry *spell_entry,
- GList *languages);
gboolean e_spell_entry_get_checking_enabled
(ESpellEntry *spell_entry);
void e_spell_entry_set_checking_enabled
(ESpellEntry *spell_entry,
gboolean enable_checking);
+ESpellChecker * e_spell_entry_get_spell_checker (ESpellEntry *spell_entry);
+void e_spell_entry_set_spell_checker (ESpellEntry *spell_entry,
+ ESpellChecker *spell_checker);
G_END_DECLS
diff --git a/e-util/e-util-enums.h b/e-util/e-util-enums.h
index 9913e4d938..736a901e7a 100644
--- a/e-util/e-util-enums.h
+++ b/e-util/e-util-enums.h
@@ -124,6 +124,227 @@ typedef enum {
E_DURATION_DAYS
} EDurationType;
+typedef enum {
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_NONE = 0,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H2,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H3,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H4,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H5,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PARAGRAPH,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_BLOCKQUOTE,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_PRE,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ADDRESS,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ROMAN,
+ E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA
+} EHTMLEditorSelectionBlockFormat;
+
+/* The values match the actual size in <font size="n"> */
+typedef enum {
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_TINY = 1,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_SMALL = 2,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_NORMAL = 3,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_BIG = 4,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_BIGGER = 5,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_LARGE = 6,
+ E_HTML_EDITOR_SELECTION_FONT_SIZE_VERY_LARGE = 7
+} EHTMLEditorSelectionFontSize;
+
+typedef enum {
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT,
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER,
+ E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT
+} EHTMLEditorSelectionAlignment;
+
+typedef enum {
+ E_HTML_EDITOR_SELECTION_GRANULARITY_CHARACTER,
+ E_HTML_EDITOR_SELECTION_GRANULARITY_WORD
+} EHTMLEditorSelectionGranularity;
+
+/**
+ * EHTMLEditorViewCommand:
+ * @E_HTML_EDITOR_VIEW_COMMAND_BACKGROUND_COLOR:
+ * Sets background color to given value.
+ * @E_HTML_EDITOR_VIEW_COMMAND_BOLD:
+ * Toggles bold formatting of current selection.
+ * @E_HTML_EDITOR_VIEW_COMMAND_COPY:
+ * Copies current selection to clipboard.
+ * @E_HTML_EDITOR_VIEW_COMMAND_CREATE_LINK:
+ * Converts current selection to a link that points to URL in value
+ * @E_HTML_EDITOR_VIEW_COMMAND_CUT:
+ * Cuts current selection to clipboard.
+ * @E_HTML_EDITOR_VIEW_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR:
+ * (XXX Explain me!)
+ * @E_HTML_EDITOR_VIEW_COMMAND_DELETE:
+ * Deletes current selection.
+ * @E_HTML_EDITOR_VIEW_COMMAND_FIND_STRING:
+ * Highlights given string.
+ * @E_HTML_EDITOR_VIEW_COMMAND_FONT_NAME:
+ * Sets font name to given value.
+ * @E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE:
+ * Sets font point size to given value (no units, just number)
+ * @E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE_DELTA:
+ * Changes font size by given delta value (no units, just number)
+ * @E_HTML_EDITOR_VIEW_COMMAND_FORE_COLOR:
+ * Sets font color to given value
+ * @E_HTML_EDITOR_VIEW_COMMAND_FORMAT_BLOCK:
+ * Sets block type of current paragraph to given format. Allowed formats
+ * are "BLOCKQUOTE", "H1", "H2", "H3", "H4", "H5", "H6", "P", "PRE" and
+ * "ADDRESS".
+ * @E_HTML_EDITOR_VIEW_COMMAND_FORWARD_DELETE:
+ * (XXX Explain me!)
+ * @E_HTML_EDITOR_VIEW_COMMAND_HILITE_COLOR:
+ * Sets color in which results of "FindString" command should be
+ * highlighted to given value.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INDENT:
+ * Indents current paragraph by one level.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML:
+ * Inserts give HTML code into document.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INSERT_HORIZONTAL_RULE:
+ * Inserts a horizontal rule (&lt;HR&gt;) on current line.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INSERT_IMAGE:
+ * Inserts an image with given source file.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INSERT_LINE_BREAK:
+ * Breaks line at current cursor position.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT:
+ * Breaks citation at current cursor position.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INSERT_ORDERED_LIST:
+ * Creates an ordered list environment at current cursor position.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INSERT_PARAGRAPH:
+ * Inserts a new paragraph at current cursor position.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT:
+ * Inserts given text at current cursor position.
+ * @E_HTML_EDITOR_VIEW_COMMAND_INSERT_UNORDERED_LIST:
+ * Creates an undordered list environment at current cursor position.
+ * @E_HTML_EDITOR_VIEW_COMMAND_ITALIC:
+ * Toggles italic formatting of current selection.
+ * @E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_CENTER:
+ * Aligns current paragraph to center.
+ * @E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_FULL:
+ * Justifies current paragraph to block.
+ * @E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_NONE:
+ * Removes any justification or alignment of current paragraph.
+ * @E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_RIGHT:
+ * Aligns current paragraph to right.
+ * @E_HTML_EDITOR_VIEW_COMMAND_OUTDENT:
+ * Outdents current paragraph by one level.
+ * @E_HTML_EDITOR_VIEW_COMMAND_PASTE:
+ * Pastes clipboard content at current cursor position.
+ * @E_HTML_EDITOR_VIEW_COMMAND_PASTE_AND_MATCH_STYLE:
+ * Pastes clipboard content and matches its style to style at current
+ * cursor position.
+ * @E_HTML_EDITOR_VIEW_COMMAND_PASTE_AS_PLAIN_TEXT:
+ * Pastes clipboard content at current cursor position removing any HTML
+ * formatting.
+ * @E_HTML_EDITOR_VIEW_COMMAND_PRINT:
+ * Print current document.
+ * @E_HTML_EDITOR_VIEW_COMMAND_REDO:
+ * Redoes last action.
+ * @E_HTML_EDITOR_VIEW_COMMAND_REMOVE_FORMAT:
+ * Removes any formatting of current selection.
+ * @E_HTML_EDITOR_VIEW_COMMAND_SELECT_ALL:
+ * Extends selects to the entire document.
+ * @E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH:
+ * Toggles strikethrough formatting.
+ * @E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS:
+ * Toggles whether style should be defined in CSS "style" attribute of
+ * elements or whether to use deprecated &lt;FONT&gt; tags. Depends on
+ * whether given value is "true" or "false".
+ * @E_HTML_EDITOR_VIEW_COMMAND_SUBSCRIPT:
+ * Toggles subscript of current selection.
+ * @E_HTML_EDITOR_VIEW_COMMAND_SUPERSCRIPT:
+ * Toggles superscript of current selection.
+ * @E_HTML_EDITOR_VIEW_COMMAND_TRANSPOSE:
+ * (XXX Explain me!)
+ * @E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE:
+ * Toggles underline formatting of current selection.
+ * @E_HTML_EDITOR_VIEW_COMMAND_UNDO:
+ * Undoes last action.
+ * @E_HTML_EDITOR_VIEW_COMMAND_UNLINK:
+ * Removes active links (&lt;A&gt;) from current selection (if there's any).
+ * @E_HTML_EDITOR_VIEW_COMMAND_UNSELECT:
+ * Cancels current selection.
+ * @E_HTML_EDITOR_VIEW_COMMAND_USE_CSS:
+ * Whether to allow use of CSS or not depending on whether given value is
+ * "true" or "false".
+ *
+ * Specifies the DOM command to execute in e_editor_widget_exec_command().
+ * Some commands require value to be passed in, which is always stated in the
+ * documentation.
+ */
+typedef enum {
+ E_HTML_EDITOR_VIEW_COMMAND_BACKGROUND_COLOR,
+ E_HTML_EDITOR_VIEW_COMMAND_BOLD,
+ E_HTML_EDITOR_VIEW_COMMAND_COPY,
+ E_HTML_EDITOR_VIEW_COMMAND_CREATE_LINK,
+ E_HTML_EDITOR_VIEW_COMMAND_CUT,
+ E_HTML_EDITOR_VIEW_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR,
+ E_HTML_EDITOR_VIEW_COMMAND_DELETE,
+ E_HTML_EDITOR_VIEW_COMMAND_FIND_STRING,
+ E_HTML_EDITOR_VIEW_COMMAND_FONT_NAME,
+ E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE,
+ E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE_DELTA,
+ E_HTML_EDITOR_VIEW_COMMAND_FORE_COLOR,
+ E_HTML_EDITOR_VIEW_COMMAND_FORMAT_BLOCK,
+ E_HTML_EDITOR_VIEW_COMMAND_FORWARD_DELETE,
+ E_HTML_EDITOR_VIEW_COMMAND_HILITE_COLOR,
+ E_HTML_EDITOR_VIEW_COMMAND_INDENT,
+ E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML,
+ E_HTML_EDITOR_VIEW_COMMAND_INSERT_HORIZONTAL_RULE,
+ E_HTML_EDITOR_VIEW_COMMAND_INSERT_IMAGE,
+ E_HTML_EDITOR_VIEW_COMMAND_INSERT_LINE_BREAK,
+ E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT,
+ E_HTML_EDITOR_VIEW_COMMAND_INSERT_ORDERED_LIST,
+ E_HTML_EDITOR_VIEW_COMMAND_INSERT_PARAGRAPH,
+ E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT,
+ E_HTML_EDITOR_VIEW_COMMAND_INSERT_UNORDERED_LIST,
+ E_HTML_EDITOR_VIEW_COMMAND_ITALIC,
+ E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_CENTER,
+ E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_FULL,
+ E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_LEFT,
+ E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_NONE,
+ E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_RIGHT,
+ E_HTML_EDITOR_VIEW_COMMAND_OUTDENT,
+ E_HTML_EDITOR_VIEW_COMMAND_PASTE,
+ E_HTML_EDITOR_VIEW_COMMAND_PASTE_AND_MATCH_STYLE,
+ E_HTML_EDITOR_VIEW_COMMAND_PASTE_AS_PLAIN_TEXT,
+ E_HTML_EDITOR_VIEW_COMMAND_PRINT,
+ E_HTML_EDITOR_VIEW_COMMAND_REDO,
+ E_HTML_EDITOR_VIEW_COMMAND_REMOVE_FORMAT,
+ E_HTML_EDITOR_VIEW_COMMAND_SELECT_ALL,
+ E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH,
+ E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS,
+ E_HTML_EDITOR_VIEW_COMMAND_SUBSCRIPT,
+ E_HTML_EDITOR_VIEW_COMMAND_SUPERSCRIPT,
+ E_HTML_EDITOR_VIEW_COMMAND_TRANSPOSE,
+ E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE,
+ E_HTML_EDITOR_VIEW_COMMAND_UNDO,
+ E_HTML_EDITOR_VIEW_COMMAND_UNLINK,
+ E_HTML_EDITOR_VIEW_COMMAND_UNSELECT,
+ E_HTML_EDITOR_VIEW_COMMAND_USE_CSS
+} EHTMLEditorViewCommand;
+
+/**
+ * EImageLoadingPolicy:
+ * @E_IMAGE_LOADING_POLICY_NEVER:
+ * Never load images from a remote server.
+ * @E_IMAGE_LOADING_POLICY_SOMETIMES:
+ * Only load images from a remote server if the sender is a known contact.
+ * @E_IMAGE_LOADING_POLICY_ALWAYS:
+ * Always load images from a remote server.
+ *
+ * Policy for loading remote image URLs in email. Allowing images to be
+ * loaded from a remote server may have privacy implications.
+ **/
+typedef enum {
+ E_IMAGE_LOADING_POLICY_NEVER,
+ E_IMAGE_LOADING_POLICY_SOMETIMES,
+ E_IMAGE_LOADING_POLICY_ALWAYS
+} EImageLoadingPolicy;
+
G_END_DECLS
#endif /* E_UTIL_ENUMS_H */
diff --git a/e-util/e-util.h b/e-util/e-util.h
index 784858e99f..e9b06fe27c 100644
--- a/e-util/e-util.h
+++ b/e-util/e-util.h
@@ -81,6 +81,8 @@
#include <e-util/e-client-cache.h>
#include <e-util/e-client-combo-box.h>
#include <e-util/e-client-selector.h>
+#include <e-util/e-color-chooser-widget.h>
+#include <e-util/e-color-combo.h>
#include <e-util/e-config.h>
#include <e-util/e-contact-store.h>
#include <e-util/e-data-capture.h>
@@ -89,6 +91,11 @@
#include <e-util/e-destination-store.h>
#include <e-util/e-dialog-utils.h>
#include <e-util/e-dialog-widgets.h>
+#include <e-util/e-emoticon-action.h>
+#include <e-util/e-emoticon-chooser-menu.h>
+#include <e-util/e-emoticon-chooser.h>
+#include <e-util/e-emoticon-tool-button.h>
+#include <e-util/e-emoticon.h>
#include <e-util/e-event.h>
#include <e-util/e-file-request.h>
#include <e-util/e-file-utils.h>
@@ -103,9 +110,27 @@
#include <e-util/e-filter-part.h>
#include <e-util/e-filter-rule.h>
#include <e-util/e-focus-tracker.h>
+#include <e-util/e-html-editor-actions.h>
+#include <e-util/e-html-editor-cell-dialog.h>
+#include <e-util/e-html-editor-dialog.h>
+#include <e-util/e-html-editor-find-dialog.h>
+#include <e-util/e-html-editor-hrule-dialog.h>
+#include <e-util/e-html-editor-image-dialog.h>
+#include <e-util/e-html-editor-link-dialog.h>
+#include <e-util/e-html-editor-page-dialog.h>
+#include <e-util/e-html-editor-paragraph-dialog.h>
+#include <e-util/e-html-editor-replace-dialog.h>
+#include <e-util/e-html-editor-selection.h>
+#include <e-util/e-html-editor-spell-check-dialog.h>
+#include <e-util/e-html-editor-table-dialog.h>
+#include <e-util/e-html-editor-text-dialog.h>
+#include <e-util/e-html-editor-utils.h>
+#include <e-util/e-html-editor-view.h>
+#include <e-util/e-html-editor.h>
#include <e-util/e-html-utils.h>
#include <e-util/e-icon-factory.h>
#include <e-util/e-image-chooser.h>
+#include <e-util/e-image-chooser-dialog.h>
#include <e-util/e-import-assistant.h>
#include <e-util/e-import.h>
#include <e-util/e-interval-chooser.h>
@@ -220,7 +245,6 @@
#include <e-util/e-url-entry.h>
#include <e-util/e-util-enums.h>
#include <e-util/e-util-enumtypes.h>
-#include <e-util/e-web-view-gtkhtml.h>
#include <e-util/e-web-view-preview.h>
#include <e-util/e-web-view.h>
#include <e-util/e-widget-undo.h>
diff --git a/e-util/e-web-view-gtkhtml.c b/e-util/e-web-view-gtkhtml.c
deleted file mode 100644
index 7963a1cbf9..0000000000
--- a/e-util/e-web-view-gtkhtml.c
+++ /dev/null
@@ -1,2352 +0,0 @@
-/*
- * e-web-view-gtkhtml.c
- *
- * 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.
- *
- * 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, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include "e-web-view-gtkhtml.h"
-
-#include <string.h>
-#include <glib/gi18n-lib.h>
-
-#include <camel/camel.h>
-#include <libebackend/libebackend.h>
-
-#include "e-alert-dialog.h"
-#include "e-alert-sink.h"
-#include "e-misc-utils.h"
-#include "e-plugin-ui.h"
-#include "e-popup-action.h"
-#include "e-selectable.h"
-
-#define E_WEB_VIEW_GTKHTML_GET_PRIVATE(obj) \
- (G_TYPE_INSTANCE_GET_PRIVATE \
- ((obj), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTMLPrivate))
-
-typedef struct _EWebViewGtkHTMLRequest EWebViewGtkHTMLRequest;
-
-struct _EWebViewGtkHTMLPrivate {
- GList *requests;
- GtkUIManager *ui_manager;
- gchar *selected_uri;
- GdkPixbufAnimation *cursor_image;
-
- GtkAction *open_proxy;
- GtkAction *print_proxy;
- GtkAction *save_as_proxy;
-
- GtkTargetList *copy_target_list;
- GtkTargetList *paste_target_list;
-
- /* Lockdown Options */
- guint disable_printing : 1;
- guint disable_save_to_disk : 1;
-};
-
-struct _EWebViewGtkHTMLRequest {
- GFile *file;
- EWebViewGtkHTML *web_view;
- GCancellable *cancellable;
- GInputStream *input_stream;
- GtkHTMLStream *output_stream;
- gchar buffer[4096];
-};
-
-enum {
- PROP_0,
- PROP_ANIMATE,
- PROP_CARET_MODE,
- PROP_COPY_TARGET_LIST,
- PROP_DISABLE_PRINTING,
- PROP_DISABLE_SAVE_TO_DISK,
- PROP_EDITABLE,
- PROP_INLINE_SPELLING,
- PROP_MAGIC_LINKS,
- PROP_MAGIC_SMILEYS,
- PROP_OPEN_PROXY,
- PROP_PASTE_TARGET_LIST,
- PROP_PRINT_PROXY,
- PROP_SAVE_AS_PROXY,
- PROP_SELECTED_URI,
- PROP_CURSOR_IMAGE
-};
-
-enum {
- COPY_CLIPBOARD,
- CUT_CLIPBOARD,
- PASTE_CLIPBOARD,
- POPUP_EVENT,
- STATUS_MESSAGE,
- STOP_LOADING,
- UPDATE_ACTIONS,
- PROCESS_MAILTO,
- LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL];
-
-static const gchar *ui =
-"<ui>"
-" <popup name='context'>"
-" <menuitem action='copy-clipboard'/>"
-" <separator/>"
-" <placeholder name='custom-actions-1'>"
-" <menuitem action='open'/>"
-" <menuitem action='save-as'/>"
-" <menuitem action='http-open'/>"
-" <menuitem action='send-message'/>"
-" <menuitem action='print'/>"
-" </placeholder>"
-" <placeholder name='custom-actions-2'>"
-" <menuitem action='uri-copy'/>"
-" <menuitem action='mailto-copy'/>"
-" <menuitem action='image-copy'/>"
-" </placeholder>"
-" <placeholder name='custom-actions-3'/>"
-" <separator/>"
-" <menuitem action='select-all'/>"
-" </popup>"
-"</ui>";
-
-/* Forward Declarations */
-static void e_web_view_gtkhtml_alert_sink_init (EAlertSinkInterface *iface);
-static void e_web_view_gtkhtml_selectable_init (ESelectableInterface *iface);
-
-G_DEFINE_TYPE_WITH_CODE (
- EWebViewGtkHTML,
- e_web_view_gtkhtml,
- GTK_TYPE_HTML,
- G_IMPLEMENT_INTERFACE (
- E_TYPE_EXTENSIBLE, NULL)
- G_IMPLEMENT_INTERFACE (
- E_TYPE_ALERT_SINK,
- e_web_view_gtkhtml_alert_sink_init)
- G_IMPLEMENT_INTERFACE (
- E_TYPE_SELECTABLE,
- e_web_view_gtkhtml_selectable_init))
-
-static EWebViewGtkHTMLRequest *
-web_view_gtkhtml_request_new (EWebViewGtkHTML *web_view,
- const gchar *uri,
- GtkHTMLStream *stream)
-{
- EWebViewGtkHTMLRequest *request;
- GList *list;
-
- request = g_slice_new (EWebViewGtkHTMLRequest);
-
- /* Try to detect file paths posing as URIs. */
- if (*uri == '/')
- request->file = g_file_new_for_path (uri);
- else
- request->file = g_file_new_for_uri (uri);
-
- request->web_view = g_object_ref (web_view);
- request->cancellable = g_cancellable_new ();
- request->input_stream = NULL;
- request->output_stream = stream;
-
- list = request->web_view->priv->requests;
- list = g_list_prepend (list, request);
- request->web_view->priv->requests = list;
-
- return request;
-}
-
-static void
-web_view_gtkhtml_request_free (EWebViewGtkHTMLRequest *request)
-{
- GList *list;
-
- list = request->web_view->priv->requests;
- list = g_list_remove (list, request);
- request->web_view->priv->requests = list;
-
- g_object_unref (request->file);
- g_object_unref (request->web_view);
- g_object_unref (request->cancellable);
-
- if (request->input_stream != NULL)
- g_object_unref (request->input_stream);
-
- g_slice_free (EWebViewGtkHTMLRequest, request);
-}
-
-static void
-web_view_gtkhtml_request_cancel (EWebViewGtkHTMLRequest *request)
-{
- g_cancellable_cancel (request->cancellable);
-}
-
-static gboolean
-web_view_gtkhtml_request_check_for_error (EWebViewGtkHTMLRequest *request,
- GError *error)
-{
- GtkHTML *html;
- GtkHTMLStream *stream;
-
- if (error == NULL)
- return FALSE;
-
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
- /* use this error, but do not close the stream */
- g_error_free (error);
- return TRUE;
- }
-
- /* XXX Should we log errors that are not cancellations? */
-
- html = GTK_HTML (request->web_view);
- stream = request->output_stream;
-
- gtk_html_end (html, stream, GTK_HTML_STREAM_ERROR);
- web_view_gtkhtml_request_free (request);
- g_error_free (error);
-
- return TRUE;
-}
-
-static void
-web_view_gtkhtml_request_stream_read_cb (GInputStream *input_stream,
- GAsyncResult *result,
- EWebViewGtkHTMLRequest *request)
-{
- gssize bytes_read;
- GError *error = NULL;
-
- bytes_read = g_input_stream_read_finish (input_stream, result, &error);
-
- if (web_view_gtkhtml_request_check_for_error (request, error))
- return;
-
- if (bytes_read == 0) {
- gtk_html_end (
- GTK_HTML (request->web_view),
- request->output_stream, GTK_HTML_STREAM_OK);
- web_view_gtkhtml_request_free (request);
- return;
- }
-
- gtk_html_write (
- GTK_HTML (request->web_view),
- request->output_stream, request->buffer, bytes_read);
-
- g_input_stream_read_async (
- request->input_stream, request->buffer,
- sizeof (request->buffer), G_PRIORITY_DEFAULT,
- request->cancellable, (GAsyncReadyCallback)
- web_view_gtkhtml_request_stream_read_cb, request);
-}
-
-static void
-web_view_gtkhtml_request_read_cb (GFile *file,
- GAsyncResult *result,
- EWebViewGtkHTMLRequest *request)
-{
- GFileInputStream *input_stream;
- GError *error = NULL;
-
- /* Input stream might be NULL, so don't use cast macro. */
- input_stream = g_file_read_finish (file, result, &error);
- request->input_stream = (GInputStream *) input_stream;
-
- if (web_view_gtkhtml_request_check_for_error (request, error))
- return;
-
- g_input_stream_read_async (
- request->input_stream, request->buffer,
- sizeof (request->buffer), G_PRIORITY_DEFAULT,
- request->cancellable, (GAsyncReadyCallback)
- web_view_gtkhtml_request_stream_read_cb, request);
-}
-
-static void
-action_copy_clipboard_cb (GtkAction *action,
- EWebViewGtkHTML *web_view)
-{
- e_web_view_gtkhtml_copy_clipboard (web_view);
-}
-
-static void
-action_http_open_cb (GtkAction *action,
- EWebViewGtkHTML *web_view)
-{
- const gchar *uri;
- gpointer parent;
-
- parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
- parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
-
- uri = e_web_view_gtkhtml_get_selected_uri (web_view);
- g_return_if_fail (uri != NULL);
-
- e_show_uri (parent, uri);
-}
-
-static void
-action_mailto_copy_cb (GtkAction *action,
- EWebViewGtkHTML *web_view)
-{
- CamelURL *curl;
- CamelInternetAddress *inet_addr;
- GtkClipboard *clipboard;
- const gchar *uri;
- gchar *text;
-
- uri = e_web_view_gtkhtml_get_selected_uri (web_view);
- g_return_if_fail (uri != NULL);
-
- /* This should work because we checked it in update_actions(). */
- curl = camel_url_new (uri, NULL);
- g_return_if_fail (curl != NULL);
-
- inet_addr = camel_internet_address_new ();
- camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
- text = camel_address_format (CAMEL_ADDRESS (inet_addr));
- if (text == NULL || *text == '\0')
- text = g_strdup (uri + strlen ("mailto:"));
-
- g_object_unref (inet_addr);
- camel_url_free (curl);
-
- clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
- gtk_clipboard_set_text (clipboard, text, -1);
- gtk_clipboard_store (clipboard);
-
- clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
- gtk_clipboard_set_text (clipboard, text, -1);
- gtk_clipboard_store (clipboard);
-
- g_free (text);
-}
-
-static void
-action_select_all_cb (GtkAction *action,
- EWebViewGtkHTML *web_view)
-{
- e_web_view_gtkhtml_select_all (web_view);
-}
-
-static void
-action_send_message_cb (GtkAction *action,
- EWebViewGtkHTML *web_view)
-{
- const gchar *uri;
- gpointer parent;
- gboolean handled;
-
- parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
- parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
-
- uri = e_web_view_gtkhtml_get_selected_uri (web_view);
- g_return_if_fail (uri != NULL);
-
- handled = FALSE;
- g_signal_emit (web_view, signals[PROCESS_MAILTO], 0, uri, &handled);
-
- if (!handled)
- e_show_uri (parent, uri);
-}
-
-static void
-action_uri_copy_cb (GtkAction *action,
- EWebViewGtkHTML *web_view)
-{
- GtkClipboard *clipboard;
- const gchar *uri;
-
- clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
- uri = e_web_view_gtkhtml_get_selected_uri (web_view);
- g_return_if_fail (uri != NULL);
-
- gtk_clipboard_set_text (clipboard, uri, -1);
- gtk_clipboard_store (clipboard);
-}
-
-static void
-action_image_copy_cb (GtkAction *action,
- EWebViewGtkHTML *web_view)
-{
- GtkClipboard *clipboard;
- GdkPixbufAnimation *animation;
- GdkPixbuf *pixbuf;
-
- clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
- animation = e_web_view_gtkhtml_get_cursor_image (web_view);
- g_return_if_fail (animation != NULL);
-
- pixbuf = gdk_pixbuf_animation_get_static_image (animation);
- if (!pixbuf)
- return;
-
- gtk_clipboard_set_image (clipboard, pixbuf);
- gtk_clipboard_store (clipboard);
-}
-
-static GtkActionEntry uri_entries[] = {
-
- { "uri-copy",
- "edit-copy",
- N_("_Copy Link Location"),
- "<Control>c",
- N_("Copy the link to the clipboard"),
- G_CALLBACK (action_uri_copy_cb) }
-};
-
-static GtkActionEntry http_entries[] = {
-
- { "http-open",
- "emblem-web",
- N_("_Open Link in Browser"),
- NULL,
- N_("Open the link in a web browser"),
- G_CALLBACK (action_http_open_cb) }
-};
-
-static GtkActionEntry mailto_entries[] = {
-
- { "mailto-copy",
- "edit-copy",
- N_("_Copy Email Address"),
- "<Control>c",
- N_("Copy the email address to the clipboard"),
- G_CALLBACK (action_mailto_copy_cb) },
-
- { "send-message",
- "mail-message-new",
- N_("_Send New Message To..."),
- NULL,
- N_("Send a mail message to this address"),
- G_CALLBACK (action_send_message_cb) }
-};
-
-static GtkActionEntry image_entries[] = {
-
- { "image-copy",
- "edit-copy",
- N_("_Copy Image"),
- "<Control>c",
- N_("Copy the image to the clipboard"),
- G_CALLBACK (action_image_copy_cb) }
-};
-
-static GtkActionEntry selection_entries[] = {
-
- { "copy-clipboard",
- "edit-copy",
- N_("_Copy"),
- "<Control>c",
- N_("Copy the selection"),
- G_CALLBACK (action_copy_clipboard_cb) },
-};
-
-static GtkActionEntry standard_entries[] = {
-
- { "select-all",
- "edit-select-all",
- N_("Select _All"),
- NULL,
- N_("Select all text and images"),
- G_CALLBACK (action_select_all_cb) }
-};
-
-static gboolean
-web_view_gtkhtml_button_press_event_cb (EWebViewGtkHTML *web_view,
- GdkEventButton *event,
- GtkHTML *frame)
-{
- gboolean event_handled = FALSE;
- gchar *uri = NULL;
-
- if (event) {
- GdkPixbufAnimation *anim;
-
- if (frame == NULL)
- frame = GTK_HTML (web_view);
-
- anim = gtk_html_get_image_at (frame, event->x, event->y);
- e_web_view_gtkhtml_set_cursor_image (web_view, anim);
- if (anim != NULL)
- g_object_unref (anim);
- }
-
- if (event != NULL && event->button != 3)
- return FALSE;
-
- /* Only extract a URI if no selection is active. Selected text
- * implies the user is more likely to want to copy the selection
- * to the clipboard than open a link within the selection. */
- if (!e_web_view_gtkhtml_is_selection_active (web_view))
- uri = e_web_view_gtkhtml_extract_uri (web_view, event, frame);
-
- if (uri != NULL && g_str_has_prefix (uri, "##")) {
- g_free (uri);
- return FALSE;
- }
-
- g_signal_emit (
- web_view, signals[POPUP_EVENT], 0,
- event, uri, &event_handled);
-
- g_free (uri);
-
- return event_handled;
-}
-
-static void
-web_view_gtkhtml_menu_item_select_cb (EWebViewGtkHTML *web_view,
- GtkWidget *widget)
-{
- GtkAction *action;
- GtkActivatable *activatable;
- const gchar *tooltip;
-
- activatable = GTK_ACTIVATABLE (widget);
- action = gtk_activatable_get_related_action (activatable);
- tooltip = gtk_action_get_tooltip (action);
-
- if (tooltip == NULL)
- return;
-
- e_web_view_gtkhtml_status_message (web_view, tooltip);
-}
-
-static void
-web_view_gtkhtml_menu_item_deselect_cb (EWebViewGtkHTML *web_view)
-{
- e_web_view_gtkhtml_status_message (web_view, NULL);
-}
-
-static void
-web_view_gtkhtml_connect_proxy_cb (EWebViewGtkHTML *web_view,
- GtkAction *action,
- GtkWidget *proxy)
-{
- if (!GTK_IS_MENU_ITEM (proxy))
- return;
-
- g_signal_connect_swapped (
- proxy, "select",
- G_CALLBACK (web_view_gtkhtml_menu_item_select_cb), web_view);
-
- g_signal_connect_swapped (
- proxy, "deselect",
- G_CALLBACK (web_view_gtkhtml_menu_item_deselect_cb), web_view);
-}
-
-static void
-web_view_gtkhtml_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- switch (property_id) {
- case PROP_ANIMATE:
- e_web_view_gtkhtml_set_animate (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_boolean (value));
- return;
-
- case PROP_CARET_MODE:
- e_web_view_gtkhtml_set_caret_mode (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_boolean (value));
- return;
-
- case PROP_DISABLE_PRINTING:
- e_web_view_gtkhtml_set_disable_printing (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_boolean (value));
- return;
-
- case PROP_DISABLE_SAVE_TO_DISK:
- e_web_view_gtkhtml_set_disable_save_to_disk (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_boolean (value));
- return;
-
- case PROP_EDITABLE:
- e_web_view_gtkhtml_set_editable (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_boolean (value));
- return;
-
- case PROP_INLINE_SPELLING:
- e_web_view_gtkhtml_set_inline_spelling (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_boolean (value));
- return;
-
- case PROP_MAGIC_LINKS:
- e_web_view_gtkhtml_set_magic_links (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_boolean (value));
- return;
-
- case PROP_MAGIC_SMILEYS:
- e_web_view_gtkhtml_set_magic_smileys (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_boolean (value));
- return;
-
- case PROP_OPEN_PROXY:
- e_web_view_gtkhtml_set_open_proxy (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_object (value));
- return;
-
- case PROP_PRINT_PROXY:
- e_web_view_gtkhtml_set_print_proxy (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_object (value));
- return;
-
- case PROP_SAVE_AS_PROXY:
- e_web_view_gtkhtml_set_save_as_proxy (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_object (value));
- return;
-
- case PROP_SELECTED_URI:
- e_web_view_gtkhtml_set_selected_uri (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_string (value));
- return;
- case PROP_CURSOR_IMAGE:
- e_web_view_gtkhtml_set_cursor_image (
- E_WEB_VIEW_GTKHTML (object),
- g_value_get_object (value));
- return;
- }
-
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-}
-
-static void
-web_view_gtkhtml_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec)
-{
- switch (property_id) {
- case PROP_ANIMATE:
- g_value_set_boolean (
- value, e_web_view_gtkhtml_get_animate (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_CARET_MODE:
- g_value_set_boolean (
- value, e_web_view_gtkhtml_get_caret_mode (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_COPY_TARGET_LIST:
- g_value_set_boxed (
- value, e_web_view_gtkhtml_get_copy_target_list (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_DISABLE_PRINTING:
- g_value_set_boolean (
- value, e_web_view_gtkhtml_get_disable_printing (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_DISABLE_SAVE_TO_DISK:
- g_value_set_boolean (
- value, e_web_view_gtkhtml_get_disable_save_to_disk (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_EDITABLE:
- g_value_set_boolean (
- value, e_web_view_gtkhtml_get_editable (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_INLINE_SPELLING:
- g_value_set_boolean (
- value, e_web_view_gtkhtml_get_inline_spelling (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_MAGIC_LINKS:
- g_value_set_boolean (
- value, e_web_view_gtkhtml_get_magic_links (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_MAGIC_SMILEYS:
- g_value_set_boolean (
- value, e_web_view_gtkhtml_get_magic_smileys (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_OPEN_PROXY:
- g_value_set_object (
- value, e_web_view_gtkhtml_get_open_proxy (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_PASTE_TARGET_LIST:
- g_value_set_boxed (
- value, e_web_view_gtkhtml_get_paste_target_list (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_PRINT_PROXY:
- g_value_set_object (
- value, e_web_view_gtkhtml_get_print_proxy (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_SAVE_AS_PROXY:
- g_value_set_object (
- value, e_web_view_gtkhtml_get_save_as_proxy (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_SELECTED_URI:
- g_value_set_string (
- value, e_web_view_gtkhtml_get_selected_uri (
- E_WEB_VIEW_GTKHTML (object)));
- return;
-
- case PROP_CURSOR_IMAGE:
- g_value_set_object (
- value, e_web_view_gtkhtml_get_cursor_image (
- E_WEB_VIEW_GTKHTML (object)));
- return;
- }
-
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-}
-
-static void
-web_view_gtkhtml_dispose (GObject *object)
-{
- EWebViewGtkHTMLPrivate *priv;
-
- priv = E_WEB_VIEW_GTKHTML_GET_PRIVATE (object);
-
- if (priv->ui_manager != NULL) {
- g_object_unref (priv->ui_manager);
- priv->ui_manager = NULL;
- }
-
- if (priv->open_proxy != NULL) {
- g_object_unref (priv->open_proxy);
- priv->open_proxy = NULL;
- }
-
- if (priv->print_proxy != NULL) {
- g_object_unref (priv->print_proxy);
- priv->print_proxy = NULL;
- }
-
- if (priv->save_as_proxy != NULL) {
- g_object_unref (priv->save_as_proxy);
- priv->save_as_proxy = NULL;
- }
-
- if (priv->copy_target_list != NULL) {
- gtk_target_list_unref (priv->copy_target_list);
- priv->copy_target_list = NULL;
- }
-
- if (priv->paste_target_list != NULL) {
- gtk_target_list_unref (priv->paste_target_list);
- priv->paste_target_list = NULL;
- }
-
- if (priv->cursor_image != NULL) {
- g_object_unref (priv->cursor_image);
- priv->cursor_image = NULL;
- }
-
- /* Chain up to parent's dispose() method. */
- G_OBJECT_CLASS (e_web_view_gtkhtml_parent_class)->dispose (object);
-}
-
-static void
-web_view_gtkhtml_finalize (GObject *object)
-{
- EWebViewGtkHTMLPrivate *priv;
-
- priv = E_WEB_VIEW_GTKHTML_GET_PRIVATE (object);
-
- /* All URI requests should be complete or cancelled by now. */
- if (priv->requests != NULL)
- g_warning ("Finalizing EWebViewGtkHTML with active URI requests");
-
- g_free (priv->selected_uri);
-
- /* Chain up to parent's finalize() method. */
- G_OBJECT_CLASS (e_web_view_gtkhtml_parent_class)->finalize (object);
-}
-
-static void
-web_view_gtkhtml_constructed (GObject *object)
-{
-#ifndef G_OS_WIN32
- GSettings *settings;
-
- settings = g_settings_new ("org.gnome.desktop.lockdown");
-
- g_settings_bind (
- settings, "disable-printing",
- object, "disable-printing",
- G_SETTINGS_BIND_GET);
-
- g_settings_bind (
- settings, "disable-save-to-disk",
- object, "disable-save-to-disk",
- G_SETTINGS_BIND_GET);
-
- g_object_unref (settings);
-#endif
-
- /* Chain up to parent's constructed() method. */
- G_OBJECT_CLASS (e_web_view_gtkhtml_parent_class)->constructed (object);
-}
-
-static gboolean
-web_view_gtkhtml_button_press_event (GtkWidget *widget,
- GdkEventButton *event)
-{
- GtkWidgetClass *widget_class;
- EWebViewGtkHTML *web_view;
-
- web_view = E_WEB_VIEW_GTKHTML (widget);
-
- if (web_view_gtkhtml_button_press_event_cb (web_view, event, NULL))
- return TRUE;
-
- /* Chain up to parent's button_press_event() method. */
- widget_class = GTK_WIDGET_CLASS (e_web_view_gtkhtml_parent_class);
- return widget_class->button_press_event (widget, event);
-}
-
-static gboolean
-web_view_gtkhtml_scroll_event (GtkWidget *widget,
- GdkEventScroll *event)
-{
- if (event->state & GDK_CONTROL_MASK) {
- GdkScrollDirection direction = event->direction;
-
- if (direction == GDK_SCROLL_SMOOTH) {
- static gdouble total_delta_y = 0.0;
-
- total_delta_y += event->delta_y;
-
- if (total_delta_y >= 1.0) {
- total_delta_y = 0.0;
- direction = GDK_SCROLL_DOWN;
- } else if (total_delta_y <= -1.0) {
- total_delta_y = 0.0;
- direction = GDK_SCROLL_UP;
- } else {
- return FALSE;
- }
- }
-
- switch (direction) {
- case GDK_SCROLL_UP:
- gtk_html_zoom_in (GTK_HTML (widget));
- return TRUE;
- case GDK_SCROLL_DOWN:
- gtk_html_zoom_out (GTK_HTML (widget));
- return TRUE;
- default:
- break;
- }
- }
-
- return FALSE;
-}
-
-static void
-web_view_gtkhtml_url_requested (GtkHTML *html,
- const gchar *uri,
- GtkHTMLStream *stream)
-{
- EWebViewGtkHTMLRequest *request;
-
- request = web_view_gtkhtml_request_new (E_WEB_VIEW_GTKHTML (html), uri, stream);
-
- g_file_read_async (
- request->file, G_PRIORITY_DEFAULT,
- request->cancellable, (GAsyncReadyCallback)
- web_view_gtkhtml_request_read_cb, request);
-}
-
-static void
-web_view_gtkhtml_gtkhtml_link_clicked (GtkHTML *html,
- const gchar *uri)
-{
- EWebViewGtkHTMLClass *class;
- EWebViewGtkHTML *web_view;
-
- web_view = E_WEB_VIEW_GTKHTML (html);
-
- class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view);
- g_return_if_fail (class->link_clicked != NULL);
-
- class->link_clicked (web_view, uri);
-}
-
-static void
-web_view_gtkhtml_on_url (GtkHTML *html,
- const gchar *uri)
-{
- EWebViewGtkHTMLClass *class;
- EWebViewGtkHTML *web_view;
-
- web_view = E_WEB_VIEW_GTKHTML (html);
-
- class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view);
- g_return_if_fail (class->hovering_over_link != NULL);
-
- /* XXX WebKit would supply a title here. */
- class->hovering_over_link (web_view, NULL, uri);
-}
-
-static void
-web_view_gtkhtml_iframe_created (GtkHTML *html,
- GtkHTML *iframe)
-{
- g_signal_connect_swapped (
- iframe, "button-press-event",
- G_CALLBACK (web_view_gtkhtml_button_press_event_cb), html);
-}
-
-static gchar *
-web_view_gtkhtml_extract_uri (EWebViewGtkHTML *web_view,
- GdkEventButton *event,
- GtkHTML *html)
-{
- gchar *uri;
-
- if (event != NULL)
- uri = gtk_html_get_url_at (html, event->x, event->y);
- else
- uri = gtk_html_get_cursor_url (html);
-
- return uri;
-}
-
-static void
-web_view_gtkhtml_hovering_over_link (EWebViewGtkHTML *web_view,
- const gchar *title,
- const gchar *uri)
-{
- CamelInternetAddress *address;
- CamelURL *curl;
- const gchar *format = NULL;
- gchar *message = NULL;
- gchar *who;
-
- if (uri == NULL || *uri == '\0')
- goto exit;
-
- if (g_str_has_prefix (uri, "mailto:"))
- format = _("Click to mail %s");
- else if (g_str_has_prefix (uri, "callto:") ||
- g_str_has_prefix (uri, "h323:") ||
- g_str_has_prefix (uri, "sip:") ||
- g_str_has_prefix (uri, "tel:"))
- format = _("Click to call %s");
- else if (g_str_has_prefix (uri, "##"))
- message = g_strdup (_("Click to hide/unhide addresses"));
- else
- message = g_strdup_printf (_("Click to open %s"), uri);
-
- if (format == NULL)
- goto exit;
-
- /* XXX Use something other than Camel here. Surely
- * there's other APIs around that can do this. */
- curl = camel_url_new (uri, NULL);
- address = camel_internet_address_new ();
- camel_address_decode (CAMEL_ADDRESS (address), curl->path);
- who = camel_address_format (CAMEL_ADDRESS (address));
- g_object_unref (address);
- camel_url_free (curl);
-
- if (who == NULL)
- who = g_strdup (strchr (uri, ':') + 1);
-
- message = g_strdup_printf (format, who);
-
- g_free (who);
-
-exit:
- e_web_view_gtkhtml_status_message (web_view, message);
-
- g_free (message);
-}
-
-static void
-web_view_gtkhtml_link_clicked (EWebViewGtkHTML *web_view,
- const gchar *uri)
-{
- gpointer parent;
-
- parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
- parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
-
- e_show_uri (parent, uri);
-}
-
-static void
-web_view_gtkhtml_load_string (EWebViewGtkHTML *web_view,
- const gchar *string)
-{
- if (string != NULL && *string != '\0')
- gtk_html_load_from_string (GTK_HTML (web_view), string, -1);
- else
- e_web_view_gtkhtml_clear (web_view);
-}
-
-static void
-web_view_gtkhtml_copy_clipboard (EWebViewGtkHTML *web_view)
-{
- gtk_html_command (GTK_HTML (web_view), "copy");
-}
-
-static void
-web_view_gtkhtml_cut_clipboard (EWebViewGtkHTML *web_view)
-{
- if (e_web_view_gtkhtml_get_editable (web_view))
- gtk_html_command (GTK_HTML (web_view), "cut");
-}
-
-static void
-web_view_gtkhtml_paste_clipboard (EWebViewGtkHTML *web_view)
-{
- if (e_web_view_gtkhtml_get_editable (web_view))
- gtk_html_command (GTK_HTML (web_view), "paste");
-}
-
-static gboolean
-web_view_gtkhtml_popup_event (EWebViewGtkHTML *web_view,
- GdkEventButton *event,
- const gchar *uri)
-{
- e_web_view_gtkhtml_set_selected_uri (web_view, uri);
- e_web_view_gtkhtml_show_popup_menu (web_view, event, NULL, NULL);
-
- return TRUE;
-}
-
-static void
-web_view_gtkhtml_stop_loading (EWebViewGtkHTML *web_view)
-{
- g_list_foreach (
- web_view->priv->requests, (GFunc)
- web_view_gtkhtml_request_cancel, NULL);
-
- gtk_html_stop (GTK_HTML (web_view));
-}
-
-static void
-web_view_gtkhtml_update_actions (EWebViewGtkHTML *web_view)
-{
- GtkActionGroup *action_group;
- gboolean have_selection;
- gboolean scheme_is_http = FALSE;
- gboolean scheme_is_mailto = FALSE;
- gboolean uri_is_valid = FALSE;
- gboolean has_cursor_image;
- gboolean visible;
- const gchar *group_name;
- const gchar *uri;
-
- uri = e_web_view_gtkhtml_get_selected_uri (web_view);
- have_selection = e_web_view_gtkhtml_is_selection_active (web_view);
- has_cursor_image = e_web_view_gtkhtml_get_cursor_image (web_view) != NULL;
-
- /* Parse the URI early so we know if the actions will work. */
- if (uri != NULL) {
- CamelURL *curl;
-
- curl = camel_url_new (uri, NULL);
- uri_is_valid = (curl != NULL);
- camel_url_free (curl);
-
- scheme_is_http =
- (g_ascii_strncasecmp (uri, "http:", 5) == 0) ||
- (g_ascii_strncasecmp (uri, "https:", 6) == 0);
-
- scheme_is_mailto =
- (g_ascii_strncasecmp (uri, "mailto:", 7) == 0);
- }
-
- /* Allow copying the URI even if it's malformed. */
- group_name = "uri";
- visible = (uri != NULL) && !scheme_is_mailto;
- action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
- gtk_action_group_set_visible (action_group, visible);
-
- group_name = "http";
- visible = uri_is_valid && scheme_is_http;
- action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
- gtk_action_group_set_visible (action_group, visible);
-
- group_name = "mailto";
- visible = uri_is_valid && scheme_is_mailto;
- action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
- gtk_action_group_set_visible (action_group, visible);
-
- group_name = "image";
- visible = has_cursor_image;
- action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
- gtk_action_group_set_visible (action_group, visible);
-
- group_name = "selection";
- visible = have_selection;
- action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
- gtk_action_group_set_visible (action_group, visible);
-
- group_name = "standard";
- visible = (uri == NULL);
- action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
- gtk_action_group_set_visible (action_group, visible);
-
- group_name = "lockdown-printing";
- visible = (uri == NULL) && !web_view->priv->disable_printing;
- action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
- gtk_action_group_set_visible (action_group, visible);
-
- group_name = "lockdown-save-to-disk";
- visible = (uri == NULL) && !web_view->priv->disable_save_to_disk;
- action_group = e_web_view_gtkhtml_get_action_group (web_view, group_name);
- gtk_action_group_set_visible (action_group, visible);
-}
-
-static void
-web_view_gtkhtml_submit_alert (EAlertSink *alert_sink,
- EAlert *alert)
-{
- GtkIconInfo *icon_info;
- EWebViewGtkHTML *web_view;
- GtkWidget *dialog;
- GString *buffer;
- const gchar *icon_name = NULL;
- const gchar *filename;
- gpointer parent;
- gchar *icon_uri;
- gint size = 0;
- GError *error = NULL;
-
- web_view = E_WEB_VIEW_GTKHTML (alert_sink);
-
- parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
- parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
-
- /* We use equivalent named icons instead of stock IDs,
- * since it's easier to get the filename of the icon. */
- switch (e_alert_get_message_type (alert)) {
- case GTK_MESSAGE_INFO:
- icon_name = "dialog-information";
- break;
-
- case GTK_MESSAGE_WARNING:
- icon_name = "dialog-warning";
- break;
-
- case GTK_MESSAGE_ERROR:
- icon_name = "dialog-error";
- break;
-
- default:
- dialog = e_alert_dialog_new (parent, alert);
- gtk_dialog_run (GTK_DIALOG (dialog));
- gtk_widget_destroy (dialog);
- return;
- }
-
- gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &size, NULL);
-
- icon_info = gtk_icon_theme_lookup_icon (
- gtk_icon_theme_get_default (),
- icon_name, size, GTK_ICON_LOOKUP_NO_SVG);
- g_return_if_fail (icon_info != NULL);
-
- filename = gtk_icon_info_get_filename (icon_info);
- icon_uri = g_filename_to_uri (filename, NULL, &error);
-
- if (error != NULL) {
- g_warning ("%s", error->message);
- g_clear_error (&error);
- }
-
- buffer = g_string_sized_new (512);
-
- g_string_append (
- buffer,
- "<html>"
- "<head>"
- "<meta http-equiv=\"content-type\""
- " content=\"text/html; charset=utf-8\">"
- "</head>"
- "<body>");
-
- g_string_append (
- buffer,
- "<table bgcolor='#000000' width='100%'"
- " cellpadding='1' cellspacing='0'>"
- "<tr>"
- "<td>"
- "<table bgcolor='#dddddd' width='100%' cellpadding='6'>"
- "<tr>");
-
- g_string_append_printf (
- buffer,
- "<tr>"
- "<td valign='top'>"
- "<img src='%s'/>"
- "</td>"
- "<td align='left' width='100%%'>"
- "<h3>%s</h3>"
- "%s"
- "</td>"
- "</tr>",
- icon_uri,
- e_alert_get_primary_text (alert),
- e_alert_get_secondary_text (alert));
-
- g_string_append (
- buffer,
- "</table>"
- "</td>"
- "</tr>"
- "</table>"
- "</body>"
- "</html>");
-
- e_web_view_gtkhtml_load_string (web_view, buffer->str);
-
- g_string_free (buffer, TRUE);
-
- gtk_icon_info_free (icon_info);
- g_free (icon_uri);
-}
-
-static void
-web_view_gtkhtml_selectable_update_actions (ESelectable *selectable,
- EFocusTracker *focus_tracker,
- GdkAtom *clipboard_targets,
- gint n_clipboard_targets)
-{
- EWebViewGtkHTML *web_view;
- GtkAction *action;
- /*GtkTargetList *target_list;*/
- gboolean can_paste = FALSE;
- gboolean editable;
- gboolean have_selection;
- gboolean sensitive;
- const gchar *tooltip;
- /*gint ii;*/
-
- web_view = E_WEB_VIEW_GTKHTML (selectable);
- editable = e_web_view_gtkhtml_get_editable (web_view);
- have_selection = e_web_view_gtkhtml_is_selection_active (web_view);
-
- /* XXX GtkHtml implements its own clipboard instead of using
- * GDK_SELECTION_CLIPBOARD, so we don't get notifications
- * when the clipboard contents change. The logic below
- * is what we would do if GtkHtml worked properly.
- * Instead, we need to keep the Paste action sensitive so
- * its accelerator overrides GtkHtml's key binding. */
-#if 0
- target_list = e_selectable_get_paste_target_list (selectable);
- for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
- can_paste = gtk_target_list_find (
- target_list, clipboard_targets[ii], NULL);
-#endif
- can_paste = TRUE;
-
- action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
- sensitive = editable && have_selection;
- tooltip = _("Cut the selection");
- gtk_action_set_sensitive (action, sensitive);
- gtk_action_set_tooltip (action, tooltip);
-
- action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
- sensitive = have_selection;
- tooltip = _("Copy the selection");
- gtk_action_set_sensitive (action, sensitive);
- gtk_action_set_tooltip (action, tooltip);
-
- action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
- sensitive = editable && can_paste;
- tooltip = _("Paste the clipboard");
- gtk_action_set_sensitive (action, sensitive);
- gtk_action_set_tooltip (action, tooltip);
-
- action = e_focus_tracker_get_select_all_action (focus_tracker);
- sensitive = TRUE;
- tooltip = _("Select all text and images");
- gtk_action_set_sensitive (action, sensitive);
- gtk_action_set_tooltip (action, tooltip);
-}
-
-static void
-web_view_gtkhtml_selectable_cut_clipboard (ESelectable *selectable)
-{
- e_web_view_gtkhtml_cut_clipboard (E_WEB_VIEW_GTKHTML (selectable));
-}
-
-static void
-web_view_gtkhtml_selectable_copy_clipboard (ESelectable *selectable)
-{
- e_web_view_gtkhtml_copy_clipboard (E_WEB_VIEW_GTKHTML (selectable));
-}
-
-static void
-web_view_gtkhtml_selectable_paste_clipboard (ESelectable *selectable)
-{
- e_web_view_gtkhtml_paste_clipboard (E_WEB_VIEW_GTKHTML (selectable));
-}
-
-static void
-web_view_gtkhtml_selectable_select_all (ESelectable *selectable)
-{
- e_web_view_gtkhtml_select_all (E_WEB_VIEW_GTKHTML (selectable));
-}
-
-static void
-e_web_view_gtkhtml_class_init (EWebViewGtkHTMLClass *class)
-{
- GObjectClass *object_class;
- GtkWidgetClass *widget_class;
- GtkHTMLClass *html_class;
-
- g_type_class_add_private (class, sizeof (EWebViewGtkHTMLPrivate));
-
- object_class = G_OBJECT_CLASS (class);
- object_class->set_property = web_view_gtkhtml_set_property;
- object_class->get_property = web_view_gtkhtml_get_property;
- object_class->dispose = web_view_gtkhtml_dispose;
- object_class->finalize = web_view_gtkhtml_finalize;
- object_class->constructed = web_view_gtkhtml_constructed;
-
- widget_class = GTK_WIDGET_CLASS (class);
- widget_class->button_press_event = web_view_gtkhtml_button_press_event;
- widget_class->scroll_event = web_view_gtkhtml_scroll_event;
-
- html_class = GTK_HTML_CLASS (class);
- html_class->url_requested = web_view_gtkhtml_url_requested;
- html_class->link_clicked = web_view_gtkhtml_gtkhtml_link_clicked;
- html_class->on_url = web_view_gtkhtml_on_url;
- html_class->iframe_created = web_view_gtkhtml_iframe_created;
-
- class->extract_uri = web_view_gtkhtml_extract_uri;
- class->hovering_over_link = web_view_gtkhtml_hovering_over_link;
- class->link_clicked = web_view_gtkhtml_link_clicked;
- class->load_string = web_view_gtkhtml_load_string;
- class->copy_clipboard = web_view_gtkhtml_copy_clipboard;
- class->cut_clipboard = web_view_gtkhtml_cut_clipboard;
- class->paste_clipboard = web_view_gtkhtml_paste_clipboard;
- class->popup_event = web_view_gtkhtml_popup_event;
- class->stop_loading = web_view_gtkhtml_stop_loading;
- class->update_actions = web_view_gtkhtml_update_actions;
-
- g_object_class_install_property (
- object_class,
- PROP_ANIMATE,
- g_param_spec_boolean (
- "animate",
- "Animate Images",
- NULL,
- FALSE,
- G_PARAM_READWRITE));
-
- g_object_class_install_property (
- object_class,
- PROP_CARET_MODE,
- g_param_spec_boolean (
- "caret-mode",
- "Caret Mode",
- NULL,
- FALSE,
- G_PARAM_READWRITE));
-
- /* Inherited from ESelectableInterface */
- g_object_class_override_property (
- object_class,
- PROP_COPY_TARGET_LIST,
- "copy-target-list");
-
- g_object_class_install_property (
- object_class,
- PROP_DISABLE_PRINTING,
- g_param_spec_boolean (
- "disable-printing",
- "Disable Printing",
- NULL,
- FALSE,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT));
-
- g_object_class_install_property (
- object_class,
- PROP_DISABLE_SAVE_TO_DISK,
- g_param_spec_boolean (
- "disable-save-to-disk",
- "Disable Save-to-Disk",
- NULL,
- FALSE,
- G_PARAM_READWRITE |
- G_PARAM_CONSTRUCT));
-
- g_object_class_install_property (
- object_class,
- PROP_EDITABLE,
- g_param_spec_boolean (
- "editable",
- "Editable",
- NULL,
- FALSE,
- G_PARAM_READWRITE));
-
- g_object_class_install_property (
- object_class,
- PROP_INLINE_SPELLING,
- g_param_spec_boolean (
- "inline-spelling",
- "Inline Spelling",
- NULL,
- FALSE,
- G_PARAM_READWRITE));
-
- g_object_class_install_property (
- object_class,
- PROP_MAGIC_LINKS,
- g_param_spec_boolean (
- "magic-links",
- "Magic Links",
- NULL,
- FALSE,
- G_PARAM_READWRITE));
-
- g_object_class_install_property (
- object_class,
- PROP_MAGIC_SMILEYS,
- g_param_spec_boolean (
- "magic-smileys",
- "Magic Smileys",
- NULL,
- FALSE,
- G_PARAM_READWRITE));
-
- g_object_class_install_property (
- object_class,
- PROP_OPEN_PROXY,
- g_param_spec_object (
- "open-proxy",
- "Open Proxy",
- NULL,
- GTK_TYPE_ACTION,
- G_PARAM_READWRITE));
-
- /* Inherited from ESelectableInterface */
- g_object_class_override_property (
- object_class,
- PROP_PASTE_TARGET_LIST,
- "paste-target-list");
-
- g_object_class_install_property (
- object_class,
- PROP_PRINT_PROXY,
- g_param_spec_object (
- "print-proxy",
- "Print Proxy",
- NULL,
- GTK_TYPE_ACTION,
- G_PARAM_READWRITE));
-
- g_object_class_install_property (
- object_class,
- PROP_SAVE_AS_PROXY,
- g_param_spec_object (
- "save-as-proxy",
- "Save As Proxy",
- NULL,
- GTK_TYPE_ACTION,
- G_PARAM_READWRITE));
-
- g_object_class_install_property (
- object_class,
- PROP_SELECTED_URI,
- g_param_spec_string (
- "selected-uri",
- "Selected URI",
- NULL,
- NULL,
- G_PARAM_READWRITE));
-
- g_object_class_install_property (
- object_class,
- PROP_CURSOR_IMAGE,
- g_param_spec_object (
- "cursor-image",
- "Image animation at the mouse cursor",
- NULL,
- GDK_TYPE_PIXBUF_ANIMATION,
- G_PARAM_READWRITE));
-
- signals[COPY_CLIPBOARD] = g_signal_new (
- "copy-clipboard",
- G_TYPE_FROM_CLASS (class),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_STRUCT_OFFSET (EWebViewGtkHTMLClass, copy_clipboard),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-
- signals[CUT_CLIPBOARD] = g_signal_new (
- "cut-clipboard",
- G_TYPE_FROM_CLASS (class),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_STRUCT_OFFSET (EWebViewGtkHTMLClass, cut_clipboard),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-
- signals[PASTE_CLIPBOARD] = g_signal_new (
- "paste-clipboard",
- G_TYPE_FROM_CLASS (class),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_STRUCT_OFFSET (EWebViewGtkHTMLClass, paste_clipboard),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-
- signals[POPUP_EVENT] = g_signal_new (
- "popup-event",
- G_TYPE_FROM_CLASS (class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (EWebViewGtkHTMLClass, popup_event),
- g_signal_accumulator_true_handled, NULL,
- e_marshal_BOOLEAN__BOXED_STRING,
- G_TYPE_BOOLEAN, 2,
- GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE,
- G_TYPE_STRING);
-
- signals[STATUS_MESSAGE] = g_signal_new (
- "status-message",
- G_TYPE_FROM_CLASS (class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (EWebViewGtkHTMLClass, status_message),
- NULL, NULL,
- g_cclosure_marshal_VOID__STRING,
- G_TYPE_NONE, 1,
- G_TYPE_STRING);
-
- signals[STOP_LOADING] = g_signal_new (
- "stop-loading",
- G_TYPE_FROM_CLASS (class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (EWebViewGtkHTMLClass, stop_loading),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-
- signals[UPDATE_ACTIONS] = g_signal_new (
- "update-actions",
- G_TYPE_FROM_CLASS (class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (EWebViewGtkHTMLClass, update_actions),
- NULL, NULL,
- g_cclosure_marshal_VOID__VOID,
- G_TYPE_NONE, 0);
-
- /* return TRUE when a signal handler processed the mailto URI */
- signals[PROCESS_MAILTO] = g_signal_new (
- "process-mailto",
- G_TYPE_FROM_CLASS (class),
- G_SIGNAL_RUN_LAST,
- G_STRUCT_OFFSET (EWebViewGtkHTMLClass, process_mailto),
- NULL, NULL,
- e_marshal_BOOLEAN__STRING,
- G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
-}
-
-static void
-e_web_view_gtkhtml_alert_sink_init (EAlertSinkInterface *iface)
-{
- iface->submit_alert = web_view_gtkhtml_submit_alert;
-}
-
-static void
-e_web_view_gtkhtml_selectable_init (ESelectableInterface *iface)
-{
- iface->update_actions = web_view_gtkhtml_selectable_update_actions;
- iface->cut_clipboard = web_view_gtkhtml_selectable_cut_clipboard;
- iface->copy_clipboard = web_view_gtkhtml_selectable_copy_clipboard;
- iface->paste_clipboard = web_view_gtkhtml_selectable_paste_clipboard;
- iface->select_all = web_view_gtkhtml_selectable_select_all;
-}
-
-static void
-e_web_view_gtkhtml_init (EWebViewGtkHTML *web_view)
-{
- GtkUIManager *ui_manager;
- GtkActionGroup *action_group;
- GtkTargetList *target_list;
- EPopupAction *popup_action;
- const gchar *domain = GETTEXT_PACKAGE;
- const gchar *id;
- GError *error = NULL;
-
- web_view->priv = E_WEB_VIEW_GTKHTML_GET_PRIVATE (web_view);
-
- ui_manager = gtk_ui_manager_new ();
- web_view->priv->ui_manager = ui_manager;
-
- g_signal_connect_swapped (
- ui_manager, "connect-proxy",
- G_CALLBACK (web_view_gtkhtml_connect_proxy_cb), web_view);
-
- target_list = gtk_target_list_new (NULL, 0);
- web_view->priv->copy_target_list = target_list;
-
- target_list = gtk_target_list_new (NULL, 0);
- web_view->priv->paste_target_list = target_list;
-
- action_group = gtk_action_group_new ("uri");
- gtk_action_group_set_translation_domain (action_group, domain);
- gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
- g_object_unref (action_group);
-
- gtk_action_group_add_actions (
- action_group, uri_entries,
- G_N_ELEMENTS (uri_entries), web_view);
-
- action_group = gtk_action_group_new ("http");
- gtk_action_group_set_translation_domain (action_group, domain);
- gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
- g_object_unref (action_group);
-
- gtk_action_group_add_actions (
- action_group, http_entries,
- G_N_ELEMENTS (http_entries), web_view);
-
- action_group = gtk_action_group_new ("mailto");
- gtk_action_group_set_translation_domain (action_group, domain);
- gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
- g_object_unref (action_group);
-
- gtk_action_group_add_actions (
- action_group, mailto_entries,
- G_N_ELEMENTS (mailto_entries), web_view);
-
- action_group = gtk_action_group_new ("image");
- gtk_action_group_set_translation_domain (action_group, domain);
- gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
- g_object_unref (action_group);
-
- gtk_action_group_add_actions (
- action_group, image_entries,
- G_N_ELEMENTS (image_entries), web_view);
-
- action_group = gtk_action_group_new ("selection");
- gtk_action_group_set_translation_domain (action_group, domain);
- gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
- g_object_unref (action_group);
-
- gtk_action_group_add_actions (
- action_group, selection_entries,
- G_N_ELEMENTS (selection_entries), web_view);
-
- action_group = gtk_action_group_new ("standard");
- gtk_action_group_set_translation_domain (action_group, domain);
- gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
- g_object_unref (action_group);
-
- gtk_action_group_add_actions (
- action_group, standard_entries,
- G_N_ELEMENTS (standard_entries), web_view);
-
- popup_action = e_popup_action_new ("open");
- gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
- g_object_unref (popup_action);
-
- g_object_bind_property (
- web_view, "open-proxy",
- popup_action, "related-action",
- G_BINDING_BIDIRECTIONAL |
- G_BINDING_SYNC_CREATE);
-
- /* Support lockdown. */
-
- action_group = gtk_action_group_new ("lockdown-printing");
- gtk_action_group_set_translation_domain (action_group, domain);
- gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
- g_object_unref (action_group);
-
- popup_action = e_popup_action_new ("print");
- gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
- g_object_unref (popup_action);
-
- g_object_bind_property (
- web_view, "print-proxy",
- popup_action, "related-action",
- G_BINDING_BIDIRECTIONAL |
- G_BINDING_SYNC_CREATE);
-
- action_group = gtk_action_group_new ("lockdown-save-to-disk");
- gtk_action_group_set_translation_domain (action_group, domain);
- gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
- g_object_unref (action_group);
-
- popup_action = e_popup_action_new ("save-as");
- gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
- g_object_unref (popup_action);
-
- g_object_bind_property (
- web_view, "save-as-proxy",
- popup_action, "related-action",
- G_BINDING_BIDIRECTIONAL |
- G_BINDING_SYNC_CREATE);
-
- /* Because we are loading from a hard-coded string, there is
- * no chance of I/O errors. Failure here implies a malformed
- * UI definition. Full stop. */
- gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
- if (error != NULL)
- g_error ("%s", error->message);
-
- id = "org.gnome.evolution.webview";
- e_plugin_ui_register_manager (ui_manager, id, web_view);
- e_plugin_ui_enable_manager (ui_manager, id);
-
- e_extensible_load_extensions (E_EXTENSIBLE (web_view));
-}
-
-GtkWidget *
-e_web_view_gtkhtml_new (void)
-{
- return g_object_new (E_TYPE_WEB_VIEW_GTKHTML, NULL);
-}
-
-void
-e_web_view_gtkhtml_clear (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- gtk_html_load_empty (GTK_HTML (web_view));
-}
-
-void
-e_web_view_gtkhtml_load_string (EWebViewGtkHTML *web_view,
- const gchar *string)
-{
- EWebViewGtkHTMLClass *class;
-
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view);
- g_return_if_fail (class->load_string != NULL);
-
- class->load_string (web_view, string);
-}
-
-gboolean
-e_web_view_gtkhtml_get_animate (EWebViewGtkHTML *web_view)
-{
- /* XXX This is just here to maintain symmetry
- * with e_web_view_set_animate(). */
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return gtk_html_get_animate (GTK_HTML (web_view));
-}
-
-void
-e_web_view_gtkhtml_set_animate (EWebViewGtkHTML *web_view,
- gboolean animate)
-{
- /* XXX GtkHTML does not utilize GObject properties as well
- * as it could. This just wraps gtk_html_set_animate()
- * so we can get a "notify::animate" signal. */
-
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (gtk_html_get_animate (GTK_HTML (web_view)) == animate)
- return;
-
- gtk_html_set_animate (GTK_HTML (web_view), animate);
-
- g_object_notify (G_OBJECT (web_view), "animate");
-}
-
-gboolean
-e_web_view_gtkhtml_get_caret_mode (EWebViewGtkHTML *web_view)
-{
- /* XXX This is just here to maintain symmetry
- * with e_web_view_set_caret_mode(). */
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return gtk_html_get_caret_mode (GTK_HTML (web_view));
-}
-
-void
-e_web_view_gtkhtml_set_caret_mode (EWebViewGtkHTML *web_view,
- gboolean caret_mode)
-{
- /* XXX GtkHTML does not utilize GObject properties as well
- * as it could. This just wraps gtk_html_set_caret_mode()
- * so we can get a "notify::caret-mode" signal. */
-
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (gtk_html_get_caret_mode (GTK_HTML (web_view)) == caret_mode)
- return;
-
- gtk_html_set_caret_mode (GTK_HTML (web_view), caret_mode);
-
- g_object_notify (G_OBJECT (web_view), "caret-mode");
-}
-
-GtkTargetList *
-e_web_view_gtkhtml_get_copy_target_list (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- return web_view->priv->copy_target_list;
-}
-
-gboolean
-e_web_view_gtkhtml_get_disable_printing (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return web_view->priv->disable_printing;
-}
-
-void
-e_web_view_gtkhtml_set_disable_printing (EWebViewGtkHTML *web_view,
- gboolean disable_printing)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (web_view->priv->disable_printing == disable_printing)
- return;
-
- web_view->priv->disable_printing = disable_printing;
-
- g_object_notify (G_OBJECT (web_view), "disable-printing");
-}
-
-gboolean
-e_web_view_gtkhtml_get_disable_save_to_disk (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return web_view->priv->disable_save_to_disk;
-}
-
-void
-e_web_view_gtkhtml_set_disable_save_to_disk (EWebViewGtkHTML *web_view,
- gboolean disable_save_to_disk)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (web_view->priv->disable_save_to_disk == disable_save_to_disk)
- return;
-
- web_view->priv->disable_save_to_disk = disable_save_to_disk;
-
- g_object_notify (G_OBJECT (web_view), "disable-save-to-disk");
-}
-
-gboolean
-e_web_view_gtkhtml_get_editable (EWebViewGtkHTML *web_view)
-{
- /* XXX This is just here to maintain symmetry
- * with e_web_view_set_editable(). */
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return gtk_html_get_editable (GTK_HTML (web_view));
-}
-
-void
-e_web_view_gtkhtml_set_editable (EWebViewGtkHTML *web_view,
- gboolean editable)
-{
- /* XXX GtkHTML does not utilize GObject properties as well
- * as it could. This just wraps gtk_html_set_editable()
- * so we can get a "notify::editable" signal. */
-
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (gtk_html_get_editable (GTK_HTML (web_view)) == editable)
- return;
-
- gtk_html_set_editable (GTK_HTML (web_view), editable);
-
- g_object_notify (G_OBJECT (web_view), "editable");
-}
-
-gboolean
-e_web_view_gtkhtml_get_inline_spelling (EWebViewGtkHTML *web_view)
-{
- /* XXX This is just here to maintain symmetry
- * with e_web_view_set_inline_spelling(). */
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return gtk_html_get_inline_spelling (GTK_HTML (web_view));
-}
-
-void
-e_web_view_gtkhtml_set_inline_spelling (EWebViewGtkHTML *web_view,
- gboolean inline_spelling)
-{
- /* XXX GtkHTML does not utilize GObject properties as well
- * as it could. This just wraps gtk_html_set_inline_spelling()
- * so we get a "notify::inline-spelling" signal. */
-
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (gtk_html_get_inline_spelling (GTK_HTML (web_view)) == inline_spelling)
- return;
-
- gtk_html_set_inline_spelling (GTK_HTML (web_view), inline_spelling);
-
- g_object_notify (G_OBJECT (web_view), "inline-spelling");
-}
-
-gboolean
-e_web_view_gtkhtml_get_magic_links (EWebViewGtkHTML *web_view)
-{
- /* XXX This is just here to maintain symmetry
- * with e_web_view_set_magic_links(). */
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return gtk_html_get_magic_links (GTK_HTML (web_view));
-}
-
-void
-e_web_view_gtkhtml_set_magic_links (EWebViewGtkHTML *web_view,
- gboolean magic_links)
-{
- /* XXX GtkHTML does not utilize GObject properties as well
- * as it could. This just wraps gtk_html_set_magic_links()
- * so we can get a "notify::magic-links" signal. */
-
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (gtk_html_get_magic_links (GTK_HTML (web_view)) == magic_links)
- return;
-
- gtk_html_set_magic_links (GTK_HTML (web_view), magic_links);
-
- g_object_notify (G_OBJECT (web_view), "magic-links");
-}
-
-gboolean
-e_web_view_gtkhtml_get_magic_smileys (EWebViewGtkHTML *web_view)
-{
- /* XXX This is just here to maintain symmetry
- * with e_web_view_set_magic_smileys(). */
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return gtk_html_get_magic_smileys (GTK_HTML (web_view));
-}
-
-void
-e_web_view_gtkhtml_set_magic_smileys (EWebViewGtkHTML *web_view,
- gboolean magic_smileys)
-{
- /* XXX GtkHTML does not utilize GObject properties as well
- * as it could. This just wraps gtk_html_set_magic_smileys()
- * so we can get a "notify::magic-smileys" signal. */
-
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (gtk_html_get_magic_smileys (GTK_HTML (web_view)) == magic_smileys)
- return;
-
- gtk_html_set_magic_smileys (GTK_HTML (web_view), magic_smileys);
-
- g_object_notify (G_OBJECT (web_view), "magic-smileys");
-}
-
-const gchar *
-e_web_view_gtkhtml_get_selected_uri (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- return web_view->priv->selected_uri;
-}
-
-void
-e_web_view_gtkhtml_set_selected_uri (EWebViewGtkHTML *web_view,
- const gchar *selected_uri)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (g_strcmp0 (web_view->priv->selected_uri, selected_uri) == 0)
- return;
-
- g_free (web_view->priv->selected_uri);
- web_view->priv->selected_uri = g_strdup (selected_uri);
-
- g_object_notify (G_OBJECT (web_view), "selected-uri");
-}
-
-GdkPixbufAnimation *
-e_web_view_gtkhtml_get_cursor_image (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- return web_view->priv->cursor_image;
-}
-
-void
-e_web_view_gtkhtml_set_cursor_image (EWebViewGtkHTML *web_view,
- GdkPixbufAnimation *image)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (web_view->priv->cursor_image == image)
- return;
-
- if (image != NULL)
- g_object_ref (image);
-
- if (web_view->priv->cursor_image != NULL)
- g_object_unref (web_view->priv->cursor_image);
-
- web_view->priv->cursor_image = image;
-
- g_object_notify (G_OBJECT (web_view), "cursor-image");
-}
-
-GtkAction *
-e_web_view_gtkhtml_get_open_proxy (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- return web_view->priv->open_proxy;
-}
-
-void
-e_web_view_gtkhtml_set_open_proxy (EWebViewGtkHTML *web_view,
- GtkAction *open_proxy)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (web_view->priv->open_proxy == open_proxy)
- return;
-
- if (open_proxy != NULL) {
- g_return_if_fail (GTK_IS_ACTION (open_proxy));
- g_object_ref (open_proxy);
- }
-
- if (web_view->priv->open_proxy != NULL)
- g_object_unref (web_view->priv->open_proxy);
-
- web_view->priv->open_proxy = open_proxy;
-
- g_object_notify (G_OBJECT (web_view), "open-proxy");
-}
-
-GtkTargetList *
-e_web_view_gtkhtml_get_paste_target_list (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- return web_view->priv->paste_target_list;
-}
-
-GtkAction *
-e_web_view_gtkhtml_get_print_proxy (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- return web_view->priv->print_proxy;
-}
-
-void
-e_web_view_gtkhtml_set_print_proxy (EWebViewGtkHTML *web_view,
- GtkAction *print_proxy)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (web_view->priv->print_proxy == print_proxy)
- return;
-
- if (print_proxy != NULL) {
- g_return_if_fail (GTK_IS_ACTION (print_proxy));
- g_object_ref (print_proxy);
- }
-
- if (web_view->priv->print_proxy != NULL)
- g_object_unref (web_view->priv->print_proxy);
-
- web_view->priv->print_proxy = print_proxy;
-
- g_object_notify (G_OBJECT (web_view), "print-proxy");
-}
-
-GtkAction *
-e_web_view_gtkhtml_get_save_as_proxy (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- return web_view->priv->save_as_proxy;
-}
-
-void
-e_web_view_gtkhtml_set_save_as_proxy (EWebViewGtkHTML *web_view,
- GtkAction *save_as_proxy)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- if (web_view->priv->save_as_proxy == save_as_proxy)
- return;
-
- if (save_as_proxy != NULL) {
- g_return_if_fail (GTK_IS_ACTION (save_as_proxy));
- g_object_ref (save_as_proxy);
- }
-
- if (web_view->priv->save_as_proxy != NULL)
- g_object_unref (web_view->priv->save_as_proxy);
-
- web_view->priv->save_as_proxy = save_as_proxy;
-
- g_object_notify (G_OBJECT (web_view), "save-as-proxy");
-}
-
-GtkAction *
-e_web_view_gtkhtml_get_action (EWebViewGtkHTML *web_view,
- const gchar *action_name)
-{
- GtkUIManager *ui_manager;
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
- g_return_val_if_fail (action_name != NULL, NULL);
-
- ui_manager = e_web_view_gtkhtml_get_ui_manager (web_view);
-
- return e_lookup_action (ui_manager, action_name);
-}
-
-GtkActionGroup *
-e_web_view_gtkhtml_get_action_group (EWebViewGtkHTML *web_view,
- const gchar *group_name)
-{
- GtkUIManager *ui_manager;
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
- g_return_val_if_fail (group_name != NULL, NULL);
-
- ui_manager = e_web_view_gtkhtml_get_ui_manager (web_view);
-
- return e_lookup_action_group (ui_manager, group_name);
-}
-
-gchar *
-e_web_view_gtkhtml_extract_uri (EWebViewGtkHTML *web_view,
- GdkEventButton *event,
- GtkHTML *frame)
-{
- EWebViewGtkHTMLClass *class;
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- if (frame == NULL)
- frame = GTK_HTML (web_view);
-
- class = E_WEB_VIEW_GTKHTML_GET_CLASS (web_view);
- g_return_val_if_fail (class->extract_uri != NULL, NULL);
-
- return class->extract_uri (web_view, event, frame);
-}
-
-void
-e_web_view_gtkhtml_copy_clipboard (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- g_signal_emit (web_view, signals[COPY_CLIPBOARD], 0);
-}
-
-void
-e_web_view_gtkhtml_cut_clipboard (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- g_signal_emit (web_view, signals[CUT_CLIPBOARD], 0);
-}
-
-gboolean
-e_web_view_gtkhtml_is_selection_active (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return gtk_html_command (GTK_HTML (web_view), "is-selection-active");
-}
-
-void
-e_web_view_gtkhtml_paste_clipboard (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- g_signal_emit (web_view, signals[PASTE_CLIPBOARD], 0);
-}
-
-gboolean
-e_web_view_gtkhtml_scroll_forward (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return gtk_html_command (GTK_HTML (web_view), "scroll-forward");
-}
-
-gboolean
-e_web_view_gtkhtml_scroll_backward (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), FALSE);
-
- return gtk_html_command (GTK_HTML (web_view), "scroll-backward");
-}
-
-void
-e_web_view_gtkhtml_select_all (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- gtk_html_command (GTK_HTML (web_view), "select-all");
-}
-
-void
-e_web_view_gtkhtml_unselect_all (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- gtk_html_command (GTK_HTML (web_view), "unselect-all");
-}
-
-void
-e_web_view_gtkhtml_zoom_100 (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- gtk_html_command (GTK_HTML (web_view), "zoom-reset");
-}
-
-void
-e_web_view_gtkhtml_zoom_in (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- gtk_html_command (GTK_HTML (web_view), "zoom-in");
-}
-
-void
-e_web_view_gtkhtml_zoom_out (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- gtk_html_command (GTK_HTML (web_view), "zoom-out");
-}
-
-GtkUIManager *
-e_web_view_gtkhtml_get_ui_manager (EWebViewGtkHTML *web_view)
-{
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- return web_view->priv->ui_manager;
-}
-
-GtkWidget *
-e_web_view_gtkhtml_get_popup_menu (EWebViewGtkHTML *web_view)
-{
- GtkUIManager *ui_manager;
- GtkWidget *menu;
-
- g_return_val_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view), NULL);
-
- ui_manager = e_web_view_gtkhtml_get_ui_manager (web_view);
- menu = gtk_ui_manager_get_widget (ui_manager, "/context");
- g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
-
- return menu;
-}
-
-void
-e_web_view_gtkhtml_show_popup_menu (EWebViewGtkHTML *web_view,
- GdkEventButton *event,
- GtkMenuPositionFunc func,
- gpointer user_data)
-{
- GtkWidget *menu;
-
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- e_web_view_gtkhtml_update_actions (web_view);
-
- menu = e_web_view_gtkhtml_get_popup_menu (web_view);
-
- if (event != NULL)
- gtk_menu_popup (
- GTK_MENU (menu), NULL, NULL, func,
- user_data, event->button, event->time);
- else
- gtk_menu_popup (
- GTK_MENU (menu), NULL, NULL, func,
- user_data, 0, gtk_get_current_event_time ());
-}
-
-void
-e_web_view_gtkhtml_status_message (EWebViewGtkHTML *web_view,
- const gchar *status_message)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- g_signal_emit (web_view, signals[STATUS_MESSAGE], 0, status_message);
-}
-
-void
-e_web_view_gtkhtml_stop_loading (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- g_signal_emit (web_view, signals[STOP_LOADING], 0);
-}
-
-void
-e_web_view_gtkhtml_update_actions (EWebViewGtkHTML *web_view)
-{
- g_return_if_fail (E_IS_WEB_VIEW_GTKHTML (web_view));
-
- g_signal_emit (web_view, signals[UPDATE_ACTIONS], 0);
-}
diff --git a/e-util/e-web-view-gtkhtml.h b/e-util/e-web-view-gtkhtml.h
deleted file mode 100644
index 4ad08e9cf6..0000000000
--- a/e-util/e-web-view-gtkhtml.h
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * e-web-view-gtkhtml.h
- *
- * 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.
- *
- * 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, see <http://www.gnu.org/licenses/>.
- *
- */
-
-/* This is intended to serve as a common base class for all HTML viewing
- * needs in Evolution. Currently based on GtkHTML, the idea is to wrap
- * the GtkHTML API enough that we no longer have to make direct calls to
- * it. This should help smooth the transition to WebKit/GTK+.
- *
- * This class handles basic tasks like mouse hovers over links, clicked
- * links, and servicing URI requests asynchronously via GIO. */
-
-#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
-#error "Only <e-util/e-util.h> should be included directly."
-#endif
-
-#ifndef E_WEB_VIEW_GTKHTML_H
-#define E_WEB_VIEW_GTKHTML_H
-
-#include <gtkhtml/gtkhtml.h>
-
-/* Standard GObject macros */
-#define E_TYPE_WEB_VIEW_GTKHTML \
- (e_web_view_gtkhtml_get_type ())
-#define E_WEB_VIEW_GTKHTML(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST \
- ((obj), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTML))
-#define E_WEB_VIEW_GTKHTML_CLASS(cls) \
- (G_TYPE_CHECK_CLASS_CAST \
- ((cls), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTMLClass))
-#define E_IS_WEB_VIEW_GTKHTML(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE \
- ((obj), E_TYPE_WEB_VIEW_GTKHTML))
-#define E_IS_WEB_VIEW_GTKHTML_CLASS(cls) \
- (G_TYPE_CHECK_CLASS_TYPE \
- ((cls), E_TYPE_WEB_VIEW_GTKHTML))
-#define E_WEB_VIEW_GTKHTML_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS \
- ((obj), E_TYPE_WEB_VIEW_GTKHTML, EWebViewGtkHTMLClass))
-
-G_BEGIN_DECLS
-
-typedef struct _EWebViewGtkHTML EWebViewGtkHTML;
-typedef struct _EWebViewGtkHTMLClass EWebViewGtkHTMLClass;
-typedef struct _EWebViewGtkHTMLPrivate EWebViewGtkHTMLPrivate;
-
-struct _EWebViewGtkHTML {
- GtkHTML parent;
- EWebViewGtkHTMLPrivate *priv;
-};
-
-struct _EWebViewGtkHTMLClass {
- GtkHTMLClass parent_class;
-
- /* Methods */
- gchar * (*extract_uri) (EWebViewGtkHTML *web_view,
- GdkEventButton *event,
- GtkHTML *frame);
- void (*hovering_over_link) (EWebViewGtkHTML *web_view,
- const gchar *title,
- const gchar *uri);
- void (*link_clicked) (EWebViewGtkHTML *web_view,
- const gchar *uri);
- void (*load_string) (EWebViewGtkHTML *web_view,
- const gchar *load_string);
-
- /* Signals */
- void (*copy_clipboard) (EWebViewGtkHTML *web_view);
- void (*cut_clipboard) (EWebViewGtkHTML *web_view);
- void (*paste_clipboard) (EWebViewGtkHTML *web_view);
- gboolean (*popup_event) (EWebViewGtkHTML *web_view,
- GdkEventButton *event,
- const gchar *uri);
- void (*status_message) (EWebViewGtkHTML *web_view,
- const gchar *status_message);
- void (*stop_loading) (EWebViewGtkHTML *web_view);
- void (*update_actions) (EWebViewGtkHTML *web_view);
- gboolean (*process_mailto) (EWebViewGtkHTML *web_view,
- const gchar *mailto_uri);
-};
-
-GType e_web_view_gtkhtml_get_type (void) G_GNUC_CONST;
-GtkWidget * e_web_view_gtkhtml_new (void);
-void e_web_view_gtkhtml_clear (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_load_string (EWebViewGtkHTML *web_view,
- const gchar *string);
-gboolean e_web_view_gtkhtml_get_animate (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_animate (EWebViewGtkHTML *web_view,
- gboolean animate);
-gboolean e_web_view_gtkhtml_get_caret_mode
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_caret_mode
- (EWebViewGtkHTML *web_view,
- gboolean caret_mode);
-GtkTargetList * e_web_view_gtkhtml_get_copy_target_list
- (EWebViewGtkHTML *web_view);
-gboolean e_web_view_gtkhtml_get_disable_printing
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_disable_printing
- (EWebViewGtkHTML *web_view,
- gboolean disable_printing);
-gboolean e_web_view_gtkhtml_get_disable_save_to_disk
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_disable_save_to_disk
- (EWebViewGtkHTML *web_view,
- gboolean disable_save_to_disk);
-gboolean e_web_view_gtkhtml_get_editable (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_editable (EWebViewGtkHTML *web_view,
- gboolean editable);
-gboolean e_web_view_gtkhtml_get_inline_spelling
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_inline_spelling
- (EWebViewGtkHTML *web_view,
- gboolean inline_spelling);
-gboolean e_web_view_gtkhtml_get_magic_links
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_magic_links
- (EWebViewGtkHTML *web_view,
- gboolean magic_links);
-gboolean e_web_view_gtkhtml_get_magic_smileys
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_magic_smileys
- (EWebViewGtkHTML *web_view,
- gboolean magic_smileys);
-const gchar * e_web_view_gtkhtml_get_selected_uri
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_selected_uri
- (EWebViewGtkHTML *web_view,
- const gchar *selected_uri);
-GdkPixbufAnimation *
- e_web_view_gtkhtml_get_cursor_image
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_cursor_image
- (EWebViewGtkHTML *web_view,
- GdkPixbufAnimation *animation);
-GtkAction * e_web_view_gtkhtml_get_open_proxy
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_open_proxy
- (EWebViewGtkHTML *web_view,
- GtkAction *open_proxy);
-GtkTargetList * e_web_view_gtkhtml_get_paste_target_list
- (EWebViewGtkHTML *web_view);
-GtkAction * e_web_view_gtkhtml_get_print_proxy
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_print_proxy
- (EWebViewGtkHTML *web_view,
- GtkAction *print_proxy);
-GtkAction * e_web_view_gtkhtml_get_save_as_proxy
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_set_save_as_proxy
- (EWebViewGtkHTML *web_view,
- GtkAction *save_as_proxy);
-GtkAction * e_web_view_gtkhtml_get_action (EWebViewGtkHTML *web_view,
- const gchar *action_name);
-GtkActionGroup *e_web_view_gtkhtml_get_action_group
- (EWebViewGtkHTML *web_view,
- const gchar *group_name);
-gchar * e_web_view_gtkhtml_extract_uri (EWebViewGtkHTML *web_view,
- GdkEventButton *event,
- GtkHTML *frame);
-void e_web_view_gtkhtml_copy_clipboard
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_cut_clipboard
- (EWebViewGtkHTML *web_view);
-gboolean e_web_view_gtkhtml_is_selection_active
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_paste_clipboard
- (EWebViewGtkHTML *web_view);
-gboolean e_web_view_gtkhtml_scroll_forward
- (EWebViewGtkHTML *web_view);
-gboolean e_web_view_gtkhtml_scroll_backward
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_select_all (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_unselect_all (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_zoom_100 (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_zoom_in (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_zoom_out (EWebViewGtkHTML *web_view);
-GtkUIManager * e_web_view_gtkhtml_get_ui_manager
- (EWebViewGtkHTML *web_view);
-GtkWidget * e_web_view_gtkhtml_get_popup_menu
- (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_show_popup_menu
- (EWebViewGtkHTML *web_view,
- GdkEventButton *event,
- GtkMenuPositionFunc func,
- gpointer user_data);
-void e_web_view_gtkhtml_status_message
- (EWebViewGtkHTML *web_view,
- const gchar *status_message);
-void e_web_view_gtkhtml_stop_loading (EWebViewGtkHTML *web_view);
-void e_web_view_gtkhtml_update_actions
- (EWebViewGtkHTML *web_view);
-
-G_END_DECLS
-
-#endif /* E_WEB_VIEW_GTKHTML_H */
diff --git a/e-util/e-web-view.c b/e-util/e-web-view.c
index afb3cc9fef..0b83d81a34 100644
--- a/e-util/e-web-view.c
+++ b/e-util/e-web-view.c
@@ -359,8 +359,6 @@ static void
web_view_init_web_settings (WebKitWebView *web_view)
{
WebKitWebSettings *web_settings;
- GObjectClass *class;
- GParamSpec *pspec;
web_settings = webkit_web_settings_new ();
@@ -373,19 +371,9 @@ web_view_init_web_settings (WebKitWebView *web_view)
"enable-offline-web-application-cache", FALSE,
"enable-site-specific-quirks", TRUE,
"enable-scripts", FALSE,
+ "respect-image-orientation", TRUE,
NULL);
- /* This property was introduced in WebKitGTK 2.0,
- * so check for it and enable it if it's present. */
- class = G_OBJECT_GET_CLASS (web_settings);
- pspec = g_object_class_find_property (
- class, "respect-image-orientation");
- if (pspec != NULL) {
- g_object_set (
- G_OBJECT (web_settings),
- pspec->name, TRUE, NULL);
- }
-
g_object_bind_property (
web_settings, "enable-caret-browsing",
web_view, "caret-mode",
@@ -3377,9 +3365,9 @@ e_web_view_install_request_handler (EWebView *web_view,
soup_session_add_feature_by_type (session, handler_type);
}
-static void
-create_and_add_css_style_sheet (WebKitDOMDocument *document,
- const gchar *style_sheet_id)
+void
+e_web_view_create_and_add_css_style_sheet (WebKitDOMDocument *document,
+ const gchar *style_sheet_id)
{
WebKitDOMElement *style_element;
@@ -3388,15 +3376,9 @@ create_and_add_css_style_sheet (WebKitDOMDocument *document,
if (!style_element) {
/* Create new <style> element */
style_element = webkit_dom_document_create_element (document, "style", NULL);
-#if WEBKIT_CHECK_VERSION(2,2,0) /* XXX should really be (2,1,something) */
webkit_dom_element_set_id (
WEBKIT_DOM_ELEMENT (style_element),
style_sheet_id);
-#else
- webkit_dom_html_element_set_id (
- WEBKIT_DOM_HTML_ELEMENT (style_element),
- style_sheet_id);
-#endif
webkit_dom_html_style_element_set_media (
WEBKIT_DOM_HTML_STYLE_ELEMENT (style_element),
"screen");
@@ -3427,7 +3409,7 @@ add_css_rule_into_style_sheet (WebKitDOMDocument *document,
style_element = webkit_dom_document_get_element_by_id (document, style_sheet_id);
if (!style_element) {
- create_and_add_css_style_sheet (document, style_sheet_id);
+ e_web_view_create_and_add_css_style_sheet (document, style_sheet_id);
style_element = webkit_dom_document_get_element_by_id (document, style_sheet_id);
}
@@ -3551,3 +3533,125 @@ e_web_view_add_css_rule_into_style_sheet (EWebView *view,
selector,
style);
}
+
+gboolean
+element_has_id (WebKitDOMElement *element,
+ const gchar* id)
+{
+ gchar *element_id;
+
+ if (!element)
+ return FALSE;
+
+ if (!WEBKIT_DOM_IS_ELEMENT (element))
+ return FALSE;
+
+ element_id = webkit_dom_element_get_id (element);
+
+ if (g_ascii_strcasecmp (element_id, id) != 0) {
+ g_free (element_id);
+ return FALSE;
+ }
+ g_free (element_id);
+
+ return TRUE;
+}
+
+gboolean
+element_has_tag (WebKitDOMElement *element,
+ const gchar* tag)
+{
+ gchar *element_tag;
+
+ if (!WEBKIT_DOM_IS_ELEMENT (element))
+ return FALSE;
+
+ element_tag = webkit_dom_node_get_local_name (WEBKIT_DOM_NODE (element));
+
+ if (g_ascii_strcasecmp (element_tag, tag) != 0) {
+ g_free (element_tag);
+ return FALSE;
+ }
+ g_free (element_tag);
+
+ return TRUE;
+}
+
+gboolean
+element_has_class (WebKitDOMElement *element,
+ const gchar* class)
+{
+ gchar *element_class;
+
+ if (!element)
+ return FALSE;
+
+ if (!WEBKIT_DOM_IS_ELEMENT (element))
+ return FALSE;
+
+ element_class = webkit_dom_element_get_class_name (element);
+
+ if (g_strstr_len (element_class, -1, class)) {
+ g_free (element_class);
+ return TRUE;
+ }
+ g_free (element_class);
+
+ return FALSE;
+}
+
+void
+element_add_class (WebKitDOMElement *element,
+ const gchar* class)
+{
+ gchar *element_class;
+ gchar *new_class;
+
+ if (!WEBKIT_DOM_IS_ELEMENT (element))
+ return;
+
+ if (element_has_class (element, class))
+ return;
+
+ element_class = webkit_dom_element_get_class_name (element);
+
+ if (g_strcmp0 (element_class, "") == 0)
+ new_class = g_strdup (class);
+ else
+ new_class = g_strconcat (element_class, " ", class, NULL);
+
+ webkit_dom_element_set_class_name (element, new_class);
+
+ g_free (element_class);
+ g_free (new_class);
+}
+
+void
+element_remove_class (WebKitDOMElement *element,
+ const gchar* class)
+{
+ gchar *element_class;
+ GString *result;
+
+ if (!WEBKIT_DOM_IS_ELEMENT (element))
+ return;
+
+ if (!element_has_class (element, class))
+ return;
+
+ element_class = webkit_dom_element_get_class_name (element);
+
+ if (g_strcmp0 (element_class, class) == 0) {
+ webkit_dom_element_remove_attribute (element, "class");
+ g_free (element_class);
+ return;
+ }
+
+ result = e_str_replace_string (element_class, class, "");
+ if (result) {
+ webkit_dom_element_set_class_name (element, result->str);
+ g_string_free (result, TRUE);
+ }
+
+ g_free (element_class);
+}
diff --git a/e-util/e-web-view.h b/e-util/e-web-view.h
index 62bd045f8a..cbfd2cefad 100644
--- a/e-util/e-web-view.h
+++ b/e-util/e-web-view.h
@@ -197,11 +197,24 @@ GInputStream * e_web_view_request_finish (EWebView *web_view,
void e_web_view_install_request_handler
(EWebView *web_view,
GType handler_type);
+void e_web_view_create_and_add_css_style_sheet
+ (WebKitDOMDocument* document,
+ const gchar *style_sheet_id);
void e_web_view_add_css_rule_into_style_sheet
(EWebView *web_view,
const gchar *style_sheet_id,
const gchar *selector,
const gchar *style);
+gboolean element_has_id (WebKitDOMElement *element,
+ const gchar* id);
+gboolean element_has_tag (WebKitDOMElement *element,
+ const gchar* tag);
+gboolean element_has_class (WebKitDOMElement *element,
+ const gchar* class);
+void element_add_class (WebKitDOMElement *element,
+ const gchar* class);
+void element_remove_class (WebKitDOMElement *element,
+ const gchar* class);
G_END_DECLS
#endif /* E_WEB_VIEW_H */
diff --git a/e-util/test-html-editor.c b/e-util/test-html-editor.c
new file mode 100644
index 0000000000..488eb44bf1
--- /dev/null
+++ b/e-util/test-html-editor.c
@@ -0,0 +1,497 @@
+/*
+ * e-html-editor-test.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 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 <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <gtk/gtk.h>
+#include <e-util/e-util.h>
+
+#include <glib/gi18n-lib.h>
+
+static const gchar *file_ui =
+"<ui>\n"
+" <menubar name='main-menu'>\n"
+" <menu action='file-menu'>\n"
+" <menuitem action='save'/>\n"
+" <menuitem action='save-as'/>\n"
+" <separator/>\n"
+" <menuitem action='print-preview'/>\n"
+" <menuitem action='print'/>\n"
+" <separator/>\n"
+" <menuitem action='disable-editor'/>\n"
+" <separator/>\n"
+" <menuitem action='quit'/>\n"
+" </menu>\n"
+" </menubar>\n"
+"</ui>";
+
+static const gchar *view_ui =
+"<ui>\n"
+" <menubar name='main-menu'>\n"
+" <menu action='view-menu'>\n"
+" <menuitem action='view-html-output'/>\n"
+" <menuitem action='view-html-source'/>\n"
+" <menuitem action='view-plain-source'/>\n"
+" <menuitem action='view-inspector'/>\n"
+" </menu>\n"
+" </menubar>\n"
+"</ui>";
+
+static void
+handle_error (GError **error)
+{
+ if (*error != NULL) {
+ g_warning ("%s", (*error)->message);
+ g_clear_error (error);
+ }
+}
+
+static GtkPrintOperationResult
+print (EHTMLEditor *editor,
+ GtkPrintOperationAction action)
+{
+ WebKitWebFrame *frame;
+ GtkPrintOperation *operation;
+ GtkPrintOperationResult result;
+ GError *error = NULL;
+
+ operation = gtk_print_operation_new ();
+
+ frame = webkit_web_view_get_main_frame (
+ WEBKIT_WEB_VIEW (e_html_editor_get_view (editor)));
+ result = webkit_web_frame_print_full (frame, operation, action, NULL);
+
+ g_object_unref (operation);
+ handle_error (&error);
+
+ return result;
+}
+
+static gint
+save_dialog (EHTMLEditor *editor)
+{
+ GtkWidget *dialog;
+ const gchar *filename;
+ gint response;
+
+ dialog = gtk_file_chooser_dialog_new (
+ _("Save As"),
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (editor))),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_file_chooser_set_do_overwrite_confirmation (
+ GTK_FILE_CHOOSER (dialog), TRUE);
+
+ filename = e_html_editor_get_filename (editor);
+
+ if (filename != NULL)
+ gtk_file_chooser_set_filename (
+ GTK_FILE_CHOOSER (dialog), filename);
+ else
+ gtk_file_chooser_set_current_name (
+ GTK_FILE_CHOOSER (dialog), _("Untitled document"));
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (response == GTK_RESPONSE_ACCEPT) {
+ gchar *new_filename;
+
+ new_filename = gtk_file_chooser_get_filename (
+ GTK_FILE_CHOOSER (dialog));
+ e_html_editor_set_filename (editor, new_filename);
+ g_free (new_filename);
+ }
+
+ gtk_widget_destroy (dialog);
+
+ return response;
+}
+
+static void
+view_source_dialog (EHTMLEditor *editor,
+ const gchar *title,
+ gboolean plain_text,
+ gboolean show_source)
+{
+ GtkWidget *dialog;
+ GtkWidget *content;
+ GtkWidget *content_area;
+ GtkWidget *scrolled_window;
+ gchar * html;
+
+ dialog = gtk_dialog_new_with_buttons (
+ title,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (editor))),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+ NULL);
+
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (
+ GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (
+ GTK_SCROLLED_WINDOW (scrolled_window), GTK_SHADOW_IN);
+ gtk_box_pack_start (
+ GTK_BOX (content_area),
+ scrolled_window, TRUE, TRUE, 0);
+
+ gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
+ gtk_container_set_border_width (GTK_CONTAINER (scrolled_window), 6);
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 300);
+
+ if (plain_text) {
+ html = e_html_editor_view_get_text_plain (
+ e_html_editor_get_view (editor));
+ } else {
+ html = e_html_editor_view_get_text_html (
+ e_html_editor_get_view (editor));
+ }
+
+ if (show_source || plain_text) {
+ GtkTextBuffer *buffer;
+
+ content = gtk_text_view_new ();
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (content));
+ gtk_text_buffer_set_text (buffer, html, -1);
+ gtk_text_view_set_editable (GTK_TEXT_VIEW (content), FALSE);
+ } else {
+ content = webkit_web_view_new ();
+ webkit_web_view_load_string (
+ WEBKIT_WEB_VIEW (content), html, NULL, NULL, NULL);
+ }
+ g_free (html);
+
+ gtk_container_add (GTK_CONTAINER (scrolled_window), content);
+ gtk_widget_show_all (scrolled_window);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+static void
+action_print_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ print (editor, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG);
+}
+
+static void
+action_print_preview_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ print (editor, GTK_PRINT_OPERATION_ACTION_PREVIEW);
+}
+
+static void
+action_quit_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ gtk_main_quit ();
+}
+
+static void
+action_save_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ const gchar *filename;
+ gboolean as_html;
+ GError *error = NULL;
+
+ if (e_html_editor_get_filename (editor) == NULL)
+ if (save_dialog (editor) == GTK_RESPONSE_CANCEL)
+ return;
+
+ filename = e_html_editor_get_filename (editor);
+ as_html = (e_html_editor_view_get_html_mode (e_html_editor_get_view (editor)));
+
+ e_html_editor_save (editor, filename, as_html, &error);
+ handle_error (&error);
+}
+
+static void
+action_save_as_cb (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ const gchar *filename;
+ gboolean as_html;
+ GError *error = NULL;
+
+ if (save_dialog (editor) == GTK_RESPONSE_CANCEL)
+ return;
+
+ filename = e_html_editor_get_filename (editor);
+ as_html = (e_html_editor_view_get_html_mode (e_html_editor_get_view (editor)));
+
+ e_html_editor_save (editor, filename, as_html, &error);
+ handle_error (&error);
+}
+
+static void
+action_toggle_editor (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ EHTMLEditorView *view;
+
+ view = e_html_editor_get_view (editor);
+ webkit_web_view_set_editable (
+ WEBKIT_WEB_VIEW (view),
+ ! webkit_web_view_get_editable (WEBKIT_WEB_VIEW (view)));
+}
+
+static void
+action_view_html_output (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ view_source_dialog (editor, _("HTML Output"), FALSE, FALSE);
+}
+
+static void
+action_view_html_source (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ view_source_dialog (editor, _("HTML Source"), FALSE, TRUE);
+}
+
+static void
+action_view_plain_source (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ view_source_dialog (editor, _("Plain Source"), TRUE, FALSE);
+}
+
+static void
+action_view_inspector (GtkAction *action,
+ EHTMLEditor *editor)
+{
+ WebKitWebInspector *inspector;
+ EHTMLEditorView *view;
+
+ view = e_html_editor_get_view (editor);
+ inspector = webkit_web_view_get_inspector (WEBKIT_WEB_VIEW (view));
+
+ webkit_web_inspector_show (inspector);
+}
+
+static GtkActionEntry file_entries[] = {
+
+ { "print",
+ GTK_STOCK_PRINT,
+ N_("_Print..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_print_cb) },
+
+ { "print-preview",
+ GTK_STOCK_PRINT_PREVIEW,
+ N_("Print Pre_view"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_print_preview_cb) },
+
+ { "quit",
+ GTK_STOCK_QUIT,
+ N_("_Quit"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_quit_cb) },
+
+ { "save",
+ GTK_STOCK_SAVE,
+ N_("_Save"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_save_cb) },
+
+ { "save-as",
+ GTK_STOCK_SAVE_AS,
+ N_("Save _As..."),
+ NULL,
+ NULL,
+ G_CALLBACK (action_save_as_cb) },
+
+ { "disable-editor",
+ NULL,
+ N_("Disable/Enable Editor"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_toggle_editor) },
+
+ { "file-menu",
+ NULL,
+ N_("_File"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static GtkActionEntry view_entries[] = {
+
+ { "view-html-output",
+ NULL,
+ N_("HTML _Output"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_view_html_output) },
+
+ { "view-html-source",
+ NULL,
+ N_("_HTML Source"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_view_html_source) },
+
+ { "view-plain-source",
+ NULL,
+ N_("_Plain Source"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_view_plain_source) },
+
+ { "view-inspector",
+ NULL,
+ N_("Inspector"),
+ NULL,
+ NULL,
+ G_CALLBACK (action_view_inspector) },
+
+ { "view-menu",
+ NULL,
+ N_("_View"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static WebKitWebView *
+open_inspector (WebKitWebInspector *inspector,
+ WebKitWebView *webview,
+ gpointer user_data)
+{
+ GtkWidget *window;
+ GtkWidget *inspector_view;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ inspector_view = webkit_web_view_new ();
+
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (inspector_view));
+
+ gtk_widget_set_size_request (window, 600, 480);
+ gtk_widget_show (window);
+
+ return WEBKIT_WEB_VIEW (inspector_view);
+}
+
+gint
+main (gint argc,
+ gchar **argv)
+{
+ GtkActionGroup *action_group;
+ GtkUIManager *manager;
+ GtkWidget *container;
+ GtkWidget *widget;
+ EHTMLEditor *editor;
+ EHTMLEditorView *view;
+ WebKitWebInspector *inspector;
+
+ GError *error = NULL;
+
+ bindtextdomain (GETTEXT_PACKAGE, EVOLUTION_LOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ gtk_init (&argc, &argv);
+
+ editor = g_object_ref_sink (e_html_editor_new ());
+ view = e_html_editor_get_view (editor);
+
+ inspector = webkit_web_view_get_inspector (
+ WEBKIT_WEB_VIEW (view));
+ g_signal_connect (
+ inspector, "inspect-web-view",
+ G_CALLBACK (open_inspector), NULL);
+
+ widget = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_size_request (widget, 600, 400);
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ container = widget;
+
+ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (container), widget);
+ gtk_widget_show (widget);
+
+ container = widget;
+
+ widget = e_html_editor_get_managed_widget (editor, "/main-menu");
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = e_html_editor_get_managed_widget (editor, "/main-toolbar");
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ widget = GTK_WIDGET (editor);
+ gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+ gtk_widget_show (widget);
+
+ manager = e_html_editor_get_ui_manager (editor);
+
+ gtk_ui_manager_add_ui_from_string (manager, file_ui, -1, &error);
+ handle_error (&error);
+
+ gtk_ui_manager_add_ui_from_string (manager, view_ui, -1, &error);
+ handle_error (&error);
+
+ action_group = gtk_action_group_new ("file");
+ gtk_action_group_set_translation_domain (
+ action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (
+ action_group, file_entries,
+ G_N_ELEMENTS (file_entries), editor);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ action_group = gtk_action_group_new ("view");
+ gtk_action_group_set_translation_domain (
+ action_group, GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (
+ action_group, view_entries,
+ G_N_ELEMENTS (view_entries), editor);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ gtk_ui_manager_ensure_update (manager);
+
+ g_signal_connect (
+ editor, "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ gtk_main ();
+
+ g_object_unref (editor);
+
+ return 0;
+}