]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-persona-view.c
Display Personas' accounts in the linking dialogue
[empathy.git] / libempathy-gtk / empathy-persona-view.c
1 /*
2  * Copyright (C) 2005-2007 Imendio AB
3  * Copyright (C) 2007-2008, 2010 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Martyn Russell <martyn@imendio.com>
22  *          Xavier Claessens <xclaesse@gmail.com>
23  *          Philip Withnall <philip.withnall@collabora.co.uk>
24  *
25  * Based off EmpathyContactListView.
26  */
27
28 #include "config.h"
29
30 #include <string.h>
31
32 #include <glib/gi18n-lib.h>
33 #include <gtk/gtk.h>
34
35 #include <telepathy-glib/util.h>
36
37 #include <folks/folks.h>
38 #include <folks/folks-telepathy.h>
39
40 #include <libempathy/empathy-utils.h>
41
42 #include "empathy-persona-view.h"
43 #include "empathy-contact-widget.h"
44 #include "empathy-images.h"
45 #include "empathy-cell-renderer-text.h"
46 #include "empathy-cell-renderer-activatable.h"
47
48 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
49 #include <libempathy/empathy-debug.h>
50
51 /**
52  * SECTION:empathy-persona-view
53  * @title: EmpathyPersonaView
54  * @short_description: A tree view which displays personas from an individual
55  * @include: libempathy-gtk/empathy-persona-view.h
56  *
57  * #EmpathyPersonaView is a tree view widget which displays the personas from
58  * a given #EmpathyPersonaStore.
59  *
60  * It supports hiding offline personas and highlighting active personas. Active
61  * personas are those which have recently changed state (e.g. online, offline or
62  * from normal to a busy state).
63  */
64
65 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyPersonaView)
66
67 typedef struct
68 {
69   GtkTreeModelFilter *filter;
70   GtkWidget *tooltip_widget;
71   gboolean show_offline;
72 } EmpathyPersonaViewPriv;
73
74 enum
75 {
76   PROP_0,
77   PROP_MODEL,
78   PROP_SHOW_OFFLINE,
79 };
80
81 G_DEFINE_TYPE (EmpathyPersonaView, empathy_persona_view, GTK_TYPE_TREE_VIEW);
82
83 static gboolean
84 filter_visible_func (GtkTreeModel *model,
85     GtkTreeIter *iter,
86     EmpathyPersonaView *self)
87 {
88   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
89   gboolean is_online;
90
91   gtk_tree_model_get (model, iter,
92       EMPATHY_PERSONA_STORE_COL_IS_ONLINE, &is_online,
93       -1);
94
95   return (priv->show_offline || is_online);
96 }
97
98 static void
99 set_model (EmpathyPersonaView *self,
100     GtkTreeModel *model)
101 {
102   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
103
104   tp_clear_object (&priv->filter);
105
106   priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model,
107       NULL));
108   gtk_tree_model_filter_set_visible_func (priv->filter,
109       (GtkTreeModelFilterVisibleFunc) filter_visible_func, self, NULL);
110
111   gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (priv->filter));
112 }
113
114 static void
115 tooltip_destroy_cb (GtkWidget *widget,
116     EmpathyPersonaView *self)
117 {
118   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
119
120   if (priv->tooltip_widget)
121     {
122       DEBUG ("Tooltip destroyed");
123       g_object_unref (priv->tooltip_widget);
124       priv->tooltip_widget = NULL;
125     }
126 }
127
128 static gboolean
129 query_tooltip_cb (EmpathyPersonaView *self,
130     gint x,
131     gint y,
132     gboolean keyboard_mode,
133     GtkTooltip *tooltip,
134     gpointer user_data)
135 {
136   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
137   FolksPersona *persona;
138   EmpathyContact *contact;
139   GtkTreeModel *model;
140   GtkTreeIter iter;
141   GtkTreePath *path;
142   static gint running = 0;
143   gboolean ret = FALSE;
144
145   /* Avoid an infinite loop. See GNOME bug #574377 */
146   if (running > 0)
147     return FALSE;
148   running++;
149
150   if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (self), &x, &y,
151       keyboard_mode, &model, &path, &iter))
152     {
153       goto OUT;
154     }
155
156   gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (self), tooltip, path);
157   gtk_tree_path_free (path);
158
159   gtk_tree_model_get (model, &iter,
160       EMPATHY_PERSONA_STORE_COL_PERSONA, &persona,
161       -1);
162   if (persona == NULL)
163     goto OUT;
164
165   contact = empathy_contact_dup_from_tp_contact (tpf_persona_get_contact (
166       TPF_PERSONA (persona)));
167
168   if (priv->tooltip_widget == NULL)
169     {
170       priv->tooltip_widget = empathy_contact_widget_new (contact,
171           EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
172           EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
173       gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
174       g_object_ref (priv->tooltip_widget);
175       g_signal_connect (priv->tooltip_widget, "destroy",
176           (GCallback) tooltip_destroy_cb, self);
177       gtk_widget_show (priv->tooltip_widget);
178     }
179   else
180     {
181       empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
182     }
183
184   gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
185   ret = TRUE;
186
187   g_object_unref (contact);
188   g_object_unref (persona);
189
190 OUT:
191   running--;
192
193   return ret;
194 }
195
196 static void
197 cell_set_background (EmpathyPersonaView *self,
198     GtkCellRenderer *cell,
199     gboolean is_active)
200 {
201   GdkColor  color;
202   GtkStyle *style;
203
204   style = gtk_widget_get_style (GTK_WIDGET (self));
205
206   if (is_active)
207     {
208       color = style->bg[GTK_STATE_SELECTED];
209
210       /* Here we take the current theme colour and add it to
211        * the colour for white and average the two. This
212        * gives a colour which is inline with the theme but
213        * slightly whiter.
214        */
215       color.red = (color.red + (style->white).red) / 2;
216       color.green = (color.green + (style->white).green) / 2;
217       color.blue = (color.blue + (style->white).blue) / 2;
218
219       g_object_set (cell, "cell-background-gdk", &color, NULL);
220     }
221   else
222     {
223       g_object_set (cell, "cell-background-gdk", NULL, NULL);
224     }
225 }
226
227 static void
228 pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
229     GtkCellRenderer *cell,
230     GtkTreeModel *model,
231     GtkTreeIter *iter,
232     EmpathyPersonaView *self)
233 {
234   GdkPixbuf *pixbuf;
235   gboolean is_active;
236
237   gtk_tree_model_get (model, iter,
238       EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
239       EMPATHY_PERSONA_STORE_COL_ICON_STATUS, &pixbuf,
240       -1);
241
242   g_object_set (cell, "pixbuf", pixbuf, NULL);
243   tp_clear_object (&pixbuf);
244
245   cell_set_background (self, cell, is_active);
246 }
247
248 static void
249 audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
250     GtkCellRenderer *cell,
251     GtkTreeModel *model,
252     GtkTreeIter *iter,
253     EmpathyPersonaView *self)
254 {
255   gboolean is_active;
256   gboolean can_audio, can_video;
257
258   gtk_tree_model_get (model, iter,
259       EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
260       EMPATHY_PERSONA_STORE_COL_CAN_AUDIO_CALL, &can_audio,
261       EMPATHY_PERSONA_STORE_COL_CAN_VIDEO_CALL, &can_video,
262       -1);
263
264   g_object_set (cell,
265       "visible", (can_audio || can_video),
266       "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
267       NULL);
268
269   cell_set_background (self, cell, is_active);
270 }
271
272 static void
273 avatar_cell_data_func (GtkTreeViewColumn *tree_column,
274     GtkCellRenderer *cell,
275     GtkTreeModel *model,
276     GtkTreeIter *iter,
277     EmpathyPersonaView *self)
278 {
279   GdkPixbuf *pixbuf;
280   gboolean show_avatar, is_active;
281
282   gtk_tree_model_get (model, iter,
283       EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR, &pixbuf,
284       EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
285       EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
286       -1);
287
288   g_object_set (cell,
289       "visible", show_avatar,
290       "pixbuf", pixbuf,
291       NULL);
292
293   tp_clear_object (&pixbuf);
294
295   cell_set_background (self, cell, is_active);
296 }
297
298 static void
299 text_cell_data_func (GtkTreeViewColumn *tree_column,
300     GtkCellRenderer *cell,
301     GtkTreeModel *model,
302     GtkTreeIter *iter,
303     EmpathyPersonaView *self)
304 {
305   gboolean is_active;
306
307   gtk_tree_model_get (model, iter,
308       EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
309       -1);
310
311   cell_set_background (self, cell, is_active);
312 }
313
314 static void
315 empathy_persona_view_init (EmpathyPersonaView *self)
316 {
317   EmpathyPersonaViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
318     EMPATHY_TYPE_PERSONA_VIEW, EmpathyPersonaViewPriv);
319
320   self->priv = priv;
321
322   /* Connect to tree view signals rather than override. */
323   g_signal_connect (self, "query-tooltip", (GCallback) query_tooltip_cb, NULL);
324 }
325
326 static void
327 constructed (GObject *object)
328 {
329   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
330   GtkCellRenderer *cell;
331   GtkTreeViewColumn *col;
332
333   /* Set up view */
334   g_object_set (self,
335       "headers-visible", FALSE,
336       "show-expanders", FALSE,
337       NULL);
338
339   col = gtk_tree_view_column_new ();
340
341   /* State */
342   cell = gtk_cell_renderer_pixbuf_new ();
343   gtk_tree_view_column_pack_start (col, cell, FALSE);
344   gtk_tree_view_column_set_cell_data_func (col, cell,
345       (GtkTreeCellDataFunc) pixbuf_cell_data_func, self, NULL);
346
347   g_object_set (cell,
348       "xpad", 5,
349       "ypad", 1,
350       "visible", TRUE,
351       NULL);
352
353   /* Name */
354   cell = empathy_cell_renderer_text_new ();
355   gtk_tree_view_column_pack_start (col, cell, TRUE);
356   gtk_tree_view_column_set_cell_data_func (col, cell,
357       (GtkTreeCellDataFunc) text_cell_data_func, self, NULL);
358
359   /* We (ab)use the name and status properties here to display display ID and
360    * account name, respectively. Harmless. */
361   gtk_tree_view_column_add_attribute (col, cell,
362       "name", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID);
363   gtk_tree_view_column_add_attribute (col, cell,
364       "text", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID);
365   gtk_tree_view_column_add_attribute (col, cell,
366       "presence-type", EMPATHY_PERSONA_STORE_COL_PRESENCE_TYPE);
367   gtk_tree_view_column_add_attribute (col, cell,
368       "status", EMPATHY_PERSONA_STORE_COL_ACCOUNT_NAME);
369
370   /* Audio Call Icon */
371   cell = empathy_cell_renderer_activatable_new ();
372   gtk_tree_view_column_pack_start (col, cell, FALSE);
373   gtk_tree_view_column_set_cell_data_func (col, cell,
374       (GtkTreeCellDataFunc) audio_call_cell_data_func, self, NULL);
375
376   g_object_set (cell,
377       "visible", FALSE,
378       NULL);
379
380   /* Avatar */
381   cell = gtk_cell_renderer_pixbuf_new ();
382   gtk_tree_view_column_pack_start (col, cell, FALSE);
383   gtk_tree_view_column_set_cell_data_func (col, cell,
384       (GtkTreeCellDataFunc) avatar_cell_data_func, self, NULL);
385
386   g_object_set (cell,
387       "xpad", 0,
388       "ypad", 0,
389       "visible", FALSE,
390       "width", 32,
391       "height", 32,
392       NULL);
393
394   /* Actually add the column now we have added all cell renderers */
395   gtk_tree_view_append_column (GTK_TREE_VIEW (self), col);
396 }
397
398 static void
399 get_property (GObject *object,
400     guint param_id,
401     GValue *value,
402     GParamSpec *pspec)
403 {
404   EmpathyPersonaViewPriv *priv = GET_PRIV (object);
405
406   switch (param_id)
407     {
408       case PROP_MODEL:
409         g_value_set_object (value, priv->filter);
410         break;
411       case PROP_SHOW_OFFLINE:
412         g_value_set_boolean (value, priv->show_offline);
413         break;
414       default:
415         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
416         break;
417     }
418 }
419
420 static void
421 set_property (GObject *object,
422     guint param_id,
423     const GValue *value,
424     GParamSpec *pspec)
425 {
426   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
427
428   switch (param_id)
429     {
430       case PROP_MODEL:
431         set_model (self, g_value_get_object (value));
432         break;
433       case PROP_SHOW_OFFLINE:
434         empathy_persona_view_set_show_offline (self,
435             g_value_get_boolean (value));
436         break;
437       default:
438         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
439         break;
440     }
441 }
442
443 static void
444 dispose (GObject *object)
445 {
446   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
447   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
448
449   tp_clear_object (&priv->filter);
450
451   if (priv->tooltip_widget)
452     gtk_widget_destroy (priv->tooltip_widget);
453   priv->tooltip_widget = NULL;
454
455   G_OBJECT_CLASS (empathy_persona_view_parent_class)->dispose (object);
456 }
457
458 static void
459 empathy_persona_view_class_init (EmpathyPersonaViewClass *klass)
460 {
461   GObjectClass *object_class = G_OBJECT_CLASS (klass);
462
463   object_class->constructed = constructed;
464   object_class->dispose = dispose;
465   object_class->get_property = get_property;
466   object_class->set_property = set_property;
467
468   /* We override the "model" property so that we can wrap it in a
469    * GtkTreeModelFilter for showing/hiding offline personas. */
470   g_object_class_override_property (object_class, PROP_MODEL, "model");
471
472   /**
473    * EmpathyPersonaStore:show-offline:
474    *
475    * Whether to display offline personas.
476    */
477   g_object_class_install_property (object_class, PROP_SHOW_OFFLINE,
478       g_param_spec_boolean ("show-offline",
479           "Show Offline",
480           "Whether to display offline personas.",
481           FALSE,
482           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
483
484   g_type_class_add_private (object_class, sizeof (EmpathyPersonaViewPriv));
485 }
486
487 /**
488  * empathy_persona_view_new:
489  * @store: an #EmpathyPersonaStore
490  *
491  * Create a new #EmpathyPersonaView displaying the personas in
492  * #EmpathyPersonaStore.
493  *
494  * Return value: a new #EmpathyPersonaView
495  */
496 EmpathyPersonaView *
497 empathy_persona_view_new (EmpathyPersonaStore *store)
498 {
499   g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (store), NULL);
500
501   return g_object_new (EMPATHY_TYPE_PERSONA_VIEW, "model", store, NULL);
502 }
503
504 /**
505  * empathy_persona_view_dup_selected:
506  * @self: an #EmpathyPersonaView
507  *
508  * Return the #FolksPersona associated with the currently selected row. The
509  * persona is referenced before being returned. If no row is selected, %NULL is
510  * returned.
511  *
512  * Return value: the currently selected #FolksPersona, or %NULL; unref with
513  * g_object_unref()
514  */
515 FolksPersona *
516 empathy_persona_view_dup_selected (EmpathyPersonaView *self)
517 {
518   EmpathyPersonaViewPriv *priv;
519   GtkTreeSelection *selection;
520   GtkTreeIter iter;
521   GtkTreeModel *model;
522   FolksPersona *persona;
523
524   g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), NULL);
525
526   priv = GET_PRIV (self);
527
528   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
529   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
530     return NULL;
531
532   gtk_tree_model_get (model, &iter,
533       EMPATHY_PERSONA_STORE_COL_PERSONA, &persona,
534       -1);
535
536   return persona;
537 }
538
539 /**
540  * empathy_persona_view_get_show_offline:
541  * @self: an #EmpathyPersonaView
542  *
543  * Get the value of the #EmpathyPersonaView:show-offline property.
544  *
545  * Return value: %TRUE if offline personas are being shown, %FALSE otherwise
546  */
547 gboolean
548 empathy_persona_view_get_show_offline (EmpathyPersonaView *self)
549 {
550   g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), FALSE);
551
552   return GET_PRIV (self)->show_offline;
553 }
554
555 /**
556  * empathy_persona_view_set_show_offline:
557  * @self: an #EmpathyPersonaView
558  * @show_offline: %TRUE to show personas which are offline, %FALSE otherwise
559  *
560  * Set the #EmpathyPersonaView:show-offline property to @show_offline.
561  */
562 void
563 empathy_persona_view_set_show_offline (EmpathyPersonaView *self,
564     gboolean show_offline)
565 {
566   EmpathyPersonaViewPriv *priv;
567
568   g_return_if_fail (EMPATHY_IS_PERSONA_VIEW (self));
569
570   priv = GET_PRIV (self);
571   priv->show_offline = show_offline;
572
573   gtk_tree_model_filter_refilter (priv->filter);
574
575   g_object_notify (G_OBJECT (self), "show-offline");
576 }