Re-write of cell renderer to fix markup issues. Closes #4242
This commit is contained in:
parent
f5b7d29a8c
commit
ab9c3f01a4
2 changed files with 147 additions and 95 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue