[evolution/449-support-markdown-in-composer: 3/3] I#449 - Support markdown in composer
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution/449-support-markdown-in-composer: 3/3] I#449 - Support markdown in composer
- Date: Fri, 11 Feb 2022 09:05:09 +0000 (UTC)
commit 7153d6d2c03d340ea0c1e68021ab4696cb1e7562
Author: Milan Crha <mcrha redhat com>
Date: Thu Feb 3 14:49:44 2022 +0100
I#449 - Support markdown in composer
Closes https://gitlab.gnome.org/GNOME/evolution/-/issues/449
data/evolution.convert | 1 -
data/icons/CMakeLists.txt | 2 +
...icolor_actions_scalable_markdown-emoji-dark.svg | 25 +
.../hicolor_actions_scalable_markdown-emoji.svg | 25 +
data/org.gnome.evolution.mail.gschema.xml.in | 27 +-
src/composer/e-composer-actions.c | 44 +-
src/composer/e-composer-private.c | 37 +-
src/composer/e-msg-composer.c | 284 +++-
src/e-util/CMakeLists.txt | 2 +
src/e-util/e-content-editor.c | 154 +-
src/e-util/e-content-editor.h | 22 +-
src/e-util/e-html-editor-actions.c | 276 ++-
src/e-util/e-html-editor-find-dialog.c | 77 +-
src/e-util/e-html-editor-manager.ui | 3 +
src/e-util/e-html-editor-private.h | 24 +-
src/e-util/e-html-editor-replace-dialog.c | 60 +-
src/e-util/e-html-editor.c | 639 ++++++-
src/e-util/e-html-editor.h | 13 +
src/e-util/e-mail-signature-combo-box.c | 22 +-
src/e-util/e-mail-signature-combo-box.h | 3 +-
src/e-util/e-mail-signature-editor.c | 61 +-
src/e-util/e-mail-signature-manager.c | 50 +-
src/e-util/e-mail-signature-manager.h | 8 +-
src/e-util/e-markdown-editor.c | 1779 ++++++++++++++++++--
src/e-util/e-markdown-editor.h | 26 +-
src/e-util/e-markdown-utils.c | 608 +++++++
src/e-util/e-markdown-utils.h | 28 +
src/e-util/e-spell-text-view.c | 82 +
src/e-util/e-spell-text-view.h | 5 +
src/e-util/e-util-enums.h | 38 +
src/e-util/e-util.h | 1 +
src/e-util/test-html-editor-units-utils.c | 18 +-
src/e-util/test-html-editor.c | 22 +-
src/e-util/test-mail-signatures.c | 6 +-
src/e-util/test-markdown-editor.c | 311 +++-
src/em-format/e-mail-formatter-text-markdown.c | 2 +-
src/mail/e-mail-enums.h | 27 +-
src/mail/e-mail-notes.c | 244 ++-
src/mail/em-composer-utils.c | 96 +-
src/mail/mail-config.ui | 26 +-
.../composer-to-meeting/e-meeting-to-composer.c | 13 +-
src/modules/mail/e-mail-shell-backend.c | 16 +-
src/modules/mail/em-composer-prefs.c | 65 +-
src/modules/webkit-editor/e-webkit-editor.c | 137 +-
44 files changed, 4646 insertions(+), 763 deletions(-)
---
diff --git a/data/evolution.convert b/data/evolution.convert
index c434d52952..470a6cf56c 100644
--- a/data/evolution.convert
+++ b/data/evolution.convert
@@ -121,7 +121,6 @@ composer-no-signature-delim = /apps/evolution/mail/composer/no_signature_delim
composer-outlook-filenames = /apps/evolution/mail/composer/outlook_filenames
composer-reply-start-bottom = /apps/evolution/mail/composer/reply_start_bottom
composer-request-receipt = /apps/evolution/mail/composer/request_receipt
-composer-send-html = /apps/evolution/mail/composer/send_html
composer-show-bcc = /apps/evolution/mail/composer/show_mail_bcc
composer-show-cc = /apps/evolution/mail/composer/show_mail_cc
composer-show-post-from = /apps/evolution/mail/composer/show_post_from
diff --git a/data/icons/CMakeLists.txt b/data/icons/CMakeLists.txt
index d6cbfb2b3f..64d2395b32 100644
--- a/data/icons/CMakeLists.txt
+++ b/data/icons/CMakeLists.txt
@@ -98,6 +98,8 @@ set(private_icons
hicolor_actions_scalable_markdown-bullets-dark.svg
hicolor_actions_scalable_markdown-code.svg
hicolor_actions_scalable_markdown-code-dark.svg
+ hicolor_actions_scalable_markdown-emoji.svg
+ hicolor_actions_scalable_markdown-emoji-dark.svg
hicolor_actions_scalable_markdown-header.svg
hicolor_actions_scalable_markdown-header-dark.svg
hicolor_actions_scalable_markdown-help.svg
diff --git a/data/icons/hicolor_actions_scalable_markdown-emoji-dark.svg
b/data/icons/hicolor_actions_scalable_markdown-emoji-dark.svg
new file mode 100644
index 0000000000..a80c068f3b
--- /dev/null
+++ b/data/icons/hicolor_actions_scalable_markdown-emoji-dark.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ version="1.1"
+ id="svg33406"
+ xml:space="preserve"
+ width="20"
+ height="20"
+ viewBox="0 0 20 20"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs33410" /><path
+
style="fill:none;fill-rule:evenodd;stroke:#e6e6e6;stroke-width:1.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 1.9005051,10.0005 c -6.75e-4,-4.050675 4.049325,-8.100675 8.1000409,-8.0999595 4.050715,7.155e-4
8.100634,4.0506345 8.099919,8.1000005 -7.16e-4,4.049365 -4.050635,8.099284 -8.1,8.099919 -4.0493659,6.34e-4
-8.0992849,-4.049285 -8.0999599,-8.09996 z"
+ id="path11753" /><path
+
style="fill:#e6e6e6;fill-rule:evenodd;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m 7.0712862,5.7713633 c 1.0005,5e-4 2.0004999,1.0005001 1.9999899,2.00001 -5.1e-4,0.9995099
-1.00049,1.9994899 -1.9999999,1.9999799 -0.9995099,4.9e-4 -1.9994899,-0.99949 -1.99998,-1.9999999
-4.9e-4,-1.00051 0.99949,-2.00049 1.99999,-1.99999 z"
+ id="path12190" /><path
+
style="fill:#e6e6e6;fill-rule:evenodd;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m 12.96911,5.728406 c 1.0005,5e-4 2.0005,1.0005 1.99999,2.00001 -5.1e-4,0.99951 -1.00049,1.99949
-2,1.99998 -0.99951,4.899e-4 -1.99949,-0.9994902 -1.99998,-2 -4.9e-4,-1.0005098 0.99949,-2.0004899
1.99999,-1.99999 z"
+ id="path12190-3" /><path
+
style="fill:#e6e6e6;fill-rule:evenodd;stroke:#e6e6e6;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 5.1486969,12.08349 5.0469071,2.521786 4.953093,-2.521786 -5,1 z"
+ id="path12608" /></svg>
diff --git a/data/icons/hicolor_actions_scalable_markdown-emoji.svg
b/data/icons/hicolor_actions_scalable_markdown-emoji.svg
new file mode 100644
index 0000000000..10eec0364b
--- /dev/null
+++ b/data/icons/hicolor_actions_scalable_markdown-emoji.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ version="1.1"
+ id="svg33406"
+ xml:space="preserve"
+ width="20"
+ height="20"
+ viewBox="0 0 20 20"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><defs
+ id="defs33410" /><path
+
style="fill:none;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 1.9005051,10.0005 c -6.75e-4,-4.050675 4.049325,-8.100675 8.1000409,-8.0999595 4.050715,7.155e-4
8.100634,4.0506345 8.099919,8.1000005 -7.16e-4,4.049365 -4.050635,8.099284 -8.1,8.099919 -4.0493659,6.34e-4
-8.0992849,-4.049285 -8.0999599,-8.09996 z"
+ id="path11753" /><path
+
style="fill:#1a1a1a;fill-rule:evenodd;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m 7.0712862,5.7713633 c 1.0005,5e-4 2.0004999,1.0005001 1.9999899,2.00001 -5.1e-4,0.9995099
-1.00049,1.9994899 -1.9999999,1.9999799 -0.9995099,4.9e-4 -1.9994899,-0.99949 -1.99998,-1.9999999
-4.9e-4,-1.00051 0.99949,-2.00049 1.99999,-1.99999 z"
+ id="path12190" /><path
+
style="fill:#1a1a1a;fill-rule:evenodd;stroke:none;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="m 12.96911,5.728406 c 1.0005,5e-4 2.0005,1.0005 1.99999,2.00001 -5.1e-4,0.99951 -1.00049,1.99949
-2,1.99998 -0.99951,4.899e-4 -1.99949,-0.9994902 -1.99998,-2 -4.9e-4,-1.0005098 0.99949,-2.0004899
1.99999,-1.99999 z"
+ id="path12190-3" /><path
+
style="fill:#1a1a1a;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 5.1486969,12.08349 5.0469071,2.521786 4.953093,-2.521786 -5,1 z"
+ id="path12608" /></svg>
diff --git a/data/org.gnome.evolution.mail.gschema.xml.in b/data/org.gnome.evolution.mail.gschema.xml.in
index 11e233f982..f97b99f9ce 100644
--- a/data/org.gnome.evolution.mail.gschema.xml.in
+++ b/data/org.gnome.evolution.mail.gschema.xml.in
@@ -44,6 +44,16 @@
<value nick='books' value='2'/>
</enum>
+ <!-- Keep this synchronized with EContentEditorMode. -->
+ <enum id="org.gnome.evolution.mail.ContentEditorMode">
+ <value nick='unknown' value='-1'/>
+ <value nick='plain-text' value='0'/>
+ <value nick='html' value='1'/>
+ <value nick='markdown' value='2'/>
+ <value nick='markdown-plain-text' value='3'/>
+ <value nick='markdown-html' value='4'/>
+ </enum>
+
<schema gettext-domain="evolution" id="org.gnome.evolution.mail" path="/org/gnome/evolution/mail/">
<key name="prompt-check-if-default-mailer" type="b">
<default>true</default>
@@ -57,8 +67,8 @@
</key>
<key name="composer-editor" type="s">
<default>''</default>
- <_summary>Name of the editor to prefer in the message composer</_summary>
- <_description>If the name doesn’t correspond to any known editor, then the built-in WebKit editor is
used.</_description>
+ <_summary>Comma-separated names of the editor to prefer in the message composer</_summary>
+ <_description>If the name doesn’t correspond to any known editor, then the built-in WebKit editor is
used. The mode is optional, in which case the editor is used for all the modes it supports. Modes are
“plain”, “html”, "markdown-plain", "markdown-html" and "markdown". Example values: “webkit” (to use WebKit
for plain and html), “plain:first-editor,html:second-editor” (to use “first-editor” for the “plain” and
“second-editor” for “html”)</_description>
</key>
<key name="composer-gallery-path" type="s">
<default>''</default>
@@ -115,10 +125,9 @@
<_summary>Always request read receipt</_summary>
<_description>Whether a read receipt request gets added to every message by default.</_description>
</key>
- <key name="composer-send-html" type="b">
- <default>false</default>
- <_summary>Send HTML mail by default</_summary>
- <_description>Send HTML mail by default.</_description>
+ <key name="composer-mode" enum="org.gnome.evolution.mail.ContentEditorMode">
+ <default>'plain-text'</default>
+ <_summary>What mode open the composer with</_summary>
</key>
<key name="composer-spell-languages" type="as">
<default>[]</default>
@@ -800,9 +809,9 @@
<default>'quoted'</default>
<_summary>Alternative reply style</_summary>
</key>
- <key name="alt-reply-html-format" enum="org.gnome.evolution.mail.ThreeState">
- <default>'inconsistent'</default>
- <_summary>Format message in HTML</_summary>
+ <key name="alt-reply-format-mode" enum="org.gnome.evolution.mail.ContentEditorMode">
+ <default>'unknown'</default>
+ <_summary>Composer mode to use.</_summary>
</key>
<key name="alt-reply-start-bottom" enum="org.gnome.evolution.mail.ThreeState">
<default>'inconsistent'</default>
diff --git a/src/composer/e-composer-actions.c b/src/composer/e-composer-actions.c
index 5cb03cab94..67adb5e15d 100644
--- a/src/composer/e-composer-actions.c
+++ b/src/composer/e-composer-actions.c
@@ -558,6 +558,35 @@ static GtkToggleActionEntry toggle_entries[] = {
FALSE }
};
+static gboolean
+eca_transform_mode_html_to_boolean_cb (GBinding *binding,
+ const GValue *source_value,
+ GValue *target_value,
+ gpointer not_used)
+{
+ g_value_set_boolean (target_value, g_value_get_enum (source_value) == E_CONTENT_EDITOR_MODE_HTML);
+
+ return TRUE;
+}
+
+static gboolean
+eca_mode_to_bool_hide_in_markdown_cb (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ EContentEditorMode mode;
+
+ mode = g_value_get_enum (from_value);
+
+ g_value_set_boolean (to_value,
+ mode != E_CONTENT_EDITOR_MODE_MARKDOWN &&
+ mode != E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT &&
+ mode != E_CONTENT_EDITOR_MODE_MARKDOWN_HTML);
+
+ return TRUE;
+}
+
void
e_composer_actions_init (EMsgComposer *composer)
{
@@ -676,10 +705,12 @@ e_composer_actions_init (EMsgComposer *composer)
g_object_unref (gcr_gnupg_icon);
}
- e_binding_bind_property (
- cnt_editor, "html-mode",
+ e_binding_bind_property_full (
+ editor, "mode",
ACTION (PICTURE_GALLERY), "sensitive",
- G_BINDING_SYNC_CREATE);
+ G_BINDING_SYNC_CREATE,
+ eca_transform_mode_html_to_boolean_cb,
+ NULL, NULL, NULL);
e_binding_bind_property (
cnt_editor, "editable",
@@ -711,6 +742,13 @@ e_composer_actions_init (EMsgComposer *composer)
e_html_editor_get_action (editor, "visually-wrap-long-lines"), "active",
G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+ e_binding_bind_property_full (
+ editor, "mode",
+ e_html_editor_get_action (editor, "visually-wrap-long-lines"), "visible",
+ G_BINDING_SYNC_CREATE,
+ eca_mode_to_bool_hide_in_markdown_cb,
+ NULL, NULL, NULL);
+
#if defined (ENABLE_SMIME)
visible = TRUE;
#else
diff --git a/src/composer/e-composer-private.c b/src/composer/e-composer-private.c
index f23b1d9b8f..020ae6c442 100644
--- a/src/composer/e-composer-private.c
+++ b/src/composer/e-composer-private.c
@@ -62,14 +62,12 @@ static void
composer_update_gallery_visibility (EMsgComposer *composer)
{
EHTMLEditor *editor;
- EContentEditor *cnt_editor;
GtkToggleAction *toggle_action;
gboolean gallery_active;
gboolean is_html;
editor = e_msg_composer_get_editor (composer);
- cnt_editor = e_html_editor_get_content_editor (editor);
- is_html = e_content_editor_get_html_mode (cnt_editor);
+ is_html = e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_HTML;
toggle_action = GTK_TOGGLE_ACTION (ACTION (PICTURE_GALLERY));
gallery_active = gtk_toggle_action_get_active (toggle_action);
@@ -261,23 +259,7 @@ e_composer_private_constructed (EMsgComposer *composer)
focus_tracker = e_focus_tracker_new (GTK_WINDOW (composer));
- action = e_html_editor_get_action (editor, "cut");
- e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
-
- action = e_html_editor_get_action (editor, "copy");
- e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
-
- action = e_html_editor_get_action (editor, "paste");
- e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
-
- action = e_html_editor_get_action (editor, "select-all");
- e_focus_tracker_set_select_all_action (focus_tracker, action);
-
- action = e_html_editor_get_action (editor, "undo");
- e_focus_tracker_set_undo_action (focus_tracker, action);
-
- action = e_html_editor_get_action (editor, "redo");
- e_focus_tracker_set_redo_action (focus_tracker, action);
+ e_html_editor_connect_focus_tracker (editor, focus_tracker);
priv->focus_tracker = focus_tracker;
@@ -351,12 +333,7 @@ e_composer_private_constructed (EMsgComposer *composer)
priv->gallery_scrolled_window = g_object_ref (widget);
gtk_widget_show (widget);
- widget = GTK_WIDGET (cnt_editor);
- if (GTK_IS_SCROLLABLE (cnt_editor)) {
- /* Scrollables are packed in a scrolled window */
- widget = gtk_widget_get_parent (widget);
- g_warn_if_fail (GTK_IS_SCROLLED_WINDOW (widget));
- }
+ widget = e_html_editor_get_content_box (editor);
gtk_widget_reparent (widget, container);
/* Construct the picture gallery. */
@@ -372,7 +349,7 @@ e_composer_private_constructed (EMsgComposer *composer)
g_free (gallery_path);
e_signal_connect_notify_swapped (
- cnt_editor, "notify::html-mode",
+ editor, "notify::mode",
G_CALLBACK (composer_update_gallery_visibility), composer);
g_signal_connect_swapped (
@@ -757,13 +734,13 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box,
EMsgComposer *composer = usd->composer;
gchar *contents = NULL, *new_signature_id;
gsize length = 0;
- gboolean is_html;
+ EContentEditorMode editor_mode = E_CONTENT_EDITOR_MODE_UNKNOWN;
GError *error = NULL;
EHTMLEditor *editor;
EContentEditor *cnt_editor;
e_mail_signature_combo_box_load_selected_finish (
- combo_box, result, &contents, &length, &is_html, &error);
+ combo_box, result, &contents, &length, &editor_mode, &error);
/* FIXME Use an EAlert here. */
if (error != NULL) {
@@ -788,7 +765,7 @@ composer_load_signature_cb (EMailSignatureComboBox *combo_box,
new_signature_id = e_content_editor_insert_signature (
cnt_editor,
contents,
- is_html,
+ editor_mode,
usd->can_reposition_caret,
gtk_combo_box_get_active_id (GTK_COMBO_BOX (combo_box)),
&composer->priv->set_signature_from_message,
diff --git a/src/composer/e-msg-composer.c b/src/composer/e-msg-composer.c
index aaa35c4652..b8ba281d1e 100644
--- a/src/composer/e-msg-composer.c
+++ b/src/composer/e-msg-composer.c
@@ -86,8 +86,7 @@ typedef enum {
COMPOSER_FLAG_PGP_ENCRYPT = 1 << 5,
COMPOSER_FLAG_SMIME_SIGN = 1 << 6,
COMPOSER_FLAG_SMIME_ENCRYPT = 1 << 7,
- COMPOSER_FLAG_HTML_MODE = 1 << 8,
- COMPOSER_FLAG_SAVE_DRAFT = 1 << 9
+ COMPOSER_FLAG_SAVE_DRAFT = 1 << 8
} ComposerFlags;
enum {
@@ -217,7 +216,7 @@ e_msg_composer_dec_soft_busy (EMsgComposer *composer)
g_object_notify (G_OBJECT (composer), "soft-busy");
}
-/**
+/*
* emcu_part_to_html:
* @part:
*
@@ -227,7 +226,7 @@ e_msg_composer_dec_soft_busy (EMsgComposer *composer)
* will be performed.
*
* Return Value: The part in displayable html format.
- **/
+ */
static gchar *
emcu_part_to_html (EMsgComposer *composer,
CamelMimePart *part,
@@ -306,6 +305,45 @@ emcu_part_to_html (EMsgComposer *composer,
return text;
}
+static gchar *
+emcu_part_as_text (EMsgComposer *composer,
+ CamelMimePart *part,
+ gssize *out_len,
+ GCancellable *cancellable)
+{
+ CamelDataWrapper *dw;
+ gchar *text;
+ gssize length;
+
+ dw = camel_medium_get_content (CAMEL_MEDIUM (part));
+ if (dw) {
+ CamelStream *mem = camel_stream_mem_new ();
+ GByteArray *bytes;
+
+ camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL);
+ camel_stream_close (mem, cancellable, NULL);
+
+ bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem));
+ if (bytes && bytes->len) {
+ text = g_strndup ((const gchar *) bytes->data, bytes->len);
+ length = bytes->len;
+ } else {
+ text = g_strdup ("");
+ length = 0;
+ }
+
+ g_object_unref (mem);
+ } else {
+ text = g_strdup ("");
+ length = 0;
+ }
+
+ if (out_len)
+ *out_len = length;
+
+ return text;
+}
+
static EDestination **
destination_list_to_vector_sized (GList *list,
gint n)
@@ -1203,27 +1241,45 @@ composer_build_message_thread (GSimpleAsyncResult *simple,
#endif /* ENABLE_SMIME */
}
+static const gchar *
+composer_get_editor_mode_format_text (EContentEditorMode mode)
+{
+ switch (mode) {
+ case E_CONTENT_EDITOR_MODE_UNKNOWN:
+ g_warn_if_reached ();
+ break;
+ case E_CONTENT_EDITOR_MODE_PLAIN_TEXT:
+ return "text/plain";
+ case E_CONTENT_EDITOR_MODE_HTML:
+ return "text/html";
+ case E_CONTENT_EDITOR_MODE_MARKDOWN:
+ return "text/markdown";
+ case E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT:
+ return "text/markdown-plain";
+ case E_CONTENT_EDITOR_MODE_MARKDOWN_HTML:
+ return "text/markdown-html";
+ }
+
+ return "text/plain";
+}
+
static void
composer_add_evolution_composer_mode_header (CamelMedium *medium,
EMsgComposer *composer)
{
- gboolean html_mode;
EHTMLEditor *editor;
- EContentEditor *cnt_editor;
+ const gchar *mode;
editor = e_msg_composer_get_editor (composer);
- cnt_editor = e_html_editor_get_content_editor (editor);
- html_mode = e_content_editor_get_html_mode (cnt_editor);
+ mode = composer_get_editor_mode_format_text (e_html_editor_get_mode (editor));
- camel_medium_add_header (
- medium,
- "X-Evolution-Composer-Mode",
- html_mode ? "text/html" : "text/plain");
+ camel_medium_add_header (medium, "X-Evolution-Composer-Mode", mode);
}
static void
composer_add_evolution_format_header (CamelMedium *medium,
- ComposerFlags flags)
+ ComposerFlags flags,
+ EContentEditorMode mode)
{
GString *string;
@@ -1460,7 +1516,10 @@ composer_build_message (EMsgComposer *composer,
if (!g_str_has_suffix (text, "\r\n") && !g_str_has_suffix (text, "\n"))
g_byte_array_append (data, (const guint8 *) "\r\n", 2);
- type = camel_content_type_new ("text", "plain");
+ if (e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_MARKDOWN)
+ type = camel_content_type_new ("text", "markdown");
+ else
+ type = camel_content_type_new ("text", "plain");
charset = best_charset (
data, priv->charset, &context->plain_encoding);
if (charset != NULL) {
@@ -1468,6 +1527,12 @@ composer_build_message (EMsgComposer *composer,
iconv_charset = camel_iconv_charset_name (charset);
g_free (charset);
}
+
+ if ((flags & COMPOSER_FLAG_SAVE_DRAFT) == 0 && (
+ e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_MARKDOWN ||
+ e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT ||
+ e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML))
+ composer_add_evolution_composer_mode_header (CAMEL_MEDIUM (context->message),
composer);
}
mem_stream = camel_stream_mem_new_with_byte_array (data);
@@ -1527,7 +1592,7 @@ composer_build_message (EMsgComposer *composer,
if ((flags & COMPOSER_FLAG_SAVE_DRAFT) != 0) {
/* X-Evolution-Format */
composer_add_evolution_format_header (
- CAMEL_MEDIUM (context->message), flags);
+ CAMEL_MEDIUM (context->message), flags, e_html_editor_get_mode
(composer->priv->editor));
/* X-Evolution-Composer-Mode */
composer_add_evolution_composer_mode_header (
@@ -1968,7 +2033,7 @@ msg_composer_paste_clipboard_targets_cb (GtkClipboard *clipboard,
editor = e_msg_composer_get_editor (composer);
cnt_editor = e_html_editor_get_content_editor (editor);
- if (!e_content_editor_get_html_mode (cnt_editor) &&
+ if (e_html_editor_get_mode (editor) != E_CONTENT_EDITOR_MODE_HTML &&
gtk_targets_include_image (targets, n_targets, TRUE)) {
e_composer_paste_image (composer, clipboard);
return;
@@ -2052,7 +2117,7 @@ msg_composer_drag_data_received_cb (GtkWidget *widget,
editor = e_msg_composer_get_editor (composer);
cnt_editor = e_html_editor_get_content_editor (editor);
- html_mode = e_content_editor_get_html_mode (cnt_editor);
+ html_mode = e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_HTML;
g_signal_handler_disconnect (cnt_editor, composer->priv->drag_data_received_handler_id);
composer->priv->drag_data_received_handler_id = 0;
@@ -2716,7 +2781,7 @@ msg_composer_map (GtkWidget *widget)
/* Jump to the editor as a last resort. */
cnt_editor = e_html_editor_get_content_editor (editor);
- gtk_widget_grab_focus (GTK_WIDGET (cnt_editor));
+ e_content_editor_grab_focus (cnt_editor);
}
static gboolean
@@ -2750,11 +2815,11 @@ msg_composer_key_press_event (GtkWidget *widget,
}
if (event->keyval == GDK_KEY_Tab && gtk_widget_is_focus (input_widget)) {
- gtk_widget_grab_focus (GTK_WIDGET (cnt_editor));
+ e_content_editor_grab_focus (cnt_editor);
return TRUE;
}
- if (gtk_widget_is_focus (GTK_WIDGET (cnt_editor))) {
+ if (e_content_editor_is_focus (cnt_editor)) {
if (event->keyval == GDK_KEY_ISO_Left_Tab) {
gboolean view_processed = FALSE;
@@ -3404,7 +3469,7 @@ e_msg_composer_check_inline_attachments (EMsgComposer *composer)
editor = e_msg_composer_get_editor (composer);
content_editor = e_html_editor_get_content_editor (editor);
- if (e_content_editor_get_html_mode (content_editor)) {
+ if (e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_HTML) {
e_content_editor_get_content (content_editor, E_CONTENT_EDITOR_GET_INLINE_IMAGES,
"localhost", NULL, e_mg_composer_got_used_inline_images_cb, g_object_ref (composer));
} else {
@@ -3412,6 +3477,23 @@ e_msg_composer_check_inline_attachments (EMsgComposer *composer)
}
}
+static gboolean
+emcu_format_as_plain_text (EMsgComposer *composer,
+ CamelContentType *content_type)
+{
+ EContentEditorMode mode;
+
+ if (!camel_content_type_is (content_type, "text", "plain") &&
+ !camel_content_type_is (content_type, "text", "markdown"))
+ return FALSE;
+
+ mode = e_html_editor_get_mode (e_msg_composer_get_editor (composer));
+
+ return mode == E_CONTENT_EDITOR_MODE_MARKDOWN ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML;
+}
+
static void
handle_multipart_signed (EMsgComposer *composer,
CamelMultipart *multipart,
@@ -3488,7 +3570,14 @@ handle_multipart_signed (EMsgComposer *composer,
handle_multipart (
composer, multipart, parent_part, keep_signature, cancellable, depth);
}
+ } else if (camel_content_type_is (content_type, "text", "markdown") ||
+ emcu_format_as_plain_text (composer, content_type)) {
+ gchar *text;
+ gssize length;
+ text = emcu_part_as_text (composer, mime_part, &length, cancellable);
+ if (text)
+ e_msg_composer_set_pending_body (composer, text, length, FALSE);
} else if (camel_content_type_is (content_type, "text", "*")) {
gchar *html;
gssize length;
@@ -3591,6 +3680,14 @@ handle_multipart_encrypted (EMsgComposer *composer,
composer, content_multipart, multipart, keep_signature, cancellable, depth);
}
+ } else if (camel_content_type_is (content_type, "text", "markdown") ||
+ emcu_format_as_plain_text (composer, content_type)) {
+ gchar *text;
+ gssize length;
+
+ text = emcu_part_as_text (composer, mime_part, &length, cancellable);
+ if (text)
+ e_msg_composer_set_pending_body (composer, text, length, FALSE);
} else if (camel_content_type_is (content_type, "text", "*")) {
gchar *html;
gssize length;
@@ -3663,6 +3760,17 @@ handle_multipart_alternative (EMsgComposer *composer,
/* text/html is preferable, so once we find it we're done... */
text_part = mime_part;
break;
+ } else if (camel_content_type_is (content_type, "text", "markdown") ||
+ emcu_format_as_plain_text (composer, content_type)) {
+ gchar *text;
+ gssize length;
+
+ text = emcu_part_as_text (composer, mime_part, &length, cancellable);
+ if (text) {
+ e_msg_composer_set_pending_body (composer, text, length, FALSE);
+ text_part = NULL;
+ break;
+ }
} else if (camel_content_type_is (content_type, "text", "*")) {
/* anyt text part not text/html is second rate so the first
* text part we find isn't necessarily the one we'll use. */
@@ -3682,11 +3790,33 @@ handle_multipart_alternative (EMsgComposer *composer,
gchar *html;
gssize length;
- html = emcu_part_to_html (
- composer, text_part, &length, keep_signature, cancellable);
- if (!html && fallback_text_part)
- html = emcu_part_to_html (
- composer, fallback_text_part, &length, keep_signature, cancellable);
+ if (emcu_format_as_plain_text (composer, camel_mime_part_get_content_type (text_part))) {
+ gchar *text;
+ gssize length;
+
+ text = emcu_part_as_text (composer, text_part, &length, cancellable);
+ if (text) {
+ e_msg_composer_set_pending_body (composer, text, length, FALSE);
+ return;
+ }
+ }
+
+ html = emcu_part_to_html (composer, text_part, &length, keep_signature, cancellable);
+ if (!html && fallback_text_part) {
+ if (emcu_format_as_plain_text (composer, camel_mime_part_get_content_type
(fallback_text_part))) {
+ gchar *text;
+ gssize length;
+
+ text = emcu_part_as_text (composer, fallback_text_part, &length, cancellable);
+ if (text) {
+ e_msg_composer_set_pending_body (composer, text, length, FALSE);
+ return;
+ }
+ }
+
+ html = emcu_part_to_html (composer, fallback_text_part, &length, keep_signature,
cancellable);
+ }
+
if (html)
e_msg_composer_set_pending_body (composer, html, length, TRUE);
}
@@ -3748,15 +3878,26 @@ handle_multipart (EMsgComposer *composer,
}
} else if (depth == 0 && i == 0) {
- gchar *html = NULL;
- gssize length = 0;
-
/* Since the first part is not multipart/alternative,
* this must be the body. */
- html = emcu_part_to_html (
- composer, mime_part, &length, keep_signature, cancellable);
- e_msg_composer_set_pending_body (composer, html, length, TRUE);
+ if (camel_content_type_is (content_type, "text", "markdown") ||
+ emcu_format_as_plain_text (composer, content_type)) {
+ gchar *text;
+ gssize length;
+
+ text = emcu_part_as_text (composer, mime_part, &length, cancellable);
+ if (text)
+ e_msg_composer_set_pending_body (composer, text, length, FALSE);
+ } else {
+ gchar *html = NULL;
+ gssize length = 0;
+
+ html = emcu_part_to_html (
+ composer, mime_part, &length, keep_signature, cancellable);
+
+ e_msg_composer_set_pending_body (composer, html, length, TRUE);
+ }
} else if (camel_content_type_is (content_type, "image", "*") && (
camel_mime_part_get_content_id (mime_part) ||
@@ -4108,7 +4249,7 @@ e_msg_composer_setup_with_message (EMsgComposer *composer,
composer_mode = camel_medium_get_header (
CAMEL_MEDIUM (message), "X-Evolution-Composer-Mode");
- if (composer_mode && *composer_mode)
+ if (format && *format && composer_mode && *composer_mode)
is_message_from_draft = TRUE;
if (format != NULL) {
@@ -4121,10 +4262,24 @@ e_msg_composer_setup_with_message (EMsgComposer *composer,
for (i = 0; flags[i]; i++) {
if (g_ascii_strcasecmp (flags[i], "text/html") == 0 ||
g_ascii_strcasecmp (flags[i], "text/plain") == 0) {
- gboolean html_mode;
+ EContentEditorMode mode = E_CONTENT_EDITOR_MODE_HTML;
+
+ if (composer_mode) {
+ if (!g_ascii_strcasecmp (composer_mode, "text/plain"))
+ mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+ else if (!g_ascii_strcasecmp (composer_mode, "text/html"))
+ mode = E_CONTENT_EDITOR_MODE_HTML;
+ else if (!g_ascii_strcasecmp (composer_mode, "text/markdown"))
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN;
+ else if (!g_ascii_strcasecmp (composer_mode, "text/markdown-plain"))
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT;
+ else if (!g_ascii_strcasecmp (composer_mode, "text/markdown-html"))
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_HTML;
+ } else if (!g_ascii_strcasecmp (flags[i], "text/plain")) {
+ mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+ }
- html_mode = composer_mode && !g_ascii_strcasecmp (composer_mode, "text/html");
- e_content_editor_set_html_mode (cnt_editor, html_mode);
+ e_html_editor_set_mode (editor, mode);
} else if (g_ascii_strcasecmp (flags[i], "pgp-sign") == 0) {
action = GTK_TOGGLE_ACTION (ACTION (PGP_SIGN));
gtk_toggle_action_set_active (action, TRUE);
@@ -4140,6 +4295,21 @@ e_msg_composer_setup_with_message (EMsgComposer *composer,
}
}
g_strfreev (flags);
+ } else if (composer_mode != NULL) {
+ EContentEditorMode mode = E_CONTENT_EDITOR_MODE_HTML;
+
+ if (!g_ascii_strcasecmp (composer_mode, "text/plain"))
+ mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+ else if (!g_ascii_strcasecmp (composer_mode, "text/html"))
+ mode = E_CONTENT_EDITOR_MODE_HTML;
+ else if (!g_ascii_strcasecmp (composer_mode, "text/markdown"))
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN;
+ else if (!g_ascii_strcasecmp (composer_mode, "text/markdown-plain"))
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT;
+ else if (!g_ascii_strcasecmp (composer_mode, "text/markdown-html"))
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_HTML;
+
+ e_html_editor_set_mode (editor, mode);
}
if (is_message_from_draft || (
@@ -4250,7 +4420,6 @@ e_msg_composer_setup_with_message (EMsgComposer *composer,
/* If we are opening message from Drafts */
if (is_message_from_draft) {
/* Extract the body */
- CamelDataWrapper *dw;
#ifdef ENABLE_SMIME
if (is_smime_encrypted) {
@@ -4279,28 +4448,11 @@ e_msg_composer_setup_with_message (EMsgComposer *composer,
}
#endif
- dw = camel_medium_get_content ((CamelMedium *) mime_part);
- if (dw) {
- CamelStream *mem = camel_stream_mem_new ();
- GByteArray *bytes;
-
- camel_data_wrapper_decode_to_stream_sync (dw, mem, cancellable, NULL);
- camel_stream_close (mem, cancellable, NULL);
-
- bytes = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (mem));
- if (bytes && bytes->len) {
- html = g_strndup ((const gchar *) bytes->data, bytes->len);
- length = bytes->len;
- } else {
- html = g_strdup ("");
- length = 0;
- }
-
- g_object_unref (mem);
- } else {
- html = g_strdup ("");
- length = 0;
- }
+ html = emcu_part_as_text (composer, mime_part, &length, cancellable);
+ } else if (camel_content_type_is (content_type, "text", "markdown") ||
+ emcu_format_as_plain_text (composer, content_type)) {
+ is_html = FALSE;
+ html = emcu_part_as_text (composer, CAMEL_MIME_PART (message), &length, cancellable);
} else {
is_html = TRUE;
html = emcu_part_to_html (
@@ -5493,7 +5645,7 @@ e_msg_composer_set_body (EMsgComposer *composer,
content = _("The composer contains a non-text message body, which cannot be edited.");
set_editor_text (composer, content, TRUE, FALSE);
- e_content_editor_set_html_mode (cnt_editor, FALSE);
+ e_html_editor_set_mode (editor, E_CONTENT_EDITOR_MODE_PLAIN_TEXT);
e_content_editor_set_editable (cnt_editor, FALSE);
g_free (priv->mime_body);
@@ -5872,12 +6024,10 @@ e_msg_composer_get_message (EMsgComposer *composer,
GtkAction *action;
ComposerFlags flags = 0;
EHTMLEditor *editor;
- EContentEditor *cnt_editor;
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
editor = e_msg_composer_get_editor (composer);
- cnt_editor = e_html_editor_get_content_editor (editor);
simple = g_simple_async_result_new (
G_OBJECT (composer), callback,
@@ -5885,7 +6035,8 @@ e_msg_composer_get_message (EMsgComposer *composer,
g_simple_async_result_set_check_cancellable (simple, cancellable);
- if (e_content_editor_get_html_mode (cnt_editor))
+ if (e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_HTML ||
+ e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML)
flags |= COMPOSER_FLAG_HTML_CONTENT;
action = ACTION (PRIORITIZE_MESSAGE);
@@ -5996,8 +6147,6 @@ e_msg_composer_get_message_draft (EMsgComposer *composer,
GAsyncReadyCallback callback,
gpointer user_data)
{
- EHTMLEditor *editor;
- EContentEditor *cnt_editor;
GSimpleAsyncResult *simple;
ComposerFlags flags = COMPOSER_FLAG_SAVE_DRAFT;
GtkAction *action;
@@ -6010,11 +6159,6 @@ e_msg_composer_get_message_draft (EMsgComposer *composer,
g_simple_async_result_set_check_cancellable (simple, cancellable);
- editor = e_msg_composer_get_editor (composer);
- cnt_editor = e_html_editor_get_content_editor (editor);
- /* We need to remember composer mode */
- if (e_content_editor_get_html_mode (cnt_editor))
- flags |= COMPOSER_FLAG_HTML_MODE;
/* We want to save HTML content everytime when we save as draft */
flags |= COMPOSER_FLAG_SAVE_DRAFT;
diff --git a/src/e-util/CMakeLists.txt b/src/e-util/CMakeLists.txt
index e81570ad3c..cc8c06c2b0 100644
--- a/src/e-util/CMakeLists.txt
+++ b/src/e-util/CMakeLists.txt
@@ -169,6 +169,7 @@ set(SOURCES
e-mail-signature-tree-view.c
e-map.c
e-markdown-editor.c
+ e-markdown-utils.c
e-marshal.c
e-menu-tool-action.c
e-menu-tool-button.c
@@ -445,6 +446,7 @@ set(HEADERS
e-mail-signature-tree-view.h
e-map.h
e-markdown-editor.h
+ e-markdown-utils.h
e-menu-tool-action.h
e-menu-tool-button.h
e-misc-utils.h
diff --git a/src/e-util/e-content-editor.c b/src/e-util/e-content-editor.c
index dd01adb92a..f8b1722639 100644
--- a/src/e-util/e-content-editor.c
+++ b/src/e-util/e-content-editor.c
@@ -174,17 +174,20 @@ e_content_editor_default_init (EContentEditorInterface *iface)
G_PARAM_STATIC_STRINGS));
/**
- * EContentEditor:html-mode
+ * EContentEditor:mode
*
- * Determines whether HTML or plain text mode is enabled.
+ * Determines the mode of the content editor, as one of the #EContentEditorMode.
+ *
+ * Since: 3.44
**/
g_object_interface_install_property (
iface,
- g_param_spec_boolean (
- "html-mode",
- "HTML Mode",
- "Edit HTML or plain text",
- TRUE,
+ g_param_spec_enum (
+ "mode",
+ "Mode",
+ "Editor mode",
+ E_TYPE_CONTENT_EDITOR_MODE,
+ E_CONTENT_EDITOR_MODE_PLAIN_TEXT,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
@@ -639,6 +642,82 @@ e_content_editor_default_init (EContentEditorInterface *iface)
G_TYPE_STRING);
}
+/**
+ * e_content_editor_supports_mode:
+ * @editor: an #EContentEditor
+ * @mode: an #EContentEditorMode to check
+ *
+ * Returns: whether the @editor supports @mode
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_content_editor_supports_mode (EContentEditor *editor,
+ EContentEditorMode mode)
+{
+ EContentEditorInterface *iface;
+
+ g_return_val_if_fail (E_IS_CONTENT_EDITOR (editor), FALSE);
+
+ iface = E_CONTENT_EDITOR_GET_IFACE (editor);
+ g_return_val_if_fail (iface != NULL, FALSE);
+
+ return iface->supports_mode != NULL &&
+ iface->supports_mode (editor, mode);
+}
+
+/**
+ * e_content_editor_grab_focus:
+ * @editor: an #EContentEditor
+ *
+ * A method to grab focus on the @editor. This is an optional method,
+ * the default implementation calls gtk_widget_grab_focus().
+ *
+ * Since: 3.44
+ **/
+void
+e_content_editor_grab_focus (EContentEditor *editor)
+{
+ EContentEditorInterface *iface;
+
+ g_return_if_fail (E_IS_CONTENT_EDITOR (editor));
+
+ iface = E_CONTENT_EDITOR_GET_IFACE (editor);
+ g_return_if_fail (iface != NULL);
+
+ if (iface->grab_focus)
+ iface->grab_focus (editor);
+ else
+ gtk_widget_grab_focus (GTK_WIDGET (editor));
+}
+
+/**
+ * e_content_editor_is_focus:
+ * @editor: an #EContentEditor
+ *
+ * Returns, whether the @editor is focused. This is an optional method,
+ * the default implementation calls gtk_widget_is_focus().
+ *
+ * Returns: whether the @editor is focused
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_content_editor_is_focus (EContentEditor *editor)
+{
+ EContentEditorInterface *iface;
+
+ g_return_val_if_fail (E_IS_CONTENT_EDITOR (editor), FALSE);
+
+ iface = E_CONTENT_EDITOR_GET_IFACE (editor);
+ g_return_val_if_fail (iface != NULL, FALSE);
+
+ if (iface->is_focus)
+ return iface->is_focus (editor);
+ else
+ return gtk_widget_is_focus (GTK_WIDGET (editor));
+}
+
ESpellChecker *
e_content_editor_ref_spell_checker (EContentEditor *editor)
{
@@ -809,27 +888,6 @@ e_content_editor_set_changed (EContentEditor *editor,
g_object_set (G_OBJECT (editor), "changed", changed, NULL);
}
-gboolean
-e_content_editor_get_html_mode (EContentEditor *editor)
-{
- gboolean value = FALSE;
-
- g_return_val_if_fail (E_IS_CONTENT_EDITOR (editor), FALSE);
-
- g_object_get (G_OBJECT (editor), "html-mode", &value, NULL);
-
- return value;
-}
-
-void
-e_content_editor_set_html_mode (EContentEditor *editor,
- gboolean html_mode)
-{
- g_return_if_fail (E_IS_CONTENT_EDITOR (editor));
-
- g_object_set (G_OBJECT (editor), "html-mode", html_mode, NULL);
-}
-
/**
* e_content_editor_set_alignment:
* @editor: an #EContentEditor
@@ -2399,7 +2457,7 @@ e_content_editor_take_last_error (EContentEditor *editor,
gchar *
e_content_editor_insert_signature (EContentEditor *editor,
const gchar *content,
- gboolean is_html,
+ EContentEditorMode editor_mode,
gboolean can_reposition_caret,
const gchar *signature_id,
gboolean *set_signature_from_message,
@@ -2417,7 +2475,7 @@ e_content_editor_insert_signature (EContentEditor *editor,
return iface->insert_signature (
editor,
content,
- is_html,
+ editor_mode,
can_reposition_caret,
signature_id,
set_signature_from_message,
@@ -3915,3 +3973,39 @@ e_content_editor_delete_image (EContentEditor *editor)
iface->delete_image (editor);
}
+
+/**
+ * e_content_editor_util_three_state_to_bool:
+ * @value: an #EThreeState value
+ * @mail_key: (nullable): a key into 'org.gnome.evolution.mail'
+ *
+ * Converts the three-state @value into boolean, using the @mail_key
+ * boolean key from 'org.gnome.evolution.mail' in case the @value
+ * in %E_THREE_STATE_INCONSISTENT as a fallback, when non-%NULL.
+ *
+ * Returns: @value converted to boolean, optionally depending on @mail_key setting
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_content_editor_util_three_state_to_bool (EThreeState value,
+ const gchar *mail_key)
+{
+ gboolean res = FALSE;
+
+ if (value == E_THREE_STATE_ON)
+ return TRUE;
+
+ if (value == E_THREE_STATE_OFF)
+ return FALSE;
+
+ if (mail_key && *mail_key) {
+ GSettings *settings;
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+ res = g_settings_get_boolean (settings, mail_key);
+ g_clear_object (&settings);
+ }
+
+ return res;
+}
diff --git a/src/e-util/e-content-editor.h b/src/e-util/e-content-editor.h
index a377f5f1c0..8f376e6b2b 100644
--- a/src/e-util/e-content-editor.h
+++ b/src/e-util/e-content-editor.h
@@ -144,7 +144,7 @@ struct _EContentEditorInterface {
gchar * (*insert_signature) (EContentEditor *editor,
const gchar *content,
- gboolean is_html,
+ EContentEditorMode editor_mode,
gboolean can_reposition_caret,
const gchar *signature_id,
gboolean *set_signature_from_message,
@@ -426,14 +426,23 @@ struct _EContentEditorInterface {
void (*delete_h_rule) (EContentEditor *editor);
void (*delete_image) (EContentEditor *editor);
+ gboolean (*supports_mode) (EContentEditor *editor,
+ EContentEditorMode mode);
+ void (*grab_focus) (EContentEditor *editor);
+ gboolean (*is_focus) (EContentEditor *editor);
+
/* padding for future expansion */
- gpointer reserved[20];
+ gpointer reserved[17];
};
/* Properties */
ESpellChecker * e_content_editor_ref_spell_checker
(EContentEditor *editor);
+gboolean e_content_editor_supports_mode (EContentEditor *editor,
+ EContentEditorMode mode);
+void e_content_editor_grab_focus (EContentEditor *editor);
+gboolean e_content_editor_is_focus (EContentEditor *editor);
gboolean e_content_editor_is_malfunction (EContentEditor *editor);
gboolean e_content_editor_can_cut (EContentEditor *editor);
gboolean e_content_editor_can_copy (EContentEditor *editor);
@@ -452,9 +461,6 @@ void e_content_editor_set_editable (EContentEditor *editor,
gboolean e_content_editor_get_changed (EContentEditor *editor);
void e_content_editor_set_changed (EContentEditor *editor,
gboolean changed);
-gboolean e_content_editor_get_html_mode (EContentEditor *editor);
-void e_content_editor_set_html_mode (EContentEditor *editor,
- gboolean html_mode);
void e_content_editor_set_alignment (EContentEditor *editor,
EContentEditorAlignment value);
EContentEditorAlignment
@@ -649,7 +655,7 @@ void e_content_editor_take_last_error(EContentEditor *editor,
gchar * e_content_editor_insert_signature
(EContentEditor *editor,
const gchar *content,
- gboolean is_html,
+ EContentEditorMode editor_mode,
gboolean can_reposition_caret,
const gchar *signature_id,
gboolean *set_signature_from_message,
@@ -1011,6 +1017,10 @@ CamelMimePart * e_content_editor_emit_ref_mime_part
(EContentEditor *editor,
const gchar *uri);
+gboolean e_content_editor_util_three_state_to_bool
+ (EThreeState value,
+ const gchar *mail_key);
+
G_END_DECLS
#endif /* E_CONTENT_EDITOR_H */
diff --git a/src/e-util/e-html-editor-actions.c b/src/e-util/e-html-editor-actions.c
index 061baf6f9f..aa6f664597 100644
--- a/src/e-util/e-html-editor-actions.c
+++ b/src/e-util/e-html-editor-actions.c
@@ -607,21 +607,17 @@ update_mode_combobox (gpointer data)
{
GWeakRef *weak_ref = data;
EHTMLEditor *editor;
- EContentEditor *cnt_editor;
+ EContentEditorMode mode;
GtkAction *action;
- gboolean is_html;
editor = g_weak_ref_get (weak_ref);
if (!editor)
return FALSE;
- cnt_editor = e_html_editor_get_content_editor (editor);
- is_html = e_content_editor_get_html_mode (cnt_editor);
+ mode = e_html_editor_get_mode (editor);
- action = gtk_action_group_get_action (
- editor->priv->core_editor_actions, "mode-html");
- gtk_radio_action_set_current_value (
- GTK_RADIO_ACTION (action), (is_html ? 1 : 0));
+ action = gtk_action_group_get_action (editor->priv->core_editor_actions, "mode-html");
+ gtk_radio_action_set_current_value (GTK_RADIO_ACTION (action), mode);
g_object_unref (editor);
@@ -629,18 +625,17 @@ update_mode_combobox (gpointer data)
}
static void
-html_editor_actions_notify_html_mode_cb (EContentEditor *cnt_editor,
- GParamSpec *param,
- EHTMLEditor *editor)
+html_editor_actions_notify_mode_cb (EHTMLEditor *editor,
+ GParamSpec *param,
+ gpointer user_data)
{
GtkActionGroup *action_group;
GtkWidget *style_combo_box;
gboolean is_html;
- g_return_if_fail (E_IS_CONTENT_EDITOR (cnt_editor));
g_return_if_fail (E_IS_HTML_EDITOR (editor));
- is_html = e_content_editor_get_html_mode (cnt_editor);
+ is_html = e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_HTML;
g_object_set (G_OBJECT (editor->priv->html_actions), "sensitive", is_html, NULL);
@@ -793,7 +788,7 @@ action_paste_quote_cb (GtkAction *action,
gdk_display_get_default (),
GDK_SELECTION_CLIPBOARD);
- if (e_content_editor_get_html_mode (cnt_editor)) {
+ if (e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_HTML) {
if (e_clipboard_wait_is_html_available (clipboard))
e_clipboard_request_html (clipboard, clipboard_html_received_for_paste_quote, editor);
else if (gtk_clipboard_wait_is_text_available (clipboard))
@@ -1319,14 +1314,35 @@ static GtkRadioActionEntry core_mode_entries[] = {
N_("_HTML"),
NULL,
N_("HTML editing mode"),
- TRUE }, /* e_content_editor_set_html_mode */
+ E_CONTENT_EDITOR_MODE_HTML },
{ "mode-plain",
NULL,
N_("Plain _Text"),
NULL,
N_("Plain text editing mode"),
- FALSE } /* e_content_editor_set_html_mode */
+ E_CONTENT_EDITOR_MODE_PLAIN_TEXT },
+
+ { "mode-markdown",
+ NULL,
+ N_("_Markdown"),
+ NULL,
+ N_("Markdown editing mode"),
+ E_CONTENT_EDITOR_MODE_MARKDOWN },
+
+ { "mode-markdown-plain",
+ NULL,
+ N_("Ma_rkdown as Plain Text"),
+ NULL,
+ N_("Markdown editing mode, exported as Plain Text"),
+ E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT },
+
+ { "mode-markdown-html",
+ NULL,
+ N_("Mar_kdown as HTML"),
+ NULL,
+ N_("Markdown editing mode, exported as HTML"),
+ E_CONTENT_EDITOR_MODE_MARKDOWN_HTML }
};
static GtkRadioActionEntry core_style_entries[] = {
@@ -2131,12 +2147,13 @@ editor_actions_setup_spell_check_menu (EHTMLEditor *editor)
}
void
-editor_actions_init (EHTMLEditor *editor)
+e_html_editor_actions_init (EHTMLEditor *editor)
{
GtkAction *action;
GtkActionGroup *action_group;
GtkUIManager *manager;
const gchar *domain;
+ guint ii;
g_return_if_fail (E_IS_HTML_EDITOR (editor));
@@ -2164,7 +2181,7 @@ editor_actions_init (EHTMLEditor *editor)
gtk_action_group_add_radio_actions (
action_group, core_mode_entries,
G_N_ELEMENTS (core_mode_entries),
- TRUE,
+ E_CONTENT_EDITOR_MODE_HTML,
G_CALLBACK (action_mode_cb), editor);
gtk_action_group_add_radio_actions (
action_group, core_style_entries,
@@ -2264,6 +2281,26 @@ editor_actions_init (EHTMLEditor *editor)
gtk_action_set_sensitive (ACTION (UNINDENT), FALSE);
gtk_action_set_sensitive (ACTION (FIND_AGAIN), FALSE);
+
+ g_signal_connect_object (ACTION (SUBSCRIPT), "toggled",
+ G_CALLBACK (html_editor_actions_subscript_toggled_cb), editor, 0);
+ g_signal_connect_object (ACTION (SUPERSCRIPT), "toggled",
+ G_CALLBACK (html_editor_actions_superscript_toggled_cb), editor, 0);
+ g_signal_connect (editor, "notify::mode",
+ G_CALLBACK (html_editor_actions_notify_mode_cb), NULL);
+
+ action_group = editor->priv->core_editor_actions;
+ action = gtk_action_group_get_action (action_group, "mode-html");
+ e_binding_bind_property (
+ editor, "mode",
+ action, "current-value",
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ for (ii = 0; ii < G_N_ELEMENTS (core_mode_entries); ii++) {
+ action = gtk_action_group_get_action (action_group, core_mode_entries[ii].name);
+
+ gtk_action_set_visible (action, e_html_editor_has_editor_for_mode (editor,
core_mode_entries[ii].value));
+ }
}
static gboolean
@@ -2302,137 +2339,216 @@ e_html_editor_indent_level_to_bool_unindent_cb (GBinding *binding,
return TRUE;
}
-void
-editor_actions_bind (EHTMLEditor *editor)
+static gboolean
+e_html_editor_sensitize_html_actions_cb (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ /* It should be editable... */
+ if (g_value_get_boolean (from_value)) {
+ EHTMLEditor *editor = user_data;
+
+ /* ... and in the HTML mode */
+ g_value_set_boolean (to_value, e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_HTML);
+ } else {
+ g_value_set_boolean (to_value, FALSE);
+ }
+
+ return TRUE;
+}
+
+/**
+ * e_html_editor_util_new_mode_combobox:
+ *
+ * Creates a new combo box containing all composer modes.
+ *
+ * It's a descendant of #EActionComboBox, thus use e_action_combo_box_get_current_value()
+ * and e_action_combo_box_set_current_value() to get the currently selected mode.
+ *
+ * Returns: (transfer full): a new #EActionComboBox with composer modes
+ *
+ * Since: 3.44
+ **/
+EActionComboBox *
+e_html_editor_util_new_mode_combobox (void)
{
- GtkAction *action;
GtkActionGroup *action_group;
+ GtkAction *action;
+ GtkWidget *widget;
+
+ action_group = gtk_action_group_new ("core-mode-entries");
+
+ gtk_action_group_add_radio_actions (
+ action_group, core_mode_entries,
+ G_N_ELEMENTS (core_mode_entries),
+ E_CONTENT_EDITOR_MODE_HTML,
+ NULL, NULL);
+
+ action = gtk_action_group_get_action (action_group, "mode-html");
+
+ widget = e_action_combo_box_new_with_action (GTK_RADIO_ACTION (action));
+ gtk_combo_box_set_focus_on_click (GTK_COMBO_BOX (widget), FALSE);
+ gtk_widget_set_tooltip_text (widget, _("Editing Mode"));
+
+ g_object_set_data_full (G_OBJECT (widget), "core-mode-entries-action-group", action_group,
g_object_unref);
+
+ return E_ACTION_COMBO_BOX (widget);
+}
+
+void
+e_html_editor_actions_bind (EHTMLEditor *editor)
+{
EContentEditor *cnt_editor;
g_return_if_fail (E_IS_HTML_EDITOR (editor));
cnt_editor = e_html_editor_get_content_editor (editor);
- action_group = editor->priv->core_editor_actions;
- action = gtk_action_group_get_action (action_group, "mode-html");
- e_binding_bind_property (
- cnt_editor, "html-mode",
- action, "current-value",
- G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+ /* 'rb' for 'remember binding' */
+ #define rb(x) editor->priv->content_editor_bindings = g_slist_prepend
(editor->priv->content_editor_bindings, g_object_ref (x))
- /* Synchronize widget mode with the buttons */
- e_content_editor_set_html_mode (cnt_editor, TRUE);
+ rb (e_binding_bind_property (
+ editor->priv->fg_color_combo_box, "current-color",
+ cnt_editor, "font-color",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
+ rb (e_binding_bind_property (
+ cnt_editor, "editable",
+ editor->priv->fg_color_combo_box, "sensitive",
+ G_BINDING_SYNC_CREATE));
+ rb (e_binding_bind_property (
+ editor->priv->bg_color_combo_box, "current-color",
+ cnt_editor, "background-color",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
+ rb (e_binding_bind_property (
+ cnt_editor, "editable",
+ editor->priv->bg_color_combo_box, "sensitive",
+ G_BINDING_SYNC_CREATE));
- e_binding_bind_property (
+ rb (e_binding_bind_property (
cnt_editor, "can-redo",
ACTION (REDO), "sensitive",
- G_BINDING_SYNC_CREATE);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE));
+ rb (e_binding_bind_property (
cnt_editor, "can-undo",
ACTION (UNDO), "sensitive",
- G_BINDING_SYNC_CREATE);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE));
+ rb (e_binding_bind_property (
cnt_editor, "can-copy",
ACTION (COPY), "sensitive",
- G_BINDING_SYNC_CREATE);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE));
+ rb (e_binding_bind_property (
cnt_editor, "can-cut",
ACTION (CUT), "sensitive",
- G_BINDING_SYNC_CREATE);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE));
+ rb (e_binding_bind_property (
cnt_editor, "can-paste",
ACTION (PASTE), "sensitive",
- G_BINDING_SYNC_CREATE);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE));
+ rb (e_binding_bind_property (
cnt_editor, "can-paste",
ACTION (PASTE_QUOTE), "sensitive",
- G_BINDING_SYNC_CREATE);
+ G_BINDING_SYNC_CREATE));
/* This is connected to JUSTIFY_LEFT action only, but
* it automatically applies on all actions in the group. */
- e_binding_bind_property (
+ rb (e_binding_bind_property (
cnt_editor, "alignment",
ACTION (JUSTIFY_LEFT), "current-value",
- G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
+ rb (e_binding_bind_property (
cnt_editor, "bold",
ACTION (BOLD), "active",
- G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
+ rb (e_binding_bind_property (
cnt_editor, "font-size",
ACTION (FONT_SIZE_GROUP), "current-value",
- G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
+ rb (e_binding_bind_property (
cnt_editor, "block-format",
ACTION (STYLE_NORMAL), "current-value",
- G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
- e_binding_bind_property_full (
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
+ rb (e_binding_bind_property_full (
cnt_editor, "indent-level",
ACTION (INDENT), "sensitive",
G_BINDING_SYNC_CREATE,
e_html_editor_indent_level_to_bool_indent_cb,
- NULL, NULL, NULL);
- e_binding_bind_property_full (
+ NULL, NULL, NULL));
+ rb (e_binding_bind_property_full (
cnt_editor, "indent-level",
ACTION (UNINDENT), "sensitive",
G_BINDING_SYNC_CREATE,
e_html_editor_indent_level_to_bool_unindent_cb,
- NULL, NULL, NULL);
- e_binding_bind_property (
+ NULL, NULL, NULL));
+ rb (e_binding_bind_property (
cnt_editor, "italic",
ACTION (ITALIC), "active",
- G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
+ rb (e_binding_bind_property (
cnt_editor, "strikethrough",
ACTION (STRIKETHROUGH), "active",
- G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
+ rb (e_binding_bind_property (
cnt_editor, "underline",
ACTION (UNDERLINE), "active",
- G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
- e_binding_bind_property_full (
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL));
+ rb (e_binding_bind_property_full (
cnt_editor, "font-name",
editor->priv->font_name_combo_box, "active-id",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
e_html_editor_content_editor_font_name_to_combo_box,
NULL,
- NULL, NULL);
+ NULL, NULL));
/* Cannot use binding, due to subscript and superscript being mutually exclusive */
- g_signal_connect_object (ACTION (SUBSCRIPT), "toggled",
- G_CALLBACK (html_editor_actions_subscript_toggled_cb), editor, 0);
- g_signal_connect_object (cnt_editor, "notify::subscript",
+ editor->priv->subscript_notify_id = g_signal_connect_object (cnt_editor, "notify::subscript",
G_CALLBACK (html_editor_actions_notify_subscript_cb), editor, 0);
- g_signal_connect_object (ACTION (SUPERSCRIPT), "toggled",
- G_CALLBACK (html_editor_actions_superscript_toggled_cb), editor, 0);
- g_signal_connect_object (cnt_editor, "notify::superscript",
+ editor->priv->superscript_notify_id = g_signal_connect_object (cnt_editor, "notify::superscript",
G_CALLBACK (html_editor_actions_notify_superscript_cb), editor, 0);
- g_signal_connect_object (cnt_editor, "notify::html-mode",
- G_CALLBACK (html_editor_actions_notify_html_mode_cb), editor, 0);
-
/* Disable all actions and toolbars when editor is not editable */
- e_binding_bind_property (
+ rb (e_binding_bind_property (
cnt_editor, "editable",
editor->priv->core_editor_actions, "sensitive",
- G_BINDING_SYNC_CREATE);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE));
+ rb (e_binding_bind_property_full (
cnt_editor, "editable",
editor->priv->html_actions, "sensitive",
- G_BINDING_SYNC_CREATE);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE,
+ e_html_editor_sensitize_html_actions_cb,
+ NULL, editor, NULL));
+ rb (e_binding_bind_property (
cnt_editor, "editable",
editor->priv->spell_check_actions, "sensitive",
- G_BINDING_SYNC_CREATE);
- e_binding_bind_property (
+ G_BINDING_SYNC_CREATE));
+ rb (e_binding_bind_property (
cnt_editor, "editable",
editor->priv->suggestion_actions, "sensitive",
- G_BINDING_SYNC_CREATE);
+ G_BINDING_SYNC_CREATE));
+
+ #undef rb
+}
+
+void
+e_html_editor_actions_unbind (EHTMLEditor *editor)
+{
+ EContentEditor *cnt_editor;
+
+ g_slist_foreach (editor->priv->content_editor_bindings, (GFunc) g_binding_unbind, NULL);
+ g_slist_free_full (editor->priv->content_editor_bindings, g_object_unref);
+ editor->priv->content_editor_bindings = NULL;
+
+ cnt_editor = e_html_editor_get_content_editor (editor);
+
+ if (cnt_editor) {
+ e_signal_disconnect_notify_handler (cnt_editor, &editor->priv->subscript_notify_id);
+ e_signal_disconnect_notify_handler (cnt_editor, &editor->priv->superscript_notify_id);
+ }
}
void
-editor_actions_update_spellcheck_languages_menu (EHTMLEditor *editor,
- const gchar * const *languages)
+e_html_editor_actions_update_spellcheck_languages_menu (EHTMLEditor *editor,
+ const gchar * const *languages)
{
GHashTable *active;
GList *actions, *link;
diff --git a/src/e-util/e-html-editor-find-dialog.c b/src/e-util/e-html-editor-find-dialog.c
index fe063ccc70..f38e3883c4 100644
--- a/src/e-util/e-html-editor-find-dialog.c
+++ b/src/e-util/e-html-editor-find-dialog.c
@@ -56,13 +56,39 @@ reset_dialog (EHTMLEditorFindDialog *dialog)
gtk_widget_hide (dialog->priv->result_label);
}
+static void
+content_editor_find_done_cb (EContentEditor *cnt_editor,
+ guint match_count,
+ EHTMLEditorFindDialog *dialog)
+{
+ if (match_count) {
+ gtk_widget_hide (dialog->priv->result_label);
+ } else {
+ gtk_label_set_label (GTK_LABEL (dialog->priv->result_label), _("No match found"));
+ gtk_widget_show (dialog->priv->result_label);
+ }
+
+ gtk_widget_set_sensitive (dialog->priv->find_button, match_count > 0);
+}
+
static void
html_editor_find_dialog_hide (GtkWidget *widget)
{
EHTMLEditorFindDialog *dialog = E_HTML_EDITOR_FIND_DIALOG (widget);
+ g_warn_if_fail (dialog->priv->cnt_editor != NULL);
+
e_content_editor_on_dialog_close (dialog->priv->cnt_editor, E_CONTENT_EDITOR_DIALOG_FIND);
+ if (dialog->priv->find_done_handler_id > 0) {
+ g_signal_handler_disconnect (
+ dialog->priv->cnt_editor,
+ dialog->priv->find_done_handler_id);
+ dialog->priv->find_done_handler_id = 0;
+ }
+
+ dialog->priv->cnt_editor = NULL;
+
/* Chain up to parent's implementation */
GTK_WIDGET_CLASS (e_html_editor_find_dialog_parent_class)->hide (widget);
}
@@ -71,6 +97,19 @@ static void
html_editor_find_dialog_show (GtkWidget *widget)
{
EHTMLEditorFindDialog *dialog = E_HTML_EDITOR_FIND_DIALOG (widget);
+ EHTMLEditor *editor;
+ EContentEditor *cnt_editor;
+
+ g_warn_if_fail (dialog->priv->cnt_editor == NULL);
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ cnt_editor = e_html_editor_get_content_editor (editor);
+
+ dialog->priv->find_done_handler_id = g_signal_connect (
+ cnt_editor, "find-done",
+ G_CALLBACK (content_editor_find_done_cb), dialog);
+
+ dialog->priv->cnt_editor = cnt_editor;
reset_dialog (dialog);
gtk_widget_grab_focus (dialog->priv->entry);
@@ -81,21 +120,6 @@ html_editor_find_dialog_show (GtkWidget *widget)
GTK_WIDGET_CLASS (e_html_editor_find_dialog_parent_class)->show (widget);
}
-static void
-content_editor_find_done_cb (EContentEditor *cnt_editor,
- guint match_count,
- EHTMLEditorFindDialog *dialog)
-{
- if (match_count) {
- gtk_widget_hide (dialog->priv->result_label);
- } else {
- gtk_label_set_label (GTK_LABEL (dialog->priv->result_label), _("No match found"));
- gtk_widget_show (dialog->priv->result_label);
- }
-
- gtk_widget_set_sensitive (dialog->priv->find_button, match_count > 0);
-}
-
static void
html_editor_find_dialog_find_cb (EHTMLEditorFindDialog *dialog)
{
@@ -151,28 +175,6 @@ html_editor_find_dialog_dispose (GObject *object)
G_OBJECT_CLASS (e_html_editor_find_dialog_parent_class)->dispose (object);
}
-static void
-html_editor_find_dialog_constructed (GObject *object)
-{
- EHTMLEditor *editor;
- EHTMLEditorFindDialog *dialog;
- EContentEditor *cnt_editor;
-
- dialog = E_HTML_EDITOR_FIND_DIALOG (object);
- dialog->priv = E_HTML_EDITOR_FIND_DIALOG_GET_PRIVATE (dialog);
-
- editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
- cnt_editor = e_html_editor_get_content_editor (editor);
-
- dialog->priv->find_done_handler_id = g_signal_connect (
- cnt_editor, "find-done",
- G_CALLBACK (content_editor_find_done_cb), dialog);
-
- dialog->priv->cnt_editor = cnt_editor;
-
- G_OBJECT_CLASS (e_html_editor_find_dialog_parent_class)->constructed (object);
-}
-
static void
e_html_editor_find_dialog_class_init (EHTMLEditorFindDialogClass *class)
{
@@ -182,7 +184,6 @@ e_html_editor_find_dialog_class_init (EHTMLEditorFindDialogClass *class)
g_type_class_add_private (class, sizeof (EHTMLEditorFindDialogPrivate));
object_class = G_OBJECT_CLASS (class);
- object_class->constructed = html_editor_find_dialog_constructed;
object_class->dispose = html_editor_find_dialog_dispose;
widget_class = GTK_WIDGET_CLASS (class);
diff --git a/src/e-util/e-html-editor-manager.ui b/src/e-util/e-html-editor-manager.ui
index 3f7468d4de..885108bde2 100644
--- a/src/e-util/e-html-editor-manager.ui
+++ b/src/e-util/e-html-editor-manager.ui
@@ -51,6 +51,9 @@
<placeholder name='format-menu-top'/>
<menuitem action='mode-html'/>
<menuitem action='mode-plain'/>
+ <menuitem action='mode-markdown-plain'/>
+ <menuitem action='mode-markdown-html'/>
+ <menuitem action='mode-markdown'/>
<separator/>
<menu action='font-style-menu'>
<menuitem action='bold'/>
diff --git a/src/e-util/e-html-editor-private.h b/src/e-util/e-html-editor-private.h
index 032b77cdb3..909a603011 100644
--- a/src/e-util/e-html-editor-private.h
+++ b/src/e-util/e-html-editor-private.h
@@ -36,6 +36,7 @@
#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-util-enumtypes.h>
#ifdef HAVE_XFREE
#include <X11/XF86keysym.h>
@@ -47,6 +48,10 @@
G_BEGIN_DECLS
struct _EHTMLEditorPrivate {
+ EContentEditorMode mode;
+
+ GtkWidget *content_editors_box;
+
GtkUIManager *manager;
GtkActionGroup *core_actions;
GtkActionGroup *core_editor_actions;
@@ -64,6 +69,7 @@ struct _EHTMLEditorPrivate {
GtkWidget *activity_bar;
GtkWidget *alert_bar;
GtkWidget *edit_area;
+ GtkWidget *markdown_editor;
GtkWidget *find_dialog;
GtkWidget *replace_dialog;
@@ -80,6 +86,7 @@ struct _EHTMLEditorPrivate {
GtkWidget *fg_color_combo_box;
GtkWidget *bg_color_combo_box;
GtkWidget *mode_combo_box;
+ GtkToolItem *mode_tool_item;
GtkWidget *size_combo_box;
GtkWidget *style_combo_box;
GtkWidget *font_name_combo_box;
@@ -88,10 +95,15 @@ struct _EHTMLEditorPrivate {
GtkWidget *emoji_chooser;
GHashTable *cid_parts; /* gchar *cid: URI ~> CamelMimePart * */
- GHashTable *content_editors;
+ GHashTable *content_editors; /* gchar *name ~> EContentEditor * */
+ GHashTable *content_editors_for_mode; /* EContentEditorMode ~> EContentEditor *; pointers borrowed
from content_editors */
EContentEditor *use_content_editor;
+ GCancellable *mode_change_content_cancellable;
gchar *filename;
+ GSList *content_editor_bindings; /* reffed GBinding-s related to the EContentEditor */
+ gulong subscript_notify_id;
+ gulong superscript_notify_id;
guint spell_suggestions_merge_id;
guint recent_spell_languages_merge_id;
@@ -101,9 +113,10 @@ struct _EHTMLEditorPrivate {
gboolean paste_plain_prefer_pre;
};
-void editor_actions_init (EHTMLEditor *editor);
-void editor_actions_bind (EHTMLEditor *editor);
-void editor_actions_update_spellcheck_languages_menu
+void e_html_editor_actions_init (EHTMLEditor *editor);
+void e_html_editor_actions_bind (EHTMLEditor *editor);
+void e_html_editor_actions_unbind (EHTMLEditor *editor);
+void e_html_editor_actions_update_spellcheck_languages_menu
(EHTMLEditor *editor,
const gchar * const *languages);
const gchar * e_html_editor_get_content_editor_name
@@ -112,6 +125,9 @@ GtkWidget * e_html_editor_util_create_font_name_combo
(void);
gchar * e_html_editor_util_dup_font_id (GtkComboBox *combo_box,
const gchar *font_name);
+gboolean e_html_editor_has_editor_for_mode
+ (EHTMLEditor *editor,
+ EContentEditorMode mode);
G_END_DECLS
diff --git a/src/e-util/e-html-editor-replace-dialog.c b/src/e-util/e-html-editor-replace-dialog.c
index e33e7dfcbc..55f1af62ac 100644
--- a/src/e-util/e-html-editor-replace-dialog.c
+++ b/src/e-util/e-html-editor-replace-dialog.c
@@ -168,6 +168,23 @@ static void
html_editor_replace_dialog_show (GtkWidget *widget)
{
EHTMLEditorReplaceDialog *dialog = E_HTML_EDITOR_REPLACE_DIALOG (widget);
+ EHTMLEditor *editor;
+ EContentEditor *cnt_editor;
+
+ g_warn_if_fail (dialog->priv->cnt_editor == NULL);
+
+ editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
+ cnt_editor = e_html_editor_get_content_editor (editor);
+
+ dialog->priv->find_done_handler_id = g_signal_connect (
+ cnt_editor, "find-done",
+ G_CALLBACK (content_editor_find_done_cb), dialog);
+
+ dialog->priv->replace_all_done_handler_id = g_signal_connect (
+ cnt_editor, "replace-all-done",
+ G_CALLBACK (content_editor_replace_all_done_cb), dialog);
+
+ dialog->priv->cnt_editor = cnt_editor;
e_content_editor_on_dialog_open (dialog->priv->cnt_editor, E_CONTENT_EDITOR_DIALOG_REPLACE);
@@ -183,36 +200,28 @@ html_editor_replace_dialog_hide (GtkWidget *widget)
{
EHTMLEditorReplaceDialog *dialog = E_HTML_EDITOR_REPLACE_DIALOG (widget);
- e_content_editor_on_dialog_close (dialog->priv->cnt_editor, E_CONTENT_EDITOR_DIALOG_REPLACE);
-
- /* Chain up to parent implementation */
- GTK_WIDGET_CLASS (e_html_editor_replace_dialog_parent_class)->hide (widget);
-}
-
-static void
-html_editor_replace_dialog_constructed (GObject *object)
-{
- EContentEditor *cnt_editor;
- EHTMLEditor *editor;
- EHTMLEditorReplaceDialog *dialog;
-
- dialog = E_HTML_EDITOR_REPLACE_DIALOG (object);
- dialog->priv = E_HTML_EDITOR_REPLACE_DIALOG_GET_PRIVATE (dialog);
+ g_warn_if_fail (dialog->priv->cnt_editor != NULL);
- editor = e_html_editor_dialog_get_editor (E_HTML_EDITOR_DIALOG (dialog));
- cnt_editor = e_html_editor_get_content_editor (editor);
+ e_content_editor_on_dialog_close (dialog->priv->cnt_editor, E_CONTENT_EDITOR_DIALOG_REPLACE);
- dialog->priv->find_done_handler_id = g_signal_connect (
- cnt_editor, "find-done",
- G_CALLBACK (content_editor_find_done_cb), dialog);
+ if (dialog->priv->find_done_handler_id > 0) {
+ g_signal_handler_disconnect (
+ dialog->priv->cnt_editor,
+ dialog->priv->find_done_handler_id);
+ dialog->priv->find_done_handler_id = 0;
+ }
- dialog->priv->replace_all_done_handler_id = g_signal_connect (
- cnt_editor, "replace-all-done",
- G_CALLBACK (content_editor_replace_all_done_cb), dialog);
+ if (dialog->priv->replace_all_done_handler_id > 0) {
+ g_signal_handler_disconnect (
+ dialog->priv->cnt_editor,
+ dialog->priv->replace_all_done_handler_id);
+ dialog->priv->replace_all_done_handler_id = 0;
+ }
- dialog->priv->cnt_editor = cnt_editor;
+ dialog->priv->cnt_editor = NULL;
- G_OBJECT_CLASS (e_html_editor_replace_dialog_parent_class)->constructed (object);
+ /* Chain up to parent implementation */
+ GTK_WIDGET_CLASS (e_html_editor_replace_dialog_parent_class)->hide (widget);
}
static void
@@ -249,7 +258,6 @@ e_html_editor_replace_dialog_class_init (EHTMLEditorReplaceDialogClass *class)
g_type_class_add_private (class, sizeof (EHTMLEditorReplaceDialogPrivate));
object_class = G_OBJECT_CLASS (class);
- object_class->constructed = html_editor_replace_dialog_constructed;
object_class->dispose = html_editor_replace_dialog_dispose;
widget_class = GTK_WIDGET_CLASS (class);
diff --git a/src/e-util/e-html-editor.c b/src/e-util/e-html-editor.c
index 04a6434a6e..91fca9b100 100644
--- a/src/e-util/e-html-editor.c
+++ b/src/e-util/e-html-editor.c
@@ -34,8 +34,12 @@
#include "e-alert-sink.h"
#include "e-html-editor-private.h"
#include "e-content-editor.h"
+#include "e-markdown-editor.h"
#include "e-misc-utils.h"
#include "e-simple-async-result.h"
+#include "e-util-enumtypes.h"
+
+#define MARKDOWN_EDITOR_NAME "markdown"
#define E_HTML_EDITOR_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
@@ -70,6 +74,7 @@
enum {
PROP_0,
+ PROP_MODE,
PROP_FILENAME,
PROP_PASTE_PLAIN_PREFER_PRE
};
@@ -605,7 +610,7 @@ html_editor_spell_languages_changed (EHTMLEditor *editor)
E_HTML_EDITOR_SPELL_CHECK_DIALOG (
editor->priv->spell_check_dialog));
- editor_actions_update_spellcheck_languages_menu (editor, (const gchar * const *) languages);
+ e_html_editor_actions_update_spellcheck_languages_menu (editor, (const gchar * const *) languages);
g_clear_object (&spell_checker);
g_strfreev (languages);
}
@@ -760,6 +765,24 @@ html_editor_get_paste_plain_prefer_pre (EHTMLEditor *editor)
return editor->priv->paste_plain_prefer_pre;
}
+static gboolean
+e_html_editor_mode_to_bool_hide_in_markdown_cb (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ EContentEditorMode mode;
+
+ mode = g_value_get_enum (from_value);
+
+ g_value_set_boolean (to_value,
+ mode != E_CONTENT_EDITOR_MODE_MARKDOWN &&
+ mode != E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT &&
+ mode != E_CONTENT_EDITOR_MODE_MARKDOWN_HTML);
+
+ return TRUE;
+}
+
static void
html_editor_set_property (GObject *object,
guint property_id,
@@ -767,6 +790,12 @@ html_editor_set_property (GObject *object,
GParamSpec *pspec)
{
switch (property_id) {
+ case PROP_MODE:
+ e_html_editor_set_mode (
+ E_HTML_EDITOR (object),
+ g_value_get_enum (value));
+ return;
+
case PROP_FILENAME:
e_html_editor_set_filename (
E_HTML_EDITOR (object),
@@ -790,6 +819,12 @@ html_editor_get_property (GObject *object,
GParamSpec *pspec)
{
switch (property_id) {
+ case PROP_MODE:
+ g_value_set_enum (
+ value, e_html_editor_get_mode (
+ E_HTML_EDITOR (object)));
+ return;
+
case PROP_FILENAME:
g_value_set_string (
value, e_html_editor_get_filename (
@@ -822,7 +857,23 @@ html_editor_constructed (GObject *object)
e_extensible_load_extensions (E_EXTENSIBLE (object));
- editor_actions_init (editor);
+ /* Register the markdown editor */
+ priv->markdown_editor = g_object_ref_sink (e_markdown_editor_new ());
+ e_html_editor_register_content_editor (editor, MARKDOWN_EDITOR_NAME, E_CONTENT_EDITOR
(priv->markdown_editor));
+
+ /* Construct the main editing area. */
+ widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ "visible", TRUE,
+ NULL);
+ gtk_grid_attach (GTK_GRID (editor), widget, 0, 4, 1, 1);
+ priv->content_editors_box = widget;
+
+ e_html_editor_actions_init (editor);
priv->editor_layout_row = 2;
/* Tweak the main-toolbar style. */
@@ -862,31 +913,10 @@ html_editor_constructed (GObject *object)
priv->alert_bar = g_object_ref (widget);
/* EAlertBar controls its own visibility. */
- /* Construct the main editing area. */
+ /* Have the default editor added (it's done inside the function) */
widget = GTK_WIDGET (e_html_editor_get_content_editor (editor));
-
- /* Pack editors which implement GtkScrollable in a scrolled window */
- if (GTK_IS_SCROLLABLE (widget)) {
- GtkWidget *scrolled_window;
-
- 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_widget_show (scrolled_window);
-
- gtk_grid_attach (GTK_GRID (editor), scrolled_window, 0, 4, 1, 1);
-
- gtk_container_add (GTK_CONTAINER (scrolled_window), widget);
- } else {
- gtk_grid_attach (GTK_GRID (editor), widget, 0, 4, 1, 1);
- }
-
gtk_widget_show (widget);
- g_signal_connect (
- widget, "context-menu-requested",
- G_CALLBACK (html_editor_context_menu_requested_cb), editor);
-
/* Add some combo boxes to the "edit" toolbar. */
toolbar = GTK_TOOLBAR (priv->edit_toolbar);
@@ -913,6 +943,7 @@ html_editor_constructed (GObject *object)
gtk_widget_set_tooltip_text (widget, _("Editing Mode"));
gtk_toolbar_insert (toolbar, tool_item, 0);
priv->mode_combo_box = g_object_ref (widget);
+ priv->mode_tool_item = g_object_ref (tool_item);
gtk_widget_show_all (GTK_WIDGET (tool_item));
/* Add some combo boxes to the "html" toolbar. */
@@ -957,6 +988,41 @@ html_editor_constructed (GObject *object)
priv->font_name_combo_box = g_object_ref (widget);
gtk_widget_show_all (GTK_WIDGET (tool_item));
+ e_binding_bind_property_full (
+ editor, "mode",
+ E_HTML_EDITOR_ACTION (editor, "paragraph-style-menu"), "visible",
+ G_BINDING_SYNC_CREATE,
+ e_html_editor_mode_to_bool_hide_in_markdown_cb,
+ NULL, NULL, NULL);
+
+ e_binding_bind_property_full (
+ editor, "mode",
+ E_HTML_EDITOR_ACTION (editor, "justify-menu"), "visible",
+ G_BINDING_SYNC_CREATE,
+ e_html_editor_mode_to_bool_hide_in_markdown_cb,
+ NULL, NULL, NULL);
+
+ e_binding_bind_property_full (
+ editor, "mode",
+ E_HTML_EDITOR_ACTION_WRAP_LINES (editor), "visible",
+ G_BINDING_SYNC_CREATE,
+ e_html_editor_mode_to_bool_hide_in_markdown_cb,
+ NULL, NULL, NULL);
+
+ e_binding_bind_property_full (
+ editor, "mode",
+ E_HTML_EDITOR_ACTION_INDENT (editor), "visible",
+ G_BINDING_SYNC_CREATE,
+ e_html_editor_mode_to_bool_hide_in_markdown_cb,
+ NULL, NULL, NULL);
+
+ e_binding_bind_property_full (
+ editor, "mode",
+ E_HTML_EDITOR_ACTION_UNINDENT (editor), "visible",
+ G_BINDING_SYNC_CREATE,
+ e_html_editor_mode_to_bool_hide_in_markdown_cb,
+ NULL, NULL, NULL);
+
g_signal_connect_after (object, "realize", G_CALLBACK (html_editor_realize), NULL);
settings = e_util_ref_settings ("org.gnome.evolution.mail");
@@ -976,6 +1042,9 @@ html_editor_dispose (GObject *object)
priv = E_HTML_EDITOR_GET_PRIVATE (object);
+ if (priv->mode_change_content_cancellable)
+ g_cancellable_cancel (priv->mode_change_content_cancellable);
+
g_clear_object (&priv->manager);
g_clear_object (&priv->core_actions);
g_clear_object (&priv->core_editor_actions);
@@ -993,14 +1062,24 @@ html_editor_dispose (GObject *object)
g_clear_object (&priv->activity_bar);
g_clear_object (&priv->alert_bar);
g_clear_object (&priv->edit_area);
+ g_clear_object (&priv->markdown_editor);
g_clear_object (&priv->fg_color_combo_box);
g_clear_object (&priv->bg_color_combo_box);
g_clear_object (&priv->mode_combo_box);
+ g_clear_object (&priv->mode_tool_item);
g_clear_object (&priv->size_combo_box);
g_clear_object (&priv->font_name_combo_box);
g_clear_object (&priv->style_combo_box);
+ g_clear_object (&priv->mode_change_content_cancellable);
+
+ /* Do not unbind/disconnect signal handlers here, just free/unset them */
+ g_slist_free_full (priv->content_editor_bindings, g_object_unref);
+ priv->content_editor_bindings = NULL;
+ priv->subscript_notify_id = 0;
+ priv->superscript_notify_id = 0;
+
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_html_editor_parent_class)->dispose (object);
}
@@ -1012,6 +1091,7 @@ html_editor_finalize (GObject *object)
g_hash_table_destroy (editor->priv->cid_parts);
g_hash_table_destroy (editor->priv->content_editors);
+ g_hash_table_destroy (editor->priv->content_editors_for_mode);
/* Chain up to parent's method. */
G_OBJECT_CLASS (e_html_editor_parent_class)->finalize (object);
@@ -1049,6 +1129,19 @@ e_html_editor_class_init (EHTMLEditorClass *class)
class->update_actions = html_editor_update_actions;
class->spell_languages_changed = html_editor_spell_languages_changed;
+ g_object_class_install_property (
+ object_class,
+ PROP_MODE,
+ g_param_spec_enum (
+ "mode",
+ NULL,
+ NULL,
+ E_TYPE_CONTENT_EDITOR_MODE,
+ E_CONTENT_EDITOR_MODE_HTML,
+ G_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY |
+ G_PARAM_STATIC_STRINGS));
+
g_object_class_install_property (
object_class,
PROP_FILENAME,
@@ -1109,6 +1202,7 @@ e_html_editor_init (EHTMLEditor *editor)
priv = editor->priv;
+ priv->mode = E_CONTENT_EDITOR_MODE_HTML;
priv->manager = gtk_ui_manager_new ();
priv->core_actions = gtk_action_group_new ("core");
priv->core_editor_actions = gtk_action_group_new ("core-editor");
@@ -1120,6 +1214,7 @@ e_html_editor_init (EHTMLEditor *editor)
priv->suggestion_actions = gtk_action_group_new ("suggestion");
priv->cid_parts = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free,
g_object_unref);
priv->content_editors = g_hash_table_new_full (camel_strcase_hash, camel_strcase_equal, g_free, NULL);
+ priv->content_editors_for_mode = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
filename = html_editor_find_ui_file ("e-html-editor-manager.ui");
if (!gtk_ui_manager_add_ui_from_file (priv->manager, filename, &error)) {
@@ -1142,23 +1237,12 @@ e_html_editor_content_editor_initialized (EContentEditor *content_editor,
g_return_if_fail (E_IS_HTML_EDITOR (html_editor));
g_return_if_fail (content_editor == e_html_editor_get_content_editor (html_editor));
- e_binding_bind_property (
- html_editor->priv->fg_color_combo_box, "current-color",
- content_editor, "font-color",
- G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
- e_binding_bind_property (
- content_editor, "editable",
- html_editor->priv->fg_color_combo_box, "sensitive",
- G_BINDING_SYNC_CREATE);
- e_binding_bind_property (
- html_editor->priv->bg_color_combo_box, "current-color",
- content_editor, "background-color",
- G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
- e_binding_bind_property (
- content_editor, "editable",
- html_editor->priv->bg_color_combo_box, "sensitive",
- G_BINDING_SYNC_CREATE);
- editor_actions_bind (html_editor);
+ /* Synchronize widget mode with the buttons */
+ e_html_editor_set_mode (html_editor, E_CONTENT_EDITOR_MODE_HTML);
+
+ /* Make sure the actions did bind, even when the content editor did not change */
+ e_html_editor_actions_unbind (html_editor);
+ e_html_editor_actions_bind (html_editor);
g_object_set (G_OBJECT (content_editor),
"halign", GTK_ALIGN_FILL,
@@ -1227,53 +1311,268 @@ e_html_editor_new_finish (GAsyncResult *result,
}
/**
- * e_html_editor_get_content_editor:
+ * e_html_editor_connect_focus_tracker:
* @editor: an #EHTMLEditor
+ * @focus_tracker: an #EFocusTracker
*
- * Returns instance of #EContentEditor used in the @editor.
- */
-EContentEditor *
-e_html_editor_get_content_editor (EHTMLEditor *editor)
+ * Connects @editor actions and widgets to the @focus_tracker.
+ *
+ * Since: 3.44
+ **/
+void
+e_html_editor_connect_focus_tracker (EHTMLEditor *editor,
+ EFocusTracker *focus_tracker)
+{
+ g_return_if_fail (E_IS_HTML_EDITOR (editor));
+ g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+ e_focus_tracker_set_cut_clipboard_action (focus_tracker,
+ e_html_editor_get_action (editor, "cut"));
+
+ e_focus_tracker_set_copy_clipboard_action (focus_tracker,
+ e_html_editor_get_action (editor, "copy"));
+
+ e_focus_tracker_set_paste_clipboard_action (focus_tracker,
+ e_html_editor_get_action (editor, "paste"));
+
+ e_focus_tracker_set_select_all_action (focus_tracker,
+ e_html_editor_get_action (editor, "select-all"));
+
+ e_focus_tracker_set_undo_action (focus_tracker,
+ e_html_editor_get_action (editor, "undo"));
+
+ e_focus_tracker_set_redo_action (focus_tracker,
+ e_html_editor_get_action (editor, "redo"));
+
+ e_markdown_editor_connect_focus_tracker (E_MARKDOWN_EDITOR (editor->priv->markdown_editor),
focus_tracker);
+}
+
+/**
+ * e_html_editor_get_content_box:
+ * @editor: an #EHTMLEditor
+ *
+ * Returns: (transfer none): the content box, the content editors are
+ * packed into.
+ *
+ * Since: 3.44
+ **/
+GtkWidget *
+e_html_editor_get_content_box (EHTMLEditor *editor)
{
g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
- if (!editor->priv->use_content_editor) {
- GSettings *settings;
- gchar *name;
+ return editor->priv->content_editors_box;
+}
+
+static void
+e_html_editor_content_editor_notify_mode_cb (GObject *object,
+ GParamSpec *param,
+ gpointer user_data)
+{
+ EHTMLEditor *editor = user_data;
+
+ g_return_if_fail (E_IS_HTML_EDITOR (editor));
+ g_return_if_fail (E_IS_CONTENT_EDITOR (object));
+
+ if (E_CONTENT_EDITOR (object) == e_html_editor_get_content_editor (editor)) {
+ EContentEditorMode mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+
+ g_object_get (object, "mode", &mode, NULL);
+
+ e_html_editor_set_mode (editor, mode);
+ }
+}
+
+static EContentEditor *
+e_html_editor_get_content_editor_for_mode (EHTMLEditor *editor,
+ EContentEditorMode mode)
+{
+ EContentEditor *cnt_editor;
+ GSettings *settings;
+ const gchar *mode_name = NULL;
+ gchar *name;
+
+ if (!g_hash_table_size (editor->priv->content_editors))
+ return NULL;
+
+ cnt_editor = g_hash_table_lookup (editor->priv->content_editors_for_mode, GINT_TO_POINTER (mode));
+
+ if (cnt_editor)
+ return cnt_editor;
+
+ switch (mode) {
+ case E_CONTENT_EDITOR_MODE_UNKNOWN:
+ g_warn_if_reached ();
+ break;
+ case E_CONTENT_EDITOR_MODE_PLAIN_TEXT:
+ mode_name = "plain";
+ break;
+ case E_CONTENT_EDITOR_MODE_HTML:
+ mode_name = "html";
+ break;
+ case E_CONTENT_EDITOR_MODE_MARKDOWN:
+ mode_name = "markdown";
+ break;
+ case E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT:
+ mode_name = "markdown-plain";
+ break;
+ case E_CONTENT_EDITOR_MODE_MARKDOWN_HTML:
+ mode_name = "markdown-html";
+ break;
+ }
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+ name = g_settings_get_string (settings, "composer-editor");
+ g_clear_object (&settings);
+
+ if (name && *name && mode_name) {
+ gchar **split_names;
+ gint ii, mode_name_len = strlen (mode_name);
- if (!g_hash_table_size (editor->priv->content_editors))
- return NULL;
+ split_names = g_strsplit (name, ",", -1);
- settings = e_util_ref_settings ("org.gnome.evolution.mail");
- name = g_settings_get_string (settings, "composer-editor");
- g_clear_object (&settings);
+ /* first round with the mode-specific overrides */
+ for (ii = 0; split_names && split_names[ii] && !cnt_editor; ii++) {
+ const gchar *check_name = split_names[ii];
- if (name)
- editor->priv->use_content_editor = g_hash_table_lookup
(editor->priv->content_editors, name);
+ if (g_ascii_strncasecmp (check_name, mode_name, mode_name_len) == 0 &&
+ check_name[mode_name_len] == ':') {
+ cnt_editor = g_hash_table_lookup (editor->priv->content_editors, check_name +
mode_name_len + 1);
- g_free (name);
+ if (cnt_editor && !e_content_editor_supports_mode (cnt_editor, mode))
+ cnt_editor = NULL;
+ }
+ }
+
+ /* second round without the mode-specific overrides */
+ for (ii = 0; split_names && split_names[ii] && !cnt_editor; ii++) {
+ const gchar *check_name = split_names[ii];
+
+ cnt_editor = g_hash_table_lookup (editor->priv->content_editors, check_name);
+
+ if (cnt_editor && !e_content_editor_supports_mode (cnt_editor, mode))
+ cnt_editor = NULL;
+ }
+
+ g_strfreev (split_names);
+ }
- if (!editor->priv->use_content_editor)
- editor->priv->use_content_editor = g_hash_table_lookup
(editor->priv->content_editors, DEFAULT_CONTENT_EDITOR_NAME);
+ g_free (name);
- if (!editor->priv->use_content_editor) {
- GHashTableIter iter;
- gpointer key, value;
+ if (!cnt_editor) {
+ if (mode == E_CONTENT_EDITOR_MODE_MARKDOWN ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML)
+ cnt_editor = g_hash_table_lookup (editor->priv->content_editors,
MARKDOWN_EDITOR_NAME);
+ else
+ cnt_editor = g_hash_table_lookup (editor->priv->content_editors,
DEFAULT_CONTENT_EDITOR_NAME);
- g_hash_table_iter_init (&iter, editor->priv->content_editors);
- if (g_hash_table_iter_next (&iter, &key, &value)) {
- editor->priv->use_content_editor = value;
+ if (cnt_editor && !e_content_editor_supports_mode (cnt_editor, mode))
+ cnt_editor = NULL;
+ }
+
+ if (!cnt_editor) {
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, editor->priv->content_editors);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ if (e_content_editor_supports_mode (value, mode)) {
+ cnt_editor = value;
+ break;
}
}
+ }
- if (editor->priv->use_content_editor) {
- e_content_editor_setup_editor (editor->priv->use_content_editor, editor);
+ if (cnt_editor) {
+ GHashTableIter iter;
+ gpointer value;
- g_signal_connect_swapped (editor->priv->use_content_editor, "ref-mime-part",
- G_CALLBACK (e_html_editor_ref_cid_part), editor);
+ g_hash_table_iter_init (&iter, editor->priv->content_editors_for_mode);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ /* The editor can be used for multiple modes and it is already packed in the content
box. */
+ if (value == cnt_editor) {
+ g_hash_table_insert (editor->priv->content_editors_for_mode, GINT_TO_POINTER
(mode), cnt_editor);
+ return cnt_editor;
+ }
}
}
+ if (cnt_editor) {
+ e_content_editor_setup_editor (cnt_editor, editor);
+
+ g_signal_connect_swapped (cnt_editor, "ref-mime-part",
+ G_CALLBACK (e_html_editor_ref_cid_part), editor);
+
+ e_signal_connect_notify (cnt_editor, "notify::mode",
+ G_CALLBACK (e_html_editor_content_editor_notify_mode_cb), editor);
+
+ /* Pack editors which implement GtkScrollable in a scrolled window */
+ if (GTK_IS_SCROLLABLE (cnt_editor)) {
+ GtkWidget *scrolled_window;
+
+ 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_box_pack_start (GTK_BOX (editor->priv->content_editors_box), scrolled_window,
TRUE, TRUE, 0);
+
+ gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (cnt_editor));
+
+ e_binding_bind_property (cnt_editor, "visible",
+ scrolled_window, "visible",
+ G_BINDING_SYNC_CREATE);
+ } else {
+ gtk_box_pack_start (GTK_BOX (editor->priv->content_editors_box), GTK_WIDGET
(cnt_editor), TRUE, TRUE, 0);
+ }
+
+ g_signal_connect (
+ cnt_editor, "context-menu-requested",
+ G_CALLBACK (html_editor_context_menu_requested_cb), editor);
+
+ g_hash_table_insert (editor->priv->content_editors_for_mode, GINT_TO_POINTER (mode),
cnt_editor);
+ }
+
+ return cnt_editor;
+}
+
+gboolean
+e_html_editor_has_editor_for_mode (EHTMLEditor *editor,
+ EContentEditorMode mode)
+{
+ GHashTableIter iter;
+ gpointer value;
+
+ g_return_val_if_fail (E_IS_HTML_EDITOR (editor), FALSE);
+
+ g_hash_table_iter_init (&iter, editor->priv->content_editors);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ EContentEditor *cnt_editor = value;
+
+ if (e_content_editor_supports_mode (cnt_editor, mode))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * e_html_editor_get_content_editor:
+ * @editor: an #EHTMLEditor
+ *
+ * Returns instance of #EContentEditor used in the @editor.
+ */
+EContentEditor *
+e_html_editor_get_content_editor (EHTMLEditor *editor)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR (editor), NULL);
+
+ if (!editor->priv->use_content_editor) {
+ editor->priv->use_content_editor =
+ e_html_editor_get_content_editor_for_mode (editor, editor->priv->mode);
+ }
+
return editor->priv->use_content_editor;
}
@@ -1300,6 +1599,25 @@ e_html_editor_get_content_editor_name (EHTMLEditor *editor)
return NULL;
}
+static void
+e_html_editor_content_changed_cb (EContentEditor *cnt_editor,
+ gpointer user_data)
+{
+ EHTMLEditor *editor = user_data;
+
+ g_return_if_fail (E_IS_HTML_EDITOR (editor));
+
+ if (editor->priv->mode_change_content_cancellable) {
+ if (cnt_editor == editor->priv->use_content_editor) {
+ g_cancellable_cancel (editor->priv->mode_change_content_cancellable);
+ g_clear_object (&editor->priv->mode_change_content_cancellable);
+ }
+ }
+
+ g_signal_handlers_disconnect_by_func (cnt_editor,
+ G_CALLBACK (e_html_editor_content_changed_cb), editor);
+}
+
void
e_html_editor_register_content_editor (EHTMLEditor *editor,
const gchar *name,
@@ -1321,6 +1639,189 @@ e_html_editor_register_content_editor (EHTMLEditor *editor,
}
}
+static void
+e_html_editor_update_content_on_mode_change_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GWeakRef *weak_ref = user_data;
+ EContentEditorContentHash *content_hash;
+ EContentEditor *cnt_editor;
+ EHTMLEditor *editor;
+
+ g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
+ g_return_if_fail (weak_ref != NULL);
+
+ editor = g_weak_ref_get (weak_ref);
+
+ e_weak_ref_free (weak_ref);
+
+ if (!editor)
+ return;
+
+ g_clear_object (&editor->priv->mode_change_content_cancellable);
+
+ cnt_editor = E_CONTENT_EDITOR (source_object);
+ content_hash = e_content_editor_get_content_finish (cnt_editor, result, NULL);
+
+ if (content_hash) {
+ gpointer text;
+
+ text = e_content_editor_util_get_content_data (content_hash,
E_CONTENT_EDITOR_GET_TO_SEND_HTML);
+
+ if (editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML && text) {
+ e_content_editor_insert_content (editor->priv->use_content_editor, text,
+ E_CONTENT_EDITOR_INSERT_CONVERT |
+ E_CONTENT_EDITOR_INSERT_TEXT_HTML |
+ E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
+ } else {
+ text = e_content_editor_util_get_content_data (content_hash,
E_CONTENT_EDITOR_GET_TO_SEND_PLAIN);
+
+ if (text) {
+ e_content_editor_insert_content (editor->priv->use_content_editor, text,
+ E_CONTENT_EDITOR_INSERT_CONVERT |
+ E_CONTENT_EDITOR_INSERT_TEXT_PLAIN |
+ E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
+ }
+ }
+
+ e_content_editor_clear_undo_redo_history (editor->priv->use_content_editor);
+
+ e_content_editor_util_free_content_hash (content_hash);
+ }
+
+ g_object_unref (editor);
+}
+
+/**
+ * e_html_editor_get_mode:
+ * @editor: an #EHTMLEditor
+ *
+ * Returns: Current editor mode, as an #EContentEditorMode
+ *
+ * Since: 3.44
+ **/
+EContentEditorMode
+e_html_editor_get_mode (EHTMLEditor *editor)
+{
+ g_return_val_if_fail (E_IS_HTML_EDITOR (editor), E_CONTENT_EDITOR_MODE_PLAIN_TEXT);
+
+ return editor->priv->mode;
+}
+
+/**
+ * e_html_editor_set_mode:
+ * @editor: an #EHTMLEditor
+ * @mode: an #EContentEditorMode
+ *
+ * Sets the editor mode.
+ *
+ * Since: 3.44
+ **/
+void
+e_html_editor_set_mode (EHTMLEditor *editor,
+ EContentEditorMode mode)
+{
+ EContentEditor *cnt_editor;
+
+ g_return_if_fail (E_IS_HTML_EDITOR (editor));
+
+ if (mode == E_CONTENT_EDITOR_MODE_UNKNOWN)
+ mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+
+ /* In case the same mode is set, but no editor is assigned, then assign it */
+ if (editor->priv->mode == mode && editor->priv->use_content_editor != NULL)
+ return;
+
+ if (editor->priv->mode_change_content_cancellable) {
+ g_cancellable_cancel (editor->priv->mode_change_content_cancellable);
+ g_clear_object (&editor->priv->mode_change_content_cancellable);
+ }
+
+ cnt_editor = e_html_editor_get_content_editor_for_mode (editor, mode);
+
+ if (cnt_editor) {
+ gboolean editor_changed = cnt_editor != editor->priv->use_content_editor;
+
+ if (editor_changed) {
+ EContentEditorInterface *iface;
+ gboolean is_focused = FALSE;
+
+ if (editor->priv->use_content_editor) {
+ e_html_editor_actions_unbind (editor);
+
+ is_focused = e_content_editor_is_focus (editor->priv->use_content_editor);
+
+ editor->priv->mode_change_content_cancellable = g_cancellable_new ();
+
+ g_signal_connect_object (cnt_editor, "content-changed",
+ G_CALLBACK (e_html_editor_content_changed_cb), editor, 0);
+
+ /* Transfer also the content between editors */
+ e_content_editor_get_content (editor->priv->use_content_editor,
+ E_CONTENT_EDITOR_GET_TO_SEND_HTML |
E_CONTENT_EDITOR_GET_TO_SEND_PLAIN,
+ "localhost", editor->priv->mode_change_content_cancellable,
+ e_html_editor_update_content_on_mode_change_cb,
+ e_weak_ref_new (editor));
+
+ gtk_widget_hide (GTK_WIDGET (editor->priv->use_content_editor));
+
+ if (E_IS_MARKDOWN_EDITOR (editor->priv->use_content_editor)) {
+ EMarkdownEditor *markdown_editor;
+ GtkToolbar *toolbar;
+
+ markdown_editor = E_MARKDOWN_EDITOR
(editor->priv->use_content_editor);
+
+ e_markdown_editor_set_preview_mode (markdown_editor, FALSE);
+
+ toolbar = e_markdown_editor_get_action_toolbar (markdown_editor);
+ gtk_container_remove (GTK_CONTAINER (toolbar), GTK_WIDGET
(editor->priv->mode_tool_item));
+
+ toolbar = GTK_TOOLBAR (editor->priv->edit_toolbar);
+ gtk_toolbar_insert (toolbar, editor->priv->mode_tool_item, 0);
+
+ gtk_widget_show (GTK_WIDGET (editor->priv->edit_toolbar));
+ }
+ }
+
+ gtk_widget_show (GTK_WIDGET (cnt_editor));
+
+ if (E_IS_MARKDOWN_EDITOR (cnt_editor)) {
+ GtkToolbar *toolbar;
+
+ toolbar = GTK_TOOLBAR (editor->priv->edit_toolbar);
+ gtk_container_remove (GTK_CONTAINER (toolbar), GTK_WIDGET
(editor->priv->mode_tool_item));
+
+ toolbar = e_markdown_editor_get_action_toolbar (E_MARKDOWN_EDITOR
(cnt_editor));
+ gtk_toolbar_insert (toolbar, editor->priv->mode_tool_item, 0);
+
+ gtk_widget_hide (GTK_WIDGET (editor->priv->edit_toolbar));
+ }
+
+ if (is_focused)
+ e_content_editor_grab_focus (cnt_editor);
+
+ /* Disable spell-check dialog when the content editor doesn't
+ support moving between misspelled words. */
+ iface = E_CONTENT_EDITOR_GET_IFACE (cnt_editor);
+
+ gtk_action_set_visible (e_html_editor_get_action (editor, "spell-check"),
+ iface && iface->spell_check_next_word && iface->spell_check_prev_word);
+
+ e_content_editor_clear_undo_redo_history (cnt_editor);
+ }
+
+ editor->priv->mode = mode;
+ editor->priv->use_content_editor = cnt_editor;
+
+ if (editor_changed)
+ e_html_editor_actions_bind (editor);
+
+ g_object_set (G_OBJECT (cnt_editor), "mode", mode, NULL);
+ g_object_notify (G_OBJECT (editor), "mode");
+ }
+}
+
/**
* e_html_editor_get_ui_manager:
* @editor: an #EHTMLEditor
diff --git a/src/e-util/e-html-editor.h b/src/e-util/e-html-editor.h
index b96ab542f9..b30250bdf3 100644
--- a/src/e-util/e-html-editor.h
+++ b/src/e-util/e-html-editor.h
@@ -26,9 +26,11 @@
#define E_HTML_EDITOR_H
#include <gtk/gtk.h>
+#include <e-util/e-action-combo-box.h>
#include <e-util/e-activity.h>
#include <e-util/e-activity-bar.h>
#include <e-util/e-content-editor.h>
+#include <e-util/e-focus-tracker.h>
/* Standard GObject macros */
#define E_TYPE_HTML_EDITOR \
@@ -78,6 +80,10 @@ void e_html_editor_new (GAsyncReadyCallback callback,
gpointer user_data);
GtkWidget * e_html_editor_new_finish (GAsyncResult *result,
GError **error);
+void e_html_editor_connect_focus_tracker
+ (EHTMLEditor *editor,
+ EFocusTracker *focus_tracker);
+GtkWidget * e_html_editor_get_content_box (EHTMLEditor *editor);
EContentEditor *
e_html_editor_get_content_editor
(EHTMLEditor *editor);
@@ -85,6 +91,10 @@ void e_html_editor_register_content_editor
(EHTMLEditor *editor,
const gchar *name,
EContentEditor *cnt_editor);
+EContentEditorMode
+ e_html_editor_get_mode (EHTMLEditor *editor);
+void e_html_editor_set_mode (EHTMLEditor *editor,
+ EContentEditorMode mode);
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,
@@ -129,6 +139,9 @@ void e_html_editor_remove_all_cid_parts
CamelMimePart * e_html_editor_ref_cid_part (EHTMLEditor *editor,
const gchar *cid_uri);
+EActionComboBox *
+ e_html_editor_util_new_mode_combobox
+ (void);
G_END_DECLS
#endif /* E_HTML_EDITOR_H */
diff --git a/src/e-util/e-mail-signature-combo-box.c b/src/e-util/e-mail-signature-combo-box.c
index 7dac48bc1e..34c313590a 100644
--- a/src/e-util/e-mail-signature-combo-box.c
+++ b/src/e-util/e-mail-signature-combo-box.c
@@ -618,7 +618,7 @@ struct _LoadContext {
GCancellable *cancellable;
gchar *contents;
gsize length;
- gboolean is_html;
+ EContentEditorMode editor_mode;
};
static void
@@ -708,7 +708,7 @@ mail_signature_combo_box_autogenerate (EMailSignatureComboBox *combo_box,
context->length = buffer->len;
context->contents = g_string_free (buffer, FALSE);
- context->is_html = TRUE;
+ context->editor_mode = E_CONTENT_EDITOR_MODE_HTML;
g_object_unref (source);
}
@@ -740,7 +740,17 @@ mail_signature_combo_box_load_cb (ESource *source,
extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
extension = e_source_get_extension (source, extension_name);
mime_type = e_source_mail_signature_get_mime_type (extension);
- context->is_html = (g_strcmp0 (mime_type, "text/html") == 0);
+
+ if (g_strcmp0 (mime_type, "text/html") == 0)
+ context->editor_mode = E_CONTENT_EDITOR_MODE_HTML;
+ else if (g_strcmp0 (mime_type, "text/markdown") == 0)
+ context->editor_mode = E_CONTENT_EDITOR_MODE_MARKDOWN;
+ else if (g_strcmp0 (mime_type, "text/markdown-plain") == 0)
+ context->editor_mode = E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT;
+ else if (g_strcmp0 (mime_type, "text/markdown-html") == 0)
+ context->editor_mode = E_CONTENT_EDITOR_MODE_MARKDOWN_HTML;
+ else
+ context->editor_mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
g_simple_async_result_complete (simple);
@@ -810,7 +820,7 @@ e_mail_signature_combo_box_load_selected_finish (EMailSignatureComboBox *combo_b
GAsyncResult *result,
gchar **contents,
gsize *length,
- gboolean *is_html,
+ EContentEditorMode *out_editor_mode,
GError **error)
{
GSimpleAsyncResult *simple;
@@ -838,8 +848,8 @@ e_mail_signature_combo_box_load_selected_finish (EMailSignatureComboBox *combo_b
if (length != NULL)
*length = context->length;
- if (is_html != NULL)
- *is_html = context->is_html;
+ if (out_editor_mode)
+ *out_editor_mode = context->editor_mode;
return TRUE;
}
diff --git a/src/e-util/e-mail-signature-combo-box.h b/src/e-util/e-mail-signature-combo-box.h
index 62c440aad7..ba88019a42 100644
--- a/src/e-util/e-mail-signature-combo-box.h
+++ b/src/e-util/e-mail-signature-combo-box.h
@@ -24,6 +24,7 @@
#include <gtk/gtk.h>
#include <libedataserver/libedataserver.h>
+#include <e-util/e-util-enums.h>
/* Standard GObject macros */
#define E_TYPE_MAIL_SIGNATURE_COMBO_BOX \
@@ -101,7 +102,7 @@ gboolean e_mail_signature_combo_box_load_selected_finish
GAsyncResult *result,
gchar **contents,
gsize *length,
- gboolean *is_html,
+ EContentEditorMode *out_editor_mode,
GError **error);
G_END_DECLS
diff --git a/src/e-util/e-mail-signature-editor.c b/src/e-util/e-mail-signature-editor.c
index 38d8d2a729..7d1d88342d 100644
--- a/src/e-util/e-mail-signature-editor.c
+++ b/src/e-util/e-mail-signature-editor.c
@@ -50,6 +50,7 @@ struct _AsyncContext {
ESource *source;
GCancellable *cancellable;
EContentEditorGetContentFlags contents_flag;
+ EContentEditorMode editor_mode;
gchar *contents;
gsize length;
GDestroyNotify destroy_contents;
@@ -108,13 +109,13 @@ mail_signature_editor_loaded_cb (GObject *object,
{
EHTMLEditor *editor;
EContentEditor *cnt_editor;
+ EContentEditorMode mode;
ESource *source;
EMailSignatureEditor *window;
ESourceMailSignature *extension;
const gchar *extension_name;
const gchar *mime_type;
gchar *contents = NULL;
- gboolean is_html;
GError *error = NULL;
source = E_SOURCE (object);
@@ -147,15 +148,24 @@ mail_signature_editor_loaded_cb (GObject *object,
extension_name = E_SOURCE_EXTENSION_MAIL_SIGNATURE;
extension = e_source_get_extension (source, extension_name);
mime_type = e_source_mail_signature_get_mime_type (extension);
- is_html = (g_strcmp0 (mime_type, "text/html") == 0);
+ if (g_strcmp0 (mime_type, "text/html") == 0)
+ mode = E_CONTENT_EDITOR_MODE_HTML;
+ else if (g_strcmp0 (mime_type, "text/markdown") == 0)
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN;
+ else if (g_strcmp0 (mime_type, "text/markdown-plain") == 0)
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT;
+ else if (g_strcmp0 (mime_type, "text/markdown-html") == 0)
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_HTML;
+ else
+ mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
editor = e_mail_signature_editor_get_editor (window);
+ e_html_editor_set_mode (editor, mode);
cnt_editor = e_html_editor_get_content_editor (editor);
- e_content_editor_set_html_mode (cnt_editor, is_html);
- if (is_html) {
+ if (mode == E_CONTENT_EDITOR_MODE_HTML) {
if (strstr (contents, "data-evo-signature-plain-text-mode"))
- e_content_editor_set_html_mode (cnt_editor, FALSE);
+ e_html_editor_set_mode (editor, E_CONTENT_EDITOR_MODE_PLAIN_TEXT);
e_content_editor_insert_content (
cnt_editor,
@@ -597,17 +607,7 @@ mail_signature_editor_constructed (GObject *object)
/* Configure an EFocusTracker to manage selection actions. */
focus_tracker = e_focus_tracker_new (GTK_WINDOW (window));
- action = e_html_editor_get_action (editor, "cut");
- e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
-
- action = e_html_editor_get_action (editor, "copy");
- e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
-
- action = e_html_editor_get_action (editor, "paste");
- e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
-
- action = e_html_editor_get_action (editor, "select-all");
- e_focus_tracker_set_select_all_action (focus_tracker, action);
+ e_html_editor_connect_focus_tracker (editor, focus_tracker);
window->priv->focus_tracker = focus_tracker;
@@ -917,6 +917,7 @@ mail_signature_editor_content_hash_ready_cb (GObject *source_object,
EContentEditorContentHash *content_hash;
ESourceMailSignature *extension;
AsyncContext *async_context;
+ const gchar *mime_type = "text/plain";
GError *error = NULL;
g_return_if_fail (E_IS_CONTENT_EDITOR (source_object));
@@ -946,9 +947,29 @@ mail_signature_editor_content_hash_ready_cb (GObject *source_object,
async_context->length = strlen (async_context->contents);
+ switch (async_context->editor_mode) {
+ case E_CONTENT_EDITOR_MODE_UNKNOWN:
+ g_warn_if_reached ();
+ break;
+ case E_CONTENT_EDITOR_MODE_PLAIN_TEXT:
+ mime_type = "text/plain";
+ break;
+ case E_CONTENT_EDITOR_MODE_HTML:
+ mime_type = "text/html";
+ break;
+ case E_CONTENT_EDITOR_MODE_MARKDOWN:
+ mime_type = "text/markdown";
+ break;
+ case E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT:
+ mime_type = "text/markdown-plain";
+ break;
+ case E_CONTENT_EDITOR_MODE_MARKDOWN_HTML:
+ mime_type = "text/markdown-html";
+ break;
+ }
+
extension = e_source_get_extension (async_context->source, E_SOURCE_EXTENSION_MAIL_SIGNATURE);
- e_source_mail_signature_set_mime_type (extension,
- async_context->contents_flag == E_CONTENT_EDITOR_GET_RAW_BODY_HTML ? "text/html" :
"text/plain");
+ e_source_mail_signature_set_mime_type (extension, mime_type);
e_source_registry_commit_source (
async_context->registry, async_context->source,
@@ -981,7 +1002,9 @@ e_mail_signature_editor_commit (EMailSignatureEditor *window,
async_context = g_slice_new0 (AsyncContext);
async_context->registry = g_object_ref (registry);
async_context->source = g_object_ref (source);
- async_context->contents_flag = e_content_editor_get_html_mode (cnt_editor) ?
E_CONTENT_EDITOR_GET_RAW_BODY_HTML : E_CONTENT_EDITOR_GET_TO_SEND_PLAIN;
+ async_context->editor_mode = e_html_editor_get_mode (editor);
+ async_context->contents_flag = async_context->editor_mode == E_CONTENT_EDITOR_MODE_HTML ?
+ E_CONTENT_EDITOR_GET_RAW_BODY_HTML : E_CONTENT_EDITOR_GET_TO_SEND_PLAIN;
if (G_IS_CANCELLABLE (cancellable))
async_context->cancellable = g_object_ref (cancellable);
diff --git a/src/e-util/e-mail-signature-manager.c b/src/e-util/e-mail-signature-manager.c
index b414828d1f..e1d7cca68d 100644
--- a/src/e-util/e-mail-signature-manager.c
+++ b/src/e-util/e-mail-signature-manager.c
@@ -46,12 +46,12 @@ struct _EMailSignatureManagerPrivate {
GtkWidget *preview; /* not referenced */
GtkWidget *preview_frame; /* not referenced */
- gboolean prefer_html;
+ EContentEditorMode prefer_mode;
};
enum {
PROP_0,
- PROP_PREFER_HTML,
+ PROP_PREFER_MODE,
PROP_REGISTRY
};
@@ -188,10 +188,10 @@ mail_signature_manager_set_property (GObject *object,
GParamSpec *pspec)
{
switch (property_id) {
- case PROP_PREFER_HTML:
- e_mail_signature_manager_set_prefer_html (
+ case PROP_PREFER_MODE:
+ e_mail_signature_manager_set_prefer_mode (
E_MAIL_SIGNATURE_MANAGER (object),
- g_value_get_boolean (value));
+ g_value_get_enum (value));
return;
case PROP_REGISTRY:
@@ -211,10 +211,10 @@ mail_signature_manager_get_property (GObject *object,
GParamSpec *pspec)
{
switch (property_id) {
- case PROP_PREFER_HTML:
- g_value_set_boolean (
+ case PROP_PREFER_MODE:
+ g_value_set_enum (
value,
- e_mail_signature_manager_get_prefer_html (
+ e_mail_signature_manager_get_prefer_mode (
E_MAIL_SIGNATURE_MANAGER (object)));
return;
@@ -396,7 +396,6 @@ mail_signature_manager_editor_created_add_signature_cb (GObject *source_object,
{
EMailSignatureManager *manager = user_data;
EHTMLEditor *editor;
- EContentEditor *cnt_editor;
GtkWidget *widget;
GError *error = NULL;
@@ -411,8 +410,7 @@ mail_signature_manager_editor_created_add_signature_cb (GObject *source_object,
}
editor = e_mail_signature_editor_get_editor (E_MAIL_SIGNATURE_EDITOR (widget));
- cnt_editor = e_html_editor_get_content_editor (editor);
- e_content_editor_set_html_mode (cnt_editor, manager->priv->prefer_html);
+ e_html_editor_set_mode (editor, manager->priv->prefer_mode);
mail_signature_manager_emit_editor_created (manager, widget);
@@ -601,12 +599,13 @@ e_mail_signature_manager_class_init (EMailSignatureManagerClass *class)
g_object_class_install_property (
object_class,
- PROP_PREFER_HTML,
- g_param_spec_boolean (
- "prefer-html",
- "Prefer HTML",
+ PROP_PREFER_MODE,
+ g_param_spec_enum (
+ "prefer-mode",
+ "Prefer editor mode",
NULL,
- TRUE,
+ E_TYPE_CONTENT_EDITOR_MODE,
+ E_CONTENT_EDITOR_MODE_HTML,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
@@ -719,26 +718,29 @@ e_mail_signature_manager_remove_signature (EMailSignatureManager *manager)
g_signal_emit (manager, signals[REMOVE_SIGNATURE], 0);
}
-gboolean
-e_mail_signature_manager_get_prefer_html (EMailSignatureManager *manager)
+EContentEditorMode
+e_mail_signature_manager_get_prefer_mode (EMailSignatureManager *manager)
{
g_return_val_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager), FALSE);
- return manager->priv->prefer_html;
+ return manager->priv->prefer_mode;
}
void
-e_mail_signature_manager_set_prefer_html (EMailSignatureManager *manager,
- gboolean prefer_html)
+e_mail_signature_manager_set_prefer_mode (EMailSignatureManager *manager,
+ EContentEditorMode prefer_mode)
{
g_return_if_fail (E_IS_MAIL_SIGNATURE_MANAGER (manager));
- if (manager->priv->prefer_html == prefer_html)
+ if (prefer_mode == E_CONTENT_EDITOR_MODE_UNKNOWN)
+ prefer_mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+
+ if (manager->priv->prefer_mode == prefer_mode)
return;
- manager->priv->prefer_html = prefer_html;
+ manager->priv->prefer_mode = prefer_mode;
- g_object_notify (G_OBJECT (manager), "prefer-html");
+ g_object_notify (G_OBJECT (manager), "prefer-mode");
}
ESourceRegistry *
diff --git a/src/e-util/e-mail-signature-manager.h b/src/e-util/e-mail-signature-manager.h
index b67b3df9d7..c6d986d0ba 100644
--- a/src/e-util/e-mail-signature-manager.h
+++ b/src/e-util/e-mail-signature-manager.h
@@ -24,6 +24,7 @@
#include <gtk/gtk.h>
+#include <e-util/e-util-enumtypes.h>
#include <e-util/e-mail-signature-editor.h>
#include <e-util/e-mail-signature-tree-view.h>
@@ -80,11 +81,12 @@ void e_mail_signature_manager_edit_signature
(EMailSignatureManager *manager);
void e_mail_signature_manager_remove_signature
(EMailSignatureManager *manager);
-gboolean e_mail_signature_manager_get_prefer_html
+EContentEditorMode
+ e_mail_signature_manager_get_prefer_mode
(EMailSignatureManager *manager);
-void e_mail_signature_manager_set_prefer_html
+void e_mail_signature_manager_set_prefer_mode
(EMailSignatureManager *manager,
- gboolean prefer_html);
+ EContentEditorMode prefer_mode);
ESourceRegistry *
e_mail_signature_manager_get_registry
(EMailSignatureManager *manager);
diff --git a/src/e-util/e-markdown-editor.c b/src/e-util/e-markdown-editor.c
index 08d4db5bee..1c9950b839 100644
--- a/src/e-util/e-markdown-editor.c
+++ b/src/e-util/e-markdown-editor.c
@@ -6,36 +6,1130 @@
#include "evolution-config.h"
-#ifdef HAVE_MARKDOWN
-#include <cmark.h>
-#endif
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <libedataserverui/libedataserverui.h>
+
+#include "e-content-editor.h"
+#include "e-markdown-utils.h"
+#include "e-misc-utils.h"
+#include "e-spell-text-view.h"
+#include "e-web-view.h"
+#include "e-widget-undo.h"
+
+#include "e-markdown-editor.h"
+
+/* Where an existing signature is */
+#define EVO_SIGNATURE_START_MARK "x-evo-signature-start"
+#define EVO_SIGNATURE_END_MARK "x-evo-signature-end"
+
+struct _EMarkdownEditorPrivate {
+ GtkNotebook *notebook;
+ GtkTextView *text_view;
+ EWebView *web_view;
+ GtkToolbar *action_toolbar;
+ gboolean is_dark_theme;
+
+ /* EContentEditor properties */
+ gboolean can_copy;
+ gboolean can_cut;
+ gboolean can_paste;
+ gboolean can_redo;
+ gboolean can_undo;
+ gboolean changed;
+ EContentEditorMode mode;
+ ESpellChecker *spell_checker; /* this is not used internally */
+ EThreeState start_bottom;
+ EThreeState top_signature;
+ gchar *signature_uid;
+ gboolean selection_saved;
+ GtkTextIter selection_start; /* valid only if selection_saved is TRUE */
+ GtkTextIter selection_end; /* valid only if selection_saved is TRUE */
+};
+
+static void e_markdown_editor_content_editor_init (EContentEditorInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EMarkdownEditor, e_markdown_editor, GTK_TYPE_BOX,
+ G_ADD_PRIVATE (EMarkdownEditor)
+ G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)
+ G_IMPLEMENT_INTERFACE (E_TYPE_CONTENT_EDITOR, e_markdown_editor_content_editor_init))
+
+enum {
+ PROP_0,
+ PROP_IS_MALFUNCTION,
+ PROP_CAN_COPY,
+ PROP_CAN_CUT,
+ PROP_CAN_PASTE,
+ PROP_CAN_REDO,
+ PROP_CAN_UNDO,
+ PROP_CHANGED,
+ PROP_EDITABLE,
+ PROP_MODE,
+ PROP_SPELL_CHECK_ENABLED,
+ PROP_SPELL_CHECKER,
+ PROP_START_BOTTOM,
+ PROP_TOP_SIGNATURE,
+ PROP_VISUALLY_WRAP_LONG_LINES,
+ PROP_LAST_ERROR,
+
+ PROP_ALIGNMENT,
+ PROP_BACKGROUND_COLOR,
+ PROP_BLOCK_FORMAT,
+ PROP_BOLD,
+ PROP_FONT_COLOR,
+ PROP_FONT_NAME,
+ PROP_FONT_SIZE,
+ PROP_INDENT_LEVEL,
+ PROP_ITALIC,
+ PROP_STRIKETHROUGH,
+ PROP_SUBSCRIPT,
+ PROP_SUPERSCRIPT,
+ PROP_UNDERLINE
+};
+
+enum {
+ CHANGED,
+ FORMAT_BOLD,
+ FORMAT_ITALIC,
+ FORMAT_QUOTE,
+ FORMAT_CODE,
+ FORMAT_BULLET_LIST,
+ FORMAT_NUMBERED_LIST,
+ FORMAT_HEADER,
+ INSERT_LINK,
+ INSERT_EMOJI,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+typedef void (* AsyncCallback) (EMarkdownEditor *self,
+ gpointer user_data);
+
+typedef struct _AsyncData {
+ EMarkdownEditor *self;
+ AsyncCallback callback;
+ gpointer user_data;
+} AsyncData;
+
+static AsyncData *
+async_data_new (EMarkdownEditor *self,
+ AsyncCallback callback,
+ gpointer user_data)
+{
+ AsyncData *data;
+
+ data = g_slice_new (AsyncData);
+ data->self = g_object_ref (self);
+ data->callback = callback;
+ data->user_data = user_data;
+
+ return data;
+}
+
+static void
+async_data_free (gpointer ptr)
+{
+ AsyncData *data = ptr;
+
+ if (data) {
+ g_clear_object (&data->self);
+ g_slice_free (AsyncData, data);
+ }
+}
+
+static gboolean
+e_markdown_editor_call_async_cb (gpointer user_data)
+{
+ AsyncData *data = user_data;
+
+ g_return_val_if_fail (data != NULL, FALSE);
+ g_return_val_if_fail (data->callback != NULL, FALSE);
+
+ data->callback (data->self, data->user_data);
+
+ return FALSE;
+}
+
+static void
+e_markdown_editor_call_async (EMarkdownEditor *self,
+ AsyncCallback callback,
+ gpointer user_data)
+{
+ g_timeout_add_full (G_PRIORITY_HIGH, 1,
+ e_markdown_editor_call_async_cb,
+ async_data_new (self, callback, user_data),
+ async_data_free);
+}
+
+static gboolean
+e_markdown_editor_supports_mode (EContentEditor *cnt_editor,
+ EContentEditorMode mode)
+{
+ return mode == E_CONTENT_EDITOR_MODE_MARKDOWN ||
+ #ifdef HAVE_MARKDOWN
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML ||
+ #endif
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT;
+}
+
+static void
+e_markdown_editor_grab_focus (EContentEditor *cnt_editor)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->priv->text_view));
+}
+
+static gboolean
+e_markdown_editor_is_focus (EContentEditor *cnt_editor)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ return gtk_widget_is_focus (GTK_WIDGET (self->priv->text_view));
+}
+
+typedef struct _InitAsyncData {
+ EContentEditorInitializedCallback callback;
+ gpointer user_data;
+} InitAsyncData;
+
+static void
+e_markdown_editor_initialize_done (EMarkdownEditor *self,
+ gpointer user_data)
+{
+ InitAsyncData *data = user_data;
+
+ g_return_if_fail (data != NULL);
+ g_return_if_fail (data->callback != NULL);
+
+ data->callback (E_CONTENT_EDITOR (self), data->user_data);
+
+ g_slice_free (InitAsyncData, data);
+}
+
+static void
+e_markdown_editor_initialize (EContentEditor *cnt_editor,
+ EContentEditorInitializedCallback callback,
+ gpointer user_data)
+{
+ InitAsyncData *data;
+
+ data = g_slice_new (InitAsyncData);
+ data->callback = callback;
+ data->user_data = user_data;
+
+ e_markdown_editor_call_async (E_MARKDOWN_EDITOR (cnt_editor), e_markdown_editor_initialize_done,
data);
+}
+
+static gboolean
+e_markdown_editor_is_ready (EContentEditor *cnt_editor)
+{
+ return TRUE;
+}
+
+static void
+e_markdown_editor_update_styles (EContentEditor *cnt_editor)
+{
+}
+
+static void
+e_markdown_editor_insert_content (EContentEditor *cnt_editor,
+ const gchar *content,
+ EContentEditorInsertContentFlags flags)
+{
+ EMarkdownEditor *self;
+ gchar *text = NULL;
+
+ g_return_if_fail (E_IS_MARKDOWN_EDITOR (cnt_editor));
+ g_return_if_fail (content != NULL);
+
+ self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ if ((flags & E_CONTENT_EDITOR_INSERT_TEXT_HTML) != 0) {
+ text = e_markdown_utils_html_to_text (content, -1,
E_MARKDOWN_HTML_TO_TEXT_FLAG_COMPOSER_QUIRKS);
+ content = text;
+ }
+
+ if ((flags & E_CONTENT_EDITOR_INSERT_REPLACE_ALL) != 0) {
+ e_markdown_editor_set_text (self, content);
+ } else if ((flags & E_CONTENT_EDITOR_INSERT_QUOTE_CONTENT) != 0) {
+ GtkTextBuffer *text_buffer;
+ GString *quoted;
+ gint ii;
+
+ quoted = g_string_sized_new (strlen (content) + 4);
+ g_string_append (quoted, "> ");
+ g_string_append (quoted, content);
+
+ for (ii = 0; ii < quoted->len; ii++) {
+ if (quoted->str[ii] == '\n' && ii + 1 < quoted->len)
+ g_string_insert (quoted, ii + 1, "> ");
+ }
+
+ text_buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_insert_at_cursor (text_buffer, quoted->str, -1);
+
+ g_string_free (quoted, TRUE);
+ } else {
+ GtkTextBuffer *text_buffer;
+
+ text_buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_insert_at_cursor (text_buffer, content, -1);
+ }
+
+ g_free (text);
+}
+
+static void
+e_markdown_editor_get_content (EContentEditor *cnt_editor,
+ guint32 flags, /* bit-or of EContentEditorGetContentFlags */
+ const gchar *inline_images_from_domain,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ EContentEditorContentHash *content_hash;
+
+ content_hash = e_content_editor_util_new_content_hash ();
+ if ((flags & E_CONTENT_EDITOR_GET_RAW_BODY_HTML) != 0 ||
+ (flags & E_CONTENT_EDITOR_GET_TO_SEND_HTML) != 0) {
+ gchar *html;
+
+ html = e_markdown_editor_dup_html (E_MARKDOWN_EDITOR (cnt_editor));
+
+ if (html) {
+ if ((flags & E_CONTENT_EDITOR_GET_RAW_BODY_HTML) != 0 &&
+ (flags & E_CONTENT_EDITOR_GET_TO_SEND_HTML) != 0) {
+ e_content_editor_util_put_content_data (content_hash,
+ E_CONTENT_EDITOR_GET_RAW_BODY_HTML, html);
+ e_content_editor_util_take_content_data (content_hash,
+ E_CONTENT_EDITOR_GET_TO_SEND_HTML, html, g_free);
+ } else if ((flags & E_CONTENT_EDITOR_GET_RAW_BODY_HTML) != 0) {
+ e_content_editor_util_take_content_data (content_hash,
+ E_CONTENT_EDITOR_GET_RAW_BODY_HTML, html, g_free);
+ } else {
+ e_content_editor_util_take_content_data (content_hash,
+ E_CONTENT_EDITOR_GET_TO_SEND_HTML, html, g_free);
+ }
+ }
+ }
+
+ if ((flags & E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN) != 0 ||
+ (flags & E_CONTENT_EDITOR_GET_RAW_DRAFT) != 0 ||
+ (flags & E_CONTENT_EDITOR_GET_TO_SEND_PLAIN) != 0) {
+ gchar *text;
+
+ text = e_markdown_editor_dup_text (E_MARKDOWN_EDITOR (cnt_editor));
+
+ if (text) {
+ gint n_formats = ((flags & E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN) != 0 ? 1 : 0) +
+ ((flags & E_CONTENT_EDITOR_GET_RAW_DRAFT) != 0 ? 1 : 0) +
+ ((flags & E_CONTENT_EDITOR_GET_TO_SEND_PLAIN) != 0 ? 1 : 0);
+
+ if (n_formats == 1) {
+ EContentEditorGetContentFlags format;
+ if ((flags & E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN) != 0)
+ format = E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN;
+ else if ((flags & E_CONTENT_EDITOR_GET_RAW_DRAFT) != 0)
+ format = E_CONTENT_EDITOR_GET_RAW_DRAFT;
+ else
+ format = E_CONTENT_EDITOR_GET_TO_SEND_PLAIN;
+
+ e_content_editor_util_take_content_data (content_hash,
+ format, text, g_free);
+ } else {
+ if ((flags & E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN) != 0)
+ e_content_editor_util_put_content_data (content_hash,
+ E_CONTENT_EDITOR_GET_RAW_BODY_PLAIN, text);
+
+ if ((flags & E_CONTENT_EDITOR_GET_RAW_DRAFT) != 0)
+ e_content_editor_util_put_content_data (content_hash,
+ E_CONTENT_EDITOR_GET_RAW_DRAFT, text);
+
+ if ((flags & E_CONTENT_EDITOR_GET_TO_SEND_PLAIN) != 0)
+ e_content_editor_util_put_content_data (content_hash,
+ E_CONTENT_EDITOR_GET_TO_SEND_PLAIN, text);
+
+ g_free (text);
+ }
+ }
+ }
+
+ if ((flags & E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED) != 0) {
+ gchar *text;
+
+ text = e_markdown_editor_dup_text (E_MARKDOWN_EDITOR (cnt_editor));
+
+ if (text) {
+ gchar *separator;
+
+ separator = strstr (text, "-- \n");
+
+ if (separator)
+ *separator = '\0';
+ }
+
+ if (text) {
+ e_content_editor_util_take_content_data (content_hash,
+ E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED, text, g_free);
+ } else {
+ e_content_editor_util_put_content_data (content_hash,
+ E_CONTENT_EDITOR_GET_RAW_BODY_STRIPPED, "");
+ }
+ }
+
+ task = g_task_new (cnt_editor, cancellable, callback, user_data);
+ g_task_set_source_tag (task, e_markdown_editor_get_content);
+ g_task_return_pointer (task, content_hash, (GDestroyNotify) e_content_editor_util_free_content_hash);
+ g_object_unref (task);
+}
+
+static EContentEditorContentHash *
+e_markdown_editor_get_content_finish (EContentEditor *cnt_editor,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (g_task_is_valid (result, cnt_editor), NULL);
+
+ return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+e_markdown_editor_move_caret_on_coordinates (EContentEditor *cnt_editor,
+ gint x,
+ gint y,
+ gboolean cancel_if_not_collapsed)
+{
+}
+
+static void
+e_markdown_editor_cut (EContentEditor *cnt_editor)
+{
+ /* Handled by GtktextView itself */
+}
+
+static void
+e_markdown_editor_copy (EContentEditor *cnt_editor)
+{
+ /* Handled by GtktextView itself */
+}
+
+static void
+e_markdown_editor_paste (EContentEditor *cnt_editor)
+{
+ /* Handled by GtktextView itself */
+}
+
+static void
+e_markdown_editor_paste_primary (EContentEditor *cnt_editor)
+{
+ /* Handled by GtktextView itself */
+}
+
+static void
+e_markdown_editor_undo (EContentEditor *cnt_editor)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ e_widget_undo_do_undo (GTK_WIDGET (self->priv->text_view));
+}
+
+static void
+e_markdown_editor_redo (EContentEditor *cnt_editor)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ e_widget_undo_do_redo (GTK_WIDGET (self->priv->text_view));
+}
+
+static void
+e_markdown_editor_clear_undo_redo_history (EContentEditor *cnt_editor)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ e_widget_undo_reset (GTK_WIDGET (self->priv->text_view));
+
+ g_object_notify (G_OBJECT (self), "can-undo");
+ g_object_notify (G_OBJECT (self), "can-redo");
+}
+
+static void
+e_markdown_editor_set_spell_checking_languages (EContentEditor *cnt_editor,
+ const gchar **languages)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ e_spell_text_view_set_languages (self->priv->text_view, languages);
+}
+
+static gchar *
+e_markdown_editor_get_caret_word (EContentEditor *cnt_editor)
+{
+ return NULL;
+}
+
+static void
+e_markdown_editor_replace_caret_word (EContentEditor *cnt_editor,
+ const gchar *replacement)
+{
+}
+
+static void
+e_markdown_editor_select_all (EContentEditor *cnt_editor)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ g_signal_emit_by_name (self->priv->text_view, "select-all", 0, TRUE, NULL);
+}
+
+static gunichar *
+e_markdown_editor_prepare_search_text (const gchar *text,
+ guint32 *flags)
+{
+ gunichar *search_text;
+ guint ii;
+
+ if (!text || !*text)
+ return NULL;
+
+ /* Fine-tune the direction flags:
+ forward & next = forward
+ forward & previous = backward
+ backward & next = backward
+ backward & previous = forward
+ */
+ if ((*flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) == 0 &&
+ (*flags & E_CONTENT_EDITOR_FIND_PREVIOUS) != 0) {
+ *flags = ((*flags) & ~(E_CONTENT_EDITOR_FIND_MODE_BACKWARDS | E_CONTENT_EDITOR_FIND_PREVIOUS
| E_CONTENT_EDITOR_FIND_NEXT)) |
+ E_CONTENT_EDITOR_FIND_MODE_BACKWARDS;
+ } else if ((*flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0 &&
+ (*flags & E_CONTENT_EDITOR_FIND_PREVIOUS) != 0) {
+ *flags = ((*flags) & ~(E_CONTENT_EDITOR_FIND_MODE_BACKWARDS | E_CONTENT_EDITOR_FIND_PREVIOUS
| E_CONTENT_EDITOR_FIND_NEXT));
+ }
+
+ search_text = g_utf8_to_ucs4 (text, -1, NULL, NULL, NULL);
+
+ if (search_text && (*flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0) {
+ guint len;
+
+ for (len = 0; search_text[len]; len++) {
+ /* Just count them */
+ }
+
+ if (len) {
+ len--;
+
+ /* Swap the letters backwards */
+ for (ii = 0; ii < len; ii++) {
+ gunichar chr = search_text[ii];
+ search_text[ii] = search_text[len];
+ search_text[len] = chr;
+ len--;
+ }
+ }
+ }
+
+ if (search_text && (*flags & E_CONTENT_EDITOR_FIND_CASE_INSENSITIVE) != 0) {
+ for (ii = 0; search_text[ii]; ii++) {
+ search_text[ii] = g_unichar_tolower (search_text[ii]);
+ }
+ }
+
+ return search_text;
+}
+
+static gboolean
+e_markdown_editor_do_search_text (GtkTextBuffer *buffer,
+ const gunichar *search_text,
+ guint32 flags,
+ gboolean *did_wrap_around,
+ const GtkTextIter *from_iter,
+ const GtkTextIter *limit, /* used only after wrap around */
+ GtkTextIter *out_occur_start,
+ GtkTextIter *out_occur_end)
+{
+ GtkTextIter iter, from_cursor;
+ gboolean case_insensitive = (flags & E_CONTENT_EDITOR_FIND_CASE_INSENSITIVE) != 0;
+ gboolean wrap_around = (!*did_wrap_around) && (flags & E_CONTENT_EDITOR_FIND_WRAP_AROUND) != 0;
+ gboolean backwards = (flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0;
+ gboolean found = FALSE;
+
+ if (from_iter) {
+ iter = *from_iter;
+ } else {
+ gtk_text_buffer_get_selection_bounds (buffer, &from_cursor, &iter);
+
+ if (!backwards)
+ from_cursor = iter;
+
+ if (!limit)
+ limit = &from_cursor;
+
+ iter = from_cursor;
+ }
+
+ if (backwards && !gtk_text_iter_backward_char (&iter)) {
+ if (wrap_around) {
+ wrap_around = FALSE;
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ if (!gtk_text_iter_backward_char (&iter))
+ return FALSE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ while (!found) {
+ gunichar chr;
+
+ chr = gtk_text_iter_get_char (&iter);
+
+ if (chr) {
+ if ((case_insensitive && g_unichar_tolower (chr) == search_text[0]) ||
+ (!case_insensitive && chr == search_text[0])) {
+ GtkTextIter next = iter;
+ guint ii;
+
+ for (ii = 1; !found; ii++) {
+ if (!search_text[ii]) {
+ /* To have selected also the last character */
+ if (backwards)
+ gtk_text_iter_forward_char (&iter);
+ else
+ gtk_text_iter_forward_char (&next);
+
+ found = TRUE;
+ if (backwards) {
+ *out_occur_start = next;
+ *out_occur_end = iter;
+ } else {
+ *out_occur_start = iter;
+ *out_occur_end = next;
+ }
+ break;
+ }
+
+ if ((backwards && !gtk_text_iter_backward_char (&next)) ||
+ (!backwards && !gtk_text_iter_forward_char (&next)))
+ break;
+
+ if (*did_wrap_around && !gtk_text_iter_compare (&iter, limit))
+ break;
+
+ chr = gtk_text_iter_get_char (&next);
+
+ if (!chr)
+ break;
+
+ if ((case_insensitive && g_unichar_tolower (chr) == search_text[ii])
||
+ (!case_insensitive && chr == search_text[ii])) {
+ /* matched the next letter */
+ } else {
+ break;
+ }
+ }
+
+ if (found)
+ break;
+ }
+
+ if (!found && *did_wrap_around && !gtk_text_iter_compare (&iter, limit))
+ break;
+ }
+
+ if ((backwards && !gtk_text_iter_backward_char (&iter)) ||
+ (!backwards && !gtk_text_iter_forward_char (&iter))) {
+ if (!wrap_around)
+ break;
+
+ *did_wrap_around = TRUE;
+ wrap_around = FALSE;
+
+ if (backwards)
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ else
+ gtk_text_buffer_get_start_iter (buffer, &iter);
+
+ if (!gtk_text_iter_compare (&iter, limit))
+ break;
+ }
+ }
+
+ return found;
+}
+
+static void
+e_markdown_editor_find (EContentEditor *cnt_editor,
+ guint32 flags,
+ const gchar *text)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+ GtkTextBuffer *buffer;
+ GtkTextIter occur_start, occur_end;
+ gboolean did_wrap_around = FALSE;
+ gunichar *search_text;
+
+ search_text = e_markdown_editor_prepare_search_text (text, &flags);
+
+ if (!search_text) {
+ e_content_editor_emit_find_done (cnt_editor, 0);
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (self->priv->text_view);
+
+ if (e_markdown_editor_do_search_text (buffer, search_text, flags, &did_wrap_around, NULL, NULL,
&occur_start, &occur_end)) {
+ gtk_text_buffer_select_range (buffer, &occur_start, &occur_end);
+ e_content_editor_emit_find_done (cnt_editor, 1);
+ } else {
+ e_content_editor_emit_find_done (cnt_editor, 0);
+ }
+
+ g_free (search_text);
+}
+
+static void
+e_markdown_editor_replace (EContentEditor *cnt_editor,
+ const gchar *replacement)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+
+ buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
+ gtk_text_buffer_delete (buffer, &start, &end);
+ gtk_text_buffer_insert_at_cursor (buffer, replacement, -1);
+}
+
+static void
+e_markdown_editor_replace_all (EContentEditor *cnt_editor,
+ guint32 flags,
+ const gchar *find_text,
+ const gchar *replace_with)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+ GtkTextBuffer *buffer;
+ GtkTextIter occur_start, occur_end, limit, last_replace;
+ gboolean did_wrap_around = FALSE;
+ gunichar *search_text;
+ guint count = 0, replace_len = 0;
+
+ search_text = e_markdown_editor_prepare_search_text (find_text, &flags);
+
+ if (!search_text) {
+ e_content_editor_emit_replace_all_done (cnt_editor, 0);
+ return;
+ }
+
+ buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_get_selection_bounds (buffer, &occur_start, &occur_end);
+
+ /* Different bound than in search, to replace also current match */
+ if ((flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0)
+ limit = occur_end;
+ else
+ limit = occur_start;
+
+ if (replace_with)
+ replace_len = g_utf8_strlen (replace_with, -1);
+
+ last_replace = limit;
+
+ while (e_markdown_editor_do_search_text (buffer, search_text, flags, &did_wrap_around, &last_replace,
&limit, &occur_start, &occur_end)) {
+ GtkTextMark *mark;
+ gboolean last_match;
+
+ last_match = did_wrap_around && !gtk_text_iter_compare (&occur_start, &limit);
+
+ if (last_match && !(flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS))
+ break;
+
+ /* Remember where the limit was... */
+ mark = gtk_text_buffer_create_mark (buffer, NULL, &limit, !(flags &
E_CONTENT_EDITOR_FIND_MODE_BACKWARDS));
+
+ gtk_text_buffer_delete (buffer, &occur_start, &occur_end);
+
+ last_replace = occur_start;
+
+ if (replace_with && *replace_with) {
+ gtk_text_buffer_insert (buffer, &occur_start, replace_with, -1);
+
+ /* Get on the first letter of the replaced word */
+ if ((flags & E_CONTENT_EDITOR_FIND_MODE_BACKWARDS) != 0 &&
+ !gtk_text_iter_backward_chars (&occur_start, replace_len)) {
+ break;
+ }
+
+ last_replace = occur_start;
+ }
+
+ /* ... then restore the limit, after the buffer changed (which invalidated the iterator) */
+ gtk_text_buffer_get_iter_at_mark (buffer, &limit, mark);
+ gtk_text_buffer_delete_mark (buffer, mark);
+
+ count++;
+
+ if (last_match)
+ break;
+ }
+
+ g_free (search_text);
+
+ if (count)
+ gtk_text_buffer_select_range (buffer, &last_replace, &last_replace);
+
+ e_content_editor_emit_replace_all_done (cnt_editor, count);
+}
+
+static void
+e_markdown_editor_selection_save (EContentEditor *cnt_editor)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_get_selection_bounds (buffer, &self->priv->selection_start,
&self->priv->selection_end);
+
+ self->priv->selection_saved = TRUE;
+}
+
+static void
+e_markdown_editor_selection_restore (EContentEditor *cnt_editor)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ if (self->priv->selection_saved) {
+ GtkTextBuffer *buffer;
+
+ self->priv->selection_saved = FALSE;
+
+ buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_select_range (buffer, &self->priv->selection_start,
&self->priv->selection_end);
+ }
+}
+
+static gchar *
+e_markdown_editor_get_current_signature_uid (EContentEditor *cnt_editor)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+
+ return self->priv->signature_uid;
+}
+
+static gchar *
+e_markdown_editor_insert_signature (EContentEditor *cnt_editor,
+ const gchar *content,
+ EContentEditorMode editor_mode,
+ gboolean can_reposition_caret,
+ const gchar *signature_id,
+ gboolean *set_signature_from_message,
+ gboolean *check_if_signature_is_changed,
+ gboolean *ignore_next_signature_change)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+ GtkTextMark *sig_start_mark, *sig_end_mark;
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+ gchar *plain_text = NULL;
+
+ g_clear_pointer (&self->priv->signature_uid, g_free);
+ self->priv->signature_uid = g_strdup (signature_id);
+
+ if (content && *content && editor_mode == E_CONTENT_EDITOR_MODE_HTML) {
+ plain_text = e_markdown_utils_html_to_text (content, -1,
E_MARKDOWN_HTML_TO_TEXT_FLAG_PLAIN_TEXT);
+ content = plain_text;
+ editor_mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+ }
+
+ if (content && *content && editor_mode == E_CONTENT_EDITOR_MODE_PLAIN_TEXT) {
+ gchar *tmp;
+
+ tmp = g_strconcat ("```\n", content,
+ g_str_has_suffix (content, "\n") ? "" : "\n",
+ "```\n", NULL);
+
+ g_free (plain_text);
+ plain_text = tmp;
+ content = plain_text;
+ }
+
+ if (!e_content_editor_util_three_state_to_bool (E_THREE_STATE_INCONSISTENT,
"composer-no-signature-delim") &&
+ content && *content && !g_str_has_prefix (content, "-- \n") && !strstr (content, "\n-- \n")) {
+ gchar *tmp;
+
+ tmp = g_strconcat ("-- \n",
+ /* Add an empty line between the delimiter and the markdown signature */
+ editor_mode == E_CONTENT_EDITOR_MODE_PLAIN_TEXT ? "" : "\n",
+ content, NULL);
+
+ g_free (plain_text);
+ plain_text = tmp;
+ content = plain_text;
+ }
+
+ buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+
+ sig_start_mark = gtk_text_buffer_get_mark (buffer, EVO_SIGNATURE_START_MARK);
+ sig_end_mark = gtk_text_buffer_get_mark (buffer, EVO_SIGNATURE_END_MARK);
+
+ if (content && *content) {
+ gtk_text_buffer_begin_user_action (buffer);
+
+ if (sig_start_mark && sig_end_mark) {
+ GtkTextIter sig_start, sig_end;
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &sig_start, sig_start_mark);
+ gtk_text_buffer_get_iter_at_mark (buffer, &sig_end, sig_end_mark);
+
+ gtk_text_buffer_delete (buffer, &sig_start, &sig_end);
+
+ gtk_text_buffer_insert (buffer, &sig_start, content, -1);
+
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ } else if (e_content_editor_util_three_state_to_bool (e_content_editor_get_top_signature
(cnt_editor), "composer-top-signature")) {
+ if (!g_str_has_suffix (content, "\n\n")) {
+ if (g_str_has_suffix (content, "\n"))
+ gtk_text_buffer_insert (buffer, &start, "\n", 1);
+ else
+ gtk_text_buffer_insert (buffer, &start, "\n\n", 2);
+
+ gtk_text_buffer_get_start_iter (buffer, &start);
+ }
+
+ if (sig_start_mark)
+ gtk_text_buffer_delete_mark_by_name (buffer, EVO_SIGNATURE_START_MARK);
+ if (sig_end_mark)
+ gtk_text_buffer_delete_mark_by_name (buffer, EVO_SIGNATURE_END_MARK);
+
+ gtk_text_buffer_create_mark (buffer, EVO_SIGNATURE_END_MARK, &start, FALSE);
+
+ gtk_text_buffer_insert (buffer, &start, content, -1);
+ gtk_text_buffer_get_start_iter (buffer, &start);
+
+ gtk_text_buffer_create_mark (buffer, EVO_SIGNATURE_START_MARK, &start, TRUE);
+
+ gtk_text_buffer_insert (buffer, &start, "\n", 1);
+ gtk_text_buffer_get_start_iter (buffer, &start);
+ } else {
+ GtkTextIter iter = end;
+
+ if (gtk_text_iter_backward_char (&iter) &&
+ gtk_text_iter_get_char (&iter) != '\n') {
+ gtk_text_buffer_insert (buffer, &end, "\n", 1);
+ gtk_text_buffer_get_end_iter (buffer, &end);
+ }
+
+ if (sig_start_mark)
+ gtk_text_buffer_delete_mark_by_name (buffer, EVO_SIGNATURE_START_MARK);
+ if (sig_end_mark)
+ gtk_text_buffer_delete_mark_by_name (buffer, EVO_SIGNATURE_END_MARK);
+
+ gtk_text_buffer_create_mark (buffer, EVO_SIGNATURE_START_MARK, &end, TRUE);
+
+ gtk_text_buffer_insert (buffer, &end, content, -1);
+ gtk_text_buffer_get_end_iter (buffer, &end);
+
+ gtk_text_buffer_create_mark (buffer, EVO_SIGNATURE_END_MARK, &end, FALSE);
+ }
+
+ gtk_text_buffer_end_user_action (buffer);
+ } else if (sig_start_mark && sig_end_mark) {
+ GtkTextIter sig_start, sig_end;
+
+ gtk_text_buffer_begin_user_action (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &sig_start, sig_start_mark);
+ gtk_text_buffer_get_iter_at_mark (buffer, &sig_end, sig_end_mark);
+ gtk_text_buffer_delete (buffer, &sig_start, &sig_end);
+ gtk_text_buffer_get_bounds (buffer, &start, &end);
+ gtk_text_buffer_end_user_action (buffer);
+ } else {
+ if (sig_start_mark)
+ gtk_text_buffer_delete_mark_by_name (buffer, EVO_SIGNATURE_START_MARK);
+ if (sig_end_mark)
+ gtk_text_buffer_delete_mark_by_name (buffer, EVO_SIGNATURE_END_MARK);
+ }
+
+ if (can_reposition_caret) {
+ if (e_content_editor_util_three_state_to_bool (e_content_editor_get_start_bottom
(cnt_editor), "composer-reply-start-bottom")) {
+ gtk_text_buffer_select_range (buffer, &end, &end);
+ } else {
+ gtk_text_buffer_get_start_iter (buffer, &start);
+ gtk_text_buffer_select_range (buffer, &start, &start);
+ }
+ }
+
+ g_free (plain_text);
+
+ return g_strdup (self->priv->signature_uid);
+}
+
+static void
+e_markdown_editor_on_dialog_open (EContentEditor *cnt_editor,
+ const gchar *name)
+{
+ if (g_strcmp0 (name, E_CONTENT_EDITOR_DIALOG_REPLACE) == 0) {
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_begin_user_action (buffer);
+ }
+}
+
+static void
+e_markdown_editor_on_dialog_close (EContentEditor *cnt_editor,
+ const gchar *name)
+{
+ if (g_strcmp0 (name, E_CONTENT_EDITOR_DIALOG_REPLACE) == 0) {
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (cnt_editor);
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_end_user_action (buffer);
+ }
+}
+
+static gboolean
+e_markdown_editor_can_copy (EMarkdownEditor *self)
+{
+ return self->priv->can_copy;
+}
+
+static gboolean
+e_markdown_editor_can_cut (EMarkdownEditor *self)
+{
+ return self->priv->can_cut;
+}
+
+static gboolean
+e_markdown_editor_can_paste (EMarkdownEditor *self)
+{
+ return self->priv->can_paste;
+}
+
+static gboolean
+e_markdown_editor_can_redo (EMarkdownEditor *self)
+{
+ return e_widget_undo_has_redo (GTK_WIDGET (self->priv->text_view));
+}
+
+static gboolean
+e_markdown_editor_can_undo (EMarkdownEditor *self)
+{
+ return e_widget_undo_has_undo (GTK_WIDGET (self->priv->text_view));
+}
+
+static gboolean
+e_markdown_editor_get_changed (EMarkdownEditor *self)
+{
+ return self->priv->changed;
+}
-#include <glib/gi18n-lib.h>
-#include <gtk/gtk.h>
+static void
+e_markdown_editor_set_changed (EMarkdownEditor *self,
+ gboolean value)
+{
+ if ((self->priv->changed ? 1 : 0) != (value ? 1 : 0)) {
+ self->priv->changed = value;
+ g_object_notify (G_OBJECT (self), "changed");
+ }
+}
-#include <libedataserverui/libedataserverui.h>
+static gboolean
+e_markdown_editor_is_editable (EMarkdownEditor *self)
+{
+ return gtk_text_view_get_editable (self->priv->text_view);
+}
-#include "e-misc-utils.h"
-#include "e-spell-text-view.h"
-#include "e-web-view.h"
+static void
+e_markdown_editor_set_editable (EMarkdownEditor *self,
+ gboolean value)
+{
+ if ((gtk_text_view_get_editable (self->priv->text_view) ? 1 : 0) != (value ? 1 : 0)) {
+ gtk_text_view_set_editable (self->priv->text_view, value);
+ g_object_notify (G_OBJECT (self), "editable");
+ }
+}
-#include "e-markdown-editor.h"
+static EContentEditorMode
+e_markdown_editor_get_mode (EMarkdownEditor *self)
+{
+ return self->priv->mode;
+}
-struct _EMarkdownEditorPrivate {
- GtkTextView *text_view;
- EWebView *web_view;
- GtkToolbar *action_toolbar;
- gboolean is_dark_theme;
-};
+static void
+e_markdown_editor_set_mode (EMarkdownEditor *self,
+ EContentEditorMode mode)
+{
+ g_return_if_fail (mode == E_CONTENT_EDITOR_MODE_MARKDOWN ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML);
-G_DEFINE_TYPE_WITH_PRIVATE (EMarkdownEditor, e_markdown_editor, GTK_TYPE_BOX)
+ if (self->priv->mode != mode) {
+ self->priv->mode = mode;
+ g_object_notify (G_OBJECT (self), "mode");
+ }
+}
-enum {
- CHANGED,
- LAST_SIGNAL
-};
+static gboolean
+e_markdown_editor_get_spell_check_enabled (EMarkdownEditor *self)
+{
+ return e_spell_text_view_get_enabled (self->priv->text_view);
+}
-static guint signals[LAST_SIGNAL];
+static void
+e_markdown_editor_set_spell_check_enabled (EMarkdownEditor *self,
+ gboolean value)
+{
+ gboolean spell_check_enabled = e_markdown_editor_get_spell_check_enabled (self);
+ if ((spell_check_enabled ? 1 : 0) != (value ? 1 : 0)) {
+ e_spell_text_view_set_enabled (self->priv->text_view, value);
+ g_object_notify (G_OBJECT (self), "spell-check-enabled");
+ }
+}
+
+static ESpellChecker *
+e_markdown_editor_get_spell_checker (EMarkdownEditor *self)
+{
+ return self->priv->spell_checker;
+}
+
+static EThreeState
+e_markdown_editor_get_start_bottom (EMarkdownEditor *self)
+{
+ return self->priv->start_bottom;
+}
+
+static void
+e_markdown_editor_set_start_bottom (EMarkdownEditor *self,
+ EThreeState value)
+{
+ if (self->priv->start_bottom != value) {
+ self->priv->start_bottom = value;
+ g_object_notify (G_OBJECT (self), "start-bottom");
+ }
+}
+
+static EThreeState
+e_markdown_editor_get_top_signature (EMarkdownEditor *self)
+{
+ return self->priv->top_signature;
+}
+
+static void
+e_markdown_editor_set_top_signature (EMarkdownEditor *self,
+ EThreeState value)
+{
+ if (self->priv->top_signature != value) {
+ self->priv->top_signature = value;
+ g_object_notify (G_OBJECT (self), "top-signature");
+ }
+}
static void
e_markdown_editor_get_selection (EMarkdownEditor *self,
@@ -116,43 +1210,32 @@ e_markdown_editor_surround_selection (EMarkdownEditor *self,
}
static void
-e_markdown_editor_add_bold_text_cb (GtkToolButton *button,
- gpointer user_data)
+e_markdown_editor_format_bold_text_cb (EMarkdownEditor *self)
{
- EMarkdownEditor *self = user_data;
-
g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
e_markdown_editor_surround_selection (self, FALSE, "**", "**");
}
static void
-e_markdown_editor_add_italic_text_cb (GtkToolButton *button,
- gpointer user_data)
+e_markdown_editor_format_italic_text_cb (EMarkdownEditor *self)
{
- EMarkdownEditor *self = user_data;
-
g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
- e_markdown_editor_surround_selection (self, FALSE, "_", "_");
+ e_markdown_editor_surround_selection (self, FALSE, "*", "*");
}
static void
-e_markdown_editor_insert_quote_cb (GtkToolButton *button,
- gpointer user_data)
+e_markdown_editor_format_quote_cb (EMarkdownEditor *self)
{
- EMarkdownEditor *self = user_data;
-
g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
e_markdown_editor_surround_selection (self, TRUE, "> ", NULL);
}
static void
-e_markdown_editor_insert_code_cb (GtkToolButton *button,
- gpointer user_data)
+e_markdown_editor_format_code_cb (EMarkdownEditor *self)
{
- EMarkdownEditor *self = user_data;
GtkTextIter start, end;
gchar *selection = NULL;
@@ -199,10 +1282,8 @@ e_markdown_editor_insert_code_cb (GtkToolButton *button,
}
static void
-e_markdown_editor_add_link_cb (GtkToolButton *button,
- gpointer user_data)
+e_markdown_editor_insert_link_cb (EMarkdownEditor *self)
{
- EMarkdownEditor *self = user_data;
GtkTextBuffer *buffer;
GtkTextIter start, end;
gchar *selection = NULL;
@@ -254,43 +1335,40 @@ e_markdown_editor_add_link_cb (GtkToolButton *button,
}
static void
-e_markdown_editor_add_bullet_list_cb (GtkToolButton *button,
- gpointer user_data)
+e_markdown_editor_format_bullet_list_cb (EMarkdownEditor *self)
{
- EMarkdownEditor *self = user_data;
-
g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
e_markdown_editor_surround_selection (self, TRUE, "- ", NULL);
}
static void
-e_markdown_editor_add_numbered_list_cb (GtkToolButton *button,
- gpointer user_data)
+e_markdown_editor_format_numbered_list_cb (EMarkdownEditor *self)
{
- EMarkdownEditor *self = user_data;
-
g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
e_markdown_editor_surround_selection (self, TRUE, "1. ", NULL);
}
static void
-e_markdown_editor_add_header_cb (GtkToolButton *button,
- gpointer user_data)
+e_markdown_editor_format_header_cb (EMarkdownEditor *self)
{
- EMarkdownEditor *self = user_data;
-
g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
e_markdown_editor_surround_selection (self, TRUE, "# ", NULL);
}
static void
-e_markdown_editor_markdown_syntax_cb (GtkToolButton *button,
- gpointer user_data)
+e_markdown_editor_insert_emoji_cb (EMarkdownEditor *self)
+{
+ g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
+
+ g_signal_emit_by_name (self->priv->text_view, "insert-emoji", 0, NULL);
+}
+
+static void
+e_markdown_editor_markdown_syntax_cb (EMarkdownEditor *self)
{
- EMarkdownEditor *self = user_data;
GtkWidget *toplevel;
g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
@@ -375,14 +1453,15 @@ struct _toolbar_items {
static struct _toolbar_items toolbar_items[] = {
#define ITEM(lbl, icn, cbk) { lbl, icn, icn "-dark", G_CALLBACK (cbk) }
- ITEM (N_("Add bold text"), "markdown-bold", e_markdown_editor_add_bold_text_cb),
- ITEM (N_("Add italic text"), "markdown-italic", e_markdown_editor_add_italic_text_cb),
- ITEM (N_("Insert a quote"), "markdown-quote", e_markdown_editor_insert_quote_cb),
- ITEM (N_("Insert code"), "markdown-code", e_markdown_editor_insert_code_cb),
- ITEM (N_("Add a link"), "markdown-link", e_markdown_editor_add_link_cb),
- ITEM (N_("Add a bullet list"), "markdown-bullets", e_markdown_editor_add_bullet_list_cb),
- ITEM (N_("Add a numbered list"), "markdown-numbers", e_markdown_editor_add_numbered_list_cb),
- ITEM (N_("Add a header"), "markdown-header", e_markdown_editor_add_header_cb),
+ ITEM (N_("Add bold text"), "markdown-bold", e_markdown_editor_format_bold_text_cb),
+ ITEM (N_("Add italic text"), "markdown-italic", e_markdown_editor_format_italic_text_cb),
+ ITEM (N_("Insert a quote"), "markdown-quote", e_markdown_editor_format_quote_cb),
+ ITEM (N_("Insert code"), "markdown-code", e_markdown_editor_format_code_cb),
+ ITEM (N_("Add a link"), "markdown-link", e_markdown_editor_insert_link_cb),
+ ITEM (N_("Add a bullet list"), "markdown-bullets", e_markdown_editor_format_bullet_list_cb),
+ ITEM (N_("Add a numbered list"), "markdown-numbers", e_markdown_editor_format_numbered_list_cb),
+ ITEM (N_("Add a header"), "markdown-header", e_markdown_editor_format_header_cb),
+ ITEM (N_("Insert Emoji"), "markdown-emoji", e_markdown_editor_insert_emoji_cb),
ITEM (NULL, "", NULL),
ITEM (N_("Open online common mark documentation"), "markdown-help", G_CALLBACK
(e_markdown_editor_markdown_syntax_cb))
#undef ITEM
@@ -478,7 +1557,210 @@ e_markdown_editor_text_view_changed_cb (GtkTextView *text_view,
g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
+ e_markdown_editor_set_changed (self, TRUE);
+
g_signal_emit (self, signals[CHANGED], 0, NULL);
+ e_content_editor_emit_content_changed (E_CONTENT_EDITOR (self));
+}
+
+static void
+e_markdown_editor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CHANGED:
+ e_markdown_editor_set_changed (
+ E_MARKDOWN_EDITOR (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_EDITABLE:
+ e_markdown_editor_set_editable (
+ E_MARKDOWN_EDITOR (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_MODE:
+ e_markdown_editor_set_mode (
+ E_MARKDOWN_EDITOR (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_SPELL_CHECK_ENABLED:
+ e_markdown_editor_set_spell_check_enabled (
+ E_MARKDOWN_EDITOR (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_START_BOTTOM:
+ e_markdown_editor_set_start_bottom (
+ E_MARKDOWN_EDITOR (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_TOP_SIGNATURE:
+ e_markdown_editor_set_top_signature (
+ E_MARKDOWN_EDITOR (object),
+ g_value_get_enum (value));
+ return;
+
+ case PROP_VISUALLY_WRAP_LONG_LINES:
+ case PROP_LAST_ERROR:
+ case PROP_ALIGNMENT:
+ case PROP_BACKGROUND_COLOR:
+ case PROP_BLOCK_FORMAT:
+ case PROP_BOLD:
+ case PROP_FONT_COLOR:
+ case PROP_FONT_NAME:
+ case PROP_FONT_SIZE:
+ case PROP_INDENT_LEVEL:
+ case PROP_ITALIC:
+ case PROP_STRIKETHROUGH:
+ case PROP_SUBSCRIPT:
+ case PROP_SUPERSCRIPT:
+ case PROP_UNDERLINE:
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_markdown_editor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_IS_MALFUNCTION:
+ g_value_set_boolean (value, FALSE);
+ return;
+
+ case PROP_CAN_COPY:
+ g_value_set_boolean (
+ value, e_markdown_editor_can_copy (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_CAN_CUT:
+ g_value_set_boolean (
+ value, e_markdown_editor_can_cut (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_CAN_PASTE:
+ g_value_set_boolean (
+ value, e_markdown_editor_can_paste (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_CAN_REDO:
+ g_value_set_boolean (
+ value, e_markdown_editor_can_redo (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_CAN_UNDO:
+ g_value_set_boolean (
+ value, e_markdown_editor_can_undo (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_CHANGED:
+ g_value_set_boolean (
+ value, e_markdown_editor_get_changed (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_EDITABLE:
+ g_value_set_boolean (
+ value, e_markdown_editor_is_editable (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_MODE:
+ g_value_set_enum (
+ value, e_markdown_editor_get_mode (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_SPELL_CHECK_ENABLED:
+ g_value_set_boolean (
+ value,
+ e_markdown_editor_get_spell_check_enabled (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_SPELL_CHECKER:
+ g_value_set_object (
+ value,
+ e_markdown_editor_get_spell_checker (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_START_BOTTOM:
+ g_value_set_enum (
+ value,
+ e_markdown_editor_get_start_bottom (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_TOP_SIGNATURE:
+ g_value_set_enum (
+ value,
+ e_markdown_editor_get_top_signature (
+ E_MARKDOWN_EDITOR (object)));
+ return;
+
+ case PROP_VISUALLY_WRAP_LONG_LINES:
+ g_value_set_boolean (value, FALSE);
+ return;
+
+ case PROP_LAST_ERROR:
+ g_value_set_boxed (value, NULL);
+ return;
+
+ case PROP_ALIGNMENT:
+ g_value_set_enum (value, E_CONTENT_EDITOR_ALIGNMENT_LEFT);
+ return;
+
+ case PROP_BACKGROUND_COLOR:
+ g_value_set_boxed (value, NULL);
+ return;
+
+ case PROP_BLOCK_FORMAT:
+ g_value_set_enum (value, E_CONTENT_EDITOR_BLOCK_FORMAT_PRE);
+ return;
+
+ case PROP_FONT_COLOR:
+ g_value_set_boxed (value, NULL);
+ return;
+
+ case PROP_FONT_NAME:
+ g_value_set_string (value, NULL);
+ return;
+
+ case PROP_FONT_SIZE:
+ g_value_set_int (value, E_CONTENT_EDITOR_FONT_SIZE_NORMAL);
+ return;
+
+ case PROP_INDENT_LEVEL:
+ g_value_set_int (value, 0);
+ return;
+
+ case PROP_BOLD:
+ case PROP_ITALIC:
+ case PROP_STRIKETHROUGH:
+ case PROP_SUBSCRIPT:
+ case PROP_SUPERSCRIPT:
+ case PROP_UNDERLINE:
+ g_value_set_boolean (value, FALSE);
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
@@ -486,7 +1768,6 @@ e_markdown_editor_constructed (GObject *object)
{
EMarkdownEditor *self = E_MARKDOWN_EDITOR (object);
GtkWidget *widget;
- GtkNotebook *notebook;
GtkScrolledWindow *scrolled_window;
guint ii;
@@ -505,7 +1786,7 @@ e_markdown_editor_constructed (GObject *object)
NULL);
gtk_box_pack_start (GTK_BOX (self), widget, TRUE, TRUE, 0);
- notebook = GTK_NOTEBOOK (widget);
+ self->priv->notebook = GTK_NOTEBOOK (widget);
widget = gtk_scrolled_window_new (NULL, NULL);
g_object_set (G_OBJECT (widget),
@@ -518,7 +1799,7 @@ e_markdown_editor_constructed (GObject *object)
"vscrollbar-policy", GTK_POLICY_AUTOMATIC,
NULL);
- gtk_notebook_append_page (notebook, widget, gtk_label_new_with_mnemonic (_("_Write")));
+ gtk_notebook_append_page (self->priv->notebook, widget, gtk_label_new_with_mnemonic (_("_Write")));
scrolled_window = GTK_SCROLLED_WINDOW (widget);
@@ -553,7 +1834,7 @@ e_markdown_editor_constructed (GObject *object)
"vscrollbar-policy", GTK_POLICY_AUTOMATIC,
NULL);
- gtk_notebook_append_page (notebook, widget, gtk_label_new_with_mnemonic (_("_Preview")));
+ gtk_notebook_append_page (self->priv->notebook, widget, gtk_label_new_with_mnemonic (_("_Preview")));
scrolled_window = GTK_SCROLLED_WINDOW (widget);
@@ -575,7 +1856,7 @@ e_markdown_editor_constructed (GObject *object)
widget = gtk_toolbar_new ();
gtk_toolbar_set_icon_size (GTK_TOOLBAR (widget), GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_widget_show (widget);
- gtk_notebook_set_action_widget (notebook, widget, GTK_PACK_END);
+ gtk_notebook_set_action_widget (self->priv->notebook, widget, GTK_PACK_END);
self->priv->action_toolbar = GTK_TOOLBAR (widget);
self->priv->is_dark_theme = e_markdown_editor_is_dark_theme (self);
@@ -593,7 +1874,7 @@ e_markdown_editor_constructed (GObject *object)
item = gtk_tool_button_new (icon, _(toolbar_items[ii].label));
gtk_widget_set_name (GTK_WIDGET (item), toolbar_items[ii].icon_name);
gtk_tool_item_set_tooltip_text (item, _(toolbar_items[ii].label));
- g_signal_connect_object (item, "clicked", toolbar_items[ii].callback, self, 0);
+ g_signal_connect_object (item, "clicked", toolbar_items[ii].callback, self,
G_CONNECT_SWAPPED);
} else {
item = gtk_separator_tool_item_new ();
}
@@ -603,7 +1884,7 @@ e_markdown_editor_constructed (GObject *object)
}
#ifdef HAVE_MARKDOWN
- g_signal_connect_object (notebook, "switch-page", G_CALLBACK (e_markdown_editor_switch_page_cb),
self, 0);
+ g_signal_connect_object (self->priv->notebook, "switch-page", G_CALLBACK
(e_markdown_editor_switch_page_cb), self, 0);
#endif
g_signal_connect (self, "style-updated", G_CALLBACK (e_markdown_editor_style_updated_cb), NULL);
@@ -611,13 +1892,68 @@ e_markdown_editor_constructed (GObject *object)
e_signal_connect_notify_object (self->priv->text_view, "notify::editable", G_CALLBACK
(e_markdown_editor_notify_editable_cb), self, 0);
}
+static void
+e_markdown_editor_finalize (GObject *object)
+{
+ EMarkdownEditor *self = E_MARKDOWN_EDITOR (object);
+
+ g_clear_object (&self->priv->spell_checker);
+ g_clear_pointer (&self->priv->signature_uid, g_free);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_markdown_editor_parent_class)->finalize (object);
+}
+
static void
e_markdown_editor_class_init (EMarkdownEditorClass *klass)
{
GObjectClass *object_class;
+ GtkBindingSet *binding_set;
+
+ klass->format_bold = e_markdown_editor_format_bold_text_cb;
+ klass->format_italic = e_markdown_editor_format_italic_text_cb;
+ klass->format_quote = e_markdown_editor_format_quote_cb;
+ klass->format_code = e_markdown_editor_format_code_cb;
+ klass->format_bullet_list = e_markdown_editor_format_bullet_list_cb;
+ klass->format_numbered_list = e_markdown_editor_format_numbered_list_cb;
+ klass->format_header = e_markdown_editor_format_header_cb;
+ klass->insert_link = e_markdown_editor_insert_link_cb;
+ klass->insert_emoji = e_markdown_editor_insert_emoji_cb;
object_class = G_OBJECT_CLASS (klass);
+ object_class->get_property = e_markdown_editor_get_property;
+ object_class->set_property = e_markdown_editor_set_property;
object_class->constructed = e_markdown_editor_constructed;
+ object_class->finalize = e_markdown_editor_finalize;
+
+ g_object_class_override_property (object_class, PROP_IS_MALFUNCTION, "is-malfunction");
+ g_object_class_override_property (object_class, PROP_CAN_COPY, "can-copy");
+ g_object_class_override_property (object_class, PROP_CAN_CUT, "can-cut");
+ g_object_class_override_property (object_class, PROP_CAN_PASTE, "can-paste");
+ g_object_class_override_property (object_class, PROP_CAN_REDO, "can-redo");
+ g_object_class_override_property (object_class, PROP_CAN_UNDO, "can-undo");
+ g_object_class_override_property (object_class, PROP_CHANGED, "changed");
+ g_object_class_override_property (object_class, PROP_MODE, "mode");
+ g_object_class_override_property (object_class, PROP_EDITABLE, "editable");
+ g_object_class_override_property (object_class, PROP_ALIGNMENT, "alignment");
+ g_object_class_override_property (object_class, PROP_BACKGROUND_COLOR, "background-color");
+ g_object_class_override_property (object_class, PROP_BLOCK_FORMAT, "block-format");
+ g_object_class_override_property (object_class, PROP_BOLD, "bold");
+ g_object_class_override_property (object_class, PROP_FONT_COLOR, "font-color");
+ g_object_class_override_property (object_class, PROP_FONT_NAME, "font-name");
+ g_object_class_override_property (object_class, PROP_FONT_SIZE, "font-size");
+ g_object_class_override_property (object_class, PROP_INDENT_LEVEL, "indent-level");
+ g_object_class_override_property (object_class, PROP_ITALIC, "italic");
+ g_object_class_override_property (object_class, PROP_STRIKETHROUGH, "strikethrough");
+ g_object_class_override_property (object_class, PROP_SUBSCRIPT, "subscript");
+ g_object_class_override_property (object_class, PROP_SUPERSCRIPT, "superscript");
+ g_object_class_override_property (object_class, PROP_UNDERLINE, "underline");
+ g_object_class_override_property (object_class, PROP_START_BOTTOM, "start-bottom");
+ g_object_class_override_property (object_class, PROP_TOP_SIGNATURE, "top-signature");
+ g_object_class_override_property (object_class, PROP_SPELL_CHECK_ENABLED, "spell-check-enabled");
+ g_object_class_override_property (object_class, PROP_VISUALLY_WRAP_LONG_LINES,
"visually-wrap-long-lines");
+ g_object_class_override_property (object_class, PROP_LAST_ERROR, "last-error");
+ g_object_class_override_property (object_class, PROP_SPELL_CHECKER, "spell-checker");
/**
* EMarkdownEditor::changed:
@@ -631,16 +1967,215 @@ e_markdown_editor_class_init (EMarkdownEditorClass *klass)
"changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
- 0,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+
+ /**
+ * EMarkdownEditor::format-bold:
+ * @self: an #EMarkdownEditor, which receives the signal
+ *
+ * A signal to set text format to bold.
+ *
+ * Since: 3.44
+ **/
+ signals[FORMAT_BOLD] = g_signal_new (
+ "format-bold",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, format_bold),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+
+ /**
+ * EMarkdownEditor::format-italic:
+ * @self: an #EMarkdownEditor, which receives the signal
+ *
+ * A signal to set text format to italic.
+ *
+ * Since: 3.44
+ **/
+ signals[FORMAT_ITALIC] = g_signal_new (
+ "format-italic",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, format_italic),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+
+ /**
+ * EMarkdownEditor::format-quote:
+ * @self: an #EMarkdownEditor, which receives the signal
+ *
+ * A signal to set text format to quote.
+ *
+ * Since: 3.44
+ **/
+ signals[FORMAT_QUOTE] = g_signal_new (
+ "format-quote",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, format_quote),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+
+ /**
+ * EMarkdownEditor::format-code:
+ * @self: an #EMarkdownEditor, which receives the signal
+ *
+ * A signal to set text format to code.
+ *
+ * Since: 3.44
+ **/
+ signals[FORMAT_CODE] = g_signal_new (
+ "format-code",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, format_code),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+
+ /**
+ * EMarkdownEditor::format-bullet-list:
+ * @self: an #EMarkdownEditor, which receives the signal
+ *
+ * A signal to set text format to bullet list.
+ *
+ * Since: 3.44
+ **/
+ signals[FORMAT_BULLET_LIST] = g_signal_new (
+ "format-bullet-list",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, format_bullet_list),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+
+ /**
+ * EMarkdownEditor::format-numbered-list:
+ * @self: an #EMarkdownEditor, which receives the signal
+ *
+ * A signal to set text format to numbered list.
+ *
+ * Since: 3.44
+ **/
+ signals[FORMAT_NUMBERED_LIST] = g_signal_new (
+ "format-numbered-list",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, format_numbered_list),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+
+ /**
+ * EMarkdownEditor::format-header:
+ * @self: an #EMarkdownEditor, which receives the signal
+ *
+ * A signal to set text format to header.
+ *
+ * Since: 3.44
+ **/
+ signals[FORMAT_HEADER] = g_signal_new (
+ "format-header",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, format_header),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+
+ /**
+ * EMarkdownEditor::insert-link:
+ * @self: an #EMarkdownEditor, which receives the signal
+ *
+ * A signal to insert a link.
+ *
+ * Since: 3.44
+ **/
+ signals[INSERT_LINK] = g_signal_new (
+ "insert-link",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, insert_link),
NULL, NULL, NULL,
G_TYPE_NONE, 0,
G_TYPE_NONE);
+
+ /**
+ * EMarkdownEditor::insert-emoji:
+ * @self: an #EMarkdownEditor, which receives the signal
+ *
+ * A signal to open a dialog to insert Emoji.
+ *
+ * Since: 3.44
+ **/
+ signals[INSERT_EMOJI] = g_signal_new (
+ "insert-emoji",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMarkdownEditorClass, insert_emoji),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0,
+ G_TYPE_NONE);
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_b, GDK_CONTROL_MASK, "format-bold", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_i, GDK_CONTROL_MASK, "format-italic", 0);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_k, GDK_CONTROL_MASK, "insert-link", 0);
+}
+
+static void
+e_markdown_editor_content_editor_init (EContentEditorInterface *iface)
+{
+ iface->supports_mode = e_markdown_editor_supports_mode;
+ iface->grab_focus = e_markdown_editor_grab_focus;
+ iface->is_focus = e_markdown_editor_is_focus;
+ iface->initialize = e_markdown_editor_initialize;
+ iface->is_ready = e_markdown_editor_is_ready;
+ iface->update_styles = e_markdown_editor_update_styles;
+ iface->insert_content = e_markdown_editor_insert_content;
+ iface->get_content = e_markdown_editor_get_content;
+ iface->get_content_finish = e_markdown_editor_get_content_finish;
+ iface->move_caret_on_coordinates = e_markdown_editor_move_caret_on_coordinates;
+ iface->cut = e_markdown_editor_cut;
+ iface->copy = e_markdown_editor_copy;
+ iface->paste = e_markdown_editor_paste;
+ iface->paste_primary = e_markdown_editor_paste_primary;
+ iface->undo = e_markdown_editor_undo;
+ iface->redo = e_markdown_editor_redo;
+ iface->clear_undo_redo_history = e_markdown_editor_clear_undo_redo_history;
+ iface->set_spell_checking_languages = e_markdown_editor_set_spell_checking_languages;
+ iface->get_caret_word = e_markdown_editor_get_caret_word;
+ iface->replace_caret_word = e_markdown_editor_replace_caret_word;
+ iface->select_all = e_markdown_editor_select_all;
+ iface->find = e_markdown_editor_find;
+ iface->replace = e_markdown_editor_replace;
+ iface->replace_all = e_markdown_editor_replace_all;
+ iface->selection_save = e_markdown_editor_selection_save;
+ iface->selection_restore = e_markdown_editor_selection_restore;
+ iface->get_current_signature_uid = e_markdown_editor_get_current_signature_uid;
+ iface->insert_signature = e_markdown_editor_insert_signature;
+ iface->on_dialog_open = e_markdown_editor_on_dialog_open;
+ iface->on_dialog_close = e_markdown_editor_on_dialog_close;
}
static void
e_markdown_editor_init (EMarkdownEditor *self)
{
self->priv = e_markdown_editor_get_instance_private (self);
+
+ self->priv->spell_checker = e_spell_checker_new ();
+ self->priv->mode = E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT;
+ self->priv->start_bottom = E_THREE_STATE_INCONSISTENT;
+ self->priv->top_signature = E_THREE_STATE_INCONSISTENT;
}
/**
@@ -658,6 +2193,25 @@ e_markdown_editor_new (void)
return g_object_new (E_TYPE_MARKDOWN_EDITOR, NULL);
}
+/**
+ * e_markdown_editor_connect_focus_tracker:
+ * @self: an #EMarkdownEditor
+ * @focus_tracker: an #EFocusTracker
+ *
+ * Connects @self widgets to the @focus_tracker.
+ *
+ * Since: 3.44
+ **/
+void
+e_markdown_editor_connect_focus_tracker (EMarkdownEditor *self,
+ EFocusTracker *focus_tracker)
+{
+ g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
+ g_return_if_fail (E_IS_FOCUS_TRACKER (focus_tracker));
+
+ e_widget_undo_attach (GTK_WIDGET (self->priv->text_view), focus_tracker);
+}
+
/**
* e_markdown_editor_get_text_view:
* @self: an #EMarkdownEditor
@@ -691,6 +2245,28 @@ e_markdown_editor_get_action_toolbar (EMarkdownEditor *self)
return self->priv->action_toolbar;
}
+/**
+ * e_markdown_editor_set_text:
+ * @self an #EMarkdownEditor
+ * @text: text to set
+ *
+ * Sets the @text as the editor content.
+ *
+ * Since: 3.44
+ **/
+void
+e_markdown_editor_set_text (EMarkdownEditor *self,
+ const gchar *text)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
+ g_return_if_fail (text != NULL);
+
+ buffer = gtk_text_view_get_buffer (self->priv->text_view);
+ gtk_text_buffer_set_text (buffer, text, -1);
+}
+
/**
* e_markdown_editor_dup_text:
* @self: an #EMarkdownEditor
@@ -744,7 +2320,7 @@ e_markdown_editor_dup_html (EMarkdownEditor *self)
#ifdef HAVE_MARKDOWN
text = e_markdown_editor_dup_text (self);
- html = e_markdown_util_text_to_html (text, -1);
+ html = e_markdown_utils_text_to_html (text, -1);
g_free (text);
@@ -755,41 +2331,44 @@ e_markdown_editor_dup_html (EMarkdownEditor *self)
}
/**
- * e_markdown_util_text_to_html:
- * @plain_text: plain text with markdown to convert to HTML
- * @length: length of the @plain_text, or -1 when it's nul-terminated
- *
- * Convert @plain_text, possibly with markdown, into the HTML.
- *
- * Note: The function can return %NULL when was not built
- * with the markdown support.
+ * e_markdown_editor_get_preview_mode:
+ * @self: an #EMarkdownEditor
*
- * Returns: (transfer full) (nullable): text converted into HTML,
- * or %NULL, when was not built with the markdown support.
- * Free the string with g_free(), when no longer needed.
+ * Returns: whether the @self is in the preview mode; %FALSE means
+ * it is in the editing mode
*
* Since: 3.44
**/
-gchar *
-e_markdown_util_text_to_html (const gchar *plain_text,
- gssize length)
+gboolean
+e_markdown_editor_get_preview_mode (EMarkdownEditor *self)
{
- #ifdef HAVE_MARKDOWN
- GString *html;
- gchar *converted;
-
- if (length == -1)
- length = plain_text ? strlen (plain_text) : 0;
+ g_return_val_if_fail (E_IS_MARKDOWN_EDITOR (self), FALSE);
- converted = cmark_markdown_to_html (plain_text ? plain_text : "", length,
- CMARK_OPT_VALIDATE_UTF8 | CMARK_OPT_UNSAFE);
-
- html = e_str_replace_string (converted, "<blockquote>", "<blockquote type=\"cite\">");
+ return gtk_notebook_get_current_page (self->priv->notebook) == 1;
+}
- g_free (converted);
+/**
+ * e_markdown_editor_set_preview_mode:
+ * @self: an #EMarkdownEditor
+ * @preview_mode: %TRUE to set the preview mode, %FALSE otherwise
+ *
+ * Sets the @self into the preview mode, when @preview_mode is %TRUE, or
+ * into editing mode, when @preview_mode is %FALSE.
+ *
+ * Note: The request to move to the preview mode can be silently ignored
+ * when the Evolution was not built with the markdown support.
+ *
+ * Since: 3.44
+ **/
+void
+e_markdown_editor_set_preview_mode (EMarkdownEditor *self,
+ gboolean preview_mode)
+{
+ g_return_if_fail (E_IS_MARKDOWN_EDITOR (self));
- return g_string_free (html, FALSE);
+ #ifdef HAVE_MARKDOWN
+ gtk_notebook_set_current_page (self->priv->notebook, preview_mode ? 1 : 0);
#else
- return NULL;
+ gtk_notebook_set_current_page (self->priv->notebook, 0);
#endif
}
diff --git a/src/e-util/e-markdown-editor.h b/src/e-util/e-markdown-editor.h
index 0cacea1493..db62d706f5 100644
--- a/src/e-util/e-markdown-editor.h
+++ b/src/e-util/e-markdown-editor.h
@@ -13,6 +13,8 @@
#include <gtk/gtk.h>
+#include <e-util/e-focus-tracker.h>
+
/* Standard GObject macros */
#define E_TYPE_MARKDOWN_EDITOR \
(e_markdown_editor_get_type ())
@@ -45,17 +47,35 @@ struct _EMarkdownEditor {
struct _EMarkdownEditorClass {
GtkBoxClass parent_class;
+
+ void (* changed) (EMarkdownEditor *self);
+ void (* format_bold) (EMarkdownEditor *self);
+ void (* format_italic) (EMarkdownEditor *self);
+ void (* format_quote) (EMarkdownEditor *self);
+ void (* format_code) (EMarkdownEditor *self);
+ void (* format_bullet_list) (EMarkdownEditor *self);
+ void (* format_numbered_list)(EMarkdownEditor *self);
+ void (* format_header) (EMarkdownEditor *self);
+ void (* insert_link) (EMarkdownEditor *self);
+ void (* insert_emoji) (EMarkdownEditor *self);
+
+ /* Padding for future expansion */
+ gpointer padding[12];
};
GType e_markdown_editor_get_type (void) G_GNUC_CONST;
GtkWidget * e_markdown_editor_new (void);
+void e_markdown_editor_connect_focus_tracker (EMarkdownEditor *self,
+ EFocusTracker *focus_tracker);
GtkTextView * e_markdown_editor_get_text_view (EMarkdownEditor *self);
GtkToolbar * e_markdown_editor_get_action_toolbar (EMarkdownEditor *self);
+void e_markdown_editor_set_text (EMarkdownEditor *self,
+ const gchar *text);
gchar * e_markdown_editor_dup_text (EMarkdownEditor *self);
gchar * e_markdown_editor_dup_html (EMarkdownEditor *self);
-
-gchar * e_markdown_util_text_to_html (const gchar *plain_text,
- gssize length);
+gboolean e_markdown_editor_get_preview_mode (EMarkdownEditor *self);
+void e_markdown_editor_set_preview_mode (EMarkdownEditor *self,
+ gboolean preview_mode);
G_END_DECLS
diff --git a/src/e-util/e-markdown-utils.c b/src/e-util/e-markdown-utils.c
new file mode 100644
index 0000000000..ccfc6acb2a
--- /dev/null
+++ b/src/e-util/e-markdown-utils.c
@@ -0,0 +1,608 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-config.h"
+
+#ifdef HAVE_MARKDOWN
+#include <cmark.h>
+#endif
+
+#include <libxml/HTMLparser.h>
+#include <libxml/HTMLtree.h>
+
+#include "e-misc-utils.h"
+
+#include "e-markdown-utils.h"
+
+#define dd(x)
+
+/**
+ * e_markdown_utils_text_to_html:
+ * @plain_text: plain text with markdown to convert to HTML
+ * @length: length of the @plain_text, or -1 when it's nul-terminated
+ *
+ * Convert @plain_text, possibly with markdown, into the HTML.
+ *
+ * Note: The function can return %NULL when was not built
+ * with the markdown support.
+ *
+ * Returns: (transfer full) (nullable): text converted into HTML,
+ * or %NULL, when was not built with the markdown support.
+ * Free the string with g_free(), when no longer needed.
+ *
+ * Since: 3.44
+ **/
+gchar *
+e_markdown_utils_text_to_html (const gchar *plain_text,
+ gssize length)
+{
+ #ifdef HAVE_MARKDOWN
+ GString *html;
+ gchar *converted;
+
+ if (length == -1)
+ length = plain_text ? strlen (plain_text) : 0;
+
+ converted = cmark_markdown_to_html (plain_text ? plain_text : "", length,
+ CMARK_OPT_VALIDATE_UTF8 | CMARK_OPT_UNSAFE);
+
+ html = e_str_replace_string (converted, "<blockquote>", "<blockquote type=\"cite\">");
+
+ g_free (converted);
+
+ return g_string_free (html, FALSE);
+ #else
+ return NULL;
+ #endif
+}
+
+static const gchar *
+markdown_utils_get_attribute_value (const xmlChar **xcattrs,
+ const gchar *name)
+{
+ gint ii;
+
+ if (!xcattrs)
+ return NULL;
+
+ for (ii = 0; xcattrs[ii] && xcattrs[ii + 1]; ii += 2) {
+ if (g_ascii_strcasecmp (name, (const gchar *) xcattrs[ii]) == 0)
+ return (const gchar *) xcattrs[ii + 1];
+ }
+
+ return NULL;
+}
+
+struct _ComposerQuirks {
+ gboolean enabled;
+ gboolean reading_html_end;
+ gchar *to_body_credits;
+ gboolean cite_body;
+};
+
+static void
+markdown_utils_apply_composer_quirks (GString *buffer,
+ struct _ComposerQuirks *quirks)
+{
+ if (!quirks || !quirks->enabled)
+ return;
+
+ if (quirks->cite_body) {
+ gint ii;
+
+ g_string_insert (buffer, 0, "> ");
+
+ for (ii = 0; ii < buffer->len; ii++) {
+ if (buffer->str[ii] == '\n' && ii + 1 < buffer->len) {
+ g_string_insert (buffer, ii + 1, "> ");
+ ii += 2;
+ }
+ }
+ }
+
+ if (quirks->to_body_credits) {
+ g_string_insert (buffer, 0, "\n");
+ g_string_insert (buffer, 0, quirks->to_body_credits);
+ }
+}
+
+typedef struct _HTMLToTextData {
+ GString *buffer;
+ gboolean in_body;
+ gint in_code;
+ gint in_pre;
+ gint in_paragraph;
+ gboolean in_paragraph_end;
+ gboolean in_li;
+ GString *quote_prefix;
+ gchar *href;
+ GString *link_text;
+ GSList *list_index; /* gint; -1 for unordered list */
+ gboolean plain_text;
+ struct _ComposerQuirks composer_quirks;
+} HTMLToTextData;
+
+static void
+markdown_utils_sax_start_element_cb (gpointer ctx,
+ const xmlChar *xcname,
+ const xmlChar **xcattrs)
+{
+ HTMLToTextData *data = ctx;
+ const gchar *name = (const gchar *) xcname;
+ #if dd(1)+0
+ {
+ gint ii;
+
+ printf ("%s: '%s'\n", G_STRFUNC, name);
+ for (ii = 0; xcattrs && xcattrs[ii]; ii++) {
+ printf (" attr[%d]: '%s'\n", ii, xcattrs[ii]);
+ }
+ }
+ #endif
+
+ if (data->composer_quirks.enabled && g_ascii_strcasecmp (name, "span") == 0) {
+ const gchar *value;
+
+ value = markdown_utils_get_attribute_value (xcattrs, "class");
+
+ if (value && g_ascii_strcasecmp (value, "-x-evo-cite-body") == 0) {
+ data->composer_quirks.cite_body = TRUE;
+ return;
+ } else if (value && g_ascii_strcasecmp (value, "-x-evo-to-body") == 0) {
+ value = markdown_utils_get_attribute_value (xcattrs, "data-credits");
+
+ if (value && *value) {
+ g_free (data->composer_quirks.to_body_credits);
+ data->composer_quirks.to_body_credits = g_strdup (value);
+ return;
+ }
+ }
+ }
+
+ if (data->composer_quirks.reading_html_end)
+ return;
+
+ if (g_ascii_strcasecmp (name, "body") == 0) {
+ data->in_body = TRUE;
+ return;
+ }
+
+ if (!data->in_body)
+ return;
+
+ if (g_ascii_strcasecmp (name, "a") == 0) {
+ if (!data->plain_text && !data->href) {
+ const gchar *href;
+
+ href = markdown_utils_get_attribute_value (xcattrs, "href");
+
+ if (href && *href) {
+ data->href = g_strdup (href);
+ data->link_text = g_string_new (NULL);
+ }
+ }
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "blockquote") == 0) {
+ if (data->in_paragraph_end) {
+ if (data->quote_prefix->len)
+ g_string_append (data->buffer, data->quote_prefix->str);
+
+ g_string_append_c (data->buffer, '\n');
+
+ data->in_paragraph_end = FALSE;
+ }
+
+ g_string_append (data->quote_prefix, "> ");
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "br") == 0) {
+ if (data->plain_text) {
+ g_string_append (data->buffer, "\n");
+
+ if (data->quote_prefix->len)
+ g_string_append (data->buffer, data->quote_prefix->str);
+ } else if (!data->composer_quirks.enabled) {
+ g_string_append (data->buffer, "<br>");
+ }
+
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "b") == 0 ||
+ g_ascii_strcasecmp (name, "strong") == 0) {
+ if (!data->plain_text)
+ g_string_append (data->buffer, "**");
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "i") == 0 ||
+ g_ascii_strcasecmp (name, "em") == 0) {
+ if (!data->plain_text)
+ g_string_append (data->buffer, "*");
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "pre") == 0) {
+ data->in_paragraph++;
+ data->in_pre++;
+ if (data->in_pre == 1) {
+ if (!data->plain_text)
+ g_string_append (data->buffer, "```\n");
+ }
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "code") == 0) {
+ data->in_code++;
+ if (data->in_code == 1 && !data->in_pre && !data->plain_text)
+ g_string_append (data->buffer, "`");
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "h1") == 0 ||
+ g_ascii_strcasecmp (name, "h2") == 0 ||
+ g_ascii_strcasecmp (name, "h3") == 0 ||
+ g_ascii_strcasecmp (name, "h4") == 0 ||
+ g_ascii_strcasecmp (name, "h5") == 0 ||
+ g_ascii_strcasecmp (name, "h6") == 0) {
+ if (data->in_paragraph_end) {
+ g_string_append_c (data->buffer, '\n');
+ data->in_paragraph_end = FALSE;
+ }
+
+ data->in_paragraph++;
+ if (data->quote_prefix->len)
+ g_string_append (data->buffer, data->quote_prefix->str);
+
+ if (!data->plain_text) {
+ switch (name[1]) {
+ case '1':
+ g_string_append (data->buffer, "# ");
+ break;
+ case '2':
+ g_string_append (data->buffer, "## ");
+ break;
+ case '3':
+ g_string_append (data->buffer, "### ");
+ break;
+ case '4':
+ g_string_append (data->buffer, "#### ");
+ break;
+ case '5':
+ g_string_append (data->buffer, "##### ");
+ break;
+ case '6':
+ g_string_append (data->buffer, "###### ");
+ break;
+ }
+ }
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "p") == 0 ||
+ g_ascii_strcasecmp (name, "div") == 0) {
+ if (data->in_paragraph_end) {
+ data->in_paragraph_end = FALSE;
+
+ if (data->quote_prefix->len)
+ g_string_append (data->buffer, data->quote_prefix->str);
+
+ g_string_append_c (data->buffer, '\n');
+ }
+
+ data->in_paragraph++;
+ if (data->quote_prefix->len)
+ g_string_append (data->buffer, data->quote_prefix->str);
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "ul") == 0) {
+ if (data->in_paragraph_end) {
+ g_string_append_c (data->buffer, '\n');
+ data->in_paragraph_end = FALSE;
+ }
+ data->list_index = g_slist_prepend (data->list_index, GINT_TO_POINTER (-1));
+ data->in_li = FALSE;
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "ol") == 0) {
+ if (data->in_paragraph_end) {
+ g_string_append_c (data->buffer, '\n');
+ data->in_paragraph_end = FALSE;
+ }
+ data->list_index = g_slist_prepend (data->list_index, GINT_TO_POINTER (1));
+ data->in_li = FALSE;
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "li") == 0) {
+ data->in_paragraph_end = FALSE;
+ data->in_li = TRUE;
+
+ if (data->list_index) {
+ gint index = GPOINTER_TO_INT (data->list_index->data);
+ gint level = g_slist_length (data->list_index) - 1;
+
+ if (data->quote_prefix->len)
+ g_string_append (data->buffer, data->quote_prefix->str);
+
+ if (level > 0)
+ g_string_append_printf (data->buffer, "%*s", level * 3, "");
+
+ if (index == -1) {
+ g_string_append (data->buffer, "- ");
+ } else {
+ g_string_append_printf (data->buffer, "%d. ", index);
+ data->list_index->data = GINT_TO_POINTER (index + 1);
+ }
+ }
+ return;
+ }
+}
+
+static void
+markdown_utils_sax_end_element_cb (gpointer ctx,
+ const xmlChar *xcname)
+{
+ HTMLToTextData *data = ctx;
+ const gchar *name = (const gchar *) xcname;
+
+ dd (printf ("%s: '%s'\n", G_STRFUNC, name);)
+
+ if (g_ascii_strcasecmp (name, "body") == 0) {
+ data->in_body = FALSE;
+ return;
+ }
+
+ if (!data->in_body)
+ return;
+
+ if (g_ascii_strcasecmp (name, "a") == 0) {
+ if (!data->plain_text && data->href && data->link_text) {
+ g_string_append_printf (data->buffer, "[%s](%s)", data->link_text->str, data->href);
+
+ g_free (data->href);
+ data->href = NULL;
+
+ g_string_free (data->link_text, TRUE);
+ data->link_text = NULL;
+ }
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "blockquote") == 0) {
+ if (data->quote_prefix->len > 1)
+ g_string_truncate (data->quote_prefix, data->quote_prefix->len - 2);
+
+ data->in_paragraph_end = data->quote_prefix->len > 1;
+
+ if (!data->in_paragraph_end)
+ g_string_append_c (data->buffer, '\n');
+
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "b") == 0 ||
+ g_ascii_strcasecmp (name, "strong") == 0) {
+ if (!data->plain_text)
+ g_string_append (data->buffer, "**");
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "i") == 0 ||
+ g_ascii_strcasecmp (name, "em") == 0) {
+ if (!data->plain_text)
+ g_string_append (data->buffer, "*");
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "pre") == 0) {
+ if (data->in_paragraph > 0)
+ data->in_paragraph--;
+
+ if (data->in_pre > 0) {
+ data->in_pre--;
+ if (data->in_pre == 0 && !data->plain_text)
+ g_string_append (data->buffer, "```");
+ g_string_append_c (data->buffer, '\n');
+ }
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "code") == 0) {
+ if (data->in_code > 0) {
+ data->in_code--;
+ if (data->in_code == 0 && !data->in_pre && !data->plain_text)
+ g_string_append (data->buffer, "`");
+ }
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "p") == 0 ||
+ g_ascii_strcasecmp (name, "div") == 0 ||
+ g_ascii_strcasecmp (name, "h1") == 0 ||
+ g_ascii_strcasecmp (name, "h2") == 0 ||
+ g_ascii_strcasecmp (name, "h3") == 0 ||
+ g_ascii_strcasecmp (name, "h4") == 0 ||
+ g_ascii_strcasecmp (name, "h5") == 0 ||
+ g_ascii_strcasecmp (name, "h6") == 0) {
+ /* To avoid double-line ends when parsing composer HTML */
+ if (data->composer_quirks.enabled && !(
+ g_ascii_strcasecmp (name, "p") == 0 ||
+ g_ascii_strcasecmp (name, "div") == 0))
+ g_string_append_c (data->buffer, '\n');
+
+ data->in_paragraph_end = TRUE;
+
+ if (data->in_paragraph > 0)
+ data->in_paragraph--;
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "ul") == 0 ||
+ g_ascii_strcasecmp (name, "ol") == 0) {
+ if (data->list_index)
+ data->list_index = g_slist_remove (data->list_index, data->list_index->data);
+ data->in_paragraph_end = data->list_index == NULL;
+
+ if (!data->in_paragraph_end && data->buffer->len && data->buffer->str[data->buffer->len - 1]
== '\n')
+ g_string_truncate (data->buffer, data->buffer->len - 1);
+
+ return;
+ }
+
+ if (g_ascii_strcasecmp (name, "li") == 0) {
+ g_string_append_c (data->buffer, '\n');
+
+ data->in_paragraph_end = FALSE;
+ data->in_li = FALSE;
+
+ return;
+ }
+}
+
+static gboolean
+markdown_utils_only_whitespace (const gchar *text,
+ gint len)
+{
+ gint ii;
+
+ for (ii = 0; ii < len && text[ii]; ii++) {
+ if (!g_ascii_isspace (text[ii]))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+markdown_utils_sax_characters_cb (gpointer ctx,
+ const xmlChar *xctext,
+ gint len)
+{
+ HTMLToTextData *data = ctx;
+ const gchar *text = (const gchar *) xctext;
+
+ dd (printf ("%s: text:'%.*s' in_body:%d in_paragraph:%d in_li:%d\n", G_STRFUNC, len, text,
data->in_body, data->in_paragraph, data->in_li);)
+
+ if (data->in_body && (data->in_paragraph || data->in_li || !markdown_utils_only_whitespace (text,
len))) {
+ if (data->link_text) {
+ g_string_append_len (data->link_text, text, len);
+ } else {
+ gsize from_index = data->buffer->len;
+
+ g_string_append_len (data->buffer, text, len);
+
+ if (data->quote_prefix->len && !data->in_li && strchr (data->buffer->str +
from_index, '\n')) {
+ gint ii;
+
+ for (ii = from_index; ii < data->buffer->len; ii++) {
+ if (data->buffer->str[ii] == '\n') {
+ g_string_insert (data->buffer, ii + 1,
data->quote_prefix->str);
+ ii += data->quote_prefix->len + 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+static void
+markdown_utils_sax_warning_cb (gpointer ctx,
+ const gchar *msg,
+ ...)
+{
+ /* Ignore these */
+}
+
+static void
+markdown_utils_sax_error_cb (gpointer ctx,
+ const gchar *msg,
+ ...)
+{
+ /* Ignore these */
+}
+
+/**
+ * e_markdown_utils_html_to_text:
+ * @html: a text in HTML
+ * @length: length of the @html, or -1 when it's nul-terminated
+ * @flags: a bit-or of %EMarkdownHTMLToTextFlags
+ *
+ * Convert @html into the markdown text. The @flags influence
+ * what can be preserved from the @html.
+ *
+ * Returns: (transfer full) (nullable): HTML converted into markdown text.
+ * Free the string with g_free(), when no longer needed.
+ *
+ * Since: 3.44
+ **/
+gchar *
+e_markdown_utils_html_to_text (const gchar *html,
+ gssize length,
+ EMarkdownHTMLToTextFlags flags)
+{
+ htmlParserCtxtPtr ctxt;
+ htmlSAXHandler sax;
+ HTMLToTextData data;
+
+ if (length < 0)
+ length = html ? strlen (html) : 0;
+
+ memset (&data, 0, sizeof (HTMLToTextData));
+
+ data.buffer = g_string_new (NULL);
+ data.quote_prefix = g_string_new (NULL);
+ data.plain_text = (flags & E_MARKDOWN_HTML_TO_TEXT_FLAG_PLAIN_TEXT) != 0;
+ data.composer_quirks.enabled = (flags & E_MARKDOWN_HTML_TO_TEXT_FLAG_COMPOSER_QUIRKS) != 0;
+
+ memset (&sax, 0, sizeof (htmlSAXHandler));
+
+ sax.startElement = markdown_utils_sax_start_element_cb;
+ sax.endElement = markdown_utils_sax_end_element_cb;
+ sax.characters = markdown_utils_sax_characters_cb;
+ sax.warning = markdown_utils_sax_warning_cb;
+ sax.error = markdown_utils_sax_error_cb;
+
+ ctxt = htmlCreatePushParserCtxt (&sax, &data, html ? html : "", length, "", XML_CHAR_ENCODING_UTF8);
+
+ htmlParseChunk (ctxt, "", 0, 1);
+
+ /* The libxml doesn't read elements after </html>, but the quirks can be stored after them,
+ thus retry after that element end, if it exists. */
+ if (data.composer_quirks.enabled && html && ctxt->input && ctxt->input->cur) {
+ guint html_end_length = ctxt->input->end - ctxt->input->cur;
+
+ if (html_end_length > 1) {
+ htmlParserCtxtPtr ctxt2;
+
+ data.composer_quirks.reading_html_end = TRUE;
+
+ ctxt2 = htmlCreatePushParserCtxt (&sax, &data, (const gchar *) ctxt->input->cur,
html_end_length, "", XML_CHAR_ENCODING_UTF8);
+ htmlParseChunk (ctxt2, "", 0, 1);
+ htmlFreeParserCtxt (ctxt2);
+ }
+ }
+
+ htmlFreeParserCtxt (ctxt);
+
+ markdown_utils_apply_composer_quirks (data.buffer, &data.composer_quirks);
+
+ g_free (data.href);
+
+ if (data.link_text)
+ g_string_free (data.link_text, TRUE);
+
+ g_string_free (data.quote_prefix, TRUE);
+ g_slist_free (data.list_index);
+ g_free (data.composer_quirks.to_body_credits);
+
+ return g_string_free (data.buffer, FALSE);
+}
diff --git a/src/e-util/e-markdown-utils.h b/src/e-util/e-markdown-utils.h
new file mode 100644
index 0000000000..928dbbbb70
--- /dev/null
+++ b/src/e-util/e-markdown-utils.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MARKDOWN_UTILS_H
+#define E_MARKDOWN_UTILS_H
+
+#include <glib.h>
+
+#include <e-util/e-util-enums.h>
+
+G_BEGIN_DECLS
+
+gchar * e_markdown_utils_text_to_html (const gchar *plain_text,
+ gssize length);
+gchar * e_markdown_utils_html_to_text (const gchar *html,
+ gssize length,
+ EMarkdownHTMLToTextFlags flags);
+
+G_END_DECLS
+
+#endif /* E_MARKDOWN_UTILS_H */
diff --git a/src/e-util/e-spell-text-view.c b/src/e-util/e-spell-text-view.c
index d63a594ef0..0a2a80de21 100644
--- a/src/e-util/e-spell-text-view.c
+++ b/src/e-util/e-spell-text-view.c
@@ -103,3 +103,85 @@ e_spell_text_view_attach (GtkTextView *text_view)
gspell_text_view_set_enable_language_menu (spell_view, TRUE);
#endif /* HAVE_GSPELL */
}
+
+/**
+ * e_spell_text_view_get_enabled:
+ * @text_view: a #GtkTextView
+ *
+ * Returns: whether the inline spell checking is enabled for the @text_view.
+ * This can be used only after calling e_spell_text_view_attach().
+ *
+ * Since: 3.44
+ **/
+gboolean
+e_spell_text_view_get_enabled (GtkTextView *text_view)
+{
+#ifdef HAVE_GSPELL
+ GspellTextView *spell_view;
+
+ spell_view = gspell_text_view_get_from_gtk_text_view (text_view);
+
+ return gspell_text_view_get_inline_spell_checking (spell_view);
+#else /* HAVE_GSPELL */
+ return FALSE;
+#endif /* HAVE_GSPELL */
+}
+
+/**
+ * e_spell_text_view_set_enabled:
+ * @text_view: a #GtkTextView
+ * @enabled: value to set
+ *
+ * Sets whether the inline spell checking is enabled for the @text_view.
+ * This can be used only after calling e_spell_text_view_attach().
+ *
+ * Since: 3.44
+ **/
+void
+e_spell_text_view_set_enabled (GtkTextView *text_view,
+ gboolean enabled)
+{
+#ifdef HAVE_GSPELL
+ GspellTextView *spell_view;
+
+ spell_view = gspell_text_view_get_from_gtk_text_view (text_view);
+
+ gspell_text_view_set_inline_spell_checking (spell_view, enabled);
+#endif /* HAVE_GSPELL */
+}
+
+/**
+ * e_spell_text_view_set_languages:
+ * @text_view: a #GtkTextView
+ * @languages: (nullable): languages to set, or %NULL to unset any previous
+ *
+ * Sets @languages for inline spell checking for the @text_view.
+ * This can be used only after calling e_spell_text_view_attach().
+ *
+ * Since: 3.44
+ **/
+void
+e_spell_text_view_set_languages (GtkTextView *text_view,
+ const gchar **languages)
+{
+#ifdef HAVE_GSPELL
+ GspellTextBuffer *spell_buffer;
+ GspellChecker *checker = NULL;
+ GtkTextBuffer *text_buffer;
+ guint ii;
+
+ for (ii = 0; !checker && languages && languages[ii]; ii++) {
+ const GspellLanguage *language;
+
+ language = gspell_language_lookup (languages[ii]);
+
+ if (language)
+ checker = gspell_checker_new (language);
+ }
+
+ text_buffer = gtk_text_view_get_buffer (text_view);
+ spell_buffer = gspell_text_buffer_get_from_gtk_text_buffer (text_buffer);
+ gspell_text_buffer_set_spell_checker (spell_buffer, checker);
+ g_clear_object (&checker);
+#endif /* HAVE_GSPELL */
+}
diff --git a/src/e-util/e-spell-text-view.h b/src/e-util/e-spell-text-view.h
index 0d8788eca0..7026fd0e4c 100644
--- a/src/e-util/e-spell-text-view.h
+++ b/src/e-util/e-spell-text-view.h
@@ -28,6 +28,11 @@ G_BEGIN_DECLS
gboolean e_spell_text_view_is_supported (void);
void e_spell_text_view_attach (GtkTextView *text_view);
+gboolean e_spell_text_view_get_enabled (GtkTextView *text_view);
+void e_spell_text_view_set_enabled (GtkTextView *text_view,
+ gboolean enabled);
+void e_spell_text_view_set_languages (GtkTextView *text_view,
+ const gchar **languages);
G_END_DECLS
diff --git a/src/e-util/e-util-enums.h b/src/e-util/e-util-enums.h
index 2a32dfa8bb..18cd855ca0 100644
--- a/src/e-util/e-util-enums.h
+++ b/src/e-util/e-util-enums.h
@@ -525,6 +525,28 @@ typedef enum {
E_CONTENT_EDITOR_FIND_WRAP_AROUND = 1 << 4
} EContentEditorFindFlags;
+/**
+ * EContentEditorMode:
+ * @E_CONTENT_EDITOR_MODE_UNKNOWN: unknown mode
+ * @E_CONTENT_EDITOR_MODE_PLAIN_TEXT: plain text, expects export as text/plain
+ * @E_CONTENT_EDITOR_MODE_HTML: HTML, expects export as text/html
+ * @E_CONTENT_EDITOR_MODE_MARKDOWN: markdown, expects export as text/markdown
+ * @E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT: markdown, expects export as text/plain
+ * @E_CONTENT_EDITOR_MODE_MARKDOWN_HTML: markdown, expects export as text/html
+ *
+ * Editing mode of a content editor.
+ *
+ * Since: 3.44
+ **/
+typedef enum {
+ E_CONTENT_EDITOR_MODE_UNKNOWN = -1,
+ E_CONTENT_EDITOR_MODE_PLAIN_TEXT,
+ E_CONTENT_EDITOR_MODE_HTML,
+ E_CONTENT_EDITOR_MODE_MARKDOWN,
+ E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT,
+ E_CONTENT_EDITOR_MODE_MARKDOWN_HTML
+} EContentEditorMode;
+
/**
* EUndoRedoState:
* @E_UNDO_REDO_STATE_NONE: Cannot undo, neither redo.
@@ -620,6 +642,22 @@ typedef enum {
**/
#define E_CONFIG_LOOKUP_RESULT_LAST_KIND E_CONFIG_LOOKUP_RESULT_TASK_LIST
+/**
+ * EMarkdownHTMLToTextFlags:
+ * @E_MARKDOWN_HTML_TO_TEXT_FLAG_NONE: no flag set
+ * @E_MARKDOWN_HTML_TO_TEXT_FLAG_PLAIN_TEXT: disallow any HTML, save in pure plain text
+ * @E_MARKDOWN_HTML_TO_TEXT_FLAG_COMPOSER_QUIRKS: enable composer quirks to post-process the text
+ *
+ * Flags used in e_markdown_util_html_to_text().
+ *
+ * Since: 3.44
+ **/
+typedef enum { /*< flags >*/
+ E_MARKDOWN_HTML_TO_TEXT_FLAG_NONE = 0,
+ E_MARKDOWN_HTML_TO_TEXT_FLAG_PLAIN_TEXT = 1 << 0,
+ E_MARKDOWN_HTML_TO_TEXT_FLAG_COMPOSER_QUIRKS = 1 << 1
+} EMarkdownHTMLToTextFlags;
+
G_END_DECLS
#endif /* E_UTIL_ENUMS_H */
diff --git a/src/e-util/e-util.h b/src/e-util/e-util.h
index 666363e9e8..6801baf41c 100644
--- a/src/e-util/e-util.h
+++ b/src/e-util/e-util.h
@@ -153,6 +153,7 @@
#include <e-util/e-mail-signature-tree-view.h>
#include <e-util/e-map.h>
#include <e-util/e-markdown-editor.h>
+#include <e-util/e-markdown-utils.h>
#include <e-util/e-menu-tool-action.h>
#include <e-util/e-menu-tool-button.h>
#include <e-util/e-misc-utils.h>
diff --git a/src/e-util/test-html-editor-units-utils.c b/src/e-util/test-html-editor-units-utils.c
index abbeef8130..8912e64c38 100644
--- a/src/e-util/test-html-editor-units-utils.c
+++ b/src/e-util/test-html-editor-units-utils.c
@@ -292,23 +292,7 @@ test_utils_html_editor_created_cb (GObject *source_object,
fixture->focus_tracker = e_focus_tracker_new (GTK_WINDOW (fixture->window));
- e_focus_tracker_set_cut_clipboard_action (fixture->focus_tracker,
- e_html_editor_get_action (fixture->editor, "cut"));
-
- e_focus_tracker_set_copy_clipboard_action (fixture->focus_tracker,
- e_html_editor_get_action (fixture->editor, "copy"));
-
- e_focus_tracker_set_paste_clipboard_action (fixture->focus_tracker,
- e_html_editor_get_action (fixture->editor, "paste"));
-
- e_focus_tracker_set_select_all_action (fixture->focus_tracker,
- e_html_editor_get_action (fixture->editor, "select-all"));
-
- e_focus_tracker_set_undo_action (fixture->focus_tracker,
- e_html_editor_get_action (fixture->editor, "undo"));
-
- e_focus_tracker_set_redo_action (fixture->focus_tracker,
- e_html_editor_get_action (fixture->editor, "redo"));
+ e_html_editor_connect_focus_tracker (fixture->editor, fixture->focus_tracker);
/* Make sure this is off */
test_utils_fixture_change_setting_boolean (fixture,
diff --git a/src/e-util/test-html-editor.c b/src/e-util/test-html-editor.c
index 7dd5765be4..f8a9bb8cf6 100644
--- a/src/e-util/test-html-editor.c
+++ b/src/e-util/test-html-editor.c
@@ -344,7 +344,7 @@ action_save_cb (GtkAction *action,
return;
filename = e_html_editor_get_filename (editor);
- as_html = (e_content_editor_get_html_mode (e_html_editor_get_content_editor (editor)));
+ as_html = e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_HTML;
e_html_editor_save (editor, filename, as_html, NULL, html_editor_save_done_cb, NULL);
}
@@ -360,7 +360,7 @@ action_save_as_cb (GtkAction *action,
return;
filename = e_html_editor_get_filename (editor);
- as_html = (e_content_editor_get_html_mode (e_html_editor_get_content_editor (editor)));
+ as_html = e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_HTML;
e_html_editor_save (editor, filename, as_html, NULL, html_editor_save_done_cb, NULL);
}
@@ -592,23 +592,7 @@ create_new_editor_cb (GObject *source_object,
focus_tracker = e_focus_tracker_new (GTK_WINDOW (widget));
g_object_set_data_full (G_OBJECT (widget), "e-focus-tracker", focus_tracker, g_object_unref);
- e_focus_tracker_set_cut_clipboard_action (focus_tracker,
- e_html_editor_get_action (editor, "cut"));
-
- e_focus_tracker_set_copy_clipboard_action (focus_tracker,
- e_html_editor_get_action (editor, "copy"));
-
- e_focus_tracker_set_paste_clipboard_action (focus_tracker,
- e_html_editor_get_action (editor, "paste"));
-
- e_focus_tracker_set_select_all_action (focus_tracker,
- e_html_editor_get_action (editor, "select-all"));
-
- e_focus_tracker_set_undo_action (focus_tracker,
- e_html_editor_get_action (editor, "undo"));
-
- e_focus_tracker_set_redo_action (focus_tracker,
- e_html_editor_get_action (editor, "redo"));
+ e_html_editor_connect_focus_tracker (editor, focus_tracker);
g_signal_connect_swapped (
widget, "destroy",
diff --git a/src/e-util/test-mail-signatures.c b/src/e-util/test-mail-signatures.c
index 95ad835668..b0f5dd5da4 100644
--- a/src/e-util/test-mail-signatures.c
+++ b/src/e-util/test-mail-signatures.c
@@ -29,11 +29,11 @@ signature_loaded_cb (EMailSignatureComboBox *combo_box,
EWebView *web_view)
{
gchar *contents = NULL;
- gboolean is_html;
+ EContentEditorMode editor_mode = E_CONTENT_EDITOR_MODE_UNKNOWN;
GError *error = NULL;
e_mail_signature_combo_box_load_selected_finish (
- combo_box, result, &contents, NULL, &is_html, &error);
+ combo_box, result, &contents, NULL, &editor_mode, &error);
/* Ignore cancellations. */
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
@@ -55,7 +55,7 @@ signature_loaded_cb (EMailSignatureComboBox *combo_box,
if (contents == NULL)
e_web_view_clear (web_view);
- else if (is_html)
+ else if (editor_mode == E_CONTENT_EDITOR_MODE_HTML)
e_web_view_load_string (web_view, contents);
else {
gchar *string;
diff --git a/src/e-util/test-markdown-editor.c b/src/e-util/test-markdown-editor.c
index 88d439dd7e..9fc5dc32a3 100644
--- a/src/e-util/test-markdown-editor.c
+++ b/src/e-util/test-markdown-editor.c
@@ -19,10 +19,153 @@ window_delete_event_cb (GtkWidget *widget,
return FALSE;
}
+static void
+editor_to_plain_text_cb (GObject *button,
+ EMarkdownEditor *editor)
+{
+ GtkTextView *text_view;
+ GtkTextBuffer *buffer;
+ gchar *text;
+
+ text_view = g_object_get_data (button, "text_view");
+ buffer = gtk_text_view_get_buffer (text_view);
+ text = e_markdown_editor_dup_text (editor);
+
+ gtk_text_buffer_set_text (buffer, text, -1);
+
+ g_free (text);
+}
+
+static void
+editor_to_html_cb (GObject *button,
+ EMarkdownEditor *editor)
+{
+ GtkTextView *text_view;
+ GtkTextBuffer *buffer;
+ gchar *text;
+
+ text_view = g_object_get_data (button, "text_view");
+ buffer = gtk_text_view_get_buffer (text_view);
+ text = e_markdown_editor_dup_html (editor);
+
+ gtk_text_buffer_set_text (buffer, text ? text : "NULL", -1);
+
+ g_free (text);
+}
+
+/* The cmark can add an empty line at the end of the HTML, thus compare without it too */
+static gboolean
+texts_are_same (gchar *text1,
+ gchar *text2)
+{
+ gint len1 = 0, len2 = 0;
+ gboolean text1_modified = FALSE, text2_modified = FALSE;
+ gboolean same;
+
+ if (text1 && text2) {
+ len1 = strlen (text1);
+ len2 = strlen (text2);
+
+ if (len1 + 1 == len2 && text2[len2 - 1] == '\n') {
+ text2[len2 - 1] = '\0';
+ text2_modified = TRUE;
+ } else if (len1 == len2 + 1 && text1[len1 - 1] == '\n') {
+ text1[len1 - 1] = '\0';
+ text1_modified = TRUE;
+ }
+ }
+
+ same = g_strcmp0 (text1, text2) == 0;
+
+ if (text1_modified)
+ text1[len1 - 1] = '\n';
+ else if (text2_modified)
+ text2[len2 - 1] = '\n';
+
+ return same || ((text1_modified || text2_modified) && g_strcmp0 (text1, text2) == 0);
+}
+
+static void
+plain_text_to_html_cb (GObject *button,
+ GtkLabel *label)
+{
+ GtkTextView *plain_text_view, *html_text_view;
+ GtkTextBuffer *plain_buffer, *html_buffer;
+ GtkTextIter start_iter, end_iter;
+ gchar *old_text, *new_text, *tmp;
+
+ plain_text_view = g_object_get_data (button, "plain_text_view");
+ plain_buffer = gtk_text_view_get_buffer (plain_text_view);
+
+ html_text_view = g_object_get_data (button, "html_text_view");
+ html_buffer = gtk_text_view_get_buffer (html_text_view);
+
+ gtk_text_buffer_get_bounds (html_buffer, &start_iter, &end_iter);
+ old_text = gtk_text_buffer_get_text (html_buffer, &start_iter, &end_iter, FALSE);
+
+ gtk_text_buffer_get_bounds (plain_buffer, &start_iter, &end_iter);
+ tmp = gtk_text_buffer_get_text (plain_buffer, &start_iter, &end_iter, FALSE);
+
+ new_text = e_markdown_utils_text_to_html (tmp, -1);
+
+ gtk_text_buffer_set_text (html_buffer, new_text ? new_text : "NULL", -1);
+
+ if (texts_are_same (new_text, old_text))
+ gtk_label_set_text (label, "HTML text matches");
+ else
+ gtk_label_set_text (label, "Old and new HTML texts differ");
+
+ g_free (old_text);
+ g_free (new_text);
+ g_free (tmp);
+}
+
+static void
+html_to_plain_text_cb (GObject *button,
+ GtkLabel *label)
+{
+ GtkTextView *plain_text_view, *html_text_view;
+ GtkTextBuffer *plain_buffer, *html_buffer;
+ GtkTextIter start_iter, end_iter;
+ GtkToggleButton *disallow_html_check;
+ gchar *old_text, *new_text, *tmp;
+
+ disallow_html_check = g_object_get_data (button, "disallow_html_check");
+
+ plain_text_view = g_object_get_data (button, "plain_text_view");
+ plain_buffer = gtk_text_view_get_buffer (plain_text_view);
+
+ html_text_view = g_object_get_data (button, "html_text_view");
+ html_buffer = gtk_text_view_get_buffer (html_text_view);
+
+ gtk_text_buffer_get_bounds (plain_buffer, &start_iter, &end_iter);
+ old_text = gtk_text_buffer_get_text (plain_buffer, &start_iter, &end_iter, FALSE);
+
+ gtk_text_buffer_get_bounds (html_buffer, &start_iter, &end_iter);
+ tmp = gtk_text_buffer_get_text (html_buffer, &start_iter, &end_iter, FALSE);
+
+ new_text = e_markdown_utils_html_to_text (tmp, -1, E_MARKDOWN_HTML_TO_TEXT_FLAG_NONE |
+ (gtk_toggle_button_get_active (disallow_html_check) ? E_MARKDOWN_HTML_TO_TEXT_FLAG_PLAIN_TEXT
: 0));
+
+ gtk_text_buffer_set_text (plain_buffer, new_text ? new_text : "NULL", -1);
+
+ if (texts_are_same (new_text, old_text))
+ gtk_label_set_text (label, "Plain text matches");
+ else
+ gtk_label_set_text (label, "Old and new plain texts differ");
+
+ g_free (old_text);
+ g_free (new_text);
+ g_free (tmp);
+}
+
static gint
on_idle_create_widget (gpointer user_data)
{
- GtkWidget *window, *editor;
+ GtkWidget *window, *editor, *widget, *button1, *button2;
+ GtkGrid *grid;
+ GtkTextView *plain_text_view;
+ GtkTextView *html_text_view;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (window), 640, 480);
@@ -31,6 +174,20 @@ on_idle_create_widget (gpointer user_data)
window, "delete-event",
G_CALLBACK (window_delete_event_cb), NULL);
+ widget = gtk_grid_new ();
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ "vexpand", TRUE,
+ "visible", TRUE,
+ NULL);
+
+ gtk_container_add (GTK_CONTAINER (window), widget);
+
+ grid = GTK_GRID (widget);
+
editor = e_markdown_editor_new ();
g_object_set (G_OBJECT (editor),
@@ -41,7 +198,157 @@ on_idle_create_widget (gpointer user_data)
"visible", TRUE,
NULL);
- gtk_container_add (GTK_CONTAINER (window), editor);
+ gtk_grid_attach (grid, editor, 0, 0, 3, 1);
+
+ widget = gtk_button_new_with_label ("vvv As Plain Text vvv");
+ button1 = widget;
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_START,
+ "hexpand", FALSE,
+ "valign", GTK_ALIGN_START,
+ "vexpand", FALSE,
+ "visible", TRUE,
+ NULL);
+
+ gtk_grid_attach (grid, widget, 0, 1, 1, 1);
+
+ widget = gtk_button_new_with_label ("vvv As HTML vvv");
+ button2 = widget;
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_START,
+ "hexpand", FALSE,
+ "valign", GTK_ALIGN_START,
+ "vexpand", FALSE,
+ "visible", TRUE,
+ NULL);
+
+ gtk_grid_attach (grid, widget, 2, 1, 1, 1);
+
+ widget = gtk_text_view_new ();
+ plain_text_view = GTK_TEXT_VIEW (widget);
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ "vexpand", TRUE,
+ "visible", TRUE,
+ "editable", TRUE,
+ "wrap-mode", GTK_WRAP_WORD_CHAR,
+ NULL);
+
+ widget = gtk_scrolled_window_new (NULL, NULL);
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ "vexpand", TRUE,
+ "visible", TRUE,
+ "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+ "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
+ NULL);
+
+ gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (plain_text_view));
+
+ gtk_grid_attach (grid, widget, 0, 2, 1, 2);
+
+ widget = gtk_text_view_new ();
+ html_text_view = GTK_TEXT_VIEW (widget);
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ "vexpand", TRUE,
+ "visible", TRUE,
+ "editable", TRUE,
+ "wrap-mode", GTK_WRAP_WORD_CHAR,
+ NULL);
+
+ widget = gtk_scrolled_window_new (NULL, NULL);
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_FILL,
+ "hexpand", TRUE,
+ "valign", GTK_ALIGN_FILL,
+ "vexpand", TRUE,
+ "visible", TRUE,
+ "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+ "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
+ NULL);
+
+ gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (html_text_view));
+
+ gtk_grid_attach (grid, widget, 2, 2, 1, 2);
+
+ g_object_set_data (G_OBJECT (button1), "text_view", plain_text_view);
+ g_signal_connect (button1, "clicked", G_CALLBACK (editor_to_plain_text_cb), editor);
+
+ g_object_set_data (G_OBJECT (button2), "text_view", html_text_view);
+ g_signal_connect (button2, "clicked", G_CALLBACK (editor_to_html_cb), editor);
+
+ widget = gtk_button_new_with_label (">\n>\n>");
+ button1 = widget;
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_START,
+ "hexpand", FALSE,
+ "valign", GTK_ALIGN_START,
+ "vexpand", FALSE,
+ "visible", TRUE,
+ NULL);
+
+ gtk_grid_attach (grid, widget, 1, 2, 1, 1);
+
+ widget = gtk_button_new_with_label ("<\n<\n<");
+ button2 = widget;
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_START,
+ "hexpand", FALSE,
+ "valign", GTK_ALIGN_START,
+ "vexpand", FALSE,
+ "visible", TRUE,
+ NULL);
+
+ gtk_grid_attach (grid, widget, 1, 3, 1, 1);
+
+ widget = gtk_label_new ("");
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_START,
+ "hexpand", FALSE,
+ "valign", GTK_ALIGN_START,
+ "vexpand", FALSE,
+ "visible", TRUE,
+ NULL);
+
+ gtk_grid_attach (grid, widget, 0, 4, 3, 1);
+
+ g_object_set_data (G_OBJECT (button1), "plain_text_view", plain_text_view);
+ g_object_set_data (G_OBJECT (button1), "html_text_view", html_text_view);
+ g_signal_connect (button1, "clicked", G_CALLBACK (plain_text_to_html_cb), widget);
+
+ g_object_set_data (G_OBJECT (button2), "plain_text_view", plain_text_view);
+ g_object_set_data (G_OBJECT (button2), "html_text_view", html_text_view);
+ g_signal_connect (button2, "clicked", G_CALLBACK (html_to_plain_text_cb), widget);
+
+ widget = gtk_check_button_new_with_label ("HTML2Text: Disallow HTML");
+
+ g_object_set (G_OBJECT (widget),
+ "halign", GTK_ALIGN_START,
+ "hexpand", FALSE,
+ "valign", GTK_ALIGN_START,
+ "vexpand", FALSE,
+ "visible", TRUE,
+ NULL);
+
+ gtk_grid_attach (grid, widget, 0, 5, 3, 1);
+
+ g_object_set_data (G_OBJECT (button2), "disallow_html_check", widget);
gtk_widget_show (window);
diff --git a/src/em-format/e-mail-formatter-text-markdown.c b/src/em-format/e-mail-formatter-text-markdown.c
index 1d0fb906dc..1e2e634dcc 100644
--- a/src/em-format/e-mail-formatter-text-markdown.c
+++ b/src/em-format/e-mail-formatter-text-markdown.c
@@ -65,7 +65,7 @@ emfe_text_markdown_format (EMailFormatterExtension *extension,
e_mail_formatter_format_text (formatter, part, output_stream, cancellable);
g_output_stream_flush (output_stream, cancellable, NULL);
- html = e_markdown_util_text_to_html ((const gchar *) g_memory_output_stream_get_data
(G_MEMORY_OUTPUT_STREAM (output_stream)),
+ html = e_markdown_utils_text_to_html ((const gchar *) g_memory_output_stream_get_data
(G_MEMORY_OUTPUT_STREAM (output_stream)),
g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (output_stream)));
g_object_unref (output_stream);
diff --git a/src/mail/e-mail-enums.h b/src/mail/e-mail-enums.h
index 9ae17d76bf..4702bc1997 100644
--- a/src/mail/e-mail-enums.h
+++ b/src/mail/e-mail-enums.h
@@ -50,11 +50,25 @@ typedef enum {
* @E_MAIL_REPLY_FLAG_FORCE_STYLE: Force use of the passed-in reply style; if not set,
* then also checks reply style setting for the used mail account.
* @E_MAIL_REPLY_FLAG_FORMAT_PLAIN: Force compose in Plain Text format; cannot be used together
- * with @E_MAIL_REPLY_FLAG_FORMAT_HTML. If none of these is set, then uses
- * global setting.
+ * with @E_MAIL_REPLY_FLAG_FORMAT_HTML, @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN,
+ * @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_PLAIN nor @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_HTML.
+ * If none of these is set, then uses global setting.
* @E_MAIL_REPLY_FLAG_FORMAT_HTML: Force compose in HTML format; cannot be used together
- * with @E_MAIL_REPLY_FLAG_FORMAT_PLAIN. If none of these is set, then uses
- * global setting.
+ * with @E_MAIL_REPLY_FLAG_FORMAT_PLAIN, @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN,
+ * @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_PLAIN nor @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_HTML.
+ * If none of these is set, then uses global setting.
+ * @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN: Force compose in Markdown format; cannot be used together
+ * with @E_MAIL_REPLY_FLAG_FORMAT_PLAIN, @E_MAIL_REPLY_FLAG_FORMAT_HTML,
+ * @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_PLAIN nor @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_HTML.
+ * If none of these is set, then uses global setting. (Since: 3.44)
+ * @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_PLAIN: Force compose in Markdown as Plain Text format; cannot be used
together
+ * with @E_MAIL_REPLY_FLAG_FORMAT_PLAIN, @E_MAIL_REPLY_FLAG_FORMAT_HTML,
+ * @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN nor @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_HTML.
+ * If none of these is set, then uses global setting. (Since: 3.44)
+ * @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_HTML: Force compose in Markdown as HTML format; cannot be used together
+ * with @E_MAIL_REPLY_FLAG_FORMAT_PLAIN, @E_MAIL_REPLY_FLAG_FORMAT_HTML,
+ * @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN nor @E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_PLAIN.
+ * If none of these is set, then uses global setting. (Since: 3.44)
* @E_MAIL_REPLY_FLAG_TOP_POSTING: Force top posting; cannot be used together
* with @E_MAIL_REPLY_FLAG_BOTTOM_POSTING. If none is set, then uses global settings.
* @E_MAIL_REPLY_FLAG_BOTTOM_POSTING: Force bottom posting; cannot be used together
@@ -79,7 +93,10 @@ typedef enum { /*< flags >*/
E_MAIL_REPLY_FLAG_BOTTOM_POSTING = 1 << 4,
E_MAIL_REPLY_FLAG_TOP_SIGNATURE = 1 << 5,
E_MAIL_REPLY_FLAG_BOTTOM_SIGNATURE = 1 << 6,
- E_MAIL_REPLY_FLAG_FORCE_SENDER_REPLY = 1 << 7
+ E_MAIL_REPLY_FLAG_FORCE_SENDER_REPLY = 1 << 7,
+ E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN = 1 << 8,
+ E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_PLAIN = 1 << 9,
+ E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_HTML = 1 << 10
} EMailReplyFlags;
G_END_DECLS
diff --git a/src/mail/e-mail-notes.c b/src/mail/e-mail-notes.c
index a4fe577690..7a4a7589bf 100644
--- a/src/mail/e-mail-notes.c
+++ b/src/mail/e-mail-notes.c
@@ -30,6 +30,8 @@
#include "e-mail-notes.h"
+#define E_MAIL_NOTES_FORMAT "X-Evolution-Format"
+
#define E_TYPE_MAIL_NOTES_EDITOR \
(e_mail_notes_editor_get_type ())
#define E_MAIL_NOTES_EDITOR(obj) \
@@ -49,6 +51,7 @@ struct _EMailNotesEditor {
EAttachmentPaned *attachment_paned; /* not referenced */
EFocusTracker *focus_tracker;
GtkActionGroup *action_group;
+ GBinding *attachment_paned_binding;
gboolean had_message;
CamelMimeMessage *message;
@@ -91,13 +94,54 @@ e_mail_notes_extract_text_content (CamelMimePart *part)
return text;
}
+static gboolean
+e_mail_notes_editor_extract_text_part (EHTMLEditor *editor,
+ CamelContentType *content_type,
+ CamelMimePart *part,
+ EContentEditorMode mode)
+{
+ guint32 insert_flag = E_CONTENT_EDITOR_INSERT_TEXT_PLAIN;
+ gchar *text;
+
+ if (camel_content_type_is (content_type, "text", "plain")) {
+ if (mode == E_CONTENT_EDITOR_MODE_UNKNOWN)
+ mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+ } else if (camel_content_type_is (content_type, "text", "markdown")) {
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN;
+ } else if (camel_content_type_is (content_type, "text", "html")) {
+ mode = E_CONTENT_EDITOR_MODE_HTML;
+ insert_flag = E_CONTENT_EDITOR_INSERT_TEXT_HTML;
+ } else {
+ return FALSE;
+ }
+
+ text = e_mail_notes_extract_text_content (part);
+ if (text) {
+ e_html_editor_set_mode (editor, mode);
+
+ e_content_editor_insert_content (
+ e_html_editor_get_content_editor (editor),
+ text,
+ insert_flag |
+ E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
+
+ g_free (text);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static void
-e_mail_notes_extract_text_from_multipart_alternative (EContentEditor *cnt_editor,
- CamelMultipart *in_multipart)
+e_mail_notes_extract_text_from_multipart_alternative (EHTMLEditor *editor,
+ CamelMultipart *in_multipart,
+ EContentEditorMode mode)
{
+ CamelMimePart *fallback_part = NULL;
guint ii, nparts;
- g_return_if_fail (E_IS_CONTENT_EDITOR (cnt_editor));
+ g_return_if_fail (E_IS_HTML_EDITOR (editor));
g_return_if_fail (CAMEL_IS_MULTIPART (in_multipart));
nparts = camel_multipart_get_number (in_multipart);
@@ -115,40 +159,36 @@ e_mail_notes_extract_text_from_multipart_alternative (EContentEditor *cnt_editor
if (!ct)
continue;
- if (camel_content_type_is (ct, "text", "html")) {
- gchar *text;
-
- text = e_mail_notes_extract_text_content (part);
- if (text) {
- e_content_editor_set_html_mode (cnt_editor, TRUE);
- e_content_editor_insert_content (
- cnt_editor,
- text,
- E_CONTENT_EDITOR_INSERT_TEXT_HTML |
- E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
- g_free (text);
- break;
- }
- } else if (camel_content_type_is (ct, "text", "plain")) {
- gchar *text;
-
- text = e_mail_notes_extract_text_content (part);
- if (text) {
- e_content_editor_insert_content (
- cnt_editor,
- text,
- E_CONTENT_EDITOR_INSERT_TEXT_PLAIN |
- E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
- g_free (text);
+ if (mode == E_CONTENT_EDITOR_MODE_MARKDOWN ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML) {
+ /* Fallback to the text/html part when reading markdown text,
+ to avoid conversion from HTML to markdown, because the text/plain
+ part contains the raw markdown */
+ if (camel_content_type_is (ct, "text", "html")) {
+ fallback_part = part;
+ continue;
}
+ }
+
+ if (e_mail_notes_editor_extract_text_part (editor, ct, part, mode)) {
+ fallback_part = NULL;
break;
}
}
+
+ if (fallback_part) {
+ CamelContentType *ct;
+
+ ct = camel_mime_part_get_content_type (fallback_part);
+ e_mail_notes_editor_extract_text_part (editor, ct, fallback_part, mode);
+ }
}
static void
e_mail_notes_editor_extract_text_from_multipart_related (EMailNotesEditor *notes_editor,
- CamelMultipart *multipart)
+ CamelMultipart *multipart,
+ EContentEditorMode mode)
{
guint ii, nparts;
@@ -176,8 +216,8 @@ e_mail_notes_editor_extract_text_from_multipart_related (EMailNotesEditor *notes
content = camel_medium_get_content (CAMEL_MEDIUM (part));
if (CAMEL_IS_MULTIPART (content)) {
- e_mail_notes_extract_text_from_multipart_alternative (
- e_html_editor_get_content_editor (notes_editor->editor),
CAMEL_MULTIPART (content));
+ e_mail_notes_extract_text_from_multipart_alternative (notes_editor->editor,
+ CAMEL_MULTIPART (content), mode);
}
}
}
@@ -185,11 +225,11 @@ e_mail_notes_editor_extract_text_from_multipart_related (EMailNotesEditor *notes
static void
e_mail_notes_editor_extract_text_from_part (EMailNotesEditor *notes_editor,
- CamelMimePart *part)
+ CamelMimePart *part,
+ EContentEditorMode mode)
{
CamelContentType *ct;
CamelDataWrapper *content;
- EContentEditor *cnt_editor;
g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));
g_return_if_fail (CAMEL_IS_MIME_PART (part));
@@ -200,28 +240,16 @@ e_mail_notes_editor_extract_text_from_part (EMailNotesEditor *notes_editor,
g_return_if_fail (content != NULL);
g_return_if_fail (ct != NULL);
- cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
-
if (camel_content_type_is (ct, "multipart", "related")) {
g_return_if_fail (CAMEL_IS_MULTIPART (content));
- e_mail_notes_editor_extract_text_from_multipart_related (notes_editor, CAMEL_MULTIPART
(content));
+ e_mail_notes_editor_extract_text_from_multipart_related (notes_editor, CAMEL_MULTIPART
(content), mode);
} else if (camel_content_type_is (ct, "multipart", "alternative")) {
if (CAMEL_IS_MULTIPART (content)) {
- e_mail_notes_extract_text_from_multipart_alternative (cnt_editor, CAMEL_MULTIPART
(content));
- }
- } else if (camel_content_type_is (ct, "text", "plain")) {
- gchar *text;
-
- text = e_mail_notes_extract_text_content (part);
- if (text) {
- e_content_editor_insert_content (
- cnt_editor,
- text,
- E_CONTENT_EDITOR_INSERT_TEXT_PLAIN |
- E_CONTENT_EDITOR_INSERT_REPLACE_ALL);
- g_free (text);
+ e_mail_notes_extract_text_from_multipart_alternative (notes_editor->editor,
CAMEL_MULTIPART (content), mode);
}
+ } else {
+ e_mail_notes_editor_extract_text_part (notes_editor->editor, ct, part, mode);
}
}
@@ -231,7 +259,8 @@ e_mail_notes_editor_extract_text_from_message (EMailNotesEditor *notes_editor,
{
CamelContentType *ct;
CamelDataWrapper *content;
- EContentEditor *cnt_editor;
+ EContentEditorMode mode = E_CONTENT_EDITOR_MODE_UNKNOWN;
+ const gchar *format_header;
g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));
g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
@@ -242,7 +271,14 @@ e_mail_notes_editor_extract_text_from_message (EMailNotesEditor *notes_editor,
g_return_if_fail (content != NULL);
g_return_if_fail (ct != NULL);
- cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
+ format_header = camel_medium_get_header (CAMEL_MEDIUM (message), E_MAIL_NOTES_FORMAT);
+
+ if (format_header) {
+ if (g_ascii_strcasecmp (format_header, "text/markdown-plain") == 0)
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT;
+ else if (g_ascii_strcasecmp (format_header, "text/markdown-html") == 0)
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_HTML;
+ }
if (camel_content_type_is (ct, "multipart", "mixed")) {
EAttachmentStore *attachment_store;
@@ -268,7 +304,7 @@ e_mail_notes_editor_extract_text_from_message (EMailNotesEditor *notes_editor,
continue;
if (ii == 0) {
- e_mail_notes_editor_extract_text_from_part (notes_editor, part);
+ e_mail_notes_editor_extract_text_from_part (notes_editor, part, mode);
} else {
EAttachment *attachment;
@@ -283,10 +319,10 @@ e_mail_notes_editor_extract_text_from_message (EMailNotesEditor *notes_editor,
}
}
} else {
- e_mail_notes_editor_extract_text_from_part (notes_editor, CAMEL_MIME_PART (message));
+ e_mail_notes_editor_extract_text_from_part (notes_editor, CAMEL_MIME_PART (message), mode);
}
- e_content_editor_set_changed (cnt_editor, FALSE);
+ e_content_editor_set_changed (e_html_editor_get_content_editor (notes_editor->editor), FALSE);
}
static CamelMimeMessage *
@@ -294,6 +330,7 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor,
EContentEditorContentHash *content_hash)
{
EContentEditor *cnt_editor;
+ EContentEditorMode mode;
EAttachmentStore *attachment_store;
CamelMimeMessage *message = NULL;
gchar *message_uid;
@@ -328,7 +365,18 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor,
attachment_store = e_attachment_view_get_store (E_ATTACHMENT_VIEW (notes_editor->attachment_paned));
has_attachments = e_attachment_store_get_num_attachments (attachment_store) > 0;
- if (e_content_editor_get_html_mode (cnt_editor)) {
+ mode = e_html_editor_get_mode (notes_editor->editor);
+
+ if (mode == E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML) {
+ if (mode == E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT)
+ camel_medium_add_header (CAMEL_MEDIUM (message), E_MAIL_NOTES_FORMAT,
"text/markdown-plain");
+ else
+ camel_medium_add_header (CAMEL_MEDIUM (message), E_MAIL_NOTES_FORMAT,
"text/markdown-html");
+ }
+
+ if (mode == E_CONTENT_EDITOR_MODE_HTML ||
+ mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML) {
CamelMultipart *multipart_alternative;
CamelMultipart *multipart_body;
CamelMimePart *part;
@@ -350,7 +398,8 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor,
}
part = camel_mime_part_new ();
- camel_mime_part_set_content (part, text, strlen (text), "text/plain");
+ camel_mime_part_set_content (part, text, strlen (text), mode ==
E_CONTENT_EDITOR_MODE_MARKDOWN ?
+ "text/markdown" : "text/plain");
camel_multipart_add_part (multipart_alternative, part);
g_object_unref (part);
@@ -463,7 +512,8 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor,
camel_multipart_set_boundary (multipart, NULL);
part = camel_mime_part_new ();
- camel_mime_part_set_content (part, text, strlen (text), "text/plain");
+ camel_mime_part_set_content (part, text, strlen (text), mode ==
E_CONTENT_EDITOR_MODE_MARKDOWN ?
+ "text/markdown" : "text/plain");
camel_multipart_add_part (multipart, part);
g_object_unref (part);
@@ -473,7 +523,8 @@ e_mail_notes_editor_encode_text_to_message (EMailNotesEditor *notes_editor,
g_object_unref (multipart);
} else {
- camel_mime_part_set_content (CAMEL_MIME_PART (message), text, strlen (text),
"text/plain");
+ camel_mime_part_set_content (CAMEL_MIME_PART (message), text, strlen (text),
mode == E_CONTENT_EDITOR_MODE_MARKDOWN ?
+ "text/markdown" : "text/plain");
}
has_text = TRUE;
@@ -587,17 +638,18 @@ mail_notes_editor_delete_event_cb (EMailNotesEditor *notes_editor,
}
static void
-notes_editor_activity_notify_cb (EActivityBar *activity_bar,
- GParamSpec *param,
- EMailNotesEditor *notes_editor)
+notes_editor_update_editable_on_notify_cb (GObject *object,
+ GParamSpec *param,
+ EMailNotesEditor *notes_editor)
{
+ EActivityBar *activity_bar;
EContentEditor *cnt_editor;
GtkAction *action;
gboolean can_edit;
- g_return_if_fail (E_IS_ACTIVITY_BAR (activity_bar));
g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));
+ activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);
cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
can_edit = notes_editor->had_message && !e_activity_bar_get_activity (activity_bar);
@@ -919,6 +971,40 @@ action_save_and_close_cb (GtkAction *action,
mail_notes_get_content_ready_cb, scd);
}
+static void
+notes_editor_notify_mode_cb (GObject *object,
+ GParamSpec *param,
+ EMailNotesEditor *notes_editor)
+{
+ g_return_if_fail (E_IS_MAIL_NOTES_EDITOR (notes_editor));
+
+ if (notes_editor->attachment_paned_binding) {
+ g_binding_unbind (notes_editor->attachment_paned_binding);
+ g_clear_object (¬es_editor->attachment_paned_binding);
+ }
+
+ if (notes_editor->editor) {
+ EContentEditor *cnt_editor;
+
+ cnt_editor = e_html_editor_get_content_editor (notes_editor->editor);
+
+ if (cnt_editor) {
+ EActivityBar *activity_bar;
+ gboolean can_edit;
+
+ activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);
+ can_edit = notes_editor->had_message && !e_activity_bar_get_activity (activity_bar);
+
+ g_object_set (cnt_editor, "editable", can_edit, NULL);
+
+ notes_editor->attachment_paned_binding = g_object_ref (e_binding_bind_property (
+ cnt_editor, "editable",
+ notes_editor->attachment_paned, "sensitive",
+ G_BINDING_SYNC_CREATE));
+ }
+ }
+}
+
static void
e_mail_notes_editor_dispose (GObject *object)
{
@@ -929,13 +1015,14 @@ e_mail_notes_editor_dispose (GObject *object)
activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);
g_signal_handlers_disconnect_by_func (activity_bar,
- G_CALLBACK (notes_editor_activity_notify_cb), notes_editor);
+ G_CALLBACK (notes_editor_update_editable_on_notify_cb), notes_editor);
notes_editor->editor = NULL;
}
g_clear_object (¬es_editor->focus_tracker);
g_clear_object (¬es_editor->action_group);
+ g_clear_object (¬es_editor->attachment_paned_binding);
/* Chain up to parent's method */
G_OBJECT_CLASS (e_mail_notes_editor_parent_class)->dispose (object);
@@ -974,9 +1061,12 @@ static void
set_preformatted_block_format_on_load_finished_cb (EContentEditor *cnt_editor,
gpointer user_data)
{
+ EHTMLEditor *editor = user_data;
+
+ g_return_if_fail (E_IS_HTML_EDITOR (editor));
g_return_if_fail (E_IS_CONTENT_EDITOR (cnt_editor));
- if (!e_content_editor_get_html_mode (cnt_editor)) {
+ if (e_html_editor_get_mode (editor) == E_CONTENT_EDITOR_MODE_PLAIN_TEXT) {
e_content_editor_set_block_format (cnt_editor, E_CONTENT_EDITOR_BLOCK_FORMAT_PRE);
e_content_editor_set_changed (cnt_editor, FALSE);
e_content_editor_clear_undo_redo_history (cnt_editor);
@@ -1113,35 +1203,25 @@ e_mail_notes_editor_new_with_editor (EHTMLEditor *html_editor,
notes_editor->attachment_paned = E_ATTACHMENT_PANED (widget);
gtk_widget_show (widget);
- e_binding_bind_property (
+ notes_editor->attachment_paned_binding = g_object_ref (e_binding_bind_property (
cnt_editor, "editable",
widget, "sensitive",
- G_BINDING_SYNC_CREATE);
+ G_BINDING_SYNC_CREATE));
/* Configure an EFocusTracker to manage selection actions. */
focus_tracker = e_focus_tracker_new (GTK_WINDOW (notes_editor));
- action = e_html_editor_get_action (notes_editor->editor, "cut");
- e_focus_tracker_set_cut_clipboard_action (focus_tracker, action);
-
- action = e_html_editor_get_action (notes_editor->editor, "copy");
- e_focus_tracker_set_copy_clipboard_action (focus_tracker, action);
-
- action = e_html_editor_get_action (notes_editor->editor, "paste");
- e_focus_tracker_set_paste_clipboard_action (focus_tracker, action);
-
- action = e_html_editor_get_action (notes_editor->editor, "select-all");
- e_focus_tracker_set_select_all_action (focus_tracker, action);
+ e_html_editor_connect_focus_tracker (notes_editor->editor, focus_tracker);
notes_editor->focus_tracker = focus_tracker;
gtk_widget_grab_focus (GTK_WIDGET (cnt_editor));
settings = e_util_ref_settings ("org.gnome.evolution.mail");
- e_content_editor_set_html_mode (cnt_editor, g_settings_get_boolean (settings, "composer-send-html"));
+ e_html_editor_set_mode (html_editor, g_settings_get_enum (settings, "composer-mode"));
if (g_settings_get_boolean (settings, "composer-plain-text-starts-preformatted")) {
- g_signal_connect (cnt_editor, "load-finished",
- G_CALLBACK (set_preformatted_block_format_on_load_finished_cb), NULL);
+ g_signal_connect_object (cnt_editor, "load-finished",
+ G_CALLBACK (set_preformatted_block_format_on_load_finished_cb), html_editor, 0);
}
g_object_unref (settings);
@@ -1152,7 +1232,9 @@ e_mail_notes_editor_new_with_editor (EHTMLEditor *html_editor,
activity_bar = e_html_editor_get_activity_bar (notes_editor->editor);
g_signal_connect (activity_bar, "notify::activity",
- G_CALLBACK (notes_editor_activity_notify_cb), notes_editor);
+ G_CALLBACK (notes_editor_update_editable_on_notify_cb), notes_editor);
+ g_signal_connect_object (notes_editor->editor, "notify::mode",
+ G_CALLBACK (notes_editor_notify_mode_cb), notes_editor, 0);
notes_editor->folder = g_object_ref (folder);
notes_editor->uid = g_strdup (uid);
diff --git a/src/mail/em-composer-utils.c b/src/mail/em-composer-utils.c
index 33d29d99ef..ebd6df7a26 100644
--- a/src/mail/em-composer-utils.c
+++ b/src/mail/em-composer-utils.c
@@ -552,8 +552,8 @@ composer_presend_check_unwanted_html (EMsgComposer *composer,
{
EDestination **recipients;
EHTMLEditor *editor;
- EContentEditor *cnt_editor;
EComposerHeaderTable *table;
+ EContentEditorMode mode;
GSettings *settings;
gboolean check_passed = TRUE;
gboolean html_mode;
@@ -564,13 +564,14 @@ composer_presend_check_unwanted_html (EMsgComposer *composer,
settings = e_util_ref_settings ("org.gnome.evolution.mail");
editor = e_msg_composer_get_editor (composer);
- cnt_editor = e_html_editor_get_content_editor (editor);
- html_mode = e_content_editor_get_html_mode (cnt_editor);
+ mode = e_html_editor_get_mode (editor);
+ html_mode = mode == E_CONTENT_EDITOR_MODE_HTML || mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML;
table = e_msg_composer_get_header_table (composer);
recipients = e_composer_header_table_get_destinations (table);
- send_html = g_settings_get_boolean (settings, "composer-send-html");
+ mode = g_settings_get_enum (settings, "composer-mode");
+ send_html = mode == E_CONTENT_EDITOR_MODE_HTML || mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML;
confirm_html = g_settings_get_boolean (settings, "prompt-on-unwanted-html");
/* Only show this warning if our default is to send html. If it
@@ -2582,7 +2583,7 @@ forward_non_attached (EMsgComposer *composer,
E_MAIL_FORMATTER_QUOTE_FLAG_KEEP_SIG;
if (style == E_MAIL_FORWARD_STYLE_QUOTED)
flags |= E_MAIL_FORMATTER_QUOTE_FLAG_CITE;
- if (!e_content_editor_get_html_mode (e_html_editor_get_content_editor (e_msg_composer_get_editor
(composer))))
+ if (e_html_editor_get_mode (e_msg_composer_get_editor (composer)) != E_CONTENT_EDITOR_MODE_HTML)
flags |= E_MAIL_FORMATTER_QUOTE_FLAG_NO_FORMATTING;
/* Setup composer's From account before calling quoting_text() and
@@ -3855,9 +3856,9 @@ alt_reply_composer_created_cb (GObject *source_object,
composer = e_msg_composer_new_finish (result, &error);
if (composer) {
- EContentEditor *cnt_editor;
+ EHTMLEditor *editor;
- cnt_editor = e_html_editor_get_content_editor (e_msg_composer_get_editor (composer));
+ editor = e_msg_composer_get_editor (composer);
if (context->new_message) {
CamelInternetAddress *to = NULL, *cc = NULL;
@@ -3873,7 +3874,7 @@ alt_reply_composer_created_cb (GObject *source_object,
}
if ((context->flags & (E_MAIL_REPLY_FLAG_FORMAT_PLAIN |
E_MAIL_REPLY_FLAG_FORMAT_HTML)) != 0) {
- e_content_editor_set_html_mode (cnt_editor, (context->flags &
E_MAIL_REPLY_FLAG_FORMAT_HTML) != 0);
+ e_html_editor_set_mode (editor, (context->flags &
E_MAIL_REPLY_FLAG_FORMAT_HTML) != 0 ? E_CONTENT_EDITOR_MODE_HTML : E_CONTENT_EDITOR_MODE_PLAIN_TEXT);
}
em_utils_edit_message (composer, context->folder, context->new_message,
context->message_uid, TRUE, FALSE);
@@ -4110,7 +4111,8 @@ em_utils_reply_alternative (GtkWindow *parent,
GtkRadioButton *recip_sender, *recip_list, *recip_all;
GtkLabel *sender_label, *list_label, *all_label;
GtkRadioButton *style_default, *style_attach, *style_inline, *style_quote, *style_no_quote;
- GtkToggleButton *html_format;
+ EActionComboBox *mode_combo;
+ GtkRadioAction *radio_action, *html_mode_radio_action;
GtkToggleButton *bottom_posting;
GtkToggleButton *top_signature;
GtkCheckButton *apply_template;
@@ -4226,8 +4228,21 @@ em_utils_reply_alternative (GtkWindow *parent,
widget = gtk_label_new (" ");
gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);
- html_format = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_mnemonic (_("_Format message in HTML")));
- gtk_box_pack_start (vbox, GTK_WIDGET (html_format), FALSE, FALSE, 0);
+ hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6));
+ gtk_box_pack_start (vbox, GTK_WIDGET (hbox), FALSE, FALSE, 0);
+
+ /* Translators: The text is followed by the format combo with values like 'Plain Text', 'HTML' and so
on */
+ widget = gtk_label_new_with_mnemonic (_("_Format message in"));
+ gtk_box_pack_start (hbox, widget, FALSE, FALSE, 0);
+
+ mode_combo = e_html_editor_util_new_mode_combobox ();
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), GTK_WIDGET (mode_combo));
+ gtk_box_pack_start (hbox, GTK_WIDGET (mode_combo), FALSE, FALSE, 0);
+
+ html_mode_radio_action = e_action_combo_box_get_action (mode_combo);
+ radio_action = gtk_radio_action_new ("unknown", _("Use global setting"), NULL, NULL,
E_CONTENT_EDITOR_MODE_UNKNOWN);
+ gtk_radio_action_join_group (radio_action, html_mode_radio_action);
+ e_action_combo_box_update_model (mode_combo);
bottom_posting = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_mnemonic (_("Start _typing at the
bottom")));
gtk_box_pack_start (vbox, GTK_WIDGET (bottom_posting), FALSE, FALSE, 0);
@@ -4366,7 +4381,7 @@ em_utils_reply_alternative (GtkWindow *parent,
break;
}
- emcu_three_state_set_value (html_format, g_settings_get_enum (settings, "alt-reply-html-format"));
+ e_action_combo_box_set_current_value (mode_combo, g_settings_get_enum (settings,
"alt-reply-format-mode"));
emcu_three_state_set_value (bottom_posting, g_settings_get_enum (settings, "alt-reply-start-bottom"));
emcu_three_state_set_value (top_signature, g_settings_get_enum (settings, "alt-reply-top-signature"));
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_template), g_settings_get_boolean (settings,
"alt-reply-template-apply"));
@@ -4375,7 +4390,6 @@ em_utils_reply_alternative (GtkWindow *parent,
if (!gtk_widget_get_sensitive (GTK_WIDGET (templates)))
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (apply_template), FALSE);
- emcu_connect_three_state_changer (html_format);
emcu_connect_three_state_changer (bottom_posting);
emcu_connect_three_state_changer (top_signature);
@@ -4434,6 +4448,7 @@ em_utils_reply_alternative (GtkWindow *parent,
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
GtkTreeIter iter;
AltReplyContext *context;
+ EContentEditorMode mode;
EThreeState three_state;
CamelFolder *template_folder = NULL;
gchar *template_message_uid = NULL;
@@ -4461,13 +4476,28 @@ em_utils_reply_alternative (GtkWindow *parent,
else
context->flags = context->flags & (~E_MAIL_REPLY_FLAG_FORCE_STYLE);
- three_state = emcu_three_state_get_value (html_format);
- g_settings_set_enum (settings, "alt-reply-html-format", three_state);
+ mode = e_action_combo_box_get_current_value (mode_combo);
+ g_settings_set_enum (settings, "alt-reply-format-mode", mode);
- if (three_state == E_THREE_STATE_ON)
- context->flags |= E_MAIL_REPLY_FLAG_FORMAT_HTML;
- else if (three_state == E_THREE_STATE_OFF)
+ switch (mode) {
+ case E_CONTENT_EDITOR_MODE_UNKNOWN:
+ break;
+ case E_CONTENT_EDITOR_MODE_PLAIN_TEXT:
context->flags |= E_MAIL_REPLY_FLAG_FORMAT_PLAIN;
+ break;
+ case E_CONTENT_EDITOR_MODE_HTML:
+ context->flags |= E_MAIL_REPLY_FLAG_FORMAT_HTML;
+ break;
+ case E_CONTENT_EDITOR_MODE_MARKDOWN:
+ context->flags |= E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN;
+ break;
+ case E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT:
+ context->flags |= E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_PLAIN;
+ break;
+ case E_CONTENT_EDITOR_MODE_MARKDOWN_HTML:
+ context->flags |= E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_HTML;
+ break;
+ }
three_state = emcu_three_state_get_value (bottom_posting);
g_settings_set_enum (settings, "alt-reply-start-bottom", three_state);
@@ -4597,8 +4627,34 @@ em_utils_reply_to_message (EMsgComposer *composer,
cnt_editor = e_html_editor_get_content_editor (e_msg_composer_get_editor (composer));
- if ((reply_flags & (E_MAIL_REPLY_FLAG_FORMAT_PLAIN | E_MAIL_REPLY_FLAG_FORMAT_HTML)) != 0) {
- e_content_editor_set_html_mode (cnt_editor, (reply_flags & E_MAIL_REPLY_FLAG_FORMAT_HTML) !=
0);
+ flags = reply_flags & (E_MAIL_REPLY_FLAG_FORMAT_PLAIN |
+ E_MAIL_REPLY_FLAG_FORMAT_HTML |
+ E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN |
+ E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_PLAIN |
+ E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_HTML);
+ if (flags != 0) {
+ EContentEditorMode mode = E_CONTENT_EDITOR_MODE_UNKNOWN;
+
+ switch (flags) {
+ case E_MAIL_REPLY_FLAG_FORMAT_PLAIN:
+ mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+ break;
+ case E_MAIL_REPLY_FLAG_FORMAT_HTML:
+ mode = E_CONTENT_EDITOR_MODE_HTML;
+ break;
+ case E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN:
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN;
+ break;
+ case E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_PLAIN:
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT;
+ break;
+ case E_MAIL_REPLY_FLAG_FORMAT_MARKDOWN_HTML:
+ mode = E_CONTENT_EDITOR_MODE_MARKDOWN_HTML;
+ break;
+ }
+
+ if (mode != E_CONTENT_EDITOR_MODE_UNKNOWN)
+ e_html_editor_set_mode (e_msg_composer_get_editor (composer), mode);
}
em_utils_update_by_reply_flags (cnt_editor, reply_flags);
diff --git a/src/mail/mail-config.ui b/src/mail/mail-config.ui
index 890837162a..646909865b 100644
--- a/src/mail/mail-config.ui
+++ b/src/mail/mail-config.ui
@@ -211,14 +211,26 @@
<property name="can_focus">False</property>
<property name="spacing">8</property>
<child>
- <object class="GtkCheckButton" id="chkSendHTML">
- <property name="label" translatable="yes">For_mat messages in HTML</property>
+ <object class="GtkBox" id="hboxSendMode">
+ <property name="orientation">horizontal</property>
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="use_underline">True</property>
- <property name="xalign">0.5</property>
- <property name="draw_indicator">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkLabel" id="lblSendMode">
+ <property name="label" translatable="yes" comments="Translators: The text is
followed by the format combo with values like 'Plain Text', 'HTML' and so on">For_mat messages in</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="use_underline">True</property>
+ <property name="justify">center</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
</object>
<packing>
<property name="expand">False</property>
diff --git a/src/modules/composer-to-meeting/e-meeting-to-composer.c
b/src/modules/composer-to-meeting/e-meeting-to-composer.c
index 0335a9cdde..63437521d7 100644
--- a/src/modules/composer-to-meeting/e-meeting-to-composer.c
+++ b/src/modules/composer-to-meeting/e-meeting-to-composer.c
@@ -298,11 +298,22 @@ meeting_to_composer_composer_created_cb (GObject *source_object,
if (text && *text) {
EHTMLEditor *html_editor;
EContentEditor *cnt_editor;
+ EContentEditorMode mode;
+ GSettings *settings;
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+ mode = g_settings_get_enum (settings, "composer-mode");
+ g_clear_object (&settings);
+
+ /* Let the markdown be allowed, otherwise use the plain text mode */
+ if (mode != E_CONTENT_EDITOR_MODE_MARKDOWN &&
+ mode != E_CONTENT_EDITOR_MODE_MARKDOWN_PLAIN_TEXT)
+ mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
html_editor = e_msg_composer_get_editor (composer);
cnt_editor = e_html_editor_get_content_editor (html_editor);
- e_content_editor_set_html_mode (cnt_editor, FALSE);
+ e_html_editor_set_mode (html_editor, mode);
e_content_editor_insert_content (cnt_editor, text,
E_CONTENT_EDITOR_INSERT_REPLACE_ALL | E_CONTENT_EDITOR_INSERT_TEXT_PLAIN);
}
diff --git a/src/modules/mail/e-mail-shell-backend.c b/src/modules/mail/e-mail-shell-backend.c
index 0cb48eb40e..2e393baa2e 100644
--- a/src/modules/mail/e-mail-shell-backend.c
+++ b/src/modules/mail/e-mail-shell-backend.c
@@ -720,9 +720,12 @@ static void
set_preformatted_block_format_on_load_finished_cb (EContentEditor *cnt_editor,
gpointer user_data)
{
+ EHTMLEditor *editor = user_data;
+
+ g_return_if_fail (E_IS_HTML_EDITOR (editor));
g_return_if_fail (E_IS_CONTENT_EDITOR (cnt_editor));
- if (!e_content_editor_get_html_mode (cnt_editor)) {
+ if (e_html_editor_get_mode (editor) != E_CONTENT_EDITOR_MODE_HTML) {
e_content_editor_set_block_format (cnt_editor, E_CONTENT_EDITOR_BLOCK_FORMAT_PRE);
e_content_editor_set_changed (cnt_editor, FALSE);
e_content_editor_clear_undo_redo_history (cnt_editor);
@@ -756,23 +759,24 @@ mail_shell_backend_window_added_cb (GtkApplication *application,
/* This applies to both the composer and signature editor. */
if (editor != NULL) {
EContentEditor *cnt_editor;
+ EContentEditorMode mode;
GSettings *settings;
- gboolean use_html, use_preformatted;
+ gboolean use_preformatted;
cnt_editor = e_html_editor_get_content_editor (editor);
settings = e_util_ref_settings ("org.gnome.evolution.mail");
- use_html = g_settings_get_boolean (settings, "composer-send-html");
+ mode = g_settings_get_enum (settings, "composer-mode");
use_preformatted = g_settings_get_boolean (settings,
"composer-plain-text-starts-preformatted");
g_object_unref (settings);
- e_content_editor_set_html_mode (cnt_editor, use_html);
+ e_html_editor_set_mode (editor, mode);
if (use_preformatted) {
- g_signal_connect (cnt_editor, "load-finished",
- G_CALLBACK (set_preformatted_block_format_on_load_finished_cb), NULL);
+ g_signal_connect_object (cnt_editor, "load-finished",
+ G_CALLBACK (set_preformatted_block_format_on_load_finished_cb), editor, 0);
}
}
diff --git a/src/modules/mail/em-composer-prefs.c b/src/modules/mail/em-composer-prefs.c
index 721c07e6e2..24b6734a80 100644
--- a/src/modules/mail/em-composer-prefs.c
+++ b/src/modules/mail/em-composer-prefs.c
@@ -1056,6 +1056,45 @@ emcp_free (EConfig *ec,
g_slist_free (items);
}
+static gboolean
+emcp_composer_mode_to_current_value_cb (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ const gchar *str;
+ gint mode = E_CONTENT_EDITOR_MODE_UNKNOWN;
+
+ str = g_variant_get_string (variant, NULL);
+
+ if (!e_enum_from_string (E_TYPE_CONTENT_EDITOR_MODE, str, &mode))
+ mode = E_CONTENT_EDITOR_MODE_UNKNOWN;
+
+ if (mode == E_CONTENT_EDITOR_MODE_UNKNOWN)
+ mode = E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
+
+ g_value_set_int (value, mode);
+
+ return TRUE;
+}
+
+static GVariant *
+emcp_current_value_to_composer_mode_cb (const GValue *value,
+ const GVariantType *expected_type,
+ gpointer user_data)
+{
+ const gchar *str;
+ gint mode;
+
+ mode = g_value_get_int (value);
+
+ str = e_enum_to_string (E_TYPE_CONTENT_EDITOR_MODE, mode);
+
+ if (!str)
+ str = e_enum_to_string (E_TYPE_CONTENT_EDITOR_MODE, E_CONTENT_EDITOR_MODE_PLAIN_TEXT);
+
+ return g_variant_new_string (str);
+}
+
static void
em_composer_prefs_construct (EMComposerPrefs *prefs,
EShell *shell)
@@ -1063,7 +1102,9 @@ em_composer_prefs_construct (EMComposerPrefs *prefs,
GtkWidget *toplevel, *widget, *info_pixmap;
GtkWidget *container;
GSettings *settings;
+ EActionComboBox *action_combo_box;
ESourceRegistry *registry;
+ GtkRadioAction *radio_action;
GtkTreeView *view;
GtkListStore *store;
GtkTreeSelection *selection;
@@ -1106,11 +1147,21 @@ em_composer_prefs_construct (EMComposerPrefs *prefs,
/* Default Behavior */
- widget = e_builder_get_widget (prefs->builder, "chkSendHTML");
- g_settings_bind (
- settings, "composer-send-html",
- widget, "active",
- G_SETTINGS_BIND_DEFAULT);
+ container = e_builder_get_widget (prefs->builder, "hboxSendMode");
+ action_combo_box = e_html_editor_util_new_mode_combobox ();
+ widget = GTK_WIDGET (action_combo_box);
+ gtk_widget_show (widget);
+ gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+ widget = e_builder_get_widget (prefs->builder, "lblSendMode");
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), GTK_WIDGET (action_combo_box));
+ radio_action = e_action_combo_box_get_action (action_combo_box);
+ g_settings_bind_with_mapping (
+ settings, "composer-mode",
+ radio_action, "current-value",
+ G_SETTINGS_BIND_DEFAULT,
+ emcp_composer_mode_to_current_value_cb,
+ emcp_current_value_to_composer_mode_cb,
+ NULL, NULL);
widget = e_builder_get_widget (prefs->builder, "chkInheritThemeColors");
g_settings_bind (
@@ -1359,8 +1410,8 @@ em_composer_prefs_construct (EMComposerPrefs *prefs,
G_CALLBACK (gtk_application_add_window), shell);
g_settings_bind (
- settings, "composer-send-html",
- widget, "prefer-html",
+ settings, "composer-mode",
+ widget, "prefer-mode",
G_SETTINGS_BIND_GET);
/* Send Account override */
diff --git a/src/modules/webkit-editor/e-webkit-editor.c b/src/modules/webkit-editor/e-webkit-editor.c
index 1042cc1bcd..0fc8b47afa 100644
--- a/src/modules/webkit-editor/e-webkit-editor.c
+++ b/src/modules/webkit-editor/e-webkit-editor.c
@@ -44,7 +44,7 @@ enum {
PROP_CAN_UNDO,
PROP_CHANGED,
PROP_EDITABLE,
- PROP_HTML_MODE,
+ PROP_MODE,
PROP_SPELL_CHECK_ENABLED,
PROP_SPELL_CHECKER,
PROP_START_BOTTOM,
@@ -82,7 +82,7 @@ struct _EWebKitEditorPrivate {
GHashTable *scheme_handlers; /* const gchar *scheme ~> EContentRequest */
GCancellable *cancellable;
- gboolean html_mode;
+ EContentEditorMode mode;
gboolean changed;
gboolean can_copy;
gboolean can_cut;
@@ -545,7 +545,7 @@ webkit_editor_dialog_utils_get_attribute_with_unit (EWebKitEditor *wk_editor,
*out_unit = E_CONTENT_EDITOR_UNIT_AUTO;
- if (!wk_editor->priv->html_mode)
+ if (wk_editor->priv->mode != E_CONTENT_EDITOR_MODE_HTML)
return default_value;
value = webkit_editor_dialog_utils_get_attribute (wk_editor, selector, name);
@@ -596,29 +596,6 @@ webkit_editor_dialog_utils_has_attribute (EWebKitEditor *wk_editor,
FALSE);
}
-static gboolean
-e_webkit_editor_three_state_to_bool (EThreeState value,
- const gchar *mail_key)
-{
- gboolean res = FALSE;
-
- if (value == E_THREE_STATE_ON)
- return TRUE;
-
- if (value == E_THREE_STATE_OFF)
- return FALSE;
-
- if (mail_key && *mail_key) {
- GSettings *settings;
-
- settings = e_util_ref_settings ("org.gnome.evolution.mail");
- res = g_settings_get_boolean (settings, mail_key);
- g_clear_object (&settings);
- }
-
- return res;
-}
-
EWebKitEditor *
e_webkit_editor_new (void)
{
@@ -838,8 +815,8 @@ formatting_changed_cb (WebKitUserContentManager *manager,
if (jsc_value && jsc_value_is_number (jsc_value)) {
gint value = jsc_value_to_int32 (jsc_value);
- if ((value ? 1 : 0) != (wk_editor->priv->html_mode ? 1 : 0)) {
- wk_editor->priv->html_mode = value;
+ if ((value ? 1 : 0) != (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML ? 1 : 0)) {
+ wk_editor->priv->mode = value ? E_CONTENT_EDITOR_MODE_HTML :
E_CONTENT_EDITOR_MODE_PLAIN_TEXT;
changed = TRUE;
}
}
@@ -850,7 +827,7 @@ formatting_changed_cb (WebKitUserContentManager *manager,
webkit_editor_update_styles (E_CONTENT_EDITOR (wk_editor));
webkit_editor_style_updated (wk_editor, FALSE);
- g_object_notify (object, "html-mode");
+ g_object_notify (object, "mode");
}
changed = FALSE;
@@ -1135,6 +1112,16 @@ webkit_editor_show_inspector (EWebKitEditor *wk_editor)
webkit_web_inspector_show (inspector);
}
+static gboolean
+webkit_editor_supports_mode (EContentEditor *content_editor,
+ EContentEditorMode mode)
+{
+ g_return_val_if_fail (E_IS_WEBKIT_EDITOR (content_editor), FALSE);
+
+ return mode == E_CONTENT_EDITOR_MODE_PLAIN_TEXT ||
+ mode == E_CONTENT_EDITOR_MODE_HTML;
+}
+
static void
webkit_editor_initialize (EContentEditor *content_editor,
EContentEditorInitializedCallback callback,
@@ -1194,7 +1181,7 @@ webkit_editor_update_styles (EContentEditor *editor)
ms = pango_font_description_from_string ("monospace 10");
}
- if (wk_editor->priv->html_mode) {
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML) {
if (use_custom_font) {
font = g_settings_get_string (
wk_editor->priv->mail_settings, "variable-width-font");
@@ -1315,7 +1302,7 @@ webkit_editor_update_styles (EContentEditor *editor)
" vertical-align: top;\n"
"}\n");
- if (wk_editor->priv->html_mode) {
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML) {
g_string_append (
stylesheet,
"body ul > li.-x-evo-align-center,ol > li.-x-evo-align-center "
@@ -1564,7 +1551,7 @@ webkit_editor_update_styles (EContentEditor *editor)
" -webkit-margin-after: 0em; \n"
"}\n");
- if (wk_editor->priv->html_mode) {
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML) {
g_string_append (
stylesheet,
"a "
@@ -1715,7 +1702,7 @@ webkit_editor_page_get_text_color (EContentEditor *editor,
{
EWebKitEditor *wk_editor = E_WEBKIT_EDITOR (editor);
- if (wk_editor->priv->html_mode &&
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML &&
wk_editor->priv->body_fg_color) {
*color = *wk_editor->priv->body_fg_color;
} else {
@@ -1736,7 +1723,7 @@ webkit_editor_page_get_background_color (EContentEditor *editor,
{
EWebKitEditor *wk_editor = E_WEBKIT_EDITOR (editor);
- if (wk_editor->priv->html_mode &&
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML &&
wk_editor->priv->body_bg_color) {
*color = *wk_editor->priv->body_bg_color;
} else {
@@ -1757,7 +1744,7 @@ webkit_editor_page_get_link_color (EContentEditor *editor,
{
EWebKitEditor *wk_editor = E_WEBKIT_EDITOR (editor);
- if (wk_editor->priv->html_mode &&
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML &&
wk_editor->priv->body_link_color) {
*color = *wk_editor->priv->body_link_color;
} else {
@@ -1781,7 +1768,7 @@ webkit_editor_page_get_visited_link_color (EContentEditor *editor,
{
EWebKitEditor *wk_editor = E_WEBKIT_EDITOR (editor);
- if (wk_editor->priv->html_mode &&
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML &&
wk_editor->priv->body_vlink_color) {
*color = *wk_editor->priv->body_vlink_color;
} else {
@@ -1808,7 +1795,7 @@ webkit_editor_page_get_font_name (EContentEditor *editor)
{
EWebKitEditor *wk_editor = E_WEBKIT_EDITOR (editor);
- if (!wk_editor->priv->html_mode)
+ if (wk_editor->priv->mode != E_CONTENT_EDITOR_MODE_HTML)
return NULL;
return wk_editor->priv->body_font_name;
@@ -1871,7 +1858,7 @@ webkit_editor_style_updated (EWebKitEditor *wk_editor,
style_context = gtk_widget_get_style_context (GTK_WIDGET (wk_editor));
backdrop = (state_flags & GTK_STATE_FLAG_BACKDROP) != 0;
- if (wk_editor->priv->html_mode && !inherit_theme_colors) {
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML && !inherit_theme_colors) {
/* Default to white background when not inheriting theme colors */
bgcolor.red = 1.0;
bgcolor.green = 1.0;
@@ -1884,7 +1871,7 @@ webkit_editor_style_updated (EWebKitEditor *wk_editor,
gdk_rgba_parse (&bgcolor, E_UTILS_DEFAULT_THEME_BASE_COLOR);
}
- if (wk_editor->priv->html_mode && !inherit_theme_colors) {
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML && !inherit_theme_colors) {
/* Default to black text color when not inheriting theme colors */
fgcolor.red = 0.0;
fgcolor.green = 0.0;
@@ -1942,10 +1929,10 @@ webkit_editor_style_updated_cb (EWebKitEditor *wk_editor)
webkit_editor_style_updated (wk_editor, FALSE);
}
-static gboolean
-webkit_editor_get_html_mode (EWebKitEditor *wk_editor)
+static EContentEditorMode
+webkit_editor_get_mode (EWebKitEditor *wk_editor)
{
- return wk_editor->priv->html_mode;
+ return wk_editor->priv->mode;
}
static gboolean
@@ -1966,7 +1953,7 @@ show_lose_formatting_dialog (EWebKitEditor *wk_editor)
if (!lose) {
/* Nothing has changed, but notify anyway */
- g_object_notify (G_OBJECT (wk_editor), "html-mode");
+ g_object_notify (G_OBJECT (wk_editor), "mode");
return FALSE;
}
@@ -1974,17 +1961,18 @@ show_lose_formatting_dialog (EWebKitEditor *wk_editor)
}
static void
-webkit_editor_set_html_mode (EWebKitEditor *wk_editor,
- gboolean html_mode)
+webkit_editor_set_mode (EWebKitEditor *wk_editor,
+ EContentEditorMode mode)
{
g_return_if_fail (E_IS_WEBKIT_EDITOR (wk_editor));
+ g_return_if_fail (mode == E_CONTENT_EDITOR_MODE_PLAIN_TEXT || mode == E_CONTENT_EDITOR_MODE_HTML);
- if (html_mode == wk_editor->priv->html_mode)
+ if (mode == wk_editor->priv->mode)
return;
- wk_editor->priv->html_mode = html_mode;
+ wk_editor->priv->mode = mode;
- if (html_mode) {
+ if (mode == E_CONTENT_EDITOR_MODE_HTML) {
e_web_view_jsc_run_script (WEBKIT_WEB_VIEW (wk_editor), wk_editor->priv->cancellable,
"EvoEditor.SetMode(EvoEditor.MODE_HTML);");
} else {
@@ -2041,11 +2029,11 @@ webkit_editor_insert_content (EContentEditor *editor,
}
/* Only convert messages that are in HTML */
- if (!(wk_editor->priv->html_mode)) {
+ if (wk_editor->priv->mode != E_CONTENT_EDITOR_MODE_HTML) {
if (strstr (content, "<!-- text/html -->") &&
!strstr (content, "<!-- disable-format-prompt -->")) {
if (!show_lose_formatting_dialog (wk_editor)) {
- webkit_editor_set_html_mode (wk_editor, TRUE);
+ webkit_editor_set_mode (wk_editor, E_CONTENT_EDITOR_MODE_HTML);
e_web_view_jsc_run_script (WEBKIT_WEB_VIEW (wk_editor),
wk_editor->priv->cancellable,
"EvoEditor.LoadHTML(%s);", content);
if (cleanup_sig_id)
@@ -2467,7 +2455,7 @@ webkit_editor_set_start_bottom (EWebKitEditor *wk_editor,
e_web_view_jsc_run_script (WEBKIT_WEB_VIEW (wk_editor), wk_editor->priv->cancellable,
"EvoEditor.START_BOTTOM = %x;",
- e_webkit_editor_three_state_to_bool (value, "composer-reply-start-bottom"));
+ e_content_editor_util_three_state_to_bool (value, "composer-reply-start-bottom"));
g_object_notify (G_OBJECT (wk_editor), "start-bottom");
}
@@ -2595,7 +2583,7 @@ webkit_editor_get_current_signature_uid (EContentEditor *editor)
static gchar *
webkit_editor_insert_signature (EContentEditor *editor,
const gchar *content,
- gboolean is_html,
+ EContentEditorMode editor_mode,
gboolean can_reposition_caret,
const gchar *signature_id,
gboolean *set_signature_from_message,
@@ -2607,8 +2595,12 @@ webkit_editor_insert_signature (EContentEditor *editor,
g_return_val_if_fail (E_IS_WEBKIT_EDITOR (editor), NULL);
- if (!is_html && content && *content) {
- tmp = camel_text_to_html (content, CAMEL_MIME_FILTER_TOHTML_PRE, 0);
+ if (editor_mode != E_CONTENT_EDITOR_MODE_HTML && content && *content) {
+ if (editor_mode == E_CONTENT_EDITOR_MODE_MARKDOWN_HTML)
+ tmp = e_markdown_utils_text_to_html (content, -1);
+
+ if (!tmp)
+ tmp = camel_text_to_html (content, CAMEL_MIME_FILTER_TOHTML_PRE, 0);
if (tmp)
content = tmp;
@@ -2617,15 +2609,15 @@ webkit_editor_insert_signature (EContentEditor *editor,
jsc_value = webkit_editor_call_jsc_sync (E_WEBKIT_EDITOR (editor),
"EvoEditor.InsertSignature(%s, %x, %x, %s, %x, %x, %x, %x, %x, %x);",
content ? content : "",
- is_html,
+ editor_mode == E_CONTENT_EDITOR_MODE_HTML,
can_reposition_caret,
signature_id,
*set_signature_from_message,
*check_if_signature_is_changed,
*ignore_next_signature_change,
- e_webkit_editor_three_state_to_bool (e_content_editor_get_start_bottom (editor),
"composer-reply-start-bottom"),
- e_webkit_editor_three_state_to_bool (e_content_editor_get_top_signature (editor),
"composer-top-signature"),
- !e_webkit_editor_three_state_to_bool (E_THREE_STATE_INCONSISTENT,
"composer-no-signature-delim"));
+ e_content_editor_util_three_state_to_bool (e_content_editor_get_start_bottom (editor),
"composer-reply-start-bottom"),
+ e_content_editor_util_three_state_to_bool (e_content_editor_get_top_signature (editor),
"composer-top-signature"),
+ !e_content_editor_util_three_state_to_bool (E_THREE_STATE_INCONSISTENT,
"composer-no-signature-delim"));
g_free (tmp);
@@ -3477,7 +3469,7 @@ webkit_editor_get_font_name (EWebKitEditor *wk_editor)
{
g_return_val_if_fail (E_IS_WEBKIT_EDITOR (wk_editor), NULL);
- if (!wk_editor->priv->html_mode)
+ if (wk_editor->priv->mode != E_CONTENT_EDITOR_MODE_HTML)
return NULL;
return wk_editor->priv->font_name;
@@ -3506,7 +3498,7 @@ webkit_editor_get_font_color (EWebKitEditor *wk_editor)
{
g_return_val_if_fail (E_IS_WEBKIT_EDITOR (wk_editor), NULL);
- if (!wk_editor->priv->html_mode || !wk_editor->priv->font_color)
+ if (wk_editor->priv->mode != E_CONTENT_EDITOR_MODE_HTML || !wk_editor->priv->font_color)
return &black;
return wk_editor->priv->font_color;
@@ -4474,10 +4466,10 @@ webkit_editor_set_property (GObject *object,
g_value_get_boolean (value));
return;
- case PROP_HTML_MODE:
- webkit_editor_set_html_mode (
+ case PROP_MODE:
+ webkit_editor_set_mode (
E_WEBKIT_EDITOR (object),
- g_value_get_boolean (value));
+ g_value_get_enum (value));
return;
case PROP_NORMAL_PARAGRAPH_WIDTH:
@@ -4683,9 +4675,9 @@ webkit_editor_get_property (GObject *object,
E_WEBKIT_EDITOR (object)));
return;
- case PROP_HTML_MODE:
- g_value_set_boolean (
- value, webkit_editor_get_html_mode (
+ case PROP_MODE:
+ g_value_set_enum (
+ value, webkit_editor_get_mode (
E_WEBKIT_EDITOR (object)));
return;
@@ -4970,7 +4962,7 @@ webkit_editor_load_changed_cb (EWebKitEditor *wk_editor,
"EvoEditor.UNICODE_SMILEYS = %x;"
"EvoEditor.WRAP_QUOTED_TEXT_IN_REPLIES = %x;",
wk_editor->priv->normal_paragraph_width,
- e_webkit_editor_three_state_to_bool (wk_editor->priv->start_bottom,
"composer-reply-start-bottom"),
+ e_content_editor_util_three_state_to_bool (wk_editor->priv->start_bottom,
"composer-reply-start-bottom"),
wk_editor->priv->magic_links,
wk_editor->priv->magic_smileys,
wk_editor->priv->unicode_smileys,
@@ -5081,7 +5073,7 @@ webkit_editor_paste_clipboard_targets_cb (GtkClipboard *clipboard,
* with SRCSET attribute in clipboard correctly). And if this fails the
* source application can cancel the content and we could not fallback
* to at least some content. */
- if (wk_editor->priv->html_mode) {
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML) {
if (e_targets_include_html (targets, n_targets)) {
content = e_clipboard_wait_for_html (clipboard);
is_html = TRUE;
@@ -5096,7 +5088,7 @@ webkit_editor_paste_clipboard_targets_cb (GtkClipboard *clipboard,
}
}
- if (wk_editor->priv->html_mode &&
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML &&
gtk_targets_include_image (targets, n_targets, TRUE) &&
(!content || !*content || !is_libreoffice_content (targets, n_targets))) {
gchar *uri;
@@ -5441,7 +5433,7 @@ paste_primary_clipboard_quoted (EContentEditor *editor)
gdk_display_get_default (),
GDK_SELECTION_PRIMARY);
- if (wk_editor->priv->html_mode) {
+ if (wk_editor->priv->mode == E_CONTENT_EDITOR_MODE_HTML) {
if (e_clipboard_wait_is_html_available (clipboard))
e_clipboard_request_html (clipboard, clipboard_html_received_for_paste_quote, editor);
else if (gtk_clipboard_wait_is_text_available (clipboard))
@@ -5608,7 +5600,7 @@ e_webkit_editor_class_init (EWebKitEditorClass *class)
g_object_class_override_property (
object_class, PROP_CHANGED, "changed");
g_object_class_override_property (
- object_class, PROP_HTML_MODE, "html-mode");
+ object_class, PROP_MODE, "mode");
g_object_class_override_property (
object_class, PROP_EDITABLE, "editable");
g_object_class_override_property (
@@ -5821,7 +5813,7 @@ e_webkit_editor_init (EWebKitEditor *wk_editor)
g_settings, "changed::composer-inherit-theme-colors",
G_CALLBACK (webkit_editor_style_settings_changed_cb), wk_editor);
- wk_editor->priv->html_mode = TRUE;
+ wk_editor->priv->mode = E_CONTENT_EDITOR_MODE_HTML;
wk_editor->priv->changed = FALSE;
wk_editor->priv->can_copy = FALSE;
wk_editor->priv->can_cut = FALSE;
@@ -5845,6 +5837,7 @@ e_webkit_editor_init (EWebKitEditor *wk_editor)
static void
e_webkit_editor_content_editor_init (EContentEditorInterface *iface)
{
+ iface->supports_mode = webkit_editor_supports_mode;
iface->initialize = webkit_editor_initialize;
iface->update_styles = webkit_editor_update_styles;
iface->insert_content = webkit_editor_insert_content;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]