Port empathy-call to GtkApplication
[empathy-mirror.git] / libempathy-gtk / empathy-contact-list-view.c
blob2b0b51a36af7228428d10852c6625ed59644f78d
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-2008 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>
26 #include "config.h"
28 #include <string.h>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <gtk/gtk.h>
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
37 #include <libempathy/empathy-tp-contact-factory.h>
38 #include <libempathy/empathy-contact-list.h>
39 #include <libempathy/empathy-contact-groups.h>
40 #include <libempathy/empathy-request-util.h>
41 #include <libempathy/empathy-utils.h>
43 #include "empathy-contact-list-view.h"
44 #include "empathy-contact-list-store.h"
45 #include "empathy-images.h"
46 #include "empathy-cell-renderer-expander.h"
47 #include "empathy-cell-renderer-text.h"
48 #include "empathy-cell-renderer-activatable.h"
49 #include "empathy-ui-utils.h"
50 #include "empathy-gtk-enum-types.h"
51 #include "empathy-gtk-marshal.h"
53 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
54 #include <libempathy/empathy-debug.h>
56 /* Active users are those which have recently changed state
57 * (e.g. online, offline or from normal to a busy state).
60 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
61 typedef struct {
62 EmpathyContactListStore *store;
63 GtkTreeRowReference *drag_row;
64 EmpathyContactListFeatureFlags list_features;
65 EmpathyContactFeatureFlags contact_features;
66 GtkWidget *tooltip_widget;
67 GtkTargetList *file_targets;
69 GtkTreeModelFilter *filter;
70 GtkWidget *search_widget;
71 } EmpathyContactListViewPriv;
73 typedef struct {
74 EmpathyContactListView *view;
75 GtkTreePath *path;
76 guint timeout_id;
77 } DragMotionData;
79 typedef struct {
80 EmpathyContactListView *view;
81 EmpathyContact *contact;
82 gboolean remove;
83 } ShowActiveData;
85 enum {
86 PROP_0,
87 PROP_STORE,
88 PROP_LIST_FEATURES,
89 PROP_CONTACT_FEATURES,
92 enum DndDragType {
93 DND_DRAG_TYPE_CONTACT_ID,
94 DND_DRAG_TYPE_URI_LIST,
95 DND_DRAG_TYPE_STRING,
98 static const GtkTargetEntry drag_types_dest[] = {
99 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
100 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
101 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
102 { "text/plain", 0, DND_DRAG_TYPE_STRING },
103 { "STRING", 0, DND_DRAG_TYPE_STRING },
106 static const GtkTargetEntry drag_types_dest_file[] = {
107 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
108 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
111 static const GtkTargetEntry drag_types_source[] = {
112 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
115 enum {
116 DRAG_CONTACT_RECEIVED,
117 LAST_SIGNAL
120 static guint signals[LAST_SIGNAL];
122 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
124 static void
125 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
126 EmpathyContactListView *view)
128 EmpathyContactListViewPriv *priv = GET_PRIV (view);
130 if (priv->tooltip_widget) {
131 DEBUG ("Tooltip destroyed");
132 g_object_unref (priv->tooltip_widget);
133 priv->tooltip_widget = NULL;
137 static gboolean
138 contact_list_view_is_visible_contact (EmpathyContactListView *self,
139 EmpathyContact *contact)
141 EmpathyContactListViewPriv *priv = GET_PRIV (self);
142 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
143 const gchar *str;
144 const gchar *p;
145 gchar *dup_str = NULL;
146 gboolean visible;
148 g_assert (live != NULL);
150 /* check alias name */
151 str = empathy_contact_get_alias (contact);
152 if (empathy_live_search_match (live, str))
153 return TRUE;
155 /* check contact id, remove the @server.com part */
156 str = empathy_contact_get_id (contact);
157 p = strstr (str, "@");
158 if (p != NULL)
159 str = dup_str = g_strndup (str, p - str);
161 visible = empathy_live_search_match (live, str);
162 g_free (dup_str);
163 if (visible)
164 return TRUE;
166 /* FIXME: Add more rules here, we could check phone numbers in
167 * contact's vCard for example. */
169 return FALSE;
172 static gboolean
173 contact_list_view_filter_visible_func (GtkTreeModel *model,
174 GtkTreeIter *iter,
175 gpointer user_data)
177 EmpathyContactListView *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
178 EmpathyContactListViewPriv *priv = GET_PRIV (self);
179 EmpathyContact *contact = NULL;
180 gboolean is_group, is_separator, valid;
181 GtkTreeIter child_iter;
182 gboolean visible;
184 if (priv->search_widget == NULL ||
185 !gtk_widget_get_visible (priv->search_widget))
186 return TRUE;
188 gtk_tree_model_get (model, iter,
189 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
190 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
191 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
192 -1);
194 if (contact != NULL) {
195 visible = contact_list_view_is_visible_contact (self, contact);
196 g_object_unref (contact);
197 return visible;
200 if (is_separator) {
201 return TRUE;
204 /* Not a contact, not a separator, must be a group */
205 g_return_val_if_fail (is_group, FALSE);
207 /* only show groups which are not empty */
208 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
209 valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
210 gtk_tree_model_get (model, &child_iter,
211 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
212 -1);
214 if (contact == NULL)
215 continue;
217 visible = contact_list_view_is_visible_contact (self, contact);
218 g_object_unref (contact);
220 /* show group if it has at least one visible contact in it */
221 if (visible)
222 return TRUE;
225 return FALSE;
228 static gboolean
229 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
230 gint x,
231 gint y,
232 gboolean keyboard_mode,
233 GtkTooltip *tooltip,
234 gpointer user_data)
236 EmpathyContactListViewPriv *priv = GET_PRIV (view);
237 EmpathyContact *contact;
238 GtkTreeModel *model;
239 GtkTreeIter iter;
240 GtkTreePath *path;
241 static gint running = 0;
242 gboolean ret = FALSE;
244 /* Avoid an infinite loop. See GNOME bug #574377 */
245 if (running > 0) {
246 return FALSE;
248 running++;
250 /* Don't show the tooltip if there's already a popup menu */
251 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
252 goto OUT;
255 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
256 keyboard_mode,
257 &model, &path, &iter)) {
258 goto OUT;
261 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
262 gtk_tree_path_free (path);
264 gtk_tree_model_get (model, &iter,
265 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
266 -1);
267 if (!contact) {
268 goto OUT;
271 if (!priv->tooltip_widget) {
272 priv->tooltip_widget = empathy_contact_widget_new (contact,
273 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
274 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
275 gtk_container_set_border_width (
276 GTK_CONTAINER (priv->tooltip_widget), 8);
277 g_object_ref (priv->tooltip_widget);
278 g_signal_connect (priv->tooltip_widget, "destroy",
279 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
280 view);
281 gtk_widget_show (priv->tooltip_widget);
282 } else {
283 empathy_contact_widget_set_contact (priv->tooltip_widget,
284 contact);
287 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
288 ret = TRUE;
290 g_object_unref (contact);
291 OUT:
292 running--;
294 return ret;
297 typedef struct {
298 gchar *new_group;
299 gchar *old_group;
300 GdkDragAction action;
301 } DndGetContactData;
303 static void
304 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
306 g_free (data->new_group);
307 g_free (data->old_group);
308 g_slice_free (DndGetContactData, data);
311 static void
312 contact_list_view_drag_got_contact (TpConnection *connection,
313 EmpathyContact *contact,
314 const GError *error,
315 gpointer user_data,
316 GObject *view)
318 EmpathyContactListViewPriv *priv = GET_PRIV (view);
319 DndGetContactData *data = user_data;
320 EmpathyContactList *list;
322 if (error != NULL) {
323 DEBUG ("Error: %s", error->message);
324 return;
327 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
328 empathy_contact_get_id (contact),
329 empathy_contact_get_handle (contact),
330 data->old_group, data->new_group);
332 list = empathy_contact_list_store_get_list_iface (priv->store);
334 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
335 /* Mark contact as favourite */
336 empathy_contact_list_add_to_favourites (list, contact);
337 return;
340 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
341 /* Remove contact as favourite */
342 empathy_contact_list_remove_from_favourites (list, contact);
343 /* Don't try to remove it */
344 g_free (data->old_group);
345 data->old_group = NULL;
348 if (data->new_group) {
349 empathy_contact_list_add_to_group (list, contact, data->new_group);
351 if (data->old_group && data->action == GDK_ACTION_MOVE) {
352 empathy_contact_list_remove_from_group (list, contact, data->old_group);
356 static gboolean
357 group_can_be_modified (const gchar *name,
358 gboolean is_fake_group,
359 gboolean adding)
361 /* Real groups can always be modified */
362 if (!is_fake_group)
363 return TRUE;
365 /* The favorite fake group can be modified so users can
366 * add/remove favorites using DnD */
367 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
368 return TRUE;
370 /* We can remove contacts from the 'ungrouped' fake group */
371 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
372 return TRUE;
374 return FALSE;
377 static gboolean
378 contact_list_view_contact_drag_received (GtkWidget *view,
379 GdkDragContext *context,
380 GtkTreeModel *model,
381 GtkTreePath *path,
382 GtkSelectionData *selection)
384 EmpathyContactListViewPriv *priv;
385 TpAccountManager *account_manager;
386 TpConnection *connection = NULL;
387 TpAccount *account = NULL;
388 DndGetContactData *data;
389 GtkTreePath *source_path;
390 const gchar *sel_data;
391 gchar **strv = NULL;
392 const gchar *account_id = NULL;
393 const gchar *contact_id = NULL;
394 gchar *new_group = NULL;
395 gchar *old_group = NULL;
396 gboolean new_group_is_fake, old_group_is_fake = TRUE;
398 priv = GET_PRIV (view);
400 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
401 new_group = empathy_contact_list_store_get_parent_group (model,
402 path, NULL, &new_group_is_fake);
404 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
405 return FALSE;
407 /* Get source group information. */
408 if (priv->drag_row) {
409 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
410 if (source_path) {
411 old_group = empathy_contact_list_store_get_parent_group (
412 model, source_path, NULL, &old_group_is_fake);
413 gtk_tree_path_free (source_path);
417 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
418 return FALSE;
420 if (!tp_strdiff (old_group, new_group)) {
421 g_free (new_group);
422 g_free (old_group);
423 return FALSE;
426 account_manager = tp_account_manager_dup ();
427 strv = g_strsplit (sel_data, ":", 2);
428 if (g_strv_length (strv) == 2) {
429 account_id = strv[0];
430 contact_id = strv[1];
431 account = tp_account_manager_ensure_account (account_manager, account_id);
433 if (account) {
434 connection = tp_account_get_connection (account);
437 if (!connection) {
438 DEBUG ("Failed to get connection for account '%s'", account_id);
439 g_free (new_group);
440 g_free (old_group);
441 g_object_unref (account_manager);
442 return FALSE;
445 data = g_slice_new0 (DndGetContactData);
446 data->new_group = new_group;
447 data->old_group = old_group;
448 data->action = gdk_drag_context_get_selected_action (context);
450 /* FIXME: We should probably wait for the cb before calling
451 * gtk_drag_finish */
452 empathy_tp_contact_factory_get_from_id (connection, contact_id,
453 contact_list_view_drag_got_contact,
454 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
455 G_OBJECT (view));
456 g_strfreev (strv);
457 g_object_unref (account_manager);
459 return TRUE;
462 static gboolean
463 contact_list_view_file_drag_received (GtkWidget *view,
464 GdkDragContext *context,
465 GtkTreeModel *model,
466 GtkTreePath *path,
467 GtkSelectionData *selection)
469 GtkTreeIter iter;
470 const gchar *sel_data;
471 EmpathyContact *contact;
473 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
475 gtk_tree_model_get_iter (model, &iter, path);
476 gtk_tree_model_get (model, &iter,
477 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
478 -1);
479 if (!contact) {
480 return FALSE;
483 empathy_send_file_from_uri_list (contact, sel_data);
485 g_object_unref (contact);
487 return TRUE;
490 static void
491 contact_list_view_drag_data_received (GtkWidget *view,
492 GdkDragContext *context,
493 gint x,
494 gint y,
495 GtkSelectionData *selection,
496 guint info,
497 guint time_)
499 GtkTreeModel *model;
500 gboolean is_row;
501 GtkTreeViewDropPosition position;
502 GtkTreePath *path;
503 gboolean success = TRUE;
505 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
507 /* Get destination group information. */
508 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
511 &path,
512 &position);
513 if (!is_row) {
514 success = FALSE;
516 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
517 success = contact_list_view_contact_drag_received (view,
518 context,
519 model,
520 path,
521 selection);
523 else if (info == DND_DRAG_TYPE_URI_LIST) {
524 success = contact_list_view_file_drag_received (view,
525 context,
526 model,
527 path,
528 selection);
531 gtk_tree_path_free (path);
532 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
535 static gboolean
536 contact_list_view_drag_motion_cb (DragMotionData *data)
538 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
539 data->path,
540 FALSE);
542 data->timeout_id = 0;
544 return FALSE;
547 static gboolean
548 contact_list_view_drag_motion (GtkWidget *widget,
549 GdkDragContext *context,
550 gint x,
551 gint y,
552 guint time_)
554 EmpathyContactListViewPriv *priv;
555 GtkTreeModel *model;
556 GdkAtom target;
557 GtkTreeIter iter;
558 static DragMotionData *dm = NULL;
559 GtkTreePath *path;
560 gboolean is_row;
561 gboolean is_different = FALSE;
562 gboolean cleanup = TRUE;
563 gboolean retval = TRUE;
565 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
566 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
568 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
571 &path,
572 NULL,
573 NULL,
574 NULL);
576 cleanup &= (!dm);
578 if (is_row) {
579 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
580 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
581 } else {
582 cleanup &= FALSE;
585 if (path == NULL) {
586 /* Coordinates don't point to an actual row, so make sure the pointer
587 and highlighting don't indicate that a drag is possible.
589 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
590 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
591 return FALSE;
593 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
594 gtk_tree_model_get_iter (model, &iter, path);
596 if (target == GDK_NONE) {
597 /* If target == GDK_NONE, then we don't have a target that can be
598 dropped on a contact. This means a contact drag. If we're
599 pointing to a group, highlight it. Otherwise, if the contact
600 we're pointing to is in a group, highlight that. Otherwise,
601 set the drag position to before the first row for a drag into
602 the "non-group" at the top.
604 GtkTreeIter group_iter;
605 gboolean is_group;
606 GtkTreePath *group_path;
607 gtk_tree_model_get (model, &iter,
608 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
609 -1);
610 if (is_group) {
611 group_iter = iter;
613 else {
614 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
615 gtk_tree_model_get (model, &group_iter,
616 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
617 -1);
619 if (is_group) {
620 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
621 group_path = gtk_tree_model_get_path (model, &group_iter);
622 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
623 group_path,
624 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
625 gtk_tree_path_free (group_path);
627 else {
628 group_path = gtk_tree_path_new_first ();
629 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
630 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
631 group_path,
632 GTK_TREE_VIEW_DROP_BEFORE);
635 else {
636 /* This is a file drag, and it can only be dropped on contacts,
637 not groups.
639 EmpathyContact *contact;
640 gtk_tree_model_get (model, &iter,
641 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
642 -1);
643 if (contact != NULL &&
644 empathy_contact_is_online (contact) &&
645 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
646 gdk_drag_status (context, GDK_ACTION_COPY, time_);
647 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
648 path,
649 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
650 g_object_unref (contact);
652 else {
653 gdk_drag_status (context, 0, time_);
654 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
655 retval = FALSE;
659 if (!is_different && !cleanup) {
660 return retval;
663 if (dm) {
664 gtk_tree_path_free (dm->path);
665 if (dm->timeout_id) {
666 g_source_remove (dm->timeout_id);
669 g_free (dm);
671 dm = NULL;
674 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
675 dm = g_new0 (DragMotionData, 1);
677 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
678 dm->path = gtk_tree_path_copy (path);
680 dm->timeout_id = g_timeout_add_seconds (1,
681 (GSourceFunc) contact_list_view_drag_motion_cb,
682 dm);
685 return retval;
688 static void
689 contact_list_view_drag_begin (GtkWidget *widget,
690 GdkDragContext *context)
692 EmpathyContactListViewPriv *priv;
693 GtkTreeSelection *selection;
694 GtkTreeModel *model;
695 GtkTreePath *path;
696 GtkTreeIter iter;
698 priv = GET_PRIV (widget);
700 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
701 context);
703 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
704 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
705 return;
708 path = gtk_tree_model_get_path (model, &iter);
709 priv->drag_row = gtk_tree_row_reference_new (model, path);
710 gtk_tree_path_free (path);
713 static void
714 contact_list_view_drag_data_get (GtkWidget *widget,
715 GdkDragContext *context,
716 GtkSelectionData *selection,
717 guint info,
718 guint time_)
720 EmpathyContactListViewPriv *priv;
721 GtkTreePath *src_path;
722 GtkTreeIter iter;
723 GtkTreeModel *model;
724 EmpathyContact *contact;
725 TpAccount *account;
726 const gchar *contact_id;
727 const gchar *account_id;
728 gchar *str;
730 priv = GET_PRIV (widget);
732 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
733 if (!priv->drag_row) {
734 return;
737 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
738 if (!src_path) {
739 return;
742 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
743 gtk_tree_path_free (src_path);
744 return;
747 gtk_tree_path_free (src_path);
749 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
750 if (!contact) {
751 return;
754 account = empathy_contact_get_account (contact);
755 account_id = tp_proxy_get_object_path (account);
756 contact_id = empathy_contact_get_id (contact);
757 g_object_unref (contact);
758 str = g_strconcat (account_id, ":", contact_id, NULL);
760 if (info == DND_DRAG_TYPE_CONTACT_ID) {
761 gtk_selection_data_set (selection,
762 gdk_atom_intern ("text/contact-id", FALSE), 8,
763 (guchar *) str, strlen (str) + 1);
766 g_free (str);
769 static void
770 contact_list_view_drag_end (GtkWidget *widget,
771 GdkDragContext *context)
773 EmpathyContactListViewPriv *priv;
775 priv = GET_PRIV (widget);
777 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
778 context);
780 if (priv->drag_row) {
781 gtk_tree_row_reference_free (priv->drag_row);
782 priv->drag_row = NULL;
786 static gboolean
787 contact_list_view_drag_drop (GtkWidget *widget,
788 GdkDragContext *drag_context,
789 gint x,
790 gint y,
791 guint time_)
793 return FALSE;
796 typedef struct {
797 EmpathyContactListView *view;
798 guint button;
799 guint32 time;
800 } MenuPopupData;
802 static void
803 menu_deactivate_cb (GtkMenuShell *menushell,
804 gpointer user_data)
806 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
807 g_signal_handlers_disconnect_by_func (menushell,
808 menu_deactivate_cb, user_data);
810 gtk_menu_detach (GTK_MENU (menushell));
813 static gboolean
814 contact_list_view_popup_menu_idle_cb (gpointer user_data)
816 MenuPopupData *data = user_data;
817 GtkWidget *menu;
819 menu = empathy_contact_list_view_get_contact_menu (data->view);
820 if (!menu) {
821 menu = empathy_contact_list_view_get_group_menu (data->view);
824 if (menu) {
825 gtk_menu_attach_to_widget (GTK_MENU (menu),
826 GTK_WIDGET (data->view), NULL);
827 gtk_widget_show (menu);
828 gtk_menu_popup (GTK_MENU (menu),
829 NULL, NULL, NULL, NULL,
830 data->button, data->time);
832 /* menu is initially unowned but gtk_menu_attach_to_widget () taked its
833 * floating ref. We can either wait that the treeview releases its ref
834 * when it will be destroyed (when leaving Empathy) or explicitely
835 * detach the menu when it's not displayed any more.
836 * We go for the latter as we don't want to keep useless menus in memory
837 * during the whole lifetime of Empathy. */
838 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
839 NULL);
842 g_slice_free (MenuPopupData, data);
844 return FALSE;
847 static gboolean
848 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
849 GdkEventButton *event,
850 gpointer user_data)
852 if (event->button == 3) {
853 MenuPopupData *data;
855 data = g_slice_new (MenuPopupData);
856 data->view = view;
857 data->button = event->button;
858 data->time = event->time;
859 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
862 return FALSE;
865 static gboolean
866 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
867 GdkEventKey *event,
868 gpointer user_data)
870 if (event->keyval == GDK_KEY_Menu) {
871 MenuPopupData *data;
873 data = g_slice_new (MenuPopupData);
874 data->view = view;
875 data->button = 0;
876 data->time = event->time;
877 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
880 return FALSE;
883 static void
884 contact_list_view_row_activated (GtkTreeView *view,
885 GtkTreePath *path,
886 GtkTreeViewColumn *column)
888 EmpathyContactListViewPriv *priv = GET_PRIV (view);
889 EmpathyContact *contact;
890 GtkTreeModel *model;
891 GtkTreeIter iter;
893 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
894 return;
897 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
898 gtk_tree_model_get_iter (model, &iter, path);
899 gtk_tree_model_get (model, &iter,
900 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
901 -1);
903 if (contact) {
904 DEBUG ("Starting a chat");
905 empathy_chat_with_contact (contact,
906 empathy_get_current_action_time ());
907 g_object_unref (contact);
911 static void
912 contact_list_view_call_activated_cb (
913 EmpathyCellRendererActivatable *cell,
914 const gchar *path_string,
915 EmpathyContactListView *view)
917 GtkWidget *menu;
918 GtkTreeModel *model;
919 GtkTreeIter iter;
920 EmpathyContact *contact;
921 GdkEventButton *event;
922 GtkMenuShell *shell;
923 GtkWidget *item;
925 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
926 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
927 return;
929 gtk_tree_model_get (model, &iter,
930 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
931 -1);
932 if (contact == NULL)
933 return;
935 event = (GdkEventButton *) gtk_get_current_event ();
937 menu = empathy_context_menu_new (GTK_WIDGET (view));
938 shell = GTK_MENU_SHELL (menu);
940 /* audio */
941 item = empathy_contact_audio_call_menu_item_new (contact);
942 gtk_menu_shell_append (shell, item);
943 gtk_widget_show (item);
945 /* video */
946 item = empathy_contact_video_call_menu_item_new (contact);
947 gtk_menu_shell_append (shell, item);
948 gtk_widget_show (item);
950 gtk_widget_show (menu);
951 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
952 event->button, event->time);
954 g_object_unref (contact);
957 static void
958 contact_list_view_cell_set_background (EmpathyContactListView *view,
959 GtkCellRenderer *cell,
960 gboolean is_group,
961 gboolean is_active)
963 if (!is_group && is_active) {
964 GdkRGBA color;
965 GtkStyleContext *style;
967 style = gtk_widget_get_style_context (GTK_WIDGET (view));
969 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
970 &color);
972 /* Here we take the current theme colour and add it to
973 * the colour for white and average the two. This
974 * gives a colour which is inline with the theme but
975 * slightly whiter.
977 empathy_make_color_whiter (&color);
979 g_object_set (cell,
980 "cell-background-rgba", &color,
981 NULL);
982 } else {
983 g_object_set (cell,
984 "cell-background-rgba", NULL,
985 NULL);
989 static void
990 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
991 GtkCellRenderer *cell,
992 GtkTreeModel *model,
993 GtkTreeIter *iter,
994 EmpathyContactListView *view)
996 GdkPixbuf *pixbuf;
997 gboolean is_group;
998 gboolean is_active;
1000 gtk_tree_model_get (model, iter,
1001 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1002 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1003 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
1004 -1);
1006 g_object_set (cell,
1007 "visible", !is_group,
1008 "pixbuf", pixbuf,
1009 NULL);
1011 if (pixbuf != NULL) {
1012 g_object_unref (pixbuf);
1015 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1018 static void
1019 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1020 GtkCellRenderer *cell,
1021 GtkTreeModel *model,
1022 GtkTreeIter *iter,
1023 EmpathyContactListView *view)
1025 GdkPixbuf *pixbuf = NULL;
1026 gboolean is_group;
1027 gchar *name;
1029 gtk_tree_model_get (model, iter,
1030 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1031 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1032 -1);
1034 if (!is_group)
1035 goto out;
1037 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1038 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1039 GTK_ICON_SIZE_MENU);
1041 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1042 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1043 GTK_ICON_SIZE_MENU);
1046 out:
1047 g_object_set (cell,
1048 "visible", pixbuf != NULL,
1049 "pixbuf", pixbuf,
1050 NULL);
1052 if (pixbuf != NULL)
1053 g_object_unref (pixbuf);
1055 g_free (name);
1058 static void
1059 contact_list_view_audio_call_cell_data_func (
1060 GtkTreeViewColumn *tree_column,
1061 GtkCellRenderer *cell,
1062 GtkTreeModel *model,
1063 GtkTreeIter *iter,
1064 EmpathyContactListView *view)
1066 gboolean is_group;
1067 gboolean is_active;
1068 gboolean can_audio, can_video;
1070 gtk_tree_model_get (model, iter,
1071 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1072 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1073 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1074 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1075 -1);
1077 g_object_set (cell,
1078 "visible", !is_group && (can_audio || can_video),
1079 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1080 NULL);
1082 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1085 static void
1086 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1087 GtkCellRenderer *cell,
1088 GtkTreeModel *model,
1089 GtkTreeIter *iter,
1090 EmpathyContactListView *view)
1092 GdkPixbuf *pixbuf;
1093 gboolean show_avatar;
1094 gboolean is_group;
1095 gboolean is_active;
1097 gtk_tree_model_get (model, iter,
1098 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1099 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1100 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1101 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1102 -1);
1104 g_object_set (cell,
1105 "visible", !is_group && show_avatar,
1106 "pixbuf", pixbuf,
1107 NULL);
1109 if (pixbuf) {
1110 g_object_unref (pixbuf);
1113 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1116 static void
1117 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1118 GtkCellRenderer *cell,
1119 GtkTreeModel *model,
1120 GtkTreeIter *iter,
1121 EmpathyContactListView *view)
1123 gboolean is_group;
1124 gboolean is_active;
1126 gtk_tree_model_get (model, iter,
1127 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1128 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1129 -1);
1131 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1134 static void
1135 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1136 GtkCellRenderer *cell,
1137 GtkTreeModel *model,
1138 GtkTreeIter *iter,
1139 EmpathyContactListView *view)
1141 gboolean is_group;
1142 gboolean is_active;
1144 gtk_tree_model_get (model, iter,
1145 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1146 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1147 -1);
1149 if (gtk_tree_model_iter_has_child (model, iter)) {
1150 GtkTreePath *path;
1151 gboolean row_expanded;
1153 path = gtk_tree_model_get_path (model, iter);
1154 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1155 gtk_tree_path_free (path);
1157 g_object_set (cell,
1158 "visible", TRUE,
1159 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1160 NULL);
1161 } else {
1162 g_object_set (cell, "visible", FALSE, NULL);
1165 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1168 static void
1169 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1170 GtkTreeIter *iter,
1171 GtkTreePath *path,
1172 gpointer user_data)
1174 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1175 GtkTreeModel *model;
1176 gchar *name;
1177 gboolean expanded;
1179 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1180 return;
1183 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1185 gtk_tree_model_get (model, iter,
1186 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1187 -1);
1189 expanded = GPOINTER_TO_INT (user_data);
1190 empathy_contact_group_set_expanded (name, expanded);
1192 g_free (name);
1195 static gboolean
1196 contact_list_view_start_search_cb (EmpathyContactListView *view,
1197 gpointer data)
1199 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1201 if (priv->search_widget == NULL)
1202 return FALSE;
1204 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1205 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1206 else
1207 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1209 return TRUE;
1212 static void
1213 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1214 GParamSpec *pspec,
1215 EmpathyContactListView *view)
1217 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1218 GtkTreePath *path;
1219 GtkTreeViewColumn *focus_column;
1220 GtkTreeModel *model;
1221 GtkTreeIter iter;
1222 gboolean set_cursor = FALSE;
1224 gtk_tree_model_filter_refilter (priv->filter);
1226 /* Set cursor on the first contact. If it is already set on a group,
1227 * set it on its first child contact. Note that first child of a group
1228 * is its separator, that's why we actually set to the 2nd
1231 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1232 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1234 if (path == NULL) {
1235 path = gtk_tree_path_new_from_string ("0:1");
1236 set_cursor = TRUE;
1237 } else if (gtk_tree_path_get_depth (path) < 2) {
1238 gboolean is_group;
1240 gtk_tree_model_get_iter (model, &iter, path);
1241 gtk_tree_model_get (model, &iter,
1242 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1243 -1);
1245 if (is_group) {
1246 gtk_tree_path_down (path);
1247 gtk_tree_path_next (path);
1248 set_cursor = TRUE;
1252 if (set_cursor) {
1253 /* FIXME: Workaround for GTK bug #621651, we have to make sure
1254 * the path is valid. */
1255 if (gtk_tree_model_get_iter (model, &iter, path)) {
1256 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
1257 focus_column, FALSE);
1261 gtk_tree_path_free (path);
1264 static void
1265 contact_list_view_search_activate_cb (GtkWidget *search,
1266 EmpathyContactListView *view)
1268 GtkTreePath *path;
1269 GtkTreeViewColumn *focus_column;
1271 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1272 if (path != NULL) {
1273 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
1274 focus_column);
1275 gtk_tree_path_free (path);
1277 gtk_widget_hide (search);
1281 static gboolean
1282 contact_list_view_search_key_navigation_cb (GtkWidget *search,
1283 GdkEvent *event,
1284 EmpathyContactListView *view)
1286 GdkEvent *new_event;
1287 gboolean ret = FALSE;
1289 new_event = gdk_event_copy (event);
1290 gtk_widget_grab_focus (GTK_WIDGET (view));
1291 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1292 gtk_widget_grab_focus (search);
1294 gdk_event_free (new_event);
1296 return ret;
1299 static void
1300 contact_list_view_search_hide_cb (EmpathyLiveSearch *search,
1301 EmpathyContactListView *view)
1303 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1304 GtkTreeModel *model;
1305 GtkTreeIter iter;
1306 gboolean valid = FALSE;
1308 /* block expand or collapse handlers, they would write the
1309 * expand or collapsed setting to file otherwise */
1310 g_signal_handlers_block_by_func (view,
1311 contact_list_view_row_expand_or_collapse_cb,
1312 GINT_TO_POINTER (TRUE));
1313 g_signal_handlers_block_by_func (view,
1314 contact_list_view_row_expand_or_collapse_cb,
1315 GINT_TO_POINTER (FALSE));
1317 /* restore which groups are expanded and which are not */
1318 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1319 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1320 valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1321 gboolean is_group;
1322 gchar *name = NULL;
1323 GtkTreePath *path;
1325 gtk_tree_model_get (model, &iter,
1326 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1327 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1328 -1);
1330 if (!is_group) {
1331 g_free (name);
1332 continue;
1335 path = gtk_tree_model_get_path (model, &iter);
1336 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1337 empathy_contact_group_get_expanded (name)) {
1338 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1339 TRUE);
1340 } else {
1341 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1344 gtk_tree_path_free (path);
1345 g_free (name);
1348 /* unblock expand or collapse handlers */
1349 g_signal_handlers_unblock_by_func (view,
1350 contact_list_view_row_expand_or_collapse_cb,
1351 GINT_TO_POINTER (TRUE));
1352 g_signal_handlers_unblock_by_func (view,
1353 contact_list_view_row_expand_or_collapse_cb,
1354 GINT_TO_POINTER (FALSE));
1357 static void
1358 contact_list_view_search_show_cb (EmpathyLiveSearch *search,
1359 EmpathyContactListView *view)
1361 /* block expand or collapse handlers during expand all, they would
1362 * write the expand or collapsed setting to file otherwise */
1363 g_signal_handlers_block_by_func (view,
1364 contact_list_view_row_expand_or_collapse_cb,
1365 GINT_TO_POINTER (TRUE));
1367 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1369 g_signal_handlers_unblock_by_func (view,
1370 contact_list_view_row_expand_or_collapse_cb,
1371 GINT_TO_POINTER (TRUE));
1374 typedef struct {
1375 EmpathyContactListView *view;
1376 GtkTreeRowReference *row_ref;
1377 gboolean expand;
1378 } ExpandData;
1380 static gboolean
1381 contact_list_view_expand_idle_cb (gpointer user_data)
1383 ExpandData *data = user_data;
1384 GtkTreePath *path;
1386 path = gtk_tree_row_reference_get_path (data->row_ref);
1387 if (path == NULL)
1388 goto done;
1390 g_signal_handlers_block_by_func (data->view,
1391 contact_list_view_row_expand_or_collapse_cb,
1392 GINT_TO_POINTER (data->expand));
1394 if (data->expand) {
1395 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path,
1396 TRUE);
1397 } else {
1398 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1400 gtk_tree_path_free (path);
1402 g_signal_handlers_unblock_by_func (data->view,
1403 contact_list_view_row_expand_or_collapse_cb,
1404 GINT_TO_POINTER (data->expand));
1406 done:
1407 g_object_unref (data->view);
1408 gtk_tree_row_reference_free (data->row_ref);
1409 g_slice_free (ExpandData, data);
1411 return FALSE;
1414 static void
1415 contact_list_view_row_has_child_toggled_cb (GtkTreeModel *model,
1416 GtkTreePath *path,
1417 GtkTreeIter *iter,
1418 EmpathyContactListView *view)
1420 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1421 gboolean is_group = FALSE;
1422 gchar *name = NULL;
1423 ExpandData *data;
1425 gtk_tree_model_get (model, iter,
1426 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1427 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1428 -1);
1430 if (!is_group || EMP_STR_EMPTY (name)) {
1431 g_free (name);
1432 return;
1435 data = g_slice_new0 (ExpandData);
1436 data->view = g_object_ref (view);
1437 data->row_ref = gtk_tree_row_reference_new (model, path);
1438 data->expand =
1439 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1440 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1441 empathy_contact_group_get_expanded (name);
1443 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1444 * gtk_tree_model_filter_refilter () */
1445 g_idle_add (contact_list_view_expand_idle_cb, data);
1447 g_free (name);
1450 static void
1451 contact_list_view_verify_group_visibility (EmpathyContactListView *view,
1452 GtkTreePath *path)
1454 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1455 GtkTreeModel *model;
1456 GtkTreePath *parent_path;
1457 GtkTreeIter parent_iter;
1459 if (gtk_tree_path_get_depth (path) < 2)
1460 return;
1462 /* A group row is visible if and only if at least one if its child is
1463 * visible. So when a row is inserted/deleted/changed in the base model,
1464 * that could modify the visibility of its parent in the filter model.
1467 model = GTK_TREE_MODEL (priv->store);
1468 parent_path = gtk_tree_path_copy (path);
1469 gtk_tree_path_up (parent_path);
1470 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path)) {
1471 /* This tells the filter to verify the visibility of that row,
1472 * and show/hide it if necessary */
1473 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1474 parent_path, &parent_iter);
1476 gtk_tree_path_free (parent_path);
1479 static void
1480 contact_list_view_store_row_changed_cb (GtkTreeModel *model,
1481 GtkTreePath *path,
1482 GtkTreeIter *iter,
1483 EmpathyContactListView *view)
1485 contact_list_view_verify_group_visibility (view, path);
1488 static void
1489 contact_list_view_store_row_deleted_cb (GtkTreeModel *model,
1490 GtkTreePath *path,
1491 EmpathyContactListView *view)
1493 contact_list_view_verify_group_visibility (view, path);
1496 static void
1497 contact_list_view_constructed (GObject *object)
1499 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1500 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1501 GtkCellRenderer *cell;
1502 GtkTreeViewColumn *col;
1504 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1505 GTK_TREE_MODEL (priv->store), NULL));
1506 gtk_tree_model_filter_set_visible_func (priv->filter,
1507 contact_list_view_filter_visible_func,
1508 view, NULL);
1510 g_signal_connect (priv->filter, "row-has-child-toggled",
1511 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1512 view);
1514 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1515 GTK_TREE_MODEL (priv->filter));
1517 tp_g_signal_connect_object (priv->store, "row-changed",
1518 G_CALLBACK (contact_list_view_store_row_changed_cb),
1519 view, 0);
1520 tp_g_signal_connect_object (priv->store, "row-inserted",
1521 G_CALLBACK (contact_list_view_store_row_changed_cb),
1522 view, 0);
1523 tp_g_signal_connect_object (priv->store, "row-deleted",
1524 G_CALLBACK (contact_list_view_store_row_deleted_cb),
1525 view, 0);
1527 /* Setup view */
1528 /* Setting reorderable is a hack that gets us row previews as drag icons
1529 for free. We override all the drag handlers. It's tricky to get the
1530 position of the drag icon right in drag_begin. GtkTreeView has special
1531 voodoo for it, so we let it do the voodoo that he do.
1533 g_object_set (view,
1534 "headers-visible", FALSE,
1535 "reorderable", TRUE,
1536 "show-expanders", FALSE,
1537 NULL);
1539 col = gtk_tree_view_column_new ();
1541 /* State */
1542 cell = gtk_cell_renderer_pixbuf_new ();
1543 gtk_tree_view_column_pack_start (col, cell, FALSE);
1544 gtk_tree_view_column_set_cell_data_func (
1545 col, cell,
1546 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1547 view, NULL);
1549 g_object_set (cell,
1550 "xpad", 5,
1551 "ypad", 1,
1552 "visible", FALSE,
1553 NULL);
1555 /* Group icon */
1556 cell = gtk_cell_renderer_pixbuf_new ();
1557 gtk_tree_view_column_pack_start (col, cell, FALSE);
1558 gtk_tree_view_column_set_cell_data_func (
1559 col, cell,
1560 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1561 view, NULL);
1563 g_object_set (cell,
1564 "xpad", 0,
1565 "ypad", 0,
1566 "visible", FALSE,
1567 "width", 16,
1568 "height", 16,
1569 NULL);
1571 /* Name */
1572 cell = empathy_cell_renderer_text_new ();
1573 gtk_tree_view_column_pack_start (col, cell, TRUE);
1574 gtk_tree_view_column_set_cell_data_func (
1575 col, cell,
1576 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1577 view, NULL);
1579 gtk_tree_view_column_add_attribute (col, cell,
1580 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1581 gtk_tree_view_column_add_attribute (col, cell,
1582 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1583 gtk_tree_view_column_add_attribute (col, cell,
1584 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1585 gtk_tree_view_column_add_attribute (col, cell,
1586 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1587 gtk_tree_view_column_add_attribute (col, cell,
1588 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1589 gtk_tree_view_column_add_attribute (col, cell,
1590 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1592 /* Audio Call Icon */
1593 cell = empathy_cell_renderer_activatable_new ();
1594 gtk_tree_view_column_pack_start (col, cell, FALSE);
1595 gtk_tree_view_column_set_cell_data_func (
1596 col, cell,
1597 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1598 view, NULL);
1600 g_object_set (cell,
1601 "visible", FALSE,
1602 NULL);
1604 g_signal_connect (cell, "path-activated",
1605 G_CALLBACK (contact_list_view_call_activated_cb),
1606 view);
1608 /* Avatar */
1609 cell = gtk_cell_renderer_pixbuf_new ();
1610 gtk_tree_view_column_pack_start (col, cell, FALSE);
1611 gtk_tree_view_column_set_cell_data_func (
1612 col, cell,
1613 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1614 view, NULL);
1616 g_object_set (cell,
1617 "xpad", 0,
1618 "ypad", 0,
1619 "visible", FALSE,
1620 "width", 32,
1621 "height", 32,
1622 NULL);
1624 /* Expander */
1625 cell = empathy_cell_renderer_expander_new ();
1626 gtk_tree_view_column_pack_end (col, cell, FALSE);
1627 gtk_tree_view_column_set_cell_data_func (
1628 col, cell,
1629 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1630 view, NULL);
1632 /* Actually add the column now we have added all cell renderers */
1633 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1636 static void
1637 contact_list_view_set_list_features (EmpathyContactListView *view,
1638 EmpathyContactListFeatureFlags features)
1640 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1641 gboolean has_tooltip;
1643 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1645 priv->list_features = features;
1647 /* Update DnD source/dest */
1648 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1649 gtk_drag_source_set (GTK_WIDGET (view),
1650 GDK_BUTTON1_MASK,
1651 drag_types_source,
1652 G_N_ELEMENTS (drag_types_source),
1653 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1654 } else {
1655 gtk_drag_source_unset (GTK_WIDGET (view));
1659 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1660 gtk_drag_dest_set (GTK_WIDGET (view),
1661 GTK_DEST_DEFAULT_ALL,
1662 drag_types_dest,
1663 G_N_ELEMENTS (drag_types_dest),
1664 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1665 } else {
1666 /* FIXME: URI could still be droped depending on FT feature */
1667 gtk_drag_dest_unset (GTK_WIDGET (view));
1670 /* Update has-tooltip */
1671 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1672 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1675 static void
1676 contact_list_view_dispose (GObject *object)
1678 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1679 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1681 if (priv->store) {
1682 g_object_unref (priv->store);
1683 priv->store = NULL;
1685 if (priv->filter) {
1686 g_object_unref (priv->filter);
1687 priv->filter = NULL;
1689 if (priv->tooltip_widget) {
1690 gtk_widget_destroy (priv->tooltip_widget);
1691 priv->tooltip_widget = NULL;
1693 if (priv->file_targets) {
1694 gtk_target_list_unref (priv->file_targets);
1695 priv->file_targets = NULL;
1698 empathy_contact_list_view_set_live_search (view, NULL);
1700 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1703 static void
1704 contact_list_view_get_property (GObject *object,
1705 guint param_id,
1706 GValue *value,
1707 GParamSpec *pspec)
1709 EmpathyContactListViewPriv *priv;
1711 priv = GET_PRIV (object);
1713 switch (param_id) {
1714 case PROP_STORE:
1715 g_value_set_object (value, priv->store);
1716 break;
1717 case PROP_LIST_FEATURES:
1718 g_value_set_flags (value, priv->list_features);
1719 break;
1720 case PROP_CONTACT_FEATURES:
1721 g_value_set_flags (value, priv->contact_features);
1722 break;
1723 default:
1724 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1725 break;
1729 static void
1730 contact_list_view_set_property (GObject *object,
1731 guint param_id,
1732 const GValue *value,
1733 GParamSpec *pspec)
1735 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1736 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1738 switch (param_id) {
1739 case PROP_STORE:
1740 priv->store = g_value_dup_object (value);
1741 break;
1742 case PROP_LIST_FEATURES:
1743 contact_list_view_set_list_features (view, g_value_get_flags (value));
1744 break;
1745 case PROP_CONTACT_FEATURES:
1746 priv->contact_features = g_value_get_flags (value);
1747 break;
1748 default:
1749 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1750 break;
1754 static void
1755 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1757 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1758 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1759 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1761 object_class->constructed = contact_list_view_constructed;
1762 object_class->dispose = contact_list_view_dispose;
1763 object_class->get_property = contact_list_view_get_property;
1764 object_class->set_property = contact_list_view_set_property;
1766 widget_class->drag_data_received = contact_list_view_drag_data_received;
1767 widget_class->drag_drop = contact_list_view_drag_drop;
1768 widget_class->drag_begin = contact_list_view_drag_begin;
1769 widget_class->drag_data_get = contact_list_view_drag_data_get;
1770 widget_class->drag_end = contact_list_view_drag_end;
1771 widget_class->drag_motion = contact_list_view_drag_motion;
1773 /* We use the class method to let user of this widget to connect to
1774 * the signal and stop emission of the signal so the default handler
1775 * won't be called. */
1776 tree_view_class->row_activated = contact_list_view_row_activated;
1778 signals[DRAG_CONTACT_RECEIVED] =
1779 g_signal_new ("drag-contact-received",
1780 G_OBJECT_CLASS_TYPE (klass),
1781 G_SIGNAL_RUN_LAST,
1783 NULL, NULL,
1784 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1785 G_TYPE_NONE,
1786 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1788 g_object_class_install_property (object_class,
1789 PROP_STORE,
1790 g_param_spec_object ("store",
1791 "The store of the view",
1792 "The store of the view",
1793 EMPATHY_TYPE_CONTACT_LIST_STORE,
1794 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1795 g_object_class_install_property (object_class,
1796 PROP_LIST_FEATURES,
1797 g_param_spec_flags ("list-features",
1798 "Features of the view",
1799 "Flags for all enabled features",
1800 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1801 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1802 G_PARAM_READWRITE));
1803 g_object_class_install_property (object_class,
1804 PROP_CONTACT_FEATURES,
1805 g_param_spec_flags ("contact-features",
1806 "Features of the contact menu",
1807 "Flags for all enabled features for the menu",
1808 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1809 EMPATHY_CONTACT_FEATURE_NONE,
1810 G_PARAM_READWRITE));
1812 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1815 static void
1816 empathy_contact_list_view_init (EmpathyContactListView *view)
1818 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1819 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1821 view->priv = priv;
1823 /* Get saved group states. */
1824 empathy_contact_groups_get_all ();
1826 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1827 empathy_contact_list_store_row_separator_func,
1828 NULL, NULL);
1830 /* Set up drag target lists. */
1831 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1832 G_N_ELEMENTS (drag_types_dest_file));
1834 /* Connect to tree view signals rather than override. */
1835 g_signal_connect (view, "button-press-event",
1836 G_CALLBACK (contact_list_view_button_press_event_cb),
1837 NULL);
1838 g_signal_connect (view, "key-press-event",
1839 G_CALLBACK (contact_list_view_key_press_event_cb),
1840 NULL);
1841 g_signal_connect (view, "row-expanded",
1842 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1843 GINT_TO_POINTER (TRUE));
1844 g_signal_connect (view, "row-collapsed",
1845 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1846 GINT_TO_POINTER (FALSE));
1847 g_signal_connect (view, "query-tooltip",
1848 G_CALLBACK (contact_list_view_query_tooltip_cb),
1849 NULL);
1852 EmpathyContactListView *
1853 empathy_contact_list_view_new (EmpathyContactListStore *store,
1854 EmpathyContactListFeatureFlags list_features,
1855 EmpathyContactFeatureFlags contact_features)
1857 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1859 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1860 "store", store,
1861 "contact-features", contact_features,
1862 "list-features", list_features,
1863 NULL);
1866 EmpathyContact *
1867 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1869 GtkTreeSelection *selection;
1870 GtkTreeIter iter;
1871 GtkTreeModel *model;
1872 EmpathyContact *contact;
1874 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1876 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1877 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1878 return NULL;
1881 gtk_tree_model_get (model, &iter,
1882 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1883 -1);
1885 return contact;
1888 EmpathyContactListFlags
1889 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1891 GtkTreeSelection *selection;
1892 GtkTreeIter iter;
1893 GtkTreeModel *model;
1894 EmpathyContactListFlags flags;
1896 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1898 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1899 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1900 return 0;
1903 gtk_tree_model_get (model, &iter,
1904 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1905 -1);
1907 return flags;
1910 gchar *
1911 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1912 gboolean *is_fake_group)
1914 GtkTreeSelection *selection;
1915 GtkTreeIter iter;
1916 GtkTreeModel *model;
1917 gboolean is_group;
1918 gchar *name;
1919 gboolean fake;
1921 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1923 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1924 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1925 return NULL;
1928 gtk_tree_model_get (model, &iter,
1929 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1930 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1931 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1932 -1);
1934 if (!is_group) {
1935 g_free (name);
1936 return NULL;
1939 if (is_fake_group != NULL)
1940 *is_fake_group = fake;
1942 return name;
1945 static gboolean
1946 contact_list_view_remove_dialog_show (GtkWindow *parent,
1947 const gchar *message,
1948 const gchar *secondary_text)
1950 GtkWidget *dialog;
1951 gboolean res;
1953 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1954 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1955 "%s", message);
1956 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1957 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1958 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1959 NULL);
1960 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1961 "%s", secondary_text);
1963 gtk_widget_show (dialog);
1965 res = gtk_dialog_run (GTK_DIALOG (dialog));
1966 gtk_widget_destroy (dialog);
1968 return (res == GTK_RESPONSE_YES);
1971 static void
1972 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1973 EmpathyContactListView *view)
1975 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1976 gchar *group;
1978 group = empathy_contact_list_view_get_selected_group (view, NULL);
1979 if (group) {
1980 gchar *text;
1981 GtkWindow *parent;
1983 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1984 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1985 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1986 EmpathyContactList *list;
1988 list = empathy_contact_list_store_get_list_iface (priv->store);
1989 empathy_contact_list_remove_group (list, group);
1992 g_free (text);
1995 g_free (group);
1998 GtkWidget *
1999 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
2001 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2002 gchar *group;
2003 GtkWidget *menu;
2004 GtkWidget *item;
2005 GtkWidget *image;
2006 gboolean is_fake_group;
2008 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2010 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
2011 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
2012 return NULL;
2015 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
2016 if (!group || is_fake_group) {
2017 /* We can't alter fake groups */
2018 return NULL;
2021 menu = gtk_menu_new ();
2023 /* FIXME: Not implemented yet
2024 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
2025 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2026 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2027 gtk_widget_show (item);
2028 g_signal_connect (item, "activate",
2029 G_CALLBACK (contact_list_view_group_rename_activate_cb),
2030 view);
2033 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
2034 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2035 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2036 GTK_ICON_SIZE_MENU);
2037 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2038 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2039 gtk_widget_show (item);
2040 g_signal_connect (item, "activate",
2041 G_CALLBACK (contact_list_view_group_remove_activate_cb),
2042 view);
2045 g_free (group);
2047 return menu;
2050 static void
2051 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
2052 EmpathyContactListView *view)
2054 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2055 EmpathyContact *contact;
2057 contact = empathy_contact_list_view_dup_selected (view);
2059 if (contact) {
2060 gchar *text;
2061 GtkWindow *parent;
2063 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2064 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
2065 empathy_contact_get_alias (contact));
2066 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
2067 EmpathyContactList *list;
2069 list = empathy_contact_list_store_get_list_iface (priv->store);
2070 empathy_contact_list_remove (list, contact, "");
2073 g_free (text);
2074 g_object_unref (contact);
2078 GtkWidget *
2079 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
2081 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2082 EmpathyContact *contact;
2083 GtkWidget *menu;
2084 GtkWidget *item;
2085 GtkWidget *image;
2086 EmpathyContactListFlags flags;
2088 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2090 contact = empathy_contact_list_view_dup_selected (view);
2091 if (!contact) {
2092 return NULL;
2094 flags = empathy_contact_list_view_get_flags (view);
2096 menu = empathy_contact_menu_new (contact, priv->contact_features);
2098 /* Remove contact */
2099 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
2100 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
2101 /* create the menu if required, or just add a separator */
2102 if (!menu) {
2103 menu = gtk_menu_new ();
2104 } else {
2105 item = gtk_separator_menu_item_new ();
2106 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2107 gtk_widget_show (item);
2110 /* Remove */
2111 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2112 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2113 GTK_ICON_SIZE_MENU);
2114 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2115 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2116 gtk_widget_show (item);
2117 g_signal_connect (item, "activate",
2118 G_CALLBACK (contact_list_view_remove_activate_cb),
2119 view);
2122 g_object_unref (contact);
2124 return menu;
2127 void
2128 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2129 EmpathyLiveSearch *search)
2131 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2133 /* remove old handlers if old search was not null */
2134 if (priv->search_widget != NULL) {
2135 g_signal_handlers_disconnect_by_func (view,
2136 contact_list_view_start_search_cb,
2137 NULL);
2139 g_signal_handlers_disconnect_by_func (priv->search_widget,
2140 contact_list_view_search_text_notify_cb,
2141 view);
2142 g_signal_handlers_disconnect_by_func (priv->search_widget,
2143 contact_list_view_search_activate_cb,
2144 view);
2145 g_signal_handlers_disconnect_by_func (priv->search_widget,
2146 contact_list_view_search_key_navigation_cb,
2147 view);
2148 g_signal_handlers_disconnect_by_func (priv->search_widget,
2149 contact_list_view_search_hide_cb,
2150 view);
2151 g_signal_handlers_disconnect_by_func (priv->search_widget,
2152 contact_list_view_search_show_cb,
2153 view);
2154 g_object_unref (priv->search_widget);
2155 priv->search_widget = NULL;
2158 /* connect handlers if new search is not null */
2159 if (search != NULL) {
2160 priv->search_widget = g_object_ref (search);
2162 g_signal_connect (view, "start-interactive-search",
2163 G_CALLBACK (contact_list_view_start_search_cb),
2164 NULL);
2166 g_signal_connect (priv->search_widget, "notify::text",
2167 G_CALLBACK (contact_list_view_search_text_notify_cb),
2168 view);
2169 g_signal_connect (priv->search_widget, "activate",
2170 G_CALLBACK (contact_list_view_search_activate_cb),
2171 view);
2172 g_signal_connect (priv->search_widget, "key-navigation",
2173 G_CALLBACK (contact_list_view_search_key_navigation_cb),
2174 view);
2175 g_signal_connect (priv->search_widget, "hide",
2176 G_CALLBACK (contact_list_view_search_hide_cb),
2177 view);
2178 g_signal_connect (priv->search_widget, "show",
2179 G_CALLBACK (contact_list_view_search_show_cb),
2180 view);