Closes #4550. HTML composition.
This commit is contained in:
parent
19b8e871ab
commit
459af216b2
11 changed files with 714 additions and 104 deletions
|
|
@ -301,16 +301,21 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
|
|||
return builder;
|
||||
}
|
||||
|
||||
// Loads a UI file (in the ui directory) into the UI manager.
|
||||
public void load_ui_file(string ui_filename) {
|
||||
// Loads a UI file (in the ui directory) into the specified UI manager.
|
||||
public void load_ui_file_for_manager(Gtk.UIManager ui, string ui_filename) {
|
||||
try {
|
||||
ui_manager.add_ui_from_file(get_resource_directory().get_child("ui").get_child(
|
||||
ui.add_ui_from_file(get_resource_directory().get_child("ui").get_child(
|
||||
ui_filename).get_path());
|
||||
} catch(GLib.Error error) {
|
||||
warning("Unable to create Gtk.UIManager: %s".printf(error.message));
|
||||
}
|
||||
}
|
||||
|
||||
// Loads a UI file (in the ui directory) into the UI manager.
|
||||
public void load_ui_file(string ui_filename) {
|
||||
load_ui_file_for_manager(ui_manager, ui_filename);
|
||||
}
|
||||
|
||||
public Gtk.Window get_main_window() {
|
||||
return controller.main_window;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,32 @@
|
|||
public class ComposerWindow : Gtk.Window {
|
||||
private static string DEFAULT_TITLE = _("New Message");
|
||||
|
||||
private Gtk.Entry to_entry;
|
||||
private Gtk.Entry cc_entry;
|
||||
private Gtk.Entry bcc_entry;
|
||||
private Gtk.Entry subject_entry;
|
||||
private Gtk.SourceView message_text = new Gtk.SourceView();
|
||||
private Gtk.Button send_button;
|
||||
private const string REPLY_ID = "reply";
|
||||
private const string HTML_BODY = """
|
||||
<html><head><title></title>
|
||||
<style>
|
||||
body {
|
||||
margin: 10px !important;
|
||||
padding: 0 !important;
|
||||
background-color: white !important;
|
||||
font-size: 11pt !important;
|
||||
}
|
||||
blockquote {
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
background-color: white;
|
||||
border: 0;
|
||||
border-left: 3px #aaa solid;
|
||||
}
|
||||
</style>
|
||||
</head><body>
|
||||
<p id="top"></p><br /><br />
|
||||
<span id="reply"></span>
|
||||
<br />
|
||||
</body></html>""";
|
||||
|
||||
// Signal sent when the "Send" button is clicked.
|
||||
public signal void send(ComposerWindow composer);
|
||||
|
||||
public string from { get; set; }
|
||||
|
||||
|
|
@ -41,12 +61,24 @@ public class ComposerWindow : Gtk.Window {
|
|||
}
|
||||
|
||||
public string message {
|
||||
owned get { return message_text.buffer.text; }
|
||||
set { message_text.buffer.text = value; }
|
||||
owned get { return get_html(); }
|
||||
set {
|
||||
reply_body = value;
|
||||
editor.load_string(HTML_BODY, "text/html", "UTF8", "");
|
||||
}
|
||||
}
|
||||
|
||||
// Signal sent when the "Send" button is clicked.
|
||||
public signal void send(ComposerWindow composer);
|
||||
private string? reply_body = null;
|
||||
|
||||
private Gtk.Entry to_entry;
|
||||
private Gtk.Entry cc_entry;
|
||||
private Gtk.Entry bcc_entry;
|
||||
private Gtk.Entry subject_entry;
|
||||
private Gtk.Button send_button;
|
||||
private Gtk.Label message_overlay_label;
|
||||
|
||||
private WebKit.WebView editor;
|
||||
private Gtk.UIManager ui;
|
||||
|
||||
public ComposerWindow(Geary.ComposedEmail? prefill = null) {
|
||||
add_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK);
|
||||
|
|
@ -60,10 +92,21 @@ public class ComposerWindow : Gtk.Window {
|
|||
cc_entry = builder.get_object("cc") as Gtk.Entry;
|
||||
bcc_entry = builder.get_object("bcc") as Gtk.Entry;
|
||||
subject_entry = builder.get_object("subject") as Gtk.Entry;
|
||||
Gtk.ScrolledWindow scroll = builder.get_object("scrolledwindow") as Gtk.ScrolledWindow;
|
||||
scroll.add(message_text);
|
||||
message_text.set_wrap_mode(Gtk.WrapMode.WORD_CHAR);
|
||||
((Gtk.SourceBuffer) message_text.buffer).highlight_matching_brackets = false;
|
||||
Gtk.Alignment msg_area = builder.get_object("message area") as Gtk.Alignment;
|
||||
Gtk.ActionGroup actions = builder.get_object("compose actions") as Gtk.ActionGroup;
|
||||
|
||||
Gtk.ScrolledWindow scroll = new Gtk.ScrolledWindow(null, null);
|
||||
scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
|
||||
|
||||
Gtk.Overlay message_overlay = new Gtk.Overlay();
|
||||
message_overlay.add(scroll);
|
||||
msg_area.add(message_overlay);
|
||||
|
||||
message_overlay_label = new Gtk.Label(null);
|
||||
message_overlay_label.ellipsize = Pango.EllipsizeMode.MIDDLE;
|
||||
message_overlay_label.halign = Gtk.Align.START;
|
||||
message_overlay_label.valign = Gtk.Align.END;
|
||||
message_overlay.add_overlay(message_overlay_label);
|
||||
|
||||
title = DEFAULT_TITLE;
|
||||
subject_entry.changed.connect(on_subject_changed);
|
||||
|
|
@ -71,6 +114,37 @@ public class ComposerWindow : Gtk.Window {
|
|||
cc_entry.changed.connect(validate_send_button);
|
||||
bcc_entry.changed.connect(validate_send_button);
|
||||
|
||||
actions.get_action("undo").activate.connect(on_action);
|
||||
actions.get_action("redo").activate.connect(on_action);
|
||||
|
||||
actions.get_action("cut").activate.connect(on_cut);
|
||||
actions.get_action("copy").activate.connect(on_copy);
|
||||
actions.get_action("paste").activate.connect(on_paste);
|
||||
|
||||
actions.get_action("bold").activate.connect(on_action);
|
||||
actions.get_action("italic").activate.connect(on_action);
|
||||
actions.get_action("underline").activate.connect(on_action);
|
||||
actions.get_action("strikethrough").activate.connect(on_action);
|
||||
|
||||
actions.get_action("removeformat").activate.connect(on_remove_format);
|
||||
|
||||
actions.get_action("indent").activate.connect(on_action);
|
||||
actions.get_action("outdent").activate.connect(on_action);
|
||||
|
||||
actions.get_action("justifyleft").activate.connect(on_action);
|
||||
actions.get_action("justifyright").activate.connect(on_action);
|
||||
actions.get_action("justifycenter").activate.connect(on_action);
|
||||
actions.get_action("justifyfull").activate.connect(on_action);
|
||||
|
||||
actions.get_action("font").activate.connect(on_select_font);
|
||||
actions.get_action("color").activate.connect(on_select_color);
|
||||
actions.get_action("insertlink").activate.connect(on_insert_link);
|
||||
|
||||
ui = new Gtk.UIManager();
|
||||
ui.insert_action_group(actions, 0);
|
||||
add_accel_group(ui.get_accel_group());
|
||||
GearyApplication.instance.load_ui_file_for_manager(ui, "composer_accelerators.ui");
|
||||
|
||||
if (prefill != null) {
|
||||
if (prefill.from != null)
|
||||
from = prefill.from.to_rfc822_string();
|
||||
|
|
@ -86,19 +160,40 @@ public class ComposerWindow : Gtk.Window {
|
|||
references = prefill.references.to_rfc822_string();
|
||||
if (prefill.subject != null)
|
||||
subject = prefill.subject.value;
|
||||
if (prefill.body != null)
|
||||
message = prefill.body.buffer.to_utf8();
|
||||
|
||||
if (!Geary.String.is_empty(to) && !Geary.String.is_empty(subject))
|
||||
message_text.grab_focus();
|
||||
else if (!Geary.String.is_empty(to))
|
||||
subject_entry.grab_focus();
|
||||
if (prefill.body_html != null)
|
||||
reply_body = prefill.body_html.buffer.to_utf8();
|
||||
if (reply_body == null && prefill.body_text != null)
|
||||
reply_body = "<pre>" + prefill.body_text.buffer.to_utf8();
|
||||
}
|
||||
|
||||
editor = new WebKit.WebView();
|
||||
editor.set_editable(true);
|
||||
editor.load_finished.connect(on_load_finished);
|
||||
editor.hovering_over_link.connect(on_hovering_over_link);
|
||||
editor.load_string(HTML_BODY, "text/html", "UTF8", ""); // only do this after setting reply_body
|
||||
|
||||
if (!Geary.String.is_empty(to) && !Geary.String.is_empty(subject))
|
||||
editor.grab_focus();
|
||||
else if (!Geary.String.is_empty(to))
|
||||
subject_entry.grab_focus();
|
||||
|
||||
editor.navigation_policy_decision_requested.connect(on_navigation_policy_decision_requested);
|
||||
editor.new_window_policy_decision_requested.connect(on_navigation_policy_decision_requested);
|
||||
|
||||
WebKit.WebSettings s = new WebKit.WebSettings();
|
||||
s.enable_spell_checking = true;
|
||||
s.auto_load_images = false;
|
||||
s.enable_default_context_menu = true;
|
||||
s.enable_scripts = false;
|
||||
s.enable_java_applet = false;
|
||||
s.enable_plugins = false;
|
||||
editor.settings = s;
|
||||
|
||||
scroll.add(editor);
|
||||
scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
|
||||
|
||||
add(box);
|
||||
validate_send_button();
|
||||
|
||||
message_text.move_cursor(Gtk.MovementStep.BUFFER_ENDS, -1, false);
|
||||
}
|
||||
|
||||
public Geary.ComposedEmail get_composed_email(
|
||||
|
|
@ -128,20 +223,21 @@ public class ComposerWindow : Gtk.Window {
|
|||
if (!Geary.String.is_empty(subject))
|
||||
email.subject = new Geary.RFC822.Subject(subject);
|
||||
|
||||
email.body = new Geary.RFC822.Text(new Geary.Memory.StringBuffer(message));
|
||||
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()));
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
public override void show_all() {
|
||||
set_default_size(650, 550);
|
||||
set_default_size(680, 600);
|
||||
|
||||
base.show_all();
|
||||
}
|
||||
|
||||
private bool should_close() {
|
||||
// TODO: Check if the message was (automatically) saved
|
||||
if (((Gtk.SourceBuffer) message_text.buffer).can_undo) {
|
||||
if (editor.can_undo()) {
|
||||
var dialog = new Gtk.MessageDialog(this, 0,
|
||||
Gtk.MessageType.WARNING, Gtk.ButtonsType.NONE,
|
||||
_("Do you want to discard the unsaved message?"));
|
||||
|
|
@ -176,6 +272,114 @@ public class ComposerWindow : Gtk.Window {
|
|||
!Geary.String.is_empty(bcc_entry.get_text().strip());
|
||||
}
|
||||
|
||||
private void on_action(Gtk.Action action) {
|
||||
editor.get_dom_document().exec_command(action.get_name(), false, "");
|
||||
}
|
||||
|
||||
private void on_cut() {
|
||||
editor.cut_clipboard();
|
||||
}
|
||||
|
||||
private void on_copy() {
|
||||
editor.copy_clipboard();
|
||||
}
|
||||
|
||||
private void on_paste() {
|
||||
editor.paste_clipboard();
|
||||
}
|
||||
|
||||
private void on_remove_format() {
|
||||
editor.get_dom_document().exec_command("removeformat", false, "");
|
||||
editor.get_dom_document().exec_command("removeparaformat", false, "");
|
||||
editor.get_dom_document().exec_command("unlink", false, "");
|
||||
editor.get_dom_document().exec_command("backcolor", false, "#ffffff");
|
||||
editor.get_dom_document().exec_command("forecolor", false, "#000000");
|
||||
}
|
||||
|
||||
private void on_select_font() {
|
||||
Gtk.FontChooserDialog dialog = new Gtk.FontChooserDialog("Select font", this);
|
||||
if (dialog.run() == Gtk.ResponseType.OK) {
|
||||
editor.get_dom_document().exec_command("fontname", false, dialog.get_font_family().
|
||||
get_name());
|
||||
editor.get_dom_document().exec_command("fontsize", false,
|
||||
(((double) dialog.get_font_size()) / 4000.0).to_string());
|
||||
}
|
||||
|
||||
dialog.destroy();
|
||||
}
|
||||
|
||||
private void on_select_color() {
|
||||
Gtk.ColorSelectionDialog dialog = new Gtk.ColorSelectionDialog("Select Color");
|
||||
if (dialog.run() == Gtk.ResponseType.OK) {
|
||||
string color = ((Gtk.ColorSelection) dialog.get_color_selection()).
|
||||
current_rgba.to_string();
|
||||
|
||||
editor.get_dom_document().exec_command("forecolor", false, color);
|
||||
}
|
||||
|
||||
dialog.destroy();
|
||||
}
|
||||
|
||||
private void on_insert_link() {
|
||||
link_dialog("http://");
|
||||
}
|
||||
|
||||
private void link_dialog(string link) {
|
||||
Gtk.Dialog dialog = new Gtk.Dialog.with_buttons("", this, 0,
|
||||
Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL, Gtk.Stock.OK, Gtk.ResponseType.OK);
|
||||
Gtk.Entry entry = new Gtk.Entry();
|
||||
dialog.get_content_area().pack_start(new Gtk.Label("Link URL:"));
|
||||
dialog.get_content_area().pack_start(entry);
|
||||
dialog.set_default_response(Gtk.ResponseType.OK);
|
||||
dialog.show_all();
|
||||
|
||||
entry.set_text(link);
|
||||
|
||||
if (dialog.run() == Gtk.ResponseType.OK) {
|
||||
if (!Geary.String.is_empty(entry.text.strip()))
|
||||
editor.get_dom_document().exec_command("createLink", false, entry.text);
|
||||
else
|
||||
editor.get_dom_document().exec_command("unlink", false, "");
|
||||
}
|
||||
|
||||
dialog.destroy();
|
||||
}
|
||||
|
||||
private string get_html() {
|
||||
return editor.get_dom_document().get_body().get_inner_html();
|
||||
}
|
||||
|
||||
private string get_text() {
|
||||
return editor.get_dom_document().get_body().get_inner_text();
|
||||
}
|
||||
|
||||
private void on_load_finished(WebKit.WebFrame frame) {
|
||||
if (reply_body == null)
|
||||
return;
|
||||
|
||||
WebKit.DOM.HTMLElement? reply = editor.get_dom_document().get_element_by_id(
|
||||
REPLY_ID) as WebKit.DOM.HTMLElement;
|
||||
assert(reply != null);
|
||||
|
||||
try {
|
||||
reply.set_inner_html(reply_body);
|
||||
} catch (Error e) {
|
||||
debug("Failed to load email for reply: %s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
private bool on_navigation_policy_decision_requested(WebKit.WebFrame frame,
|
||||
WebKit.NetworkRequest request, WebKit.WebNavigationAction navigation_action,
|
||||
WebKit.WebPolicyDecision policy_decision) {
|
||||
policy_decision.ignore();
|
||||
link_dialog(request.uri);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void on_hovering_over_link(string? title, string? url) {
|
||||
message_overlay_label.label = url;
|
||||
}
|
||||
|
||||
public override bool key_press_event(Gdk.EventKey event) {
|
||||
bool handled = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class MainToolbar : Gtk.Box {
|
|||
|
||||
GearyApplication.instance.load_ui_file("toolbar_mark_menu.ui");
|
||||
mark_menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMarkMenu") as Gtk.Menu;
|
||||
|
||||
|
||||
GearyApplication.instance.load_ui_file("toolbar_menu.ui");
|
||||
menu = GearyApplication.instance.ui_manager.get_widget("/ui/ToolbarMenu") as Gtk.Menu;
|
||||
|
||||
|
|
@ -48,48 +48,27 @@ public class MainToolbar : Gtk.Box {
|
|||
as Gtk.ToolButton;
|
||||
archive_message.set_related_action(GearyApplication.instance.actions.get_action(
|
||||
GearyController.ACTION_DELETE_MESSAGE));
|
||||
|
||||
|
||||
mark_menu_button = builder.get_object(GearyController.ACTION_MARK_AS_MENU) as Gtk.ToolButton;
|
||||
mark_menu_button.set_related_action(GearyApplication.instance.actions.get_action(
|
||||
GearyController.ACTION_MARK_AS_MENU));
|
||||
mark_menu.attach_to_widget(mark_menu_button, null);
|
||||
mark_menu_button.clicked.connect(on_show_mark_menu);
|
||||
|
||||
menu_button = builder.get_object("menu_button") as Gtk.ToolButton;
|
||||
menu.attach_to_widget(menu_button, null);
|
||||
menu_button.clicked.connect(on_show_menu);
|
||||
|
||||
|
||||
toolbar.get_style_context().add_class("primary-toolbar");
|
||||
|
||||
add(toolbar);
|
||||
}
|
||||
|
||||
private void on_show_menu() {
|
||||
menu.popup(null, null, popup_pos, 0, 0);
|
||||
menu.popup(null, null, menu_popup_relative, 0, 0);
|
||||
}
|
||||
|
||||
public void on_show_mark_menu() {
|
||||
mark_menu.popup(null, null, popup_pos, 0, 0);
|
||||
}
|
||||
|
||||
// Calculates the position of menu popups. It handles both menus in the toolbar.
|
||||
private void popup_pos(Gtk.Menu menu, out int x, out int y, out bool push_in) {
|
||||
menu.realize();
|
||||
|
||||
int rx, ry;
|
||||
get_window().get_origin(out rx, out ry);
|
||||
|
||||
Gtk.Allocation menu_button_allocation;
|
||||
if (menu == mark_menu) {
|
||||
mark_menu_button.get_allocation(out menu_button_allocation);
|
||||
} else {
|
||||
menu_button.get_allocation(out menu_button_allocation);
|
||||
}
|
||||
|
||||
Gtk.Allocation toolbar_allocation;
|
||||
get_allocation(out toolbar_allocation);
|
||||
|
||||
x = rx + menu_button_allocation.x;
|
||||
y = ry + toolbar_allocation.height;
|
||||
|
||||
push_in = false;
|
||||
|
||||
private void on_show_mark_menu() {
|
||||
mark_menu.popup(null, null, menu_popup_relative, 0, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
src/client/util/util-menu.vala
Normal file
26
src/client/util/util-menu.vala
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* Copyright 2011-2012 Yorba Foundation
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
// Use this MenuPositionFunc to position a popup menu relative to a widget
|
||||
// with Gtk.Menu.popup().
|
||||
//
|
||||
// You *must* attach the button widget with Gtk.Menu.attach_to_widget() before
|
||||
// this function can be used.
|
||||
public void menu_popup_relative(Gtk.Menu menu, out int x, out int y, out bool push_in) {
|
||||
menu.realize();
|
||||
|
||||
int rx, ry;
|
||||
menu.get_attach_widget().get_window().get_origin(out rx, out ry);
|
||||
|
||||
Gtk.Allocation menu_button_allocation;
|
||||
menu.get_attach_widget().get_allocation(out menu_button_allocation);
|
||||
|
||||
x = rx + menu_button_allocation.x;
|
||||
y = ry + menu_button_allocation.y + menu_button_allocation.height;
|
||||
|
||||
push_in = false;
|
||||
}
|
||||
|
||||
|
|
@ -28,6 +28,7 @@ client_src = [
|
|||
|
||||
'util/util-email.vala',
|
||||
'util/util-keyring.vala',
|
||||
'util/util-menu.vala',
|
||||
]
|
||||
|
||||
gsettings_schemas = [
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ public class Geary.ComposedEmail : Object {
|
|||
public Geary.Email? reply_to_email { get; set; default = null; }
|
||||
public RFC822.MessageIDList? references { get; set; default = null; }
|
||||
public RFC822.Subject? subject { get; set; default = null; }
|
||||
public RFC822.Text? body { get; set; default = null; }
|
||||
public RFC822.Text? body_text { get; set; default = null; }
|
||||
public RFC822.Text? body_html { get; set; default = null; }
|
||||
|
||||
public ComposedEmail(DateTime date, RFC822.MailboxAddresses from,
|
||||
RFC822.MailboxAddresses? to = null) {
|
||||
|
|
@ -42,8 +43,10 @@ public class Geary.ComposedEmail : Object {
|
|||
subject = create_subject_for_reply(source);
|
||||
set_reply_references(source);
|
||||
|
||||
body = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" +
|
||||
Geary.RFC822.Utils.quote_email_for_reply(source)));
|
||||
body_text = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" +
|
||||
Geary.RFC822.Utils.quote_email_for_reply(source, false)));
|
||||
body_html = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" +
|
||||
Geary.RFC822.Utils.quote_email_for_reply(source, true)));
|
||||
}
|
||||
|
||||
public ComposedEmail.as_reply_all(DateTime date, RFC822.MailboxAddresses from, Geary.Email source) {
|
||||
|
|
@ -56,8 +59,10 @@ public class Geary.ComposedEmail : Object {
|
|||
subject = create_subject_for_reply(source);
|
||||
set_reply_references(source);
|
||||
|
||||
body = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" +
|
||||
Geary.RFC822.Utils.quote_email_for_reply(source)));
|
||||
body_text = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" +
|
||||
Geary.RFC822.Utils.quote_email_for_reply(source, false)));
|
||||
body_html = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" +
|
||||
Geary.RFC822.Utils.quote_email_for_reply(source, true)));
|
||||
}
|
||||
|
||||
public ComposedEmail.as_forward(DateTime date, RFC822.MailboxAddresses from, Geary.Email source) {
|
||||
|
|
@ -65,8 +70,10 @@ public class Geary.ComposedEmail : Object {
|
|||
|
||||
subject = create_subject_for_forward(source);
|
||||
|
||||
body = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" +
|
||||
Geary.RFC822.Utils.quote_email_for_forward(source)));
|
||||
body_text = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" +
|
||||
Geary.RFC822.Utils.quote_email_for_forward(source, false)));
|
||||
body_html = new RFC822.Text(new Geary.Memory.StringBuffer("\n\n" +
|
||||
Geary.RFC822.Utils.quote_email_for_forward(source, true)));
|
||||
}
|
||||
|
||||
private void set_reply_references(Geary.Email source) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ public class Geary.RFC822.Message : Object {
|
|||
}
|
||||
|
||||
public Message.from_composed_email(Geary.ComposedEmail email) {
|
||||
GMime.Part? body_html = null;
|
||||
GMime.Part? body_text = null;
|
||||
message = new GMime.Message(true);
|
||||
|
||||
// Required headers
|
||||
|
|
@ -86,16 +88,38 @@ public class Geary.RFC822.Message : Object {
|
|||
message.set_subject(email.subject.value);
|
||||
}
|
||||
|
||||
// Body (also optional)
|
||||
if (email.body != null) {
|
||||
// Body: text format (optional)
|
||||
if (email.body_text != null) {
|
||||
GMime.DataWrapper content = new GMime.DataWrapper.with_stream(
|
||||
new GMime.StreamMem.with_buffer(email.body.buffer.get_array()),
|
||||
new GMime.StreamMem.with_buffer(email.body_text.buffer.get_array()),
|
||||
GMime.ContentEncoding.DEFAULT);
|
||||
|
||||
GMime.Part part = new GMime.Part();
|
||||
part.set_content_object(content);
|
||||
body_text = new GMime.Part();
|
||||
body_text.set_content_type(new GMime.ContentType("text", "plain"));
|
||||
body_text.set_content_object(content);
|
||||
}
|
||||
|
||||
// Body: HTML format (also optional)
|
||||
if (email.body_html != null) {
|
||||
GMime.DataWrapper content = new GMime.DataWrapper.with_stream(
|
||||
new GMime.StreamMem.with_buffer(email.body_html.buffer.get_array()),
|
||||
GMime.ContentEncoding.DEFAULT);
|
||||
|
||||
message.set_mime_part(part);
|
||||
body_html = new GMime.Part();
|
||||
body_html.set_content_type(new GMime.ContentType("text", "html"));
|
||||
body_html.set_content_object(content);
|
||||
}
|
||||
|
||||
// Setup body depending on what MIME components were filled out.
|
||||
if (body_text != null && body_html != null) {
|
||||
GMime.Multipart multipart = new GMime.Multipart.with_subtype("alternative");
|
||||
multipart.add(body_text);
|
||||
multipart.add(body_html);
|
||||
message.set_mime_part(multipart);
|
||||
} else if (body_text != null) {
|
||||
message.set_mime_part(body_text);
|
||||
} else if (body_html != null) {
|
||||
message.set_mime_part(body_html);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ namespace Geary.RFC822.Utils {
|
|||
*
|
||||
* If there's no message body in the supplied email, this function will
|
||||
* return the empty string.
|
||||
*
|
||||
* TODO: Support HTML as an option.
|
||||
*
|
||||
* If html_format is true, the message will be quoted in HTML format.
|
||||
* Otherwise it will be in plain text.
|
||||
*/
|
||||
public string quote_email_for_reply(Geary.Email email) {
|
||||
public string quote_email_for_reply(Geary.Email email, bool html_format) {
|
||||
if (email.body == null)
|
||||
return "";
|
||||
|
||||
|
|
@ -26,8 +27,11 @@ public string quote_email_for_reply(Geary.Email email) {
|
|||
if (email.from != null)
|
||||
quoted += _("%s wrote:").printf(email.from.to_string());
|
||||
|
||||
if (html_format)
|
||||
quoted += "<br />";
|
||||
|
||||
if (email.body != null)
|
||||
quoted += "\n" + quote_body(email);
|
||||
quoted += "\n" + quote_body(email, true, html_format);
|
||||
|
||||
return quoted;
|
||||
}
|
||||
|
|
@ -38,9 +42,10 @@ public string quote_email_for_reply(Geary.Email email) {
|
|||
* If there's no message body in the supplied email, this function will
|
||||
* return the empty string.
|
||||
*
|
||||
* TODO: Support HTML as an option.
|
||||
* If html_format is true, the message will be quoted in HTML format.
|
||||
* Otherwise it will be in plain text.
|
||||
*/
|
||||
public string quote_email_for_forward(Geary.Email email) {
|
||||
public string quote_email_for_forward(Geary.Email email, bool html_format) {
|
||||
if (email.body == null)
|
||||
return "";
|
||||
|
||||
|
|
@ -53,35 +58,67 @@ public string quote_email_for_forward(Geary.Email email) {
|
|||
quoted += _("Date: %s\n").printf(email.date != null ? email.date.to_string() : "");
|
||||
quoted += _("To: %s\n").printf(email.to != null ? email.to.to_string() : "");
|
||||
|
||||
if (html_format)
|
||||
quoted = quoted.replace("\n", "<br />");
|
||||
|
||||
if (email.body != null)
|
||||
quoted += "\n" + quote_body(email, false);
|
||||
quoted += "\n" + quote_body(email, false, html_format);
|
||||
|
||||
return quoted;
|
||||
}
|
||||
|
||||
private string quote_body(Geary.Email email, bool line_start_char = true) {
|
||||
private string text_from_message(Geary.Email email, bool html_format) throws Error {
|
||||
if (html_format) {
|
||||
return email.get_message().get_first_mime_part_of_content_type("text/html").to_utf8();
|
||||
} else {
|
||||
return email.get_message().get_first_mime_part_of_content_type("text/plain").to_utf8();
|
||||
}
|
||||
}
|
||||
|
||||
private string quote_body(Geary.Email email, bool use_quotes, bool html_format) {
|
||||
string body_text = "";
|
||||
try {
|
||||
body_text = email.get_message().get_first_mime_part_of_content_type("text/plain").to_utf8();
|
||||
} catch (Error err) {
|
||||
|
||||
if (html_format) {
|
||||
try {
|
||||
body_text = Geary.HTML.remove_html_tags(email.get_message().
|
||||
get_first_mime_part_of_content_type("text/html").to_utf8());
|
||||
} catch (Error err2) {
|
||||
debug("Could not get message text. %s", err2.message);
|
||||
body_text = text_from_message(email, true);
|
||||
} catch (Error err) {
|
||||
try {
|
||||
body_text = text_from_message(email, false).replace("\n", "<br />");
|
||||
} catch (Error err2) {
|
||||
debug("Could not get message text. %s", err2.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string ret = "";
|
||||
string[] lines = body_text.split("\n");
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
if (line_start_char)
|
||||
ret += "> ";
|
||||
|
||||
ret += lines[i];
|
||||
// Wrap the whole thing in a blockquote.
|
||||
if (use_quotes)
|
||||
body_text = "<blockquote>%s</blockquote>".printf(body_text);
|
||||
|
||||
return body_text;
|
||||
} else {
|
||||
// Get message text. First we'll try text, but if that fails we'll
|
||||
// resort to stripping tags out of the HTML section.
|
||||
try {
|
||||
body_text = text_from_message(email, false);
|
||||
} catch (Error err) {
|
||||
try {
|
||||
body_text = Geary.HTML.remove_html_tags(text_from_message(email, false));
|
||||
} catch (Error err2) {
|
||||
debug("Could not get message text. %s", err2.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the quoted message > symbols.
|
||||
string ret = "";
|
||||
string[] lines = body_text.split("\n");
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
if (use_quotes)
|
||||
ret += "> ";
|
||||
|
||||
ret += lines[i];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ int main(string[] args) {
|
|||
if (!Geary.String.is_empty(subject))
|
||||
composed_email.subject = new Geary.RFC822.Subject(subject);
|
||||
if (!Geary.String.is_empty(builder.str))
|
||||
composed_email.body = new Geary.RFC822.Text(new Geary.Memory.StringBuffer(builder.str));
|
||||
composed_email.body_text = new Geary.RFC822.Text(new Geary.Memory.StringBuffer(builder.str));
|
||||
|
||||
main_loop = new MainLoop();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,124 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<!-- interface-requires gtk+ 3.0 -->
|
||||
<object class="GtkActionGroup" id="compose actions">
|
||||
<child>
|
||||
<object class="GtkAction" id="undo">
|
||||
<property name="stock_id">gtk-undo</property>
|
||||
</object>
|
||||
<accelerator key="z" modifiers="GDK_CONTROL_MASK"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="redo">
|
||||
<property name="stock_id">gtk-redo</property>
|
||||
</object>
|
||||
<accelerator key="z" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="cut">
|
||||
<property name="stock_id">gtk-cut</property>
|
||||
</object>
|
||||
<accelerator key="x" modifiers="GDK_CONTROL_MASK"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="copy">
|
||||
<property name="stock_id">gtk-copy</property>
|
||||
</object>
|
||||
<accelerator key="c" modifiers="GDK_CONTROL_MASK"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="paste">
|
||||
<property name="stock_id">gtk-paste</property>
|
||||
</object>
|
||||
<accelerator key="v" modifiers="GDK_CONTROL_MASK"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="bold">
|
||||
<property name="stock_id">gtk-bold</property>
|
||||
</object>
|
||||
<accelerator key="b" modifiers="GDK_CONTROL_MASK"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="italic">
|
||||
<property name="stock_id">gtk-italic</property>
|
||||
</object>
|
||||
<accelerator key="i" modifiers="GDK_CONTROL_MASK"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="underline">
|
||||
<property name="stock_id">gtk-underline</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="strikethrough">
|
||||
<property name="stock_id">gtk-strikethrough</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="justifyleft">
|
||||
<property name="label" translatable="yes">_Left</property>
|
||||
<property name="stock_id">gtk-justify-left</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="justifyright">
|
||||
<property name="label" translatable="yes">_Right</property>
|
||||
<property name="stock_id">gtk-justify-right</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="justifycenter">
|
||||
<property name="label" translatable="yes">_Center</property>
|
||||
<property name="stock_id">gtk-justify-center</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="justifyfull">
|
||||
<property name="label" translatable="yes">_Justify</property>
|
||||
<property name="stock_id">gtk-justify-fill</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="insertlink">
|
||||
<property name="label" translatable="yes">Lin_k</property>
|
||||
<property name="icon_name">insert-link</property>
|
||||
</object>
|
||||
<accelerator key="l" modifiers="GDK_CONTROL_MASK"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="color">
|
||||
<property name="label" translatable="yes">C_olor</property>
|
||||
<property name="stock_id">gtk-select-color</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="font">
|
||||
<property name="label" translatable="yes">Font</property>
|
||||
<property name="stock_id">gtk-select-font</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="composemenu">
|
||||
<property name="stock_id">gtk-properties</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="indent">
|
||||
<property name="stock_id">gtk-indent</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="outdent">
|
||||
<property name="stock_id">gtk-unindent</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAction" id="removeformat">
|
||||
<property name="label" translatable="yes">Remove formatting</property>
|
||||
<property name="stock_id">gtk-cancel</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkBox" id="composer">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
|
|
@ -192,12 +310,194 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow">
|
||||
<object class="GtkToolbar" id="compose_toolbar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="margin_top">6</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="bold button">
|
||||
<property name="related_action">bold</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Bold</property>
|
||||
<property name="related_action">bold</property>
|
||||
<property name="label" translatable="yes">toolbutton2</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="italic button">
|
||||
<property name="related_action">italic</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Italic</property>
|
||||
<property name="related_action">italic</property>
|
||||
<property name="label" translatable="yes">toolbutton2</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="strikethrough button">
|
||||
<property name="related_action">strikethrough</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Strikethrough</property>
|
||||
<property name="related_action">strikethrough</property>
|
||||
<property name="label" translatable="yes">toolbutton1</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="underline button">
|
||||
<property name="related_action">underline</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Underline</property>
|
||||
<property name="related_action">underline</property>
|
||||
<property name="label" translatable="yes">toolbutton1</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorToolItem" id="toolbutton5">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="indent button">
|
||||
<property name="related_action">indent</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Indent</property>
|
||||
<property name="related_action">indent</property>
|
||||
<property name="label" translatable="yes">toolbutton6</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="outdent1">
|
||||
<property name="related_action">outdent</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Un-indent</property>
|
||||
<property name="related_action">outdent</property>
|
||||
<property name="label" translatable="yes">toolbutton6</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSeparatorToolItem" id="toolbutton3">
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="font button">
|
||||
<property name="related_action">font</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Fonts</property>
|
||||
<property name="related_action">font</property>
|
||||
<property name="label" translatable="yes">toolbutton4</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="color button">
|
||||
<property name="related_action">color</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Color</property>
|
||||
<property name="related_action">color</property>
|
||||
<property name="label" translatable="yes">toolbutton4</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="link button">
|
||||
<property name="related_action">insertlink</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Link</property>
|
||||
<property name="related_action">insertlink</property>
|
||||
<property name="label" translatable="yes">toolbutton4</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton" id="remove format button">
|
||||
<property name="related_action">removeformat</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Remove formatting</property>
|
||||
<property name="related_action">removeformat</property>
|
||||
<property name="label" translatable="yes">toolbutton6</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="message area">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
|
|
@ -205,7 +505,7 @@
|
|||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
|||
27
ui/composer_accelerators.ui
Normal file
27
ui/composer_accelerators.ui
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<ui>
|
||||
<accelerator action="undo" />
|
||||
<accelerator action="redo" />
|
||||
|
||||
<accelerator action="cut" />
|
||||
<accelerator action="copy" />
|
||||
<accelerator action="paste" />
|
||||
|
||||
<accelerator action="bold" />
|
||||
<accelerator action="italic" />
|
||||
<accelerator action="underline" />
|
||||
<accelerator action="strikethrough" />
|
||||
|
||||
<accelerator action="removeformat" />
|
||||
|
||||
<accelerator action="indent" />
|
||||
<accelerator action="outdent" />
|
||||
|
||||
<accelerator action="justifyleft" />
|
||||
<accelerator action="justifyright" />
|
||||
<accelerator action="justifycenter" />
|
||||
<accelerator action="justifyfull" />
|
||||
|
||||
<accelerator action="font" />
|
||||
<accelerator action="color" />
|
||||
<accelerator action="insertlink" />
|
||||
</ui>
|
||||
Loading…
Add table
Add a link
Reference in a new issue