[Xfce4-commits] <postler:master> Render all messages in conversation style
Christian Dywan
noreply at xfce.org
Sun Jun 26 03:20:01 CEST 2011
Updating branch refs/heads/master
to 9829b764ae34a8185c7651b52a3627c2eec1c071 (commit)
from 1482b7ecb0f8f99354579b35207ab50196994efa (commit)
commit 9829b764ae34a8185c7651b52a3627c2eec1c071
Author: Christian Dywan <christian at twotoasts.de>
Date: Sat Jun 25 22:59:43 2011 +0200
Render all messages in conversation style
Display messages in terms of message objects, not files.
Merge parse_message and display_part into display.
Display sender and date above each reply.
Render attachments below each reply.
Accept message/ thread id's on the command line.
postler/postler-content.vala | 366 ++++++++++++++++-------------------------
postler/postler-message.vala | 49 +++++-
postler/postler-messages.vala | 2 +-
postler/postler-reader.vala | 20 ++-
postler/postler-viewer.vala | 21 +--
5 files changed, 206 insertions(+), 252 deletions(-)
diff --git a/postler/postler-content.vala b/postler/postler-content.vala
index 55eb739..81c03c6 100644
--- a/postler/postler-content.vala
+++ b/postler/postler-content.vala
@@ -17,9 +17,6 @@ struct Postler.EmoticonMapping {
public class Postler.Content : WebKit.WebView {
AccountInfo? selected_account;
public Message message { get; private set; }
- string charset;
- string carbon_copy;
- string blind_copy;
string reply;
string? save_folder = null;
@@ -35,8 +32,6 @@ public class Postler.Content : WebKit.WebView {
public unowned List<MessagePart> message_parts { public get; set; }
public MessagePart? current_part { get; private set; }
public int current_part_index { get; private set; }
- public MessagePart? html_part { get; private set; }
- public MessagePart? text_part { get; private set; }
Gtk.Settings gtk_settings;
GLib.Settings glib_settings;
@@ -69,6 +64,9 @@ public class Postler.Content : WebKit.WebView {
background-color: ButtonFace; color: ButtonText;
padding: 1em; margin: 1em;
}
+ .sender {
+ margin-bottom: 0.5em;
+ }
.project_reply .signature {
display: none;
}
@@ -420,7 +418,14 @@ public class Postler.Content : WebKit.WebView {
body.append_c ('\n');
var contents = File.new_for_path (location);
try {
- parse_message(location);
+ last_location = location;
+ message = new Message.from_file (contents);
+ message_id = message.id;
+ subject = message.subject;
+ message.parse_body ();
+ message_parts = message.parts.copy ();
+ notify_property ("message-parts");
+
bool last_line_empty = true;
StringBuilder b_temp = new StringBuilder ();
if (excerpt != null)
@@ -529,9 +534,9 @@ public class Postler.Content : WebKit.WebView {
return g_content_type_is_a (mime_type, "text/plain");
}
- public void display_source (string location) {
- last_location = location;
- subject = _("Source Code: %s").printf (location);
+ public async void display_source (Message message) {
+ last_location = message.get_path ();
+ subject = _("Source Code: %s").printf (last_location);
string line;
string content_type = null;
@@ -539,7 +544,7 @@ public class Postler.Content : WebKit.WebView {
string charset = null;
string[] parts;
- var contents = File.new_for_path (location);
+ var contents = File.new_for_path (last_location);
GLib.StringBuilder body = new GLib.StringBuilder ();
try {
@@ -593,111 +598,134 @@ public class Postler.Content : WebKit.WebView {
return selected_account.address.split (",")[0];
}
- void parse_message (string location) throws GLib.Error {
- last_location = location;
- message = new Message.from_file (File.new_for_path (location));
- message_id = message.id;
- subject = message.subject;
- charset = message.get_charset ();
- string arguments = "?subject=Re: " + subject;
- string? chosen_from = choose_from ();
- if (chosen_from != null)
- arguments += "?from=" + html_escape (chosen_from);
- sender = linkify_address (message.sender ?? _("Unknown"), arguments);
- carbon_copy = message.get_field ("cc");
- if (carbon_copy != null)
- carbon_copy = linkify_address (carbon_copy, arguments);
- blind_copy = message.get_field ("bcc");
- if (blind_copy != null)
- blind_copy = linkify_address (blind_copy, arguments);
- reply = message.reply_to;
- if (reply != null)
- reply = linkify_address (reply, arguments);
- list_post = message.get_field ("list-post");
- if (list_post != null)
- list_post = Postler.Contact.address_from_string (list_post);
- list_unsubscribe = message.get_field ("list-unsubscribe");
- if (list_unsubscribe != null) {
- list_unsubscribe = Postler.Contact.address_from_string (list_unsubscribe);
- list_unsubscribe = linkify_address (list_unsubscribe, null);
- }
-
- message.parse_body ();
- message_parts = message.parts.copy ();
- notify_property ("message-parts");
- }
-
- public async bool display (string location, AccountInfo? account_info=null) {
+ public async bool display (Message the_message, AccountInfo? account_info=null) {
selected_account = account_info;
try {
- parse_message (location);
- var client = new Postler.Client ();
- GLib.List<Message> thread = yield client.get_thread (message);
- if (thread.nth_data (0) != null) {
- text_part = null;
- html_part = new MessagePart ("text/html");
+ GLib.List<Message> thread;
+ if (the_message.stream != null) {
+ thread = new GLib.List<Message> ();
+ thread.append (the_message);
+ }
+ else {
+ var client = new Postler.Client ();
+ thread = yield client.get_thread (the_message);
+ }
+ var body_chunk = new StringBuilder ();
+ foreach (var child in thread) {
+ MessagePart? html_part = null;
+ MessagePart? text_part = null;
+
+ try {
+ if (child.uri == "error:")
+ throw new GLib.FileError.FAILED (child.subject);
+ child.parse_body ();
+ child.parse_text ();
+ html_part = child.html_part;
+ text_part = child.text_part;
+ }
+ catch (GLib.Error parse_error) {
+ html_part = null;
+ text_part = new MessagePart ("text/plain");
+ text_part.body.append (parse_error.message);
+ }
+
string reply_class = "reply";
- if (message.project != null)
+ if (child.project != null)
reply_class = "project_reply";
- foreach (var child in thread) {
- message = new Message.from_file (File.new_for_path (child.get_path ()));
- message.parse_body ();
- foreach (var thread_part in message.parts) {
- if (mime_type_is_text (thread_part.mime_type)) {
- html_part.body.append ("<div class=\"%s\">%s</div>"
- .printf (reply_class,
- render_plain_text (thread_part.body.str)));
- break;
- }
- }
+ body_chunk.append_printf ("""
+ <div class="%s"><div class="sender">%s<div style="float: right;">%s</div></div>
+ """,
+ reply_class, linkify_address (child.sender, null), format_date (child.date));
+ if (html_part != null)
+ body_chunk.append (html_part.body.str);
+ else if (text_part != null) {
+ if (child.project != null)
+ reply_class += " plain_text";
+ body_chunk.append (render_plain_text (text_part.body.str));
}
- message_parts = new List<MessagePart> ();
- message_parts.append (html_part);
- notify_property ("message-parts");
- display_part (html_part);
- return false;
+ else
+ body_chunk.append (text_part.body.str);
+
+ foreach (var attachment_part in child.parts) {
+ if (attachment_part != html_part && attachment_part != text_part
+ && !attachment_part.mime_type.has_prefix ("multipart/"))
+ body_chunk.append_printf ("""<p>
+ <b>%s</b>
+ <a href="message-part:open:%d">%s</a>
+ <a href="message-part:save:%d">%s</a></p>
+ """,
+ attachment_part.filename ?? attachment_part.mime_type,
+ child.parts.position (child.parts.find (attachment_part)),
+ _("Open file"),
+ child.parts.position (child.parts.find (attachment_part)),
+ _("Save As..."));
+ }
+
+ body_chunk.append ("</div>");
}
- /* Look for an HTML part, or otherwise plain text */
- html_part = null;
- text_part = null;
- foreach (var part in message_parts) {
- /* Ignore empty parts inserted by faulty clients */
- if (part.body.str.strip () == "")
- continue;
- /* Select part, merge "inline" parts, commonly signatures */
- if (part.mime_type == "text/html") {
- if (html_part == null)
- html_part = part;
- else if (part.content_disposition == "inline") {
- html_part.body.append (part.body.str);
- message_parts.remove (part);
- }
- }
- if (part.mime_type == "text/plain") {
- if (html_part != null && part.content_disposition == "inline") {
- html_part.body.append (part.body.str);
- message_parts.remove (part);
- }
- if (text_part == null)
- text_part = part;
- else if (part.content_disposition == "inline") {
- text_part.body.append (part.body.str);
- message_parts.remove (part);
- }
- }
+ message = thread.nth_data (0);
+ message_parts = message.parts.copy ();
+ last_location = message.get_path ();
+ subject = message.subject;
+ string arguments = "?subject=Re: " + subject;
+ string? chosen_from = choose_from ();
+ if (chosen_from != null)
+ arguments += "?from=" + html_escape (chosen_from);
+ list_post = message.get_field ("list-post");
+ if (list_post != null)
+ list_post = Postler.Contact.address_from_string (list_post);
+ list_unsubscribe = message.get_field ("list-unsubscribe");
+ if (list_unsubscribe != null) {
+ list_unsubscribe = Postler.Contact.address_from_string (list_unsubscribe);
+ list_unsubscribe = linkify_address (list_unsubscribe, null);
}
notify_property ("message-parts");
- if (html_part != null)
- display_part (html_part);
- else if (text_part != null)
- display_part (text_part);
- else {
- text_part = message_parts.nth_data (0);
- display_part (text_part);
+
+ /* Emoticons */
+ /* foreach (var emoticon in emoticons) { */
+ for (int i = 0; i < emoticons.length; i++) {
+ var emoticon = emoticons[i];
+ try {
+ var escaped = GLib.Regex.escape_string (" " + emoticon.token);
+ var regex = new GLib.Regex (escaped);
+ body_chunk.str = regex.replace_eval (body_chunk.str, -1, 0, 0,
+ (match_info, result) => { evaluate_emoticon (match_info, result, emoticon); });
+ }
+ catch (GLib.RegexError regex_error) { }
}
+
+ load_string (
+ """
+ <span class="headers">
+ <b>%s</b><br>
+ %s
+ <span style="float: right;"><a href="#more" id="more">%s</a></span>
+ <span id="extra_headers">
+ %s
+ %s
+ %s
+ %s
+ </span>
+ </span>
+ <style text="text/css">%s</style><p class="body">%s</p>
+ """
+ .printf (
+ message.subject,
+ /* FIXME: Merge all recipients here? */
+ linkify_address (message.recipients, arguments),
+ reply != null || message.organization != null
+ || message.application != null
+ || list_unsubscribe != null
+ ? "%s »".printf (_("More")) : "",
+ format_header (_("Reply To:"), reply),
+ format_header (_("Organization:"), message.organization),
+ format_header (_("Application:"), message.application),
+ format_header (_("Unsubscribe:"), list_unsubscribe),
+ themed_style_sheet (), body_chunk.str),
+ "text/html", "UTF-8", "about:blank");
} catch (GLib.Error error) {
- display_error (error.message);
+ display_error (error.message);
}
return false;
}
@@ -754,6 +782,17 @@ public class Postler.Content : WebKit.WebView {
line = line.substring (1);
new_body.append (line + "<br>");
}
+
+ /* Linkify */
+ try {
+ foreach (var link_format in link_formats) {
+ var regex = new GLib.Regex (link_format);
+ new_body.str = regex.replace (new_body.str, -1, 0,
+ "<a href=\"\\1\">\\1</a>");
+ }
+ }
+ catch (GLib.RegexError regex_error) { }
+
return new_body.str;
}
@@ -777,115 +816,6 @@ public class Postler.Content : WebKit.WebView {
"Link", color_to_rgb (*((Gdk.Color*)link_color)));
}
- public void display_part (MessagePart message_part) {
- if (current_part != message_part) {
- current_part = message_part;
- current_part_index = message_parts.index (message_part);
- }
-
- string body_chunk;
- string? html_or_text = "";
- if (message_part == html_part) {
- body_chunk = message_part.body.str;
- if (text_part != null)
- html_or_text = "<a href=\"#text\">" + _("View as plain text") + "</a>";
- }
- else if (message_part == text_part) {
- body_chunk = render_plain_text (message_part.body.str);
- if (html_part != null)
- html_or_text = "<a href=\"#html\">" + _("View as HTML") + "</a>";
- if (message.project != null)
- body_chunk = "<p><span class=\"plain_text\">%s</span>".
- printf (body_chunk);
- }
- else {
- body_chunk = """
- <b>%s</b>
- <a href="message-part:open:%d">%s</a>
- <a href="message-part:save:%d">%s</a>
- """.
- printf (
- message_part.filename ?? message_part.mime_type,
- message_parts.position (message_parts.find (message_part)),
- _("Open file"),
- message_parts.position (message_parts.find (message_part)),
- _("Save As..."));
- if (mime_type_is_text (message_part.mime_type))
- body_chunk += "<p><span class=\"plain_text\">%s</span>".
- printf (message_part.body.str);
- else if (message_part.mime_type.has_prefix ("image/"))
- body_chunk += "<p><img src=\"data:image/jpg;base64,%s\">".
- printf (message_part.body.str);
- }
-
- try {
- /* Linkify */
- if (message_part.plain_text) {
- foreach (var link_format in link_formats) {
- var regex = new GLib.Regex (link_format);
- body_chunk = regex.replace (body_chunk, -1, 0,
- "<a href=\"\\1\">\\1</a>");
- }
- }
-
- /* Emoticons */
- /* foreach (var emoticon in emoticons) { */
- for (int i = 0; i < emoticons.length; i++) {
- var emoticon = emoticons[i];
- try {
- var escaped = GLib.Regex.escape_string (" " + emoticon.token);
- var regex = new GLib.Regex (escaped);
- body_chunk = regex.replace_eval (body_chunk, -1, 0, 0,
- (match_info, result) => { evaluate_emoticon (match_info, result, emoticon); });
- }
- catch (GLib.RegexError error) { }
- }
-
- load_string ("""
- <style text="text/css">%s</style>
- <span class="headers">
- <div style="float: right;">%s</div>
- <b>%s</b> %s<br>
- %s
- %s
- %s <span style="float: right;"><a href="#more" id="more">%s</a></span>
- <b>%s</b> %s <br>
- <span id="extra_headers">
- %s
- %s
- %s
- %s
- %s
- </span>
- </span>
- <p class="body">%s</p>
- """.
- printf (themed_style_sheet (),
- format_date (message.date),
- _("From:"), sender,
- format_header (_("To:"), message.get_field ("to")),
- format_header (_("Copy:"), carbon_copy),
- format_header (_("Blind Copy:"), blind_copy),
- reply != null || message.organization != null
- || message.application != null
- || list_unsubscribe != null
- || html_or_text != ""
- ? "%s »".printf (_("More")) : "",
- _("Subject:"), subject,
- /* TODO: Sender:? */
- format_header (_("Reply To:"), reply),
- format_header (_("Organization:"), message.organization),
- format_header (_("Application:"), message.application),
- format_header (_("Unsubscribe:"), list_unsubscribe),
- html_or_text,
- body_chunk),
- "text/html", "UTF-8", "about:blank");
- } catch (GLib.Error contents_error) {
- display_error (_("Failed to display message part \"%s\": %s").printf (
- message_part.mime_type, contents_error.message));
- }
- }
-
void display_error (string message) {
load_string ("""
<style text="text/css">%s</style>
@@ -919,16 +849,6 @@ public class Postler.Content : WebKit.WebView {
settings.set ("enable-scripts", false);
return true;
}
- else if (uri.has_suffix ("#html")) {
- decision.ignore ();
- display_part (html_part);
- return true;
- }
- else if (uri.has_suffix ("#text")) {
- decision.ignore ();
- display_part (text_part);
- return true;
- }
if (uri != null && uri.has_prefix ("about:"))
return false;
diff --git a/postler/postler-message.vala b/postler/postler-message.vala
index 9c6ae4e..83a3324 100644
--- a/postler/postler-message.vala
+++ b/postler/postler-message.vala
@@ -107,7 +107,9 @@ namespace Postler {
GLib.HashTable<string,string> fields = new GLib.HashTable<string,string> (str_hash, str_equal);
public string get_field (string field) { return fields.lookup (field); }
public GLib.List<MessagePart>? parts = null;
- GLib.DataInputStream? stream = null;
+ public GLib.DataInputStream? stream = null;
+ public MessagePart? html_part = null;
+ public MessagePart? text_part = null;
bool init (GLib.Cancellable? cancellable = null) throws GLib.Error {
return false;
@@ -214,6 +216,11 @@ namespace Postler {
public Message.from_file (GLib.File file,
GLib.Cancellable? cancellable = null) throws GLib.Error {
+ read_from_file (file, cancellable);
+ }
+
+ void read_from_file (GLib.File file,
+ GLib.Cancellable? cancellable = null) throws GLib.Error {
uri = file.get_uri ();
read_flags (file.get_path ());
@@ -401,8 +408,11 @@ namespace Postler {
return unquoted;
}
- public void parse_body () throws GLib.Error
- requires (stream != null) {
+ public void parse_body (GLib.Cancellable? cancellable=null) throws GLib.Error {
+ if (stream == null) {
+ return_if_fail (uri != null);
+ read_from_file (GLib.File.new_for_uri (uri), cancellable);
+ }
parts = new GLib.List<MessagePart> ();
string? content_encoding = get_field ("content-transfer-encoding");
@@ -532,6 +542,39 @@ namespace Postler {
}
}
}
+
+ public void parse_text (GLib.Cancellable? cancellable=null) throws GLib.Error {
+ if (parts == null)
+ parse_body (cancellable);
+
+ var child_parts = new List<MessagePart> ();
+ foreach (var thread_part in parts) {
+ /* Ignore empty parts inserted by faulty clients */
+ if (thread_part.body.str.strip () == "")
+ continue;
+ /* Select part, merge "inline" parts, commonly signatures */
+ if (thread_part.mime_type == "text/html") {
+ if (html_part == null)
+ html_part = thread_part;
+ else if (thread_part.content_disposition == "inline") {
+ html_part.body.append (thread_part.body.str);
+ child_parts.remove (thread_part);
+ }
+ }
+ else if (thread_part.mime_type == "text/plain") {
+ if (html_part != null && thread_part.content_disposition == "inline") {
+ html_part.body.append (thread_part.body.str);
+ child_parts.remove (thread_part);
+ }
+ if (text_part == null)
+ text_part = thread_part;
+ else if (thread_part.content_disposition == "inline") {
+ text_part.body.append (thread_part.body.str);
+ child_parts.remove (thread_part);
+ }
+ }
+ }
+ }
}
}
diff --git a/postler/postler-messages.vala b/postler/postler-messages.vala
index b25890f..0a4e702 100644
--- a/postler/postler-messages.vala
+++ b/postler/postler-messages.vala
@@ -595,7 +595,7 @@ public class Postler.Messages : Gtk.TreeView {
void display_message (Gtk.TreeIter iter) {
toggle_message_flag (iter, MessageFlags.MARK_READ, (message, result) => {
- content.display ((message as Message).get_path (), account_info);
+ content.display (message as Message, account_info);
});
}
diff --git a/postler/postler-reader.vala b/postler/postler-reader.vala
index 8ac4a38..d9736a9 100644
--- a/postler/postler-reader.vala
+++ b/postler/postler-reader.vala
@@ -145,17 +145,25 @@ public class Postler.Reader {
shelf.add (new Postler.Viewer (content));
if (filenames != null && filenames[0] != null) {
- string filename;
+ Message message;
try {
- filename = Filename.from_uri (filenames[0], null);
+ string filename = filenames[0];
+ if (filename.has_prefix ("file://"))
+ message = new Message.from_file (GLib.File.new_for_uri (filename));
+ else if (FileUtils.test (filename, FileTest.EXISTS))
+ message = new Message.from_file (GLib.File.new_for_path (filename));
+ else
+ message = new Message.from_id (filename);
} catch (GLib.Error error) {
- filename = filenames[0];
+ GLib.error (error.message);
}
+ content.notify["subject"].connect ((object, pspec) => {
+ window.set_title (content.subject);
+ });
if (module == "source")
- content.display_source (filename);
+ content.display_source (message);
else
- content.display (filename);
- window.set_title (content.subject);
+ content.display (message);
}
shelf.show_all ();
diff --git a/postler/postler-viewer.vala b/postler/postler-viewer.vala
index 1decf0d..65fe1c7 100644
--- a/postler/postler-viewer.vala
+++ b/postler/postler-viewer.vala
@@ -84,29 +84,12 @@ public class Postler.Viewer : Gtk.VBox {
void infobar_response (Gtk.InfoBar infobar, int response) {
infobar.hide ();
allow_external_images = true;
- content.display_part (content.current_part);
+ content.display (content.message);
}
void notify_message_parts (GLib.Object object, GLib.ParamSpec? pspec) {
infobar.hide ();
allow_external_images = false;
-
- if (content.message_parts.length () < 2) {
- attachments.hide ();
- return;
- }
-
- (attachments.model as Gtk.ListStore).clear ();
- foreach (var part in content.message_parts) {
- if (part.mime_type.has_prefix ("multipart/"))
- continue;
- if (part == content.html_part || part == content.text_part)
- continue;
-
- attachments.add_part (part);
- if (part == content.current_part)
- attachments.select (content.current_part);
- }
}
void notify_current_part () {
@@ -116,7 +99,7 @@ public class Postler.Viewer : Gtk.VBox {
void part_selected () {
MessagePart? part = attachments.get_selected_part ();
if (part != null)
- content.display_part (part);
+ content.display (content.message);
}
bool key_pressed (Gdk.EventKey event) {
More information about the Xfce4-commits
mailing list