From ca331e85efe2a74a6b9b5a5ff0fbcd10a36758ce Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 16 Jan 2018 16:17:42 +0100 Subject: [PATCH] Render avatar on demand --- main/CMakeLists.txt | 1 + main/data/add_conversation/list_row.ui | 7 +- main/data/contact_details_dialog.ui | 7 +- .../conversation_selector/conversation_row.ui | 6 +- main/data/manage_accounts/account_row.ui | 6 +- main/data/manage_accounts/dialog.ui | 7 +- main/data/occupant_list_item.ui | 6 +- .../ui/add_conversation/conference_list.vala | 2 +- main/src/ui/add_conversation/list_row.vala | 4 +- .../add_conversation/select_jid_fragment.vala | 2 +- main/src/ui/avatar_generator.vala | 101 +++--- main/src/ui/avatar_image.vala | 302 ++++++++++++++++++ main/src/ui/contact_details/dialog.vala | 4 +- .../conversation_row.vala | 22 +- .../conversation_item_skeleton.vala | 4 +- .../conversation_summary/image_display.vala | 17 +- main/src/ui/manage_accounts/account_row.vala | 4 +- main/src/ui/manage_accounts/dialog.vala | 6 +- main/src/ui/notifications.vala | 3 +- main/src/ui/occupant_menu/list_row.vala | 6 +- 20 files changed, 424 insertions(+), 93 deletions(-) create mode 100644 main/src/ui/avatar_image.vala diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index bc4f3570..58e1ab7d 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -82,6 +82,7 @@ SOURCES src/ui/add_conversation/select_contact_dialog.vala src/ui/add_conversation/select_jid_fragment.vala src/ui/avatar_generator.vala + src/ui/avatar_image.vala src/ui/chat_input/edit_history.vala src/ui/chat_input/encryption_button.vala src/ui/chat_input/occupants_tab_completer.vala diff --git a/main/data/add_conversation/list_row.ui b/main/data/add_conversation/list_row.ui index b9c3a47b..4b3e409f 100644 --- a/main/data/add_conversation/list_row.ui +++ b/main/data/add_conversation/list_row.ui @@ -9,9 +9,10 @@ 10 True - - 30 - 30 + + False + 30 + 30 True diff --git a/main/data/contact_details_dialog.ui b/main/data/contact_details_dialog.ui index 8ce7306d..4058bc77 100644 --- a/main/data/contact_details_dialog.ui +++ b/main/data/contact_details_dialog.ui @@ -33,10 +33,11 @@ 10 True - - 50 - 50 + + 50 + 50 True + False 0 diff --git a/main/data/conversation_selector/conversation_row.ui b/main/data/conversation_selector/conversation_row.ui index 4c73e341..fa5df4e4 100644 --- a/main/data/conversation_selector/conversation_row.ui +++ b/main/data/conversation_selector/conversation_row.ui @@ -15,9 +15,9 @@ 14 True - - 40 - 40 + + 40 + 40 True diff --git a/main/data/manage_accounts/account_row.ui b/main/data/manage_accounts/account_row.ui index 3887c51b..965171a7 100644 --- a/main/data/manage_accounts/account_row.ui +++ b/main/data/manage_accounts/account_row.ui @@ -9,9 +9,9 @@ 6 True - - 40 - 40 + + 40 + 40 True diff --git a/main/data/manage_accounts/dialog.ui b/main/data/manage_accounts/dialog.ui index 2dc2762b..8d50d449 100644 --- a/main/data/manage_accounts/dialog.ui +++ b/main/data/manage_accounts/dialog.ui @@ -102,11 +102,12 @@ - - 50 - 50 + + 50 + 50 1 True + False diff --git a/main/data/occupant_list_item.ui b/main/data/occupant_list_item.ui index b3689262..e2b35ad6 100644 --- a/main/data/occupant_list_item.ui +++ b/main/data/occupant_list_item.ui @@ -12,9 +12,9 @@ 10 True - - 30 - 30 + + 30 + 30 True diff --git a/main/src/ui/add_conversation/conference_list.vala b/main/src/ui/add_conversation/conference_list.vala index dfe80a66..b9f13432 100644 --- a/main/src/ui/add_conversation/conference_list.vala +++ b/main/src/ui/add_conversation/conference_list.vala @@ -94,7 +94,7 @@ internal class ConferenceListRow : ListRow { via_label.visible = false; } - image.set_from_pixbuf((new AvatarGenerator(35, 35)).set_stateless(true).draw_jid(stream_interactor, jid, account)); + image.set_jid(stream_interactor, jid, account); } } diff --git a/main/src/ui/add_conversation/list_row.vala b/main/src/ui/add_conversation/list_row.vala index ffc1802b..2d15c32a 100644 --- a/main/src/ui/add_conversation/list_row.vala +++ b/main/src/ui/add_conversation/list_row.vala @@ -9,7 +9,7 @@ namespace Dino.Ui { [GtkTemplate (ui = "/im/dino/Dino/add_conversation/list_row.ui")] public class ListRow : ListBoxRow { - [GtkChild] public Image image; + [GtkChild] public AvatarImage image; [GtkChild] public Label name_label; [GtkChild] public Label via_label; @@ -33,7 +33,7 @@ public class ListRow : ListBoxRow { via_label.visible = false; } name_label.label = display_name; - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(35, 35, image.scale_factor)).draw_jid(stream_interactor, jid, account)); + image.set_jid(stream_interactor, jid, account); } } diff --git a/main/src/ui/add_conversation/select_jid_fragment.vala b/main/src/ui/add_conversation/select_jid_fragment.vala index aca0a937..09792c75 100644 --- a/main/src/ui/add_conversation/select_jid_fragment.vala +++ b/main/src/ui/add_conversation/select_jid_fragment.vala @@ -92,7 +92,7 @@ public class SelectJidFragment : Gtk.Box { } else { via_label.visible = false; } - image.set_from_pixbuf((new AvatarGenerator(35, 35)).set_greyscale(true).draw_text("?")); + image.set_text("?"); } } } diff --git a/main/src/ui/avatar_generator.vala b/main/src/ui/avatar_generator.vala index b89fed21..b2138577 100644 --- a/main/src/ui/avatar_generator.vala +++ b/main/src/ui/avatar_generator.vala @@ -26,34 +26,36 @@ public class AvatarGenerator { this.scale_factor = scale_factor; } - public Pixbuf draw_jid(StreamInteractor stream_interactor, Jid jid_, Account account) { + public 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).get_avatar(account, real_jid) != null) { jid = real_jid; } - return crop_corners(draw_tile(jid, account, width * scale_factor, height * scale_factor), 3 * scale_factor); + ImageSurface surface = crop_corners(draw_tile(jid, account, width * scale_factor, height * scale_factor), 3 * scale_factor); + surface.set_device_scale(scale_factor, scale_factor); + return surface; } - public Pixbuf draw_message(StreamInteractor stream_interactor, Message message) { + public ImageSurface draw_message(StreamInteractor stream_interactor, Message message) { if (message.real_jid != null && stream_interactor.get_module(AvatarManager.IDENTITY).get_avatar(message.account, message.real_jid) != null) { return draw_jid(stream_interactor, message.real_jid, message.account); } return draw_jid(stream_interactor, message.from, message.account); } - public Pixbuf draw_conversation(StreamInteractor stream_interactor, Conversation conversation) { + public ImageSurface draw_conversation(StreamInteractor stream_interactor, Conversation conversation) { return draw_jid(stream_interactor, conversation.counterpart, conversation.account); } - public Pixbuf draw_account(StreamInteractor stream_interactor, Account account) { + public ImageSurface draw_account(StreamInteractor stream_interactor, Account account) { return draw_jid(stream_interactor, account.bare_jid, account); } - public Pixbuf draw_text(string text) { - Pixbuf pixbuf = draw_colored_rectangle_text(COLOR_GREY, text, width, height); - return crop_corners(pixbuf, 3 * scale_factor); + 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) { @@ -75,33 +77,39 @@ public class AvatarGenerator { } private void add_tile_to_pixbuf(Pixbuf pixbuf, Jid jid, Account account, int width, int height, int x, int y) { - Pixbuf tile = draw_chat_tile(jid, account, width, height); + Pixbuf tile = pixbuf_get_from_surface(draw_chat_tile(jid, account, width, height), 0, 0, width, height); tile.copy_area(0, 0, width, height, pixbuf, x, y); } - private Pixbuf draw_tile(Jid jid, Account account, int width, int height) { + private ImageSurface draw_tile(Jid jid, Account account, int width, int height) { + ImageSurface surface; if (stream_interactor.get_module(MucManager.IDENTITY).is_groupchat(jid, account)) { - return draw_groupchat_tile(jid, account, width, height); + surface = draw_groupchat_tile(jid, account, width, height); } else { - return draw_chat_tile(jid, account, width, height); + surface = draw_chat_tile(jid, account, width, height); } + return surface; } - private Pixbuf draw_chat_tile(Jid jid, Account account, int width, int height) { - Pixbuf? avatar = stream_interactor.get_module(AvatarManager.IDENTITY).get_avatar(account, jid); - if (avatar != null) { + private ImageSurface draw_chat_tile(Jid jid, Account account, int width, int height) { + Pixbuf? pixbuf = stream_interactor.get_module(AvatarManager.IDENTITY).get_avatar(account, jid); + if (pixbuf != null) { double desired_ratio = (double) width / height; - double avatar_ratio = (double) avatar.width / avatar.height; + double avatar_ratio = (double) pixbuf.width / pixbuf.height; if (avatar_ratio > desired_ratio) { - int comp_width = width * avatar.height / height; - avatar = new Pixbuf.subpixbuf(avatar, avatar.width / 2 - comp_width / 2, 0, comp_width, avatar.height); + 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 * avatar.width / width; - avatar = new Pixbuf.subpixbuf(avatar, 0, avatar.height / 2 - comp_height / 2, avatar.width, comp_height); + int comp_height = height * pixbuf.width / width; + pixbuf = new Pixbuf.subpixbuf(pixbuf, 0, pixbuf.height / 2 - comp_height / 2, pixbuf.width, comp_height); } - avatar = avatar.scale_simple(width, height, InterpType.BILINEAR); - if (greyscale) avatar = convert_to_greyscale(avatar); - return avatar; + 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); @@ -109,7 +117,7 @@ public class AvatarGenerator { } } - private Pixbuf draw_groupchat_tile(Jid jid, Account account, int width, int height) { + private 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 draw_chat_tile(jid, account, width, height); @@ -139,15 +147,18 @@ public class AvatarGenerator { if (occupants.size == 4) { 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) { - Pixbuf plus_pixbuf = draw_colored_rectangle_text("555753", "+", width / 2 - get_left_border(), height / 2 - get_left_border()); - if (greyscale) plus_pixbuf = convert_to_greyscale(plus_pixbuf); - plus_pixbuf.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()); + 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()); } } - return pixbuf; + 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 Pixbuf draw_colored_icon(string hex_color, string icon, int width, int height) { + 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)); @@ -166,14 +177,14 @@ public class AvatarGenerator { rectancle_context.paint(); } catch (Error e) { warning(@"Icon $icon does not exist"); } - return pixbuf_get_from_surface(rectancle_context.get_target(), 0, 0, width, height); + return (ImageSurface) rectancle_context.get_target(); } - public Pixbuf draw_colored_rectangle_text(string hex_color, string text, int width, int height) { + 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 pixbuf_get_from_surface(ctx.get_target(), 0, 0, width, height); + return (ImageSurface) ctx.get_target(); } private static void draw_center_text(Context ctx, string text, int fontsize, int width, int height) { @@ -194,23 +205,22 @@ public class AvatarGenerator { ctx.fill(); } - private static Pixbuf convert_to_greyscale(Pixbuf pixbuf) { - Surface surface = cairo_surface_create_from_pixbuf(pixbuf, 1, null); + 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, pixbuf.width, pixbuf.height); + 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, pixbuf.width, pixbuf.height); + context.rectangle(0, 0, surface.get_width(), surface.get_height()); context.fill(); - return pixbuf_get_from_surface(context.get_target(), 0, 0, pixbuf.width, pixbuf.height); + return (ImageSurface) context.get_target(); } - public static Pixbuf crop_corners(Pixbuf pixbuf, double radius = 3) { + 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; @@ -225,6 +235,21 @@ public class AvatarGenerator { 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); diff --git a/main/src/ui/avatar_image.vala b/main/src/ui/avatar_image.vala new file mode 100644 index 00000000..98460a57 --- /dev/null +++ b/main/src/ui/avatar_image.vala @@ -0,0 +1,302 @@ +using Gtk; +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui { + +public class AvatarImage : Misc { + public int height { get; set; default = 32; } + public int width { get; set; default = 32; } + 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 AvatarImage() { + can_focus = false; + get_style_context().add_class("avatar"); + } + + public override void get_preferred_width(out int minimum_width, out int natural_width) { + minimum_width = width; + natural_width = width; + } + + public override void get_preferred_height(out int minimum_height, out int natural_height) { + minimum_height = height; + 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 ? "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 ? "…" : 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) hex_color.substring(0, 2).to_long(null, 16) / 255, + (double) hex_color.substring(2, 2).to_long(null, 16) / 255, + (double) hex_color.substring(4, 2).to_long(null, 16) / 255, + hex_color.length > 6 ? (double) hex_color.substring(6, 2).to_long(null, 16) / 255 : 1); + } + + public override bool draw(Cairo.Context ctx) { + if (text_only == null && (current_jids == null || current_avatars == null || current_jids.length == 0)) return false; + double radius = 3; + 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), 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), 0, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 1, width - 1, height - 1, 2), width + 1, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 2, width - 1, height - 1, 2), 0, height + 1); + bufctx.paint(); + if (with_plus) { + bufctx.set_source_surface(sub_surface(ctx, -1, width - 1, height - 1, 2), width + 1, height + 1); + bufctx.paint(); + } else { + bufctx.set_source_surface(sub_surface(ctx, 3, width - 1, height - 1, 2), 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), 0, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 1, width - 1, height * 2, 2), width + 1, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 2, width - 1 , height - 1, 2), 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), 0, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 1, width - 1, height * 2, 2), 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), 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(); + } + + return true; + } + + public override void destroy() { + 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); + stream_interactor.connection_manager.connection_state_changed.disconnect(on_connection_changed); + stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.disconnect(on_roster_updated); + } + } + + 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); + } + if (muc_manager.is_groupchat(jid_, account)) { + // Groupchat + Gee.List? 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 + 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()); + } + } + } + + 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; + } + } + } + + 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); + return; + } + foreach (Jid jid_ in current_jids) { + if (jid.equals_bare(jid_)) { + set_jid(stream_interactor, this.jid, account, true); + return; + } + } + } + + private void on_connection_changed(Account account, ConnectionManager.ConnectionState state) { + if (!account.equals(this.account)) return; + set_jid(stream_interactor, this.jid, account, true); + } + + 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); + } + + 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.text_only = null; + this.gray = gray && allow_gray; + this.with_plus = with_plus; + this.current_jids = jids; + this.current_avatars = new Gdk.Pixbuf[jids.length]; + for (int i = 0; i < current_jids.length; ++i) { + Jid? real_jid = muc_manager.get_real_jid(current_jids[i], account); + if (real_jid != null) { + current_avatars[i] = avatar_manager.get_avatar(account, real_jid); + if (current_avatars[i] != null) { + current_jids[i] = real_jid; + continue; + } + } + current_avatars[i] = avatar_manager.get_avatar(account, current_jids[i]); + } + queue_draw(); + } + + 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; + queue_draw(); + } +} + +} diff --git a/main/src/ui/contact_details/dialog.vala b/main/src/ui/contact_details/dialog.vala index 422a0e0e..fd3b4751 100644 --- a/main/src/ui/contact_details/dialog.vala +++ b/main/src/ui/contact_details/dialog.vala @@ -10,7 +10,7 @@ namespace Dino.Ui.ContactDetails { [GtkTemplate (ui = "/im/dino/Dino/contact_details_dialog.ui")] public class Dialog : Gtk.Dialog { - [GtkChild] public Image avatar; + [GtkChild] public AvatarImage avatar; [GtkChild] public Util.EntryLabelHybrid name_hybrid; [GtkChild] public Label name_label; [GtkChild] public Label jid_label; @@ -70,7 +70,7 @@ public class Dialog : Gtk.Dialog { } jid_label.label = conversation.counterpart.to_string(); account_label.label = "via " + conversation.account.bare_jid.to_string(); - Util.image_set_from_scaled_pixbuf(avatar, (new AvatarGenerator(50, 50, avatar.scale_factor)).draw_conversation(stream_interactor, conversation)); + avatar.set_jid(stream_interactor, conversation.counterpart, conversation.account); } private void add_entry(string category, string label, string? description, Object wo) { diff --git a/main/src/ui/conversation_selector/conversation_row.vala b/main/src/ui/conversation_selector/conversation_row.vala index 450ae570..d79b840b 100644 --- a/main/src/ui/conversation_selector/conversation_row.vala +++ b/main/src/ui/conversation_selector/conversation_row.vala @@ -13,7 +13,7 @@ public abstract class ConversationRow : ListBoxRow { public signal void closed(); - [GtkChild] protected Image image; + [GtkChild] protected AvatarImage image; [GtkChild] protected Label name_label; [GtkChild] protected Label time_label; [GtkChild] protected Label nick_label; @@ -42,11 +42,10 @@ public abstract class ConversationRow : ListBoxRow { this.stream_interactor = stream_interactor; x_button.clicked.connect(close_conversation); + image.set_jid(stream_interactor, conversation.counterpart, conversation.account); conversation.notify["read-up-to"].connect(update_read); - stream_interactor.connection_manager.connection_state_changed.connect(update_avatar); update_name_label(); - update_avatar(); message_received(); } @@ -62,21 +61,6 @@ public abstract class ConversationRow : ListBoxRow { update_read(); } - public virtual void on_show_received(Show presence) { - update_avatar(); - } - - public void update_avatar() { - bool self_online = stream_interactor.connection_manager.get_state(conversation.account) == ConnectionManager.ConnectionState.CONNECTED; - bool counterpart_online = stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(conversation.counterpart, conversation.account) != null; - bool greyscale = !self_online || !counterpart_online; - - Pixbuf pixbuf = ((new AvatarGenerator(AVATAR_SIZE, AVATAR_SIZE, image.scale_factor)) - .set_greyscale(greyscale) - .draw_conversation(stream_interactor, conversation)); - Util.image_set_from_scaled_pixbuf(image, pixbuf, image.get_scale_factor()); - } - protected void update_name_label(string? new_name = null) { name_label.label = Util.get_conversation_display_name(stream_interactor, conversation); } @@ -91,7 +75,7 @@ public abstract class ConversationRow : ListBoxRow { protected virtual void update_message_label() { if (last_message != null) { message_label.visible = true; - message_label.label = last_message.body.replace("\n", " "); + message_label.label = (new Regex("\\s+")).replace_literal(last_message.body, -1, 0, " "); } } diff --git a/main/src/ui/conversation_summary/conversation_item_skeleton.vala b/main/src/ui/conversation_summary/conversation_item_skeleton.vala index 127f0179..33d135fc 100644 --- a/main/src/ui/conversation_summary/conversation_item_skeleton.vala +++ b/main/src/ui/conversation_summary/conversation_item_skeleton.vala @@ -9,7 +9,7 @@ namespace Dino.Ui.ConversationSummary { public class ConversationItemSkeleton : Box { - private Image image = new Image() { margin_top=2, valign=Align.START, visible=true }; + private AvatarImage image = new AvatarImage() { margin_top=2, valign=Align.START, visible=true, allow_gray = false }; public StreamInteractor stream_interactor; public Conversation conversation { get; set; } @@ -24,7 +24,7 @@ public class ConversationItemSkeleton : Box { this.stream_interactor = stream_interactor; if (item.requires_avatar) { - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(32, 32, image.scale_factor)).set_greyscale(item.dim).draw_jid(stream_interactor, item.jid, conversation.account)); + image.set_jid(stream_interactor, item.jid, conversation.account); } if (item.display_time != null) { default_header = new DefaultSkeletonHeader(stream_interactor, conversation, item) { visible=true }; diff --git a/main/src/ui/conversation_summary/image_display.vala b/main/src/ui/conversation_summary/image_display.vala index 7b77759b..c1cd32a1 100644 --- a/main/src/ui/conversation_summary/image_display.vala +++ b/main/src/ui/conversation_summary/image_display.vala @@ -54,7 +54,7 @@ public class ImageDisplay : Plugins.MetaConversationItem { if (pixbuf.width > max_scaled_width) { pixbuf = pixbuf.scale_simple(max_scaled_width, (int) ((double) max_scaled_width / pixbuf.width * pixbuf.height), Gdk.InterpType.BILINEAR); } - pixbuf = AvatarGenerator.crop_corners(pixbuf, 3 * image.get_scale_factor()); + pixbuf = crop_corners(pixbuf, 3 * image.get_scale_factor()); Util.image_set_from_scaled_pixbuf(image, pixbuf); Util.force_css(image, "* { box-shadow: 0px 0px 2px 0px rgba(0,0,0,0.1); margin: 2px; border-radius: 3px; }"); @@ -103,6 +103,21 @@ public class ImageDisplay : Plugins.MetaConversationItem { return event_box; } + private static Gdk.Pixbuf crop_corners(Gdk.Pixbuf pixbuf, double radius = 3) { + Cairo.Context ctx = new Cairo.Context(new Cairo.ImageSurface(Cairo.Format.ARGB32, pixbuf.width, pixbuf.height)); + Gdk.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 Gdk.pixbuf_get_from_surface(ctx.get_target(), 0, 0, pixbuf.width, pixbuf.height); + } + private void update_info(Label url_label, string? info) { string url = info ?? ""; if (url.has_prefix("https://")) url = url.substring(8); diff --git a/main/src/ui/manage_accounts/account_row.vala b/main/src/ui/manage_accounts/account_row.vala index 04fad66f..13a8857d 100644 --- a/main/src/ui/manage_accounts/account_row.vala +++ b/main/src/ui/manage_accounts/account_row.vala @@ -7,7 +7,7 @@ namespace Dino.Ui.ManageAccounts { [GtkTemplate (ui = "/im/dino/Dino/manage_accounts/account_row.ui")] public class AccountRow : Gtk.ListBoxRow { - [GtkChild] public Image image; + [GtkChild] public AvatarImage image; [GtkChild] public Label jid_label; [GtkChild] public Image icon; @@ -17,7 +17,7 @@ public class AccountRow : Gtk.ListBoxRow { public AccountRow(StreamInteractor stream_interactor, Account account) { this.stream_interactor = stream_interactor; this.account = account; - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(40, 40, image.scale_factor)).draw_account(stream_interactor, account)); + image.set_jid(stream_interactor, account.bare_jid, account); 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 6fb0c427..8f2b76a7 100644 --- a/main/src/ui/manage_accounts/dialog.vala +++ b/main/src/ui/manage_accounts/dialog.vala @@ -19,7 +19,7 @@ public class Dialog : Gtk.Dialog { [GtkChild] public Button no_accounts_add; [GtkChild] public ToolButton add_account_button; [GtkChild] public ToolButton remove_account_button; - [GtkChild] public Image image; + [GtkChild] public AvatarImage image; [GtkChild] public Button image_button; [GtkChild] public Label jid_label; [GtkChild] public Label state_label; @@ -185,14 +185,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)) { - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(50, 50, image.scale_factor)).draw_account(stream_interactor, account)); + image.set_jid(stream_interactor, account.bare_jid, account); } } private void populate_grid_data(Account account) { active_switch.state_set.disconnect(change_account_state); - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(50, 50, image.scale_factor)).draw_account(stream_interactor, account)); + image.set_jid(stream_interactor, account.bare_jid, account); 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 d9f70668..246452ea 100644 --- a/main/src/ui/notifications.vala +++ b/main/src/ui/notifications.vala @@ -96,7 +96,8 @@ public class Notifications : Object { return true; } - private Icon get_pixbuf_icon(Gdk.Pixbuf avatar) throws Error { + private Icon get_pixbuf_icon(Cairo.ImageSurface surface) throws Error { + Gdk.Pixbuf avatar = Gdk.pixbuf_get_from_surface(surface, 0, 0, surface.get_width(), surface.get_height()); uint8[] buffer; avatar.save_to_buffer(out buffer, "png"); return new BytesIcon(new Bytes(buffer)); diff --git a/main/src/ui/occupant_menu/list_row.vala b/main/src/ui/occupant_menu/list_row.vala index 762c281b..0827ae35 100644 --- a/main/src/ui/occupant_menu/list_row.vala +++ b/main/src/ui/occupant_menu/list_row.vala @@ -8,7 +8,7 @@ namespace Dino.Ui.OccupantMenu { [GtkTemplate (ui = "/im/dino/Dino/occupant_list_item.ui")] public class ListRow : ListBoxRow { - [GtkChild] private Image image; + [GtkChild] private AvatarImage image; [GtkChild] public Label name_label; public Account? account; @@ -19,12 +19,12 @@ public class ListRow : ListBoxRow { this.jid = jid; name_label.label = Util.get_display_name(stream_interactor, jid, account); - Util.image_set_from_scaled_pixbuf(image, (new AvatarGenerator(30, 30, image.scale_factor)).draw_jid(stream_interactor, jid, account)); + image.set_jid(stream_interactor, jid, account); } public ListRow.label(string c, string text) { name_label.label = text; - image.set_from_pixbuf((new AvatarGenerator(30, 30, 1)).set_greyscale(true).draw_text(c)); // why 1 + image.set_text(c); } }