/* * Copyright (C) 2011 Alexander Larsson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Gtk; using Folks; using Gee; /** * The Avatar of a Contact is responsible for showing an {@link Folks.Individual}'s * avatar, or a fallback if it's not available. */ public class Contacts.Avatar : DrawingArea { private int size; private Gdk.Pixbuf? pixbuf = null; private Contact? contact = null; // We want to lazily load the Pixbuf to make sure we don't draw all contact avatars at once. // As long as there is no need for it to be drawn, keep this to false. private bool avatar_loaded = false; // The background color used in case of a fallback avatar private Gdk.RGBA? bg_color = null; // The color used for an initial or the fallback icon private const Gdk.RGBA fg_color = { 0, 0, 0, 0.25 }; public Avatar (int size, Contact? contact = null) { this.contact = contact; if (contact != null) { contact.individual.notify["avatar"].connect ( (s, p) => { load_avatar.begin (); }); } this.size = size; set_size_request (size, size); // If we don't have an avatar, don't try to load it later this.avatar_loaded = (contact == null || contact.individual.avatar == null); show (); } /** * Manually set the avatar to the given pixbuf, even if the contact has an avatar. */ public void set_pixbuf (Gdk.Pixbuf? a_pixbuf) { this.pixbuf = a_pixbuf; queue_draw (); } private async void load_avatar () { assert (this.contact != null); this.avatar_loaded = true; try { var stream = yield this.contact.individual.avatar.load_async (this.size); this.pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async (stream, this.size, this.size, true); queue_draw (); } catch (Error e) { debug ("Couldn't load avatar of contact %s. Reason: %s", this.contact.individual.display_name, e.message); } } public override bool draw (Cairo.Context cr) { // This exists to implement lazy loading: i.e. only load the avatar on the first draw() if (!this.avatar_loaded) load_avatar.begin (); if (this.pixbuf != null) draw_contact_avatar (cr); else // No avatar available, draw a fallback draw_fallback (cr); return true; } private void draw_contact_avatar (Cairo.Context cr) { Gdk.cairo_set_source_pixbuf (cr, this.pixbuf, 0, 0); // Clip with a circle create_circle (cr); cr.clip_preserve (); cr.paint (); } private void draw_fallback (Cairo.Context cr) { // The background color if (this.bg_color == null) calculate_color (); // Fill the background circle cr.set_source_rgb (this.bg_color.red, this.bg_color.green, this.bg_color.blue); cr.arc (this.size / 2, this.size / 2, this.size / 2, 0, 2*Math.PI); create_circle (cr); cr.fill_preserve (); // Draw the icon try { // FIXME we can probably cache this var theme = IconTheme.get_default (); var fallback_avatar = theme.lookup_icon ("avatar-default", this.size * 4 / 5, IconLookupFlags.FORCE_SYMBOLIC); var icon_pixbuf = fallback_avatar.load_symbolic (fg_color); create_circle (cr); cr.clip_preserve (); Gdk.cairo_set_source_pixbuf (cr, icon_pixbuf, 1 + this.size / 10, 1 + this.size / 5); cr.paint (); } catch (Error e) { warning ("Couldn't get default avatar icon: %s", e.message); } } private void calculate_color () { // We use the hash of the id so we get the same color each time for the same contact var hash = (this.contact != null)? str_hash (this.contact.individual.id) : Gdk.CURRENT_TIME; var r = ((hash & 0xFF0000) >> 16) / 255.0; var g = ((hash & 0x00FF00) >> 8) / 255.0; var b = (hash & 0x0000FF) / 255.0; // Make it a bit lighter by default (and since the foreground will be darker) this.bg_color = Gdk.RGBA () { red = (r + 2) / 3.0, green = (g + 2) / 3.0, blue = (b + 2) / 3.0, alpha = 0 }; } private void create_circle (Cairo.Context cr) { cr.arc (this.size / 2, this.size / 2, this.size / 2, 0, 2*Math.PI); } }