From e330e60f83e6e46bbc3d320711709f2448b802e7 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 18 Oct 2019 16:52:29 +0200 Subject: [PATCH] Base avatars and names on conversation, not JID. Fixes #598 --- libdino/src/service/muc_manager.vala | 4 +- libdino/src/service/search_processor.vala | 23 +- libdino/src/service/stream_interactor.vala | 7 + main/CMakeLists.txt | 2 +- .../ui/add_conversation/conference_list.vala | 2 +- main/src/ui/add_conversation/list_row.vala | 5 +- main/src/ui/avatar_drawer.vala | 192 ++++++++++ main/src/ui/avatar_generator.vala | 270 -------------- main/src/ui/avatar_image.vala | 339 +++++------------- main/src/ui/contact_details/dialog.vala | 2 +- .../ui/contact_details/settings_provider.vala | 2 +- .../conversation_selector_row.vala | 7 +- .../chat_state_populator.vala | 4 +- .../content_item_widget_factory.vala | 2 +- .../conversation_item_skeleton.vala | 4 +- main/src/ui/global_search.vala | 29 +- main/src/ui/manage_accounts/account_row.vala | 2 +- main/src/ui/manage_accounts/dialog.vala | 4 +- main/src/ui/notifications.vala | 19 +- main/src/ui/occupant_menu/list.vala | 10 +- main/src/ui/occupant_menu/list_row.vala | 10 +- main/src/ui/occupant_menu/view.vala | 2 +- main/src/ui/util/helper.vala | 176 ++++++--- 23 files changed, 482 insertions(+), 635 deletions(-) create mode 100644 main/src/ui/avatar_drawer.vala diff --git a/libdino/src/service/muc_manager.vala b/libdino/src/service/muc_manager.vala index c1ef2d80..2c4512e6 100644 --- a/libdino/src/service/muc_manager.vala +++ b/libdino/src/service/muc_manager.vala @@ -142,8 +142,8 @@ public class MucManager : StreamInteractionModule, Object { } public bool is_groupchat(Jid jid, Account account) { - Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account); - return !jid.is_full() && conversation != null && conversation.type_ == Conversation.Type.GROUPCHAT; + Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid, account, Conversation.Type.GROUPCHAT); + return !jid.is_full() && conversation != null; } public bool is_groupchat_occupant(Jid jid, Account account) { diff --git a/libdino/src/service/search_processor.vala b/libdino/src/service/search_processor.vala index 6a08d6b8..37f524ac 100644 --- a/libdino/src/service/search_processor.vala +++ b/libdino/src/service/search_processor.vala @@ -133,27 +133,29 @@ public class SearchProcessor : StreamInteractionModule, Object { .order_by(db.conversation.last_active, "DESC"); foreach(Row chat in chats) { if (suggestions.size == 0) { - suggestions.add(new SearchSuggestion(new Account.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "from:"+chat[db.jid.bare_jid], after_prev_space, next_space)); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "from:"+chat[db.jid.bare_jid], after_prev_space, next_space)); } - suggestions.add(new SearchSuggestion(new Account.from_row(db, chat), new Jid(chat[db.account.bare_jid]), "from:"+chat[db.account.bare_jid], after_prev_space, next_space)); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.account.bare_jid]), "from:"+chat[db.account.bare_jid], after_prev_space, next_space)); } return suggestions; } if (current_in != null) { // All members of the MUC with history QueryBuilder msgs = db.message.select() - .select_string(@"account.*, $(db.message.counterpart_resource)") + .select_string(@"account.*, $(db.message.counterpart_resource), conversation.*") .join_with(db.jid, db.jid.id, db.message.counterpart_id) .join_with(db.account, db.account.id, db.message.account_id) + .join_on(db.conversation, @"$(db.conversation.account_id)=$(db.account.id) AND $(db.conversation.jid_id)=$(db.jid.id)") .with(db.jid.bare_jid, "=", current_in) .with(db.account.enabled, "=", true) .with(db.message.type_, "=", Message.Type.GROUPCHAT) + .with(db.conversation.type_, "=", Conversation.Type.GROUPCHAT) .with(db.message.counterpart_resource, "LIKE", @"%$current_from%") .group_by({db.message.counterpart_resource}) .order_by_name(@"MAX($(db.message.time))", "DESC") .limit(5); foreach(Row msg in msgs) { - suggestions.add(new SearchSuggestion(new Account.from_row(db, msg), new Jid(current_in).with_resource(msg[db.message.counterpart_resource]), "from:"+msg[db.message.counterpart_resource], after_prev_space, next_space)); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, msg), new Jid(current_in).with_resource(msg[db.message.counterpart_resource]), "from:"+msg[db.message.counterpart_resource], after_prev_space, next_space)); } } // TODO: auto complete from @@ -179,7 +181,7 @@ public class SearchProcessor : StreamInteractionModule, Object { .order_by(db.conversation.last_active, "DESC") .limit(limit); foreach(Row chat in chats) { - suggestions.add(new SearchSuggestion(new Account.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "with:"+chat[db.jid.bare_jid], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "with:"+chat[db.jid.bare_jid], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); } // Groupchat PM @@ -193,7 +195,7 @@ public class SearchProcessor : StreamInteractionModule, Object { .order_by(db.conversation.last_active, "DESC") .limit(limit - suggestions.size); foreach(Row chat in chats) { - suggestions.add(new SearchSuggestion(new Account.from_row(db, chat), new Jid(chat[db.jid.bare_jid]).with_resource(chat[db.conversation.resource]), "with:"+chat[db.jid.bare_jid]+"/"+chat[db.conversation.resource], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]).with_resource(chat[db.conversation.resource]), "with:"+chat[db.jid.bare_jid]+"/"+chat[db.conversation.resource], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); } suggestions.sort((a, b) => (int)(b.order - a.order)); } @@ -216,7 +218,7 @@ public class SearchProcessor : StreamInteractionModule, Object { .order_by(db.conversation.last_active, "DESC") .limit(limit); foreach(Row chat in groupchats) { - suggestions.add(new SearchSuggestion(new Account.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "in:"+chat[db.jid.bare_jid], after_prev_space, next_space)); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "in:"+chat[db.jid.bare_jid], after_prev_space, next_space)); } } else { // Other auto complete? @@ -244,15 +246,16 @@ public class SearchProcessor : StreamInteractionModule, Object { } public class SearchSuggestion : Object { - public Account account { get; private set; } + public Account account { get { return conversation.account; } } + public Conversation conversation { get; private set; } public Jid? jid { get; private set; } public string completion { get; private set; } public int start_index { get; private set; } public int end_index { get; private set; } public long order { get; set; } - public SearchSuggestion(Account account, Jid? jid, string completion, int start_index, int end_index) { - this.account = account; + public SearchSuggestion(Conversation conversation, Jid? jid, string completion, int start_index, int end_index) { + this.conversation = conversation; this.jid = jid; this.completion = completion; this.start_index = start_index; diff --git a/libdino/src/service/stream_interactor.vala b/libdino/src/service/stream_interactor.vala index ab4a4a93..91707996 100644 --- a/libdino/src/service/stream_interactor.vala +++ b/libdino/src/service/stream_interactor.vala @@ -58,6 +58,13 @@ public class StreamInteractor : Object { return null; } + public T? get() { + foreach (StreamInteractionModule module in modules) { + if (module.get_type() == typeof(T)) return (T?) module; + } + return null; + } + private void on_stream_opened(Account account, XmppStream stream) { stream.stream_negotiated.connect( (stream) => { stream_negotiated(account, stream); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ae3cd494..48a349d5 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -91,7 +91,7 @@ SOURCES src/main.vala src/ui/application.vala - src/ui/avatar_generator.vala + src/ui/avatar_drawer.vala src/ui/avatar_image.vala src/ui/chat_input_controller.vala src/ui/conversation_list_titlebar.vala diff --git a/main/src/ui/add_conversation/conference_list.vala b/main/src/ui/add_conversation/conference_list.vala index 435d42e0..f18a89d5 100644 --- a/main/src/ui/add_conversation/conference_list.vala +++ b/main/src/ui/add_conversation/conference_list.vala @@ -118,7 +118,7 @@ internal class ConferenceListRow : ListRow { via_label.visible = false; } - image.set_jid(stream_interactor, jid, account); + image.set_conversation(stream_interactor, new Conversation(jid, account, Conversation.Type.GROUPCHAT)); } } diff --git a/main/src/ui/add_conversation/list_row.vala b/main/src/ui/add_conversation/list_row.vala index 2d15c32a..4dec567a 100644 --- a/main/src/ui/add_conversation/list_row.vala +++ b/main/src/ui/add_conversation/list_row.vala @@ -22,7 +22,8 @@ public class ListRow : ListBoxRow { this.jid = jid; this.account = account; - string display_name = Util.get_display_name(stream_interactor, jid, account); + Conversation conv = new Conversation(jid, account, Conversation.Type.CHAT); + string display_name = Util.get_conversation_display_name(stream_interactor, conv); if (show_account && stream_interactor.get_accounts().size > 1) { via_label.label = @"via $(account.bare_jid)"; this.has_tooltip = true; @@ -33,7 +34,7 @@ public class ListRow : ListBoxRow { via_label.visible = false; } name_label.label = display_name; - image.set_jid(stream_interactor, jid, account); + image.set_conversation(stream_interactor, conv); } } diff --git a/main/src/ui/avatar_drawer.vala b/main/src/ui/avatar_drawer.vala new file mode 100644 index 00000000..8b7cd0f2 --- /dev/null +++ b/main/src/ui/avatar_drawer.vala @@ -0,0 +1,192 @@ +using Cairo; +using Gee; +using Gdk; +using Gtk; +using Xmpp.Util; + +namespace Dino.Ui { + +public class AvatarDrawer { + public const string GRAY = "555753"; + + private Gee.List tiles = new ArrayList(); + private int height = 35; + private int width = 35; + private bool gray; + private int base_factor = 1; + private string font_family = "Sans"; + + public AvatarDrawer size(int height, int width = height) { + this.height = height; + this.width = width; + return this; + } + + public AvatarDrawer grayscale() { + this.gray = true; + return this; + } + + public AvatarDrawer tile(Pixbuf? image, string? name, string? hex_color) { + tiles.add(new AvatarTile(image, name, hex_color)); + return this; + } + + public AvatarDrawer plus() { + tiles.add(new AvatarTile(null, "…", GRAY)); + return this; + } + + public AvatarDrawer scale(int base_factor) { + this.base_factor = base_factor; + return this; + } + + public AvatarDrawer font(string font_family) { + this.font_family = font_family; + return this; + } + + public ImageSurface draw_image_surface() { + ImageSurface surface = new ImageSurface(Format.ARGB32, width, height); + draw_on_context(new Context(surface)); + return surface; + } + + public void draw_on_context(Cairo.Context ctx) { + double radius = 3 * base_factor; + double degrees = Math.PI / 180.0; + ctx.new_sub_path(); + ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees); + ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees); + ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees); + ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); + ctx.close_path(); + ctx.clip(); + + if (this.tiles.size == 4) { + Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); + Cairo.Context bufctx = new Cairo.Context(buffer); + bufctx.scale(0.5, 0.5); + bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height - 1, 2 * base_factor), width + 1, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1); + bufctx.paint(); + bufctx.set_source_surface(sub_surface_idx(ctx, 3, width - 1, height - 1, 2 * base_factor), width + 1, height + 1); + bufctx.paint(); + + ctx.set_source_surface(buffer, 0, 0); + ctx.paint(); + } else if (this.tiles.size == 3) { + Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); + Cairo.Context bufctx = new Cairo.Context(buffer); + bufctx.scale(0.5, 0.5); + bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface_idx(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1); + bufctx.paint(); + + ctx.set_source_surface(buffer, 0, 0); + ctx.paint(); + } else if (this.tiles.size == 2) { + Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); + Cairo.Context bufctx = new Cairo.Context(buffer); + bufctx.scale(0.5, 0.5); + bufctx.set_source_surface(sub_surface_idx(ctx, 0, width - 1, height * 2, 2 * base_factor), 0, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface_idx(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0); + bufctx.paint(); + + ctx.set_source_surface(buffer, 0, 0); + ctx.paint(); + } else if (this.tiles.size == 1) { + ctx.set_source_surface(sub_surface_idx(ctx, 0, width, height, base_factor), 0, 0); + ctx.paint(); + } else if (this.tiles.size == 0) { + ctx.set_source_surface(sub_surface_idx(ctx, -1, width, height, base_factor), 0, 0); + ctx.paint(); + } + + if (gray) { + // convert to greyscale + ctx.set_operator(Cairo.Operator.HSL_COLOR); + ctx.set_source_rgb(1, 1, 1); + ctx.rectangle(0, 0, width, height); + ctx.fill(); + // make the visible part more light + ctx.set_operator(Cairo.Operator.ATOP); + ctx.set_source_rgba(1, 1, 1, 0.7); + ctx.rectangle(0, 0, width, height); + ctx.fill(); + } + } + + private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) { + Gdk.Pixbuf? avatar = idx >= 0 ? tiles[idx].image : null; + string? name = idx >= 0 ? tiles[idx].name : ""; + string hex_color = !gray && idx >= 0 ? tiles[idx].hex_color : GRAY; + return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor); + } + + private static Cairo.Surface sub_surface(Cairo.Context ctx, string font_family, Gdk.Pixbuf? avatar, string? name, string? hex_color, int width, int height, int font_factor = 1) { + Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); + Cairo.Context bufctx = new Cairo.Context(buffer); + if (avatar == null) { + set_source_hex_color(bufctx, hex_color ?? GRAY); + bufctx.rectangle(0, 0, width, height); + bufctx.fill(); + + string text = name == null ? "…" : name.get_char(0).toupper().to_string(); + bufctx.select_font_face(font_family, Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); + bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25); + Cairo.TextExtents extents; + bufctx.text_extents(text, out extents); + double x_pos = width/2 - (extents.width/2 + extents.x_bearing); + double y_pos = height/2 - (extents.height/2 + extents.y_bearing); + bufctx.move_to(x_pos, y_pos); + bufctx.set_source_rgba(1, 1, 1, 1); + bufctx.show_text(text); + } else { + double w_scale = (double) width / avatar.width; + double h_scale = (double) height / avatar.height; + double scale = double.max(w_scale, h_scale); + bufctx.scale(scale, scale); + + double x_off = 0, y_off = 0; + if (scale == h_scale) { + x_off = (width / scale - avatar.width) / 2.0; + } else { + y_off = (height / scale - avatar.height) / 2.0; + } + Gdk.cairo_set_source_pixbuf(bufctx, avatar, x_off, y_off); + bufctx.get_source().set_filter(Cairo.Filter.BEST); + bufctx.paint(); + } + return buffer; + } + + private static void set_source_hex_color(Cairo.Context ctx, string hex_color) { + ctx.set_source_rgba((double) from_hex(hex_color.substring(0, 2)) / 255, + (double) from_hex(hex_color.substring(2, 2)) / 255, + (double) from_hex(hex_color.substring(4, 2)) / 255, + hex_color.length > 6 ? (double) from_hex(hex_color.substring(6, 2)) / 255 : 1); + } +} + +private class AvatarTile { + public Pixbuf? image { get; private set; } + public string? name { get; private set; } + public string? hex_color { get; private set; } + + public AvatarTile(Pixbuf? image, string? name, string? hex_color) { + this.image = image; + this.name = name; + this.hex_color = hex_color; + } +} + +} \ No newline at end of file diff --git a/main/src/ui/avatar_generator.vala b/main/src/ui/avatar_generator.vala index 2a6aa397..e69de29b 100644 --- a/main/src/ui/avatar_generator.vala +++ b/main/src/ui/avatar_generator.vala @@ -1,270 +0,0 @@ -using Cairo; -using Gee; -using Gdk; -using Gtk; - -using Dino.Entities; -using Xmpp; -using Xmpp.Util; - -namespace Dino.Ui { - -public class AvatarGenerator { - - private const string COLOR_GREY = "E0E0E0"; - private const string GROUPCHAT_ICON = "system-users-symbolic"; - - StreamInteractor? stream_interactor; - bool greyscale = false; - bool stateless = false; - int width; - int height; - int scale_factor; - - public AvatarGenerator(int width, int height, int scale_factor = 1) { - this.width = width; - this.height = height; - this.scale_factor = scale_factor; - } - - public async ImageSurface draw_jid(StreamInteractor stream_interactor, Jid jid_, Account account) { - Jid? jid = jid_; - this.stream_interactor = stream_interactor; - Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(jid, account); - if (real_jid != null && stream_interactor.get_module(AvatarManager.IDENTITY).has_avatar(account, real_jid)) { - jid = real_jid; - } - ImageSurface surface = crop_corners(yield draw_tile(jid, account, width * scale_factor, height * scale_factor), 3 * scale_factor); - surface.set_device_scale(scale_factor, scale_factor); - return surface; - } - - public async ImageSurface draw_message(StreamInteractor stream_interactor, Message message) { - if (message.real_jid != null && stream_interactor.get_module(AvatarManager.IDENTITY).has_avatar(message.account, message.real_jid)) { - return yield draw_jid(stream_interactor, message.real_jid, message.account); - } - return yield draw_jid(stream_interactor, message.from, message.account); - } - - public async ImageSurface draw_conversation(StreamInteractor stream_interactor, Conversation conversation) { - return yield draw_jid(stream_interactor, conversation.counterpart, conversation.account); - } - - public async ImageSurface draw_account(StreamInteractor stream_interactor, Account account) { - return yield draw_jid(stream_interactor, account.bare_jid, account); - } - - public ImageSurface draw_text(string text) { - ImageSurface surface = draw_colored_rectangle_text(COLOR_GREY, text, width, height); - return crop_corners(surface, 3 * scale_factor); - } - - public AvatarGenerator set_greyscale(bool greyscale) { - this.greyscale = greyscale; - return this; - } - - public AvatarGenerator set_stateless(bool stateless) { - this.stateless = stateless; - return this; - } - - private int get_left_border() { - return (int)Math.floor(scale_factor/2.0); - } - - private int get_right_border() { - return (int)Math.ceil(scale_factor/2.0); - } - - private async void add_tile_to_pixbuf(Pixbuf pixbuf, Jid jid, Account account, int width, int height, int x, int y) { - Pixbuf tile = pixbuf_get_from_surface(yield draw_chat_tile(jid, account, width, height), 0, 0, width, height); - tile.copy_area(0, 0, width, height, pixbuf, x, y); - } - - private async ImageSurface draw_tile(Jid jid, Account account, int width, int height) { - ImageSurface surface; - if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(jid, account)) { - surface = yield draw_groupchat_tile(jid, account, width, height); - } else { - surface = yield draw_chat_tile(jid, account, width, height); - } - return surface; - } - - private async ImageSurface draw_chat_tile(Jid jid, Account account, int width, int height) { - Pixbuf? pixbuf = yield stream_interactor.get_module(AvatarManager.IDENTITY).get_avatar(account, jid); - if (pixbuf != null) { - double desired_ratio = (double) width / height; - double avatar_ratio = (double) pixbuf.width / pixbuf.height; - if (avatar_ratio > desired_ratio) { - int comp_width = width * pixbuf.height / height; - pixbuf = new Pixbuf.subpixbuf(pixbuf, pixbuf.width / 2 - comp_width / 2, 0, comp_width, pixbuf.height); - } else if (avatar_ratio < desired_ratio) { - int comp_height = height * pixbuf.width / width; - pixbuf = new Pixbuf.subpixbuf(pixbuf, 0, pixbuf.height / 2 - comp_height / 2, pixbuf.width, comp_height); - } - pixbuf = pixbuf.scale_simple(width, height, InterpType.BILINEAR); - Context ctx = new Context(new ImageSurface(Format.ARGB32, pixbuf.width, pixbuf.height)); - cairo_set_source_pixbuf(ctx, pixbuf, 0, 0); - ctx.paint(); - ImageSurface avatar_surface = (ImageSurface) ctx.get_target(); - if (greyscale) avatar_surface = convert_to_greyscale(avatar_surface); - return avatar_surface; - } else { - string display_name = Util.get_display_name(stream_interactor, jid, account); - string color = greyscale ? COLOR_GREY : Util.get_avatar_hex_color(stream_interactor, account, jid); - return draw_colored_rectangle_text(color, display_name.get_char(0).toupper().to_string(), width, height); - } - } - - private async ImageSurface draw_groupchat_tile(Jid jid, Account account, int width, int height) { - Gee.List? occupants = stream_interactor.get_module(MucManager.IDENTITY).get_other_occupants(jid, account); - if (stateless || occupants == null || occupants.size == 0) { - return yield draw_chat_tile(jid, account, width, height); - } - - for (int i = 0; i < occupants.size && i < 4; i++) { - Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupants[i], account); - if (real_jid != null && stream_interactor.get_module(AvatarManager.IDENTITY).has_avatar(account, real_jid)) { - occupants[i] = real_jid; - } - } - Pixbuf pixbuf = initialize_pixbuf(width, height); - if (occupants.size == 1 || occupants.size == 2 || occupants.size == 3) { - yield add_tile_to_pixbuf(pixbuf, occupants[0], account, width / 2 - get_right_border(), height, 0, 0); - if (occupants.size == 1) { - yield add_tile_to_pixbuf(pixbuf, account.bare_jid, account, width / 2 - get_left_border(), height, width / 2 + get_left_border(), 0); - } else if (occupants.size == 2) { - yield add_tile_to_pixbuf(pixbuf, occupants[1], account, width / 2 - get_left_border(), height, width / 2 + get_left_border(), 0); - } else if (occupants.size == 3) { - yield add_tile_to_pixbuf(pixbuf, occupants[1], account, width / 2 - get_left_border(), height / 2 - get_right_border(), width / 2 + get_left_border(), 0); - yield add_tile_to_pixbuf(pixbuf, occupants[2], account, width / 2 - get_left_border(), height / 2 - get_left_border(), width / 2 + get_left_border(), height / 2 + get_left_border()); - } - } else if (occupants.size >= 4) { - yield add_tile_to_pixbuf(pixbuf, occupants[0], account, width / 2 - get_right_border(), height / 2 - get_right_border(), 0, 0); - yield add_tile_to_pixbuf(pixbuf, occupants[1], account, width / 2 - get_left_border(), height / 2 - get_right_border(), width / 2 + get_left_border(), 0); - yield add_tile_to_pixbuf(pixbuf, occupants[2], account, width / 2 - get_right_border(), height / 2 - get_left_border(), 0, height / 2 + get_left_border()); - if (occupants.size == 4) { - yield add_tile_to_pixbuf(pixbuf, occupants[3], account, width / 2 - get_left_border(), height / 2 - get_left_border(), width / 2 + get_left_border(), height / 2 + get_left_border()); - } else if (occupants.size > 4) { - ImageSurface plus_surface = draw_colored_rectangle_text("555753", "+", width / 2 - get_left_border(), height / 2 - get_left_border()); - if (greyscale) plus_surface = convert_to_greyscale(plus_surface); - pixbuf_get_from_surface(plus_surface, 0, 0, plus_surface.get_width(), plus_surface.get_height()).copy_area(0, 0, width / 2 - get_left_border(), height / 2 - get_left_border(), pixbuf, width / 2 + get_left_border(), height / 2 + get_left_border()); - } - } - Context ctx = new Context(new ImageSurface(Format.ARGB32, pixbuf.width, pixbuf.height)); - cairo_set_source_pixbuf(ctx, pixbuf, 0, 0); - ctx.paint(); - return (ImageSurface) ctx.get_target(); - } - - public ImageSurface draw_colored_icon(string hex_color, string icon, int width, int height) { - int ICON_SIZE = width > 20 * scale_factor ? 17 * scale_factor : 14 * scale_factor; - - Context rectancle_context = new Context(new ImageSurface(Format.ARGB32, width, height)); - draw_colored_rectangle(rectancle_context, hex_color, width, height); - - try { - Pixbuf icon_pixbuf = IconTheme.get_default().load_icon(icon, ICON_SIZE, IconLookupFlags.FORCE_SIZE); - Surface icon_surface = cairo_surface_create_from_pixbuf(icon_pixbuf, 1, null); - Context context = new Context(icon_surface); - context.set_operator(Operator.IN); - context.set_source_rgba(1, 1, 1, 1); - context.rectangle(0, 0, width, height); - context.fill(); - - rectancle_context.set_source_surface(icon_surface, width / 2 - ICON_SIZE / 2, height / 2 - ICON_SIZE / 2); - rectancle_context.paint(); - } catch (Error e) { warning(@"Icon $icon does not exist"); } - - return (ImageSurface) rectancle_context.get_target(); - } - - public ImageSurface draw_colored_rectangle_text(string hex_color, string text, int width, int height) { - Context ctx = new Context(new ImageSurface(Format.ARGB32, width, height)); - draw_colored_rectangle(ctx, hex_color, width, height); - draw_center_text(ctx, text, width < 40 * scale_factor ? 17 * scale_factor : 25 * scale_factor, width, height); - return (ImageSurface) ctx.get_target(); - } - - private static void draw_center_text(Context ctx, string text, int fontsize, int width, int height) { - ctx.select_font_face("Sans", Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); - ctx.set_font_size(fontsize); - Cairo.TextExtents extents; - ctx.text_extents(text, out extents); - double x_pos = width/2 - (extents.width/2 + extents.x_bearing); - double y_pos = height/2 - (extents.height/2 + extents.y_bearing); - ctx.move_to(x_pos, y_pos); - ctx.set_source_rgba(1, 1, 1, 1); - ctx.show_text(text); - } - - private static void draw_colored_rectangle(Context ctx, string hex_color, int width, int height) { - set_source_hex_color(ctx, hex_color); - ctx.rectangle(0, 0, width, height); - ctx.fill(); - } - - private static ImageSurface convert_to_greyscale(ImageSurface surface) { - Context context = new Context(surface); - // convert to greyscale - context.set_operator(Operator.HSL_COLOR); - context.set_source_rgb(1, 1, 1); - context.rectangle(0, 0, surface.get_width(), surface.get_height()); - context.fill(); - // make the visible part more light - context.set_operator(Operator.ATOP); - context.set_source_rgba(1, 1, 1, 0.7); - context.rectangle(0, 0, surface.get_width(), surface.get_height()); - context.fill(); - return (ImageSurface) context.get_target(); - } - - public static Pixbuf crop_corners_pixbuf(Pixbuf pixbuf, double radius = 3) { - Context ctx = new Context(new ImageSurface(Format.ARGB32, pixbuf.width, pixbuf.height)); - cairo_set_source_pixbuf(ctx, pixbuf, 0, 0); - double degrees = Math.PI / 180.0; - ctx.new_sub_path(); - ctx.arc(pixbuf.width - radius, radius, radius, -90 * degrees, 0 * degrees); - ctx.arc(pixbuf.width - radius, pixbuf.height - radius, radius, 0 * degrees, 90 * degrees); - ctx.arc(radius, pixbuf.height - radius, radius, 90 * degrees, 180 * degrees); - ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); - ctx.close_path(); - ctx.clip(); - ctx.paint(); - return pixbuf_get_from_surface(ctx.get_target(), 0, 0, pixbuf.width, pixbuf.height); - } - - public static ImageSurface crop_corners(ImageSurface surface, double radius = 3) { - Context ctx = new Context(new ImageSurface(Format.ARGB32, surface.get_width(), surface.get_height())); - ctx.set_source_surface(surface, 0, 0); - double degrees = Math.PI / 180.0; - ctx.new_sub_path(); - ctx.arc(surface.get_width() - radius, radius, radius, -90 * degrees, 0 * degrees); - ctx.arc(surface.get_width() - radius, surface.get_height() - radius, radius, 0 * degrees, 90 * degrees); - ctx.arc(radius, surface.get_height() - radius, radius, 90 * degrees, 180 * degrees); - ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); - ctx.close_path(); - ctx.clip(); - ctx.paint(); - return (ImageSurface) ctx.get_target(); - } - - private static Pixbuf initialize_pixbuf(int width, int height) { - Context ctx = new Context(new ImageSurface(Format.ARGB32, width, height)); - ctx.set_source_rgba(1, 1, 1, 0); - ctx.rectangle(0, 0, width, height); - ctx.fill(); - return pixbuf_get_from_surface(ctx.get_target(), 0, 0, width, height); - } - - private static void set_source_hex_color(Context ctx, string hex_color) { - ctx.set_source_rgba((double) from_hex(hex_color.substring(0, 2)) / 255, - (double) from_hex(hex_color.substring(2, 2)) / 255, - (double) from_hex(hex_color.substring(4, 2)) / 255, - hex_color.length > 6 ? (double) from_hex(hex_color.substring(6, 2)) / 255 : 1); - } -} - -} diff --git a/main/src/ui/avatar_image.vala b/main/src/ui/avatar_image.vala index cc700f00..a3b386e3 100644 --- a/main/src/ui/avatar_image.vala +++ b/main/src/ui/avatar_image.vala @@ -9,16 +9,15 @@ public class AvatarImage : Misc { public int height { get; set; default = 35; } public int width { get; set; default = 35; } public bool allow_gray { get; set; default = true; } - public Account account { get; private set; } - public StreamInteractor stream_interactor { get; set; } - public AvatarManager avatar_manager { owned get { return stream_interactor.get_module(AvatarManager.IDENTITY); } } - public MucManager muc_manager { owned get { return stream_interactor.get_module(MucManager.IDENTITY); } } - private Jid jid; - private string? text_only; - private bool with_plus; - private bool gray; - private Jid[] current_jids; - private Gdk.Pixbuf[] current_avatars; + public StreamInteractor? stream_interactor { get; set; } + public AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } } + public MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } } + public PresenceManager? presence_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(PresenceManager.IDENTITY); } } + public ConnectionManager? connection_manager { owned get { return stream_interactor == null ? null : stream_interactor.connection_manager; } } + public Account account { get { return conversation.account; } } + private AvatarDrawer? drawer; + private Conversation conversation; + private Jid[] jids; private Cairo.ImageSurface? cached_surface; private static int8 use_image_surface = -1; @@ -37,52 +36,8 @@ public class AvatarImage : Misc { natural_height = height; } - private Cairo.Surface sub_surface(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) { - Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); - Cairo.Context bufctx = new Cairo.Context(buffer); - if (idx == -1 || current_avatars[idx] == null) { - set_source_hex_color(bufctx, gray || idx == -1 || current_jids[idx] == null ? "555753" : Util.get_avatar_hex_color(stream_interactor, account, current_jids[idx])); - bufctx.rectangle(0, 0, width, height); - bufctx.fill(); - - string text = text_only ?? (idx == -1 || current_jids[idx] == null ? "…" : Util.get_display_name(stream_interactor, current_jids[idx], account).get_char(0).toupper().to_string()); - bufctx.select_font_face(get_pango_context().get_font_description().get_family(), Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); - bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25); - Cairo.TextExtents extents; - bufctx.text_extents(text, out extents); - double x_pos = width/2 - (extents.width/2 + extents.x_bearing); - double y_pos = height/2 - (extents.height/2 + extents.y_bearing); - bufctx.move_to(x_pos, y_pos); - bufctx.set_source_rgba(1, 1, 1, 1); - bufctx.show_text(text); - } else { - double w_scale = (double) width / current_avatars[idx].width; - double h_scale = (double) height / current_avatars[idx].height; - double scale = double.max(w_scale, h_scale); - bufctx.scale(scale, scale); - - double x_off = 0, y_off = 0; - if (scale == h_scale) { - x_off = (width / scale - current_avatars[idx].width) / 2.0; - } else { - y_off = (height / scale - current_avatars[idx].height) / 2.0; - } - Gdk.cairo_set_source_pixbuf(bufctx, current_avatars[idx], x_off, y_off); - bufctx.get_source().set_filter(Cairo.Filter.BEST); - bufctx.paint(); - } - return buffer; - } - - private static void set_source_hex_color(Cairo.Context ctx, string hex_color) { - ctx.set_source_rgba((double) from_hex(hex_color.substring(0, 2)) / 255, - (double) from_hex(hex_color.substring(2, 2)) / 255, - (double) from_hex(hex_color.substring(4, 2)) / 255, - hex_color.length > 6 ? (double) from_hex(hex_color.substring(6, 2)) / 255 : 1); - } - public override bool draw(Cairo.Context ctx_in) { - if (text_only == null && (current_jids == null || current_avatars == null || current_jids.length == 0)) return false; + if (drawer == null) return false; Cairo.Context ctx = ctx_in; int width = this.width, height = this.height, base_factor = 1; @@ -104,82 +59,10 @@ public class AvatarImage : Misc { ctx = new Cairo.Context(cached_surface); } - double radius = 3 * base_factor; - double degrees = Math.PI / 180.0; - ctx.new_sub_path(); - ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees); - ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees); - ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees); - ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); - ctx.close_path(); - ctx.clip(); - - if (text_only != null) { - ctx.set_source_surface(sub_surface(ctx, -1, width, height, base_factor), 0, 0); - ctx.paint(); - } else if (current_jids.length == 4 || with_plus) { - Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); - Cairo.Context bufctx = new Cairo.Context(buffer); - bufctx.scale(0.5, 0.5); - bufctx.set_source_surface(sub_surface(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0); - bufctx.paint(); - bufctx.set_source_surface(sub_surface(ctx, 1, width - 1, height - 1, 2 * base_factor), width + 1, 0); - bufctx.paint(); - bufctx.set_source_surface(sub_surface(ctx, 2, width - 1, height - 1, 2 * base_factor), 0, height + 1); - bufctx.paint(); - if (with_plus) { - bufctx.set_source_surface(sub_surface(ctx, -1, width - 1, height - 1, 2 * base_factor), width + 1, height + 1); - bufctx.paint(); - } else { - bufctx.set_source_surface(sub_surface(ctx, 3, width - 1, height - 1, 2 * base_factor), width + 1, height + 1); - bufctx.paint(); - } - - ctx.set_source_surface(buffer, 0, 0); - ctx.paint(); - } else if (current_jids.length == 3) { - Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); - Cairo.Context bufctx = new Cairo.Context(buffer); - bufctx.scale(0.5, 0.5); - bufctx.set_source_surface(sub_surface(ctx, 0, width - 1, height - 1, 2 * base_factor), 0, 0); - bufctx.paint(); - bufctx.set_source_surface(sub_surface(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0); - bufctx.paint(); - bufctx.set_source_surface(sub_surface(ctx, 2, width - 1 , height - 1, 2 * base_factor), 0, height + 1); - bufctx.paint(); - - ctx.set_source_surface(buffer, 0, 0); - ctx.paint(); - } else if (current_jids.length == 2) { - Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); - Cairo.Context bufctx = new Cairo.Context(buffer); - bufctx.scale(0.5, 0.5); - bufctx.set_source_surface(sub_surface(ctx, 0, width - 1, height * 2, 2 * base_factor), 0, 0); - bufctx.paint(); - bufctx.set_source_surface(sub_surface(ctx, 1, width - 1, height * 2, 2 * base_factor), width + 1, 0); - bufctx.paint(); - - ctx.set_source_surface(buffer, 0, 0); - ctx.paint(); - } else if (current_jids.length == 1) { - ctx.set_source_surface(sub_surface(ctx, 0, width, height, base_factor), 0, 0); - ctx.paint(); - } else { - assert_not_reached(); - } - - if (gray) { - // convert to greyscale - ctx.set_operator(Cairo.Operator.HSL_COLOR); - ctx.set_source_rgb(1, 1, 1); - ctx.rectangle(0, 0, width, height); - ctx.fill(); - // make the visible part more light - ctx.set_operator(Cairo.Operator.ATOP); - ctx.set_source_rgba(1, 1, 1, 0.7); - ctx.rectangle(0, 0, width, height); - ctx.fill(); - } + drawer.size(height, width) + .scale(base_factor) + .font(get_pango_context().get_font_description().get_family()) + .draw_on_context(ctx); if (use_image_surface == 1) { ctx_in.set_source_surface(ctx.get_target(), 0, 0); @@ -190,100 +73,38 @@ public class AvatarImage : Misc { } public override void destroy() { + disconnect_stream_interactor(); + } + + private void disconnect_stream_interactor() { if (stream_interactor != null) { - stream_interactor.get_module(PresenceManager.IDENTITY).show_received.disconnect(on_show_received); - stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.disconnect(on_received_avatar); + presence_manager.show_received.disconnect(on_show_received); + avatar_manager.received_avatar.disconnect(on_received_avatar); stream_interactor.connection_manager.connection_state_changed.disconnect(on_connection_changed); stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.disconnect(on_roster_updated); - stream_interactor.get_module(MucManager.IDENTITY).private_room_occupant_updated.disconnect(on_occupant_updated); + muc_manager.private_room_occupant_updated.disconnect(on_private_room_occupant_updated); + stream_interactor = null; } } - public void set_jid(StreamInteractor stream_interactor, Jid jid_, Account account, bool force_update = false) { - this.account = account; - if (this.stream_interactor == null) { - this.stream_interactor = stream_interactor; - stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect(on_show_received); - stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.connect(on_received_avatar); - stream_interactor.connection_manager.connection_state_changed.connect(on_connection_changed); - stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated); - stream_interactor.get_module(MucManager.IDENTITY).private_room_occupant_updated.connect(on_occupant_updated); - } - if (muc_manager.is_groupchat(jid_, account) && !avatar_manager.has_avatar(account, jid_)) { - // Groupchat without avatar - Gee.List? occupants; - if (muc_manager.is_private_room(account, jid_)) { - occupants = muc_manager.get_other_offline_members(jid_, account); - } else { - occupants = muc_manager.get_other_occupants(jid_, account); - } - jid = jid_; - if (occupants == null || occupants.size == 0) { - if (force_update || current_jids.length != 1 || !current_jids[0].equals(jid_) || gray != (allow_gray && (occupants == null || !is_self_online()))) { - set_jids_(new Jid[] {jid_}, false, occupants == null || !is_self_online()); - } - } else if (occupants.size > 4) { - bool requires_update = force_update; - if (!with_plus) requires_update = true; - foreach (Jid jid in current_jids) { - if (!occupants.contains(jid)) { - requires_update = true; - } - } - if (requires_update) { - set_jids_(occupants.slice(0, 3).to_array(), true); - } - } else { // 1 <= occupants.size <= 4 - bool requires_update = force_update; - if (with_plus) requires_update = true; - if (current_jids.length != occupants.size) requires_update = true; - foreach (Jid jid in current_jids) { - if (!occupants.contains(jid)) { - requires_update = true; - } - } - if (requires_update) { - set_jids_(occupants.to_array(), false); - } - } - } else { - // Single user or MUC with vcard avatar - this.jid = jid_; - if (force_update || current_jids.length != 1 || !current_jids[0].equals(jid) || gray != (allow_gray && (!is_counterpart_online(jid) || !is_self_online()))) { - set_jids_(new Jid[] { jid }, false, !is_counterpart_online(jid) || !is_self_online()); - } - } - } - - public void set_jids(StreamInteractor stream_interactor, Jid[] jids, Account account, bool gray = false) { - this.stream_interactor = stream_interactor; - this.account = account; - set_jids_(jids.length > 3 ? jids[0:3] : jids, jids.length > 3, gray); - } - private void on_show_received(Show show, Jid jid, Account account) { if (!account.equals(this.account)) return; - if (jid.equals_bare(this.jid)) { - set_jid(stream_interactor, this.jid, account, true); - return; - } - foreach (Jid jid_ in current_jids) { - if (jid.equals_bare(jid_)) { - set_jid(stream_interactor, this.jid, account, true); - return; - } - } + update_avatar_if_jid(jid); } private void on_received_avatar(Gdk.Pixbuf avatar, Jid jid, Account account) { if (!account.equals(this.account)) return; - if (jid.equals_bare(this.jid)) { - set_jid(stream_interactor, this.jid, account, true); + update_avatar_if_jid(jid); + } + + private void update_avatar_if_jid(Jid jid) { + if (jid.equals_bare(this.conversation.counterpart)) { + update_avatar_async.begin(); return; } - foreach (Jid jid_ in current_jids) { - if (jid.equals_bare(jid_)) { - set_jid(stream_interactor, this.jid, account, true); + foreach (Jid ours in this.jids) { + if (jid.equals_bare(ours)) { + update_avatar_async.begin(); return; } } @@ -291,67 +112,73 @@ public class AvatarImage : Misc { private void on_connection_changed(Account account, ConnectionManager.ConnectionState state) { if (!account.equals(this.account)) return; - set_jid(stream_interactor, this.jid, account, true); + update_avatar_async.begin(); } private void on_roster_updated(Account account, Jid jid, Roster.Item roster_item) { if (!account.equals(this.account)) return; - if (!jid.equals_bare(this.jid)) return; - set_jid(stream_interactor, this.jid, account, true); + update_avatar_if_jid(jid); } - private void on_occupant_updated(Account account, Jid room, Jid occupant) { + private void on_private_room_occupant_updated(Account account, Jid room, Jid occupant) { if (!account.equals(this.account)) return; - if (!room.equals_bare(this.jid)) return; - set_jid(stream_interactor, this.jid, account, true); + update_avatar_if_jid(room); } private bool is_self_online() { - return stream_interactor.connection_manager.get_state(account) == ConnectionManager.ConnectionState.CONNECTED; - } - - private bool is_counterpart_online(Jid counterpart) { - return stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(counterpart, account) != null; - } - - public void set_jids_(Jid[] jids, bool with_plus = false, bool gray = false) { - assert(jids.length > 0); - assert(jids.length < 5); - assert(!with_plus || jids.length == 3); - this.cached_surface = null; - this.text_only = null; - this.gray = gray && allow_gray; - this.with_plus = with_plus; - - set_jids_async.begin(jids); - } - - public async void set_jids_async(Jid[] jids) { - Jid[] jids_ = jids; - Gdk.Pixbuf[] avatars = new Gdk.Pixbuf[jids.length]; - for (int i = 0; i < jids_.length; ++i) { - Jid? real_jid = muc_manager.get_real_jid(jids_[i], account); - if (real_jid != null) { - avatars[i] = yield avatar_manager.get_avatar(account, real_jid); - if (avatars[i] != null) { - jids_[i] = real_jid; - continue; - } - } - avatars[i] = yield avatar_manager.get_avatar(account, jids_[i]); + if (connection_manager != null) { + return connection_manager.get_state(account) == ConnectionManager.ConnectionState.CONNECTED; } - this.current_avatars = avatars; - this.current_jids = jids_; + return false; + } + + private bool is_counterpart_online() { + return presence_manager.get_full_jids(conversation.counterpart, account) != null; + } + + public void set_conversation(StreamInteractor stream_interactor, Conversation conversation) { + set_avatar_async.begin(stream_interactor, conversation, new Jid[0]); + } + + public void set_conversation_participant(StreamInteractor stream_interactor, Conversation conversation, Jid sub_jid) { + set_avatar_async.begin(stream_interactor, conversation, new Jid[] {sub_jid}); + } + + public void set_conversation_participants(StreamInteractor stream_interactor, Conversation conversation, Jid[] sub_jids) { + set_avatar_async.begin(stream_interactor, conversation, sub_jids); + } + + private async void update_avatar_async() { + this.cached_surface = null; + this.drawer = yield Util.get_conversation_participants_avatar_drawer(stream_interactor, conversation, jids); + if (allow_gray && (!is_self_online() || !is_counterpart_online())) drawer.grayscale(); queue_draw(); } + private async void set_avatar_async(StreamInteractor stream_interactor, Conversation conversation, Jid[] jids) { + if (this.stream_interactor != null && stream_interactor != this.stream_interactor) { + disconnect_stream_interactor(); + } + if (this.stream_interactor != stream_interactor) { + this.stream_interactor = stream_interactor; + presence_manager.show_received.connect(on_show_received); + stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.connect(on_received_avatar); + stream_interactor.connection_manager.connection_state_changed.connect(on_connection_changed); + stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated); + muc_manager.private_room_occupant_updated.connect(on_private_room_occupant_updated); + } + this.cached_surface = null; + this.conversation = conversation; + this.jids = jids; + + yield update_avatar_async(); + } + public void set_text(string text, bool gray = true) { - this.text_only = text; - this.gray = gray; - this.with_plus = false; - this.current_jids = null; - this.current_avatars = null; + disconnect_stream_interactor(); + this.drawer = new AvatarDrawer().tile(null, text, null); + if (gray) drawer.grayscale(); queue_draw(); } } diff --git a/main/src/ui/contact_details/dialog.vala b/main/src/ui/contact_details/dialog.vala index 8cbd8c54..ba9213a8 100644 --- a/main/src/ui/contact_details/dialog.vala +++ b/main/src/ui/contact_details/dialog.vala @@ -72,7 +72,7 @@ public class Dialog : Gtk.Dialog { } jid_label.label = conversation.counterpart.to_string(); account_label.label = "via " + conversation.account.bare_jid.to_string(); - avatar.set_jid(stream_interactor, conversation.counterpart, conversation.account); + avatar.set_conversation(stream_interactor, conversation); } private void add_entry(string category, string label, string? description, Object wo) { diff --git a/main/src/ui/contact_details/settings_provider.vala b/main/src/ui/contact_details/settings_provider.vala index aa397814..adc2e371 100644 --- a/main/src/ui/contact_details/settings_provider.vala +++ b/main/src/ui/contact_details/settings_provider.vala @@ -19,7 +19,7 @@ public class SettingsProvider : Plugins.ContactDetailsProvider, Object { public void populate(Conversation conversation, Plugins.ContactDetails contact_details, Plugins.WidgetType type) { if (type != Plugins.WidgetType.GTK) return; - if (!stream_interactor.get_module(MucManager.IDENTITY).is_public_room(conversation.account, conversation.counterpart)) { + if (!stream_interactor.get().is_public_room(conversation.account, conversation.counterpart)) { string details_headline = conversation.type_ == Conversation.Type.GROUPCHAT ? DETAILS_HEADLINE_ROOM : DETAILS_HEADLINE_CHAT; ComboBoxText combobox_typing = get_combobox(Dino.Application.get_default().settings.send_typing); diff --git a/main/src/ui/conversation_selector/conversation_selector_row.vala b/main/src/ui/conversation_selector/conversation_selector_row.vala index 3696f6da..fb4fbf1a 100644 --- a/main/src/ui/conversation_selector/conversation_selector_row.vala +++ b/main/src/ui/conversation_selector/conversation_selector_row.vala @@ -94,7 +94,7 @@ public class ConversationSelectorRow : ListBoxRow { last_content_item = stream_interactor.get_module(ContentItemStore.IDENTITY).get_latest(conversation); x_button.clicked.connect(close_conversation); - image.set_jid(stream_interactor, conversation.counterpart, conversation.account); + image.set_conversation(stream_interactor, conversation); conversation.notify["read-up-to"].connect(update_read); update_name_label(); @@ -131,7 +131,7 @@ public class ConversationSelectorRow : ListBoxRow { Message last_message = message_item.message; if (conversation.type_ == Conversation.Type.GROUPCHAT) { - nick_label.label = Util.get_message_display_name(stream_interactor, last_message, conversation.account) + ": "; + nick_label.label = Util.get_participant_display_name(stream_interactor, conversation, last_message.from, true) + ": "; } else { nick_label.label = last_message.direction == Message.DIRECTION_SENT ? _("Me") + ": " : ""; } @@ -145,8 +145,7 @@ public class ConversationSelectorRow : ListBoxRow { if (conversation.type_ == Conversation.Type.GROUPCHAT) { // TODO properly display nick for oneself - string nick = transfer.direction == Message.DIRECTION_SENT ? _("Me") : Util.get_display_name(stream_interactor, file_item.file_transfer.counterpart, conversation.account); - nick_label.label = nick + ": "; + nick_label.label = Util.get_participant_display_name(stream_interactor, conversation, file_item.file_transfer.counterpart, true) + ": "; } else { nick_label.label = transfer.direction == Message.DIRECTION_SENT ? _("Me") + ": " : ""; } diff --git a/main/src/ui/conversation_summary/chat_state_populator.vala b/main/src/ui/conversation_summary/chat_state_populator.vala index d07ab743..04eb4a20 100644 --- a/main/src/ui/conversation_summary/chat_state_populator.vala +++ b/main/src/ui/conversation_summary/chat_state_populator.vala @@ -135,11 +135,11 @@ private class MetaChatStateItem : Plugins.MetaConversationItem { private void update() { if (image == null || label == null) return; - image.set_jids(stream_interactor, jids.to_array(), conversation.account, true); + image.set_conversation_participants(stream_interactor, conversation, jids.to_array()); Gee.List display_names = new ArrayList(); foreach (Jid jid in jids) { - display_names.add(Util.get_display_name(stream_interactor, jid, conversation.account)); + display_names.add(Util.get_participant_display_name(stream_interactor, conversation, jid)); } string new_text = ""; if (jids.size > 3) { diff --git a/main/src/ui/conversation_summary/content_item_widget_factory.vala b/main/src/ui/conversation_summary/content_item_widget_factory.vala index 533b2af1..00972371 100644 --- a/main/src/ui/conversation_summary/content_item_widget_factory.vala +++ b/main/src/ui/conversation_summary/content_item_widget_factory.vala @@ -70,7 +70,7 @@ public class MessageItemWidgetGenerator : WidgetGenerator, Object { } if (message_item.message.body.has_prefix("/me")) { - string display_name = Util.get_message_display_name(stream_interactor, message, conversation.account); + string display_name = Util.get_participant_display_name(stream_interactor, conversation, message.from); update_me_style(stream_interactor, message.real_jid ?? message.from, display_name, conversation.account, label, markup_text); label.realize.connect(() => update_me_style(stream_interactor, message.real_jid ?? message.from, display_name, conversation.account, label, markup_text)); label.style_updated.connect(() => update_me_style(stream_interactor, message.real_jid ?? message.from, display_name, conversation.account, label, markup_text)); diff --git a/main/src/ui/conversation_summary/conversation_item_skeleton.vala b/main/src/ui/conversation_summary/conversation_item_skeleton.vala index 1e47fa7c..182ec298 100644 --- a/main/src/ui/conversation_summary/conversation_item_skeleton.vala +++ b/main/src/ui/conversation_summary/conversation_item_skeleton.vala @@ -29,7 +29,7 @@ public class ConversationItemSkeleton : EventBox { this.get_style_context().add_class("message-box"); if (item.requires_avatar) { - image.set_jid(stream_interactor, item.jid, conversation.account); + image.set_conversation_participant(stream_interactor, conversation, item.jid); image_content_box.add(image); } if (item.display_time != null) { @@ -137,7 +137,7 @@ public class ItemMetaDataHeader : Box { } private void update_name_label() { - string display_name = Markup.escape_text(Util.get_display_name(stream_interactor, item.jid, conversation.account)); + string display_name = Markup.escape_text(Util.get_participant_display_name(stream_interactor, conversation, item.jid)); string color = Util.get_name_hex_color(stream_interactor, conversation.account, item.jid, Util.is_dark_theme(name_label)); name_label.label = @"$display_name"; } diff --git a/main/src/ui/global_search.vala b/main/src/ui/global_search.vala index c9e475df..e92a6ac9 100644 --- a/main/src/ui/global_search.vala +++ b/main/src/ui/global_search.vala @@ -86,9 +86,15 @@ public class GlobalSearch : Overlay { foreach(SearchSuggestion suggestion in suggestions) { Builder builder = new Builder.from_resource("/im/dino/Dino/search_autocomplete.ui"); AvatarImage avatar = (AvatarImage)builder.get_object("image"); - avatar.set_jid(stream_interactor, suggestion.jid, suggestion.account); Label label = (Label)builder.get_object("label"); - string display_name = Util.get_display_name(stream_interactor, suggestion.jid, suggestion.account); + string display_name; + if (suggestion.conversation.type_ == Conversation.Type.GROUPCHAT && !suggestion.conversation.counterpart.equals(suggestion.jid) || suggestion.conversation.type_ == Conversation.Type.GROUPCHAT_PM) { + display_name = Util.get_participant_display_name(stream_interactor, suggestion.conversation, suggestion.jid); + avatar.set_conversation_participant(stream_interactor, suggestion.conversation, suggestion.jid); + } else { + display_name = Util.get_conversation_display_name(stream_interactor, suggestion.conversation); + avatar.set_conversation(stream_interactor, suggestion.conversation); + } if (display_name != suggestion.jid.to_string()) { label.set_markup(@"$display_name $(suggestion.jid)"); } else { @@ -188,7 +194,6 @@ public class GlobalSearch : Overlay { } } Label label = new Label("") { use_markup=true, xalign=0, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, vexpand=true, visible=true }; - string markup_text = Markup.escape_text(text); // Build regex containing all keywords string regex_str = "("; @@ -205,19 +210,19 @@ public class GlobalSearch : Overlay { regex_str += ")"; // Color the keywords + string markup_text = ""; try { - int elongated_by = 0; - Regex highlight_regex = new Regex(regex_str); + Regex highlight_regex = new Regex(regex_str, RegexCompileFlags.CASELESS); MatchInfo match_info; - string markup_text_bak = markup_text.down(); - highlight_regex.match(markup_text_bak, 0, out match_info); + highlight_regex.match(text, 0, out match_info); + int last_end = 0; for (; match_info.matches(); match_info.next()) { int start, end; match_info.fetch_pos(0, out start, out end); - markup_text = markup_text[0:start+elongated_by] + "" + markup_text[start+elongated_by:end+elongated_by] + "" + markup_text[end+elongated_by:markup_text.length]; - elongated_by += "".length + "".length; + markup_text += Markup.escape_text(text[last_end:start]) + "" + Markup.escape_text(text[start:end]) + ""; + last_end = end; } - markup_text_bak += ""; // We need markup_text_bak to live until here because url_regex.match does not copy the string + markup_text += Markup.escape_text(text[last_end:text.length]); } catch (RegexError e) { assert_not_reached(); } @@ -244,11 +249,11 @@ public class GlobalSearch : Overlay { private Grid get_skeleton(MessageItem item) { AvatarImage image = new AvatarImage() { height=32, width=32, margin_end=7, valign=Align.START, visible=true, allow_gray = false }; - image.set_jid(stream_interactor, item.jid, item.message.account); + image.set_conversation_participant(stream_interactor, item.conversation, item.jid); Grid grid = new Grid() { row_homogeneous=false, visible=true }; grid.attach(image, 0, 0, 1, 2); - string display_name = Util.get_display_name(stream_interactor, item.jid, item.message.account); + string display_name = Util.get_participant_display_name(stream_interactor, item.conversation, item.jid); string color = Util.get_name_hex_color(stream_interactor, item.message.account, item.jid, false); // TODO Util.is_dark_theme(name_label) Label name_label = new Label("") { use_markup=true, xalign=0, visible=true }; name_label.label = @"$display_name"; diff --git a/main/src/ui/manage_accounts/account_row.vala b/main/src/ui/manage_accounts/account_row.vala index 13a8857d..f5415d4d 100644 --- a/main/src/ui/manage_accounts/account_row.vala +++ b/main/src/ui/manage_accounts/account_row.vala @@ -17,7 +17,7 @@ public class AccountRow : Gtk.ListBoxRow { public AccountRow(StreamInteractor stream_interactor, Account account) { this.stream_interactor = stream_interactor; this.account = account; - image.set_jid(stream_interactor, account.bare_jid, account); + image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT)); jid_label.set_label(account.bare_jid.to_string()); stream_interactor.connection_manager.connection_error.connect((account, error) => { diff --git a/main/src/ui/manage_accounts/dialog.vala b/main/src/ui/manage_accounts/dialog.vala index e605e083..8685ed88 100644 --- a/main/src/ui/manage_accounts/dialog.vala +++ b/main/src/ui/manage_accounts/dialog.vala @@ -182,14 +182,14 @@ public class Dialog : Gtk.Dialog { private void on_received_avatar(Pixbuf pixbuf, Jid jid, Account account) { if (selected_account.equals(account) && jid.equals(account.bare_jid)) { - image.set_jid(stream_interactor, account.bare_jid, account); + image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT)); } } private void populate_grid_data(Account account) { active_switch.state_set.disconnect(change_account_state); - image.set_jid(stream_interactor, account.bare_jid, account); + image.set_conversation(stream_interactor, new Conversation(account.bare_jid, account, Conversation.Type.CHAT)); active_switch.set_active(account.enabled); jid_label.label = account.bare_jid.to_string(); diff --git a/main/src/ui/notifications.vala b/main/src/ui/notifications.vala index e495e629..0bc03a73 100644 --- a/main/src/ui/notifications.vala +++ b/main/src/ui/notifications.vala @@ -72,13 +72,13 @@ public class Notifications : Object { break; } if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(conversation.counterpart, conversation.account)) { - string muc_occupant = Util.get_display_name(stream_interactor, content_item.jid, conversation.account); + string muc_occupant = Util.get_participant_display_name(stream_interactor, conversation, content_item.jid); text = @"$muc_occupant: $text"; } notifications[conversation].set_title(display_name); notifications[conversation].set_body(text); try { - Cairo.ImageSurface conversation_avatar = yield (new AvatarGenerator(40, 40)).draw_conversation(stream_interactor, conversation); + Cairo.ImageSurface conversation_avatar = (yield Util.get_conversation_avatar_drawer(stream_interactor, conversation)).size(40, 40).draw_image_surface(); notifications[conversation].set_icon(get_pixbuf_icon(conversation_avatar)); } catch (Error e) { } window.get_application().send_notification(conversation.id.to_string(), notifications[conversation]); @@ -95,7 +95,7 @@ public class Notifications : Object { Notification notification = new Notification(_("Subscription request")); notification.set_body(conversation.counterpart.to_string()); try { - Cairo.ImageSurface jid_avatar = yield (new AvatarGenerator(40, 40)).draw_jid(stream_interactor, conversation.counterpart, conversation.account); + Cairo.ImageSurface jid_avatar = (yield Util.get_conversation_avatar_drawer(stream_interactor, conversation)).size(40, 40).draw_image_surface(); notification.set_icon(get_pixbuf_icon(jid_avatar)); } catch (Error e) { } notification.set_default_action_and_target_value("app.open-conversation", new Variant.int32(conversation.id)); @@ -119,21 +119,22 @@ public class Notifications : Object { } private async void on_invite_received(Account account, Jid room_jid, Jid from_jid, string? password, string? reason) { - string display_name = Util.get_display_name(stream_interactor, from_jid, account); + Conversation direct_conversation = new Conversation(from_jid, account, Conversation.Type.CHAT); + string display_name = Util.get_participant_display_name(stream_interactor, direct_conversation, from_jid); string display_room = room_jid.bare_jid.to_string(); Notification notification = new Notification(_("Invitation to %s").printf(display_room)); string body = _("%s invited you to %s").printf(display_name, display_room); notification.set_body(body); try { - Cairo.ImageSurface jid_avatar = yield (new AvatarGenerator(40, 40)).draw_jid(stream_interactor, from_jid, account); + Cairo.ImageSurface jid_avatar = (yield Util.get_conversation_avatar_drawer(stream_interactor, direct_conversation)).size(40, 40).draw_image_surface(); notification.set_icon(get_pixbuf_icon(jid_avatar)); } catch (Error e) { } - Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(room_jid, account, Conversation.Type.GROUPCHAT); - notification.set_default_action_and_target_value("app.open-muc-join", new Variant.int32(conversation.id)); - notification.add_button_with_target_value(_("Deny"), "app.deny-invite", conversation.id); - notification.add_button_with_target_value(_("Accept"), "app.open-muc-join", conversation.id); + Conversation group_conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(room_jid, account, Conversation.Type.GROUPCHAT); + notification.set_default_action_and_target_value("app.open-muc-join", new Variant.int32(group_conversation.id)); + notification.add_button_with_target_value(_("Deny"), "app.deny-invite", group_conversation.id); + notification.add_button_with_target_value(_("Accept"), "app.open-muc-join", group_conversation.id); window.get_application().send_notification(null, notification); } diff --git a/main/src/ui/occupant_menu/list.vala b/main/src/ui/occupant_menu/list.vala index f31ada93..4abc3636 100644 --- a/main/src/ui/occupant_menu/list.vala +++ b/main/src/ui/occupant_menu/list.vala @@ -57,7 +57,7 @@ public class List : Box { } public void add_occupant(Jid jid) { - rows[jid] = new ListRow(stream_interactor, conversation.account, jid); + rows[jid] = new ListRow(stream_interactor, conversation, jid); list_box.add(rows[jid]); } @@ -83,12 +83,12 @@ public class List : Box { private void header(ListBoxRow row, ListBoxRow? before_row) { ListRow c1 = row as ListRow; - Xmpp.Xep.Muc.Affiliation? a1 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.account); + Xmpp.Xep.Muc.Affiliation? a1 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.conversation.account); if (a1 == null) return; if (before_row != null) { ListRow c2 = (ListRow) before_row; - Xmpp.Xep.Muc.Affiliation? a2 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.account); + Xmpp.Xep.Muc.Affiliation? a2 = stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.conversation.account); if (a1 != a2) { row.set_header(generate_header_widget(a1, false)); } else if (row.get_header() != null){ @@ -145,8 +145,8 @@ public class List : Box { if (row1.get_type().is_a(typeof(ListRow)) && row2.get_type().is_a(typeof(ListRow))) { ListRow c1 = row1 as ListRow; ListRow c2 = row2 as ListRow; - int affiliation1 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); - int affiliation2 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); + int affiliation1 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c1.jid, c1.conversation.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); + int affiliation2 = get_affiliation_ranking(stream_interactor.get_module(MucManager.IDENTITY).get_affiliation(conversation.counterpart, c2.jid, c2.conversation.account) ?? Xmpp.Xep.Muc.Affiliation.NONE); if (affiliation1 < affiliation2) return -1; else if (affiliation1 > affiliation2) return 1; else return c1.name_label.label.collate(c2.name_label.label); diff --git a/main/src/ui/occupant_menu/list_row.vala b/main/src/ui/occupant_menu/list_row.vala index 0827ae35..92fff32f 100644 --- a/main/src/ui/occupant_menu/list_row.vala +++ b/main/src/ui/occupant_menu/list_row.vala @@ -11,15 +11,15 @@ public class ListRow : ListBoxRow { [GtkChild] private AvatarImage image; [GtkChild] public Label name_label; - public Account? account; + public Conversation? conversation; public Jid? jid; - public ListRow(StreamInteractor stream_interactor, Account account, Jid jid) { - this.account = account; + public ListRow(StreamInteractor stream_interactor, Conversation conversation, Jid jid) { + this.conversation = conversation; this.jid = jid; - name_label.label = Util.get_display_name(stream_interactor, jid, account); - image.set_jid(stream_interactor, jid, account); + name_label.label = Util.get_participant_display_name(stream_interactor, conversation, jid); + image.set_conversation_participant(stream_interactor, conversation, jid); } public ListRow.label(string c, string text) { diff --git a/main/src/ui/occupant_menu/view.vala b/main/src/ui/occupant_menu/view.vala index 119d7cfe..defc2e1c 100644 --- a/main/src/ui/occupant_menu/view.vala +++ b/main/src/ui/occupant_menu/view.vala @@ -105,7 +105,7 @@ public class View : Popover { ListRow? list_row = list.list_box.get_selected_row() as ListRow; if (list_row == null) return; - Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(list_row.jid, list_row.account, Conversation.Type.GROUPCHAT_PM); + Conversation conversation = stream_interactor.get_module(ConversationManager.IDENTITY).create_conversation(list_row.jid, list_row.conversation.account, Conversation.Type.GROUPCHAT_PM); stream_interactor.get_module(ConversationManager.IDENTITY).start_conversation(conversation); } diff --git a/main/src/ui/util/helper.vala b/main/src/ui/util/helper.vala index 521a0089..16f2d66b 100644 --- a/main/src/ui/util/helper.vala +++ b/main/src/ui/util/helper.vala @@ -12,14 +12,14 @@ private const string[] material_colors_500 = {"F44336", "E91E63", "9C27B0", "673 private const string[] material_colors_300 = {"E57373", "F06292", "BA68C8", "9575CD", "7986CB", "64B5F6", "4FC3F7", "4DD0E1", "4DB6AC", "81C784", "AED581", "DCE775", "FFD54F", "FFB74D", "FF8A65", "A1887F"}; private const string[] material_colors_200 = {"EF9A9A", "F48FB1", "CE93D8", "B39DDB", "9FA8DA", "90CAF9", "81D4FA", "80DEEA", "80CBC4", "A5D6A7", "C5E1A5", "E6EE9C", "FFE082", "FFCC80", "FFAB91", "BCAAA4"}; -public static string get_avatar_hex_color(StreamInteractor stream_interactor, Account account, Jid jid) { - uint hash = get_relevant_jid(stream_interactor, account, jid).to_string().hash(); +public static string get_avatar_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) { + uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash(); return material_colors_300[hash % material_colors_300.length]; // return tango_colors_light[name.hash() % tango_colors_light.length]; } -public static string get_name_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false) { - uint hash = get_relevant_jid(stream_interactor, account, jid).to_string().hash(); +public static string get_name_hex_color(StreamInteractor stream_interactor, Account account, Jid jid, bool dark_theme = false, Conversation? conversation = null) { + uint hash = get_relevant_jid(stream_interactor, account, jid, conversation).to_string().hash(); if (dark_theme) { return material_colors_300[hash % material_colors_300.length]; } else { @@ -28,8 +28,9 @@ public static string get_name_hex_color(StreamInteractor stream_interactor, Acco // return tango_colors_medium[name.hash() % tango_colors_medium.length]; } -private static Jid get_relevant_jid(StreamInteractor stream_interactor, Account account, Jid jid) { - if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(jid.bare_jid, account)) { +private static Jid get_relevant_jid(StreamInteractor stream_interactor, Account account, Jid jid, Conversation? conversation = null) { + Conversation conversation_ = conversation ?? stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(jid.bare_jid, account); + if (conversation_ != null && conversation_.type_ == Conversation.Type.GROUPCHAT) { Jid? real_jid = stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(jid, account); if (real_jid != null) { return real_jid.bare_jid; @@ -51,60 +52,141 @@ public static string color_for_show(string show) { } } +public static async AvatarDrawer get_conversation_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation) { + return yield get_conversation_participants_avatar_drawer(stream_interactor, conversation, new Jid[0]); +} + +public static async AvatarDrawer get_conversation_participants_avatar_drawer(StreamInteractor stream_interactor, Conversation conversation, Jid[] jids) { + AvatarManager avatar_manager = stream_interactor.get_module(AvatarManager.IDENTITY); + MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); + if (conversation.type_ != Conversation.Type.GROUPCHAT) { + Jid jid = jids.length == 1 ? jids[0] : conversation.counterpart; + Jid avatar_jid = jid; + if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid; + return new AvatarDrawer().tile(yield avatar_manager.get_avatar(conversation.account, avatar_jid), jids.length == 1 ? + get_participant_display_name(stream_interactor, conversation, jid) : + get_conversation_display_name(stream_interactor, conversation), + Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation)); + } + if (jids.length > 0) { + AvatarDrawer drawer = new AvatarDrawer(); + for (int i = 0; i < (jids.length <= 4 ? jids.length : 3); i++) { + Jid avatar_jid = jids[i]; + Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); + if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) { + avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid; + part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); + } + drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jids[i]), + Util.get_avatar_hex_color(stream_interactor, conversation.account, jids[i], conversation)); + } + if (jids.length > 4) { + drawer.plus(); + } + return drawer; + } + Gdk.Pixbuf? room_avatar = yield avatar_manager.get_avatar(conversation.account, conversation.counterpart); + Gee.List? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account); + if (room_avatar != null || !muc_manager.is_private_room(conversation.account, conversation.counterpart) || occupants == null || occupants.size == 0) { + return new AvatarDrawer().tile(room_avatar, "#", Util.get_avatar_hex_color(stream_interactor, conversation.account, conversation.counterpart, conversation)); + } + AvatarDrawer drawer = new AvatarDrawer(); + for (int i = 0; i < (occupants.size <= 4 ? occupants.size : 3); i++) { + Jid jid = occupants[i]; + Jid avatar_jid = jid; + Gdk.Pixbuf? part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); + if (part_avatar == null && avatar_jid.equals_bare(conversation.counterpart) && muc_manager.is_private_room(conversation.account, conversation.counterpart)) { + avatar_jid = muc_manager.get_real_jid(avatar_jid, conversation.account) ?? avatar_jid; + part_avatar = yield avatar_manager.get_avatar(conversation.account, avatar_jid); + } + drawer.tile(part_avatar, get_participant_display_name(stream_interactor, conversation, jid), + Util.get_avatar_hex_color(stream_interactor, conversation.account, jid, conversation)); + } + if (occupants.size > 4) { + drawer.plus(); + } + return drawer; +} + public static string get_conversation_display_name(StreamInteractor stream_interactor, Conversation conversation) { + if (conversation.type_ == Conversation.Type.CHAT) { + string? display_name = get_real_display_name(stream_interactor, conversation.account, conversation.counterpart); + if (display_name != null) return display_name; + return conversation.counterpart.to_string(); + } + if (conversation.type_ == Conversation.Type.GROUPCHAT) { + return get_groupchat_display_name(stream_interactor, conversation.account, conversation.counterpart); + } if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { - return conversation.counterpart.resourcepart + " from " + get_display_name(stream_interactor, conversation.counterpart.bare_jid, conversation.account); + return _("%s from %s").printf(get_occupant_display_name(stream_interactor, conversation.account, conversation.counterpart), get_groupchat_display_name(stream_interactor, conversation.account, conversation.counterpart.bare_jid)); } - return get_display_name(stream_interactor, conversation.counterpart, conversation.account); + return conversation.counterpart.to_string(); } -public static string get_display_name(StreamInteractor stream_interactor, Jid jid, Account account, bool fallback_to_localpart = false) { - if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(jid, account)) { - MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); - string room_name = muc_manager.get_room_name(account, jid); - if (room_name != null && room_name != jid.localpart) { - return room_name; +public static string get_participant_display_name(StreamInteractor stream_interactor, Conversation conversation, Jid participant, bool me_is_me = false) { + if (me_is_me) { + if (conversation.account.bare_jid.equals_bare(participant) || + (conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) && + conversation.nickname != null && participant.equals_bare(conversation.counterpart) && conversation.nickname == participant.resourcepart) { + return _("Me"); } - if (muc_manager.is_private_room(account, jid)) { - Gee.List? other_occupants = muc_manager.get_other_offline_members(jid, account); - if (other_occupants != null && other_occupants.size > 0) { - var builder = new StringBuilder (); - foreach(Jid occupant in other_occupants) { + } + if (conversation.type_ == Conversation.Type.CHAT) { + return get_real_display_name(stream_interactor, conversation.account, participant, me_is_me) ?? participant.bare_jid.to_string(); + } + if ((conversation.type_ == Conversation.Type.GROUPCHAT || conversation.type_ == Conversation.Type.GROUPCHAT_PM) && conversation.counterpart.equals_bare(participant)) { + return get_occupant_display_name(stream_interactor, conversation.account, participant); + } + return participant.bare_jid.to_string(); +} - if (builder.len != 0) { - builder.append(", "); - } - builder.append(get_display_name(stream_interactor, occupant, account, true).split(" ")[0]); +private static string? get_real_display_name(StreamInteractor stream_interactor, Account account, Jid jid, bool me_is_me = false) { + if (me_is_me && jid.equals_bare(account.bare_jid)) { + return _("Me"); + } + if (jid.equals_bare(account.bare_jid) && account.alias != null && account.alias.length != 0) { + return account.alias; + } + Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid); + if (roster_item != null && roster_item.name != null && roster_item.name != "") { + return roster_item.name; + } + return null; +} + +private static string get_groupchat_display_name(StreamInteractor stream_interactor, Account account, Jid jid) { + MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); + string room_name = muc_manager.get_room_name(account, jid); + if (room_name != null && room_name != jid.localpart) { + return room_name; + } + if (muc_manager.is_private_room(account, jid)) { + Gee.List? other_occupants = muc_manager.get_other_offline_members(jid, account); + if (other_occupants != null && other_occupants.size > 0) { + var builder = new StringBuilder (); + foreach(Jid occupant in other_occupants) { + if (builder.len != 0) { + builder.append(", "); } - return builder.str; + builder.append((get_real_display_name(stream_interactor, account, occupant) ?? occupant.localpart).split(" ")[0]); } - } - } else if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat_occupant(jid, account)) { - return jid.resourcepart; - } else { - if (jid.equals_bare(account.bare_jid)) { - if (account.alias == null || account.alias == "") { - return _("Me"); - } else { - return account.alias; - } - } - Roster.Item roster_item = stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, jid); - if (roster_item != null && roster_item.name != null && roster_item.name != "") { - return roster_item.name; + return builder.str; } } - - // Fallback to bare_jid / localpart - if (fallback_to_localpart && jid.localpart != null) { - return jid.localpart; - } else { - return jid.bare_jid.to_string(); - } + return jid.to_string(); } -public static string get_message_display_name(StreamInteractor stream_interactor, Entities.Message message, Account account) { - return get_display_name(stream_interactor, message.from, account); +private static string get_occupant_display_name(StreamInteractor stream_interactor, Account account, Jid jid, bool me_is_me = false) { + MucManager muc_manager = stream_interactor.get_module(MucManager.IDENTITY); + /* TODO: MUC Real JID + if (muc_manager.is_private_room(account, jid.bare_jid)) { + Jid? real_jid = muc_manager.get_real_jid(jid, account); + if (real_jid != null) { + string? display_name = get_real_display_name(stream_interactor, account, real_jid, me_is_me); + if (display_name != null) return display_name; + } + }*/ + return jid.resourcepart ?? jid.to_string(); } public static void image_set_from_scaled_pixbuf(Image image, Gdk.Pixbuf pixbuf, int scale = 0, int width = 0, int height = 0) {