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