df57a87fb1a8d0d11cf73a0e6bf0c045493efc17
[empathy.git] / libempathy-gtk / empathy-roster-contact.c
1 #include "config.h"
2 #include "empathy-roster-contact.h"
3
4 #include <glib/gi18n-lib.h>
5 #include <tp-account-widgets/tpaw-images.h>
6 #include <tp-account-widgets/tpaw-pixbuf-utils.h>
7
8 #include "empathy-ui-utils.h"
9 #include "empathy-utils.h"
10
11 G_DEFINE_TYPE (EmpathyRosterContact, empathy_roster_contact, GTK_TYPE_LIST_BOX_ROW)
12
13 #define AVATAR_SIZE 48
14
15 enum
16 {
17   PROP_INDIVIDIUAL = 1,
18   PROP_GROUP,
19   PROP_ONLINE,
20   PROP_ALIAS,
21   N_PROPS
22 };
23
24 /*
25 enum
26 {
27   LAST_SIGNAL
28 };
29
30 static guint signals[LAST_SIGNAL];
31 */
32
33 struct _EmpathyRosterContactPriv
34 {
35   FolksIndividual *individual;
36   gchar *group;
37
38   GtkWidget *avatar;
39   GtkWidget *first_line_alig;
40   GtkWidget *alias;
41   GtkWidget *presence_msg;
42   GtkWidget *presence_icon;
43   GtkWidget *phone_icon;
44
45   /* If not NULL, used instead of the individual's presence icon */
46   gchar *event_icon;
47
48   gboolean online;
49 };
50
51 static const gchar *
52 get_alias (EmpathyRosterContact *self)
53 {
54   return folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (
55         self->priv->individual));
56 }
57
58 static void
59 empathy_roster_contact_get_property (GObject *object,
60     guint property_id,
61     GValue *value,
62     GParamSpec *pspec)
63 {
64   EmpathyRosterContact *self = EMPATHY_ROSTER_CONTACT (object);
65
66   switch (property_id)
67     {
68       case PROP_INDIVIDIUAL:
69         g_value_set_object (value, self->priv->individual);
70         break;
71       case PROP_GROUP:
72         g_value_set_string (value, self->priv->group);
73         break;
74       case PROP_ONLINE:
75         g_value_set_boolean (value, self->priv->online);
76         break;
77       case PROP_ALIAS:
78         g_value_set_string (value, get_alias (self));
79         break;
80       default:
81         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
82         break;
83     }
84 }
85
86 static void
87 empathy_roster_contact_set_property (GObject *object,
88     guint property_id,
89     const GValue *value,
90     GParamSpec *pspec)
91 {
92   EmpathyRosterContact *self = EMPATHY_ROSTER_CONTACT (object);
93
94   switch (property_id)
95     {
96       case PROP_INDIVIDIUAL:
97         g_assert (self->priv->individual == NULL); /* construct only */
98         self->priv->individual = g_value_dup_object (value);
99         break;
100       case PROP_GROUP:
101         g_assert (self->priv->group == NULL); /* construct only */
102         self->priv->group = g_value_dup_string (value);
103         break;
104       default:
105         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
106         break;
107     }
108 }
109
110 static void
111 avatar_loaded_cb (GObject *source,
112     GAsyncResult *result,
113     gpointer user_data)
114 {
115   TpWeakRef *wr = user_data;
116   EmpathyRosterContact *self;
117   GdkPixbuf *pixbuf;
118
119   self = tp_weak_ref_dup_object (wr);
120   if (self == NULL)
121     goto out;
122
123   pixbuf = empathy_pixbuf_avatar_from_individual_scaled_finish (
124       FOLKS_INDIVIDUAL (source), result, NULL);
125
126   if (pixbuf == NULL)
127     {
128       pixbuf = tpaw_pixbuf_from_icon_name_sized (
129           TPAW_IMAGE_AVATAR_DEFAULT, AVATAR_SIZE);
130     }
131
132   gtk_image_set_from_pixbuf (GTK_IMAGE (self->priv->avatar), pixbuf);
133   g_object_unref (pixbuf);
134
135   g_object_unref (self);
136
137 out:
138   tp_weak_ref_destroy (wr);
139 }
140
141 static void
142 update_avatar (EmpathyRosterContact *self)
143 {
144   empathy_pixbuf_avatar_from_individual_scaled_async (self->priv->individual,
145       AVATAR_SIZE, AVATAR_SIZE, NULL, avatar_loaded_cb,
146       tp_weak_ref_new (self, NULL, NULL));
147 }
148
149 static void
150 avatar_changed_cb (FolksIndividual *individual,
151     GParamSpec *spec,
152     EmpathyRosterContact *self)
153 {
154   update_avatar (self);
155 }
156
157 static void
158 update_alias (EmpathyRosterContact *self)
159 {
160   gtk_label_set_text (GTK_LABEL (self->priv->alias), get_alias (self));
161
162   g_object_notify (G_OBJECT (self), "alias");
163 }
164
165 static void
166 alias_changed_cb (FolksIndividual *individual,
167     GParamSpec *spec,
168     EmpathyRosterContact *self)
169 {
170   update_alias (self);
171 }
172
173 static void
174 update_presence_msg (EmpathyRosterContact *self)
175 {
176   const gchar *msg;
177   GStrv types;
178
179   msg = folks_presence_details_get_presence_message (
180       FOLKS_PRESENCE_DETAILS (self->priv->individual));
181
182   if (tp_str_empty (msg))
183     {
184       /* Just display the alias in the center of the row */
185       gtk_alignment_set (GTK_ALIGNMENT (self->priv->first_line_alig),
186           0, 0.5, 1, 1);
187
188       gtk_widget_hide (self->priv->presence_msg);
189     }
190   else
191     {
192       FolksPresenceType type;
193
194       type = folks_presence_details_get_presence_type (
195           FOLKS_PRESENCE_DETAILS (self->priv->individual));
196       if (type == FOLKS_PRESENCE_TYPE_ERROR)
197         {
198           gchar *tmp;
199
200           /* Add a prefix explaining that something goes wrong when trying to
201            * fetch contact's presence. */
202           tmp = g_strdup_printf (_("Server cannot find contact: %s"), msg);
203           gtk_label_set_text (GTK_LABEL (self->priv->presence_msg), tmp);
204
205           g_free (tmp);
206         }
207       else
208         {
209           gtk_label_set_text (GTK_LABEL (self->priv->presence_msg), msg);
210         }
211
212       gtk_alignment_set (GTK_ALIGNMENT (self->priv->first_line_alig),
213           0, 0.75, 1, 1);
214       gtk_misc_set_alignment (GTK_MISC (self->priv->presence_msg), 0, 0.25);
215
216       gtk_widget_show (self->priv->presence_msg);
217     }
218
219   types = (GStrv) empathy_individual_get_client_types (self->priv->individual);
220
221   gtk_widget_set_visible (self->priv->phone_icon,
222       empathy_client_types_contains_mobile_device (types));
223 }
224
225 static void
226 presence_message_changed_cb (FolksIndividual *individual,
227     GParamSpec *spec,
228     EmpathyRosterContact *self)
229 {
230   update_presence_msg (self);
231 }
232
233 static void
234 update_presence_icon (EmpathyRosterContact *self)
235 {
236   const gchar *icon;
237
238   if (self->priv->event_icon == NULL)
239     icon = empathy_icon_name_for_individual (self->priv->individual);
240   else
241     icon = self->priv->event_icon;
242
243   gtk_image_set_from_icon_name (GTK_IMAGE (self->priv->presence_icon), icon,
244       GTK_ICON_SIZE_MENU);
245 }
246
247 static void
248 update_online (EmpathyRosterContact *self)
249 {
250   FolksPresenceType presence;
251   gboolean online;
252
253   presence = folks_presence_details_get_presence_type (
254       FOLKS_PRESENCE_DETAILS (self->priv->individual));
255
256   switch (presence)
257     {
258       case FOLKS_PRESENCE_TYPE_UNSET:
259       case FOLKS_PRESENCE_TYPE_OFFLINE:
260       case FOLKS_PRESENCE_TYPE_UNKNOWN:
261       case FOLKS_PRESENCE_TYPE_ERROR:
262         online = FALSE;
263         break;
264
265       case FOLKS_PRESENCE_TYPE_AVAILABLE:
266       case FOLKS_PRESENCE_TYPE_AWAY:
267       case FOLKS_PRESENCE_TYPE_EXTENDED_AWAY:
268       case FOLKS_PRESENCE_TYPE_HIDDEN:
269       case FOLKS_PRESENCE_TYPE_BUSY:
270         online = TRUE;
271         break;
272
273       default:
274         g_warning ("Unknown FolksPresenceType: %d", presence);
275         online = FALSE;
276     }
277
278   if (self->priv->online == online)
279     return;
280
281   self->priv->online = online;
282   g_object_notify (G_OBJECT (self), "online");
283 }
284
285 static void
286 presence_status_changed_cb (FolksIndividual *individual,
287     GParamSpec *spec,
288     EmpathyRosterContact *self)
289 {
290   update_presence_icon (self);
291   update_online (self);
292 }
293
294 static void
295 empathy_roster_contact_constructed (GObject *object)
296 {
297   EmpathyRosterContact *self = EMPATHY_ROSTER_CONTACT (object);
298   void (*chain_up) (GObject *) =
299       ((GObjectClass *) empathy_roster_contact_parent_class)->constructed;
300
301   if (chain_up != NULL)
302     chain_up (object);
303
304   g_assert (FOLKS_IS_INDIVIDUAL (self->priv->individual));
305
306   tp_g_signal_connect_object (self->priv->individual, "notify::avatar",
307       G_CALLBACK (avatar_changed_cb), self, 0);
308   tp_g_signal_connect_object (self->priv->individual, "notify::alias",
309       G_CALLBACK (alias_changed_cb), self, 0);
310   tp_g_signal_connect_object (self->priv->individual,
311       "notify::presence-message",
312       G_CALLBACK (presence_message_changed_cb), self, 0);
313   tp_g_signal_connect_object (self->priv->individual, "notify::presence-status",
314       G_CALLBACK (presence_status_changed_cb), self, 0);
315
316   update_avatar (self);
317   update_alias (self);
318   update_presence_msg (self);
319   update_presence_icon (self);
320
321   update_online (self);
322 }
323
324 static void
325 empathy_roster_contact_dispose (GObject *object)
326 {
327   EmpathyRosterContact *self = EMPATHY_ROSTER_CONTACT (object);
328   void (*chain_up) (GObject *) =
329       ((GObjectClass *) empathy_roster_contact_parent_class)->dispose;
330
331   g_clear_object (&self->priv->individual);
332
333   if (chain_up != NULL)
334     chain_up (object);
335 }
336
337 static void
338 empathy_roster_contact_finalize (GObject *object)
339 {
340   EmpathyRosterContact *self = EMPATHY_ROSTER_CONTACT (object);
341   void (*chain_up) (GObject *) =
342       ((GObjectClass *) empathy_roster_contact_parent_class)->finalize;
343
344   g_free (self->priv->group);
345   g_free (self->priv->event_icon);
346
347   if (chain_up != NULL)
348     chain_up (object);
349 }
350
351 static void
352 empathy_roster_contact_class_init (
353     EmpathyRosterContactClass *klass)
354 {
355   GObjectClass *oclass = G_OBJECT_CLASS (klass);
356   GParamSpec *spec;
357
358   oclass->get_property = empathy_roster_contact_get_property;
359   oclass->set_property = empathy_roster_contact_set_property;
360   oclass->constructed = empathy_roster_contact_constructed;
361   oclass->dispose = empathy_roster_contact_dispose;
362   oclass->finalize = empathy_roster_contact_finalize;
363
364   spec = g_param_spec_object ("individual", "Individual",
365       "FolksIndividual",
366       FOLKS_TYPE_INDIVIDUAL,
367       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
368   g_object_class_install_property (oclass, PROP_INDIVIDIUAL, spec);
369
370   spec = g_param_spec_string ("group", "Group",
371       "Group of this widget, or NULL",
372       NULL,
373       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
374   g_object_class_install_property (oclass, PROP_GROUP, spec);
375
376   spec = g_param_spec_boolean ("online", "Online",
377       "TRUE if Individual is online",
378       FALSE,
379       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
380   g_object_class_install_property (oclass, PROP_ONLINE, spec);
381
382   spec = g_param_spec_string ("alias", "Alias",
383       "The Alias of the individual displayed in the widget",
384       NULL,
385       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
386   g_object_class_install_property (oclass, PROP_ALIAS, spec);
387
388   g_type_class_add_private (klass, sizeof (EmpathyRosterContactPriv));
389 }
390
391 static void
392 empathy_roster_contact_init (EmpathyRosterContact *self)
393 {
394   GtkWidget *alig, *main_box, *box, *first_line_box;
395   GtkStyleContext *context;
396
397   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
398       EMPATHY_TYPE_ROSTER_CONTACT, EmpathyRosterContactPriv);
399
400   alig = gtk_alignment_new (0.5, 0.5, 1, 1);
401   gtk_widget_show (alig);
402   gtk_alignment_set_padding (GTK_ALIGNMENT (alig), 4, 4, 4, 12);
403
404   main_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8);
405
406   /* Avatar */
407   self->priv->avatar = gtk_image_new ();
408
409   gtk_widget_set_size_request (self->priv->avatar, AVATAR_SIZE, AVATAR_SIZE);
410
411   gtk_box_pack_start (GTK_BOX (main_box), self->priv->avatar, FALSE, FALSE, 0);
412   gtk_widget_show (self->priv->avatar);
413
414   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
415
416   /* Alias and phone icon */
417   self->priv->first_line_alig = gtk_alignment_new (0, 0.5, 1, 1);
418   first_line_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
419
420   self->priv->alias = gtk_label_new (NULL);
421   gtk_label_set_ellipsize (GTK_LABEL (self->priv->alias), PANGO_ELLIPSIZE_END);
422   gtk_box_pack_start (GTK_BOX (first_line_box), self->priv->alias,
423       FALSE, FALSE, 0);
424   gtk_misc_set_alignment (GTK_MISC (self->priv->alias), 0, 0.5);
425   gtk_widget_show (self->priv->alias);
426
427   self->priv->phone_icon = gtk_image_new_from_icon_name ("phone-symbolic",
428       GTK_ICON_SIZE_MENU);
429   gtk_misc_set_alignment (GTK_MISC (self->priv->phone_icon), 0, 0.5);
430   gtk_box_pack_start (GTK_BOX (first_line_box), self->priv->phone_icon,
431       TRUE, TRUE, 0);
432
433   gtk_container_add (GTK_CONTAINER (self->priv->first_line_alig),
434       first_line_box);
435   gtk_widget_show (self->priv->first_line_alig);
436
437   gtk_box_pack_start (GTK_BOX (box), self->priv->first_line_alig,
438       TRUE, TRUE, 0);
439   gtk_widget_show (first_line_box);
440
441   gtk_box_pack_start (GTK_BOX (main_box), box, TRUE, TRUE, 0);
442   gtk_widget_show (box);
443
444   /* Presence */
445   self->priv->presence_msg = gtk_label_new (NULL);
446   gtk_label_set_ellipsize (GTK_LABEL (self->priv->presence_msg),
447       PANGO_ELLIPSIZE_END);
448   gtk_box_pack_start (GTK_BOX (box), self->priv->presence_msg, TRUE, TRUE, 0);
449   gtk_widget_show (self->priv->presence_msg);
450
451   context = gtk_widget_get_style_context (self->priv->presence_msg);
452   gtk_style_context_add_class (context, GTK_STYLE_CLASS_DIM_LABEL);
453
454   /* Presence icon */
455   self->priv->presence_icon = gtk_image_new ();
456
457   gtk_box_pack_start (GTK_BOX (main_box), self->priv->presence_icon,
458       FALSE, FALSE, 0);
459   gtk_widget_show (self->priv->presence_icon);
460
461   gtk_container_add (GTK_CONTAINER (self), alig);
462   gtk_container_add (GTK_CONTAINER (alig), main_box);
463   gtk_widget_show (main_box);
464 }
465
466 GtkWidget *
467 empathy_roster_contact_new (FolksIndividual *individual,
468     const gchar *group)
469 {
470   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
471
472   return g_object_new (EMPATHY_TYPE_ROSTER_CONTACT,
473       "individual", individual,
474       "group", group,
475       NULL);
476 }
477
478 FolksIndividual *
479 empathy_roster_contact_get_individual (EmpathyRosterContact *self)
480 {
481   return self->priv->individual;
482 }
483
484 gboolean
485 empathy_roster_contact_is_online (EmpathyRosterContact *self)
486 {
487   return self->priv->online;
488 }
489
490 const gchar *
491 empathy_roster_contact_get_group (EmpathyRosterContact *self)
492 {
493   return self->priv->group;
494 }
495
496 void
497 empathy_roster_contact_set_event_icon (EmpathyRosterContact *self,
498     const gchar *icon)
499 {
500   if (!tp_strdiff (self->priv->event_icon, icon))
501     return;
502
503   g_free (self->priv->event_icon);
504   self->priv->event_icon = g_strdup (icon);
505
506   update_presence_icon (self);
507 }
508
509 GdkPixbuf *
510 empathy_roster_contact_get_avatar_pixbuf (EmpathyRosterContact *self)
511 {
512   return gtk_image_get_pixbuf (GTK_IMAGE (self->priv->avatar));
513 }