Optionally send email in plain text: Closes #3198

The composer can now be switched between rich and plain text mode
using the context menu.
This commit is contained in:
Robert Schroll 2013-02-26 15:30:47 -08:00 committed by Jim Nelson
parent 5328b3448f
commit 3e4b94de0e
4 changed files with 117 additions and 23 deletions

View file

@ -64,6 +64,11 @@
<summary>ask when opening an attachment</summary> <summary>ask when opening an attachment</summary>
<description>True to ask when opening an attachment.</description> <description>True to ask when opening an attachment.</description>
</key> </key>
<key name="compose-as-html" type="b">
<default>true</default>
<summary>whether to compose emails in HTML</summary>
<description>True to compose emails in HTML; false for plain text.</description>
</key>
</schema> </schema>
</schemalist> </schemalist>

View file

@ -36,6 +36,7 @@ public class ComposerWindow : Gtk.Window {
private const string ACTION_FONT_SIZE = "fontsize"; private const string ACTION_FONT_SIZE = "fontsize";
private const string ACTION_COLOR = "color"; private const string ACTION_COLOR = "color";
private const string ACTION_INSERT_LINK = "insertlink"; private const string ACTION_INSERT_LINK = "insertlink";
private const string ACTION_COMPOSE_AS_HTML = "compose as html";
private const string URI_LIST_MIME_TYPE = "text/uri-list"; private const string URI_LIST_MIME_TYPE = "text/uri-list";
private const string FILE_URI_PREFIX = "file://"; private const string FILE_URI_PREFIX = "file://";
@ -49,6 +50,9 @@ public class ComposerWindow : Gtk.Window {
background-color: white !important; background-color: white !important;
font-size: medium !important; font-size: medium !important;
} }
body.plain {
font-family: monospace !important;
}
blockquote { blockquote {
margin-top: 0px; margin-top: 0px;
margin-bottom: 0px; margin-bottom: 0px;
@ -60,6 +64,10 @@ public class ComposerWindow : Gtk.Window {
border: 0; border: 0;
border-left: 3px #aaa solid; border-left: 3px #aaa solid;
} }
pre {
white-space: pre-wrap;
margin: 0;
}
</style> </style>
</head><body id="message-body"></body></html>"""; </head><body id="message-body"></body></html>""";
@ -101,6 +109,11 @@ public class ComposerWindow : Gtk.Window {
} }
} }
public bool compose_as_html {
get { return ((Gtk.ToggleAction) actions.get_action(ACTION_COMPOSE_AS_HTML)).active; }
set { ((Gtk.ToggleAction) actions.get_action(ACTION_COMPOSE_AS_HTML)).active = value; }
}
public ComposeType compose_type { get; private set; default = ComposeType.NEW_MESSAGE; } public ComposeType compose_type { get; private set; default = ComposeType.NEW_MESSAGE; }
private string? body_html = null; private string? body_html = null;
@ -126,6 +139,7 @@ public class ComposerWindow : Gtk.Window {
private Gtk.Alignment visible_on_attachment_drag_over; private Gtk.Alignment visible_on_attachment_drag_over;
private Gtk.Widget hidden_on_attachment_drag_over_child; private Gtk.Widget hidden_on_attachment_drag_over_child;
private Gtk.Widget visible_on_attachment_drag_over_child; private Gtk.Widget visible_on_attachment_drag_over_child;
private Gtk.Toolbar compose_toolbar;
private Gtk.RadioMenuItem font_small; private Gtk.RadioMenuItem font_small;
private Gtk.RadioMenuItem font_medium; private Gtk.RadioMenuItem font_medium;
@ -198,6 +212,8 @@ public class ComposerWindow : Gtk.Window {
subject_entry = builder.get_object("subject") as Gtk.Entry; subject_entry = builder.get_object("subject") as Gtk.Entry;
Gtk.Alignment message_area = builder.get_object("message area") as Gtk.Alignment; Gtk.Alignment message_area = builder.get_object("message area") as Gtk.Alignment;
actions = builder.get_object("compose actions") as Gtk.ActionGroup; actions = builder.get_object("compose actions") as Gtk.ActionGroup;
// Can only hapen after actions exits
compose_as_html = GearyApplication.instance.config.compose_as_html;
// Listen to account signals to update from menu. // Listen to account signals to update from menu.
Geary.Engine.instance.account_available.connect(update_from_field); Geary.Engine.instance.account_available.connect(update_from_field);
@ -222,7 +238,7 @@ public class ComposerWindow : Gtk.Window {
cc_entry.changed.connect(validate_send_button); cc_entry.changed.connect(validate_send_button);
bcc_entry.changed.connect(validate_send_button); bcc_entry.changed.connect(validate_send_button);
Gtk.Toolbar compose_toolbar = (Gtk.Toolbar) builder.get_object("compose_toolbar"); compose_toolbar = (Gtk.Toolbar) builder.get_object("compose_toolbar");
actions.get_action(ACTION_UNDO).activate.connect(on_action); actions.get_action(ACTION_UNDO).activate.connect(on_action);
actions.get_action(ACTION_REDO).activate.connect(on_action); actions.get_action(ACTION_REDO).activate.connect(on_action);
@ -233,20 +249,21 @@ public class ComposerWindow : Gtk.Window {
actions.get_action(ACTION_PASTE).activate.connect(on_paste); actions.get_action(ACTION_PASTE).activate.connect(on_paste);
actions.get_action(ACTION_PASTE_FORMAT).activate.connect(on_paste_with_formatting); actions.get_action(ACTION_PASTE_FORMAT).activate.connect(on_paste_with_formatting);
actions.get_action(ACTION_BOLD).activate.connect(on_action); actions.get_action(ACTION_BOLD).activate.connect(on_formatting_action);
actions.get_action(ACTION_ITALIC).activate.connect(on_action); actions.get_action(ACTION_ITALIC).activate.connect(on_formatting_action);
actions.get_action(ACTION_UNDERLINE).activate.connect(on_action); actions.get_action(ACTION_UNDERLINE).activate.connect(on_formatting_action);
actions.get_action(ACTION_STRIKETHROUGH).activate.connect(on_action); actions.get_action(ACTION_STRIKETHROUGH).activate.connect(on_formatting_action);
actions.get_action(ACTION_REMOVE_FORMAT).activate.connect(on_remove_format); actions.get_action(ACTION_REMOVE_FORMAT).activate.connect(on_remove_format);
actions.get_action(ACTION_COMPOSE_AS_HTML).activate.connect(on_compose_as_html);
actions.get_action(ACTION_INDENT).activate.connect(on_action); actions.get_action(ACTION_INDENT).activate.connect(on_formatting_action);
actions.get_action(ACTION_OUTDENT).activate.connect(on_action); actions.get_action(ACTION_OUTDENT).activate.connect(on_formatting_action);
actions.get_action(ACTION_JUSTIFY_LEFT).activate.connect(on_action); actions.get_action(ACTION_JUSTIFY_LEFT).activate.connect(on_formatting_action);
actions.get_action(ACTION_JUSTIFY_RIGHT).activate.connect(on_action); actions.get_action(ACTION_JUSTIFY_RIGHT).activate.connect(on_formatting_action);
actions.get_action(ACTION_JUSTIFY_CENTER).activate.connect(on_action); actions.get_action(ACTION_JUSTIFY_CENTER).activate.connect(on_formatting_action);
actions.get_action(ACTION_JUSTIFY_FULL).activate.connect(on_action); actions.get_action(ACTION_JUSTIFY_FULL).activate.connect(on_formatting_action);
actions.get_action(ACTION_FONT).activate.connect(on_select_font); actions.get_action(ACTION_FONT).activate.connect(on_select_font);
actions.get_action(ACTION_FONT_SIZE).activate.connect(on_select_font_size); actions.get_action(ACTION_FONT_SIZE).activate.connect(on_select_font_size);
@ -392,6 +409,9 @@ public class ComposerWindow : Gtk.Window {
editor.grab_focus(); editor.grab_focus();
body.focus(); body.focus();
} }
// Ensure the editor is in correct mode re HTML
on_compose_as_html();
bind_event(editor,"a", "click", (Callback) on_link_clicked, this); bind_event(editor,"a", "click", (Callback) on_link_clicked, this);
update_actions(); update_actions();
@ -504,7 +524,8 @@ public class ComposerWindow : Gtk.Window {
email.attachment_files.add_all(attachment_files); email.attachment_files.add_all(attachment_files);
email.body_html = new Geary.RFC822.Text(new Geary.Memory.StringBuffer(get_html())); if (compose_as_html)
email.body_html = new Geary.RFC822.Text(new Geary.Memory.StringBuffer(get_html()));
email.body_text = new Geary.RFC822.Text(new Geary.Memory.StringBuffer(get_text())); email.body_text = new Geary.RFC822.Text(new Geary.Memory.StringBuffer(get_text()));
// User-Agent // User-Agent
@ -671,6 +692,11 @@ public class ComposerWindow : Gtk.Window {
&& (!to_entry.empty || !cc_entry.empty || !bcc_entry.empty); && (!to_entry.empty || !cc_entry.empty || !bcc_entry.empty);
} }
private void on_formatting_action(Gtk.Action action) {
if (compose_as_html)
on_action(action);
}
private void on_action(Gtk.Action action) { private void on_action(Gtk.Action action) {
if (action_flag) if (action_flag)
return; return;
@ -786,6 +812,50 @@ public class ComposerWindow : Gtk.Window {
editor.get_dom_document().exec_command("forecolor", false, "#000000"); editor.get_dom_document().exec_command("forecolor", false, "#000000");
} }
private void on_compose_as_html() {
WebKit.DOM.DOMTokenList body_classes = editor.get_dom_document().body.get_class_list();
if (!compose_as_html) {
// Do the equivalent of on_remove_format, but for entire document while maintaining
// selection.
editor.settings.enable_scripts = true;
editor.execute_script("""
selection = document.getSelection();
anchorNode = selection.anchorNode;
anchorOffset = selection.anchorOffset;
focusNode = selection.focusNode;
focusOffset = selection.focusOffset;
selection.selectAllChildren(document);
document.execCommand("removeformat", false, "");
// TODO: Use this when a reset stylesheet is available:
// http://redmine.yorba.org/issues/6437
//document.execCommand("removeparaformat", false, "");
document.execCommand("unlink", false, "");
document.execCommand("backcolor", false, "#ffffff");
document.execCommand("forecolor", false, "#000000");
selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
""");
editor.settings.enable_scripts = false;
compose_toolbar.hide();
try {
body_classes.add("plain");
} catch (Error error) {
debug("Error setting composer style: %s", error.message);
}
} else {
compose_toolbar.show();
try {
body_classes.remove("plain");
} catch (Error error) {
debug("Error setting composer style: %s", error.message);
}
}
GearyApplication.instance.config.compose_as_html = compose_as_html;
}
private void on_select_font() { private void on_select_font() {
if (!font_button.active) if (!font_button.active)
return; return;
@ -841,17 +911,18 @@ public class ComposerWindow : Gtk.Window {
} }
private void on_select_color() { private void on_select_color() {
Gtk.ColorChooserDialog dialog = new Gtk.ColorChooserDialog("Select Color", this); if (compose_as_html) {
if (dialog.run() == Gtk.ResponseType.OK) { Gtk.ColorChooserDialog dialog = new Gtk.ColorChooserDialog(_("Select Color"), this);
string color = dialog.get_rgba().to_string(); if (dialog.run() == Gtk.ResponseType.OK)
editor.get_dom_document().exec_command("forecolor", false, color); editor.get_dom_document().exec_command("forecolor", false, dialog.get_rgba().to_string());
dialog.destroy();
} }
dialog.destroy();
} }
private void on_insert_link() { private void on_insert_link() {
link_dialog("http://"); if (compose_as_html)
link_dialog("http://");
} }
private static void on_link_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event, private static void on_link_clicked(WebKit.DOM.Element element, WebKit.DOM.Event event,
@ -1014,9 +1085,11 @@ public class ComposerWindow : Gtk.Window {
context_menu.append(paste); context_menu.append(paste);
// Paste with formatting // Paste with formatting
Gtk.MenuItem paste_format = new Gtk.ImageMenuItem(); if (compose_as_html) {
paste_format.related_action = actions.get_action(ACTION_PASTE_FORMAT); Gtk.MenuItem paste_format = new Gtk.ImageMenuItem();
context_menu.append(paste_format); paste_format.related_action = actions.get_action(ACTION_PASTE_FORMAT);
context_menu.append(paste_format);
}
context_menu.append(new Gtk.SeparatorMenuItem()); context_menu.append(new Gtk.SeparatorMenuItem());
@ -1025,6 +1098,11 @@ public class ComposerWindow : Gtk.Window {
select_all_item.activate.connect(on_select_all); select_all_item.activate.connect(on_select_all);
context_menu.append(select_all_item); context_menu.append(select_all_item);
// HTML or plain text
Gtk.CheckMenuItem html_item = new Gtk.CheckMenuItem();
html_item.related_action = actions.get_action(ACTION_COMPOSE_AS_HTML);
context_menu.append(html_item);
context_menu.show_all(); context_menu.show_all();
context_menu.popup(null, null, null, event.button, event.time); context_menu.popup(null, null, null, event.button, event.time);
@ -1043,7 +1121,7 @@ public class ComposerWindow : Gtk.Window {
actions.get_action(ACTION_COPY).sensitive = editor.can_copy_clipboard(); actions.get_action(ACTION_COPY).sensitive = editor.can_copy_clipboard();
actions.get_action(ACTION_COPY_LINK).sensitive = hover_url != null; actions.get_action(ACTION_COPY_LINK).sensitive = hover_url != null;
actions.get_action(ACTION_PASTE).sensitive = editor.can_paste_clipboard(); actions.get_action(ACTION_PASTE).sensitive = editor.can_paste_clipboard();
actions.get_action(ACTION_PASTE_FORMAT).sensitive = editor.can_paste_clipboard(); actions.get_action(ACTION_PASTE_FORMAT).sensitive = editor.can_paste_clipboard() && compose_as_html;
// Style toggle buttons. // Style toggle buttons.
WebKit.DOM.DOMWindow window = editor.get_dom_document().get_default_view(); WebKit.DOM.DOMWindow window = editor.get_dom_document().get_default_view();

View file

@ -108,6 +108,12 @@ public class Configuration {
set { set_boolean(ASK_OPEN_ATTACHMENT, value); } set { set_boolean(ASK_OPEN_ATTACHMENT, value); }
} }
private const string COMPOSE_AS_HTML = "compose-as-html";
public bool compose_as_html {
get { return settings.get_boolean(COMPOSE_AS_HTML); }
set { set_boolean(COMPOSE_AS_HTML, value); }
}
// Creates a configuration object. // Creates a configuration object.
public Configuration() { public Configuration() {
// Start GSettings. // Start GSettings.

View file

@ -142,6 +142,11 @@
<property name="icon_name">font</property> <property name="icon_name">font</property>
</object> </object>
</child> </child>
<child>
<object class="GtkToggleAction" id="compose as html">
<property name="label" translatable="yes">Rich text</property>
</object>
</child>
</object> </object>
<object class="GtkBox" id="composer"> <object class="GtkBox" id="composer">
<property name="visible">True</property> <property name="visible">True</property>