Merge branch 'trivia'
[empathy-mirror.git] / libempathy-gtk / empathy-contact-list-view.c
blobce586420e0c1c917dc1f03614bc557bf480e396a
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-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
62 typedef struct {
63 EmpathyContactListStore *store;
64 GtkTreeRowReference *drag_row;
65 EmpathyContactListFeatureFlags list_features;
66 EmpathyContactFeatureFlags contact_features;
67 GtkWidget *tooltip_widget;
68 GtkTargetList *file_targets;
70 GtkTreeModelFilter *filter;
71 GtkWidget *search_widget;
72 } EmpathyContactListViewPriv;
74 typedef struct {
75 EmpathyContactListView *view;
76 GtkTreePath *path;
77 guint timeout_id;
78 } DragMotionData;
80 typedef struct {
81 EmpathyContactListView *view;
82 EmpathyContact *contact;
83 gboolean remove;
84 } ShowActiveData;
86 enum {
87 PROP_0,
88 PROP_STORE,
89 PROP_LIST_FEATURES,
90 PROP_CONTACT_FEATURES,
93 enum DndDragType {
94 DND_DRAG_TYPE_CONTACT_ID,
95 DND_DRAG_TYPE_URI_LIST,
96 DND_DRAG_TYPE_STRING,
99 static const GtkTargetEntry drag_types_dest[] = {
100 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
101 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
102 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
103 { "text/plain", 0, DND_DRAG_TYPE_STRING },
104 { "STRING", 0, DND_DRAG_TYPE_STRING },
107 static const GtkTargetEntry drag_types_dest_file[] = {
108 { "text/path-list", 0, DND_DRAG_TYPE_URI_LIST },
109 { "text/uri-list", 0, DND_DRAG_TYPE_URI_LIST },
112 static const GtkTargetEntry drag_types_source[] = {
113 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
116 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
117 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
119 enum {
120 DRAG_CONTACT_RECEIVED,
121 LAST_SIGNAL
124 static guint signals[LAST_SIGNAL];
126 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
128 static void
129 contact_list_view_tooltip_destroy_cb (GtkWidget *widget,
130 EmpathyContactListView *view)
132 EmpathyContactListViewPriv *priv = GET_PRIV (view);
134 if (priv->tooltip_widget) {
135 DEBUG ("Tooltip destroyed");
136 g_object_unref (priv->tooltip_widget);
137 priv->tooltip_widget = NULL;
141 static gboolean
142 contact_list_view_is_visible_contact (EmpathyContactListView *self,
143 EmpathyContact *contact)
145 EmpathyContactListViewPriv *priv = GET_PRIV (self);
146 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
147 const gchar *str;
148 const gchar *p;
149 gchar *dup_str = NULL;
150 gboolean visible;
152 g_assert (live != NULL);
154 /* check alias name */
155 str = empathy_contact_get_alias (contact);
156 if (empathy_live_search_match (live, str))
157 return TRUE;
159 /* check contact id, remove the @server.com part */
160 str = empathy_contact_get_id (contact);
161 p = strstr (str, "@");
162 if (p != NULL)
163 str = dup_str = g_strndup (str, p - str);
165 visible = empathy_live_search_match (live, str);
166 g_free (dup_str);
167 if (visible)
168 return TRUE;
170 /* FIXME: Add more rules here, we could check phone numbers in
171 * contact's vCard for example. */
173 return FALSE;
176 static gboolean
177 contact_list_view_filter_visible_func (GtkTreeModel *model,
178 GtkTreeIter *iter,
179 gpointer user_data)
181 EmpathyContactListView *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
182 EmpathyContactListViewPriv *priv = GET_PRIV (self);
183 EmpathyContact *contact = NULL;
184 gboolean is_group, is_separator, valid;
185 GtkTreeIter child_iter;
186 gboolean visible;
188 if (priv->search_widget == NULL ||
189 !gtk_widget_get_visible (priv->search_widget))
190 return TRUE;
192 gtk_tree_model_get (model, iter,
193 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
194 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
195 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
196 -1);
198 if (contact != NULL) {
199 visible = contact_list_view_is_visible_contact (self, contact);
200 g_object_unref (contact);
201 return visible;
204 if (is_separator) {
205 return TRUE;
208 /* Not a contact, not a separator, must be a group */
209 g_return_val_if_fail (is_group, FALSE);
211 /* only show groups which are not empty */
212 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
213 valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
214 gtk_tree_model_get (model, &child_iter,
215 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
216 -1);
218 if (contact == NULL)
219 continue;
221 visible = contact_list_view_is_visible_contact (self, contact);
222 g_object_unref (contact);
224 /* show group if it has at least one visible contact in it */
225 if (visible)
226 return TRUE;
229 return FALSE;
232 static gboolean
233 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
234 gint x,
235 gint y,
236 gboolean keyboard_mode,
237 GtkTooltip *tooltip,
238 gpointer user_data)
240 EmpathyContactListViewPriv *priv = GET_PRIV (view);
241 EmpathyContact *contact;
242 GtkTreeModel *model;
243 GtkTreeIter iter;
244 GtkTreePath *path;
245 static gint running = 0;
246 gboolean ret = FALSE;
248 /* Avoid an infinite loop. See GNOME bug #574377 */
249 if (running > 0) {
250 return FALSE;
252 running++;
254 /* Don't show the tooltip if there's already a popup menu */
255 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
256 goto OUT;
259 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
260 keyboard_mode,
261 &model, &path, &iter)) {
262 goto OUT;
265 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
266 gtk_tree_path_free (path);
268 gtk_tree_model_get (model, &iter,
269 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
270 -1);
271 if (!contact) {
272 goto OUT;
275 if (!priv->tooltip_widget) {
276 priv->tooltip_widget = empathy_contact_widget_new (contact,
277 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
278 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
279 gtk_container_set_border_width (
280 GTK_CONTAINER (priv->tooltip_widget), 8);
281 g_object_ref (priv->tooltip_widget);
282 g_signal_connect (priv->tooltip_widget, "destroy",
283 G_CALLBACK (contact_list_view_tooltip_destroy_cb),
284 view);
285 gtk_widget_show (priv->tooltip_widget);
286 } else {
287 empathy_contact_widget_set_contact (priv->tooltip_widget,
288 contact);
291 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
292 ret = TRUE;
294 g_object_unref (contact);
295 OUT:
296 running--;
298 return ret;
301 typedef struct {
302 gchar *new_group;
303 gchar *old_group;
304 GdkDragAction action;
305 } DndGetContactData;
307 static void
308 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
310 g_free (data->new_group);
311 g_free (data->old_group);
312 g_slice_free (DndGetContactData, data);
315 static void
316 contact_list_view_drag_got_contact (TpConnection *connection,
317 EmpathyContact *contact,
318 const GError *error,
319 gpointer user_data,
320 GObject *view)
322 EmpathyContactListViewPriv *priv = GET_PRIV (view);
323 DndGetContactData *data = user_data;
324 EmpathyContactList *list;
326 if (error != NULL) {
327 DEBUG ("Error: %s", error->message);
328 return;
331 DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
332 empathy_contact_get_id (contact),
333 empathy_contact_get_handle (contact),
334 data->old_group, data->new_group);
336 list = empathy_contact_list_store_get_list_iface (priv->store);
338 if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
339 /* Mark contact as favourite */
340 empathy_contact_list_add_to_favourites (list, contact);
341 return;
344 if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
345 /* Remove contact as favourite */
346 empathy_contact_list_remove_from_favourites (list, contact);
347 /* Don't try to remove it */
348 g_free (data->old_group);
349 data->old_group = NULL;
352 if (data->new_group) {
353 empathy_contact_list_add_to_group (list, contact, data->new_group);
355 if (data->old_group && data->action == GDK_ACTION_MOVE) {
356 empathy_contact_list_remove_from_group (list, contact, data->old_group);
360 static gboolean
361 group_can_be_modified (const gchar *name,
362 gboolean is_fake_group,
363 gboolean adding)
365 /* Real groups can always be modified */
366 if (!is_fake_group)
367 return TRUE;
369 /* The favorite fake group can be modified so users can
370 * add/remove favorites using DnD */
371 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
372 return TRUE;
374 /* We can remove contacts from the 'ungrouped' fake group */
375 if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
376 return TRUE;
378 return FALSE;
381 static gboolean
382 contact_list_view_contact_drag_received (GtkWidget *view,
383 GdkDragContext *context,
384 GtkTreeModel *model,
385 GtkTreePath *path,
386 GtkSelectionData *selection)
388 EmpathyContactListViewPriv *priv;
389 TpAccountManager *account_manager;
390 TpConnection *connection;
391 TpAccount *account;
392 DndGetContactData *data;
393 GtkTreePath *source_path;
394 const gchar *sel_data;
395 gchar **strv = NULL;
396 const gchar *account_id = NULL;
397 const gchar *contact_id = NULL;
398 gchar *new_group = NULL;
399 gchar *old_group = NULL;
400 gboolean success = TRUE;
401 gboolean new_group_is_fake, old_group_is_fake = TRUE;
403 priv = GET_PRIV (view);
405 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
406 new_group = empathy_contact_list_store_get_parent_group (model,
407 path, NULL, &new_group_is_fake);
409 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
410 return FALSE;
412 /* Get source group information. */
413 if (priv->drag_row) {
414 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
415 if (source_path) {
416 old_group = empathy_contact_list_store_get_parent_group (
417 model, source_path, NULL, &old_group_is_fake);
418 gtk_tree_path_free (source_path);
422 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
423 return FALSE;
425 if (!tp_strdiff (old_group, new_group)) {
426 g_free (new_group);
427 g_free (old_group);
428 return FALSE;
431 account_manager = tp_account_manager_dup ();
432 strv = g_strsplit (sel_data, ":", 2);
433 if (g_strv_length (strv) == 2) {
434 account_id = strv[0];
435 contact_id = strv[1];
436 account = tp_account_manager_ensure_account (account_manager, account_id);
438 if (account) {
439 connection = tp_account_get_connection (account);
442 if (!connection) {
443 DEBUG ("Failed to get connection for account '%s'", account_id);
444 success = FALSE;
445 g_free (new_group);
446 g_free (old_group);
447 g_object_unref (account_manager);
448 return FALSE;
451 data = g_slice_new0 (DndGetContactData);
452 data->new_group = new_group;
453 data->old_group = old_group;
454 data->action = gdk_drag_context_get_selected_action (context);
456 /* FIXME: We should probably wait for the cb before calling
457 * gtk_drag_finish */
458 empathy_tp_contact_factory_get_from_id (connection, contact_id,
459 contact_list_view_drag_got_contact,
460 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
461 G_OBJECT (view));
462 g_strfreev (strv);
463 g_object_unref (account_manager);
465 return TRUE;
468 static gboolean
469 contact_list_view_file_drag_received (GtkWidget *view,
470 GdkDragContext *context,
471 GtkTreeModel *model,
472 GtkTreePath *path,
473 GtkSelectionData *selection)
475 GtkTreeIter iter;
476 const gchar *sel_data;
477 EmpathyContact *contact;
479 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
481 gtk_tree_model_get_iter (model, &iter, path);
482 gtk_tree_model_get (model, &iter,
483 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
484 -1);
485 if (!contact) {
486 return FALSE;
489 empathy_send_file_from_uri_list (contact, sel_data);
491 g_object_unref (contact);
493 return TRUE;
496 static void
497 contact_list_view_drag_data_received (GtkWidget *view,
498 GdkDragContext *context,
499 gint x,
500 gint y,
501 GtkSelectionData *selection,
502 guint info,
503 guint time_)
505 GtkTreeModel *model;
506 gboolean is_row;
507 GtkTreeViewDropPosition position;
508 GtkTreePath *path;
509 gboolean success = TRUE;
511 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
513 /* Get destination group information. */
514 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
517 &path,
518 &position);
519 if (!is_row) {
520 success = FALSE;
522 else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
523 success = contact_list_view_contact_drag_received (view,
524 context,
525 model,
526 path,
527 selection);
529 else if (info == DND_DRAG_TYPE_URI_LIST) {
530 success = contact_list_view_file_drag_received (view,
531 context,
532 model,
533 path,
534 selection);
537 gtk_tree_path_free (path);
538 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
541 static gboolean
542 contact_list_view_drag_motion_cb (DragMotionData *data)
544 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
545 data->path,
546 FALSE);
548 data->timeout_id = 0;
550 return FALSE;
553 static gboolean
554 contact_list_view_drag_motion (GtkWidget *widget,
555 GdkDragContext *context,
556 gint x,
557 gint y,
558 guint time_)
560 EmpathyContactListViewPriv *priv;
561 GtkTreeModel *model;
562 GdkAtom target;
563 GtkTreeIter iter;
564 static DragMotionData *dm = NULL;
565 GtkTreePath *path;
566 gboolean is_row;
567 gboolean is_different = FALSE;
568 gboolean cleanup = TRUE;
569 gboolean retval = TRUE;
571 priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
572 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
574 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
577 &path,
578 NULL,
579 NULL,
580 NULL);
582 cleanup &= (!dm);
584 if (is_row) {
585 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
586 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
587 } else {
588 cleanup &= FALSE;
591 if (path == NULL) {
592 /* Coordinates don't point to an actual row, so make sure the pointer
593 and highlighting don't indicate that a drag is possible.
595 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
596 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
597 return FALSE;
599 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
600 gtk_tree_model_get_iter (model, &iter, path);
602 if (target == GDK_NONE) {
603 /* If target == GDK_NONE, then we don't have a target that can be
604 dropped on a contact. This means a contact drag. If we're
605 pointing to a group, highlight it. Otherwise, if the contact
606 we're pointing to is in a group, highlight that. Otherwise,
607 set the drag position to before the first row for a drag into
608 the "non-group" at the top.
610 GtkTreeIter group_iter;
611 gboolean is_group;
612 GtkTreePath *group_path;
613 gtk_tree_model_get (model, &iter,
614 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
615 -1);
616 if (is_group) {
617 group_iter = iter;
619 else {
620 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
621 gtk_tree_model_get (model, &group_iter,
622 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
623 -1);
625 if (is_group) {
626 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
627 group_path = gtk_tree_model_get_path (model, &group_iter);
628 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
629 group_path,
630 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
631 gtk_tree_path_free (group_path);
633 else {
634 group_path = gtk_tree_path_new_first ();
635 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
636 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
637 group_path,
638 GTK_TREE_VIEW_DROP_BEFORE);
641 else {
642 /* This is a file drag, and it can only be dropped on contacts,
643 not groups.
645 EmpathyContact *contact;
646 gtk_tree_model_get (model, &iter,
647 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
648 -1);
649 if (contact != NULL &&
650 empathy_contact_is_online (contact) &&
651 (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
652 gdk_drag_status (context, GDK_ACTION_COPY, time_);
653 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
654 path,
655 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
656 g_object_unref (contact);
658 else {
659 gdk_drag_status (context, 0, time_);
660 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
661 retval = FALSE;
665 if (!is_different && !cleanup) {
666 return retval;
669 if (dm) {
670 gtk_tree_path_free (dm->path);
671 if (dm->timeout_id) {
672 g_source_remove (dm->timeout_id);
675 g_free (dm);
677 dm = NULL;
680 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
681 dm = g_new0 (DragMotionData, 1);
683 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
684 dm->path = gtk_tree_path_copy (path);
686 dm->timeout_id = g_timeout_add_seconds (1,
687 (GSourceFunc) contact_list_view_drag_motion_cb,
688 dm);
691 return retval;
694 static void
695 contact_list_view_drag_begin (GtkWidget *widget,
696 GdkDragContext *context)
698 EmpathyContactListViewPriv *priv;
699 GtkTreeSelection *selection;
700 GtkTreeModel *model;
701 GtkTreePath *path;
702 GtkTreeIter iter;
704 priv = GET_PRIV (widget);
706 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
707 context);
709 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
710 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
711 return;
714 path = gtk_tree_model_get_path (model, &iter);
715 priv->drag_row = gtk_tree_row_reference_new (model, path);
716 gtk_tree_path_free (path);
719 static void
720 contact_list_view_drag_data_get (GtkWidget *widget,
721 GdkDragContext *context,
722 GtkSelectionData *selection,
723 guint info,
724 guint time_)
726 EmpathyContactListViewPriv *priv;
727 GtkTreePath *src_path;
728 GtkTreeIter iter;
729 GtkTreeModel *model;
730 EmpathyContact *contact;
731 TpAccount *account;
732 const gchar *contact_id;
733 const gchar *account_id;
734 gchar *str;
736 priv = GET_PRIV (widget);
738 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
739 if (!priv->drag_row) {
740 return;
743 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
744 if (!src_path) {
745 return;
748 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
749 gtk_tree_path_free (src_path);
750 return;
753 gtk_tree_path_free (src_path);
755 contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
756 if (!contact) {
757 return;
760 account = empathy_contact_get_account (contact);
761 account_id = tp_proxy_get_object_path (account);
762 contact_id = empathy_contact_get_id (contact);
763 g_object_unref (contact);
764 str = g_strconcat (account_id, ":", contact_id, NULL);
766 if (info == DND_DRAG_TYPE_CONTACT_ID) {
767 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
768 (guchar *) str, strlen (str) + 1);
771 g_free (str);
774 static void
775 contact_list_view_drag_end (GtkWidget *widget,
776 GdkDragContext *context)
778 EmpathyContactListViewPriv *priv;
780 priv = GET_PRIV (widget);
782 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
783 context);
785 if (priv->drag_row) {
786 gtk_tree_row_reference_free (priv->drag_row);
787 priv->drag_row = NULL;
791 static gboolean
792 contact_list_view_drag_drop (GtkWidget *widget,
793 GdkDragContext *drag_context,
794 gint x,
795 gint y,
796 guint time_)
798 return FALSE;
801 typedef struct {
802 EmpathyContactListView *view;
803 guint button;
804 guint32 time;
805 } MenuPopupData;
807 static gboolean
808 contact_list_view_popup_menu_idle_cb (gpointer user_data)
810 MenuPopupData *data = user_data;
811 GtkWidget *menu;
813 menu = empathy_contact_list_view_get_contact_menu (data->view);
814 if (!menu) {
815 menu = empathy_contact_list_view_get_group_menu (data->view);
818 if (menu) {
819 g_signal_connect (menu, "deactivate",
820 G_CALLBACK (gtk_menu_detach), NULL);
821 gtk_menu_attach_to_widget (GTK_MENU (menu),
822 GTK_WIDGET (data->view), NULL);
823 gtk_widget_show (menu);
824 gtk_menu_popup (GTK_MENU (menu),
825 NULL, NULL, NULL, NULL,
826 data->button, data->time);
827 g_object_ref_sink (menu);
828 g_object_unref (menu);
831 g_slice_free (MenuPopupData, data);
833 return FALSE;
836 static gboolean
837 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
838 GdkEventButton *event,
839 gpointer user_data)
841 if (event->button == 3) {
842 MenuPopupData *data;
844 data = g_slice_new (MenuPopupData);
845 data->view = view;
846 data->button = event->button;
847 data->time = event->time;
848 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
851 return FALSE;
854 static gboolean
855 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
856 GdkEventKey *event,
857 gpointer user_data)
859 if (event->keyval == GDK_Menu) {
860 MenuPopupData *data;
862 data = g_slice_new (MenuPopupData);
863 data->view = view;
864 data->button = 0;
865 data->time = event->time;
866 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
869 return FALSE;
872 static void
873 contact_list_view_row_activated (GtkTreeView *view,
874 GtkTreePath *path,
875 GtkTreeViewColumn *column)
877 EmpathyContactListViewPriv *priv = GET_PRIV (view);
878 EmpathyContact *contact;
879 GtkTreeModel *model;
880 GtkTreeIter iter;
882 if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
883 return;
886 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
887 gtk_tree_model_get_iter (model, &iter, path);
888 gtk_tree_model_get (model, &iter,
889 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
890 -1);
892 if (contact) {
893 DEBUG ("Starting a chat");
894 empathy_dispatcher_chat_with_contact (contact,
895 gtk_get_current_event_time ());
896 g_object_unref (contact);
900 static void
901 contact_list_view_call_activated_cb (
902 EmpathyCellRendererActivatable *cell,
903 const gchar *path_string,
904 EmpathyContactListView *view)
906 GtkWidget *menu;
907 GtkTreeModel *model;
908 GtkTreeIter iter;
909 EmpathyContact *contact;
910 GdkEventButton *event;
911 GtkMenuShell *shell;
912 GtkWidget *item;
914 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
915 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
916 return;
918 gtk_tree_model_get (model, &iter,
919 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
920 -1);
921 if (contact == NULL)
922 return;
924 event = (GdkEventButton *) gtk_get_current_event ();
926 menu = gtk_menu_new ();
927 shell = GTK_MENU_SHELL (menu);
929 /* audio */
930 item = empathy_contact_audio_call_menu_item_new (contact);
931 gtk_menu_shell_append (shell, item);
932 gtk_widget_show (item);
934 /* video */
935 item = empathy_contact_video_call_menu_item_new (contact);
936 gtk_menu_shell_append (shell, item);
937 gtk_widget_show (item);
939 g_signal_connect (menu, "deactivate",
940 G_CALLBACK (gtk_menu_detach), NULL);
941 gtk_menu_attach_to_widget (GTK_MENU (menu),
942 GTK_WIDGET (view), NULL);
943 gtk_widget_show (menu);
944 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
945 event->button, event->time);
946 g_object_ref_sink (menu);
947 g_object_unref (menu);
949 g_object_unref (contact);
952 static void
953 contact_list_view_cell_set_background (EmpathyContactListView *view,
954 GtkCellRenderer *cell,
955 gboolean is_group,
956 gboolean is_active)
958 GdkColor color;
959 GtkStyle *style;
961 style = gtk_widget_get_style (GTK_WIDGET (view));
963 if (!is_group && is_active) {
964 color = style->bg[GTK_STATE_SELECTED];
966 /* Here we take the current theme colour and add it to
967 * the colour for white and average the two. This
968 * gives a colour which is inline with the theme but
969 * slightly whiter.
971 color.red = (color.red + (style->white).red) / 2;
972 color.green = (color.green + (style->white).green) / 2;
973 color.blue = (color.blue + (style->white).blue) / 2;
975 g_object_set (cell,
976 "cell-background-gdk", &color,
977 NULL);
978 } else {
979 g_object_set (cell,
980 "cell-background-gdk", NULL,
981 NULL);
985 static void
986 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
987 GtkCellRenderer *cell,
988 GtkTreeModel *model,
989 GtkTreeIter *iter,
990 EmpathyContactListView *view)
992 GdkPixbuf *pixbuf;
993 gboolean is_group;
994 gboolean is_active;
996 gtk_tree_model_get (model, iter,
997 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
998 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
999 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
1000 -1);
1002 g_object_set (cell,
1003 "visible", !is_group,
1004 "pixbuf", pixbuf,
1005 NULL);
1007 if (pixbuf != NULL) {
1008 g_object_unref (pixbuf);
1011 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1014 static void
1015 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1016 GtkCellRenderer *cell,
1017 GtkTreeModel *model,
1018 GtkTreeIter *iter,
1019 EmpathyContactListView *view)
1021 GdkPixbuf *pixbuf = NULL;
1022 gboolean is_group;
1023 gchar *name;
1025 gtk_tree_model_get (model, iter,
1026 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1027 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1028 -1);
1030 if (!is_group)
1031 goto out;
1033 if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1034 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1035 GTK_ICON_SIZE_MENU);
1037 else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1038 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1039 GTK_ICON_SIZE_MENU);
1042 out:
1043 g_object_set (cell,
1044 "visible", pixbuf != NULL,
1045 "pixbuf", pixbuf,
1046 NULL);
1048 if (pixbuf != NULL)
1049 g_object_unref (pixbuf);
1051 g_free (name);
1054 static void
1055 contact_list_view_audio_call_cell_data_func (
1056 GtkTreeViewColumn *tree_column,
1057 GtkCellRenderer *cell,
1058 GtkTreeModel *model,
1059 GtkTreeIter *iter,
1060 EmpathyContactListView *view)
1062 gboolean is_group;
1063 gboolean is_active;
1064 gboolean can_audio, can_video;
1066 gtk_tree_model_get (model, iter,
1067 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1068 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1069 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1070 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1071 -1);
1073 g_object_set (cell,
1074 "visible", !is_group && (can_audio || can_video),
1075 "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1076 NULL);
1078 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1081 static void
1082 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1083 GtkCellRenderer *cell,
1084 GtkTreeModel *model,
1085 GtkTreeIter *iter,
1086 EmpathyContactListView *view)
1088 GdkPixbuf *pixbuf;
1089 gboolean show_avatar;
1090 gboolean is_group;
1091 gboolean is_active;
1093 gtk_tree_model_get (model, iter,
1094 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1095 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1096 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1097 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1098 -1);
1100 g_object_set (cell,
1101 "visible", !is_group && show_avatar,
1102 "pixbuf", pixbuf,
1103 NULL);
1105 if (pixbuf) {
1106 g_object_unref (pixbuf);
1109 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1112 static void
1113 contact_list_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1114 GtkCellRenderer *cell,
1115 GtkTreeModel *model,
1116 GtkTreeIter *iter,
1117 EmpathyContactListView *view)
1119 gboolean is_group;
1120 gboolean is_active;
1122 gtk_tree_model_get (model, iter,
1123 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1124 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1125 -1);
1127 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1130 static void
1131 contact_list_view_expander_cell_data_func (GtkTreeViewColumn *column,
1132 GtkCellRenderer *cell,
1133 GtkTreeModel *model,
1134 GtkTreeIter *iter,
1135 EmpathyContactListView *view)
1137 gboolean is_group;
1138 gboolean is_active;
1140 gtk_tree_model_get (model, iter,
1141 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1142 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1143 -1);
1145 if (gtk_tree_model_iter_has_child (model, iter)) {
1146 GtkTreePath *path;
1147 gboolean row_expanded;
1149 path = gtk_tree_model_get_path (model, iter);
1150 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1151 gtk_tree_path_free (path);
1153 g_object_set (cell,
1154 "visible", TRUE,
1155 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1156 NULL);
1157 } else {
1158 g_object_set (cell, "visible", FALSE, NULL);
1161 contact_list_view_cell_set_background (view, cell, is_group, is_active);
1164 static void
1165 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1166 GtkTreeIter *iter,
1167 GtkTreePath *path,
1168 gpointer user_data)
1170 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1171 GtkTreeModel *model;
1172 gchar *name;
1173 gboolean expanded;
1175 if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1176 return;
1179 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1181 gtk_tree_model_get (model, iter,
1182 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1183 -1);
1185 expanded = GPOINTER_TO_INT (user_data);
1186 empathy_contact_group_set_expanded (name, expanded);
1188 g_free (name);
1191 static gboolean
1192 contact_list_view_start_search_cb (EmpathyContactListView *view,
1193 gpointer data)
1195 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1197 if (priv->search_widget == NULL)
1198 return FALSE;
1200 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1201 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1202 else
1203 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1205 return TRUE;
1208 static void
1209 contact_list_view_search_text_notify_cb (EmpathyLiveSearch *search,
1210 GParamSpec *pspec,
1211 EmpathyContactListView *view)
1213 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1214 GtkTreePath *path;
1215 GtkTreeViewColumn *focus_column;
1216 GtkTreeModel *model;
1217 GtkTreeIter iter;
1218 gboolean set_cursor = FALSE;
1220 gtk_tree_model_filter_refilter (priv->filter);
1222 /* Set cursor on the first contact. If it is already set on a group,
1223 * set it on its first child contact. Note that first child of a group
1224 * is its separator, that's why we actually set to the 2nd
1227 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1228 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1230 if (path == NULL) {
1231 path = gtk_tree_path_new_from_string ("0:1");
1232 set_cursor = TRUE;
1233 } else if (gtk_tree_path_get_depth (path) < 2) {
1234 gboolean is_group;
1236 gtk_tree_model_get_iter (model, &iter, path);
1237 gtk_tree_model_get (model, &iter,
1238 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1239 -1);
1241 if (is_group) {
1242 gtk_tree_path_down (path);
1243 gtk_tree_path_next (path);
1244 set_cursor = TRUE;
1248 if (set_cursor) {
1249 /* FIXME: Workaround for GTK bug #621651, we have to make sure
1250 * the path is valid. */
1251 if (gtk_tree_model_get_iter (model, &iter, path)) {
1252 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
1253 focus_column, FALSE);
1257 gtk_tree_path_free (path);
1260 static void
1261 contact_list_view_search_activate_cb (GtkWidget *search,
1262 EmpathyContactListView *view)
1264 GtkTreePath *path;
1265 GtkTreeViewColumn *focus_column;
1267 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1268 if (path != NULL) {
1269 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
1270 focus_column);
1271 gtk_tree_path_free (path);
1273 gtk_widget_hide (search);
1277 static gboolean
1278 contact_list_view_search_key_navigation_cb (GtkWidget *search,
1279 GdkEvent *event,
1280 EmpathyContactListView *view)
1282 GdkEventKey *eventkey = ((GdkEventKey *) event);
1283 gboolean ret = FALSE;
1285 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down) {
1286 GdkEvent *new_event;
1288 new_event = gdk_event_copy (event);
1289 gtk_widget_grab_focus (GTK_WIDGET (view));
1290 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1291 gtk_widget_grab_focus (search);
1293 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;
1503 guint i;
1505 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1506 GTK_TREE_MODEL (priv->store), NULL));
1507 gtk_tree_model_filter_set_visible_func (priv->filter,
1508 contact_list_view_filter_visible_func,
1509 view, NULL);
1511 g_signal_connect (priv->filter, "row-has-child-toggled",
1512 G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1513 view);
1515 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1516 GTK_TREE_MODEL (priv->filter));
1518 tp_g_signal_connect_object (priv->store, "row-changed",
1519 G_CALLBACK (contact_list_view_store_row_changed_cb),
1520 view, 0);
1521 tp_g_signal_connect_object (priv->store, "row-inserted",
1522 G_CALLBACK (contact_list_view_store_row_changed_cb),
1523 view, 0);
1524 tp_g_signal_connect_object (priv->store, "row-deleted",
1525 G_CALLBACK (contact_list_view_store_row_deleted_cb),
1526 view, 0);
1528 /* Setup view */
1529 /* Setting reorderable is a hack that gets us row previews as drag icons
1530 for free. We override all the drag handlers. It's tricky to get the
1531 position of the drag icon right in drag_begin. GtkTreeView has special
1532 voodoo for it, so we let it do the voodoo that he do.
1534 g_object_set (view,
1535 "headers-visible", FALSE,
1536 "reorderable", TRUE,
1537 "show-expanders", FALSE,
1538 NULL);
1540 col = gtk_tree_view_column_new ();
1542 /* State */
1543 cell = gtk_cell_renderer_pixbuf_new ();
1544 gtk_tree_view_column_pack_start (col, cell, FALSE);
1545 gtk_tree_view_column_set_cell_data_func (
1546 col, cell,
1547 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1548 view, NULL);
1550 g_object_set (cell,
1551 "xpad", 5,
1552 "ypad", 1,
1553 "visible", FALSE,
1554 NULL);
1556 /* Group icon */
1557 cell = gtk_cell_renderer_pixbuf_new ();
1558 gtk_tree_view_column_pack_start (col, cell, FALSE);
1559 gtk_tree_view_column_set_cell_data_func (
1560 col, cell,
1561 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1562 view, NULL);
1564 g_object_set (cell,
1565 "xpad", 0,
1566 "ypad", 0,
1567 "visible", FALSE,
1568 "width", 16,
1569 "height", 16,
1570 NULL);
1572 /* Name */
1573 cell = empathy_cell_renderer_text_new ();
1574 gtk_tree_view_column_pack_start (col, cell, TRUE);
1575 gtk_tree_view_column_set_cell_data_func (
1576 col, cell,
1577 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1578 view, NULL);
1580 gtk_tree_view_column_add_attribute (col, cell,
1581 "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1582 gtk_tree_view_column_add_attribute (col, cell,
1583 "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1584 gtk_tree_view_column_add_attribute (col, cell,
1585 "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1586 gtk_tree_view_column_add_attribute (col, cell,
1587 "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1588 gtk_tree_view_column_add_attribute (col, cell,
1589 "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1590 gtk_tree_view_column_add_attribute (col, cell,
1591 "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1593 /* Audio Call Icon */
1594 cell = empathy_cell_renderer_activatable_new ();
1595 gtk_tree_view_column_pack_start (col, cell, FALSE);
1596 gtk_tree_view_column_set_cell_data_func (
1597 col, cell,
1598 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1599 view, NULL);
1601 g_object_set (cell,
1602 "visible", FALSE,
1603 NULL);
1605 g_signal_connect (cell, "path-activated",
1606 G_CALLBACK (contact_list_view_call_activated_cb),
1607 view);
1609 /* Avatar */
1610 cell = gtk_cell_renderer_pixbuf_new ();
1611 gtk_tree_view_column_pack_start (col, cell, FALSE);
1612 gtk_tree_view_column_set_cell_data_func (
1613 col, cell,
1614 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1615 view, NULL);
1617 g_object_set (cell,
1618 "xpad", 0,
1619 "ypad", 0,
1620 "visible", FALSE,
1621 "width", 32,
1622 "height", 32,
1623 NULL);
1625 /* Expander */
1626 cell = empathy_cell_renderer_expander_new ();
1627 gtk_tree_view_column_pack_end (col, cell, FALSE);
1628 gtk_tree_view_column_set_cell_data_func (
1629 col, cell,
1630 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1631 view, NULL);
1633 /* Actually add the column now we have added all cell renderers */
1634 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1636 /* Drag & Drop. */
1637 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1638 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1639 FALSE);
1642 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1643 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1644 FALSE);
1648 static void
1649 contact_list_view_set_list_features (EmpathyContactListView *view,
1650 EmpathyContactListFeatureFlags features)
1652 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1653 gboolean has_tooltip;
1655 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1657 priv->list_features = features;
1659 /* Update DnD source/dest */
1660 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1661 gtk_drag_source_set (GTK_WIDGET (view),
1662 GDK_BUTTON1_MASK,
1663 drag_types_source,
1664 G_N_ELEMENTS (drag_types_source),
1665 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1666 } else {
1667 gtk_drag_source_unset (GTK_WIDGET (view));
1671 if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1672 gtk_drag_dest_set (GTK_WIDGET (view),
1673 GTK_DEST_DEFAULT_ALL,
1674 drag_types_dest,
1675 G_N_ELEMENTS (drag_types_dest),
1676 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1677 } else {
1678 /* FIXME: URI could still be droped depending on FT feature */
1679 gtk_drag_dest_unset (GTK_WIDGET (view));
1682 /* Update has-tooltip */
1683 has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1684 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1687 static void
1688 contact_list_view_dispose (GObject *object)
1690 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1691 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1693 if (priv->store) {
1694 g_object_unref (priv->store);
1695 priv->store = NULL;
1697 if (priv->filter) {
1698 g_object_unref (priv->filter);
1699 priv->filter = NULL;
1701 if (priv->tooltip_widget) {
1702 gtk_widget_destroy (priv->tooltip_widget);
1703 priv->tooltip_widget = NULL;
1705 if (priv->file_targets) {
1706 gtk_target_list_unref (priv->file_targets);
1707 priv->file_targets = NULL;
1710 empathy_contact_list_view_set_live_search (view, NULL);
1712 G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1715 static void
1716 contact_list_view_get_property (GObject *object,
1717 guint param_id,
1718 GValue *value,
1719 GParamSpec *pspec)
1721 EmpathyContactListViewPriv *priv;
1723 priv = GET_PRIV (object);
1725 switch (param_id) {
1726 case PROP_STORE:
1727 g_value_set_object (value, priv->store);
1728 break;
1729 case PROP_LIST_FEATURES:
1730 g_value_set_flags (value, priv->list_features);
1731 break;
1732 case PROP_CONTACT_FEATURES:
1733 g_value_set_flags (value, priv->contact_features);
1734 break;
1735 default:
1736 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1737 break;
1741 static void
1742 contact_list_view_set_property (GObject *object,
1743 guint param_id,
1744 const GValue *value,
1745 GParamSpec *pspec)
1747 EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1748 EmpathyContactListViewPriv *priv = GET_PRIV (object);
1750 switch (param_id) {
1751 case PROP_STORE:
1752 priv->store = g_value_dup_object (value);
1753 break;
1754 case PROP_LIST_FEATURES:
1755 contact_list_view_set_list_features (view, g_value_get_flags (value));
1756 break;
1757 case PROP_CONTACT_FEATURES:
1758 priv->contact_features = g_value_get_flags (value);
1759 break;
1760 default:
1761 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1762 break;
1766 static void
1767 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1769 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1770 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1771 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1773 object_class->constructed = contact_list_view_constructed;
1774 object_class->dispose = contact_list_view_dispose;
1775 object_class->get_property = contact_list_view_get_property;
1776 object_class->set_property = contact_list_view_set_property;
1778 widget_class->drag_data_received = contact_list_view_drag_data_received;
1779 widget_class->drag_drop = contact_list_view_drag_drop;
1780 widget_class->drag_begin = contact_list_view_drag_begin;
1781 widget_class->drag_data_get = contact_list_view_drag_data_get;
1782 widget_class->drag_end = contact_list_view_drag_end;
1783 widget_class->drag_motion = contact_list_view_drag_motion;
1785 /* We use the class method to let user of this widget to connect to
1786 * the signal and stop emission of the signal so the default handler
1787 * won't be called. */
1788 tree_view_class->row_activated = contact_list_view_row_activated;
1790 signals[DRAG_CONTACT_RECEIVED] =
1791 g_signal_new ("drag-contact-received",
1792 G_OBJECT_CLASS_TYPE (klass),
1793 G_SIGNAL_RUN_LAST,
1795 NULL, NULL,
1796 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1797 G_TYPE_NONE,
1798 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1800 g_object_class_install_property (object_class,
1801 PROP_STORE,
1802 g_param_spec_object ("store",
1803 "The store of the view",
1804 "The store of the view",
1805 EMPATHY_TYPE_CONTACT_LIST_STORE,
1806 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1807 g_object_class_install_property (object_class,
1808 PROP_LIST_FEATURES,
1809 g_param_spec_flags ("list-features",
1810 "Features of the view",
1811 "Flags for all enabled features",
1812 EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1813 EMPATHY_CONTACT_LIST_FEATURE_NONE,
1814 G_PARAM_READWRITE));
1815 g_object_class_install_property (object_class,
1816 PROP_CONTACT_FEATURES,
1817 g_param_spec_flags ("contact-features",
1818 "Features of the contact menu",
1819 "Flags for all enabled features for the menu",
1820 EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1821 EMPATHY_CONTACT_FEATURE_NONE,
1822 G_PARAM_READWRITE));
1824 g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1827 static void
1828 empathy_contact_list_view_init (EmpathyContactListView *view)
1830 EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1831 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1833 view->priv = priv;
1835 /* Get saved group states. */
1836 empathy_contact_groups_get_all ();
1838 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1839 empathy_contact_list_store_row_separator_func,
1840 NULL, NULL);
1842 /* Set up drag target lists. */
1843 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1844 G_N_ELEMENTS (drag_types_dest_file));
1846 /* Connect to tree view signals rather than override. */
1847 g_signal_connect (view, "button-press-event",
1848 G_CALLBACK (contact_list_view_button_press_event_cb),
1849 NULL);
1850 g_signal_connect (view, "key-press-event",
1851 G_CALLBACK (contact_list_view_key_press_event_cb),
1852 NULL);
1853 g_signal_connect (view, "row-expanded",
1854 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1855 GINT_TO_POINTER (TRUE));
1856 g_signal_connect (view, "row-collapsed",
1857 G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1858 GINT_TO_POINTER (FALSE));
1859 g_signal_connect (view, "query-tooltip",
1860 G_CALLBACK (contact_list_view_query_tooltip_cb),
1861 NULL);
1864 EmpathyContactListView *
1865 empathy_contact_list_view_new (EmpathyContactListStore *store,
1866 EmpathyContactListFeatureFlags list_features,
1867 EmpathyContactFeatureFlags contact_features)
1869 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1871 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1872 "store", store,
1873 "contact-features", contact_features,
1874 "list-features", list_features,
1875 NULL);
1878 EmpathyContact *
1879 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1881 EmpathyContactListViewPriv *priv;
1882 GtkTreeSelection *selection;
1883 GtkTreeIter iter;
1884 GtkTreeModel *model;
1885 EmpathyContact *contact;
1887 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1889 priv = GET_PRIV (view);
1891 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1892 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1893 return NULL;
1896 gtk_tree_model_get (model, &iter,
1897 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1898 -1);
1900 return contact;
1903 EmpathyContactListFlags
1904 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1906 EmpathyContactListViewPriv *priv;
1907 GtkTreeSelection *selection;
1908 GtkTreeIter iter;
1909 GtkTreeModel *model;
1910 EmpathyContactListFlags flags;
1912 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1914 priv = GET_PRIV (view);
1916 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1917 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1918 return 0;
1921 gtk_tree_model_get (model, &iter,
1922 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1923 -1);
1925 return flags;
1928 gchar *
1929 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1930 gboolean *is_fake_group)
1932 EmpathyContactListViewPriv *priv;
1933 GtkTreeSelection *selection;
1934 GtkTreeIter iter;
1935 GtkTreeModel *model;
1936 gboolean is_group;
1937 gchar *name;
1938 gboolean fake;
1940 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1942 priv = GET_PRIV (view);
1944 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1945 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1946 return NULL;
1949 gtk_tree_model_get (model, &iter,
1950 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1951 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1952 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1953 -1);
1955 if (!is_group) {
1956 g_free (name);
1957 return NULL;
1960 if (is_fake_group != NULL)
1961 *is_fake_group = fake;
1963 return name;
1966 static gboolean
1967 contact_list_view_remove_dialog_show (GtkWindow *parent,
1968 const gchar *message,
1969 const gchar *secondary_text)
1971 GtkWidget *dialog;
1972 gboolean res;
1974 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1975 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1976 "%s", message);
1977 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1978 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1979 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1980 NULL);
1981 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1982 "%s", secondary_text);
1984 gtk_widget_show (dialog);
1986 res = gtk_dialog_run (GTK_DIALOG (dialog));
1987 gtk_widget_destroy (dialog);
1989 return (res == GTK_RESPONSE_YES);
1992 static void
1993 contact_list_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1994 EmpathyContactListView *view)
1996 EmpathyContactListViewPriv *priv = GET_PRIV (view);
1997 gchar *group;
1999 group = empathy_contact_list_view_get_selected_group (view, NULL);
2000 if (group) {
2001 gchar *text;
2002 GtkWindow *parent;
2004 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
2005 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2006 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
2007 EmpathyContactList *list;
2009 list = empathy_contact_list_store_get_list_iface (priv->store);
2010 empathy_contact_list_remove_group (list, group);
2013 g_free (text);
2016 g_free (group);
2019 GtkWidget *
2020 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
2022 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2023 gchar *group;
2024 GtkWidget *menu;
2025 GtkWidget *item;
2026 GtkWidget *image;
2027 gboolean is_fake_group;
2029 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2031 if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
2032 EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
2033 return NULL;
2036 group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
2037 if (!group || is_fake_group) {
2038 /* We can't alter fake groups */
2039 return NULL;
2042 menu = gtk_menu_new ();
2044 /* FIXME: Not implemented yet
2045 if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
2046 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2047 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2048 gtk_widget_show (item);
2049 g_signal_connect (item, "activate",
2050 G_CALLBACK (contact_list_view_group_rename_activate_cb),
2051 view);
2054 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
2055 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2056 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2057 GTK_ICON_SIZE_MENU);
2058 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2059 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2060 gtk_widget_show (item);
2061 g_signal_connect (item, "activate",
2062 G_CALLBACK (contact_list_view_group_remove_activate_cb),
2063 view);
2066 g_free (group);
2068 return menu;
2071 static void
2072 contact_list_view_remove_activate_cb (GtkMenuItem *menuitem,
2073 EmpathyContactListView *view)
2075 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2076 EmpathyContact *contact;
2078 contact = empathy_contact_list_view_dup_selected (view);
2080 if (contact) {
2081 gchar *text;
2082 GtkWindow *parent;
2084 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2085 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
2086 empathy_contact_get_alias (contact));
2087 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
2088 EmpathyContactList *list;
2090 list = empathy_contact_list_store_get_list_iface (priv->store);
2091 empathy_contact_list_remove (list, contact, "");
2094 g_free (text);
2095 g_object_unref (contact);
2099 GtkWidget *
2100 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
2102 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2103 EmpathyContact *contact;
2104 GtkWidget *menu;
2105 GtkWidget *item;
2106 GtkWidget *image;
2107 EmpathyContactListFlags flags;
2109 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2111 contact = empathy_contact_list_view_dup_selected (view);
2112 if (!contact) {
2113 return NULL;
2115 flags = empathy_contact_list_view_get_flags (view);
2117 menu = empathy_contact_menu_new (contact, priv->contact_features);
2119 /* Remove contact */
2120 if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
2121 flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
2122 /* create the menu if required, or just add a separator */
2123 if (!menu) {
2124 menu = gtk_menu_new ();
2125 } else {
2126 item = gtk_separator_menu_item_new ();
2127 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2128 gtk_widget_show (item);
2131 /* Remove */
2132 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2133 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2134 GTK_ICON_SIZE_MENU);
2135 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2136 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2137 gtk_widget_show (item);
2138 g_signal_connect (item, "activate",
2139 G_CALLBACK (contact_list_view_remove_activate_cb),
2140 view);
2143 g_object_unref (contact);
2145 return menu;
2148 void
2149 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2150 EmpathyLiveSearch *search)
2152 EmpathyContactListViewPriv *priv = GET_PRIV (view);
2154 /* remove old handlers if old search was not null */
2155 if (priv->search_widget != NULL) {
2156 g_signal_handlers_disconnect_by_func (view,
2157 contact_list_view_start_search_cb,
2158 NULL);
2160 g_signal_handlers_disconnect_by_func (priv->search_widget,
2161 contact_list_view_search_text_notify_cb,
2162 view);
2163 g_signal_handlers_disconnect_by_func (priv->search_widget,
2164 contact_list_view_search_activate_cb,
2165 view);
2166 g_signal_handlers_disconnect_by_func (priv->search_widget,
2167 contact_list_view_search_key_navigation_cb,
2168 view);
2169 g_signal_handlers_disconnect_by_func (priv->search_widget,
2170 contact_list_view_search_hide_cb,
2171 view);
2172 g_signal_handlers_disconnect_by_func (priv->search_widget,
2173 contact_list_view_search_show_cb,
2174 view);
2175 g_object_unref (priv->search_widget);
2176 priv->search_widget = NULL;
2179 /* connect handlers if new search is not null */
2180 if (search != NULL) {
2181 priv->search_widget = g_object_ref (search);
2183 g_signal_connect (view, "start-interactive-search",
2184 G_CALLBACK (contact_list_view_start_search_cb),
2185 NULL);
2187 g_signal_connect (priv->search_widget, "notify::text",
2188 G_CALLBACK (contact_list_view_search_text_notify_cb),
2189 view);
2190 g_signal_connect (priv->search_widget, "activate",
2191 G_CALLBACK (contact_list_view_search_activate_cb),
2192 view);
2193 g_signal_connect (priv->search_widget, "key-navigation",
2194 G_CALLBACK (contact_list_view_search_key_navigation_cb),
2195 view);
2196 g_signal_connect (priv->search_widget, "hide",
2197 G_CALLBACK (contact_list_view_search_hide_cb),
2198 view);
2199 g_signal_connect (priv->search_widget, "show",
2200 G_CALLBACK (contact_list_view_search_show_cb),
2201 view);