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 <folks/folks.h>
36 #include <telepathy-glib/account-manager.h>
37 #include <telepathy-glib/util.h>
39 #include <libempathy/empathy-call-factory.h>
40 #include <libempathy/empathy-individual-manager.h>
41 #include <libempathy/empathy-contact-groups.h>
42 #include <libempathy/empathy-dispatcher.h>
43 #include <libempathy/empathy-utils.h>
45 #include "empathy-individual-view.h"
46 #include "empathy-individual-menu.h"
47 #include "empathy-individual-store.h"
48 #include "empathy-images.h"
49 #include "empathy-cell-renderer-expander.h"
50 #include "empathy-cell-renderer-text.h"
51 #include "empathy-cell-renderer-activatable.h"
52 #include "empathy-ui-utils.h"
53 #include "empathy-gtk-enum-types.h"
54 #include "empathy-gtk-marshal.h"
56 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
57 #include <libempathy/empathy-debug.h>
59 /* Active users are those which have recently changed state
60 * (e.g. online, offline or from normal to a busy state).
63 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
66 EmpathyIndividualStore *store;
67 GtkTreeRowReference *drag_row;
68 EmpathyIndividualViewFeatureFlags view_features;
69 EmpathyContactFeatureFlags individual_features;
70 GtkWidget *tooltip_widget;
71 GtkTargetList *file_targets;
73 GtkTreeModelFilter *filter;
74 GtkWidget *search_widget;
75 } EmpathyIndividualViewPriv;
79 EmpathyIndividualView *view;
86 EmpathyIndividualView *view;
87 FolksIndividual *individual;
96 PROP_INDIVIDUAL_FEATURES,
99 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
100 * specific EmpathyContacts (between/in/out of Individuals) */
103 DND_DRAG_TYPE_INDIVIDUAL_ID,
104 DND_DRAG_TYPE_URI_LIST,
105 DND_DRAG_TYPE_STRING,
108 static const GtkTargetEntry drag_types_dest[] = {
109 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
110 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
111 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
112 {"text/plain", 0, DND_DRAG_TYPE_STRING},
113 {"STRING", 0, DND_DRAG_TYPE_STRING},
116 static const GtkTargetEntry drag_types_dest_file[] = {
117 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
118 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
121 static const GtkTargetEntry drag_types_source[] = {
122 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
125 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
126 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
130 DRAG_CONTACT_RECEIVED,
134 static guint signals[LAST_SIGNAL];
136 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
140 individual_view_is_visible_individual (EmpathyIndividualView *self,
141 FolksIndividual *individual)
143 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
144 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
148 g_assert (live != NULL);
150 /* check alias name */
151 str = folks_individual_get_alias (individual);
152 if (empathy_live_search_match (live, str))
155 /* check contact id, remove the @server.com part */
156 personas = folks_individual_get_personas (individual);
157 for (l = personas; l; l = l->next)
160 gchar *dup_str = NULL;
163 str = folks_persona_get_uid (l->data);
164 p = strstr (str, "@");
166 str = dup_str = g_strndup (str, p - str);
168 visible = empathy_live_search_match (live, str);
174 /* FIXME: Add more rules here, we could check phone numbers in
175 * contact's vCard for example. */
181 individual_view_filter_visible_func (GtkTreeModel *model,
185 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
186 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
187 FolksIndividual *individual = NULL;
188 gboolean is_group, is_separator, valid;
189 GtkTreeIter child_iter;
192 if (priv->search_widget == NULL ||
193 !gtk_widget_get_visible (priv->search_widget))
196 gtk_tree_model_get (model, iter,
197 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
198 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
199 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
202 if (individual != NULL)
204 visible = individual_view_is_visible_individual (self, individual);
205 g_object_unref (individual);
212 /* Not a contact, not a separator, must be a group */
213 g_return_val_if_fail (is_group, FALSE);
215 /* only show groups which are not empty */
216 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
217 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
219 gtk_tree_model_get (model, &child_iter,
220 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
223 if (individual == NULL)
226 visible = individual_view_is_visible_individual (self, individual);
227 g_object_unref (individual);
229 /* show group if it has at least one visible contact in it */
238 individual_view_tooltip_destroy_cb (GtkWidget *widget,
239 EmpathyIndividualView *view)
241 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
243 if (priv->tooltip_widget != NULL)
245 DEBUG ("Tooltip destroyed");
246 tp_clear_object (&priv->tooltip_widget);
251 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
254 gboolean keyboard_mode,
258 EmpathyIndividualViewPriv *priv;
259 FolksIndividual *individual;
263 static gint running = 0;
264 gboolean ret = FALSE;
265 EmpathyContact *contact;
267 priv = GET_PRIV (view);
269 /* Avoid an infinite loop. See GNOME bug #574377 */
275 /* Don't show the tooltip if there's already a popup menu */
276 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
279 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
280 keyboard_mode, &model, &path, &iter))
283 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
284 gtk_tree_path_free (path);
286 gtk_tree_model_get (model, &iter,
287 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
289 if (individual == NULL)
292 contact = empathy_contact_dup_from_folks_individual (individual);
293 g_object_unref (individual);
298 if (priv->tooltip_widget == NULL)
300 priv->tooltip_widget = empathy_contact_widget_new (contact,
301 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
302 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
303 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
304 g_object_ref (priv->tooltip_widget);
305 g_signal_connect (priv->tooltip_widget, "destroy",
306 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
307 gtk_widget_show (priv->tooltip_widget);
310 empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
312 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
315 g_object_unref (contact);
323 groups_change_group_cb (GObject *source,
324 GAsyncResult *result,
327 FolksGroups *groups = FOLKS_GROUPS (source);
328 GError *error = NULL;
330 folks_groups_change_group_finish (groups, result, &error);
333 g_warning ("failed to change group: %s", error->message);
334 g_clear_error (&error);
339 individual_view_handle_drag (EmpathyIndividualView *self,
340 FolksIndividual *individual,
341 const gchar *old_group,
342 const gchar *new_group,
343 GdkDragAction action)
345 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
346 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
348 DEBUG ("individual %s dragged from '%s' to '%s'",
349 folks_individual_get_id (individual), old_group, new_group);
351 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
353 /* Mark contact as favourite */
354 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
358 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
360 /* Remove contact as favourite */
361 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
363 /* Don't try to remove it */
367 if (new_group != NULL)
368 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
369 groups_change_group_cb, NULL);
371 if (old_group != NULL && action == GDK_ACTION_MOVE)
372 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE,
373 groups_change_group_cb, NULL);
377 group_can_be_modified (const gchar *name,
378 gboolean is_fake_group,
381 /* Real groups can always be modified */
385 /* The favorite fake group can be modified so users can
386 * add/remove favorites using DnD */
387 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
390 /* We can remove contacts from the 'ungrouped' fake group */
391 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
398 individual_view_contact_drag_received (GtkWidget *self,
399 GdkDragContext *context,
402 GtkSelectionData *selection)
404 EmpathyIndividualViewPriv *priv;
405 EmpathyIndividualManager *manager;
406 FolksIndividual *individual;
407 GtkTreePath *source_path;
408 const gchar *sel_data;
409 gchar *new_group = NULL;
410 gchar *old_group = NULL;
411 gboolean new_group_is_fake, old_group_is_fake = TRUE;
413 priv = GET_PRIV (self);
415 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
416 new_group = empathy_individual_store_get_parent_group (model, path,
417 NULL, &new_group_is_fake);
419 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
422 /* Get source group information. */
425 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
429 empathy_individual_store_get_parent_group (model, source_path,
430 NULL, &old_group_is_fake);
431 gtk_tree_path_free (source_path);
435 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
438 if (!tp_strdiff (old_group, new_group))
445 /* XXX: for contacts, we used to ensure the account, create the contact
446 * factory, and then wait on the contacts. But they should already be
447 * created by this point */
449 manager = empathy_individual_manager_dup_singleton ();
450 individual = empathy_individual_manager_lookup_member (manager, sel_data);
452 if (individual == NULL)
454 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
456 g_object_unref (manager);
461 /* FIXME: We should probably wait for the cb before calling
464 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
465 old_group, new_group, gdk_drag_context_get_selected_action (context));
467 g_object_unref (G_OBJECT (manager));
475 individual_view_file_drag_received (GtkWidget *view,
476 GdkDragContext *context,
479 GtkSelectionData *selection)
482 const gchar *sel_data;
483 FolksIndividual *individual;
484 EmpathyContact *contact;
486 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
488 gtk_tree_model_get_iter (model, &iter, path);
489 gtk_tree_model_get (model, &iter,
490 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
491 if (individual == NULL)
494 contact = empathy_contact_dup_from_folks_individual (individual);
495 empathy_send_file_from_uri_list (contact, sel_data);
497 g_object_unref (individual);
498 tp_clear_object (&contact);
504 individual_view_drag_data_received (GtkWidget *view,
505 GdkDragContext *context,
508 GtkSelectionData *selection,
514 GtkTreeViewDropPosition position;
516 gboolean success = TRUE;
518 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
520 /* Get destination group information. */
521 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
522 x, y, &path, &position);
527 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
528 || info == DND_DRAG_TYPE_STRING)
530 success = individual_view_contact_drag_received (view,
531 context, model, path, selection);
533 else if (info == DND_DRAG_TYPE_URI_LIST)
535 success = individual_view_file_drag_received (view,
536 context, model, path, selection);
539 gtk_tree_path_free (path);
540 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
544 individual_view_drag_motion_cb (DragMotionData *data)
546 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
548 data->timeout_id = 0;
554 individual_view_drag_motion (GtkWidget *widget,
555 GdkDragContext *context,
560 EmpathyIndividualViewPriv *priv;
564 static DragMotionData *dm = NULL;
567 gboolean is_different = FALSE;
568 gboolean cleanup = TRUE;
569 gboolean retval = TRUE;
571 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
572 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
574 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
575 x, y, &path, NULL, NULL, NULL);
577 cleanup &= (dm == NULL);
581 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
582 is_different = ((dm == NULL) || ((dm != NULL)
583 && gtk_tree_path_compare (dm->path, path) != 0));
590 /* Coordinates don't point to an actual row, so make sure the pointer
591 and highlighting don't indicate that a drag is possible.
593 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
594 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
597 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
598 gtk_tree_model_get_iter (model, &iter, path);
600 if (target == GDK_NONE)
602 /* If target == GDK_NONE, then we don't have a target that can be
603 dropped on a contact. This means a contact drag. If we're
604 pointing to a group, highlight it. Otherwise, if the contact
605 we're pointing to is in a group, highlight that. Otherwise,
606 set the drag position to before the first row for a drag into
607 the "non-group" at the top.
609 GtkTreeIter group_iter;
611 GtkTreePath *group_path;
612 gtk_tree_model_get (model, &iter,
613 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
620 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
621 gtk_tree_model_get (model, &group_iter,
622 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
626 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
627 group_path = gtk_tree_model_get_path (model, &group_iter);
628 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
629 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
630 gtk_tree_path_free (group_path);
634 group_path = gtk_tree_path_new_first ();
635 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
636 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
637 group_path, GTK_TREE_VIEW_DROP_BEFORE);
642 /* This is a file drag, and it can only be dropped on contacts,
645 FolksIndividual *individual;
646 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
648 gtk_tree_model_get (model, &iter,
649 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
650 if (individual != NULL)
652 EmpathyContact *contact = NULL;
654 contact = empathy_contact_dup_from_folks_individual (individual);
655 caps = empathy_contact_get_capabilities (contact);
657 tp_clear_object (&contact);
660 if (individual != NULL &&
661 folks_individual_is_online (individual) &&
662 (caps & EMPATHY_CAPABILITIES_FT))
664 gdk_drag_status (context, GDK_ACTION_COPY, time_);
665 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
666 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
670 gdk_drag_status (context, 0, time_);
671 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
675 if (individual != NULL)
676 g_object_unref (individual);
679 if (!is_different && !cleanup)
684 gtk_tree_path_free (dm->path);
687 g_source_remove (dm->timeout_id);
695 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
697 dm = g_new0 (DragMotionData, 1);
699 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
700 dm->path = gtk_tree_path_copy (path);
702 dm->timeout_id = g_timeout_add_seconds (1,
703 (GSourceFunc) individual_view_drag_motion_cb, dm);
710 individual_view_drag_begin (GtkWidget *widget,
711 GdkDragContext *context)
713 EmpathyIndividualViewPriv *priv;
714 GtkTreeSelection *selection;
719 priv = GET_PRIV (widget);
721 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
724 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
725 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
728 path = gtk_tree_model_get_path (model, &iter);
729 priv->drag_row = gtk_tree_row_reference_new (model, path);
730 gtk_tree_path_free (path);
734 individual_view_drag_data_get (GtkWidget *widget,
735 GdkDragContext *context,
736 GtkSelectionData *selection,
740 EmpathyIndividualViewPriv *priv;
741 GtkTreePath *src_path;
744 FolksIndividual *individual;
745 const gchar *individual_id;
747 priv = GET_PRIV (widget);
749 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
750 if (priv->drag_row == NULL)
753 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
754 if (src_path == NULL)
757 if (!gtk_tree_model_get_iter (model, &iter, src_path))
759 gtk_tree_path_free (src_path);
763 gtk_tree_path_free (src_path);
766 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
767 if (individual == NULL)
770 individual_id = folks_individual_get_id (individual);
774 case DND_DRAG_TYPE_INDIVIDUAL_ID:
775 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
776 (guchar *) individual_id, strlen (individual_id) + 1);
780 g_object_unref (individual);
784 individual_view_drag_end (GtkWidget *widget,
785 GdkDragContext *context)
787 EmpathyIndividualViewPriv *priv;
789 priv = GET_PRIV (widget);
791 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
796 gtk_tree_row_reference_free (priv->drag_row);
797 priv->drag_row = NULL;
802 individual_view_drag_drop (GtkWidget *widget,
803 GdkDragContext *drag_context,
813 EmpathyIndividualView *view;
819 individual_view_popup_menu_idle_cb (gpointer user_data)
821 MenuPopupData *data = user_data;
824 menu = empathy_individual_view_get_individual_menu (data->view);
826 menu = empathy_individual_view_get_group_menu (data->view);
830 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
831 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
833 gtk_widget_show (menu);
834 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
836 g_object_ref_sink (menu);
837 g_object_unref (menu);
840 g_slice_free (MenuPopupData, data);
846 individual_view_button_press_event_cb (EmpathyIndividualView *view,
847 GdkEventButton *event,
850 if (event->button == 3)
854 data = g_slice_new (MenuPopupData);
856 data->button = event->button;
857 data->time = event->time;
858 g_idle_add (individual_view_popup_menu_idle_cb, data);
865 individual_view_key_press_event_cb (EmpathyIndividualView *view,
869 if (event->keyval == GDK_Menu)
873 data = g_slice_new (MenuPopupData);
876 data->time = event->time;
877 g_idle_add (individual_view_popup_menu_idle_cb, data);
884 individual_view_row_activated (GtkTreeView *view,
886 GtkTreeViewColumn *column)
888 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
889 FolksIndividual *individual;
890 EmpathyContact *contact = NULL;
894 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
897 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
898 gtk_tree_model_get_iter (model, &iter, path);
899 gtk_tree_model_get (model, &iter,
900 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
902 if (individual == NULL)
905 contact = empathy_contact_dup_from_folks_individual (individual);
908 DEBUG ("Starting a chat");
910 empathy_dispatcher_chat_with_contact (contact,
911 gtk_get_current_event_time (), NULL, NULL);
914 g_object_unref (individual);
915 tp_clear_object (&contact);
919 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
920 const gchar *path_string,
921 EmpathyIndividualView *view)
926 FolksIndividual *individual;
927 GdkEventButton *event;
931 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
932 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
935 gtk_tree_model_get (model, &iter,
936 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
937 if (individual == NULL)
940 event = (GdkEventButton *) gtk_get_current_event ();
942 menu = gtk_menu_new ();
943 shell = GTK_MENU_SHELL (menu);
946 item = empathy_individual_audio_call_menu_item_new (individual);
947 gtk_menu_shell_append (shell, item);
948 gtk_widget_show (item);
951 item = empathy_individual_video_call_menu_item_new (individual);
952 gtk_menu_shell_append (shell, item);
953 gtk_widget_show (item);
955 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
956 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
957 gtk_widget_show (menu);
958 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
959 event->button, event->time);
960 g_object_ref_sink (menu);
961 g_object_unref (menu);
963 g_object_unref (individual);
967 individual_view_cell_set_background (EmpathyIndividualView *view,
968 GtkCellRenderer *cell,
975 style = gtk_widget_get_style (GTK_WIDGET (view));
977 if (!is_group && is_active)
979 color = style->bg[GTK_STATE_SELECTED];
981 /* Here we take the current theme colour and add it to
982 * the colour for white and average the two. This
983 * gives a colour which is inline with the theme but
986 color.red = (color.red + (style->white).red) / 2;
987 color.green = (color.green + (style->white).green) / 2;
988 color.blue = (color.blue + (style->white).blue) / 2;
990 g_object_set (cell, "cell-background-gdk", &color, NULL);
993 g_object_set (cell, "cell-background-gdk", NULL, NULL);
997 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
998 GtkCellRenderer *cell,
1001 EmpathyIndividualView *view)
1007 gtk_tree_model_get (model, iter,
1008 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1009 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1010 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1013 "visible", !is_group,
1017 tp_clear_object (&pixbuf);
1019 individual_view_cell_set_background (view, cell, is_group, is_active);
1023 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1024 GtkCellRenderer *cell,
1025 GtkTreeModel *model,
1027 EmpathyIndividualView *view)
1029 GdkPixbuf *pixbuf = NULL;
1033 gtk_tree_model_get (model, iter,
1034 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1035 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1040 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1042 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1043 GTK_ICON_SIZE_MENU);
1045 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1047 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1048 GTK_ICON_SIZE_MENU);
1053 "visible", pixbuf != NULL,
1057 tp_clear_object (&pixbuf);
1063 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1064 GtkCellRenderer *cell,
1065 GtkTreeModel *model,
1067 EmpathyIndividualView *view)
1071 gboolean can_audio, can_video;
1073 gtk_tree_model_get (model, iter,
1074 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1075 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1076 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1077 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1080 "visible", !is_group && (can_audio || can_video),
1081 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1084 individual_view_cell_set_background (view, cell, is_group, is_active);
1088 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1089 GtkCellRenderer *cell,
1090 GtkTreeModel *model,
1092 EmpathyIndividualView *view)
1095 gboolean show_avatar;
1099 gtk_tree_model_get (model, iter,
1100 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1101 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1102 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1103 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1106 "visible", !is_group && show_avatar,
1110 tp_clear_object (&pixbuf);
1112 individual_view_cell_set_background (view, cell, is_group, is_active);
1116 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1117 GtkCellRenderer *cell,
1118 GtkTreeModel *model,
1120 EmpathyIndividualView *view)
1125 gtk_tree_model_get (model, iter,
1126 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1127 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1129 individual_view_cell_set_background (view, cell, is_group, is_active);
1133 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1134 GtkCellRenderer *cell,
1135 GtkTreeModel *model,
1137 EmpathyIndividualView *view)
1142 gtk_tree_model_get (model, iter,
1143 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1144 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1146 if (gtk_tree_model_iter_has_child (model, iter))
1149 gboolean row_expanded;
1151 path = gtk_tree_model_get_path (model, iter);
1153 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1154 (gtk_tree_view_column_get_tree_view (column)), path);
1155 gtk_tree_path_free (path);
1160 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1164 g_object_set (cell, "visible", FALSE, NULL);
1166 individual_view_cell_set_background (view, cell, is_group, is_active);
1170 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1175 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1176 GtkTreeModel *model;
1180 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1183 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1185 gtk_tree_model_get (model, iter,
1186 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1188 expanded = GPOINTER_TO_INT (user_data);
1189 empathy_contact_group_set_expanded (name, expanded);
1195 individual_view_start_search_cb (EmpathyIndividualView *view,
1198 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1200 if (priv->search_widget == NULL)
1203 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1204 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1206 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1212 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1214 EmpathyIndividualView *view)
1216 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1218 GtkTreeViewColumn *focus_column;
1219 GtkTreeModel *model;
1221 gboolean set_cursor = FALSE;
1223 gtk_tree_model_filter_refilter (priv->filter);
1225 /* Set cursor on the first contact. If it is already set on a group,
1226 * set it on its first child contact. Note that first child of a group
1227 * is its separator, that's why we actually set to the 2nd
1230 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1231 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1235 path = gtk_tree_path_new_from_string ("0:1");
1238 else if (gtk_tree_path_get_depth (path) < 2)
1242 gtk_tree_model_get_iter (model, &iter, path);
1243 gtk_tree_model_get (model, &iter,
1244 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1249 gtk_tree_path_down (path);
1250 gtk_tree_path_next (path);
1257 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1259 if (gtk_tree_model_get_iter (model, &iter, path))
1261 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1266 gtk_tree_path_free (path);
1270 individual_view_search_activate_cb (GtkWidget *search,
1271 EmpathyIndividualView *view)
1274 GtkTreeViewColumn *focus_column;
1276 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1279 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1280 gtk_tree_path_free (path);
1282 gtk_widget_hide (search);
1287 individual_view_search_key_navigation_cb (GtkWidget *search,
1289 EmpathyIndividualView *view)
1291 GdkEventKey *eventkey = ((GdkEventKey *) event);
1292 gboolean ret = FALSE;
1294 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1296 GdkEvent *new_event;
1298 new_event = gdk_event_copy (event);
1299 gtk_widget_grab_focus (GTK_WIDGET (view));
1300 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1301 gtk_widget_grab_focus (search);
1303 gdk_event_free (new_event);
1310 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1311 EmpathyIndividualView *view)
1313 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1314 GtkTreeModel *model;
1315 GtkTreePath *cursor_path;
1317 gboolean valid = FALSE;
1319 /* block expand or collapse handlers, they would write the
1320 * expand or collapsed setting to file otherwise */
1321 g_signal_handlers_block_by_func (view,
1322 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1323 g_signal_handlers_block_by_func (view,
1324 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1326 /* restore which groups are expanded and which are not */
1327 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1328 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1329 valid; valid = gtk_tree_model_iter_next (model, &iter))
1335 gtk_tree_model_get (model, &iter,
1336 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1337 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1346 path = gtk_tree_model_get_path (model, &iter);
1347 if ((priv->view_features &
1348 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1349 empathy_contact_group_get_expanded (name))
1351 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1355 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1358 gtk_tree_path_free (path);
1362 /* unblock expand or collapse handlers */
1363 g_signal_handlers_unblock_by_func (view,
1364 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1365 g_signal_handlers_unblock_by_func (view,
1366 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1368 /* keep the selected contact visible */
1369 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1371 if (cursor_path != NULL)
1372 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1375 gtk_tree_path_free (cursor_path);
1379 individual_view_search_show_cb (EmpathyLiveSearch *search,
1380 EmpathyIndividualView *view)
1382 /* block expand or collapse handlers during expand all, they would
1383 * write the expand or collapsed setting to file otherwise */
1384 g_signal_handlers_block_by_func (view,
1385 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1387 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1389 g_signal_handlers_unblock_by_func (view,
1390 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1394 EmpathyIndividualView *view;
1395 GtkTreeRowReference *row_ref;
1400 individual_view_expand_idle_cb (gpointer user_data)
1402 ExpandData *data = user_data;
1405 path = gtk_tree_row_reference_get_path (data->row_ref);
1409 g_signal_handlers_block_by_func (data->view,
1410 individual_view_row_expand_or_collapse_cb,
1411 GINT_TO_POINTER (data->expand));
1414 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1416 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1418 gtk_tree_path_free (path);
1420 g_signal_handlers_unblock_by_func (data->view,
1421 individual_view_row_expand_or_collapse_cb,
1422 GINT_TO_POINTER (data->expand));
1425 g_object_unref (data->view);
1426 gtk_tree_row_reference_free (data->row_ref);
1427 g_slice_free (ExpandData, data);
1433 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1436 EmpathyIndividualView *view)
1438 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1439 gboolean is_group = FALSE;
1443 gtk_tree_model_get (model, iter,
1444 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1445 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1448 if (!is_group || EMP_STR_EMPTY (name))
1454 data = g_slice_new0 (ExpandData);
1455 data->view = g_object_ref (view);
1456 data->row_ref = gtk_tree_row_reference_new (model, path);
1458 (priv->view_features &
1459 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1460 (priv->search_widget != NULL &&
1461 gtk_widget_get_visible (priv->search_widget)) ||
1462 empathy_contact_group_get_expanded (name);
1464 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1465 * gtk_tree_model_filter_refilter () */
1466 g_idle_add (individual_view_expand_idle_cb, data);
1472 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1475 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1476 GtkTreeModel *model;
1477 GtkTreePath *parent_path;
1478 GtkTreeIter parent_iter;
1480 if (gtk_tree_path_get_depth (path) < 2)
1483 /* A group row is visible if and only if at least one if its child is visible.
1484 * So when a row is inserted/deleted/changed in the base model, that could
1485 * modify the visibility of its parent in the filter model.
1488 model = GTK_TREE_MODEL (priv->store);
1489 parent_path = gtk_tree_path_copy (path);
1490 gtk_tree_path_up (parent_path);
1491 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1493 /* This tells the filter to verify the visibility of that row, and
1494 * show/hide it if necessary */
1495 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1496 parent_path, &parent_iter);
1498 gtk_tree_path_free (parent_path);
1502 individual_view_store_row_changed_cb (GtkTreeModel *model,
1505 EmpathyIndividualView *view)
1507 individual_view_verify_group_visibility (view, path);
1511 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1513 EmpathyIndividualView *view)
1515 individual_view_verify_group_visibility (view, path);
1519 individual_view_constructed (GObject *object)
1521 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1522 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1523 GtkCellRenderer *cell;
1524 GtkTreeViewColumn *col;
1527 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1528 GTK_TREE_MODEL (priv->store), NULL));
1529 gtk_tree_model_filter_set_visible_func (priv->filter,
1530 individual_view_filter_visible_func, view, NULL);
1532 g_signal_connect (priv->filter, "row-has-child-toggled",
1533 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1534 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1535 GTK_TREE_MODEL (priv->filter));
1537 tp_g_signal_connect_object (priv->store, "row-changed",
1538 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1539 tp_g_signal_connect_object (priv->store, "row-inserted",
1540 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1541 tp_g_signal_connect_object (priv->store, "row-deleted",
1542 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1545 /* Setting reorderable is a hack that gets us row previews as drag icons
1546 for free. We override all the drag handlers. It's tricky to get the
1547 position of the drag icon right in drag_begin. GtkTreeView has special
1548 voodoo for it, so we let it do the voodoo that he do.
1551 "headers-visible", FALSE,
1552 "reorderable", TRUE,
1553 "show-expanders", FALSE,
1556 col = gtk_tree_view_column_new ();
1559 cell = gtk_cell_renderer_pixbuf_new ();
1560 gtk_tree_view_column_pack_start (col, cell, FALSE);
1561 gtk_tree_view_column_set_cell_data_func (col, cell,
1562 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1572 cell = gtk_cell_renderer_pixbuf_new ();
1573 gtk_tree_view_column_pack_start (col, cell, FALSE);
1574 gtk_tree_view_column_set_cell_data_func (col, cell,
1575 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1587 cell = empathy_cell_renderer_text_new ();
1588 gtk_tree_view_column_pack_start (col, cell, TRUE);
1589 gtk_tree_view_column_set_cell_data_func (col, cell,
1590 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1592 gtk_tree_view_column_add_attribute (col, cell,
1593 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1594 gtk_tree_view_column_add_attribute (col, cell,
1595 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1596 gtk_tree_view_column_add_attribute (col, cell,
1597 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1598 gtk_tree_view_column_add_attribute (col, cell,
1599 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1600 gtk_tree_view_column_add_attribute (col, cell,
1601 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1602 gtk_tree_view_column_add_attribute (col, cell,
1603 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1605 /* Audio Call Icon */
1606 cell = empathy_cell_renderer_activatable_new ();
1607 gtk_tree_view_column_pack_start (col, cell, FALSE);
1608 gtk_tree_view_column_set_cell_data_func (col, cell,
1609 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1612 g_object_set (cell, "visible", FALSE, NULL);
1614 g_signal_connect (cell, "path-activated",
1615 G_CALLBACK (individual_view_call_activated_cb), view);
1618 cell = gtk_cell_renderer_pixbuf_new ();
1619 gtk_tree_view_column_pack_start (col, cell, FALSE);
1620 gtk_tree_view_column_set_cell_data_func (col, cell,
1621 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1633 cell = empathy_cell_renderer_expander_new ();
1634 gtk_tree_view_column_pack_end (col, cell, FALSE);
1635 gtk_tree_view_column_set_cell_data_func (col, cell,
1636 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1639 /* Actually add the column now we have added all cell renderers */
1640 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1643 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1645 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1648 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1650 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1656 individual_view_set_view_features (EmpathyIndividualView *view,
1657 EmpathyIndividualFeatureFlags features)
1659 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1660 gboolean has_tooltip;
1662 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1664 priv->view_features = features;
1666 /* Update DnD source/dest */
1667 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1669 gtk_drag_source_set (GTK_WIDGET (view),
1672 G_N_ELEMENTS (drag_types_source),
1673 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1677 gtk_drag_source_unset (GTK_WIDGET (view));
1681 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1683 gtk_drag_dest_set (GTK_WIDGET (view),
1684 GTK_DEST_DEFAULT_ALL,
1686 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1690 /* FIXME: URI could still be droped depending on FT feature */
1691 gtk_drag_dest_unset (GTK_WIDGET (view));
1694 /* Update has-tooltip */
1696 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1697 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1701 individual_view_dispose (GObject *object)
1703 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1704 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1706 tp_clear_object (&priv->store);
1707 tp_clear_object (&priv->filter);
1708 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1709 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1711 empathy_individual_view_set_live_search (view, NULL);
1713 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1717 individual_view_get_property (GObject *object,
1722 EmpathyIndividualViewPriv *priv;
1724 priv = GET_PRIV (object);
1729 g_value_set_object (value, priv->store);
1731 case PROP_VIEW_FEATURES:
1732 g_value_set_flags (value, priv->view_features);
1734 case PROP_INDIVIDUAL_FEATURES:
1735 g_value_set_flags (value, priv->individual_features);
1738 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1744 individual_view_set_property (GObject *object,
1746 const GValue *value,
1749 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1750 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1755 priv->store = g_value_dup_object (value);
1757 case PROP_VIEW_FEATURES:
1758 individual_view_set_view_features (view, g_value_get_flags (value));
1760 case PROP_INDIVIDUAL_FEATURES:
1761 priv->individual_features = g_value_get_flags (value);
1764 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1770 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1772 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1773 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1774 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1776 object_class->constructed = individual_view_constructed;
1777 object_class->dispose = individual_view_dispose;
1778 object_class->get_property = individual_view_get_property;
1779 object_class->set_property = individual_view_set_property;
1781 widget_class->drag_data_received = individual_view_drag_data_received;
1782 widget_class->drag_drop = individual_view_drag_drop;
1783 widget_class->drag_begin = individual_view_drag_begin;
1784 widget_class->drag_data_get = individual_view_drag_data_get;
1785 widget_class->drag_end = individual_view_drag_end;
1786 widget_class->drag_motion = individual_view_drag_motion;
1788 /* We use the class method to let user of this widget to connect to
1789 * the signal and stop emission of the signal so the default handler
1790 * won't be called. */
1791 tree_view_class->row_activated = individual_view_row_activated;
1793 signals[DRAG_CONTACT_RECEIVED] =
1794 g_signal_new ("drag-contact-received",
1795 G_OBJECT_CLASS_TYPE (klass),
1799 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1800 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1802 g_object_class_install_property (object_class,
1804 g_param_spec_object ("store",
1805 "The store of the view",
1806 "The store of the view",
1807 EMPATHY_TYPE_INDIVIDUAL_STORE,
1808 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1809 g_object_class_install_property (object_class,
1811 g_param_spec_flags ("view-features",
1812 "Features of the view",
1813 "Flags for all enabled features",
1814 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1815 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1816 g_object_class_install_property (object_class,
1817 PROP_INDIVIDUAL_FEATURES,
1818 g_param_spec_flags ("individual-features",
1819 "Features of the contact menu",
1820 "Flags for all enabled features for the menu",
1821 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1822 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1824 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1828 empathy_individual_view_init (EmpathyIndividualView *view)
1830 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1831 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1834 /* Get saved group states. */
1835 empathy_contact_groups_get_all ();
1837 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1838 empathy_individual_store_row_separator_func, NULL, NULL);
1840 /* Set up drag target lists. */
1841 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1842 G_N_ELEMENTS (drag_types_dest_file));
1844 /* Connect to tree view signals rather than override. */
1845 g_signal_connect (view, "button-press-event",
1846 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1847 g_signal_connect (view, "key-press-event",
1848 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1849 g_signal_connect (view, "row-expanded",
1850 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1851 GINT_TO_POINTER (TRUE));
1852 g_signal_connect (view, "row-collapsed",
1853 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1854 GINT_TO_POINTER (FALSE));
1855 g_signal_connect (view, "query-tooltip",
1856 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1859 EmpathyIndividualView *
1860 empathy_individual_view_new (EmpathyIndividualStore *store,
1861 EmpathyIndividualViewFeatureFlags view_features,
1862 EmpathyIndividualFeatureFlags individual_features)
1864 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1866 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1868 "individual-features", individual_features,
1869 "view-features", view_features, NULL);
1873 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1875 EmpathyIndividualViewPriv *priv;
1876 GtkTreeSelection *selection;
1878 GtkTreeModel *model;
1879 FolksIndividual *individual;
1881 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1883 priv = GET_PRIV (view);
1885 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1886 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1889 gtk_tree_model_get (model, &iter,
1890 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1895 EmpathyIndividualManagerFlags
1896 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1898 EmpathyIndividualViewPriv *priv;
1899 GtkTreeSelection *selection;
1901 GtkTreeModel *model;
1902 EmpathyIndividualFeatureFlags flags;
1904 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1906 priv = GET_PRIV (view);
1908 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1909 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1912 gtk_tree_model_get (model, &iter,
1913 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1919 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1920 gboolean *is_fake_group)
1922 EmpathyIndividualViewPriv *priv;
1923 GtkTreeSelection *selection;
1925 GtkTreeModel *model;
1930 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1932 priv = GET_PRIV (view);
1934 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1935 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1938 gtk_tree_model_get (model, &iter,
1939 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1940 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1941 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1949 if (is_fake_group != NULL)
1950 *is_fake_group = fake;
1956 individual_view_remove_dialog_show (GtkWindow *parent,
1957 const gchar *message,
1958 const gchar *secondary_text)
1963 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1964 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1965 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1966 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1967 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1968 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1969 "%s", secondary_text);
1971 gtk_widget_show (dialog);
1973 res = gtk_dialog_run (GTK_DIALOG (dialog));
1974 gtk_widget_destroy (dialog);
1976 return (res == GTK_RESPONSE_YES);
1980 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1981 EmpathyIndividualView *view)
1985 group = empathy_individual_view_get_selected_group (view, NULL);
1992 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1994 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1995 if (individual_view_remove_dialog_show (parent, _("Removing group"),
1998 EmpathyIndividualManager *manager =
1999 empathy_individual_manager_dup_singleton ();
2000 empathy_individual_manager_remove_group (manager, group);
2001 g_object_unref (G_OBJECT (manager));
2011 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2013 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2018 gboolean is_fake_group;
2020 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2022 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2023 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2026 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2027 if (!group || is_fake_group)
2029 /* We can't alter fake groups */
2033 menu = gtk_menu_new ();
2036 if (priv->view_features &
2037 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2038 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2039 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2040 gtk_widget_show (item);
2041 g_signal_connect (item, "activate",
2042 G_CALLBACK (individual_view_group_rename_activate_cb),
2047 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2049 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2050 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2051 GTK_ICON_SIZE_MENU);
2052 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2053 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2054 gtk_widget_show (item);
2055 g_signal_connect (item, "activate",
2056 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2065 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2066 EmpathyIndividualView *view)
2068 FolksIndividual *individual;
2070 individual = empathy_individual_view_dup_selected (view);
2072 if (individual != NULL)
2077 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2080 ("Do you really want to remove the contact '%s'?"),
2081 folks_individual_get_alias (individual));
2082 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2085 EmpathyIndividualManager *manager;
2087 manager = empathy_individual_manager_dup_singleton ();
2088 empathy_individual_manager_remove (manager, individual, "");
2089 g_object_unref (G_OBJECT (manager));
2093 g_object_unref (individual);
2098 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2100 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2101 FolksIndividual *individual;
2102 GtkWidget *menu = NULL;
2105 EmpathyIndividualManagerFlags flags;
2107 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2109 individual = empathy_individual_view_dup_selected (view);
2110 if (individual == NULL)
2113 flags = empathy_individual_view_get_flags (view);
2115 menu = empathy_individual_menu_new (individual, priv->individual_features);
2117 /* Remove contact */
2118 if (priv->view_features &
2119 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2120 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2123 /* create the menu if required, or just add a separator */
2125 menu = gtk_menu_new ();
2128 item = gtk_separator_menu_item_new ();
2129 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2130 gtk_widget_show (item);
2134 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2135 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2136 GTK_ICON_SIZE_MENU);
2137 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2138 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2139 gtk_widget_show (item);
2140 g_signal_connect (item, "activate",
2141 G_CALLBACK (individual_view_remove_activate_cb), view);
2144 g_object_unref (individual);
2150 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2151 EmpathyLiveSearch *search)
2153 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2155 /* remove old handlers if old search was not null */
2156 if (priv->search_widget != NULL)
2158 g_signal_handlers_disconnect_by_func (view,
2159 individual_view_start_search_cb, NULL);
2161 g_signal_handlers_disconnect_by_func (priv->search_widget,
2162 individual_view_search_text_notify_cb, view);
2163 g_signal_handlers_disconnect_by_func (priv->search_widget,
2164 individual_view_search_activate_cb, view);
2165 g_signal_handlers_disconnect_by_func (priv->search_widget,
2166 individual_view_search_key_navigation_cb, view);
2167 g_signal_handlers_disconnect_by_func (priv->search_widget,
2168 individual_view_search_hide_cb, view);
2169 g_signal_handlers_disconnect_by_func (priv->search_widget,
2170 individual_view_search_show_cb, view);
2171 g_object_unref (priv->search_widget);
2172 priv->search_widget = NULL;
2175 /* connect handlers if new search is not null */
2178 priv->search_widget = g_object_ref (search);
2180 g_signal_connect (view, "start-interactive-search",
2181 G_CALLBACK (individual_view_start_search_cb), NULL);
2183 g_signal_connect (priv->search_widget, "notify::text",
2184 G_CALLBACK (individual_view_search_text_notify_cb), view);
2185 g_signal_connect (priv->search_widget, "activate",
2186 G_CALLBACK (individual_view_search_activate_cb), view);
2187 g_signal_connect (priv->search_widget, "key-navigation",
2188 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2189 g_signal_connect (priv->search_widget, "hide",
2190 G_CALLBACK (individual_view_search_hide_cb), view);
2191 g_signal_connect (priv->search_widget, "show",
2192 G_CALLBACK (individual_view_search_show_cb), view);