Display participants in subtitle of conversation header
This commit is contained in:
parent
fca3225d22
commit
ef87b0dbc3
3 changed files with 143 additions and 101 deletions
|
|
@ -11,6 +11,7 @@ public class MainToolbar : Gtk.Box {
|
||||||
public string account { get; set; }
|
public string account { get; set; }
|
||||||
public string folder { get; set; }
|
public string folder { get; set; }
|
||||||
public string conversation_title { get; private set; }
|
public string conversation_title { get; private set; }
|
||||||
|
public string conversation_participants { get; private set; }
|
||||||
public bool show_close_button { get; set; default = false; }
|
public bool show_close_button { get; set; default = false; }
|
||||||
public bool show_close_button_left { get; private set; }
|
public bool show_close_button_left { get; private set; }
|
||||||
public bool show_close_button_right { get; private set; }
|
public bool show_close_button_right { get; private set; }
|
||||||
|
|
@ -40,6 +41,8 @@ public class MainToolbar : Gtk.Box {
|
||||||
BindingFlags.SYNC_CREATE);
|
BindingFlags.SYNC_CREATE);
|
||||||
this.bind_property("conversation-title", conversation_header, "title",
|
this.bind_property("conversation-title", conversation_header, "title",
|
||||||
BindingFlags.SYNC_CREATE);
|
BindingFlags.SYNC_CREATE);
|
||||||
|
this.bind_property("conversation-participants", conversation_header, "subtitle",
|
||||||
|
BindingFlags.SYNC_CREATE);
|
||||||
this.bind_property("show-close-button-right", conversation_header, "show-close-button",
|
this.bind_property("show-close-button-right", conversation_header, "show-close-button",
|
||||||
BindingFlags.SYNC_CREATE);
|
BindingFlags.SYNC_CREATE);
|
||||||
|
|
||||||
|
|
@ -136,6 +139,27 @@ public class MainToolbar : Gtk.Box {
|
||||||
conversation_header.add_end(archive_trash_delete);
|
conversation_header.add_end(archive_trash_delete);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Gtk.Label title_label = new Gtk.Label(null);
|
||||||
|
conversation_header.bind_property("title", title_label, "label", BindingFlags.SYNC_CREATE);
|
||||||
|
conversation_header.bind_property("title", title_label, "tooltip-text",
|
||||||
|
BindingFlags.SYNC_CREATE);
|
||||||
|
title_label.get_style_context().add_class("title");
|
||||||
|
title_label.ellipsize = Pango.EllipsizeMode.END;
|
||||||
|
Gtk.Label subtitle_label = new Gtk.Label(null);
|
||||||
|
conversation_header.bind_property("subtitle", subtitle_label, "label",
|
||||||
|
BindingFlags.SYNC_CREATE);
|
||||||
|
conversation_header.bind_property("subtitle", subtitle_label, "tooltip-text",
|
||||||
|
BindingFlags.SYNC_CREATE);
|
||||||
|
subtitle_label.get_style_context().add_class("subtitle");
|
||||||
|
subtitle_label.get_style_context().add_class("dim-label");
|
||||||
|
subtitle_label.ellipsize = Pango.EllipsizeMode.END;
|
||||||
|
Gtk.Box title_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
|
||||||
|
title_box.pack_start(title_label);
|
||||||
|
title_box.pack_start(subtitle_label);
|
||||||
|
title_box.set_margin_left(6);
|
||||||
|
title_box.set_margin_right(6);
|
||||||
|
conversation_header.set_custom_title(title_box);
|
||||||
|
|
||||||
pack_start(folder_header, false, false);
|
pack_start(folder_header, false, false);
|
||||||
pack_start(new Gtk.Separator(Gtk.Orientation.VERTICAL), false, false);
|
pack_start(new Gtk.Separator(Gtk.Orientation.VERTICAL), false, false);
|
||||||
pack_start(conversation_header, true, true);
|
pack_start(conversation_header, true, true);
|
||||||
|
|
@ -192,16 +216,23 @@ public class MainToolbar : Gtk.Box {
|
||||||
int selected_count = conversations.size;
|
int selected_count = conversations.size;
|
||||||
if (selected_count == 0) {
|
if (selected_count == 0) {
|
||||||
conversation_title = _("No conversations selected");
|
conversation_title = _("No conversations selected");
|
||||||
|
conversation_participants = "";
|
||||||
} else if (selected_count == 1) {
|
} else if (selected_count == 1) {
|
||||||
Geary.Email? last_email = conversations.to_array()[0].get_latest_recv_email(
|
Geary.App.Conversation conversation = conversations.to_array()[0];
|
||||||
|
Geary.Email? last_email = conversation.get_latest_recv_email(
|
||||||
Geary.App.Conversation.Location.ANYWHERE);
|
Geary.App.Conversation.Location.ANYWHERE);
|
||||||
if (last_email != null)
|
if (last_email != null)
|
||||||
conversation_title = EmailUtil.strip_subject_prefixes(last_email);
|
conversation_title = EmailUtil.strip_subject_prefixes(last_email);
|
||||||
else
|
else
|
||||||
conversation_title = "";
|
conversation_title = "";
|
||||||
|
|
||||||
|
conversation_participants = EmailUtil.get_participants(conversation,
|
||||||
|
current_folder.account.information.get_all_mailboxes(),
|
||||||
|
current_folder.special_folder_type.is_outgoing(), false);
|
||||||
} else {
|
} else {
|
||||||
conversation_title = ngettext("%u conversation selected", "%u conversations selected",
|
conversation_title = ngettext("%u conversation selected", "%u conversations selected",
|
||||||
selected_count).printf(selected_count);
|
selected_count).printf(selected_count);
|
||||||
|
conversation_participants = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@
|
||||||
public class FormattedConversationData : Geary.BaseObject {
|
public class FormattedConversationData : Geary.BaseObject {
|
||||||
public const int LINE_SPACING = 6;
|
public const int LINE_SPACING = 6;
|
||||||
|
|
||||||
private const string ME = _("Me");
|
|
||||||
private const string STYLE_EXAMPLE = "Gg"; // Use both upper and lower case to get max height.
|
private const string STYLE_EXAMPLE = "Gg"; // Use both upper and lower case to get max height.
|
||||||
private const int TEXT_LEFT = LINE_SPACING * 2 + IconFactory.UNREAD_ICON_SIZE;
|
private const int TEXT_LEFT = LINE_SPACING * 2 + IconFactory.UNREAD_ICON_SIZE;
|
||||||
private const double DIM_TEXT_AMOUNT = 0.05;
|
private const double DIM_TEXT_AMOUNT = 0.05;
|
||||||
|
|
@ -19,59 +18,6 @@ public class FormattedConversationData : Geary.BaseObject {
|
||||||
private const int FONT_SIZE_FROM = 11;
|
private const int FONT_SIZE_FROM = 11;
|
||||||
private const int FONT_SIZE_PREVIEW = 8;
|
private const int FONT_SIZE_PREVIEW = 8;
|
||||||
|
|
||||||
private class ParticipantDisplay : Geary.BaseObject, Gee.Hashable<ParticipantDisplay> {
|
|
||||||
public Geary.RFC822.MailboxAddress address;
|
|
||||||
public bool is_unread;
|
|
||||||
|
|
||||||
public ParticipantDisplay(Geary.RFC822.MailboxAddress address, bool is_unread) {
|
|
||||||
this.address = address;
|
|
||||||
this.is_unread = is_unread;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string get_full_markup(Gee.List<Geary.RFC822.MailboxAddress> account_mailboxes) {
|
|
||||||
return get_as_markup((address in account_mailboxes) ? ME : address.get_short_address());
|
|
||||||
}
|
|
||||||
|
|
||||||
public string get_short_markup(Gee.List<Geary.RFC822.MailboxAddress> account_mailboxes) {
|
|
||||||
if (address in account_mailboxes)
|
|
||||||
return get_as_markup(ME);
|
|
||||||
|
|
||||||
string short_address = address.get_short_address().strip();
|
|
||||||
|
|
||||||
if (", " in short_address) {
|
|
||||||
// assume address is in Last, First format
|
|
||||||
string[] tokens = short_address.split(", ", 2);
|
|
||||||
short_address = tokens[1].strip();
|
|
||||||
if (Geary.String.is_empty(short_address))
|
|
||||||
return get_full_markup(account_mailboxes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// use first name as delimited by a space
|
|
||||||
string[] tokens = short_address.split(" ", 2);
|
|
||||||
if (tokens.length < 1)
|
|
||||||
return get_full_markup(account_mailboxes);
|
|
||||||
|
|
||||||
string first_name = tokens[0].strip();
|
|
||||||
if (Geary.String.is_empty_or_whitespace(first_name))
|
|
||||||
return get_full_markup(account_mailboxes);
|
|
||||||
|
|
||||||
return get_as_markup(first_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string get_as_markup(string participant) {
|
|
||||||
return "%s%s%s".printf(
|
|
||||||
is_unread ? "<b>" : "", Geary.HTML.escape_markup(participant), is_unread ? "</b>" : "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool equal_to(ParticipantDisplay other) {
|
|
||||||
return address.equal_to(other.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
public uint hash() {
|
|
||||||
return address.hash();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int cell_height = -1;
|
private static int cell_height = -1;
|
||||||
private static int preview_height = -1;
|
private static int preview_height = -1;
|
||||||
|
|
||||||
|
|
@ -171,52 +117,8 @@ public class FormattedConversationData : Geary.BaseObject {
|
||||||
if (conversation == null || account_owner_emails == null || account_owner_emails.size == 0)
|
if (conversation == null || account_owner_emails == null || account_owner_emails.size == 0)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
// Build chronological list of AuthorDisplay records, setting to unread if any message by
|
return EmailUtil.get_participants(conversation, account_owner_emails, use_to, true,
|
||||||
// that author is unread
|
rgba_to_markup(get_foreground_rgba(widget, selected)));
|
||||||
Gee.ArrayList<ParticipantDisplay> list = new Gee.ArrayList<ParticipantDisplay>();
|
|
||||||
foreach (Geary.Email message in conversation.get_emails(Geary.App.Conversation.Ordering.RECV_DATE_ASCENDING)) {
|
|
||||||
// only display if something to display
|
|
||||||
Geary.RFC822.MailboxAddresses? addresses = use_to ? message.to : message.from;
|
|
||||||
if (addresses == null || addresses.size < 1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
foreach (Geary.RFC822.MailboxAddress address in addresses) {
|
|
||||||
ParticipantDisplay participant_display = new ParticipantDisplay(address,
|
|
||||||
message.email_flags.is_unread());
|
|
||||||
|
|
||||||
// if not present, add in chronological order
|
|
||||||
int existing_index = list.index_of(participant_display);
|
|
||||||
if (existing_index < 0) {
|
|
||||||
list.add(participant_display);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if present and this message is unread but the prior were read,
|
|
||||||
// this author is now unread
|
|
||||||
if (message.email_flags.is_unread() && !list[existing_index].is_unread)
|
|
||||||
list[existing_index].is_unread = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder("<span foreground='%s'>".printf(
|
|
||||||
rgba_to_markup(get_foreground_rgba(widget, selected))));
|
|
||||||
if (list.size == 1) {
|
|
||||||
// if only one participant, use full name
|
|
||||||
builder.append(list[0].get_full_markup(account_owner_emails));
|
|
||||||
} else {
|
|
||||||
bool first = true;
|
|
||||||
foreach (ParticipantDisplay participant in list) {
|
|
||||||
if (!first)
|
|
||||||
builder.append(", ");
|
|
||||||
|
|
||||||
builder.append(participant.get_short_markup(account_owner_emails));
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder.append("</span>");
|
|
||||||
|
|
||||||
return builder.str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area,
|
public void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area,
|
||||||
|
|
|
||||||
|
|
@ -30,5 +30,114 @@ public string strip_subject_prefixes(Geary.Email email) {
|
||||||
return !Geary.String.is_empty(cleaned) ? cleaned : _("(no subject)");
|
return !Geary.String.is_empty(cleaned) ? cleaned : _("(no subject)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string get_participants(Geary.App.Conversation conversation,
|
||||||
|
Gee.List<Geary.RFC822.MailboxAddress> account_owner_emails, bool use_to, bool markup,
|
||||||
|
string? foreground = null) {
|
||||||
|
|
||||||
|
// Build chronological list of AuthorDisplay records, setting to unread if any message by
|
||||||
|
// that author is unread
|
||||||
|
Gee.ArrayList<ParticipantDisplay> list = new Gee.ArrayList<ParticipantDisplay>();
|
||||||
|
foreach (Geary.Email message in conversation.get_emails(
|
||||||
|
Geary.App.Conversation.Ordering.RECV_DATE_ASCENDING)) {
|
||||||
|
// only display if something to display
|
||||||
|
Geary.RFC822.MailboxAddresses? addresses = use_to ? message.to : message.from;
|
||||||
|
if (addresses == null || addresses.size < 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (Geary.RFC822.MailboxAddress address in addresses) {
|
||||||
|
ParticipantDisplay participant_display = new ParticipantDisplay(address,
|
||||||
|
markup && message.email_flags.is_unread());
|
||||||
|
|
||||||
|
// if not present, add in chronological order
|
||||||
|
int existing_index = list.index_of(participant_display);
|
||||||
|
if (existing_index < 0) {
|
||||||
|
list.add(participant_display);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if present and this message is unread but the prior were read,
|
||||||
|
// this author is now unread
|
||||||
|
if (message.email_flags.is_unread() && !list[existing_index].is_unread)
|
||||||
|
list[existing_index].is_unread = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder(markup ? @"<span foreground='$foreground'>" : "");
|
||||||
|
if (list.size == 1) {
|
||||||
|
// if only one participant, use full name
|
||||||
|
builder.append(list[0].get_full(account_owner_emails, markup));
|
||||||
|
} else {
|
||||||
|
bool first = true;
|
||||||
|
foreach (ParticipantDisplay participant in list) {
|
||||||
|
if (!first)
|
||||||
|
builder.append(", ");
|
||||||
|
|
||||||
|
builder.append(participant.get_short(account_owner_emails, markup));
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (markup)
|
||||||
|
builder.append("</span>");
|
||||||
|
|
||||||
|
return builder.str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ParticipantDisplay : Geary.BaseObject, Gee.Hashable<ParticipantDisplay> {
|
||||||
|
private const string ME = _("Me");
|
||||||
|
|
||||||
|
public Geary.RFC822.MailboxAddress address;
|
||||||
|
public bool is_unread;
|
||||||
|
|
||||||
|
public ParticipantDisplay(Geary.RFC822.MailboxAddress address, bool is_unread) {
|
||||||
|
this.address = address;
|
||||||
|
this.is_unread = is_unread;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string get_full(Gee.List<Geary.RFC822.MailboxAddress> account_mailboxes, bool markup) {
|
||||||
|
string name = (address in account_mailboxes) ? ME : address.get_short_address();
|
||||||
|
return markup ? get_as_markup(name) : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string get_short(Gee.List<Geary.RFC822.MailboxAddress> account_mailboxes, bool markup) {
|
||||||
|
if (address in account_mailboxes)
|
||||||
|
return markup ? get_as_markup(ME) : ME;
|
||||||
|
|
||||||
|
string short_address = address.get_short_address().strip();
|
||||||
|
|
||||||
|
if (", " in short_address) {
|
||||||
|
// assume address is in Last, First format
|
||||||
|
string[] tokens = short_address.split(", ", 2);
|
||||||
|
short_address = tokens[1].strip();
|
||||||
|
if (Geary.String.is_empty(short_address))
|
||||||
|
return get_full(account_mailboxes, markup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use first name as delimited by a space
|
||||||
|
string[] tokens = short_address.split(" ", 2);
|
||||||
|
if (tokens.length < 1)
|
||||||
|
return get_full(account_mailboxes, markup);
|
||||||
|
|
||||||
|
string first_name = tokens[0].strip();
|
||||||
|
if (Geary.String.is_empty_or_whitespace(first_name))
|
||||||
|
return get_full(account_mailboxes, markup);
|
||||||
|
|
||||||
|
return markup ? get_as_markup(first_name) : first_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string get_as_markup(string participant) {
|
||||||
|
return "%s%s%s".printf(
|
||||||
|
is_unread ? "<b>" : "", Geary.HTML.escape_markup(participant), is_unread ? "</b>" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool equal_to(ParticipantDisplay other) {
|
||||||
|
return address.equal_to(other.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint hash() {
|
||||||
|
return address.hash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue