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 if (data->view != NULL)
460 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
461 g_object_remove_weak_pointer (G_OBJECT (data->view),
462 (gpointer *) &data->view);
465 data->timeout_id = 0;
471 individual_view_drag_motion (GtkWidget *widget,
472 GdkDragContext *context,
477 EmpathyIndividualViewPriv *priv;
481 static DragMotionData *dm = NULL;
484 gboolean is_different = FALSE;
485 gboolean cleanup = TRUE;
486 gboolean retval = TRUE;
488 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
489 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
491 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
492 x, y, &path, NULL, NULL, NULL);
494 cleanup &= (dm == NULL);
498 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
499 is_different = ((dm == NULL) || ((dm != NULL)
500 && gtk_tree_path_compare (dm->path, path) != 0));
507 /* Coordinates don't point to an actual row, so make sure the pointer
508 and highlighting don't indicate that a drag is possible.
510 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
511 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
514 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
515 gtk_tree_model_get_iter (model, &iter, path);
517 if (target == GDK_NONE)
519 /* If target == GDK_NONE, then we don't have a target that can be
520 dropped on a contact. This means a contact drag. If we're
521 pointing to a group, highlight it. Otherwise, if the contact
522 we're pointing to is in a group, highlight that. Otherwise,
523 set the drag position to before the first row for a drag into
524 the "non-group" at the top.
526 GtkTreeIter group_iter;
528 GtkTreePath *group_path;
529 gtk_tree_model_get (model, &iter,
530 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
537 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
538 gtk_tree_model_get (model, &group_iter,
539 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
543 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
544 group_path = gtk_tree_model_get_path (model, &group_iter);
545 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
546 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
547 gtk_tree_path_free (group_path);
551 group_path = gtk_tree_path_new_first ();
552 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
553 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
554 group_path, GTK_TREE_VIEW_DROP_BEFORE);
559 /* This is a file drag, and it can only be dropped on contacts,
561 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
562 * even if we have a valid target. */
563 FolksIndividual *individual = NULL;
564 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
566 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
568 gtk_tree_model_get (model, &iter,
569 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
573 if (individual != NULL)
575 EmpathyContact *contact = NULL;
577 contact = empathy_contact_dup_from_folks_individual (individual);
578 caps = empathy_contact_get_capabilities (contact);
580 tp_clear_object (&contact);
583 if (individual != NULL &&
584 folks_individual_is_online (individual) &&
585 (caps & EMPATHY_CAPABILITIES_FT))
587 gdk_drag_status (context, GDK_ACTION_COPY, time_);
588 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
589 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
593 gdk_drag_status (context, 0, time_);
594 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
598 if (individual != NULL)
599 g_object_unref (individual);
602 if (!is_different && !cleanup)
607 gtk_tree_path_free (dm->path);
610 g_source_remove (dm->timeout_id);
618 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
620 dm = g_new0 (DragMotionData, 1);
622 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
623 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
624 dm->path = gtk_tree_path_copy (path);
626 dm->timeout_id = g_timeout_add_seconds (1,
627 (GSourceFunc) individual_view_drag_motion_cb, dm);
634 individual_view_drag_begin (GtkWidget *widget,
635 GdkDragContext *context)
637 EmpathyIndividualViewPriv *priv;
638 GtkTreeSelection *selection;
643 priv = GET_PRIV (widget);
645 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
648 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
649 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
652 path = gtk_tree_model_get_path (model, &iter);
653 priv->drag_row = gtk_tree_row_reference_new (model, path);
654 gtk_tree_path_free (path);
658 individual_view_drag_data_get (GtkWidget *widget,
659 GdkDragContext *context,
660 GtkSelectionData *selection,
664 EmpathyIndividualViewPriv *priv;
665 GtkTreePath *src_path;
668 FolksIndividual *individual;
669 const gchar *individual_id;
671 priv = GET_PRIV (widget);
673 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
674 if (priv->drag_row == NULL)
677 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
678 if (src_path == NULL)
681 if (!gtk_tree_model_get_iter (model, &iter, src_path))
683 gtk_tree_path_free (src_path);
687 gtk_tree_path_free (src_path);
690 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
691 if (individual == NULL)
694 individual_id = folks_individual_get_id (individual);
696 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
698 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
699 (guchar *) individual_id, strlen (individual_id) + 1);
702 g_object_unref (individual);
706 individual_view_drag_end (GtkWidget *widget,
707 GdkDragContext *context)
709 EmpathyIndividualViewPriv *priv;
711 priv = GET_PRIV (widget);
713 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
718 gtk_tree_row_reference_free (priv->drag_row);
719 priv->drag_row = NULL;
724 individual_view_drag_drop (GtkWidget *widget,
725 GdkDragContext *drag_context,
735 EmpathyIndividualView *view;
741 individual_view_popup_menu_idle_cb (gpointer user_data)
743 MenuPopupData *data = user_data;
746 menu = empathy_individual_view_get_individual_menu (data->view);
748 menu = empathy_individual_view_get_group_menu (data->view);
752 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
753 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
755 gtk_widget_show (menu);
756 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
758 g_object_ref_sink (menu);
759 g_object_unref (menu);
762 g_slice_free (MenuPopupData, data);
768 individual_view_button_press_event_cb (EmpathyIndividualView *view,
769 GdkEventButton *event,
772 if (event->button == 3)
776 data = g_slice_new (MenuPopupData);
778 data->button = event->button;
779 data->time = event->time;
780 g_idle_add (individual_view_popup_menu_idle_cb, data);
787 individual_view_key_press_event_cb (EmpathyIndividualView *view,
791 if (event->keyval == GDK_Menu)
795 data = g_slice_new (MenuPopupData);
798 data->time = event->time;
799 g_idle_add (individual_view_popup_menu_idle_cb, data);
806 individual_view_row_activated (GtkTreeView *view,
808 GtkTreeViewColumn *column)
810 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
811 FolksIndividual *individual;
812 EmpathyContact *contact = NULL;
816 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
819 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
820 gtk_tree_model_get_iter (model, &iter, path);
821 gtk_tree_model_get (model, &iter,
822 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
824 if (individual == NULL)
827 contact = empathy_contact_dup_from_folks_individual (individual);
830 DEBUG ("Starting a chat");
832 empathy_dispatcher_chat_with_contact (contact,
833 gtk_get_current_event_time ());
836 g_object_unref (individual);
837 tp_clear_object (&contact);
841 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
842 const gchar *path_string,
843 EmpathyIndividualView *view)
848 FolksIndividual *individual;
849 GdkEventButton *event;
853 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
854 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
857 gtk_tree_model_get (model, &iter,
858 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
859 if (individual == NULL)
862 event = (GdkEventButton *) gtk_get_current_event ();
864 menu = gtk_menu_new ();
865 shell = GTK_MENU_SHELL (menu);
868 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
869 gtk_menu_shell_append (shell, item);
870 gtk_widget_show (item);
873 item = empathy_individual_video_call_menu_item_new (individual, NULL);
874 gtk_menu_shell_append (shell, item);
875 gtk_widget_show (item);
877 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
878 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
879 gtk_widget_show (menu);
880 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
881 event->button, event->time);
882 g_object_ref_sink (menu);
883 g_object_unref (menu);
885 g_object_unref (individual);
889 individual_view_cell_set_background (EmpathyIndividualView *view,
890 GtkCellRenderer *cell,
897 style = gtk_widget_get_style (GTK_WIDGET (view));
899 if (!is_group && is_active)
901 color = style->bg[GTK_STATE_SELECTED];
903 /* Here we take the current theme colour and add it to
904 * the colour for white and average the two. This
905 * gives a colour which is inline with the theme but
908 color.red = (color.red + (style->white).red) / 2;
909 color.green = (color.green + (style->white).green) / 2;
910 color.blue = (color.blue + (style->white).blue) / 2;
912 g_object_set (cell, "cell-background-gdk", &color, NULL);
915 g_object_set (cell, "cell-background-gdk", NULL, NULL);
919 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
920 GtkCellRenderer *cell,
923 EmpathyIndividualView *view)
929 gtk_tree_model_get (model, iter,
930 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
931 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
932 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
935 "visible", !is_group,
939 tp_clear_object (&pixbuf);
941 individual_view_cell_set_background (view, cell, is_group, is_active);
945 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
946 GtkCellRenderer *cell,
949 EmpathyIndividualView *view)
951 GdkPixbuf *pixbuf = NULL;
955 gtk_tree_model_get (model, iter,
956 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
957 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
962 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
964 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
967 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
969 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
975 "visible", pixbuf != NULL,
979 tp_clear_object (&pixbuf);
985 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
986 GtkCellRenderer *cell,
989 EmpathyIndividualView *view)
993 gboolean can_audio, can_video;
995 gtk_tree_model_get (model, iter,
996 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
997 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
998 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
999 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1002 "visible", !is_group && (can_audio || can_video),
1003 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1006 individual_view_cell_set_background (view, cell, is_group, is_active);
1010 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1011 GtkCellRenderer *cell,
1012 GtkTreeModel *model,
1014 EmpathyIndividualView *view)
1017 gboolean show_avatar;
1021 gtk_tree_model_get (model, iter,
1022 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1023 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1024 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1025 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1028 "visible", !is_group && show_avatar,
1032 tp_clear_object (&pixbuf);
1034 individual_view_cell_set_background (view, cell, is_group, is_active);
1038 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1039 GtkCellRenderer *cell,
1040 GtkTreeModel *model,
1042 EmpathyIndividualView *view)
1047 gtk_tree_model_get (model, iter,
1048 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1049 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1051 individual_view_cell_set_background (view, cell, is_group, is_active);
1055 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1056 GtkCellRenderer *cell,
1057 GtkTreeModel *model,
1059 EmpathyIndividualView *view)
1064 gtk_tree_model_get (model, iter,
1065 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1066 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1068 if (gtk_tree_model_iter_has_child (model, iter))
1071 gboolean row_expanded;
1073 path = gtk_tree_model_get_path (model, iter);
1075 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1076 (gtk_tree_view_column_get_tree_view (column)), path);
1077 gtk_tree_path_free (path);
1082 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1086 g_object_set (cell, "visible", FALSE, NULL);
1088 individual_view_cell_set_background (view, cell, is_group, is_active);
1092 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1097 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1098 GtkTreeModel *model;
1102 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1105 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1107 gtk_tree_model_get (model, iter,
1108 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1110 expanded = GPOINTER_TO_INT (user_data);
1111 empathy_contact_group_set_expanded (name, expanded);
1117 individual_view_start_search_cb (EmpathyIndividualView *view,
1120 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1122 if (priv->search_widget == NULL)
1125 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1126 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1128 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1134 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1136 EmpathyIndividualView *view)
1138 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1140 GtkTreeViewColumn *focus_column;
1141 GtkTreeModel *model;
1143 gboolean set_cursor = FALSE;
1145 gtk_tree_model_filter_refilter (priv->filter);
1147 /* Set cursor on the first contact. If it is already set on a group,
1148 * set it on its first child contact. Note that first child of a group
1149 * is its separator, that's why we actually set to the 2nd
1152 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1153 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1157 path = gtk_tree_path_new_from_string ("0:1");
1160 else if (gtk_tree_path_get_depth (path) < 2)
1164 gtk_tree_model_get_iter (model, &iter, path);
1165 gtk_tree_model_get (model, &iter,
1166 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1171 gtk_tree_path_down (path);
1172 gtk_tree_path_next (path);
1179 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1181 if (gtk_tree_model_get_iter (model, &iter, path))
1183 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1188 gtk_tree_path_free (path);
1192 individual_view_search_activate_cb (GtkWidget *search,
1193 EmpathyIndividualView *view)
1196 GtkTreeViewColumn *focus_column;
1198 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1201 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1202 gtk_tree_path_free (path);
1204 gtk_widget_hide (search);
1209 individual_view_search_key_navigation_cb (GtkWidget *search,
1211 EmpathyIndividualView *view)
1213 GdkEventKey *eventkey = ((GdkEventKey *) event);
1214 gboolean ret = FALSE;
1216 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1218 GdkEvent *new_event;
1220 new_event = gdk_event_copy (event);
1221 gtk_widget_grab_focus (GTK_WIDGET (view));
1222 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1223 gtk_widget_grab_focus (search);
1225 gdk_event_free (new_event);
1232 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1233 EmpathyIndividualView *view)
1235 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1236 GtkTreeModel *model;
1237 GtkTreePath *cursor_path;
1239 gboolean valid = FALSE;
1241 /* block expand or collapse handlers, they would write the
1242 * expand or collapsed setting to file otherwise */
1243 g_signal_handlers_block_by_func (view,
1244 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1245 g_signal_handlers_block_by_func (view,
1246 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1248 /* restore which groups are expanded and which are not */
1249 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1250 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1251 valid; valid = gtk_tree_model_iter_next (model, &iter))
1257 gtk_tree_model_get (model, &iter,
1258 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1259 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1268 path = gtk_tree_model_get_path (model, &iter);
1269 if ((priv->view_features &
1270 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1271 empathy_contact_group_get_expanded (name))
1273 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1277 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1280 gtk_tree_path_free (path);
1284 /* unblock expand or collapse handlers */
1285 g_signal_handlers_unblock_by_func (view,
1286 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1287 g_signal_handlers_unblock_by_func (view,
1288 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1290 /* keep the selected contact visible */
1291 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1293 if (cursor_path != NULL)
1294 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1297 gtk_tree_path_free (cursor_path);
1301 individual_view_search_show_cb (EmpathyLiveSearch *search,
1302 EmpathyIndividualView *view)
1304 /* block expand or collapse handlers during expand all, they would
1305 * write the expand or collapsed setting to file otherwise */
1306 g_signal_handlers_block_by_func (view,
1307 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1309 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1311 g_signal_handlers_unblock_by_func (view,
1312 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1316 expand_idle_foreach_cb (GtkTreeModel *model,
1319 EmpathyIndividualView *self)
1321 EmpathyIndividualViewPriv *priv;
1323 gpointer should_expand;
1326 /* We only want groups */
1327 if (gtk_tree_path_get_depth (path) > 1)
1330 gtk_tree_model_get (model, iter,
1331 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1332 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1335 if (is_group == FALSE)
1341 priv = GET_PRIV (self);
1343 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1344 &should_expand) == TRUE)
1346 if (GPOINTER_TO_INT (should_expand) == TRUE)
1347 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1349 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1351 g_hash_table_remove (priv->expand_groups, name);
1360 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1362 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1364 DEBUG ("individual_view_expand_idle_cb");
1366 g_signal_handlers_block_by_func (self,
1367 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1368 g_signal_handlers_block_by_func (self,
1369 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1371 /* The store/filter could've been removed while we were in the idle queue */
1372 if (priv->filter != NULL)
1374 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1375 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1378 g_signal_handlers_unblock_by_func (self,
1379 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1380 g_signal_handlers_unblock_by_func (self,
1381 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1383 g_object_unref (self);
1384 priv->expand_groups_idle_handler = 0;
1390 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1393 EmpathyIndividualView *view)
1395 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1396 gboolean should_expand, is_group = FALSE;
1398 gpointer will_expand;
1400 gtk_tree_model_get (model, iter,
1401 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1402 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1405 if (!is_group || EMP_STR_EMPTY (name))
1411 should_expand = (priv->view_features &
1412 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1413 (priv->search_widget != NULL &&
1414 gtk_widget_get_visible (priv->search_widget)) ||
1415 empathy_contact_group_get_expanded (name);
1417 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1418 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1419 * a hash table, and expand or contract them as appropriate all at once in
1420 * an idle handler which iterates over all the group rows. */
1421 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1422 &will_expand) == FALSE &&
1423 GPOINTER_TO_INT (will_expand) != should_expand)
1425 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1426 GINT_TO_POINTER (should_expand));
1428 if (priv->expand_groups_idle_handler == 0)
1430 priv->expand_groups_idle_handler =
1431 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1432 g_object_ref (view));
1439 /* FIXME: This is a workaround for bgo#621076 */
1441 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1444 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1445 GtkTreeModel *model;
1446 GtkTreePath *parent_path;
1447 GtkTreeIter parent_iter;
1449 if (gtk_tree_path_get_depth (path) < 2)
1452 /* A group row is visible if and only if at least one if its child is visible.
1453 * So when a row is inserted/deleted/changed in the base model, that could
1454 * modify the visibility of its parent in the filter model.
1457 model = GTK_TREE_MODEL (priv->store);
1458 parent_path = gtk_tree_path_copy (path);
1459 gtk_tree_path_up (parent_path);
1460 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1462 /* This tells the filter to verify the visibility of that row, and
1463 * show/hide it if necessary */
1464 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1465 parent_path, &parent_iter);
1467 gtk_tree_path_free (parent_path);
1471 individual_view_store_row_changed_cb (GtkTreeModel *model,
1474 EmpathyIndividualView *view)
1476 individual_view_verify_group_visibility (view, path);
1480 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1482 EmpathyIndividualView *view)
1484 individual_view_verify_group_visibility (view, path);
1488 individual_view_is_visible_individual (EmpathyIndividualView *self,
1489 FolksIndividual *individual)
1491 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1492 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1494 GList *personas, *l;
1496 /* We're only giving the visibility wrt filtering here, not things like
1498 if (live == NULL || gtk_widget_get_visible (GTK_WIDGET (live)) == FALSE)
1501 /* check alias name */
1502 str = folks_individual_get_alias (individual);
1504 if (empathy_live_search_match (live, str))
1507 /* check contact id, remove the @server.com part */
1508 personas = folks_individual_get_personas (individual);
1509 for (l = personas; l; l = l->next)
1512 gchar *dup_str = NULL;
1515 if (!TPF_IS_PERSONA (l->data))
1518 str = folks_persona_get_display_id (l->data);
1519 p = strstr (str, "@");
1521 str = dup_str = g_strndup (str, p - str);
1523 visible = empathy_live_search_match (live, str);
1529 /* FIXME: Add more rules here, we could check phone numbers in
1530 * contact's vCard for example. */
1536 individual_view_filter_visible_func (GtkTreeModel *model,
1540 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1541 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1542 FolksIndividual *individual = NULL;
1543 gboolean is_group, is_separator, valid;
1544 GtkTreeIter child_iter;
1545 gboolean visible, is_online;
1546 gboolean is_searching = TRUE;
1548 if (priv->search_widget == NULL ||
1549 !gtk_widget_get_visible (priv->search_widget))
1550 is_searching = FALSE;
1552 gtk_tree_model_get (model, iter,
1553 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1554 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1555 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1556 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1559 if (individual != NULL)
1561 if (is_searching == TRUE)
1562 visible = individual_view_is_visible_individual (self, individual);
1564 visible = (priv->show_offline || is_online);
1566 g_object_unref (individual);
1568 /* FIXME: Work around bgo#626552/bgo#621076 */
1569 if (visible == TRUE)
1571 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1572 individual_view_verify_group_visibility (self, path);
1573 gtk_tree_path_free (path);
1582 /* Not a contact, not a separator, must be a group */
1583 g_return_val_if_fail (is_group, FALSE);
1585 /* only show groups which are not empty */
1586 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1587 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1589 gtk_tree_model_get (model, &child_iter,
1590 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1591 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1594 if (individual == NULL)
1597 visible = individual_view_is_visible_individual (self, individual);
1598 g_object_unref (individual);
1600 /* show group if it has at least one visible contact in it */
1601 if ((is_searching && visible) ||
1602 (!is_searching && (priv->show_offline || is_online)))
1610 individual_view_constructed (GObject *object)
1612 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1613 GtkCellRenderer *cell;
1614 GtkTreeViewColumn *col;
1619 "headers-visible", FALSE,
1620 "show-expanders", FALSE,
1623 col = gtk_tree_view_column_new ();
1626 cell = gtk_cell_renderer_pixbuf_new ();
1627 gtk_tree_view_column_pack_start (col, cell, FALSE);
1628 gtk_tree_view_column_set_cell_data_func (col, cell,
1629 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1639 cell = gtk_cell_renderer_pixbuf_new ();
1640 gtk_tree_view_column_pack_start (col, cell, FALSE);
1641 gtk_tree_view_column_set_cell_data_func (col, cell,
1642 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1654 cell = empathy_cell_renderer_text_new ();
1655 gtk_tree_view_column_pack_start (col, cell, TRUE);
1656 gtk_tree_view_column_set_cell_data_func (col, cell,
1657 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1659 gtk_tree_view_column_add_attribute (col, cell,
1660 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1661 gtk_tree_view_column_add_attribute (col, cell,
1662 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1663 gtk_tree_view_column_add_attribute (col, cell,
1664 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1665 gtk_tree_view_column_add_attribute (col, cell,
1666 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1667 gtk_tree_view_column_add_attribute (col, cell,
1668 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1669 gtk_tree_view_column_add_attribute (col, cell,
1670 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1672 /* Audio Call Icon */
1673 cell = empathy_cell_renderer_activatable_new ();
1674 gtk_tree_view_column_pack_start (col, cell, FALSE);
1675 gtk_tree_view_column_set_cell_data_func (col, cell,
1676 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1679 g_object_set (cell, "visible", FALSE, NULL);
1681 g_signal_connect (cell, "path-activated",
1682 G_CALLBACK (individual_view_call_activated_cb), view);
1685 cell = gtk_cell_renderer_pixbuf_new ();
1686 gtk_tree_view_column_pack_start (col, cell, FALSE);
1687 gtk_tree_view_column_set_cell_data_func (col, cell,
1688 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1700 cell = empathy_cell_renderer_expander_new ();
1701 gtk_tree_view_column_pack_end (col, cell, FALSE);
1702 gtk_tree_view_column_set_cell_data_func (col, cell,
1703 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1706 /* Actually add the column now we have added all cell renderers */
1707 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1710 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1712 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1715 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1717 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1723 individual_view_set_view_features (EmpathyIndividualView *view,
1724 EmpathyIndividualFeatureFlags features)
1726 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1727 gboolean has_tooltip;
1729 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1731 priv->view_features = features;
1733 /* Setting reorderable is a hack that gets us row previews as drag icons
1734 for free. We override all the drag handlers. It's tricky to get the
1735 position of the drag icon right in drag_begin. GtkTreeView has special
1736 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1739 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1740 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG));
1742 /* Update DnD source/dest */
1743 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1745 gtk_drag_source_set (GTK_WIDGET (view),
1748 G_N_ELEMENTS (drag_types_source),
1749 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1753 gtk_drag_source_unset (GTK_WIDGET (view));
1757 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1759 gtk_drag_dest_set (GTK_WIDGET (view),
1760 GTK_DEST_DEFAULT_ALL,
1762 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1766 /* FIXME: URI could still be droped depending on FT feature */
1767 gtk_drag_dest_unset (GTK_WIDGET (view));
1770 /* Update has-tooltip */
1772 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1773 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1777 individual_view_dispose (GObject *object)
1779 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1780 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1782 tp_clear_object (&priv->store);
1783 tp_clear_object (&priv->filter);
1784 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1785 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1787 empathy_individual_view_set_live_search (view, NULL);
1789 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1793 individual_view_finalize (GObject *object)
1795 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1797 g_hash_table_destroy (priv->expand_groups);
1799 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1803 individual_view_get_property (GObject *object,
1808 EmpathyIndividualViewPriv *priv;
1810 priv = GET_PRIV (object);
1815 g_value_set_object (value, priv->store);
1817 case PROP_VIEW_FEATURES:
1818 g_value_set_flags (value, priv->view_features);
1820 case PROP_INDIVIDUAL_FEATURES:
1821 g_value_set_flags (value, priv->individual_features);
1823 case PROP_SHOW_OFFLINE:
1824 g_value_set_boolean (value, priv->show_offline);
1827 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1833 individual_view_set_property (GObject *object,
1835 const GValue *value,
1838 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1839 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1844 empathy_individual_view_set_store (view, g_value_get_object (value));
1846 case PROP_VIEW_FEATURES:
1847 individual_view_set_view_features (view, g_value_get_flags (value));
1849 case PROP_INDIVIDUAL_FEATURES:
1850 priv->individual_features = g_value_get_flags (value);
1852 case PROP_SHOW_OFFLINE:
1853 empathy_individual_view_set_show_offline (view,
1854 g_value_get_boolean (value));
1857 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1863 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1865 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1866 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1867 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1869 object_class->constructed = individual_view_constructed;
1870 object_class->dispose = individual_view_dispose;
1871 object_class->finalize = individual_view_finalize;
1872 object_class->get_property = individual_view_get_property;
1873 object_class->set_property = individual_view_set_property;
1875 widget_class->drag_data_received = individual_view_drag_data_received;
1876 widget_class->drag_drop = individual_view_drag_drop;
1877 widget_class->drag_begin = individual_view_drag_begin;
1878 widget_class->drag_data_get = individual_view_drag_data_get;
1879 widget_class->drag_end = individual_view_drag_end;
1880 widget_class->drag_motion = individual_view_drag_motion;
1882 /* We use the class method to let user of this widget to connect to
1883 * the signal and stop emission of the signal so the default handler
1884 * won't be called. */
1885 tree_view_class->row_activated = individual_view_row_activated;
1887 signals[DRAG_CONTACT_RECEIVED] =
1888 g_signal_new ("drag-contact-received",
1889 G_OBJECT_CLASS_TYPE (klass),
1893 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1894 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1896 g_object_class_install_property (object_class,
1898 g_param_spec_object ("store",
1899 "The store of the view",
1900 "The store of the view",
1901 EMPATHY_TYPE_INDIVIDUAL_STORE,
1902 G_PARAM_READWRITE));
1903 g_object_class_install_property (object_class,
1905 g_param_spec_flags ("view-features",
1906 "Features of the view",
1907 "Flags for all enabled features",
1908 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1909 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1910 g_object_class_install_property (object_class,
1911 PROP_INDIVIDUAL_FEATURES,
1912 g_param_spec_flags ("individual-features",
1913 "Features of the contact menu",
1914 "Flags for all enabled features for the menu",
1915 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1916 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1917 g_object_class_install_property (object_class,
1919 g_param_spec_boolean ("show-offline",
1921 "Whether contact list should display "
1922 "offline contacts", FALSE, G_PARAM_READWRITE));
1924 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1928 empathy_individual_view_init (EmpathyIndividualView *view)
1930 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1931 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1934 /* Get saved group states. */
1935 empathy_contact_groups_get_all ();
1937 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
1938 (GDestroyNotify) g_free, NULL);
1940 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1941 empathy_individual_store_row_separator_func, NULL, NULL);
1943 /* Set up drag target lists. */
1944 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1945 G_N_ELEMENTS (drag_types_dest_file));
1947 /* Connect to tree view signals rather than override. */
1948 g_signal_connect (view, "button-press-event",
1949 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1950 g_signal_connect (view, "key-press-event",
1951 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1952 g_signal_connect (view, "row-expanded",
1953 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1954 GINT_TO_POINTER (TRUE));
1955 g_signal_connect (view, "row-collapsed",
1956 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1957 GINT_TO_POINTER (FALSE));
1958 g_signal_connect (view, "query-tooltip",
1959 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1962 EmpathyIndividualView *
1963 empathy_individual_view_new (EmpathyIndividualStore *store,
1964 EmpathyIndividualViewFeatureFlags view_features,
1965 EmpathyIndividualFeatureFlags individual_features)
1967 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1969 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1971 "individual-features", individual_features,
1972 "view-features", view_features, NULL);
1976 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1978 EmpathyIndividualViewPriv *priv;
1979 GtkTreeSelection *selection;
1981 GtkTreeModel *model;
1982 FolksIndividual *individual;
1984 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1986 priv = GET_PRIV (view);
1988 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1989 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1992 gtk_tree_model_get (model, &iter,
1993 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1998 EmpathyIndividualManagerFlags
1999 empathy_individual_view_get_flags (EmpathyIndividualView *view)
2001 EmpathyIndividualViewPriv *priv;
2002 GtkTreeSelection *selection;
2004 GtkTreeModel *model;
2005 EmpathyIndividualFeatureFlags flags;
2007 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
2009 priv = GET_PRIV (view);
2011 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2012 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2015 gtk_tree_model_get (model, &iter,
2016 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2022 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2023 gboolean *is_fake_group)
2025 EmpathyIndividualViewPriv *priv;
2026 GtkTreeSelection *selection;
2028 GtkTreeModel *model;
2033 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2035 priv = GET_PRIV (view);
2037 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2038 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2041 gtk_tree_model_get (model, &iter,
2042 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2043 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2044 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2052 if (is_fake_group != NULL)
2053 *is_fake_group = fake;
2059 individual_view_remove_dialog_show (GtkWindow *parent,
2060 const gchar *message,
2061 const gchar *secondary_text)
2066 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2067 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2068 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2069 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2070 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2071 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2072 "%s", secondary_text);
2074 gtk_widget_show (dialog);
2076 res = gtk_dialog_run (GTK_DIALOG (dialog));
2077 gtk_widget_destroy (dialog);
2079 return (res == GTK_RESPONSE_YES);
2083 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2084 EmpathyIndividualView *view)
2088 group = empathy_individual_view_get_selected_group (view, NULL);
2095 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2097 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2098 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2101 EmpathyIndividualManager *manager =
2102 empathy_individual_manager_dup_singleton ();
2103 empathy_individual_manager_remove_group (manager, group);
2104 g_object_unref (G_OBJECT (manager));
2114 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2116 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2121 gboolean is_fake_group;
2123 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2125 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2126 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2129 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2130 if (!group || is_fake_group)
2132 /* We can't alter fake groups */
2136 menu = gtk_menu_new ();
2139 if (priv->view_features &
2140 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2141 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2142 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2143 gtk_widget_show (item);
2144 g_signal_connect (item, "activate",
2145 G_CALLBACK (individual_view_group_rename_activate_cb),
2150 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2152 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2153 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2154 GTK_ICON_SIZE_MENU);
2155 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2156 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2157 gtk_widget_show (item);
2158 g_signal_connect (item, "activate",
2159 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2168 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2169 EmpathyIndividualView *view)
2171 FolksIndividual *individual;
2173 individual = empathy_individual_view_dup_selected (view);
2175 if (individual != NULL)
2180 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2183 ("Do you really want to remove the contact '%s'?"),
2184 folks_individual_get_alias (individual));
2185 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2188 EmpathyIndividualManager *manager;
2190 manager = empathy_individual_manager_dup_singleton ();
2191 empathy_individual_manager_remove (manager, individual, "");
2192 g_object_unref (G_OBJECT (manager));
2196 g_object_unref (individual);
2201 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2203 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2204 FolksIndividual *individual;
2205 GtkWidget *menu = NULL;
2208 EmpathyIndividualManagerFlags flags;
2210 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2212 individual = empathy_individual_view_dup_selected (view);
2213 if (individual == NULL)
2216 flags = empathy_individual_view_get_flags (view);
2218 menu = empathy_individual_menu_new (individual, priv->individual_features);
2220 /* Remove contact */
2221 if (priv->view_features &
2222 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2223 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2226 /* create the menu if required, or just add a separator */
2228 menu = gtk_menu_new ();
2231 item = gtk_separator_menu_item_new ();
2232 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2233 gtk_widget_show (item);
2237 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2238 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2239 GTK_ICON_SIZE_MENU);
2240 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2241 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2242 gtk_widget_show (item);
2243 g_signal_connect (item, "activate",
2244 G_CALLBACK (individual_view_remove_activate_cb), view);
2247 g_object_unref (individual);
2253 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2254 EmpathyLiveSearch *search)
2256 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2258 /* remove old handlers if old search was not null */
2259 if (priv->search_widget != NULL)
2261 g_signal_handlers_disconnect_by_func (view,
2262 individual_view_start_search_cb, NULL);
2264 g_signal_handlers_disconnect_by_func (priv->search_widget,
2265 individual_view_search_text_notify_cb, view);
2266 g_signal_handlers_disconnect_by_func (priv->search_widget,
2267 individual_view_search_activate_cb, view);
2268 g_signal_handlers_disconnect_by_func (priv->search_widget,
2269 individual_view_search_key_navigation_cb, view);
2270 g_signal_handlers_disconnect_by_func (priv->search_widget,
2271 individual_view_search_hide_cb, view);
2272 g_signal_handlers_disconnect_by_func (priv->search_widget,
2273 individual_view_search_show_cb, view);
2274 g_object_unref (priv->search_widget);
2275 priv->search_widget = NULL;
2278 /* connect handlers if new search is not null */
2281 priv->search_widget = g_object_ref (search);
2283 g_signal_connect (view, "start-interactive-search",
2284 G_CALLBACK (individual_view_start_search_cb), NULL);
2286 g_signal_connect (priv->search_widget, "notify::text",
2287 G_CALLBACK (individual_view_search_text_notify_cb), view);
2288 g_signal_connect (priv->search_widget, "activate",
2289 G_CALLBACK (individual_view_search_activate_cb), view);
2290 g_signal_connect (priv->search_widget, "key-navigation",
2291 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2292 g_signal_connect (priv->search_widget, "hide",
2293 G_CALLBACK (individual_view_search_hide_cb), view);
2294 g_signal_connect (priv->search_widget, "show",
2295 G_CALLBACK (individual_view_search_show_cb), view);
2300 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2302 EmpathyIndividualViewPriv *priv;
2304 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2306 priv = GET_PRIV (self);
2308 return (priv->search_widget != NULL &&
2309 gtk_widget_get_visible (priv->search_widget));
2313 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2315 EmpathyIndividualViewPriv *priv;
2317 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2319 priv = GET_PRIV (self);
2321 return priv->show_offline;
2325 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2326 gboolean show_offline)
2328 EmpathyIndividualViewPriv *priv;
2330 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2332 priv = GET_PRIV (self);
2334 priv->show_offline = show_offline;
2336 g_object_notify (G_OBJECT (self), "show-offline");
2337 gtk_tree_model_filter_refilter (priv->filter);
2340 EmpathyIndividualStore *
2341 empathy_individual_view_get_store (EmpathyIndividualView *self)
2343 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2345 return GET_PRIV (self)->store;
2349 empathy_individual_view_set_store (EmpathyIndividualView *self,
2350 EmpathyIndividualStore *store)
2352 EmpathyIndividualViewPriv *priv;
2354 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2355 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2357 priv = GET_PRIV (self);
2359 /* Destroy the old filter and remove the old store */
2360 if (priv->store != NULL)
2362 g_signal_handlers_disconnect_by_func (priv->store,
2363 individual_view_store_row_changed_cb, self);
2364 g_signal_handlers_disconnect_by_func (priv->store,
2365 individual_view_store_row_deleted_cb, self);
2367 g_signal_handlers_disconnect_by_func (priv->filter,
2368 individual_view_row_has_child_toggled_cb, self);
2370 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2373 tp_clear_object (&priv->filter);
2374 tp_clear_object (&priv->store);
2376 /* Set the new store */
2377 priv->store = store;
2381 g_object_ref (store);
2383 /* Create a new filter */
2384 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2385 GTK_TREE_MODEL (priv->store), NULL));
2386 gtk_tree_model_filter_set_visible_func (priv->filter,
2387 individual_view_filter_visible_func, self, NULL);
2389 g_signal_connect (priv->filter, "row-has-child-toggled",
2390 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2391 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2392 GTK_TREE_MODEL (priv->filter));
2394 tp_g_signal_connect_object (priv->store, "row-changed",
2395 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2396 tp_g_signal_connect_object (priv->store, "row-inserted",
2397 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2398 tp_g_signal_connect_object (priv->store, "row-deleted",
2399 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);