LogWindow: Don't cut the log messages
[empathy-mirror.git] / libempathy-gtk / empathy-individual-view.c
blob0fb24430cf76c115857cdae1873e1accf0ff5ffd
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
27 #include "config.h"
29 #include <string.h>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-individual-manager.h>
42 #include <libempathy/empathy-contact-groups.h>
43 #include <libempathy/empathy-request-util.h>
44 #include <libempathy/empathy-utils.h>
46 #include "empathy-individual-view.h"
47 #include "empathy-individual-menu.h"
48 #include "empathy-individual-store.h"
49 #include "empathy-contact-dialogs.h"
50 #include "empathy-individual-dialogs.h"
51 #include "empathy-images.h"
52 #include "empathy-linking-dialog.h"
53 #include "empathy-cell-renderer-expander.h"
54 #include "empathy-cell-renderer-text.h"
55 #include "empathy-cell-renderer-activatable.h"
56 #include "empathy-ui-utils.h"
57 #include "empathy-gtk-enum-types.h"
58 #include "empathy-gtk-marshal.h"
60 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
61 #include <libempathy/empathy-debug.h>
63 /* Active users are those which have recently changed state
64 * (e.g. online, offline or from normal to a busy state).
67 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
68 typedef struct
70 EmpathyIndividualStore *store;
71 GtkTreeRowReference *drag_row;
72 EmpathyIndividualViewFeatureFlags view_features;
73 EmpathyIndividualFeatureFlags individual_features;
74 GtkWidget *tooltip_widget;
76 gboolean show_offline;
77 gboolean show_untrusted;
79 GtkTreeModelFilter *filter;
80 GtkWidget *search_widget;
82 guint expand_groups_idle_handler;
83 /* owned string (group name) -> bool (whether to expand/contract) */
84 GHashTable *expand_groups;
86 /* Auto scroll */
87 guint auto_scroll_timeout_id;
88 /* Distance between mouse pointer and the nearby border. Negative when
89 scrolling updards.*/
90 gint distance;
92 GtkTreeModelFilterVisibleFunc custom_filter;
93 gpointer custom_filter_data;
94 } EmpathyIndividualViewPriv;
96 typedef struct
98 EmpathyIndividualView *view;
99 GtkTreePath *path;
100 guint timeout_id;
101 } DragMotionData;
103 typedef struct
105 EmpathyIndividualView *view;
106 FolksIndividual *individual;
107 gboolean remove;
108 } ShowActiveData;
110 enum
112 PROP_0,
113 PROP_STORE,
114 PROP_VIEW_FEATURES,
115 PROP_INDIVIDUAL_FEATURES,
116 PROP_SHOW_OFFLINE,
117 PROP_SHOW_UNTRUSTED,
120 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
121 * specific EmpathyContacts (between/in/out of Individuals) */
122 typedef enum
124 DND_DRAG_TYPE_UNKNOWN = -1,
125 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
126 DND_DRAG_TYPE_PERSONA_ID,
127 DND_DRAG_TYPE_URI_LIST,
128 DND_DRAG_TYPE_STRING,
129 } DndDragType;
131 #define DRAG_TYPE(T,I) \
132 { (gchar *) T, 0, I }
134 static const GtkTargetEntry drag_types_dest[] = {
135 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
136 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
137 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
138 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
139 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
140 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
143 static const GtkTargetEntry drag_types_source[] = {
144 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
147 #undef DRAG_TYPE
149 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
151 enum
153 DRAG_INDIVIDUAL_RECEIVED,
154 DRAG_PERSONA_RECEIVED,
155 LAST_SIGNAL
158 static guint signals[LAST_SIGNAL];
160 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
161 GTK_TYPE_TREE_VIEW);
163 static void
164 individual_view_tooltip_destroy_cb (GtkWidget *widget,
165 EmpathyIndividualView *view)
167 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
169 tp_clear_object (&priv->tooltip_widget);
172 static gboolean
173 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
174 gint x,
175 gint y,
176 gboolean keyboard_mode,
177 GtkTooltip *tooltip,
178 gpointer user_data)
180 EmpathyIndividualViewPriv *priv;
181 FolksIndividual *individual;
182 GtkTreeModel *model;
183 GtkTreeIter iter;
184 GtkTreePath *path;
185 static gint running = 0;
186 gboolean ret = FALSE;
188 priv = GET_PRIV (view);
190 /* Avoid an infinite loop. See GNOME bug #574377 */
191 if (running > 0)
192 return FALSE;
194 running++;
196 /* Don't show the tooltip if there's already a popup menu */
197 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
198 goto OUT;
200 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
201 keyboard_mode, &model, &path, &iter))
202 goto OUT;
204 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
205 gtk_tree_path_free (path);
207 gtk_tree_model_get (model, &iter,
208 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
209 -1);
210 if (individual == NULL)
211 goto OUT;
213 if (priv->tooltip_widget == NULL)
215 priv->tooltip_widget = empathy_individual_widget_new (individual,
216 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
217 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
218 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
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 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
223 gtk_widget_show (priv->tooltip_widget);
225 else
227 empathy_individual_widget_set_individual (
228 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
231 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
232 ret = TRUE;
234 g_object_unref (individual);
235 OUT:
236 running--;
238 return ret;
241 static void
242 groups_change_group_cb (GObject *source,
243 GAsyncResult *result,
244 gpointer user_data)
246 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
247 GError *error = NULL;
249 folks_group_details_change_group_finish (group_details, result, &error);
250 if (error != NULL)
252 g_warning ("failed to change group: %s", error->message);
253 g_clear_error (&error);
257 static gboolean
258 group_can_be_modified (const gchar *name,
259 gboolean is_fake_group,
260 gboolean adding)
262 /* Real groups can always be modified */
263 if (!is_fake_group)
264 return TRUE;
266 /* The favorite fake group can be modified so users can
267 * add/remove favorites using DnD */
268 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
269 return TRUE;
271 /* We can remove contacts from the 'ungrouped' fake group */
272 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
273 return TRUE;
275 return FALSE;
278 static gboolean
279 individual_view_individual_drag_received (GtkWidget *self,
280 GdkDragContext *context,
281 GtkTreeModel *model,
282 GtkTreePath *path,
283 GtkSelectionData *selection)
285 EmpathyIndividualViewPriv *priv;
286 EmpathyIndividualManager *manager = NULL;
287 FolksIndividual *individual;
288 GtkTreePath *source_path;
289 const gchar *sel_data;
290 gchar *new_group = NULL;
291 gchar *old_group = NULL;
292 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
294 priv = GET_PRIV (self);
296 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
297 new_group = empathy_individual_store_get_parent_group (model, path,
298 NULL, &new_group_is_fake);
300 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
301 goto finished;
303 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
304 * feature. Otherwise, we just add the dropped contact to whichever group
305 * they were dropped in, and don't remove them from their old group. This
306 * allows for Individual views which shouldn't allow Individuals to have
307 * their groups changed, and also for dragging Individuals between Individual
308 * views. */
309 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
310 priv->drag_row != NULL)
312 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
313 if (source_path)
315 old_group =
316 empathy_individual_store_get_parent_group (model, source_path,
317 NULL, &old_group_is_fake);
318 gtk_tree_path_free (source_path);
321 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
322 goto finished;
324 if (!tp_strdiff (old_group, new_group))
325 goto finished;
327 else if (priv->drag_row != NULL)
329 /* We don't allow changing Individuals' groups, and this Individual was
330 * dragged from another group in *this* Individual view, so we disallow
331 * the drop. */
332 goto finished;
335 /* XXX: for contacts, we used to ensure the account, create the contact
336 * factory, and then wait on the contacts. But they should already be
337 * created by this point */
339 manager = empathy_individual_manager_dup_singleton ();
340 individual = empathy_individual_manager_lookup_member (manager, sel_data);
342 if (individual == NULL)
344 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
345 goto finished;
348 /* FIXME: We should probably wait for the cb before calling
349 * gtk_drag_finish */
351 /* Emit a signal notifying of the drag. We change the Individual's groups in
352 * the default signal handler. */
353 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
354 gdk_drag_context_get_selected_action (context), individual, new_group,
355 old_group);
357 retval = TRUE;
359 finished:
360 tp_clear_object (&manager);
361 g_free (old_group);
362 g_free (new_group);
364 return retval;
367 static void
368 real_drag_individual_received_cb (EmpathyIndividualView *self,
369 GdkDragAction action,
370 FolksIndividual *individual,
371 const gchar *new_group,
372 const gchar *old_group)
374 DEBUG ("individual %s dragged from '%s' to '%s'",
375 folks_individual_get_id (individual), old_group, new_group);
377 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
379 /* Mark contact as favourite */
380 folks_favourite_details_set_is_favourite (
381 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
382 return;
385 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
387 /* Remove contact as favourite */
388 folks_favourite_details_set_is_favourite (
389 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
391 /* Don't try to remove it */
392 old_group = NULL;
395 if (new_group != NULL)
397 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
398 new_group, TRUE, groups_change_group_cb, NULL);
401 if (old_group != NULL && action == GDK_ACTION_MOVE)
403 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
404 old_group, FALSE, groups_change_group_cb, NULL);
408 static gboolean
409 individual_view_persona_drag_received (GtkWidget *self,
410 GdkDragContext *context,
411 GtkTreeModel *model,
412 GtkTreePath *path,
413 GtkSelectionData *selection)
415 EmpathyIndividualManager *manager = NULL;
416 FolksIndividual *individual = NULL;
417 FolksPersona *persona = NULL;
418 const gchar *persona_uid;
419 GList *individuals, *l;
420 GeeIterator *iter = NULL;
421 gboolean retval = FALSE;
423 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
425 /* FIXME: This is slow, but the only way to find the Persona we're having
426 * dropped on us. */
427 manager = empathy_individual_manager_dup_singleton ();
428 individuals = empathy_individual_manager_get_members (manager);
430 for (l = individuals; l != NULL; l = l->next)
432 GeeSet *personas;
434 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
435 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
436 while (gee_iterator_next (iter))
438 FolksPersona *persona_cur = gee_iterator_get (iter);
440 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
442 /* takes ownership of the ref */
443 persona = persona_cur;
444 individual = g_object_ref (l->data);
445 goto got_persona;
447 g_clear_object (&persona_cur);
449 g_clear_object (&iter);
452 got_persona:
453 g_clear_object (&iter);
454 g_list_free (individuals);
456 if (persona == NULL || individual == NULL)
458 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
460 else
462 /* Emit a signal notifying of the drag. We change the Individual's groups in
463 * the default signal handler. */
464 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
465 gdk_drag_context_get_selected_action (context), persona, individual,
466 &retval);
469 tp_clear_object (&manager);
470 tp_clear_object (&persona);
471 tp_clear_object (&individual);
473 return retval;
476 static gboolean
477 individual_view_file_drag_received (GtkWidget *view,
478 GdkDragContext *context,
479 GtkTreeModel *model,
480 GtkTreePath *path,
481 GtkSelectionData *selection)
483 GtkTreeIter iter;
484 const gchar *sel_data;
485 FolksIndividual *individual;
486 EmpathyContact *contact;
488 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
490 gtk_tree_model_get_iter (model, &iter, path);
491 gtk_tree_model_get (model, &iter,
492 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
493 if (individual == NULL)
494 return FALSE;
496 contact = empathy_contact_dup_from_folks_individual (individual);
497 empathy_send_file_from_uri_list (contact, sel_data);
499 g_object_unref (individual);
500 tp_clear_object (&contact);
502 return TRUE;
505 static void
506 individual_view_drag_data_received (GtkWidget *view,
507 GdkDragContext *context,
508 gint x,
509 gint y,
510 GtkSelectionData *selection,
511 guint info,
512 guint time_)
514 GtkTreeModel *model;
515 gboolean is_row;
516 GtkTreeViewDropPosition position;
517 GtkTreePath *path;
518 gboolean success = TRUE;
520 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
522 /* Get destination group information. */
523 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
524 x, y, &path, &position);
525 if (!is_row)
527 success = FALSE;
529 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
531 success = individual_view_individual_drag_received (view,
532 context, model, path, selection);
534 else if (info == DND_DRAG_TYPE_PERSONA_ID)
536 success = individual_view_persona_drag_received (view, context, model,
537 path, selection);
539 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
541 success = individual_view_file_drag_received (view,
542 context, model, path, selection);
545 gtk_tree_path_free (path);
546 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
549 static gboolean
550 individual_view_drag_motion_cb (DragMotionData *data)
552 if (data->view != NULL)
554 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
555 g_object_remove_weak_pointer (G_OBJECT (data->view),
556 (gpointer *) &data->view);
559 data->timeout_id = 0;
561 return FALSE;
564 /* Minimum distance between the mouse pointer and a horizontal border when we
565 start auto scrolling. */
566 #define AUTO_SCROLL_MARGIN_SIZE 20
567 /* How far to scroll per one tick. */
568 #define AUTO_SCROLL_PITCH 10
570 static gboolean
571 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
573 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
574 GtkAdjustment *adj;
575 gdouble new_value;
577 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
579 if (priv->distance < 0)
580 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
581 else
582 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
584 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
585 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
587 gtk_adjustment_set_value (adj, new_value);
589 return TRUE;
592 static gboolean
593 individual_view_drag_motion (GtkWidget *widget,
594 GdkDragContext *context,
595 gint x,
596 gint y,
597 guint time_)
599 EmpathyIndividualViewPriv *priv;
600 GtkTreeModel *model;
601 GdkAtom target;
602 GtkTreeIter iter;
603 static DragMotionData *dm = NULL;
604 GtkTreePath *path;
605 gboolean is_row;
606 gboolean is_different = FALSE;
607 gboolean cleanup = TRUE;
608 gboolean retval = TRUE;
609 GtkAllocation allocation;
610 guint i;
611 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
613 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
614 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
617 if (priv->auto_scroll_timeout_id != 0)
619 g_source_remove (priv->auto_scroll_timeout_id);
620 priv->auto_scroll_timeout_id = 0;
623 gtk_widget_get_allocation (widget, &allocation);
625 if (y < AUTO_SCROLL_MARGIN_SIZE ||
626 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
628 if (y < AUTO_SCROLL_MARGIN_SIZE)
629 priv->distance = MIN (-y, -1);
630 else
631 priv->distance = MAX (allocation.height - y, 1);
633 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
634 (GSourceFunc) individual_view_auto_scroll_cb, widget);
637 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
638 x, y, &path, NULL, NULL, NULL);
640 cleanup &= (dm == NULL);
642 if (is_row)
644 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
645 is_different = ((dm == NULL) || ((dm != NULL)
646 && gtk_tree_path_compare (dm->path, path) != 0));
648 else
649 cleanup &= FALSE;
651 if (path == NULL)
653 /* Coordinates don't point to an actual row, so make sure the pointer
654 and highlighting don't indicate that a drag is possible.
656 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
657 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
658 return FALSE;
660 target = gtk_drag_dest_find_target (widget, context, NULL);
661 gtk_tree_model_get_iter (model, &iter, path);
663 /* Determine the DndDragType of the data */
664 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
666 if (target == drag_atoms_dest[i])
668 drag_type = drag_types_dest[i].info;
669 break;
673 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
674 drag_type == DND_DRAG_TYPE_STRING)
676 /* This is a file drag, and it can only be dropped on contacts,
677 * not groups.
678 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
679 * even if we have a valid target. */
680 FolksIndividual *individual = NULL;
681 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
683 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
685 gtk_tree_model_get (model, &iter,
686 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
687 -1);
690 if (individual != NULL)
692 EmpathyContact *contact = NULL;
694 contact = empathy_contact_dup_from_folks_individual (individual);
695 caps = empathy_contact_get_capabilities (contact);
697 tp_clear_object (&contact);
700 if (individual != NULL &&
701 folks_presence_details_is_online (
702 FOLKS_PRESENCE_DETAILS (individual)) &&
703 (caps & EMPATHY_CAPABILITIES_FT))
705 gdk_drag_status (context, GDK_ACTION_COPY, time_);
706 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
707 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
709 else
711 gdk_drag_status (context, 0, time_);
712 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
713 retval = FALSE;
716 if (individual != NULL)
717 g_object_unref (individual);
719 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
720 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
721 priv->drag_row == NULL)) ||
722 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
723 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
725 /* If target != GDK_NONE, then we have a contact (individual or persona)
726 drag. If we're pointing to a group, highlight it. Otherwise, if the
727 contact we're pointing to is in a group, highlight that. Otherwise,
728 set the drag position to before the first row for a drag into
729 the "non-group" at the top.
730 If it's an Individual:
731 We only highlight things if the contact is from a different
732 Individual view, or if this Individual view has
733 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
734 which don't have FEATURE_GROUPS_CHANGE, but do have
735 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
736 If it's a Persona:
737 We only highlight things if we have FEATURE_PERSONA_DROP.
739 GtkTreeIter group_iter;
740 gboolean is_group;
741 GtkTreePath *group_path;
742 gtk_tree_model_get (model, &iter,
743 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
744 if (is_group)
746 group_iter = iter;
748 else
750 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
751 gtk_tree_model_get (model, &group_iter,
752 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
754 if (is_group)
756 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
757 group_path = gtk_tree_model_get_path (model, &group_iter);
758 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
759 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
760 gtk_tree_path_free (group_path);
762 else
764 group_path = gtk_tree_path_new_first ();
765 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
766 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
767 group_path, GTK_TREE_VIEW_DROP_BEFORE);
771 if (!is_different && !cleanup)
772 return retval;
774 if (dm)
776 gtk_tree_path_free (dm->path);
777 if (dm->timeout_id)
779 g_source_remove (dm->timeout_id);
782 g_free (dm);
784 dm = NULL;
787 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
789 dm = g_new0 (DragMotionData, 1);
791 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
792 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
793 dm->path = gtk_tree_path_copy (path);
795 dm->timeout_id = g_timeout_add_seconds (1,
796 (GSourceFunc) individual_view_drag_motion_cb, dm);
799 return retval;
802 static void
803 individual_view_drag_begin (GtkWidget *widget,
804 GdkDragContext *context)
806 EmpathyIndividualViewPriv *priv;
807 GtkTreeSelection *selection;
808 GtkTreeModel *model;
809 GtkTreePath *path;
810 GtkTreeIter iter;
812 priv = GET_PRIV (widget);
814 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
815 context);
817 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
818 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
819 return;
821 path = gtk_tree_model_get_path (model, &iter);
822 priv->drag_row = gtk_tree_row_reference_new (model, path);
823 gtk_tree_path_free (path);
826 static void
827 individual_view_drag_data_get (GtkWidget *widget,
828 GdkDragContext *context,
829 GtkSelectionData *selection,
830 guint info,
831 guint time_)
833 EmpathyIndividualViewPriv *priv;
834 GtkTreePath *src_path;
835 GtkTreeIter iter;
836 GtkTreeModel *model;
837 FolksIndividual *individual;
838 const gchar *individual_id;
840 priv = GET_PRIV (widget);
842 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
843 if (priv->drag_row == NULL)
844 return;
846 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
847 if (src_path == NULL)
848 return;
850 if (!gtk_tree_model_get_iter (model, &iter, src_path))
852 gtk_tree_path_free (src_path);
853 return;
856 gtk_tree_path_free (src_path);
858 individual =
859 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
860 if (individual == NULL)
861 return;
863 individual_id = folks_individual_get_id (individual);
865 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
867 gtk_selection_data_set (selection,
868 gdk_atom_intern ("text/individual-id", FALSE), 8,
869 (guchar *) individual_id, strlen (individual_id) + 1);
872 g_object_unref (individual);
875 static void
876 individual_view_drag_end (GtkWidget *widget,
877 GdkDragContext *context)
879 EmpathyIndividualViewPriv *priv;
881 priv = GET_PRIV (widget);
883 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
884 context);
886 if (priv->drag_row)
888 gtk_tree_row_reference_free (priv->drag_row);
889 priv->drag_row = NULL;
893 static gboolean
894 individual_view_drag_drop (GtkWidget *widget,
895 GdkDragContext *drag_context,
896 gint x,
897 gint y,
898 guint time_)
900 return FALSE;
903 typedef struct
905 EmpathyIndividualView *view;
906 guint button;
907 guint32 time;
908 } MenuPopupData;
910 static void
911 menu_deactivate_cb (GtkMenuShell *menushell,
912 gpointer user_data)
914 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
915 g_signal_handlers_disconnect_by_func (menushell,
916 menu_deactivate_cb, user_data);
918 gtk_menu_detach (GTK_MENU (menushell));
921 static gboolean
922 individual_view_popup_menu_idle_cb (gpointer user_data)
924 MenuPopupData *data = user_data;
925 GtkWidget *menu;
927 menu = empathy_individual_view_get_individual_menu (data->view);
928 if (menu == NULL)
929 menu = empathy_individual_view_get_group_menu (data->view);
931 if (menu != NULL)
933 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
934 NULL);
935 gtk_widget_show (menu);
936 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
937 data->time);
939 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
940 * floating ref. We can either wait that the treeview releases its ref
941 * when it will be destroyed (when leaving Empathy) or explicitely
942 * detach the menu when it's not displayed any more.
943 * We go for the latter as we don't want to keep useless menus in memory
944 * during the whole lifetime of Empathy. */
945 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
946 NULL);
949 g_slice_free (MenuPopupData, data);
951 return FALSE;
954 static gboolean
955 individual_view_button_press_event_cb (EmpathyIndividualView *view,
956 GdkEventButton *event,
957 gpointer user_data)
959 if (event->button == 3)
961 MenuPopupData *data;
963 data = g_slice_new (MenuPopupData);
964 data->view = view;
965 data->button = event->button;
966 data->time = event->time;
967 g_idle_add (individual_view_popup_menu_idle_cb, data);
970 return FALSE;
973 static gboolean
974 individual_view_key_press_event_cb (EmpathyIndividualView *view,
975 GdkEventKey *event,
976 gpointer user_data)
978 if (event->keyval == GDK_KEY_Menu)
980 MenuPopupData *data;
982 data = g_slice_new (MenuPopupData);
983 data->view = view;
984 data->button = 0;
985 data->time = event->time;
986 g_idle_add (individual_view_popup_menu_idle_cb, data);
987 } else if (event->keyval == GDK_KEY_F2) {
988 FolksIndividual *individual;
989 EmpathyContact *contact;
991 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
993 individual = empathy_individual_view_dup_selected (view);
994 if (individual == NULL)
995 return FALSE;
997 contact = empathy_contact_dup_from_folks_individual (individual);
998 if (contact == NULL) {
999 g_object_unref (individual);
1000 return FALSE;
1002 empathy_contact_edit_dialog_show (contact, NULL);
1004 g_object_unref (individual);
1005 g_object_unref (contact);
1008 return FALSE;
1011 static void
1012 individual_view_row_activated (GtkTreeView *view,
1013 GtkTreePath *path,
1014 GtkTreeViewColumn *column)
1016 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1017 FolksIndividual *individual;
1018 EmpathyContact *contact;
1019 GtkTreeModel *model;
1020 GtkTreeIter iter;
1022 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1023 return;
1025 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1026 gtk_tree_model_get_iter (model, &iter, path);
1027 gtk_tree_model_get (model, &iter,
1028 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1030 if (individual == NULL)
1031 return;
1033 /* Determine which Persona to chat to, by choosing the most available one. */
1034 contact = empathy_contact_dup_best_for_action (individual,
1035 EMPATHY_ACTION_CHAT);
1037 if (contact != NULL)
1039 DEBUG ("Starting a chat");
1041 empathy_chat_with_contact (contact,
1042 gtk_get_current_event_time ());
1045 g_object_unref (individual);
1046 tp_clear_object (&contact);
1049 static void
1050 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1051 const gchar *path_string,
1052 EmpathyIndividualView *view)
1054 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1055 GtkWidget *menu;
1056 GtkTreeModel *model;
1057 GtkTreeIter iter;
1058 FolksIndividual *individual;
1059 GdkEventButton *event;
1060 GtkMenuShell *shell;
1061 GtkWidget *item;
1063 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1064 return;
1066 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1067 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1068 return;
1070 gtk_tree_model_get (model, &iter,
1071 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1072 if (individual == NULL)
1073 return;
1075 event = (GdkEventButton *) gtk_get_current_event ();
1077 menu = empathy_context_menu_new (GTK_WIDGET (view));
1078 shell = GTK_MENU_SHELL (menu);
1080 /* audio */
1081 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
1082 gtk_menu_shell_append (shell, item);
1083 gtk_widget_show (item);
1085 /* video */
1086 item = empathy_individual_video_call_menu_item_new (individual, NULL);
1087 gtk_menu_shell_append (shell, item);
1088 gtk_widget_show (item);
1090 gtk_widget_show (menu);
1091 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1092 event->button, event->time);
1094 g_object_unref (individual);
1097 static void
1098 individual_view_cell_set_background (EmpathyIndividualView *view,
1099 GtkCellRenderer *cell,
1100 gboolean is_group,
1101 gboolean is_active)
1103 if (!is_group && is_active)
1105 GtkStyleContext *style;
1106 GdkRGBA color;
1108 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1110 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1111 &color);
1113 /* Here we take the current theme colour and add it to
1114 * the colour for white and average the two. This
1115 * gives a colour which is inline with the theme but
1116 * slightly whiter.
1118 empathy_make_color_whiter (&color);
1120 g_object_set (cell, "cell-background-rgba", &color, NULL);
1122 else
1123 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1126 static void
1127 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1128 GtkCellRenderer *cell,
1129 GtkTreeModel *model,
1130 GtkTreeIter *iter,
1131 EmpathyIndividualView *view)
1133 GdkPixbuf *pixbuf;
1134 gboolean is_group;
1135 gboolean is_active;
1137 gtk_tree_model_get (model, iter,
1138 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1139 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1140 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1142 g_object_set (cell,
1143 "visible", !is_group,
1144 "pixbuf", pixbuf,
1145 NULL);
1147 tp_clear_object (&pixbuf);
1149 individual_view_cell_set_background (view, cell, is_group, is_active);
1152 static void
1153 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1154 GtkCellRenderer *cell,
1155 GtkTreeModel *model,
1156 GtkTreeIter *iter,
1157 EmpathyIndividualView *view)
1159 GdkPixbuf *pixbuf = NULL;
1160 gboolean is_group;
1161 gchar *name;
1163 gtk_tree_model_get (model, iter,
1164 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1165 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1167 if (!is_group)
1168 goto out;
1170 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1172 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1173 GTK_ICON_SIZE_MENU);
1175 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1177 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1178 GTK_ICON_SIZE_MENU);
1181 out:
1182 g_object_set (cell,
1183 "visible", pixbuf != NULL,
1184 "pixbuf", pixbuf,
1185 NULL);
1187 tp_clear_object (&pixbuf);
1189 g_free (name);
1192 static void
1193 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1194 GtkCellRenderer *cell,
1195 GtkTreeModel *model,
1196 GtkTreeIter *iter,
1197 EmpathyIndividualView *view)
1199 gboolean is_group;
1200 gboolean is_active;
1201 gboolean can_audio, can_video;
1203 gtk_tree_model_get (model, iter,
1204 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1205 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1206 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1207 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1209 g_object_set (cell,
1210 "visible", !is_group && (can_audio || can_video),
1211 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1212 NULL);
1214 individual_view_cell_set_background (view, cell, is_group, is_active);
1217 static void
1218 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1219 GtkCellRenderer *cell,
1220 GtkTreeModel *model,
1221 GtkTreeIter *iter,
1222 EmpathyIndividualView *view)
1224 GdkPixbuf *pixbuf;
1225 gboolean show_avatar;
1226 gboolean is_group;
1227 gboolean is_active;
1229 gtk_tree_model_get (model, iter,
1230 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1231 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1232 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1233 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1235 g_object_set (cell,
1236 "visible", !is_group && show_avatar,
1237 "pixbuf", pixbuf,
1238 NULL);
1240 tp_clear_object (&pixbuf);
1242 individual_view_cell_set_background (view, cell, is_group, is_active);
1245 static void
1246 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1247 GtkCellRenderer *cell,
1248 GtkTreeModel *model,
1249 GtkTreeIter *iter,
1250 EmpathyIndividualView *view)
1252 gboolean is_group;
1253 gboolean is_active;
1255 gtk_tree_model_get (model, iter,
1256 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1257 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1259 individual_view_cell_set_background (view, cell, is_group, is_active);
1262 static void
1263 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1264 GtkCellRenderer *cell,
1265 GtkTreeModel *model,
1266 GtkTreeIter *iter,
1267 EmpathyIndividualView *view)
1269 gboolean is_group;
1270 gboolean is_active;
1272 gtk_tree_model_get (model, iter,
1273 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1274 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1276 if (gtk_tree_model_iter_has_child (model, iter))
1278 GtkTreePath *path;
1279 gboolean row_expanded;
1281 path = gtk_tree_model_get_path (model, iter);
1282 row_expanded =
1283 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1284 (gtk_tree_view_column_get_tree_view (column)), path);
1285 gtk_tree_path_free (path);
1287 g_object_set (cell,
1288 "visible", TRUE,
1289 "expander-style",
1290 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1291 NULL);
1293 else
1294 g_object_set (cell, "visible", FALSE, NULL);
1296 individual_view_cell_set_background (view, cell, is_group, is_active);
1299 static void
1300 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1301 GtkTreeIter *iter,
1302 GtkTreePath *path,
1303 gpointer user_data)
1305 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1306 GtkTreeModel *model;
1307 gchar *name;
1308 gboolean expanded;
1310 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1311 return;
1313 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1315 gtk_tree_model_get (model, iter,
1316 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1318 expanded = GPOINTER_TO_INT (user_data);
1319 empathy_contact_group_set_expanded (name, expanded);
1321 g_free (name);
1324 static gboolean
1325 individual_view_start_search_cb (EmpathyIndividualView *view,
1326 gpointer data)
1328 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1330 if (priv->search_widget == NULL)
1331 return FALSE;
1333 empathy_individual_view_start_search (view);
1335 return TRUE;
1338 static void
1339 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1340 GParamSpec *pspec,
1341 EmpathyIndividualView *view)
1343 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1344 GtkTreePath *path;
1345 GtkTreeViewColumn *focus_column;
1346 GtkTreeModel *model;
1347 GtkTreeIter iter;
1348 gboolean set_cursor = FALSE;
1350 gtk_tree_model_filter_refilter (priv->filter);
1352 /* Set cursor on the first contact. If it is already set on a group,
1353 * set it on its first child contact. Note that first child of a group
1354 * is its separator, that's why we actually set to the 2nd
1357 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1358 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1360 if (path == NULL)
1362 path = gtk_tree_path_new_from_string ("0:1");
1363 set_cursor = TRUE;
1365 else if (gtk_tree_path_get_depth (path) < 2)
1367 gboolean is_group;
1369 gtk_tree_model_get_iter (model, &iter, path);
1370 gtk_tree_model_get (model, &iter,
1371 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1372 -1);
1374 if (is_group)
1376 gtk_tree_path_down (path);
1377 gtk_tree_path_next (path);
1378 set_cursor = TRUE;
1382 if (set_cursor)
1384 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1385 * valid. */
1386 if (gtk_tree_model_get_iter (model, &iter, path))
1388 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1389 FALSE);
1393 gtk_tree_path_free (path);
1396 static void
1397 individual_view_search_activate_cb (GtkWidget *search,
1398 EmpathyIndividualView *view)
1400 GtkTreePath *path;
1401 GtkTreeViewColumn *focus_column;
1403 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1404 if (path != NULL)
1406 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1407 gtk_tree_path_free (path);
1409 gtk_widget_hide (search);
1413 static gboolean
1414 individual_view_search_key_navigation_cb (GtkWidget *search,
1415 GdkEvent *event,
1416 EmpathyIndividualView *view)
1418 GdkEvent *new_event;
1419 gboolean ret = FALSE;
1421 new_event = gdk_event_copy (event);
1422 gtk_widget_grab_focus (GTK_WIDGET (view));
1423 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1424 gtk_widget_grab_focus (search);
1426 gdk_event_free (new_event);
1428 return ret;
1431 static void
1432 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1433 EmpathyIndividualView *view)
1435 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1436 GtkTreeModel *model;
1437 GtkTreePath *cursor_path;
1438 GtkTreeIter iter;
1439 gboolean valid = FALSE;
1441 /* block expand or collapse handlers, they would write the
1442 * expand or collapsed setting to file otherwise */
1443 g_signal_handlers_block_by_func (view,
1444 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1445 g_signal_handlers_block_by_func (view,
1446 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1448 /* restore which groups are expanded and which are not */
1449 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1450 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1451 valid; valid = gtk_tree_model_iter_next (model, &iter))
1453 gboolean is_group;
1454 gchar *name = NULL;
1455 GtkTreePath *path;
1457 gtk_tree_model_get (model, &iter,
1458 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1459 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1460 -1);
1462 if (!is_group)
1464 g_free (name);
1465 continue;
1468 path = gtk_tree_model_get_path (model, &iter);
1469 if ((priv->view_features &
1470 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1471 empathy_contact_group_get_expanded (name))
1473 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1475 else
1477 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1480 gtk_tree_path_free (path);
1481 g_free (name);
1484 /* unblock expand or collapse handlers */
1485 g_signal_handlers_unblock_by_func (view,
1486 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1487 g_signal_handlers_unblock_by_func (view,
1488 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1490 /* keep the selected contact visible */
1491 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1493 if (cursor_path != NULL)
1494 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1495 FALSE, 0, 0);
1497 gtk_tree_path_free (cursor_path);
1500 static void
1501 individual_view_search_show_cb (EmpathyLiveSearch *search,
1502 EmpathyIndividualView *view)
1504 /* block expand or collapse handlers during expand all, they would
1505 * write the expand or collapsed setting to file otherwise */
1506 g_signal_handlers_block_by_func (view,
1507 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1509 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1511 g_signal_handlers_unblock_by_func (view,
1512 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1515 static gboolean
1516 expand_idle_foreach_cb (GtkTreeModel *model,
1517 GtkTreePath *path,
1518 GtkTreeIter *iter,
1519 EmpathyIndividualView *self)
1521 EmpathyIndividualViewPriv *priv;
1522 gboolean is_group;
1523 gpointer should_expand;
1524 gchar *name;
1526 /* We only want groups */
1527 if (gtk_tree_path_get_depth (path) > 1)
1528 return FALSE;
1530 gtk_tree_model_get (model, iter,
1531 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1532 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1533 -1);
1535 if (is_group == FALSE)
1537 g_free (name);
1538 return FALSE;
1541 priv = GET_PRIV (self);
1543 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1544 &should_expand) == TRUE)
1546 if (GPOINTER_TO_INT (should_expand) == TRUE)
1547 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1548 else
1549 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1551 g_hash_table_remove (priv->expand_groups, name);
1554 g_free (name);
1556 return FALSE;
1559 static gboolean
1560 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1562 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1564 DEBUG ("individual_view_expand_idle_cb");
1566 g_signal_handlers_block_by_func (self,
1567 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1568 g_signal_handlers_block_by_func (self,
1569 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1571 /* The store/filter could've been removed while we were in the idle queue */
1572 if (priv->filter != NULL)
1574 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1575 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1578 g_signal_handlers_unblock_by_func (self,
1579 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1580 g_signal_handlers_unblock_by_func (self,
1581 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1583 /* Empty the table of groups to expand/contract, since it may contain groups
1584 * which no longer exist in the tree view. This can happen after going
1585 * offline, for example. */
1586 g_hash_table_remove_all (priv->expand_groups);
1587 priv->expand_groups_idle_handler = 0;
1588 g_object_unref (self);
1590 return FALSE;
1593 static void
1594 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1595 GtkTreePath *path,
1596 GtkTreeIter *iter,
1597 EmpathyIndividualView *view)
1599 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1600 gboolean should_expand, is_group = FALSE;
1601 gchar *name = NULL;
1602 gpointer will_expand;
1604 gtk_tree_model_get (model, iter,
1605 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1606 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1607 -1);
1609 if (!is_group || EMP_STR_EMPTY (name))
1611 g_free (name);
1612 return;
1615 should_expand = (priv->view_features &
1616 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1617 (priv->search_widget != NULL &&
1618 gtk_widget_get_visible (priv->search_widget)) ||
1619 empathy_contact_group_get_expanded (name);
1621 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1622 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1623 * a hash table, and expand or contract them as appropriate all at once in
1624 * an idle handler which iterates over all the group rows. */
1625 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1626 &will_expand) == FALSE ||
1627 GPOINTER_TO_INT (will_expand) != should_expand)
1629 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1630 GINT_TO_POINTER (should_expand));
1632 if (priv->expand_groups_idle_handler == 0)
1634 priv->expand_groups_idle_handler =
1635 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1636 g_object_ref (view));
1640 g_free (name);
1643 /* FIXME: This is a workaround for bgo#621076 */
1644 static void
1645 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1646 GtkTreePath *path)
1648 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1649 GtkTreeModel *model;
1650 GtkTreePath *parent_path;
1651 GtkTreeIter parent_iter;
1653 if (gtk_tree_path_get_depth (path) < 2)
1654 return;
1656 /* A group row is visible if and only if at least one if its child is visible.
1657 * So when a row is inserted/deleted/changed in the base model, that could
1658 * modify the visibility of its parent in the filter model.
1661 model = GTK_TREE_MODEL (priv->store);
1662 parent_path = gtk_tree_path_copy (path);
1663 gtk_tree_path_up (parent_path);
1664 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1666 /* This tells the filter to verify the visibility of that row, and
1667 * show/hide it if necessary */
1668 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1669 parent_path, &parent_iter);
1671 gtk_tree_path_free (parent_path);
1674 static void
1675 individual_view_store_row_changed_cb (GtkTreeModel *model,
1676 GtkTreePath *path,
1677 GtkTreeIter *iter,
1678 EmpathyIndividualView *view)
1680 individual_view_verify_group_visibility (view, path);
1683 static void
1684 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1685 GtkTreePath *path,
1686 EmpathyIndividualView *view)
1688 individual_view_verify_group_visibility (view, path);
1691 static gboolean
1692 individual_view_is_visible_individual (EmpathyIndividualView *self,
1693 FolksIndividual *individual,
1694 gboolean is_online,
1695 gboolean is_searching,
1696 const gchar *group,
1697 gboolean is_fake_group,
1698 guint event_count)
1700 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1701 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1702 GeeSet *personas;
1703 GeeIterator *iter;
1704 gboolean is_favorite, contains_interesting_persona = FALSE;
1706 /* Always display individuals having pending events */
1707 if (event_count > 0)
1708 return TRUE;
1710 /* We're only giving the visibility wrt filtering here, not things like
1711 * presence. */
1712 if (priv->show_untrusted == FALSE &&
1713 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1715 return FALSE;
1718 /* Hide all individuals which consist entirely of uninteresting personas */
1719 personas = folks_individual_get_personas (individual);
1720 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1721 while (!contains_interesting_persona && gee_iterator_next (iter))
1723 FolksPersona *persona = gee_iterator_get (iter);
1725 if (empathy_folks_persona_is_interesting (persona))
1726 contains_interesting_persona = TRUE;
1728 g_clear_object (&persona);
1730 g_clear_object (&iter);
1732 if (contains_interesting_persona == FALSE)
1733 return FALSE;
1735 is_favorite = folks_favourite_details_get_is_favourite (
1736 FOLKS_FAVOURITE_DETAILS (individual));
1737 if (is_searching == FALSE) {
1738 if (is_favorite && is_fake_group &&
1739 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1740 /* Always display favorite contacts in the favorite group */
1741 return TRUE;
1743 return (priv->show_offline || is_online);
1746 return empathy_individual_match_string (individual,
1747 empathy_live_search_get_text (live),
1748 empathy_live_search_get_words (live));
1751 static gchar *
1752 get_group (GtkTreeModel *model,
1753 GtkTreeIter *iter,
1754 gboolean *is_fake)
1756 GtkTreeIter parent_iter;
1757 gchar *name = NULL;
1759 *is_fake = FALSE;
1761 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1762 return NULL;
1764 gtk_tree_model_get (model, &parent_iter,
1765 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1766 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1767 -1);
1769 return name;
1773 static gboolean
1774 individual_view_filter_visible_func (GtkTreeModel *model,
1775 GtkTreeIter *iter,
1776 gpointer user_data)
1778 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1779 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1780 FolksIndividual *individual = NULL;
1781 gboolean is_group, is_separator, valid;
1782 GtkTreeIter child_iter;
1783 gboolean visible, is_online;
1784 gboolean is_searching = TRUE;
1785 guint event_count;
1787 if (priv->custom_filter != NULL)
1788 return priv->custom_filter (model, iter, priv->custom_filter_data);
1790 if (priv->search_widget == NULL ||
1791 !gtk_widget_get_visible (priv->search_widget))
1792 is_searching = FALSE;
1794 gtk_tree_model_get (model, iter,
1795 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1796 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1797 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1798 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1799 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1800 -1);
1802 if (individual != NULL)
1804 gchar *group;
1805 gboolean is_fake_group;
1807 group = get_group (model, iter, &is_fake_group);
1809 visible = individual_view_is_visible_individual (self, individual,
1810 is_online, is_searching, group, is_fake_group, event_count);
1812 g_object_unref (individual);
1813 g_free (group);
1815 /* FIXME: Work around bgo#626552/bgo#621076 */
1816 if (visible == TRUE)
1818 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1819 individual_view_verify_group_visibility (self, path);
1820 gtk_tree_path_free (path);
1823 return visible;
1826 if (is_separator)
1827 return TRUE;
1829 /* Not a contact, not a separator, must be a group */
1830 g_return_val_if_fail (is_group, FALSE);
1832 /* only show groups which are not empty */
1833 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1834 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1836 gchar *group;
1837 gboolean is_fake_group;
1839 gtk_tree_model_get (model, &child_iter,
1840 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1841 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1842 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1843 -1);
1845 if (individual == NULL)
1846 continue;
1848 group = get_group (model, &child_iter, &is_fake_group);
1850 visible = individual_view_is_visible_individual (self, individual,
1851 is_online, is_searching, group, is_fake_group, event_count);
1853 g_object_unref (individual);
1854 g_free (group);
1856 /* show group if it has at least one visible contact in it */
1857 if (visible == TRUE)
1858 return TRUE;
1861 return FALSE;
1864 static void
1865 individual_view_constructed (GObject *object)
1867 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1868 GtkCellRenderer *cell;
1869 GtkTreeViewColumn *col;
1870 guint i;
1872 /* Setup view */
1873 g_object_set (view,
1874 "headers-visible", FALSE,
1875 "show-expanders", FALSE,
1876 NULL);
1878 col = gtk_tree_view_column_new ();
1880 /* State */
1881 cell = gtk_cell_renderer_pixbuf_new ();
1882 gtk_tree_view_column_pack_start (col, cell, FALSE);
1883 gtk_tree_view_column_set_cell_data_func (col, cell,
1884 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1885 view, NULL);
1887 g_object_set (cell,
1888 "xpad", 5,
1889 "ypad", 1,
1890 "visible", FALSE,
1891 NULL);
1893 /* Group icon */
1894 cell = gtk_cell_renderer_pixbuf_new ();
1895 gtk_tree_view_column_pack_start (col, cell, FALSE);
1896 gtk_tree_view_column_set_cell_data_func (col, cell,
1897 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1898 view, NULL);
1900 g_object_set (cell,
1901 "xpad", 0,
1902 "ypad", 0,
1903 "visible", FALSE,
1904 "width", 16,
1905 "height", 16,
1906 NULL);
1908 /* Name */
1909 cell = empathy_cell_renderer_text_new ();
1910 gtk_tree_view_column_pack_start (col, cell, TRUE);
1911 gtk_tree_view_column_set_cell_data_func (col, cell,
1912 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1914 gtk_tree_view_column_add_attribute (col, cell,
1915 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1916 gtk_tree_view_column_add_attribute (col, cell,
1917 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1918 gtk_tree_view_column_add_attribute (col, cell,
1919 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1920 gtk_tree_view_column_add_attribute (col, cell,
1921 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1922 gtk_tree_view_column_add_attribute (col, cell,
1923 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1924 gtk_tree_view_column_add_attribute (col, cell,
1925 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1926 gtk_tree_view_column_add_attribute (col, cell,
1927 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1929 /* Audio Call Icon */
1930 cell = empathy_cell_renderer_activatable_new ();
1931 gtk_tree_view_column_pack_start (col, cell, FALSE);
1932 gtk_tree_view_column_set_cell_data_func (col, cell,
1933 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1934 view, NULL);
1936 g_object_set (cell, "visible", FALSE, NULL);
1938 g_signal_connect (cell, "path-activated",
1939 G_CALLBACK (individual_view_call_activated_cb), view);
1941 /* Avatar */
1942 cell = gtk_cell_renderer_pixbuf_new ();
1943 gtk_tree_view_column_pack_start (col, cell, FALSE);
1944 gtk_tree_view_column_set_cell_data_func (col, cell,
1945 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1946 view, NULL);
1948 g_object_set (cell,
1949 "xpad", 0,
1950 "ypad", 0,
1951 "visible", FALSE,
1952 "width", 32,
1953 "height", 32,
1954 NULL);
1956 /* Expander */
1957 cell = empathy_cell_renderer_expander_new ();
1958 gtk_tree_view_column_pack_end (col, cell, FALSE);
1959 gtk_tree_view_column_set_cell_data_func (col, cell,
1960 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1961 view, NULL);
1963 /* Actually add the column now we have added all cell renderers */
1964 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1966 /* Drag & Drop. */
1967 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1969 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1973 static void
1974 individual_view_set_view_features (EmpathyIndividualView *view,
1975 EmpathyIndividualFeatureFlags features)
1977 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1978 gboolean has_tooltip;
1980 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1982 priv->view_features = features;
1984 /* Setting reorderable is a hack that gets us row previews as drag icons
1985 for free. We override all the drag handlers. It's tricky to get the
1986 position of the drag icon right in drag_begin. GtkTreeView has special
1987 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1988 is enabled).
1990 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1991 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1993 /* Update DnD source/dest */
1994 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1996 gtk_drag_source_set (GTK_WIDGET (view),
1997 GDK_BUTTON1_MASK,
1998 drag_types_source,
1999 G_N_ELEMENTS (drag_types_source),
2000 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2002 else
2004 gtk_drag_source_unset (GTK_WIDGET (view));
2008 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2010 gtk_drag_dest_set (GTK_WIDGET (view),
2011 GTK_DEST_DEFAULT_ALL,
2012 drag_types_dest,
2013 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2015 else
2017 /* FIXME: URI could still be droped depending on FT feature */
2018 gtk_drag_dest_unset (GTK_WIDGET (view));
2021 /* Update has-tooltip */
2022 has_tooltip =
2023 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2024 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2027 static void
2028 individual_view_dispose (GObject *object)
2030 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2031 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2033 tp_clear_object (&priv->store);
2034 tp_clear_object (&priv->filter);
2035 tp_clear_object (&priv->tooltip_widget);
2037 empathy_individual_view_set_live_search (view, NULL);
2039 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2042 static void
2043 individual_view_finalize (GObject *object)
2045 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2047 if (priv->expand_groups_idle_handler != 0)
2048 g_source_remove (priv->expand_groups_idle_handler);
2049 g_hash_table_destroy (priv->expand_groups);
2051 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2054 static void
2055 individual_view_get_property (GObject *object,
2056 guint param_id,
2057 GValue *value,
2058 GParamSpec *pspec)
2060 EmpathyIndividualViewPriv *priv;
2062 priv = GET_PRIV (object);
2064 switch (param_id)
2066 case PROP_STORE:
2067 g_value_set_object (value, priv->store);
2068 break;
2069 case PROP_VIEW_FEATURES:
2070 g_value_set_flags (value, priv->view_features);
2071 break;
2072 case PROP_INDIVIDUAL_FEATURES:
2073 g_value_set_flags (value, priv->individual_features);
2074 break;
2075 case PROP_SHOW_OFFLINE:
2076 g_value_set_boolean (value, priv->show_offline);
2077 break;
2078 case PROP_SHOW_UNTRUSTED:
2079 g_value_set_boolean (value, priv->show_untrusted);
2080 break;
2081 default:
2082 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2083 break;
2087 static void
2088 individual_view_set_property (GObject *object,
2089 guint param_id,
2090 const GValue *value,
2091 GParamSpec *pspec)
2093 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2094 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2096 switch (param_id)
2098 case PROP_STORE:
2099 empathy_individual_view_set_store (view, g_value_get_object (value));
2100 break;
2101 case PROP_VIEW_FEATURES:
2102 individual_view_set_view_features (view, g_value_get_flags (value));
2103 break;
2104 case PROP_INDIVIDUAL_FEATURES:
2105 priv->individual_features = g_value_get_flags (value);
2106 break;
2107 case PROP_SHOW_OFFLINE:
2108 empathy_individual_view_set_show_offline (view,
2109 g_value_get_boolean (value));
2110 break;
2111 case PROP_SHOW_UNTRUSTED:
2112 empathy_individual_view_set_show_untrusted (view,
2113 g_value_get_boolean (value));
2114 break;
2115 default:
2116 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2117 break;
2121 static void
2122 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2124 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2125 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2126 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2128 object_class->constructed = individual_view_constructed;
2129 object_class->dispose = individual_view_dispose;
2130 object_class->finalize = individual_view_finalize;
2131 object_class->get_property = individual_view_get_property;
2132 object_class->set_property = individual_view_set_property;
2134 widget_class->drag_data_received = individual_view_drag_data_received;
2135 widget_class->drag_drop = individual_view_drag_drop;
2136 widget_class->drag_begin = individual_view_drag_begin;
2137 widget_class->drag_data_get = individual_view_drag_data_get;
2138 widget_class->drag_end = individual_view_drag_end;
2139 widget_class->drag_motion = individual_view_drag_motion;
2141 /* We use the class method to let user of this widget to connect to
2142 * the signal and stop emission of the signal so the default handler
2143 * won't be called. */
2144 tree_view_class->row_activated = individual_view_row_activated;
2146 klass->drag_individual_received = real_drag_individual_received_cb;
2148 signals[DRAG_INDIVIDUAL_RECEIVED] =
2149 g_signal_new ("drag-individual-received",
2150 G_OBJECT_CLASS_TYPE (klass),
2151 G_SIGNAL_RUN_LAST,
2152 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2153 NULL, NULL,
2154 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2155 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2156 G_TYPE_STRING, G_TYPE_STRING);
2158 signals[DRAG_PERSONA_RECEIVED] =
2159 g_signal_new ("drag-persona-received",
2160 G_OBJECT_CLASS_TYPE (klass),
2161 G_SIGNAL_RUN_LAST,
2162 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2163 NULL, NULL,
2164 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2165 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2167 g_object_class_install_property (object_class,
2168 PROP_STORE,
2169 g_param_spec_object ("store",
2170 "The store of the view",
2171 "The store of the view",
2172 EMPATHY_TYPE_INDIVIDUAL_STORE,
2173 G_PARAM_READWRITE));
2174 g_object_class_install_property (object_class,
2175 PROP_VIEW_FEATURES,
2176 g_param_spec_flags ("view-features",
2177 "Features of the view",
2178 "Flags for all enabled features",
2179 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2180 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2181 g_object_class_install_property (object_class,
2182 PROP_INDIVIDUAL_FEATURES,
2183 g_param_spec_flags ("individual-features",
2184 "Features of the individual menu",
2185 "Flags for all enabled features for the menu",
2186 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2187 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2188 g_object_class_install_property (object_class,
2189 PROP_SHOW_OFFLINE,
2190 g_param_spec_boolean ("show-offline",
2191 "Show Offline",
2192 "Whether contact list should display "
2193 "offline contacts", FALSE, G_PARAM_READWRITE));
2194 g_object_class_install_property (object_class,
2195 PROP_SHOW_UNTRUSTED,
2196 g_param_spec_boolean ("show-untrusted",
2197 "Show Untrusted Individuals",
2198 "Whether the view should display untrusted individuals; "
2199 "those who could not be who they say they are.",
2200 TRUE, G_PARAM_READWRITE));
2202 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2205 static void
2206 empathy_individual_view_init (EmpathyIndividualView *view)
2208 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2209 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2211 view->priv = priv;
2213 priv->show_untrusted = TRUE;
2215 /* Get saved group states. */
2216 empathy_contact_groups_get_all ();
2218 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2219 (GDestroyNotify) g_free, NULL);
2221 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2222 empathy_individual_store_row_separator_func, NULL, NULL);
2224 /* Connect to tree view signals rather than override. */
2225 g_signal_connect (view, "button-press-event",
2226 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2227 g_signal_connect (view, "key-press-event",
2228 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2229 g_signal_connect (view, "row-expanded",
2230 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2231 GINT_TO_POINTER (TRUE));
2232 g_signal_connect (view, "row-collapsed",
2233 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2234 GINT_TO_POINTER (FALSE));
2235 g_signal_connect (view, "query-tooltip",
2236 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2239 EmpathyIndividualView *
2240 empathy_individual_view_new (EmpathyIndividualStore *store,
2241 EmpathyIndividualViewFeatureFlags view_features,
2242 EmpathyIndividualFeatureFlags individual_features)
2244 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2246 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2247 "store", store,
2248 "individual-features", individual_features,
2249 "view-features", view_features, NULL);
2252 FolksIndividual *
2253 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2255 GtkTreeSelection *selection;
2256 GtkTreeIter iter;
2257 GtkTreeModel *model;
2258 FolksIndividual *individual;
2260 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2262 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2263 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2264 return NULL;
2266 gtk_tree_model_get (model, &iter,
2267 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2269 return individual;
2272 static gchar *
2273 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2274 gboolean *is_fake_group)
2276 GtkTreeSelection *selection;
2277 GtkTreeIter iter;
2278 GtkTreeModel *model;
2279 gboolean is_group;
2280 gchar *name;
2281 gboolean fake;
2283 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2285 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2286 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2287 return NULL;
2289 gtk_tree_model_get (model, &iter,
2290 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2291 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2292 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2294 if (!is_group)
2296 g_free (name);
2297 return NULL;
2300 if (is_fake_group != NULL)
2301 *is_fake_group = fake;
2303 return name;
2306 enum
2308 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2309 REMOVE_DIALOG_RESPONSE_DELETE,
2310 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
2313 static int
2314 individual_view_remove_dialog_show (GtkWindow *parent,
2315 const gchar *message,
2316 const gchar *secondary_text,
2317 gboolean block_button,
2318 GdkPixbuf *avatar)
2320 GtkWidget *dialog;
2321 gboolean res;
2323 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2324 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2326 if (avatar != NULL)
2328 GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
2329 gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
2330 gtk_widget_show (image);
2333 if (block_button)
2335 GtkWidget *button;
2337 /* gtk_dialog_add_button() doesn't allow us to pass a string with a
2338 * mnemonic so we have to create the button manually. */
2339 button = gtk_button_new_with_mnemonic (
2340 _("Delete and _Block"));
2342 gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
2343 REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
2345 gtk_widget_show (button);
2348 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2349 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2350 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2351 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2352 "%s", secondary_text);
2354 gtk_widget_show (dialog);
2356 res = gtk_dialog_run (GTK_DIALOG (dialog));
2357 gtk_widget_destroy (dialog);
2359 return res;
2362 static void
2363 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2364 EmpathyIndividualView *view)
2366 gchar *group;
2368 group = empathy_individual_view_dup_selected_group (view, NULL);
2369 if (group != NULL)
2371 gchar *text;
2372 GtkWindow *parent;
2374 text =
2375 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2376 group);
2377 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2378 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2379 text, FALSE, NULL) == REMOVE_DIALOG_RESPONSE_DELETE)
2381 EmpathyIndividualManager *manager =
2382 empathy_individual_manager_dup_singleton ();
2383 empathy_individual_manager_remove_group (manager, group);
2384 g_object_unref (G_OBJECT (manager));
2387 g_free (text);
2390 g_free (group);
2393 GtkWidget *
2394 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2396 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2397 gchar *group;
2398 GtkWidget *menu;
2399 GtkWidget *item;
2400 GtkWidget *image;
2401 gboolean is_fake_group;
2403 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2405 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2406 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2407 return NULL;
2409 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2410 if (!group || is_fake_group)
2412 /* We can't alter fake groups */
2413 g_free (group);
2414 return NULL;
2417 menu = gtk_menu_new ();
2419 /* TODO: implement
2420 if (priv->view_features &
2421 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2422 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2423 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2424 gtk_widget_show (item);
2425 g_signal_connect (item, "activate",
2426 G_CALLBACK (individual_view_group_rename_activate_cb),
2427 view);
2431 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2433 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2434 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2435 GTK_ICON_SIZE_MENU);
2436 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2437 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2438 gtk_widget_show (item);
2439 g_signal_connect (item, "activate",
2440 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2443 g_free (group);
2445 return menu;
2448 static void
2449 got_avatar (GObject *source_object,
2450 GAsyncResult *result,
2451 gpointer user_data)
2453 FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
2454 EmpathyIndividualView *view = user_data;
2455 GdkPixbuf *avatar;
2456 EmpathyIndividualManager *manager;
2457 gchar *text;
2458 GtkWindow *parent;
2459 GeeSet *personas;
2460 GeeIterator *iter;
2461 guint persona_count = 0;
2462 gboolean can_block;
2463 GError *error = NULL;
2464 gint res;
2466 avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
2467 result, &error);
2469 if (error != NULL)
2471 DEBUG ("Could not get avatar: %s", error->message);
2472 g_error_free (error);
2475 /* We couldn't retrieve the avatar, but that isn't a fatal error,
2476 * so we still display the remove dialog. */
2478 personas = folks_individual_get_personas (individual);
2479 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2481 /* If we have more than one TpfPersona, display a different message
2482 * ensuring the user knows that *all* of the meta-contacts' personas will
2483 * be removed. */
2484 while (persona_count < 2 && gee_iterator_next (iter))
2486 FolksPersona *persona = gee_iterator_get (iter);
2488 if (empathy_folks_persona_is_interesting (persona))
2489 persona_count++;
2491 g_clear_object (&persona);
2493 g_clear_object (&iter);
2495 if (persona_count < 2)
2497 /* Not a meta-contact */
2498 text =
2499 g_strdup_printf (
2500 _("Do you really want to remove the contact '%s'?"),
2501 folks_alias_details_get_alias (
2502 FOLKS_ALIAS_DETAILS (individual)));
2504 else
2506 /* Meta-contact */
2507 text =
2508 g_strdup_printf (
2509 _("Do you really want to remove the linked contact '%s'? "
2510 "Note that this will remove all the contacts which make up "
2511 "this linked contact."),
2512 folks_alias_details_get_alias (
2513 FOLKS_ALIAS_DETAILS (individual)));
2517 manager = empathy_individual_manager_dup_singleton ();
2518 can_block = empathy_individual_manager_supports_blocking (manager,
2519 individual);
2520 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2521 res = individual_view_remove_dialog_show (parent, _("Removing contact"),
2522 text, can_block, avatar);
2524 if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
2525 res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2527 gboolean abusive;
2529 if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
2531 if (!empathy_block_individual_dialog_show (parent, individual,
2532 avatar, &abusive))
2533 goto finally;
2535 empathy_individual_manager_set_blocked (manager, individual,
2536 TRUE, abusive);
2539 empathy_individual_manager_remove (manager, individual, "");
2542 finally:
2543 g_free (text);
2544 g_object_unref (manager);
2547 static void
2548 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2549 EmpathyIndividualView *view)
2551 FolksIndividual *individual;
2553 individual = empathy_individual_view_dup_selected (view);
2555 if (individual != NULL)
2557 empathy_pixbuf_avatar_from_individual_scaled_async (individual,
2558 48, 48, NULL, got_avatar, view);
2559 g_object_unref (individual);
2563 static void
2564 individual_menu_link_contacts_activated_cb (EmpathyIndividualMenu *menu,
2565 EmpathyLinkingDialog *linking_dialog,
2566 EmpathyIndividualView *self)
2568 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2569 EmpathyIndividualLinker *linker;
2571 linker = empathy_linking_dialog_get_individual_linker (linking_dialog);
2572 empathy_individual_linker_set_search_text (linker,
2573 empathy_live_search_get_text (EMPATHY_LIVE_SEARCH (priv->search_widget)));
2576 GtkWidget *
2577 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2579 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2580 FolksIndividual *individual;
2581 GtkWidget *menu = NULL;
2582 GtkWidget *item;
2583 GtkWidget *image;
2584 gboolean can_remove = FALSE;
2585 GeeSet *personas;
2586 GeeIterator *iter;
2588 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2590 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2591 /* No need to create a context menu */
2592 return NULL;
2594 individual = empathy_individual_view_dup_selected (view);
2595 if (individual == NULL)
2596 return NULL;
2598 /* If any of the Individual's personas can be removed, add an option to
2599 * remove. This will act as a best-effort option. If any Personas cannot be
2600 * removed from the server, then this option will just be inactive upon
2601 * subsequent menu openings */
2602 personas = folks_individual_get_personas (individual);
2603 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2604 while (!can_remove && gee_iterator_next (iter))
2606 FolksPersona *persona = gee_iterator_get (iter);
2607 FolksPersonaStore *store = folks_persona_get_store (persona);
2608 FolksMaybeBool maybe_can_remove =
2609 folks_persona_store_get_can_remove_personas (store);
2611 if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
2612 can_remove = TRUE;
2614 g_clear_object (&persona);
2616 g_clear_object (&iter);
2618 menu = empathy_individual_menu_new (individual, priv->individual_features);
2620 /* Remove contact */
2621 if ((priv->view_features &
2622 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE) &&
2623 can_remove)
2625 /* create the menu if required, or just add a separator */
2626 if (menu == NULL)
2627 menu = gtk_menu_new ();
2628 else
2630 item = gtk_separator_menu_item_new ();
2631 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2632 gtk_widget_show (item);
2635 /* Remove */
2636 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2637 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2638 GTK_ICON_SIZE_MENU);
2639 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2640 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2641 gtk_widget_show (item);
2642 g_signal_connect (item, "activate",
2643 G_CALLBACK (individual_view_remove_activate_cb), view);
2646 /* Connect to EmpathyIndividualMenu::link-contacts-activated so that we can
2647 * set the live search text on the new linking dialogue to be the same as
2648 * our own. */
2649 g_signal_connect (menu, "link-contacts-activated",
2650 (GCallback) individual_menu_link_contacts_activated_cb, view);
2652 g_object_unref (individual);
2654 return menu;
2657 void
2658 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2659 EmpathyLiveSearch *search)
2661 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2663 /* remove old handlers if old search was not null */
2664 if (priv->search_widget != NULL)
2666 g_signal_handlers_disconnect_by_func (view,
2667 individual_view_start_search_cb, NULL);
2669 g_signal_handlers_disconnect_by_func (priv->search_widget,
2670 individual_view_search_text_notify_cb, view);
2671 g_signal_handlers_disconnect_by_func (priv->search_widget,
2672 individual_view_search_activate_cb, view);
2673 g_signal_handlers_disconnect_by_func (priv->search_widget,
2674 individual_view_search_key_navigation_cb, view);
2675 g_signal_handlers_disconnect_by_func (priv->search_widget,
2676 individual_view_search_hide_cb, view);
2677 g_signal_handlers_disconnect_by_func (priv->search_widget,
2678 individual_view_search_show_cb, view);
2679 g_object_unref (priv->search_widget);
2680 priv->search_widget = NULL;
2683 /* connect handlers if new search is not null */
2684 if (search != NULL)
2686 priv->search_widget = g_object_ref (search);
2688 g_signal_connect (view, "start-interactive-search",
2689 G_CALLBACK (individual_view_start_search_cb), NULL);
2691 g_signal_connect (priv->search_widget, "notify::text",
2692 G_CALLBACK (individual_view_search_text_notify_cb), view);
2693 g_signal_connect (priv->search_widget, "activate",
2694 G_CALLBACK (individual_view_search_activate_cb), view);
2695 g_signal_connect (priv->search_widget, "key-navigation",
2696 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2697 g_signal_connect (priv->search_widget, "hide",
2698 G_CALLBACK (individual_view_search_hide_cb), view);
2699 g_signal_connect (priv->search_widget, "show",
2700 G_CALLBACK (individual_view_search_show_cb), view);
2704 gboolean
2705 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2707 EmpathyIndividualViewPriv *priv;
2709 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2711 priv = GET_PRIV (self);
2713 return (priv->search_widget != NULL &&
2714 gtk_widget_get_visible (priv->search_widget));
2717 gboolean
2718 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2720 EmpathyIndividualViewPriv *priv;
2722 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2724 priv = GET_PRIV (self);
2726 return priv->show_offline;
2729 void
2730 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2731 gboolean show_offline)
2733 EmpathyIndividualViewPriv *priv;
2735 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2737 priv = GET_PRIV (self);
2739 priv->show_offline = show_offline;
2741 g_object_notify (G_OBJECT (self), "show-offline");
2742 gtk_tree_model_filter_refilter (priv->filter);
2745 gboolean
2746 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2748 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2750 return GET_PRIV (self)->show_untrusted;
2753 void
2754 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2755 gboolean show_untrusted)
2757 EmpathyIndividualViewPriv *priv;
2759 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2761 priv = GET_PRIV (self);
2763 priv->show_untrusted = show_untrusted;
2765 g_object_notify (G_OBJECT (self), "show-untrusted");
2766 gtk_tree_model_filter_refilter (priv->filter);
2769 EmpathyIndividualStore *
2770 empathy_individual_view_get_store (EmpathyIndividualView *self)
2772 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2774 return GET_PRIV (self)->store;
2777 void
2778 empathy_individual_view_set_store (EmpathyIndividualView *self,
2779 EmpathyIndividualStore *store)
2781 EmpathyIndividualViewPriv *priv;
2783 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2784 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2786 priv = GET_PRIV (self);
2788 /* Destroy the old filter and remove the old store */
2789 if (priv->store != NULL)
2791 g_signal_handlers_disconnect_by_func (priv->store,
2792 individual_view_store_row_changed_cb, self);
2793 g_signal_handlers_disconnect_by_func (priv->store,
2794 individual_view_store_row_deleted_cb, self);
2796 g_signal_handlers_disconnect_by_func (priv->filter,
2797 individual_view_row_has_child_toggled_cb, self);
2799 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2802 tp_clear_object (&priv->filter);
2803 tp_clear_object (&priv->store);
2805 /* Set the new store */
2806 priv->store = store;
2808 if (store != NULL)
2810 g_object_ref (store);
2812 /* Create a new filter */
2813 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2814 GTK_TREE_MODEL (priv->store), NULL));
2815 gtk_tree_model_filter_set_visible_func (priv->filter,
2816 individual_view_filter_visible_func, self, NULL);
2818 g_signal_connect (priv->filter, "row-has-child-toggled",
2819 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2820 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2821 GTK_TREE_MODEL (priv->filter));
2823 tp_g_signal_connect_object (priv->store, "row-changed",
2824 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2825 tp_g_signal_connect_object (priv->store, "row-inserted",
2826 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2827 tp_g_signal_connect_object (priv->store, "row-deleted",
2828 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);
2832 void
2833 empathy_individual_view_start_search (EmpathyIndividualView *self)
2835 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2837 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2838 g_return_if_fail (priv->search_widget != NULL);
2840 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2841 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2842 else
2843 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2846 void
2847 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2848 GtkTreeModelFilterVisibleFunc filter,
2849 gpointer data)
2851 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2853 priv->custom_filter = filter;
2854 priv->custom_filter_data = data;
2857 void
2858 empathy_individual_view_refilter (EmpathyIndividualView *self)
2860 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2862 gtk_tree_model_filter_refilter (priv->filter);