2 * Copyright (C) 2005-2007 Imendio AB
3 * Copyright (C) 2007-2008, 2010 Collabora Ltd.
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.
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.
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
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>
25 * Based off EmpathyContactListView.
32 #include <glib/gi18n-lib.h>
35 #include <telepathy-glib/util.h>
37 #include <folks/folks.h>
38 #include <folks/folks-telepathy.h>
40 #include <libempathy/empathy-individual-manager.h>
41 #include <libempathy/empathy-utils.h>
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"
51 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
52 #include <libempathy/empathy-debug.h>
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
60 * #EmpathyPersonaView is a tree view widget which displays the personas from
61 * a given #EmpathyPersonaStore.
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).
68 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyPersonaView)
72 GtkTreeModelFilter *filter;
73 GtkWidget *tooltip_widget;
74 gboolean show_offline;
75 EmpathyPersonaViewFeatureFlags features;
76 } EmpathyPersonaViewPriv;
88 DND_DRAG_TYPE_UNKNOWN = -1,
89 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
90 DND_DRAG_TYPE_PERSONA_ID,
94 #define DRAG_TYPE(T,I) \
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),
103 static const GtkTargetEntry drag_types_source[] = {
104 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
109 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
113 DRAG_INDIVIDUAL_RECEIVED,
117 static guint signals[LAST_SIGNAL];
119 G_DEFINE_TYPE (EmpathyPersonaView, empathy_persona_view, GTK_TYPE_TREE_VIEW);
122 filter_visible_func (GtkTreeModel *model,
124 EmpathyPersonaView *self)
126 EmpathyPersonaViewPriv *priv = GET_PRIV (self);
129 gtk_tree_model_get (model, iter,
130 EMPATHY_PERSONA_STORE_COL_IS_ONLINE, &is_online,
133 return (priv->show_offline || is_online);
137 set_model (EmpathyPersonaView *self,
140 EmpathyPersonaViewPriv *priv = GET_PRIV (self);
142 tp_clear_object (&priv->filter);
144 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model,
146 gtk_tree_model_filter_set_visible_func (priv->filter,
147 (GtkTreeModelFilterVisibleFunc) filter_visible_func, self, NULL);
149 gtk_tree_view_set_model (GTK_TREE_VIEW (self), GTK_TREE_MODEL (priv->filter));
153 tooltip_destroy_cb (GtkWidget *widget,
154 EmpathyPersonaView *self)
156 EmpathyPersonaViewPriv *priv = GET_PRIV (self);
158 if (priv->tooltip_widget)
160 DEBUG ("Tooltip destroyed");
161 g_object_unref (priv->tooltip_widget);
162 priv->tooltip_widget = NULL;
167 query_tooltip_cb (EmpathyPersonaView *self,
170 gboolean keyboard_mode,
174 EmpathyPersonaViewPriv *priv = GET_PRIV (self);
175 FolksPersona *persona;
176 TpContact *tp_contact;
177 EmpathyContact *contact;
181 static gint running = 0;
182 gboolean ret = FALSE;
184 /* Avoid an infinite loop. See GNOME bug #574377 */
189 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (self), &x, &y,
190 keyboard_mode, &model, &path, &iter))
195 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (self), tooltip, path);
196 gtk_tree_path_free (path);
198 gtk_tree_model_get (model, &iter,
199 EMPATHY_PERSONA_STORE_COL_PERSONA, &persona,
204 tp_contact = tpf_persona_get_contact (TPF_PERSONA (persona));
205 if (tp_contact == NULL)
207 g_clear_object (&persona);
211 contact = empathy_contact_dup_from_tp_contact (tp_contact);
213 if (priv->tooltip_widget == NULL)
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);
226 empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
229 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
232 g_object_unref (contact);
233 g_object_unref (persona);
242 cell_set_background (EmpathyPersonaView *self,
243 GtkCellRenderer *cell,
249 GtkStyleContext *style;
251 style = gtk_widget_get_style_context (GTK_WIDGET (self));
253 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
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
261 empathy_make_color_whiter (&color);
263 g_object_set (cell, "cell-background-rgba", &color, NULL);
267 g_object_set (cell, "cell-background-rgba", NULL, NULL);
272 pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
273 GtkCellRenderer *cell,
276 EmpathyPersonaView *self)
281 gtk_tree_model_get (model, iter,
282 EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
283 EMPATHY_PERSONA_STORE_COL_ICON_STATUS, &pixbuf,
286 g_object_set (cell, "pixbuf", pixbuf, NULL);
287 tp_clear_object (&pixbuf);
289 cell_set_background (self, cell, is_active);
293 audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
294 GtkCellRenderer *cell,
297 EmpathyPersonaView *self)
300 gboolean can_audio, can_video;
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,
309 "visible", (can_audio || can_video),
310 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
313 cell_set_background (self, cell, is_active);
317 avatar_cell_data_func (GtkTreeViewColumn *tree_column,
318 GtkCellRenderer *cell,
321 EmpathyPersonaView *self)
324 gboolean show_avatar, is_active;
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,
333 "visible", show_avatar,
337 tp_clear_object (&pixbuf);
339 cell_set_background (self, cell, is_active);
343 text_cell_data_func (GtkTreeViewColumn *tree_column,
344 GtkCellRenderer *cell,
347 EmpathyPersonaView *self)
351 gtk_tree_model_get (model, iter,
352 EMPATHY_PERSONA_STORE_COL_IS_ACTIVE, &is_active,
355 cell_set_background (self, cell, is_active);
359 individual_drag_received (EmpathyPersonaView *self,
360 GdkDragContext *context,
361 GtkSelectionData *selection)
363 EmpathyIndividualManager *manager = NULL;
364 FolksIndividual *individual;
365 const gchar *individual_id;
366 gboolean success = FALSE;
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,
373 if (individual == NULL)
375 DEBUG ("Failed to find drag event individual with ID '%s'",
377 g_object_unref (manager);
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);
385 g_object_unref (manager);
391 drag_data_received (GtkWidget *widget,
392 GdkDragContext *context,
395 GtkSelectionData *selection,
399 EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (widget);
400 gboolean success = TRUE;
402 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID || info == DND_DRAG_TYPE_STRING)
403 success = individual_drag_received (self, context, selection);
405 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
409 drag_motion (GtkWidget *widget,
410 GdkDragContext *context,
415 EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (widget);
418 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
420 target = gtk_drag_dest_find_target (GTK_WIDGET (self), context, NULL);
422 /* Determine the DndDragType of the data */
423 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
425 if (target == drag_atoms_dest[i])
427 drag_type = drag_types_dest[i].info;
432 if (drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID)
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
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),
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);
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);
460 drag_data_get (GtkWidget *widget,
461 GdkDragContext *context,
462 GtkSelectionData *selection,
466 EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (widget);
467 FolksPersona *persona;
468 const gchar *persona_uid;
470 if (info != DND_DRAG_TYPE_PERSONA_ID)
473 persona = empathy_persona_view_dup_selected (self);
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);
482 g_object_unref (persona);
486 drag_drop (GtkWidget *widget,
487 GdkDragContext *drag_context,
496 set_features (EmpathyPersonaView *self,
497 EmpathyPersonaViewFeatureFlags features)
499 EmpathyPersonaViewPriv *priv = GET_PRIV (self);
501 priv->features = features;
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
508 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (self),
509 (features & EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DRAG));
511 /* Update DnD source/dest */
512 if (features & EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DRAG)
514 gtk_drag_source_set (GTK_WIDGET (self),
517 G_N_ELEMENTS (drag_types_source),
518 GDK_ACTION_MOVE | GDK_ACTION_COPY);
522 gtk_drag_source_unset (GTK_WIDGET (self));
525 if (features & EMPATHY_PERSONA_VIEW_FEATURE_PERSONA_DROP)
527 gtk_drag_dest_set (GTK_WIDGET (self),
528 GTK_DEST_DEFAULT_ALL,
530 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
534 gtk_drag_dest_unset (GTK_WIDGET (self));
537 g_object_notify (G_OBJECT (self), "features");
541 empathy_persona_view_init (EmpathyPersonaView *self)
543 EmpathyPersonaViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
544 EMPATHY_TYPE_PERSONA_VIEW, EmpathyPersonaViewPriv);
548 /* Connect to tree view signals rather than override. */
549 g_signal_connect (self, "query-tooltip", (GCallback) query_tooltip_cb, NULL);
553 constructed (GObject *object)
555 EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
556 GtkCellRenderer *cell;
557 GtkTreeViewColumn *col;
562 "headers-visible", FALSE,
563 "show-expanders", FALSE,
566 col = gtk_tree_view_column_new ();
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);
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);
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);
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);
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);
621 /* Actually add the column now we have added all cell renderers */
622 gtk_tree_view_append_column (GTK_TREE_VIEW (self), col);
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);
630 get_property (GObject *object,
635 EmpathyPersonaViewPriv *priv = GET_PRIV (object);
640 g_value_set_object (value, priv->filter);
642 case PROP_SHOW_OFFLINE:
643 g_value_set_boolean (value, priv->show_offline);
646 g_value_set_flags (value, priv->features);
649 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
655 set_property (GObject *object,
660 EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
665 set_model (self, g_value_get_object (value));
667 case PROP_SHOW_OFFLINE:
668 empathy_persona_view_set_show_offline (self,
669 g_value_get_boolean (value));
672 set_features (self, g_value_get_flags (value));
675 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
681 dispose (GObject *object)
683 EmpathyPersonaView *self = EMPATHY_PERSONA_VIEW (object);
684 EmpathyPersonaViewPriv *priv = GET_PRIV (self);
686 tp_clear_object (&priv->filter);
688 if (priv->tooltip_widget)
689 gtk_widget_destroy (priv->tooltip_widget);
690 priv->tooltip_widget = NULL;
692 G_OBJECT_CLASS (empathy_persona_view_parent_class)->dispose (object);
696 empathy_persona_view_class_init (EmpathyPersonaViewClass *klass)
698 GObjectClass *object_class = G_OBJECT_CLASS (klass);
699 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
701 object_class->constructed = constructed;
702 object_class->dispose = dispose;
703 object_class->get_property = get_property;
704 object_class->set_property = set_property;
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;
711 signals[DRAG_INDIVIDUAL_RECEIVED] =
712 g_signal_new ("drag-individual-received",
713 G_OBJECT_CLASS_TYPE (klass),
715 G_STRUCT_OFFSET (EmpathyPersonaViewClass, drag_individual_received),
717 g_cclosure_marshal_generic,
718 G_TYPE_BOOLEAN, 2, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL);
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");
725 * EmpathyPersonaStore:show-offline:
727 * Whether to display offline personas.
729 g_object_class_install_property (object_class, PROP_SHOW_OFFLINE,
730 g_param_spec_boolean ("show-offline",
732 "Whether to display offline personas.",
734 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
737 * EmpathyPersonaStore:features:
739 * Features of the view, such as whether drag and drop is enabled.
741 g_object_class_install_property (object_class, PROP_FEATURES,
742 g_param_spec_flags ("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));
749 g_type_class_add_private (object_class, sizeof (EmpathyPersonaViewPriv));
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
758 * Create a new #EmpathyPersonaView displaying the personas in
759 * #EmpathyPersonaStore.
761 * Return value: a new #EmpathyPersonaView
764 empathy_persona_view_new (EmpathyPersonaStore *store,
765 EmpathyPersonaViewFeatureFlags features)
767 g_return_val_if_fail (EMPATHY_IS_PERSONA_STORE (store), NULL);
769 return g_object_new (EMPATHY_TYPE_PERSONA_VIEW,
771 "features", features,
776 * empathy_persona_view_dup_selected:
777 * @self: an #EmpathyPersonaView
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
783 * Return value: the currently selected #FolksPersona, or %NULL; unref with
787 empathy_persona_view_dup_selected (EmpathyPersonaView *self)
789 GtkTreeSelection *selection;
792 FolksPersona *persona;
794 g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), NULL);
796 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
797 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
800 gtk_tree_model_get (model, &iter,
801 EMPATHY_PERSONA_STORE_COL_PERSONA, &persona,
808 * empathy_persona_view_get_show_offline:
809 * @self: an #EmpathyPersonaView
811 * Get the value of the #EmpathyPersonaView:show-offline property.
813 * Return value: %TRUE if offline personas are being shown, %FALSE otherwise
816 empathy_persona_view_get_show_offline (EmpathyPersonaView *self)
818 g_return_val_if_fail (EMPATHY_IS_PERSONA_VIEW (self), FALSE);
820 return GET_PRIV (self)->show_offline;
824 * empathy_persona_view_set_show_offline:
825 * @self: an #EmpathyPersonaView
826 * @show_offline: %TRUE to show personas which are offline, %FALSE otherwise
828 * Set the #EmpathyPersonaView:show-offline property to @show_offline.
831 empathy_persona_view_set_show_offline (EmpathyPersonaView *self,
832 gboolean show_offline)
834 EmpathyPersonaViewPriv *priv;
836 g_return_if_fail (EMPATHY_IS_PERSONA_VIEW (self));
838 priv = GET_PRIV (self);
839 priv->show_offline = show_offline;
841 gtk_tree_model_filter_refilter (priv->filter);
843 g_object_notify (G_OBJECT (self), "show-offline");