]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-persona-view.c
Merge branch 'sasl'
[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-individual-manager.h>
41 #include <libempathy/empathy-utils.h>
42
43 #include "empathy-persona-view.h"
44 #include "empathy-contact-widget.h"
45 #include "empathy-images.h"
46 #include "empathy-cell-renderer-text.h"
47 #include "empathy-cell-renderer-activatable.h"
48 #include "empathy-gtk-enum-types.h"
49 #include "empathy-gtk-marshal.h"
50
51 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
52 #include <libempathy/empathy-debug.h>
53
54 /**
55  * SECTION:empathy-persona-view
56  * @title: EmpathyPersonaView
57  * @short_description: A tree view which displays personas from an individual
58  * @include: libempathy-gtk/empathy-persona-view.h
59  *
60  * #EmpathyPersonaView is a tree view widget which displays the personas from
61  * a given #EmpathyPersonaStore.
62  *
63  * It supports hiding offline personas and highlighting active personas. Active
64  * personas are those which have recently changed state (e.g. online, offline or
65  * from normal to a busy state).
66  */
67
68 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyPersonaView)
69
70 typedef struct
71 {
72   GtkTreeModelFilter *filter;
73   GtkWidget *tooltip_widget;
74   gboolean show_offline;
75   EmpathyPersonaViewFeatureFlags features;
76 } EmpathyPersonaViewPriv;
77
78 enum
79 {
80   PROP_0,
81   PROP_MODEL,
82   PROP_SHOW_OFFLINE,
83   PROP_FEATURES,
84 };
85
86 enum DndDragType
87 {
88   DND_DRAG_TYPE_INDIVIDUAL_ID,
89   DND_DRAG_TYPE_PERSONA_ID,
90   DND_DRAG_TYPE_STRING,
91 };
92
93 #define DRAG_TYPE(T,I) \
94   { (gchar *) T, 0, I }
95
96 static const GtkTargetEntry drag_types_dest[] = {
97   DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
98   DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
99   DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
100 };
101
102 static const GtkTargetEntry drag_types_source[] = {
103   DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
104 };
105
106 #undef DRAG_TYPE
107
108 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
109 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
110
111 enum
112 {
113   DRAG_INDIVIDUAL_RECEIVED,
114   LAST_SIGNAL
115 };
116
117 static guint signals[LAST_SIGNAL];
118
119 G_DEFINE_TYPE (EmpathyPersonaView, empathy_persona_view, GTK_TYPE_TREE_VIEW);
120
121 static gboolean
122 filter_visible_func (GtkTreeModel *model,
123     GtkTreeIter *iter,
124     EmpathyPersonaView *self)
125 {
126   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
127   gboolean is_online;
128
129   gtk_tree_model_get (model, iter,
130       EMPATHY_PERSONA_STORE_COL_IS_ONLINE, &is_online,
131       -1);
132
133   return (priv->show_offline || is_online);
134 }
135
136 static void
137 set_model (EmpathyPersonaView *self,
138     GtkTreeModel *model)
139 {
140   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
141
142   tp_clear_object (&priv->filter);
143
144   priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model,
145       NULL));
146   gtk_tree_model_filter_set_visible_func (priv->filter,
147       (GtkTreeModelFilterVisibleFunc) filter_visible_func, self, NULL);
148
149   gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (priv->filter));
150 }
151
152 static void
153 tooltip_destroy_cb (GtkWidget *widget,
154     EmpathyPersonaView *self)
155 {
156   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
157
158   if (priv->tooltip_widget)
159     {
160       DEBUG ("Tooltip destroyed");
161       g_object_unref (priv->tooltip_widget);
162       priv->tooltip_widget = NULL;
163     }
164 }
165
166 static gboolean
167 query_tooltip_cb (EmpathyPersonaView *self,
168     gint x,
169     gint y,
170     gboolean keyboard_mode,
171     GtkTooltip *tooltip,
172     gpointer user_data)
173 {
174   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
175   FolksPersona *persona;
176   EmpathyContact *contact;
177   GtkTreeModel *model;
178   GtkTreeIter iter;
179   GtkTreePath *path;
180   static gint running = 0;
181   gboolean ret = FALSE;
182
183   /* Avoid an infinite loop. See GNOME bug #574377 */
184   if (running > 0)
185     return FALSE;
186   running++;
187
188   if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (self), &x, &y,
189       keyboard_mode, &model, &path, &iter))
190     {
191       goto OUT;
192     }
193
194   gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (self), tooltip, path);
195   gtk_tree_path_free (path);
196
197   gtk_tree_model_get (model, &iter,
198       EMPATHY_PERSONA_STORE_COL_PERSONA, &persona,
199       -1);
200   if (persona == NULL)
201     goto OUT;
202
203   contact = empathy_contact_dup_from_tp_contact (tpf_persona_get_contact (
204       TPF_PERSONA (persona)));
205
206   if (priv->tooltip_widget == NULL)
207     {
208       priv->tooltip_widget = empathy_contact_widget_new (contact,
209           EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
210           EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
211       gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
212       g_object_ref (priv->tooltip_widget);
213       g_signal_connect (priv->tooltip_widget, "destroy",
214           (GCallback) tooltip_destroy_cb, self);
215       gtk_widget_show (priv->tooltip_widget);
216     }
217   else
218     {
219       empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
220     }
221
222   gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
223   ret = TRUE;
224
225   g_object_unref (contact);
226   g_object_unref (persona);
227
228 OUT:
229   running--;
230
231   return ret;
232 }
233
234 static void
235 cell_set_background (EmpathyPersonaView *self,
236     GtkCellRenderer *cell,
237     gboolean is_active)
238 {
239   GdkColor  color;
240   GtkStyle *style;
241
242   style = gtk_widget_get_style (GTK_WIDGET (self));
243
244   if (is_active)
245     {
246       color = style->bg[GTK_STATE_SELECTED];
247
248       /* Here we take the current theme colour and add it to
249        * the colour for white and average the two. This
250        * gives a colour which is inline with the theme but
251        * slightly whiter.
252        */
253       color.red = (color.red + (style->white).red) / 2;
254       color.green = (color.green + (style->white).green) / 2;
255       color.blue = (color.blue + (style->white).blue) / 2;
256
257       g_object_set (cell, "cell-background-gdk", &color, NULL);
258     }
259   else
260     {
261       g_object_set (cell, "cell-background-gdk", NULL, NULL);
262     }
263 }
264
265 static void
266 pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
267     GtkCellRenderer *cell,
268     GtkTreeModel *model,
269     GtkTreeIter *iter,
270     EmpathyPersonaView *self)
271 {
272   GdkPixbuf *pixbuf;
273   gboolean is_active;
274
275   gtk_tree_model_get (model, iter,
276       EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
277       EMPATHY_PERSONA_STORE_COL_ICON_STATUS, &pixbuf,
278       -1);
279
280   g_object_set (cell, "pixbuf", pixbuf, NULL);
281   tp_clear_object (&pixbuf);
282
283   cell_set_background (self, cell, is_active);
284 }
285
286 static void
287 audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
288     GtkCellRenderer *cell,
289     GtkTreeModel *model,
290     GtkTreeIter *iter,
291     EmpathyPersonaView *self)
292 {
293   gboolean is_active;
294   gboolean can_audio, can_video;
295
296   gtk_tree_model_get (model, iter,
297       EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
298       EMPATHY_PERSONA_STORE_COL_CAN_AUDIO_CALL, &can_audio,
299       EMPATHY_PERSONA_STORE_COL_CAN_VIDEO_CALL, &can_video,
300       -1);
301
302   g_object_set (cell,
303       "visible", (can_audio || can_video),
304       "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
305       NULL);
306
307   cell_set_background (self, cell, is_active);
308 }
309
310 static void
311 avatar_cell_data_func (GtkTreeViewColumn *tree_column,
312     GtkCellRenderer *cell,
313     GtkTreeModel *model,
314     GtkTreeIter *iter,
315     EmpathyPersonaView *self)
316 {
317   GdkPixbuf *pixbuf;
318   gboolean show_avatar, is_active;
319
320   gtk_tree_model_get (model, iter,
321       EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR, &pixbuf,
322       EMPATHY_PERSONA_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
323       EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
324       -1);
325
326   g_object_set (cell,
327       "visible", show_avatar,
328       "pixbuf", pixbuf,
329       NULL);
330
331   tp_clear_object (&pixbuf);
332
333   cell_set_background (self, cell, is_active);
334 }
335
336 static void
337 text_cell_data_func (GtkTreeViewColumn *tree_column,
338     GtkCellRenderer *cell,
339     GtkTreeModel *model,
340     GtkTreeIter *iter,
341     EmpathyPersonaView *self)
342 {
343   gboolean is_active;
344
345   gtk_tree_model_get (model, iter,
346       EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
347       -1);
348
349   cell_set_background (self, cell, is_active);
350 }
351
352 static gboolean
353 individual_drag_received (EmpathyPersonaView *self,
354     GdkDragContext *context,
355     GtkSelectionData *selection)
356 {
357   EmpathyPersonaViewPriv *priv;
358   EmpathyIndividualManager *manager = NULL;
359   FolksIndividual *individual;
360   const gchar *individual_id;
361   gboolean success = FALSE;
362
363   priv = GET_PRIV (self);
364
365   individual_id = (const gchar *) gtk_selection_data_get_data (selection);
366   manager = empathy_individual_manager_dup_singleton ();
367   individual = empathy_individual_manager_lookup_member (manager,
368       individual_id);
369
370   if (individual == NULL)
371     {
372       DEBUG ("Failed to find drag event individual with ID '%s'",
373           individual_id);
374       g_object_unref (manager);
375       return FALSE;
376     }
377
378   /* Emit a signal notifying of the drag. */
379   g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
380       gdk_drag_context_get_selected_action (context), individual, &success);
381
382   g_object_unref (manager);
383
384   return success;
385 }
386
387 static void
388 drag_data_received (GtkWidget *widget,
389     GdkDragContext *context,
390     gint x,
391     gint y,
392     GtkSelectionData *selection,
393     guint info,
394     guint time_)
395 {
396   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (widget);
397   gboolean success = TRUE;
398
399   if (info == DND_DRAG_TYPE_INDIVIDUAL_ID || info == DND_DRAG_TYPE_STRING)
400     success = individual_drag_received (self, context, selection);
401
402   gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
403 }
404
405 static gboolean
406 drag_motion (GtkWidget *widget,
407     GdkDragContext *context,
408     gint x,
409     gint y,
410     guint time_)
411 {
412   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (widget);
413   EmpathyPersonaViewPriv *priv;
414   GdkAtom target;
415
416   priv = GET_PRIV (self);
417
418   target = gtk_drag_dest_find_target (GTK_WIDGET (self), context, NULL);
419
420   if (target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID])
421     {
422       GtkTreePath *path;
423
424       /* FIXME: It doesn't make sense for us to highlight a specific row or
425        * position to drop an Individual in, so just highlight the entire
426        * widget.
427        * Since I can't find a way to do this, just highlight the first possible
428        * position in the tree. */
429       gdk_drag_status (context, gdk_drag_context_get_suggested_action (context),
430           time_);
431
432       path = gtk_tree_path_new_first ();
433       gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (self), path,
434           GTK_TREE_VIEW_DROP_BEFORE);
435       gtk_tree_path_free (path);
436
437       return TRUE;
438     }
439
440   /* Unknown or unhandled drag target */
441   gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
442   gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (self), NULL, 0);
443
444   return FALSE;
445 }
446
447 static void
448 drag_data_get (GtkWidget *widget,
449     GdkDragContext *context,
450     GtkSelectionData *selection,
451     guint info,
452     guint time_)
453 {
454   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (widget);
455   EmpathyPersonaViewPriv *priv;
456   FolksPersona *persona;
457   const gchar *persona_uid;
458
459   if (info != DND_DRAG_TYPE_PERSONA_ID)
460     return;
461
462   priv = GET_PRIV (self);
463
464   persona = empathy_persona_view_dup_selected (self);
465   if (persona == NULL)
466     return;
467
468   persona_uid = folks_persona_get_uid (persona);
469   gtk_selection_data_set (selection, drag_atoms_source[info], 8,
470       (guchar *) persona_uid, strlen (persona_uid) + 1);
471
472   g_object_unref (persona);
473 }
474
475 static gboolean
476 drag_drop (GtkWidget *widget,
477     GdkDragContext *drag_context,
478     gint x,
479     gint y,
480     guint time_)
481 {
482   return FALSE;
483 }
484
485 static void
486 set_features (EmpathyPersonaView *self,
487     EmpathyPersonaViewFeatureFlags features)
488 {
489   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
490
491   priv->features = features;
492
493   /* Setting reorderable is a hack that gets us row previews as drag icons
494      for free.  We override all the drag handlers.  It's tricky to get the
495      position of the drag icon right in drag_begin.  GtkTreeView has special
496      voodoo for it, so we let it do the voodoo that he do (but only if dragging
497      is enabled). */
498   gtk_tree_view_set_reorderable (GTK_TREE_VIEW (self),
499       (features & EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DRAG));
500
501   /* Update DnD source/dest */
502   if (features & EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DRAG)
503     {
504       gtk_drag_source_set (GTK_WIDGET (self),
505           GDK_BUTTON1_MASK,
506           drag_types_source,
507           G_N_ELEMENTS (drag_types_source),
508           GDK_ACTION_MOVE | GDK_ACTION_COPY);
509     }
510   else
511     {
512       gtk_drag_source_unset (GTK_WIDGET (self));
513     }
514
515   if (features & EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DROP)
516     {
517       gtk_drag_dest_set (GTK_WIDGET (self),
518           GTK_DEST_DEFAULT_ALL,
519           drag_types_dest,
520           G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
521     }
522   else
523     {
524       gtk_drag_dest_unset (GTK_WIDGET (self));
525     }
526
527   g_object_notify (G_OBJECT (self), "features");
528 }
529
530 static void
531 empathy_persona_view_init (EmpathyPersonaView *self)
532 {
533   EmpathyPersonaViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
534     EMPATHY_TYPE_PERSONA_VIEW, EmpathyPersonaViewPriv);
535
536   self->priv = priv;
537
538   /* Connect to tree view signals rather than override. */
539   g_signal_connect (self, "query-tooltip", (GCallback) query_tooltip_cb, NULL);
540 }
541
542 static void
543 constructed (GObject *object)
544 {
545   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
546   GtkCellRenderer *cell;
547   GtkTreeViewColumn *col;
548   guint i;
549
550   /* Set up view */
551   g_object_set (self,
552       "headers-visible", FALSE,
553       "show-expanders", FALSE,
554       NULL);
555
556   col = gtk_tree_view_column_new ();
557
558   /* State */
559   cell = gtk_cell_renderer_pixbuf_new ();
560   gtk_tree_view_column_pack_start (col, cell, FALSE);
561   gtk_tree_view_column_set_cell_data_func (col, cell,
562       (GtkTreeCellDataFunc) pixbuf_cell_data_func, self, NULL);
563
564   g_object_set (cell,
565       "xpad", 5,
566       "ypad", 1,
567       "visible", TRUE,
568       NULL);
569
570   /* Name */
571   cell = empathy_cell_renderer_text_new ();
572   gtk_tree_view_column_pack_start (col, cell, TRUE);
573   gtk_tree_view_column_set_cell_data_func (col, cell,
574       (GtkTreeCellDataFunc) text_cell_data_func, self, NULL);
575
576   /* We (ab)use the name and status properties here to display display ID and
577    * account name, respectively. Harmless. */
578   gtk_tree_view_column_add_attribute (col, cell,
579       "name", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID);
580   gtk_tree_view_column_add_attribute (col, cell,
581       "text", EMPATHY_PERSONA_STORE_COL_DISPLAY_ID);
582   gtk_tree_view_column_add_attribute (col, cell,
583       "presence-type", EMPATHY_PERSONA_STORE_COL_PRESENCE_TYPE);
584   gtk_tree_view_column_add_attribute (col, cell,
585       "status", EMPATHY_PERSONA_STORE_COL_ACCOUNT_NAME);
586
587   /* Audio Call Icon */
588   cell = empathy_cell_renderer_activatable_new ();
589   gtk_tree_view_column_pack_start (col, cell, FALSE);
590   gtk_tree_view_column_set_cell_data_func (col, cell,
591       (GtkTreeCellDataFunc) audio_call_cell_data_func, self, NULL);
592
593   g_object_set (cell,
594       "visible", FALSE,
595       NULL);
596
597   /* Avatar */
598   cell = gtk_cell_renderer_pixbuf_new ();
599   gtk_tree_view_column_pack_start (col, cell, FALSE);
600   gtk_tree_view_column_set_cell_data_func (col, cell,
601       (GtkTreeCellDataFunc) avatar_cell_data_func, self, NULL);
602
603   g_object_set (cell,
604       "xpad", 0,
605       "ypad", 0,
606       "visible", FALSE,
607       "width", 32,
608       "height", 32,
609       NULL);
610
611   /* Actually add the column now we have added all cell renderers */
612   gtk_tree_view_append_column (GTK_TREE_VIEW (self), col);
613
614   /* Drag & Drop. */
615   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
616     drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
617
618   for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
619     drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target, FALSE);
620 }
621
622 static void
623 get_property (GObject *object,
624     guint param_id,
625     GValue *value,
626     GParamSpec *pspec)
627 {
628   EmpathyPersonaViewPriv *priv = GET_PRIV (object);
629
630   switch (param_id)
631     {
632       case PROP_MODEL:
633         g_value_set_object (value, priv->filter);
634         break;
635       case PROP_SHOW_OFFLINE:
636         g_value_set_boolean (value, priv->show_offline);
637         break;
638       case PROP_FEATURES:
639         g_value_set_flags (value, priv->features);
640         break;
641       default:
642         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
643         break;
644     }
645 }
646
647 static void
648 set_property (GObject *object,
649     guint param_id,
650     const GValue *value,
651     GParamSpec *pspec)
652 {
653   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
654
655   switch (param_id)
656     {
657       case PROP_MODEL:
658         set_model (self, g_value_get_object (value));
659         break;
660       case PROP_SHOW_OFFLINE:
661         empathy_persona_view_set_show_offline (self,
662             g_value_get_boolean (value));
663         break;
664       case PROP_FEATURES:
665         set_features (self, g_value_get_flags (value));
666         break;
667       default:
668         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
669         break;
670     }
671 }
672
673 static void
674 dispose (GObject *object)
675 {
676   EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
677   EmpathyPersonaViewPriv *priv = GET_PRIV (self);
678
679   tp_clear_object (&priv->filter);
680
681   if (priv->tooltip_widget)
682     gtk_widget_destroy (priv->tooltip_widget);
683   priv->tooltip_widget = NULL;
684
685   G_OBJECT_CLASS (empathy_persona_view_parent_class)->dispose (object);
686 }
687
688 static void
689 empathy_persona_view_class_init (EmpathyPersonaViewClass *klass)
690 {
691   GObjectClass *object_class = G_OBJECT_CLASS (klass);
692   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
693
694   object_class->constructed = constructed;
695   object_class->dispose = dispose;
696   object_class->get_property = get_property;
697   object_class->set_property = set_property;
698
699   widget_class->drag_data_received = drag_data_received;
700   widget_class->drag_drop = drag_drop;
701   widget_class->drag_data_get = drag_data_get;
702   widget_class->drag_motion = drag_motion;
703
704   signals[DRAG_INDIVIDUAL_RECEIVED] =
705       g_signal_new ("drag-individual-received",
706       G_OBJECT_CLASS_TYPE (klass),
707       G_SIGNAL_RUN_LAST,
708       G_STRUCT_OFFSET (EmpathyPersonaViewClass, drag_individual_received),
709       NULL, NULL,
710       _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT,
711       G_TYPE_BOOLEAN, 2, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL);
712
713   /* We override the "model" property so that we can wrap it in a
714    * GtkTreeModelFilter for showing/hiding offline personas. */
715   g_object_class_override_property (object_class, PROP_MODEL, "model");
716
717   /**
718    * EmpathyPersonaStore:show-offline:
719    *
720    * Whether to display offline personas.
721    */
722   g_object_class_install_property (object_class, PROP_SHOW_OFFLINE,
723       g_param_spec_boolean ("show-offline",
724           "Show Offline",
725           "Whether to display offline personas.",
726           FALSE,
727           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
728
729   /**
730    * EmpathyPersonaStore:features:
731    *
732    * Features of the view, such as whether drag and drop is enabled.
733    */
734   g_object_class_install_property (object_class, PROP_FEATURES,
735       g_param_spec_flags ("features",
736           "Features",
737           "Flags for all enabled features.",
738           EMPATHY_TYPE_PERSONA_VIEW_FEATURE_FLAGS,
739           EMPATHY_PERSONA_VIEW_FEATURE_NONE,
740           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
741
742   g_type_class_add_private (object_class, sizeof (EmpathyPersonaViewPriv));
743 }
744
745 /**
746  * empathy_persona_view_new:
747  * @store: an #EmpathyPersonaStore
748  * @features: a set of flags specifying the view's functionality, or
749  * %EMPATHY_PERSONA_VIEW_FEATURE_NONE
750  *
751  * Create a new #EmpathyPersonaView displaying the personas in
752  * #EmpathyPersonaStore.
753  *
754  * Return value: a new #EmpathyPersonaView
755  */
756 EmpathyPersonaView *
757 empathy_persona_view_new (EmpathyPersonaStore *store,
758     EmpathyPersonaViewFeatureFlags features)
759 {
760   g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (store), NULL);
761
762   return g_object_new (EMPATHY_TYPE_PERSONA_VIEW,
763       "model", store,
764       "features", features,
765       NULL);
766 }
767
768 /**
769  * empathy_persona_view_dup_selected:
770  * @self: an #EmpathyPersonaView
771  *
772  * Return the #FolksPersona associated with the currently selected row. The
773  * persona is referenced before being returned. If no row is selected, %NULL is
774  * returned.
775  *
776  * Return value: the currently selected #FolksPersona, or %NULL; unref with
777  * g_object_unref()
778  */
779 FolksPersona *
780 empathy_persona_view_dup_selected (EmpathyPersonaView *self)
781 {
782   EmpathyPersonaViewPriv *priv;
783   GtkTreeSelection *selection;
784   GtkTreeIter iter;
785   GtkTreeModel *model;
786   FolksPersona *persona;
787
788   g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), NULL);
789
790   priv = GET_PRIV (self);
791
792   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
793   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
794     return NULL;
795
796   gtk_tree_model_get (model, &iter,
797       EMPATHY_PERSONA_STORE_COL_PERSONA, &persona,
798       -1);
799
800   return persona;
801 }
802
803 /**
804  * empathy_persona_view_get_show_offline:
805  * @self: an #EmpathyPersonaView
806  *
807  * Get the value of the #EmpathyPersonaView:show-offline property.
808  *
809  * Return value: %TRUE if offline personas are being shown, %FALSE otherwise
810  */
811 gboolean
812 empathy_persona_view_get_show_offline (EmpathyPersonaView *self)
813 {
814   g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), FALSE);
815
816   return GET_PRIV (self)->show_offline;
817 }
818
819 /**
820  * empathy_persona_view_set_show_offline:
821  * @self: an #EmpathyPersonaView
822  * @show_offline: %TRUE to show personas which are offline, %FALSE otherwise
823  *
824  * Set the #EmpathyPersonaView:show-offline property to @show_offline.
825  */
826 void
827 empathy_persona_view_set_show_offline (EmpathyPersonaView *self,
828     gboolean show_offline)
829 {
830   EmpathyPersonaViewPriv *priv;
831
832   g_return_if_fail (EMPATHY_IS_PERSONA_VIEW (self));
833
834   priv = GET_PRIV (self);
835   priv->show_offline = show_offline;
836
837   gtk_tree_model_filter_refilter (priv->filter);
838
839   g_object_notify (G_OBJECT (self), "show-offline");
840 }