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