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