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-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-call-factory.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-dispatcher.h>
45 #include <libempathy/empathy-utils.h>
47 #include "empathy-individual-view.h"
48 #include "empathy-individual-menu.h"
49 #include "empathy-individual-store.h"
50 #include "empathy-images.h"
51 #include "empathy-cell-renderer-expander.h"
52 #include "empathy-cell-renderer-text.h"
53 #include "empathy-cell-renderer-activatable.h"
54 #include "empathy-ui-utils.h"
55 #include "empathy-gtk-enum-types.h"
56 #include "empathy-gtk-marshal.h"
58 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
59 #include <libempathy/empathy-debug.h>
61 /* Active users are those which have recently changed state
62 * (e.g. online, offline or from normal to a busy state).
65 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
68 EmpathyIndividualStore *store;
69 GtkTreeRowReference *drag_row;
70 EmpathyIndividualViewFeatureFlags view_features;
71 EmpathyContactFeatureFlags individual_features;
72 GtkWidget *tooltip_widget;
73 GtkTargetList *file_targets;
75 gboolean show_offline;
77 GtkTreeModelFilter *filter;
78 GtkWidget *search_widget;
80 guint expand_groups_idle_handler;
81 /* owned string (group name) -> bool (whether to expand/contract) */
82 GHashTable *expand_groups;
83 } EmpathyIndividualViewPriv;
87 EmpathyIndividualView *view;
94 EmpathyIndividualView *view;
95 FolksIndividual *individual;
104 PROP_INDIVIDUAL_FEATURES,
108 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
109 * specific EmpathyContacts (between/in/out of Individuals) */
112 DND_DRAG_TYPE_INDIVIDUAL_ID,
113 DND_DRAG_TYPE_URI_LIST,
114 DND_DRAG_TYPE_STRING,
117 #define DRAG_TYPE(T,I) \
118 { (gchar *) T, 0, I }
120 static const GtkTargetEntry drag_types_dest[] = {
121 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
122 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
123 DRAG_TYPE ("text/contact-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
124 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
125 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
128 static const GtkTargetEntry drag_types_dest_file[] = {
129 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
130 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
133 static const GtkTargetEntry drag_types_source[] = {
134 DRAG_TYPE ("text/contact-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
139 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
140 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
144 DRAG_CONTACT_RECEIVED,
148 static guint signals[LAST_SIGNAL];
150 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
154 individual_view_tooltip_destroy_cb (GtkWidget *widget,
155 EmpathyIndividualView *view)
157 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
159 if (priv->tooltip_widget != NULL)
161 DEBUG ("Tooltip destroyed");
162 tp_clear_object (&priv->tooltip_widget);
167 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
170 gboolean keyboard_mode,
174 EmpathyIndividualViewPriv *priv;
175 FolksIndividual *individual;
179 static gint running = 0;
180 gboolean ret = FALSE;
182 priv = GET_PRIV (view);
184 /* Avoid an infinite loop. See GNOME bug #574377 */
190 /* Don't show the tooltip if there's already a popup menu */
191 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
194 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
195 keyboard_mode, &model, &path, &iter))
198 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
199 gtk_tree_path_free (path);
201 gtk_tree_model_get (model, &iter,
202 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
204 if (individual == NULL)
207 if (priv->tooltip_widget == NULL)
209 priv->tooltip_widget = empathy_individual_widget_new (individual,
210 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
211 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION);
212 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
213 g_object_ref (priv->tooltip_widget);
214 g_signal_connect (priv->tooltip_widget, "destroy",
215 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
216 gtk_widget_show (priv->tooltip_widget);
220 empathy_individual_widget_set_individual (
221 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
224 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
227 g_object_unref (individual);
235 groups_change_group_cb (GObject *source,
236 GAsyncResult *result,
239 FolksGroups *groups = FOLKS_GROUPS (source);
240 GError *error = NULL;
242 folks_groups_change_group_finish (groups, result, &error);
245 g_warning ("failed to change group: %s", error->message);
246 g_clear_error (&error);
251 individual_view_handle_drag (EmpathyIndividualView *self,
252 FolksIndividual *individual,
253 const gchar *old_group,
254 const gchar *new_group,
255 GdkDragAction action)
257 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
258 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
260 DEBUG ("individual %s dragged from '%s' to '%s'",
261 folks_individual_get_id (individual), old_group, new_group);
263 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
265 /* Mark contact as favourite */
266 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
270 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
272 /* Remove contact as favourite */
273 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
275 /* Don't try to remove it */
279 if (new_group != NULL)
280 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
281 groups_change_group_cb, NULL);
283 if (old_group != NULL && action == GDK_ACTION_MOVE)
284 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE,
285 groups_change_group_cb, NULL);
289 group_can_be_modified (const gchar *name,
290 gboolean is_fake_group,
293 /* Real groups can always be modified */
297 /* The favorite fake group can be modified so users can
298 * add/remove favorites using DnD */
299 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
302 /* We can remove contacts from the 'ungrouped' fake group */
303 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
310 individual_view_contact_drag_received (GtkWidget *self,
311 GdkDragContext *context,
314 GtkSelectionData *selection)
316 EmpathyIndividualViewPriv *priv;
317 EmpathyIndividualManager *manager;
318 FolksIndividual *individual;
319 GtkTreePath *source_path;
320 const gchar *sel_data;
321 gchar *new_group = NULL;
322 gchar *old_group = NULL;
323 gboolean new_group_is_fake, old_group_is_fake = TRUE;
325 priv = GET_PRIV (self);
327 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
328 new_group = empathy_individual_store_get_parent_group (model, path,
329 NULL, &new_group_is_fake);
331 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
334 /* Get source group information. */
337 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
341 empathy_individual_store_get_parent_group (model, source_path,
342 NULL, &old_group_is_fake);
343 gtk_tree_path_free (source_path);
347 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
350 if (!tp_strdiff (old_group, new_group))
357 /* XXX: for contacts, we used to ensure the account, create the contact
358 * factory, and then wait on the contacts. But they should already be
359 * created by this point */
361 manager = empathy_individual_manager_dup_singleton ();
362 individual = empathy_individual_manager_lookup_member (manager, sel_data);
364 if (individual == NULL)
366 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
368 g_object_unref (manager);
373 /* FIXME: We should probably wait for the cb before calling
376 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
377 old_group, new_group, gdk_drag_context_get_selected_action (context));
379 g_object_unref (G_OBJECT (manager));
387 individual_view_file_drag_received (GtkWidget *view,
388 GdkDragContext *context,
391 GtkSelectionData *selection)
394 const gchar *sel_data;
395 FolksIndividual *individual;
396 EmpathyContact *contact;
398 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
400 gtk_tree_model_get_iter (model, &iter, path);
401 gtk_tree_model_get (model, &iter,
402 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
403 if (individual == NULL)
406 contact = empathy_contact_dup_from_folks_individual (individual);
407 empathy_send_file_from_uri_list (contact, sel_data);
409 g_object_unref (individual);
410 tp_clear_object (&contact);
416 individual_view_drag_data_received (GtkWidget *view,
417 GdkDragContext *context,
420 GtkSelectionData *selection,
426 GtkTreeViewDropPosition position;
428 gboolean success = TRUE;
430 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
432 /* Get destination group information. */
433 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
434 x, y, &path, &position);
439 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
440 || info == DND_DRAG_TYPE_STRING)
442 success = individual_view_contact_drag_received (view,
443 context, model, path, selection);
445 else if (info == DND_DRAG_TYPE_URI_LIST)
447 success = individual_view_file_drag_received (view,
448 context, model, path, selection);
451 gtk_tree_path_free (path);
452 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
456 individual_view_drag_motion_cb (DragMotionData *data)
458 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
460 data->timeout_id = 0;
466 individual_view_drag_motion (GtkWidget *widget,
467 GdkDragContext *context,
472 EmpathyIndividualViewPriv *priv;
476 static DragMotionData *dm = NULL;
479 gboolean is_different = FALSE;
480 gboolean cleanup = TRUE;
481 gboolean retval = TRUE;
483 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
484 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
486 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
487 x, y, &path, NULL, NULL, NULL);
489 cleanup &= (dm == NULL);
493 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
494 is_different = ((dm == NULL) || ((dm != NULL)
495 && gtk_tree_path_compare (dm->path, path) != 0));
502 /* Coordinates don't point to an actual row, so make sure the pointer
503 and highlighting don't indicate that a drag is possible.
505 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
506 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
509 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
510 gtk_tree_model_get_iter (model, &iter, path);
512 if (target == GDK_NONE)
514 /* If target == GDK_NONE, then we don't have a target that can be
515 dropped on a contact. This means a contact drag. If we're
516 pointing to a group, highlight it. Otherwise, if the contact
517 we're pointing to is in a group, highlight that. Otherwise,
518 set the drag position to before the first row for a drag into
519 the "non-group" at the top.
521 GtkTreeIter group_iter;
523 GtkTreePath *group_path;
524 gtk_tree_model_get (model, &iter,
525 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
532 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
533 gtk_tree_model_get (model, &group_iter,
534 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
538 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
539 group_path = gtk_tree_model_get_path (model, &group_iter);
540 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
541 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
542 gtk_tree_path_free (group_path);
546 group_path = gtk_tree_path_new_first ();
547 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
548 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
549 group_path, GTK_TREE_VIEW_DROP_BEFORE);
554 /* This is a file drag, and it can only be dropped on contacts,
557 FolksIndividual *individual;
558 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
560 gtk_tree_model_get (model, &iter,
561 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
562 if (individual != NULL)
564 EmpathyContact *contact = NULL;
566 contact = empathy_contact_dup_from_folks_individual (individual);
567 caps = empathy_contact_get_capabilities (contact);
569 tp_clear_object (&contact);
572 if (individual != NULL &&
573 folks_individual_is_online (individual) &&
574 (caps & EMPATHY_CAPABILITIES_FT))
576 gdk_drag_status (context, GDK_ACTION_COPY, time_);
577 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
578 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
582 gdk_drag_status (context, 0, time_);
583 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
587 if (individual != NULL)
588 g_object_unref (individual);
591 if (!is_different && !cleanup)
596 gtk_tree_path_free (dm->path);
599 g_source_remove (dm->timeout_id);
607 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
609 dm = g_new0 (DragMotionData, 1);
611 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
612 dm->path = gtk_tree_path_copy (path);
614 dm->timeout_id = g_timeout_add_seconds (1,
615 (GSourceFunc) individual_view_drag_motion_cb, dm);
622 individual_view_drag_begin (GtkWidget *widget,
623 GdkDragContext *context)
625 EmpathyIndividualViewPriv *priv;
626 GtkTreeSelection *selection;
631 priv = GET_PRIV (widget);
633 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
636 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
637 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
640 path = gtk_tree_model_get_path (model, &iter);
641 priv->drag_row = gtk_tree_row_reference_new (model, path);
642 gtk_tree_path_free (path);
646 individual_view_drag_data_get (GtkWidget *widget,
647 GdkDragContext *context,
648 GtkSelectionData *selection,
652 EmpathyIndividualViewPriv *priv;
653 GtkTreePath *src_path;
656 FolksIndividual *individual;
657 const gchar *individual_id;
659 priv = GET_PRIV (widget);
661 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
662 if (priv->drag_row == NULL)
665 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
666 if (src_path == NULL)
669 if (!gtk_tree_model_get_iter (model, &iter, src_path))
671 gtk_tree_path_free (src_path);
675 gtk_tree_path_free (src_path);
678 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
679 if (individual == NULL)
682 individual_id = folks_individual_get_id (individual);
684 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
686 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
687 (guchar *) individual_id, strlen (individual_id) + 1);
690 g_object_unref (individual);
694 individual_view_drag_end (GtkWidget *widget,
695 GdkDragContext *context)
697 EmpathyIndividualViewPriv *priv;
699 priv = GET_PRIV (widget);
701 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
706 gtk_tree_row_reference_free (priv->drag_row);
707 priv->drag_row = NULL;
712 individual_view_drag_drop (GtkWidget *widget,
713 GdkDragContext *drag_context,
723 EmpathyIndividualView *view;
729 individual_view_popup_menu_idle_cb (gpointer user_data)
731 MenuPopupData *data = user_data;
734 menu = empathy_individual_view_get_individual_menu (data->view);
736 menu = empathy_individual_view_get_group_menu (data->view);
740 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
741 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
743 gtk_widget_show (menu);
744 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
746 g_object_ref_sink (menu);
747 g_object_unref (menu);
750 g_slice_free (MenuPopupData, data);
756 individual_view_button_press_event_cb (EmpathyIndividualView *view,
757 GdkEventButton *event,
760 if (event->button == 3)
764 data = g_slice_new (MenuPopupData);
766 data->button = event->button;
767 data->time = event->time;
768 g_idle_add (individual_view_popup_menu_idle_cb, data);
775 individual_view_key_press_event_cb (EmpathyIndividualView *view,
779 if (event->keyval == GDK_Menu)
783 data = g_slice_new (MenuPopupData);
786 data->time = event->time;
787 g_idle_add (individual_view_popup_menu_idle_cb, data);
794 individual_view_row_activated (GtkTreeView *view,
796 GtkTreeViewColumn *column)
798 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
799 FolksIndividual *individual;
800 EmpathyContact *contact = NULL;
804 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
807 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
808 gtk_tree_model_get_iter (model, &iter, path);
809 gtk_tree_model_get (model, &iter,
810 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
812 if (individual == NULL)
815 contact = empathy_contact_dup_from_folks_individual (individual);
818 DEBUG ("Starting a chat");
820 empathy_dispatcher_chat_with_contact (contact,
821 gtk_get_current_event_time ());
824 g_object_unref (individual);
825 tp_clear_object (&contact);
829 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
830 const gchar *path_string,
831 EmpathyIndividualView *view)
836 FolksIndividual *individual;
837 GdkEventButton *event;
841 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
842 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
845 gtk_tree_model_get (model, &iter,
846 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
847 if (individual == NULL)
850 event = (GdkEventButton *) gtk_get_current_event ();
852 menu = gtk_menu_new ();
853 shell = GTK_MENU_SHELL (menu);
856 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
857 gtk_menu_shell_append (shell, item);
858 gtk_widget_show (item);
861 item = empathy_individual_video_call_menu_item_new (individual, NULL);
862 gtk_menu_shell_append (shell, item);
863 gtk_widget_show (item);
865 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
866 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
867 gtk_widget_show (menu);
868 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
869 event->button, event->time);
870 g_object_ref_sink (menu);
871 g_object_unref (menu);
873 g_object_unref (individual);
877 individual_view_cell_set_background (EmpathyIndividualView *view,
878 GtkCellRenderer *cell,
885 style = gtk_widget_get_style (GTK_WIDGET (view));
887 if (!is_group && is_active)
889 color = style->bg[GTK_STATE_SELECTED];
891 /* Here we take the current theme colour and add it to
892 * the colour for white and average the two. This
893 * gives a colour which is inline with the theme but
896 color.red = (color.red + (style->white).red) / 2;
897 color.green = (color.green + (style->white).green) / 2;
898 color.blue = (color.blue + (style->white).blue) / 2;
900 g_object_set (cell, "cell-background-gdk", &color, NULL);
903 g_object_set (cell, "cell-background-gdk", NULL, NULL);
907 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
908 GtkCellRenderer *cell,
911 EmpathyIndividualView *view)
917 gtk_tree_model_get (model, iter,
918 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
919 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
920 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
923 "visible", !is_group,
927 tp_clear_object (&pixbuf);
929 individual_view_cell_set_background (view, cell, is_group, is_active);
933 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
934 GtkCellRenderer *cell,
937 EmpathyIndividualView *view)
939 GdkPixbuf *pixbuf = NULL;
943 gtk_tree_model_get (model, iter,
944 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
945 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
950 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
952 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
955 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
957 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
963 "visible", pixbuf != NULL,
967 tp_clear_object (&pixbuf);
973 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
974 GtkCellRenderer *cell,
977 EmpathyIndividualView *view)
981 gboolean can_audio, can_video;
983 gtk_tree_model_get (model, iter,
984 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
985 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
986 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
987 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
990 "visible", !is_group && (can_audio || can_video),
991 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
994 individual_view_cell_set_background (view, cell, is_group, is_active);
998 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
999 GtkCellRenderer *cell,
1000 GtkTreeModel *model,
1002 EmpathyIndividualView *view)
1005 gboolean show_avatar;
1009 gtk_tree_model_get (model, iter,
1010 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1011 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1012 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1013 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1016 "visible", !is_group && show_avatar,
1020 tp_clear_object (&pixbuf);
1022 individual_view_cell_set_background (view, cell, is_group, is_active);
1026 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1027 GtkCellRenderer *cell,
1028 GtkTreeModel *model,
1030 EmpathyIndividualView *view)
1035 gtk_tree_model_get (model, iter,
1036 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1037 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1039 individual_view_cell_set_background (view, cell, is_group, is_active);
1043 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1044 GtkCellRenderer *cell,
1045 GtkTreeModel *model,
1047 EmpathyIndividualView *view)
1052 gtk_tree_model_get (model, iter,
1053 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1054 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1056 if (gtk_tree_model_iter_has_child (model, iter))
1059 gboolean row_expanded;
1061 path = gtk_tree_model_get_path (model, iter);
1063 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1064 (gtk_tree_view_column_get_tree_view (column)), path);
1065 gtk_tree_path_free (path);
1070 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1074 g_object_set (cell, "visible", FALSE, NULL);
1076 individual_view_cell_set_background (view, cell, is_group, is_active);
1080 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1085 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1086 GtkTreeModel *model;
1090 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1093 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1095 gtk_tree_model_get (model, iter,
1096 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1098 expanded = GPOINTER_TO_INT (user_data);
1099 empathy_contact_group_set_expanded (name, expanded);
1105 individual_view_start_search_cb (EmpathyIndividualView *view,
1108 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1110 if (priv->search_widget == NULL)
1113 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1114 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1116 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1122 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1124 EmpathyIndividualView *view)
1126 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1128 GtkTreeViewColumn *focus_column;
1129 GtkTreeModel *model;
1131 gboolean set_cursor = FALSE;
1133 gtk_tree_model_filter_refilter (priv->filter);
1135 /* Set cursor on the first contact. If it is already set on a group,
1136 * set it on its first child contact. Note that first child of a group
1137 * is its separator, that's why we actually set to the 2nd
1140 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1141 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1145 path = gtk_tree_path_new_from_string ("0:1");
1148 else if (gtk_tree_path_get_depth (path) < 2)
1152 gtk_tree_model_get_iter (model, &iter, path);
1153 gtk_tree_model_get (model, &iter,
1154 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1159 gtk_tree_path_down (path);
1160 gtk_tree_path_next (path);
1167 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1169 if (gtk_tree_model_get_iter (model, &iter, path))
1171 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1176 gtk_tree_path_free (path);
1180 individual_view_search_activate_cb (GtkWidget *search,
1181 EmpathyIndividualView *view)
1184 GtkTreeViewColumn *focus_column;
1186 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1189 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1190 gtk_tree_path_free (path);
1192 gtk_widget_hide (search);
1197 individual_view_search_key_navigation_cb (GtkWidget *search,
1199 EmpathyIndividualView *view)
1201 GdkEventKey *eventkey = ((GdkEventKey *) event);
1202 gboolean ret = FALSE;
1204 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1206 GdkEvent *new_event;
1208 new_event = gdk_event_copy (event);
1209 gtk_widget_grab_focus (GTK_WIDGET (view));
1210 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1211 gtk_widget_grab_focus (search);
1213 gdk_event_free (new_event);
1220 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1221 EmpathyIndividualView *view)
1223 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1224 GtkTreeModel *model;
1225 GtkTreePath *cursor_path;
1227 gboolean valid = FALSE;
1229 /* block expand or collapse handlers, they would write the
1230 * expand or collapsed setting to file otherwise */
1231 g_signal_handlers_block_by_func (view,
1232 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1233 g_signal_handlers_block_by_func (view,
1234 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1236 /* restore which groups are expanded and which are not */
1237 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1238 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1239 valid; valid = gtk_tree_model_iter_next (model, &iter))
1245 gtk_tree_model_get (model, &iter,
1246 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1247 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1256 path = gtk_tree_model_get_path (model, &iter);
1257 if ((priv->view_features &
1258 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1259 empathy_contact_group_get_expanded (name))
1261 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1265 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1268 gtk_tree_path_free (path);
1272 /* unblock expand or collapse handlers */
1273 g_signal_handlers_unblock_by_func (view,
1274 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1275 g_signal_handlers_unblock_by_func (view,
1276 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1278 /* keep the selected contact visible */
1279 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1281 if (cursor_path != NULL)
1282 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1285 gtk_tree_path_free (cursor_path);
1289 individual_view_search_show_cb (EmpathyLiveSearch *search,
1290 EmpathyIndividualView *view)
1292 /* block expand or collapse handlers during expand all, they would
1293 * write the expand or collapsed setting to file otherwise */
1294 g_signal_handlers_block_by_func (view,
1295 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1297 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1299 g_signal_handlers_unblock_by_func (view,
1300 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1304 expand_idle_foreach_cb (GtkTreeModel *model,
1307 EmpathyIndividualView *self)
1309 EmpathyIndividualViewPriv *priv;
1311 gpointer should_expand;
1314 /* We only want groups */
1315 if (gtk_tree_path_get_depth (path) > 1)
1318 gtk_tree_model_get (model, iter,
1319 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1320 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1323 if (is_group == FALSE)
1329 priv = GET_PRIV (self);
1331 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1332 &should_expand) == TRUE)
1334 if (GPOINTER_TO_INT (should_expand) == TRUE)
1335 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1337 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1339 g_hash_table_remove (priv->expand_groups, name);
1348 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1350 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1352 DEBUG ("individual_view_expand_idle_cb");
1354 g_signal_handlers_block_by_func (self,
1355 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1356 g_signal_handlers_block_by_func (self,
1357 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1359 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1360 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1362 g_signal_handlers_unblock_by_func (self,
1363 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1364 g_signal_handlers_unblock_by_func (self,
1365 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1367 g_object_unref (self);
1368 priv->expand_groups_idle_handler = 0;
1374 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1377 EmpathyIndividualView *view)
1379 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1380 gboolean should_expand, is_group = FALSE;
1382 gpointer will_expand;
1384 gtk_tree_model_get (model, iter,
1385 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1386 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1389 if (!is_group || EMP_STR_EMPTY (name))
1395 should_expand = (priv->view_features &
1396 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1397 (priv->search_widget != NULL &&
1398 gtk_widget_get_visible (priv->search_widget)) ||
1399 empathy_contact_group_get_expanded (name);
1401 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1402 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1403 * a hash table, and expand or contract them as appropriate all at once in
1404 * an idle handler which iterates over all the group rows. */
1405 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1406 &will_expand) == FALSE &&
1407 GPOINTER_TO_INT (will_expand) != should_expand)
1409 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1410 GINT_TO_POINTER (should_expand));
1412 if (priv->expand_groups_idle_handler == 0)
1414 priv->expand_groups_idle_handler =
1415 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1416 g_object_ref (view));
1423 /* FIXME: This is a workaround for bgo#621076 */
1425 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1428 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1429 GtkTreeModel *model;
1430 GtkTreePath *parent_path;
1431 GtkTreeIter parent_iter;
1433 if (gtk_tree_path_get_depth (path) < 2)
1436 /* A group row is visible if and only if at least one if its child is visible.
1437 * So when a row is inserted/deleted/changed in the base model, that could
1438 * modify the visibility of its parent in the filter model.
1441 model = GTK_TREE_MODEL (priv->store);
1442 parent_path = gtk_tree_path_copy (path);
1443 gtk_tree_path_up (parent_path);
1444 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1446 /* This tells the filter to verify the visibility of that row, and
1447 * show/hide it if necessary */
1448 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1449 parent_path, &parent_iter);
1451 gtk_tree_path_free (parent_path);
1455 individual_view_store_row_changed_cb (GtkTreeModel *model,
1458 EmpathyIndividualView *view)
1460 individual_view_verify_group_visibility (view, path);
1464 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1466 EmpathyIndividualView *view)
1468 individual_view_verify_group_visibility (view, path);
1472 individual_view_is_visible_individual (EmpathyIndividualView *self,
1473 FolksIndividual *individual)
1475 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1476 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1478 GList *personas, *l;
1480 /* We're only giving the visibility wrt filtering here, not things like
1482 if (live == NULL || gtk_widget_get_visible (GTK_WIDGET (live)) == FALSE)
1485 /* check alias name */
1486 str = folks_individual_get_alias (individual);
1488 if (empathy_live_search_match (live, str))
1491 /* check contact id, remove the @server.com part */
1492 personas = folks_individual_get_personas (individual);
1493 for (l = personas; l; l = l->next)
1496 gchar *dup_str = NULL;
1499 if (!TPF_IS_PERSONA (l->data))
1502 str = folks_persona_get_display_id (l->data);
1503 p = strstr (str, "@");
1505 str = dup_str = g_strndup (str, p - str);
1507 visible = empathy_live_search_match (live, str);
1513 /* FIXME: Add more rules here, we could check phone numbers in
1514 * contact's vCard for example. */
1520 individual_view_filter_visible_func (GtkTreeModel *model,
1524 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1525 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1526 FolksIndividual *individual = NULL;
1527 gboolean is_group, is_separator, valid;
1528 GtkTreeIter child_iter;
1529 gboolean visible, is_online;
1530 gboolean is_searching = TRUE;
1532 if (priv->search_widget == NULL ||
1533 !gtk_widget_get_visible (priv->search_widget))
1534 is_searching = FALSE;
1536 gtk_tree_model_get (model, iter,
1537 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1538 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1539 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1540 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1543 if (individual != NULL)
1545 if (is_searching == TRUE)
1546 visible = individual_view_is_visible_individual (self, individual);
1548 visible = (priv->show_offline || is_online);
1550 g_object_unref (individual);
1552 /* FIXME: Work around bgo#626552/bgo#621076 */
1553 if (visible == TRUE)
1555 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1556 individual_view_verify_group_visibility (self, path);
1557 gtk_tree_path_free (path);
1566 /* Not a contact, not a separator, must be a group */
1567 g_return_val_if_fail (is_group, FALSE);
1569 /* only show groups which are not empty */
1570 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1571 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1573 gtk_tree_model_get (model, &child_iter,
1574 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1575 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1578 if (individual == NULL)
1581 visible = individual_view_is_visible_individual (self, individual);
1582 g_object_unref (individual);
1584 /* show group if it has at least one visible contact in it */
1585 if ((is_searching && visible) ||
1586 (!is_searching && (priv->show_offline || is_online)))
1594 individual_view_constructed (GObject *object)
1596 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1597 GtkCellRenderer *cell;
1598 GtkTreeViewColumn *col;
1603 "headers-visible", FALSE,
1604 "show-expanders", FALSE,
1607 col = gtk_tree_view_column_new ();
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 (col, cell,
1613 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1623 cell = gtk_cell_renderer_pixbuf_new ();
1624 gtk_tree_view_column_pack_start (col, cell, FALSE);
1625 gtk_tree_view_column_set_cell_data_func (col, cell,
1626 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1638 cell = empathy_cell_renderer_text_new ();
1639 gtk_tree_view_column_pack_start (col, cell, TRUE);
1640 gtk_tree_view_column_set_cell_data_func (col, cell,
1641 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1643 gtk_tree_view_column_add_attribute (col, cell,
1644 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1645 gtk_tree_view_column_add_attribute (col, cell,
1646 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1647 gtk_tree_view_column_add_attribute (col, cell,
1648 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1649 gtk_tree_view_column_add_attribute (col, cell,
1650 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1651 gtk_tree_view_column_add_attribute (col, cell,
1652 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1653 gtk_tree_view_column_add_attribute (col, cell,
1654 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1656 /* Audio Call Icon */
1657 cell = empathy_cell_renderer_activatable_new ();
1658 gtk_tree_view_column_pack_start (col, cell, FALSE);
1659 gtk_tree_view_column_set_cell_data_func (col, cell,
1660 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1663 g_object_set (cell, "visible", FALSE, NULL);
1665 g_signal_connect (cell, "path-activated",
1666 G_CALLBACK (individual_view_call_activated_cb), view);
1669 cell = gtk_cell_renderer_pixbuf_new ();
1670 gtk_tree_view_column_pack_start (col, cell, FALSE);
1671 gtk_tree_view_column_set_cell_data_func (col, cell,
1672 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1684 cell = empathy_cell_renderer_expander_new ();
1685 gtk_tree_view_column_pack_end (col, cell, FALSE);
1686 gtk_tree_view_column_set_cell_data_func (col, cell,
1687 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1690 /* Actually add the column now we have added all cell renderers */
1691 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1694 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1696 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1699 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1701 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1707 individual_view_set_view_features (EmpathyIndividualView *view,
1708 EmpathyIndividualFeatureFlags features)
1710 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1711 gboolean has_tooltip;
1713 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1715 priv->view_features = features;
1717 /* Setting reorderable is a hack that gets us row previews as drag icons
1718 for free. We override all the drag handlers. It's tricky to get the
1719 position of the drag icon right in drag_begin. GtkTreeView has special
1720 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1723 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1724 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG));
1726 /* Update DnD source/dest */
1727 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1729 gtk_drag_source_set (GTK_WIDGET (view),
1732 G_N_ELEMENTS (drag_types_source),
1733 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1737 gtk_drag_source_unset (GTK_WIDGET (view));
1741 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1743 gtk_drag_dest_set (GTK_WIDGET (view),
1744 GTK_DEST_DEFAULT_ALL,
1746 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1750 /* FIXME: URI could still be droped depending on FT feature */
1751 gtk_drag_dest_unset (GTK_WIDGET (view));
1754 /* Update has-tooltip */
1756 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1757 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1761 individual_view_dispose (GObject *object)
1763 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1764 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1766 tp_clear_object (&priv->store);
1767 tp_clear_object (&priv->filter);
1768 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1769 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1771 empathy_individual_view_set_live_search (view, NULL);
1773 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1777 individual_view_finalize (GObject *object)
1779 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1781 g_hash_table_destroy (priv->expand_groups);
1783 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1787 individual_view_get_property (GObject *object,
1792 EmpathyIndividualViewPriv *priv;
1794 priv = GET_PRIV (object);
1799 g_value_set_object (value, priv->store);
1801 case PROP_VIEW_FEATURES:
1802 g_value_set_flags (value, priv->view_features);
1804 case PROP_INDIVIDUAL_FEATURES:
1805 g_value_set_flags (value, priv->individual_features);
1807 case PROP_SHOW_OFFLINE:
1808 g_value_set_boolean (value, priv->show_offline);
1811 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1817 individual_view_set_property (GObject *object,
1819 const GValue *value,
1822 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1823 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1828 empathy_individual_view_set_store (view, g_value_get_object (value));
1830 case PROP_VIEW_FEATURES:
1831 individual_view_set_view_features (view, g_value_get_flags (value));
1833 case PROP_INDIVIDUAL_FEATURES:
1834 priv->individual_features = g_value_get_flags (value);
1836 case PROP_SHOW_OFFLINE:
1837 empathy_individual_view_set_show_offline (view,
1838 g_value_get_boolean (value));
1841 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1847 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1849 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1850 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1851 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1853 object_class->constructed = individual_view_constructed;
1854 object_class->dispose = individual_view_dispose;
1855 object_class->finalize = individual_view_finalize;
1856 object_class->get_property = individual_view_get_property;
1857 object_class->set_property = individual_view_set_property;
1859 widget_class->drag_data_received = individual_view_drag_data_received;
1860 widget_class->drag_drop = individual_view_drag_drop;
1861 widget_class->drag_begin = individual_view_drag_begin;
1862 widget_class->drag_data_get = individual_view_drag_data_get;
1863 widget_class->drag_end = individual_view_drag_end;
1864 widget_class->drag_motion = individual_view_drag_motion;
1866 /* We use the class method to let user of this widget to connect to
1867 * the signal and stop emission of the signal so the default handler
1868 * won't be called. */
1869 tree_view_class->row_activated = individual_view_row_activated;
1871 signals[DRAG_CONTACT_RECEIVED] =
1872 g_signal_new ("drag-contact-received",
1873 G_OBJECT_CLASS_TYPE (klass),
1877 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1878 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1880 g_object_class_install_property (object_class,
1882 g_param_spec_object ("store",
1883 "The store of the view",
1884 "The store of the view",
1885 EMPATHY_TYPE_INDIVIDUAL_STORE,
1886 G_PARAM_READWRITE));
1887 g_object_class_install_property (object_class,
1889 g_param_spec_flags ("view-features",
1890 "Features of the view",
1891 "Flags for all enabled features",
1892 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1893 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1894 g_object_class_install_property (object_class,
1895 PROP_INDIVIDUAL_FEATURES,
1896 g_param_spec_flags ("individual-features",
1897 "Features of the contact menu",
1898 "Flags for all enabled features for the menu",
1899 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1900 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1901 g_object_class_install_property (object_class,
1903 g_param_spec_boolean ("show-offline",
1905 "Whether contact list should display "
1906 "offline contacts", FALSE, G_PARAM_READWRITE));
1908 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1912 empathy_individual_view_init (EmpathyIndividualView *view)
1914 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1915 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1918 /* Get saved group states. */
1919 empathy_contact_groups_get_all ();
1921 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
1922 (GDestroyNotify) g_free, NULL);
1924 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1925 empathy_individual_store_row_separator_func, NULL, NULL);
1927 /* Set up drag target lists. */
1928 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1929 G_N_ELEMENTS (drag_types_dest_file));
1931 /* Connect to tree view signals rather than override. */
1932 g_signal_connect (view, "button-press-event",
1933 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1934 g_signal_connect (view, "key-press-event",
1935 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1936 g_signal_connect (view, "row-expanded",
1937 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1938 GINT_TO_POINTER (TRUE));
1939 g_signal_connect (view, "row-collapsed",
1940 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1941 GINT_TO_POINTER (FALSE));
1942 g_signal_connect (view, "query-tooltip",
1943 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1946 EmpathyIndividualView *
1947 empathy_individual_view_new (EmpathyIndividualStore *store,
1948 EmpathyIndividualViewFeatureFlags view_features,
1949 EmpathyIndividualFeatureFlags individual_features)
1951 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1953 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1955 "individual-features", individual_features,
1956 "view-features", view_features, NULL);
1960 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1962 EmpathyIndividualViewPriv *priv;
1963 GtkTreeSelection *selection;
1965 GtkTreeModel *model;
1966 FolksIndividual *individual;
1968 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1970 priv = GET_PRIV (view);
1972 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1973 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1976 gtk_tree_model_get (model, &iter,
1977 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1982 EmpathyIndividualManagerFlags
1983 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1985 EmpathyIndividualViewPriv *priv;
1986 GtkTreeSelection *selection;
1988 GtkTreeModel *model;
1989 EmpathyIndividualFeatureFlags flags;
1991 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1993 priv = GET_PRIV (view);
1995 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1996 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1999 gtk_tree_model_get (model, &iter,
2000 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2006 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2007 gboolean *is_fake_group)
2009 EmpathyIndividualViewPriv *priv;
2010 GtkTreeSelection *selection;
2012 GtkTreeModel *model;
2017 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2019 priv = GET_PRIV (view);
2021 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2022 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2025 gtk_tree_model_get (model, &iter,
2026 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2027 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2028 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2036 if (is_fake_group != NULL)
2037 *is_fake_group = fake;
2043 individual_view_remove_dialog_show (GtkWindow *parent,
2044 const gchar *message,
2045 const gchar *secondary_text)
2050 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2051 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2052 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2053 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2054 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2055 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2056 "%s", secondary_text);
2058 gtk_widget_show (dialog);
2060 res = gtk_dialog_run (GTK_DIALOG (dialog));
2061 gtk_widget_destroy (dialog);
2063 return (res == GTK_RESPONSE_YES);
2067 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2068 EmpathyIndividualView *view)
2072 group = empathy_individual_view_get_selected_group (view, NULL);
2079 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2081 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2082 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2085 EmpathyIndividualManager *manager =
2086 empathy_individual_manager_dup_singleton ();
2087 empathy_individual_manager_remove_group (manager, group);
2088 g_object_unref (G_OBJECT (manager));
2098 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2100 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2105 gboolean is_fake_group;
2107 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2109 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2110 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2113 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2114 if (!group || is_fake_group)
2116 /* We can't alter fake groups */
2120 menu = gtk_menu_new ();
2123 if (priv->view_features &
2124 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2125 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2126 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2127 gtk_widget_show (item);
2128 g_signal_connect (item, "activate",
2129 G_CALLBACK (individual_view_group_rename_activate_cb),
2134 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2136 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2137 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2138 GTK_ICON_SIZE_MENU);
2139 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2140 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2141 gtk_widget_show (item);
2142 g_signal_connect (item, "activate",
2143 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2152 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2153 EmpathyIndividualView *view)
2155 FolksIndividual *individual;
2157 individual = empathy_individual_view_dup_selected (view);
2159 if (individual != NULL)
2164 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2167 ("Do you really want to remove the contact '%s'?"),
2168 folks_individual_get_alias (individual));
2169 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2172 EmpathyIndividualManager *manager;
2174 manager = empathy_individual_manager_dup_singleton ();
2175 empathy_individual_manager_remove (manager, individual, "");
2176 g_object_unref (G_OBJECT (manager));
2180 g_object_unref (individual);
2185 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2187 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2188 FolksIndividual *individual;
2189 GtkWidget *menu = NULL;
2192 EmpathyIndividualManagerFlags flags;
2194 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2196 individual = empathy_individual_view_dup_selected (view);
2197 if (individual == NULL)
2200 flags = empathy_individual_view_get_flags (view);
2202 menu = empathy_individual_menu_new (individual, priv->individual_features);
2204 /* Remove contact */
2205 if (priv->view_features &
2206 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2207 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2210 /* create the menu if required, or just add a separator */
2212 menu = gtk_menu_new ();
2215 item = gtk_separator_menu_item_new ();
2216 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2217 gtk_widget_show (item);
2221 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2222 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2223 GTK_ICON_SIZE_MENU);
2224 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2225 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2226 gtk_widget_show (item);
2227 g_signal_connect (item, "activate",
2228 G_CALLBACK (individual_view_remove_activate_cb), view);
2231 g_object_unref (individual);
2237 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2238 EmpathyLiveSearch *search)
2240 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2242 /* remove old handlers if old search was not null */
2243 if (priv->search_widget != NULL)
2245 g_signal_handlers_disconnect_by_func (view,
2246 individual_view_start_search_cb, NULL);
2248 g_signal_handlers_disconnect_by_func (priv->search_widget,
2249 individual_view_search_text_notify_cb, view);
2250 g_signal_handlers_disconnect_by_func (priv->search_widget,
2251 individual_view_search_activate_cb, view);
2252 g_signal_handlers_disconnect_by_func (priv->search_widget,
2253 individual_view_search_key_navigation_cb, view);
2254 g_signal_handlers_disconnect_by_func (priv->search_widget,
2255 individual_view_search_hide_cb, view);
2256 g_signal_handlers_disconnect_by_func (priv->search_widget,
2257 individual_view_search_show_cb, view);
2258 g_object_unref (priv->search_widget);
2259 priv->search_widget = NULL;
2262 /* connect handlers if new search is not null */
2265 priv->search_widget = g_object_ref (search);
2267 g_signal_connect (view, "start-interactive-search",
2268 G_CALLBACK (individual_view_start_search_cb), NULL);
2270 g_signal_connect (priv->search_widget, "notify::text",
2271 G_CALLBACK (individual_view_search_text_notify_cb), view);
2272 g_signal_connect (priv->search_widget, "activate",
2273 G_CALLBACK (individual_view_search_activate_cb), view);
2274 g_signal_connect (priv->search_widget, "key-navigation",
2275 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2276 g_signal_connect (priv->search_widget, "hide",
2277 G_CALLBACK (individual_view_search_hide_cb), view);
2278 g_signal_connect (priv->search_widget, "show",
2279 G_CALLBACK (individual_view_search_show_cb), view);
2284 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2286 EmpathyIndividualViewPriv *priv;
2288 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2290 priv = GET_PRIV (self);
2292 return (priv->search_widget != NULL &&
2293 gtk_widget_get_visible (priv->search_widget));
2297 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2299 EmpathyIndividualViewPriv *priv;
2301 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2303 priv = GET_PRIV (self);
2305 return priv->show_offline;
2309 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2310 gboolean show_offline)
2312 EmpathyIndividualViewPriv *priv;
2314 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2316 priv = GET_PRIV (self);
2318 priv->show_offline = show_offline;
2320 g_object_notify (G_OBJECT (self), "show-offline");
2321 gtk_tree_model_filter_refilter (priv->filter);
2324 EmpathyIndividualStore *
2325 empathy_individual_view_get_store (EmpathyIndividualView *self)
2327 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2329 return GET_PRIV (self)->store;
2333 empathy_individual_view_set_store (EmpathyIndividualView *self,
2334 EmpathyIndividualStore *store)
2336 EmpathyIndividualViewPriv *priv;
2338 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2339 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2341 priv = GET_PRIV (self);
2343 /* Destroy the old filter and remove the old store */
2344 if (priv->store != NULL)
2346 g_signal_handlers_disconnect_by_func (priv->store,
2347 individual_view_store_row_changed_cb, self);
2348 g_signal_handlers_disconnect_by_func (priv->store,
2349 individual_view_store_row_deleted_cb, self);
2351 g_signal_handlers_disconnect_by_func (priv->filter,
2352 individual_view_row_has_child_toggled_cb, self);
2354 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2357 tp_clear_object (&priv->filter);
2358 tp_clear_object (&priv->store);
2360 /* Set the new store */
2361 priv->store = store;
2365 g_object_ref (store);
2367 /* Create a new filter */
2368 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2369 GTK_TREE_MODEL (priv->store), NULL));
2370 gtk_tree_model_filter_set_visible_func (priv->filter,
2371 individual_view_filter_visible_func, self, NULL);
2373 g_signal_connect (priv->filter, "row-has-child-toggled",
2374 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2375 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2376 GTK_TREE_MODEL (priv->filter));
2378 tp_g_signal_connect_object (priv->store, "row-changed",
2379 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2380 tp_g_signal_connect_object (priv->store, "row-inserted",
2381 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2382 tp_g_signal_connect_object (priv->store, "row-deleted",
2383 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);