]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-persona-view.c
l10n: Added Kazakh (kk) to po/LINGUAS for empathy
[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   gtk_tree_view_column_add_attribute (col, cell,
360       "name", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID);
361   gtk_tree_view_column_add_attribute (col, cell,
362       "text", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID);
363   gtk_tree_view_column_add_attribute (col, cell,
364       "presence-type", EMPATHY_PERSONA_STORE_COL_PRESENCE_TYPE);
365   gtk_tree_view_column_add_attribute (col, cell,
366       "status", EMPATHY_PERSONA_STORE_COL_STATUS);
367
368   /* Audio Call Icon */
369   cell = empathy_cell_renderer_activatable_new ();
370   gtk_tree_view_column_pack_start (col, cell, FALSE);
371   gtk_tree_view_column_set_cell_data_func (col, cell,
372       (GtkTreeCellDataFunc) audio_call_cell_data_func, self, NULL);
373
374   g_object_set (cell,
375       "visible", FALSE,
376       NULL);
377
378   /* Avatar */
379   cell = gtk_cell_renderer_pixbuf_new ();
380   gtk_tree_view_column_pack_start (col, cell, FALSE);
381   gtk_tree_view_column_set_cell_data_func (col, cell,
382       (GtkTreeCellDataFunc) avatar_cell_data_func, self, NULL);
383
384   g_object_set (cell,
385       "xpad", 0,
386       "ypad", 0,
387       "visible", FALSE,
388       "width", 32,
389       "height", 32,
390       NULL);
391
392   /* Actually add the column now we have added all cell renderers */
393   gtk_tree_view_append_column (GTK_TREE_VIEW (self), col);
394 }
395
396 static void
397 get_property (GObject *object,
398     guint param_id,
399     GValue *value,
400     GParamSpec *pspec)
401 {
402   EmpathyPersonaViewPriv *priv = GET_PRIV (object);
403
404   switch (param_id)
405     {
406       case PROP_MODEL:
407         g_value_set_object (value, priv->filter);
408         break;
409       case PROP_SHOW_OFFLINE:
410         g_value_set_boolean (value, priv->show_offline);
411         break;
412       default:
413         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
414         break;
415     }
416 }
417
418 static void
419 set_property (GObject *object,
420     guint param_id,
421     const GValue *value,
422     GParamSpec *pspec)
423 {
424   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
425
426   switch (param_id)
427     {
428       case PROP_MODEL:
429         set_model (self, g_value_get_object (value));
430         break;
431       case PROP_SHOW_OFFLINE:
432         empathy_persona_view_set_show_offline (self,
433             g_value_get_boolean (value));
434         break;
435       default:
436         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
437         break;
438     }
439 }
440
441 static void
442 dispose (GObject *object)
443 {
444   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
445   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
446
447   tp_clear_object (&priv->filter);
448
449   if (priv->tooltip_widget)
450     gtk_widget_destroy (priv->tooltip_widget);
451   priv->tooltip_widget = NULL;
452
453   G_OBJECT_CLASS (empathy_persona_view_parent_class)->dispose (object);
454 }
455
456 static void
457 empathy_persona_view_class_init (EmpathyPersonaViewClass *klass)
458 {
459   GObjectClass *object_class = G_OBJECT_CLASS (klass);
460
461   object_class->constructed = constructed;
462   object_class->dispose = dispose;
463   object_class->get_property = get_property;
464   object_class->set_property = set_property;
465
466   /* We override the "model" property so that we can wrap it in a
467    * GtkTreeModelFilter for showing/hiding offline personas. */
468   g_object_class_override_property (object_class, PROP_MODEL, "model");
469
470   /**
471    * EmpathyPersonaStore:show-offline:
472    *
473    * Whether to display offline personas.
474    */
475   g_object_class_install_property (object_class, PROP_SHOW_OFFLINE,
476       g_param_spec_boolean ("show-offline",
477           "Show Offline",
478           "Whether to display offline personas.",
479           FALSE,
480           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
481
482   g_type_class_add_private (object_class, sizeof (EmpathyPersonaViewPriv));
483 }
484
485 /**
486  * empathy_persona_view_new:
487  * @store: an #EmpathyPersonaStore
488  *
489  * Create a new #EmpathyPersonaView displaying the personas in
490  * #EmpathyPersonaStore.
491  *
492  * Return value: a new #EmpathyPersonaView
493  */
494 EmpathyPersonaView *
495 empathy_persona_view_new (EmpathyPersonaStore *store)
496 {
497   g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (store), NULL);
498
499   return g_object_new (EMPATHY_TYPE_PERSONA_VIEW, "model", store, NULL);
500 }
501
502 /**
503  * empathy_persona_view_dup_selected:
504  * @self: an #EmpathyPersonaView
505  *
506  * Return the #FolksPersona associated with the currently selected row. The
507  * persona is referenced before being returned. If no row is selected, %NULL is
508  * returned.
509  *
510  * Return value: the currently selected #FolksPersona, or %NULL; unref with
511  * g_object_unref()
512  */
513 FolksPersona *
514 empathy_persona_view_dup_selected (EmpathyPersonaView *self)
515 {
516   EmpathyPersonaViewPriv *priv;
517   GtkTreeSelection *selection;
518   GtkTreeIter iter;
519   GtkTreeModel *model;
520   FolksPersona *persona;
521
522   g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), NULL);
523
524   priv = GET_PRIV (self);
525
526   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
527   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
528     return NULL;
529
530   gtk_tree_model_get (model, &iter,
531       EMPATHY_PERSONA_STORE_COL_PERSONA, &persona,
532       -1);
533
534   return persona;
535 }
536
537 /**
538  * empathy_persona_view_get_show_offline:
539  * @self: an #EmpathyPersonaView
540  *
541  * Get the value of the #EmpathyPersonaView:show-offline property.
542  *
543  * Return value: %TRUE if offline personas are being shown, %FALSE otherwise
544  */
545 gboolean
546 empathy_persona_view_get_show_offline (EmpathyPersonaView *self)
547 {
548   g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), FALSE);
549
550   return GET_PRIV (self)->show_offline;
551 }
552
553 /**
554  * empathy_persona_view_set_show_offline:
555  * @self: an #EmpathyPersonaView
556  * @show_offline: %TRUE to show personas which are offline, %FALSE otherwise
557  *
558  * Set the #EmpathyPersonaView:show-offline property to @show_offline.
559  */
560 void
561 empathy_persona_view_set_show_offline (EmpathyPersonaView *self,
562     gboolean show_offline)
563 {
564   EmpathyPersonaViewPriv *priv;
565
566   g_return_if_fail (EMPATHY_IS_PERSONA_VIEW (self));
567
568   priv = GET_PRIV (self);
569   priv->show_offline = show_offline;
570
571   gtk_tree_model_filter_refilter (priv->filter);
572
573   g_object_notify (G_OBJECT (self), "show-offline");
574 }