using Gee; using Gtk; private class SizeCache { private static SizeCache instance = null; private HashMap size_cache = new HashMap(); public static SizeCache get_instance() { if (instance == null) { instance = new SizeCache(); } return instance; } public Graphene.Size? get_size(string attachment_guid) { return size_cache.get(attachment_guid); } public void set_size(string attachment_guid, Graphene.Size size) { size_cache.set(attachment_guid, size); } } private class TextureCache { private static TextureCache instance = null; private HashMap texture_cache = new HashMap(); public static TextureCache get_instance() { if (instance == null) { instance = new TextureCache(); } return instance; } public Gdk.Texture? get_texture(string attachment_guid) { return texture_cache.get(attachment_guid); } public void set_texture(string attachment_guid, Gdk.Texture texture) { texture_cache.set(attachment_guid, texture); } } private class ImageBubbleLayout : BubbleLayout { public string attachment_guid; public bool is_downloaded; private Graphene.Size image_size; private Gdk.Texture? cached_texture = null; private bool preview_download_queued = false; public ImageBubbleLayout(string attachment_guid, bool from_me, Widget parent, float max_width, Graphene.Size? image_size = null) { base(parent, max_width); this.from_me = from_me; this.attachment_guid = attachment_guid; this.is_downloaded = false; this.cached_texture = TextureCache.get_instance().get_texture(attachment_guid); // Calculate image dimensions for layout calculate_image_dimensions(image_size); } private void calculate_image_dimensions(Graphene.Size? image_size) { if (image_size != null) { this.image_size = image_size; return; } var cached_size = SizeCache.get_instance().get_size(attachment_guid); if (cached_size != null) { this.image_size = cached_size; return; } this.image_size = Graphene.Size() { width = 200.0f, height = 150.0f }; } private void queue_preview_download_if_needed() { if (is_downloaded || preview_download_queued || attachment_guid == "") { return; } try { Repository.get_instance().download_attachment(attachment_guid, true); preview_download_queued = true; } catch (GLib.Error e) { warning("Failed to queue preview download for %s: %s", attachment_guid, e.message); } } private void load_image_if_needed() { if (cached_texture != null) { return; } if (!is_downloaded) { return; } try { int fd = Repository.get_instance().open_attachment_fd(attachment_guid, true); var stream = new UnixInputStream(fd, true); var pixbuf = new Gdk.Pixbuf.from_stream(stream, null); cached_texture = Gdk.Texture.for_pixbuf(pixbuf); if (cached_texture != null) { TextureCache.get_instance().set_texture(attachment_guid, cached_texture); this.image_size = Graphene.Size() { width = (float)cached_texture.get_width(), height = (float)cached_texture.get_height() }; SizeCache.get_instance().set_size(attachment_guid, this.image_size); parent.queue_allocate(); } } catch (Error e) { warning("Failed to load preview image for %s: %s", attachment_guid, e.message); } } private float intrinsic_height { get { var scale_factor = float.min(max_width / image_size.width, 1.0f); return image_size.height * scale_factor; } } private float intrinsic_width { get { var scale_factor = float.min(max_width / image_size.width, 1.0f); return image_size.width * scale_factor; } } public override float get_height() { return float.max(intrinsic_height, 100.0f); } public override float get_width() { return float.max(intrinsic_width, 200.0f); } public override void draw_content(Snapshot snapshot) { queue_preview_download_if_needed(); load_image_if_needed(); snapshot.save(); var image_rect = Graphene.Rect () { origin = Graphene.Point() { x = 0, y = 0 }, size = Graphene.Size() { width = intrinsic_width, height = intrinsic_height } }; // Center image in the bubble (if it's smaller than the bubble) snapshot.translate(Graphene.Point() { x = (get_width() - intrinsic_width) / 2, y = (get_height() - intrinsic_height) / 2 }); if (cached_texture != null) { snapshot.append_texture(cached_texture, image_rect); } else { snapshot.append_color(Gdk.RGBA() { red = 0.6f, green = 0.6f, blue = 0.6f, alpha = 0.5f }, image_rect); } snapshot.restore(); } public override void copy(Gdk.Clipboard clipboard) { clipboard.set_texture(cached_texture); } }