1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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>
30 #include <glib/gi18n-lib.h>
31 #include <gdk/gdkkeysyms.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)
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
;
74 EmpathyContactListView
*view
;
80 EmpathyContactListView
*view
;
81 EmpathyContact
*contact
;
89 PROP_CONTACT_FEATURES
,
93 DND_DRAG_TYPE_CONTACT_ID
,
94 DND_DRAG_TYPE_URI_LIST
,
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
},
116 DRAG_CONTACT_RECEIVED
,
120 static guint signals
[LAST_SIGNAL
];
122 G_DEFINE_TYPE (EmpathyContactListView
, empathy_contact_list_view
, GTK_TYPE_TREE_VIEW
);
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
;
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
);
145 gchar
*dup_str
= NULL
;
148 g_assert (live
!= NULL
);
150 /* check alias name */
151 str
= empathy_contact_get_alias (contact
);
152 if (empathy_live_search_match (live
, str
))
155 /* check contact id, remove the @server.com part */
156 str
= empathy_contact_get_id (contact
);
157 p
= strstr (str
, "@");
159 str
= dup_str
= g_strndup (str
, p
- str
);
161 visible
= empathy_live_search_match (live
, str
);
166 /* FIXME: Add more rules here, we could check phone numbers in
167 * contact's vCard for example. */
173 contact_list_view_filter_visible_func (GtkTreeModel
*model
,
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
;
184 if (priv
->search_widget
== NULL
||
185 !gtk_widget_get_visible (priv
->search_widget
))
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
,
194 if (contact
!= NULL
) {
195 visible
= contact_list_view_is_visible_contact (self
, contact
);
196 g_object_unref (contact
);
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
,
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 */
229 contact_list_view_query_tooltip_cb (EmpathyContactListView
*view
,
232 gboolean keyboard_mode
,
236 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
237 EmpathyContact
*contact
;
241 static gint running
= 0;
242 gboolean ret
= FALSE
;
244 /* Avoid an infinite loop. See GNOME bug #574377 */
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
) {
255 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view
), &x
, &y
,
257 &model
, &path
, &iter
)) {
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
,
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
),
281 gtk_widget_show (priv
->tooltip_widget
);
283 empathy_contact_widget_set_contact (priv
->tooltip_widget
,
287 gtk_tooltip_set_custom (tooltip
, priv
->tooltip_widget
);
290 g_object_unref (contact
);
300 GdkDragAction action
;
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
);
312 contact_list_view_drag_got_contact (TpConnection
*connection
,
313 EmpathyContact
*contact
,
318 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
319 DndGetContactData
*data
= user_data
;
320 EmpathyContactList
*list
;
323 DEBUG ("Error: %s", error
->message
);
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
);
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
);
357 group_can_be_modified (const gchar
*name
,
358 gboolean is_fake_group
,
361 /* Real groups can always be modified */
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
))
370 /* We can remove contacts from the 'ungrouped' fake group */
371 if (!adding
&& !tp_strdiff (name
, EMPATHY_CONTACT_LIST_STORE_UNGROUPED
))
378 contact_list_view_contact_drag_received (GtkWidget
*view
,
379 GdkDragContext
*context
,
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
;
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
))
407 /* Get source group information. */
408 if (priv
->drag_row
) {
409 source_path
= gtk_tree_row_reference_get_path (priv
->drag_row
);
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
))
420 if (!tp_strdiff (old_group
, new_group
)) {
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
);
434 connection
= tp_account_get_connection (account
);
438 DEBUG ("Failed to get connection for account '%s'", account_id
);
441 g_object_unref (account_manager
);
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
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
,
457 g_object_unref (account_manager
);
463 contact_list_view_file_drag_received (GtkWidget
*view
,
464 GdkDragContext
*context
,
467 GtkSelectionData
*selection
)
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
,
483 empathy_send_file_from_uri_list (contact
, sel_data
);
485 g_object_unref (contact
);
491 contact_list_view_drag_data_received (GtkWidget
*view
,
492 GdkDragContext
*context
,
495 GtkSelectionData
*selection
,
501 GtkTreeViewDropPosition position
;
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
),
516 else if (info
== DND_DRAG_TYPE_CONTACT_ID
|| info
== DND_DRAG_TYPE_STRING
) {
517 success
= contact_list_view_contact_drag_received (view
,
523 else if (info
== DND_DRAG_TYPE_URI_LIST
) {
524 success
= contact_list_view_file_drag_received (view
,
531 gtk_tree_path_free (path
);
532 gtk_drag_finish (context
, success
, FALSE
, GDK_CURRENT_TIME
);
536 contact_list_view_drag_motion_cb (DragMotionData
*data
)
538 gtk_tree_view_expand_row (GTK_TREE_VIEW (data
->view
),
542 data
->timeout_id
= 0;
548 contact_list_view_drag_motion (GtkWidget
*widget
,
549 GdkDragContext
*context
,
554 EmpathyContactListViewPriv
*priv
;
558 static DragMotionData
*dm
= NULL
;
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
),
579 cleanup
&= (dm
&& gtk_tree_path_compare (dm
->path
, path
) != 0);
580 is_different
= (!dm
|| (dm
&& gtk_tree_path_compare (dm
->path
, path
) != 0));
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);
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
;
606 GtkTreePath
*group_path
;
607 gtk_tree_model_get (model
, &iter
,
608 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP
, &is_group
,
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
,
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
),
624 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
);
625 gtk_tree_path_free (group_path
);
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
),
632 GTK_TREE_VIEW_DROP_BEFORE
);
636 /* This is a file drag, and it can only be dropped on contacts,
639 EmpathyContact
*contact
;
640 gtk_tree_model_get (model
, &iter
,
641 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT
, &contact
,
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
),
649 GTK_TREE_VIEW_DROP_INTO_OR_BEFORE
);
650 g_object_unref (contact
);
653 gdk_drag_status (context
, 0, time_
);
654 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget
), NULL
, 0);
659 if (!is_different
&& !cleanup
) {
664 gtk_tree_path_free (dm
->path
);
665 if (dm
->timeout_id
) {
666 g_source_remove (dm
->timeout_id
);
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
,
689 contact_list_view_drag_begin (GtkWidget
*widget
,
690 GdkDragContext
*context
)
692 EmpathyContactListViewPriv
*priv
;
693 GtkTreeSelection
*selection
;
698 priv
= GET_PRIV (widget
);
700 GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class
)->drag_begin (widget
,
703 selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (widget
));
704 if (!gtk_tree_selection_get_selected (selection
, &model
, &iter
)) {
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
);
714 contact_list_view_drag_data_get (GtkWidget
*widget
,
715 GdkDragContext
*context
,
716 GtkSelectionData
*selection
,
720 EmpathyContactListViewPriv
*priv
;
721 GtkTreePath
*src_path
;
724 EmpathyContact
*contact
;
726 const gchar
*contact_id
;
727 const gchar
*account_id
;
730 priv
= GET_PRIV (widget
);
732 model
= gtk_tree_view_get_model (GTK_TREE_VIEW (widget
));
733 if (!priv
->drag_row
) {
737 src_path
= gtk_tree_row_reference_get_path (priv
->drag_row
);
742 if (!gtk_tree_model_get_iter (model
, &iter
, src_path
)) {
743 gtk_tree_path_free (src_path
);
747 gtk_tree_path_free (src_path
);
749 contact
= empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget
));
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);
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
,
780 if (priv
->drag_row
) {
781 gtk_tree_row_reference_free (priv
->drag_row
);
782 priv
->drag_row
= NULL
;
787 contact_list_view_drag_drop (GtkWidget
*widget
,
788 GdkDragContext
*drag_context
,
797 EmpathyContactListView
*view
;
803 menu_deactivate_cb (GtkMenuShell
*menushell
,
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
));
814 contact_list_view_popup_menu_idle_cb (gpointer user_data
)
816 MenuPopupData
*data
= user_data
;
819 menu
= empathy_contact_list_view_get_contact_menu (data
->view
);
821 menu
= empathy_contact_list_view_get_group_menu (data
->view
);
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
),
842 g_slice_free (MenuPopupData
, data
);
848 contact_list_view_button_press_event_cb (EmpathyContactListView
*view
,
849 GdkEventButton
*event
,
852 if (event
->button
== 3) {
855 data
= g_slice_new (MenuPopupData
);
857 data
->button
= event
->button
;
858 data
->time
= event
->time
;
859 g_idle_add (contact_list_view_popup_menu_idle_cb
, data
);
866 contact_list_view_key_press_event_cb (EmpathyContactListView
*view
,
870 if (event
->keyval
== GDK_KEY_Menu
) {
873 data
= g_slice_new (MenuPopupData
);
876 data
->time
= event
->time
;
877 g_idle_add (contact_list_view_popup_menu_idle_cb
, data
);
884 contact_list_view_row_activated (GtkTreeView
*view
,
886 GtkTreeViewColumn
*column
)
888 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
889 EmpathyContact
*contact
;
893 if (!(priv
->contact_features
& EMPATHY_CONTACT_FEATURE_CHAT
)) {
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
,
904 DEBUG ("Starting a chat");
905 empathy_chat_with_contact (contact
,
906 empathy_get_current_action_time ());
907 g_object_unref (contact
);
912 contact_list_view_call_activated_cb (
913 EmpathyCellRendererActivatable
*cell
,
914 const gchar
*path_string
,
915 EmpathyContactListView
*view
)
920 EmpathyContact
*contact
;
921 GdkEventButton
*event
;
925 model
= gtk_tree_view_get_model (GTK_TREE_VIEW (view
));
926 if (!gtk_tree_model_get_iter_from_string (model
, &iter
, path_string
))
929 gtk_tree_model_get (model
, &iter
,
930 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT
, &contact
,
935 event
= (GdkEventButton
*) gtk_get_current_event ();
937 menu
= empathy_context_menu_new (GTK_WIDGET (view
));
938 shell
= GTK_MENU_SHELL (menu
);
941 item
= empathy_contact_audio_call_menu_item_new (contact
);
942 gtk_menu_shell_append (shell
, item
);
943 gtk_widget_show (item
);
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
);
958 contact_list_view_cell_set_background (EmpathyContactListView
*view
,
959 GtkCellRenderer
*cell
,
963 if (!is_group
&& is_active
) {
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
,
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
977 empathy_make_color_whiter (&color
);
980 "cell-background-rgba", &color
,
984 "cell-background-rgba", NULL
,
990 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn
*tree_column
,
991 GtkCellRenderer
*cell
,
994 EmpathyContactListView
*view
)
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
,
1007 "visible", !is_group
,
1011 if (pixbuf
!= NULL
) {
1012 g_object_unref (pixbuf
);
1015 contact_list_view_cell_set_background (view
, cell
, is_group
, is_active
);
1019 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn
*tree_column
,
1020 GtkCellRenderer
*cell
,
1021 GtkTreeModel
*model
,
1023 EmpathyContactListView
*view
)
1025 GdkPixbuf
*pixbuf
= NULL
;
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
,
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
);
1048 "visible", pixbuf
!= NULL
,
1053 g_object_unref (pixbuf
);
1059 contact_list_view_audio_call_cell_data_func (
1060 GtkTreeViewColumn
*tree_column
,
1061 GtkCellRenderer
*cell
,
1062 GtkTreeModel
*model
,
1064 EmpathyContactListView
*view
)
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
,
1078 "visible", !is_group
&& (can_audio
|| can_video
),
1079 "icon-name", can_video
? EMPATHY_IMAGE_VIDEO_CALL
: EMPATHY_IMAGE_VOIP
,
1082 contact_list_view_cell_set_background (view
, cell
, is_group
, is_active
);
1086 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn
*tree_column
,
1087 GtkCellRenderer
*cell
,
1088 GtkTreeModel
*model
,
1090 EmpathyContactListView
*view
)
1093 gboolean show_avatar
;
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
,
1105 "visible", !is_group
&& show_avatar
,
1110 g_object_unref (pixbuf
);
1113 contact_list_view_cell_set_background (view
, cell
, is_group
, is_active
);
1117 contact_list_view_text_cell_data_func (GtkTreeViewColumn
*tree_column
,
1118 GtkCellRenderer
*cell
,
1119 GtkTreeModel
*model
,
1121 EmpathyContactListView
*view
)
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
,
1131 contact_list_view_cell_set_background (view
, cell
, is_group
, is_active
);
1135 contact_list_view_expander_cell_data_func (GtkTreeViewColumn
*column
,
1136 GtkCellRenderer
*cell
,
1137 GtkTreeModel
*model
,
1139 EmpathyContactListView
*view
)
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
,
1149 if (gtk_tree_model_iter_has_child (model
, iter
)) {
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
);
1159 "expander-style", row_expanded
? GTK_EXPANDER_EXPANDED
: GTK_EXPANDER_COLLAPSED
,
1162 g_object_set (cell
, "visible", FALSE
, NULL
);
1165 contact_list_view_cell_set_background (view
, cell
, is_group
, is_active
);
1169 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView
*view
,
1174 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
1175 GtkTreeModel
*model
;
1179 if (!(priv
->list_features
& EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE
)) {
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
,
1189 expanded
= GPOINTER_TO_INT (user_data
);
1190 empathy_contact_group_set_expanded (name
, expanded
);
1196 contact_list_view_start_search_cb (EmpathyContactListView
*view
,
1199 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
1201 if (priv
->search_widget
== NULL
)
1204 if (gtk_widget_get_visible (GTK_WIDGET (priv
->search_widget
)))
1205 gtk_widget_grab_focus (GTK_WIDGET (priv
->search_widget
));
1207 gtk_widget_show (GTK_WIDGET (priv
->search_widget
));
1213 contact_list_view_search_text_notify_cb (EmpathyLiveSearch
*search
,
1215 EmpathyContactListView
*view
)
1217 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
1219 GtkTreeViewColumn
*focus_column
;
1220 GtkTreeModel
*model
;
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
);
1235 path
= gtk_tree_path_new_from_string ("0:1");
1237 } else if (gtk_tree_path_get_depth (path
) < 2) {
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
,
1246 gtk_tree_path_down (path
);
1247 gtk_tree_path_next (path
);
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
);
1265 contact_list_view_search_activate_cb (GtkWidget
*search
,
1266 EmpathyContactListView
*view
)
1269 GtkTreeViewColumn
*focus_column
;
1271 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view
), &path
, &focus_column
);
1273 gtk_tree_view_row_activated (GTK_TREE_VIEW (view
), path
,
1275 gtk_tree_path_free (path
);
1277 gtk_widget_hide (search
);
1282 contact_list_view_search_key_navigation_cb (GtkWidget
*search
,
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
);
1300 contact_list_view_search_hide_cb (EmpathyLiveSearch
*search
,
1301 EmpathyContactListView
*view
)
1303 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
1304 GtkTreeModel
*model
;
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
)) {
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
,
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
,
1341 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view
), path
);
1344 gtk_tree_path_free (path
);
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
));
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
));
1375 EmpathyContactListView
*view
;
1376 GtkTreeRowReference
*row_ref
;
1381 contact_list_view_expand_idle_cb (gpointer user_data
)
1383 ExpandData
*data
= user_data
;
1386 path
= gtk_tree_row_reference_get_path (data
->row_ref
);
1390 g_signal_handlers_block_by_func (data
->view
,
1391 contact_list_view_row_expand_or_collapse_cb
,
1392 GINT_TO_POINTER (data
->expand
));
1395 gtk_tree_view_expand_row (GTK_TREE_VIEW (data
->view
), path
,
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
));
1407 g_object_unref (data
->view
);
1408 gtk_tree_row_reference_free (data
->row_ref
);
1409 g_slice_free (ExpandData
, data
);
1415 contact_list_view_row_has_child_toggled_cb (GtkTreeModel
*model
,
1418 EmpathyContactListView
*view
)
1420 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
1421 gboolean is_group
= FALSE
;
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
,
1430 if (!is_group
|| EMP_STR_EMPTY (name
)) {
1435 data
= g_slice_new0 (ExpandData
);
1436 data
->view
= g_object_ref (view
);
1437 data
->row_ref
= gtk_tree_row_reference_new (model
, path
);
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
);
1451 contact_list_view_verify_group_visibility (EmpathyContactListView
*view
,
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)
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
);
1480 contact_list_view_store_row_changed_cb (GtkTreeModel
*model
,
1483 EmpathyContactListView
*view
)
1485 contact_list_view_verify_group_visibility (view
, path
);
1489 contact_list_view_store_row_deleted_cb (GtkTreeModel
*model
,
1491 EmpathyContactListView
*view
)
1493 contact_list_view_verify_group_visibility (view
, path
);
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
,
1510 g_signal_connect (priv
->filter
, "row-has-child-toggled",
1511 G_CALLBACK (contact_list_view_row_has_child_toggled_cb
),
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
),
1520 tp_g_signal_connect_object (priv
->store
, "row-inserted",
1521 G_CALLBACK (contact_list_view_store_row_changed_cb
),
1523 tp_g_signal_connect_object (priv
->store
, "row-deleted",
1524 G_CALLBACK (contact_list_view_store_row_deleted_cb
),
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.
1534 "headers-visible", FALSE
,
1535 "reorderable", TRUE
,
1536 "show-expanders", FALSE
,
1539 col
= gtk_tree_view_column_new ();
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 (
1546 (GtkTreeCellDataFunc
) contact_list_view_pixbuf_cell_data_func
,
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 (
1560 (GtkTreeCellDataFunc
) contact_list_view_group_icon_cell_data_func
,
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 (
1576 (GtkTreeCellDataFunc
) contact_list_view_text_cell_data_func
,
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 (
1597 (GtkTreeCellDataFunc
) contact_list_view_audio_call_cell_data_func
,
1604 g_signal_connect (cell
, "path-activated",
1605 G_CALLBACK (contact_list_view_call_activated_cb
),
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 (
1613 (GtkTreeCellDataFunc
) contact_list_view_avatar_cell_data_func
,
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 (
1629 (GtkTreeCellDataFunc
) contact_list_view_expander_cell_data_func
,
1632 /* Actually add the column now we have added all cell renderers */
1633 gtk_tree_view_append_column (GTK_TREE_VIEW (view
), col
);
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
),
1652 G_N_ELEMENTS (drag_types_source
),
1653 GDK_ACTION_MOVE
| GDK_ACTION_COPY
);
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
,
1663 G_N_ELEMENTS (drag_types_dest
),
1664 GDK_ACTION_MOVE
| GDK_ACTION_COPY
);
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
);
1676 contact_list_view_dispose (GObject
*object
)
1678 EmpathyContactListView
*view
= EMPATHY_CONTACT_LIST_VIEW (object
);
1679 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
1682 g_object_unref (priv
->store
);
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
);
1704 contact_list_view_get_property (GObject
*object
,
1709 EmpathyContactListViewPriv
*priv
;
1711 priv
= GET_PRIV (object
);
1715 g_value_set_object (value
, priv
->store
);
1717 case PROP_LIST_FEATURES
:
1718 g_value_set_flags (value
, priv
->list_features
);
1720 case PROP_CONTACT_FEATURES
:
1721 g_value_set_flags (value
, priv
->contact_features
);
1724 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
1730 contact_list_view_set_property (GObject
*object
,
1732 const GValue
*value
,
1735 EmpathyContactListView
*view
= EMPATHY_CONTACT_LIST_VIEW (object
);
1736 EmpathyContactListViewPriv
*priv
= GET_PRIV (object
);
1740 priv
->store
= g_value_dup_object (value
);
1742 case PROP_LIST_FEATURES
:
1743 contact_list_view_set_list_features (view
, g_value_get_flags (value
));
1745 case PROP_CONTACT_FEATURES
:
1746 priv
->contact_features
= g_value_get_flags (value
);
1749 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
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
),
1784 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING
,
1786 3, EMPATHY_TYPE_CONTACT
, G_TYPE_STRING
, G_TYPE_STRING
);
1788 g_object_class_install_property (object_class
,
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
,
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
));
1816 empathy_contact_list_view_init (EmpathyContactListView
*view
)
1818 EmpathyContactListViewPriv
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (view
,
1819 EMPATHY_TYPE_CONTACT_LIST_VIEW
, EmpathyContactListViewPriv
);
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
,
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
),
1838 g_signal_connect (view
, "key-press-event",
1839 G_CALLBACK (contact_list_view_key_press_event_cb
),
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
),
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
,
1861 "contact-features", contact_features
,
1862 "list-features", list_features
,
1867 empathy_contact_list_view_dup_selected (EmpathyContactListView
*view
)
1869 GtkTreeSelection
*selection
;
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
)) {
1881 gtk_tree_model_get (model
, &iter
,
1882 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT
, &contact
,
1888 EmpathyContactListFlags
1889 empathy_contact_list_view_get_flags (EmpathyContactListView
*view
)
1891 GtkTreeSelection
*selection
;
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
)) {
1903 gtk_tree_model_get (model
, &iter
,
1904 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS
, &flags
,
1911 empathy_contact_list_view_get_selected_group (EmpathyContactListView
*view
,
1912 gboolean
*is_fake_group
)
1914 GtkTreeSelection
*selection
;
1916 GtkTreeModel
*model
;
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
)) {
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
,
1939 if (is_fake_group
!= NULL
)
1940 *is_fake_group
= fake
;
1946 contact_list_view_remove_dialog_show (GtkWindow
*parent
,
1947 const gchar
*message
,
1948 const gchar
*secondary_text
)
1953 dialog
= gtk_message_dialog_new (parent
, GTK_DIALOG_MODAL
,
1954 GTK_MESSAGE_QUESTION
, GTK_BUTTONS_NONE
,
1956 gtk_dialog_add_buttons (GTK_DIALOG (dialog
),
1957 GTK_STOCK_CANCEL
, GTK_RESPONSE_NO
,
1958 GTK_STOCK_DELETE
, GTK_RESPONSE_YES
,
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
);
1972 contact_list_view_group_remove_activate_cb (GtkMenuItem
*menuitem
,
1973 EmpathyContactListView
*view
)
1975 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
1978 group
= empathy_contact_list_view_get_selected_group (view
, NULL
);
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
);
1999 empathy_contact_list_view_get_group_menu (EmpathyContactListView
*view
)
2001 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
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
))) {
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 */
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),
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
),
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
);
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
, "");
2074 g_object_unref (contact
);
2079 empathy_contact_list_view_get_contact_menu (EmpathyContactListView
*view
)
2081 EmpathyContactListViewPriv
*priv
= GET_PRIV (view
);
2082 EmpathyContact
*contact
;
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
);
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 */
2103 menu
= gtk_menu_new ();
2105 item
= gtk_separator_menu_item_new ();
2106 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), item
);
2107 gtk_widget_show (item
);
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
),
2122 g_object_unref (contact
);
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
,
2139 g_signal_handlers_disconnect_by_func (priv
->search_widget
,
2140 contact_list_view_search_text_notify_cb
,
2142 g_signal_handlers_disconnect_by_func (priv
->search_widget
,
2143 contact_list_view_search_activate_cb
,
2145 g_signal_handlers_disconnect_by_func (priv
->search_widget
,
2146 contact_list_view_search_key_navigation_cb
,
2148 g_signal_handlers_disconnect_by_func (priv
->search_widget
,
2149 contact_list_view_search_hide_cb
,
2151 g_signal_handlers_disconnect_by_func (priv
->search_widget
,
2152 contact_list_view_search_show_cb
,
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
),
2166 g_signal_connect (priv
->search_widget
, "notify::text",
2167 G_CALLBACK (contact_list_view_search_text_notify_cb
),
2169 g_signal_connect (priv
->search_widget
, "activate",
2170 G_CALLBACK (contact_list_view_search_activate_cb
),
2172 g_signal_connect (priv
->search_widget
, "key-navigation",
2173 G_CALLBACK (contact_list_view_search_key_navigation_cb
),
2175 g_signal_connect (priv
->search_widget
, "hide",
2176 G_CALLBACK (contact_list_view_search_hide_cb
),
2178 g_signal_connect (priv
->search_widget
, "show",
2179 G_CALLBACK (contact_list_view_search_show_cb
),