Re-write of cell renderer to fix markup issues. Closes #4242

This commit is contained in:
Eric Gregory 2011-11-14 14:47:03 -08:00
parent f5b7d29a8c
commit ab9c3f01a4
2 changed files with 147 additions and 95 deletions

View file

@ -7,11 +7,23 @@
// Stores formatted data for a message.
public class FormattedMessageData : Object {
private const string STYLE_EXAMPLE = "Gg"; // Use both upper and lower case to get max height.
private const int LINE_SPACING = 4;
private const int UNREAD_ICON_SIZE = 12;
private const int TEXT_LEFT = LINE_SPACING * 2 + UNREAD_ICON_SIZE;
private const int FONT_SIZE_DATE = 11;
private const int FONT_SIZE_SUBJECT = 9;
private const int FONT_SIZE_FROM = 11;
private const int FONT_SIZE_PREVIEW = 8;
private static Gdk.Pixbuf? unread_pixbuf = null;
private static int cell_height = -1;
private static int preview_height = -1;
public Geary.Email email { get; private set; default = null; }
public bool is_unread { get; private set; default = false; }
public string date { get; private set; default = ""; }
public string from { get; private set; default = ""; }
public string from { get; private set; default = ""; }
public string subject { get; private set; default = ""; }
public string? body { get; private set; default = null; } // optional
public int num_emails { get; private set; default = 1; }
@ -19,11 +31,10 @@ public class FormattedMessageData : Object {
private FormattedMessageData(bool is_unread, string date, string from, string subject,
string preview, int num_emails) {
this.is_unread = is_unread;
this.date = "<span foreground='blue'>%s</span>".printf(Geary.String.escape_markup(date));
this.from = "<b>%s</b>".printf(Geary.String.escape_markup(from));
this.subject = "<small>%s</small>".printf(Geary.String.escape_markup(subject));
this.body = "<span size='x-small' foreground='#777777'>%s</span>".printf(
Geary.String.escape_markup(preview));
this.date = date;
this.from = from;
this.subject = subject;
this.body = preview;
this.num_emails = num_emails;
}
@ -45,50 +56,31 @@ public class FormattedMessageData : Object {
string from = (email.from != null && email.from.size > 0) ? email.from[0].get_short_address() : "";
this(email.properties.is_unread(), Date.pretty_print(email.date.value),
from, email.subject.value, Geary.String.escape_markup(make_preview(builder.str)), num_emails);
from, email.subject.value, Geary.String.reduce_whitespace(builder.str), num_emails);
this.email = email;
}
// Creates an example message (used interally for styling calculations.)
public FormattedMessageData.create_example() {
this(false, STYLE_EXAMPLE, STYLE_EXAMPLE, STYLE_EXAMPLE, STYLE_EXAMPLE + "\n" + STYLE_EXAMPLE, 1);
this(false, STYLE_EXAMPLE, STYLE_EXAMPLE, STYLE_EXAMPLE, STYLE_EXAMPLE + "\n" +
STYLE_EXAMPLE, 1);
}
// Distills an e-mail body into a preview by removing extra spaces, html, etc.
private static string make_preview(string body) {
// Remove newlines and tabs.
string preview = body.replace("\n", " ").replace("\r", " ").replace("\t", " ");
// Remove HTML
// TODO: needs to strip tags, special chars
// Remove extra space and return.
// TODO: remove redundant spaces within string
return preview.strip();
}
}
public class MessageListCellRenderer : Gtk.CellRenderer {
private const int LINE_SPACING = 4;
private const int UNREAD_ICON_SIZE = 12;
private const int TEXT_LEFT = LINE_SPACING * 2 + UNREAD_ICON_SIZE;
private static int cell_height = -1;
private static int preview_height = -1;
private static FormattedMessageData? example_data = null;
private static Gdk.Pixbuf? unread_pixbuf = null;
// Mail message data.
public FormattedMessageData data {get; set;}
public MessageListCellRenderer() {
public void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area,
Gdk.Rectangle cell_area, Gtk.CellRendererState flags) {
render_internal(widget, cell_area, ctx);
}
public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset,
// Call this on style changes.
public void calculate_sizes(Gtk.Widget widget) {
render_internal(widget, null, null, true);
}
// Must call calculate_sizes() first.
public void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset,
out int y_offset, out int width, out int height) {
if (cell_height == -1 || preview_height == -1 || example_data == null)
style_changed(widget);
assert(cell_height != -1); // ensures calculate_sizes() was called.
x_offset = 0;
y_offset = 0;
@ -96,78 +88,120 @@ public class MessageListCellRenderer : Gtk.CellRenderer {
height = cell_height;
}
public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area,
Gdk.Rectangle cell_area, Gtk.CellRendererState flags) {
if (data == null)
return;
// Can be used for rendering or calculating height.
private void render_internal(Gtk.Widget widget, Gdk.Rectangle? cell_area = null,
Cairo.Context? ctx = null, bool recalc_dims = false) {
Pango.Rectangle? ink_rect;
Pango.Rectangle? logical_rect;
int y = LINE_SPACING + cell_area.y;
int y = LINE_SPACING + (cell_area != null ? cell_area.y : 0);
// Date field.
Pango.FontDescription font_date = new Pango.FontDescription();
font_date.set_size(FONT_SIZE_DATE * Pango.SCALE);
Pango.AttrList list_date = new Pango.AttrList();
list_date.insert(Pango.attr_foreground_new(0, 0, 65535));
Pango.Layout layout_date = widget.create_pango_layout(null);
layout_date.set_markup(data.date, -1);
layout_date.set_font_description(font_date);
layout_date.set_attributes(list_date);
layout_date.set_text(date, -1);
layout_date.set_alignment(Pango.Alignment.RIGHT);
layout_date.get_pixel_extents(out ink_rect, out logical_rect);
ctx.move_to(cell_area.width - cell_area.x - ink_rect.width - ink_rect.x - LINE_SPACING, y);
Pango.cairo_show_layout(ctx, layout_date);
if (ctx != null && cell_area != null) {
ctx.move_to(cell_area.width - cell_area.x - ink_rect.width - ink_rect.x - LINE_SPACING, y);
Pango.cairo_show_layout(ctx, layout_date);
}
// From field.
Pango.FontDescription font_from = new Pango.FontDescription();
font_from.set_size(FONT_SIZE_FROM * Pango.SCALE);
font_from.set_weight(Pango.Weight.BOLD);
Pango.Layout layout_from = widget.create_pango_layout(null);
layout_from.set_markup(data.from, -1);
layout_from.set_width((cell_area.width - ink_rect.width - ink_rect.x - LINE_SPACING - TEXT_LEFT)
* Pango.SCALE);
layout_from.set_font_description(font_from);
layout_from.set_text(from, -1);
layout_from.set_ellipsize(Pango.EllipsizeMode.END);
ctx.move_to(cell_area.x + TEXT_LEFT, y);
Pango.cairo_show_layout(ctx, layout_from);
if (ctx != null && cell_area != null) {
layout_from.set_width((cell_area.width - ink_rect.width - ink_rect.x - LINE_SPACING -
TEXT_LEFT)
* Pango.SCALE);
ctx.move_to(cell_area.x + TEXT_LEFT, y);
Pango.cairo_show_layout(ctx, layout_from);
}
y += ink_rect.height + ink_rect.y + LINE_SPACING;
// Subject field.
Pango.FontDescription font_subject = new Pango.FontDescription();
font_subject.set_size(FONT_SIZE_SUBJECT * Pango.SCALE);
Pango.Layout layout_subject = widget.create_pango_layout(null);
layout_subject.set_markup(data.subject, -1);
layout_subject.set_width((cell_area.width - TEXT_LEFT) * Pango.SCALE);
layout_subject.set_font_description(font_subject);
layout_subject.set_text(subject, -1);
if (cell_area != null)
layout_subject.set_width((cell_area.width - TEXT_LEFT) * Pango.SCALE);
layout_subject.set_ellipsize(Pango.EllipsizeMode.END);
layout_date.get_pixel_extents(out ink_rect, out logical_rect);
ctx.move_to(cell_area.x + TEXT_LEFT, y);
Pango.cairo_show_layout(ctx, layout_subject);
if (ctx != null && cell_area != null) {
ctx.move_to(cell_area.x + TEXT_LEFT, y);
Pango.cairo_show_layout(ctx, layout_subject);
}
y += ink_rect.height + ink_rect.y + LINE_SPACING;
// Number of e-mails field.
int num_email_width = 0;
if (data.num_emails > 1) {
if (num_emails > 1) {
Pango.Rectangle? num_ink_rect;
Pango.Rectangle? num_logical_rect;
string mails =
"<span background='#999999' foreground='white' size='x-small' weight='bold'> %d </span>"
.printf(data.num_emails);
.printf(num_emails);
Pango.Layout layout_num = widget.create_pango_layout(null);
layout_num.set_markup(mails, -1);
layout_num.set_alignment(Pango.Alignment.RIGHT);
layout_num.get_pixel_extents(out num_ink_rect, out num_logical_rect);
ctx.move_to(cell_area.width - cell_area.x - num_ink_rect.width - num_ink_rect.x -
LINE_SPACING, y);
Pango.cairo_show_layout(ctx, layout_num);
if (ctx != null && cell_area != null) {
ctx.move_to(cell_area.width - cell_area.x - num_ink_rect.width - num_ink_rect.x -
LINE_SPACING, y);
Pango.cairo_show_layout(ctx, layout_num);
}
num_email_width = num_ink_rect.width + (LINE_SPACING * 3);
}
// Body preview.
Pango.FontDescription font_preview = new Pango.FontDescription();
font_preview.set_size(FONT_SIZE_PREVIEW * Pango.SCALE);
Pango.AttrList list_preview = new Pango.AttrList();
list_preview.insert(Pango.attr_foreground_new(28671, 28671, 28671)); // #777
Pango.Layout layout_preview = widget.create_pango_layout(null);
layout_preview.set_markup(data.body, -1);
layout_preview.set_width((cell_area.width - TEXT_LEFT - num_email_width) * Pango.SCALE);
layout_preview.set_height(preview_height * Pango.SCALE);
layout_preview.set_font_description(font_preview);
layout_preview.set_attributes(list_preview);
layout_preview.set_text(body != null ? body : "\n\n", -1);
layout_preview.set_wrap(Pango.WrapMode.WORD);
layout_preview.set_ellipsize(Pango.EllipsizeMode.END);
ctx.move_to(cell_area.x + TEXT_LEFT, y);
Pango.cairo_show_layout(ctx, layout_preview);
if (ctx != null && cell_area != null) {
layout_preview.set_width((cell_area.width - TEXT_LEFT - num_email_width) * Pango.SCALE);
layout_preview.set_height(preview_height * Pango.SCALE);
ctx.move_to(cell_area.x + TEXT_LEFT, y);
Pango.cairo_show_layout(ctx, layout_preview);
} else {
layout_preview.set_width(int.MAX);
layout_preview.set_height(int.MAX);
}
if (recalc_dims) {
layout_preview.get_pixel_extents(out ink_rect, out logical_rect);
preview_height = ink_rect.height + ink_rect.y + LINE_SPACING;
cell_height = y + preview_height;
}
// Unread indicator.
if (data.is_unread) {
if (is_unread) {
if (unread_pixbuf == null) {
try {
unread_pixbuf = Gtk.IconTheme.get_default().load_icon(Gtk.Stock.YES,
@ -182,42 +216,41 @@ public class MessageListCellRenderer : Gtk.CellRenderer {
ctx.paint();
}
}
}
public class MessageListCellRenderer : Gtk.CellRenderer {
private static FormattedMessageData? example_data = null;
// Mail message data.
public FormattedMessageData data { get; set; }
public MessageListCellRenderer() {
}
public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset,
out int y_offset, out int width, out int height) {
if (example_data == null)
style_changed(widget);
example_data.get_size(widget, cell_area, out x_offset, out y_offset, out width, out height);
}
public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area,
Gdk.Rectangle cell_area, Gtk.CellRendererState flags) {
if (data == null)
return;
data.render(ctx, widget, background_area, cell_area, flags);
}
// Recalculates size when the style changed.
// Note: this must be called by the parent TreeView.
public static void style_changed(Gtk.Widget widget) {
Pango.Rectangle? ink_rect;
Pango.Rectangle? logical_rect;
Pango.Layout layout;
if (example_data == null) {
example_data = new FormattedMessageData.create_example();
}
cell_height = LINE_SPACING;
// Date
layout = widget.create_pango_layout(null);
layout.set_markup(example_data.date, -1);
layout.get_pixel_extents(out ink_rect, out logical_rect);
cell_height += ink_rect.height + ink_rect.y + LINE_SPACING;
// Subject
layout = widget.create_pango_layout(null);
layout.set_markup(example_data.subject, -1);
layout.get_pixel_extents(out ink_rect, out logical_rect);
cell_height += ink_rect.height + ink_rect.y + LINE_SPACING;
// Body preview
layout = widget.create_pango_layout(null);
layout.set_markup(example_data.body, -1);
layout.set_width(int.MAX);
layout.set_height(int.MAX);
layout.set_wrap(Pango.WrapMode.WORD);
layout.set_ellipsize(Pango.EllipsizeMode.END);
layout.get_pixel_extents(out ink_rect, out logical_rect);
preview_height = ink_rect.height + ink_rect.y + LINE_SPACING;
cell_height += preview_height;
example_data.calculate_sizes(widget);
}
}

View file

@ -62,5 +62,24 @@ public string uint8_to_hex(uint8[] buffer) {
return builder.str;
}
// Removes redundant spaces, tabs, and newlines.
public string reduce_whitespace(string _s) {
string s = _s;
s = s.replace("\n", " ");
s = s.replace("\r", " ");
s = s.replace("\t", " ");
s = s.strip();
// Condense multiple spaces to one.
for (int i = 1; i < s.length; i++) {
if (s.get_char(i) == ' ' && s.get_char(i - 1) == ' ') {
s = s.slice(0, i - 1) + s.slice(i, s.length);
i--;
}
}
return s;
}
}