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 gboolean show_offline;
75 GtkTreeModelFilter *filter;
76 GtkWidget *search_widget;
77 } EmpathyIndividualViewPriv;
81 EmpathyIndividualView *view;
88 EmpathyIndividualView *view;
89 FolksIndividual *individual;
98 PROP_INDIVIDUAL_FEATURES,
102 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
103 * specific EmpathyContacts (between/in/out of Individuals) */
106 DND_DRAG_TYPE_INDIVIDUAL_ID,
107 DND_DRAG_TYPE_URI_LIST,
108 DND_DRAG_TYPE_STRING,
111 static const GtkTargetEntry drag_types_dest[] = {
112 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
113 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
114 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
115 {"text/plain", 0, DND_DRAG_TYPE_STRING},
116 {"STRING", 0, DND_DRAG_TYPE_STRING},
119 static const GtkTargetEntry drag_types_dest_file[] = {
120 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
121 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
124 static const GtkTargetEntry drag_types_source[] = {
125 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
128 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
129 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
133 DRAG_CONTACT_RECEIVED,
137 static guint signals[LAST_SIGNAL];
139 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
143 individual_view_is_visible_individual (EmpathyIndividualView *self,
144 FolksIndividual *individual)
146 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
147 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
151 g_assert (live != NULL);
153 /* check alias name */
154 str = folks_individual_get_alias (individual);
155 if (empathy_live_search_match (live, str))
158 /* check contact id, remove the @server.com part */
159 personas = folks_individual_get_personas (individual);
160 for (l = personas; l; l = l->next)
163 gchar *dup_str = NULL;
166 str = folks_persona_get_uid (l->data);
167 p = strstr (str, "@");
169 str = dup_str = g_strndup (str, p - str);
171 visible = empathy_live_search_match (live, str);
177 /* FIXME: Add more rules here, we could check phone numbers in
178 * contact's vCard for example. */
184 individual_view_filter_visible_func (GtkTreeModel *model,
188 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
189 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
190 FolksIndividual *individual = NULL;
191 gboolean is_group, is_separator, valid;
192 GtkTreeIter child_iter;
193 gboolean visible, show_offline, is_online;
194 gboolean is_searching = TRUE;
196 show_offline = empathy_individual_view_get_show_offline (self);
198 if (priv->search_widget == NULL ||
199 !gtk_widget_get_visible (priv->search_widget))
200 is_searching = FALSE;
203 gtk_tree_model_get (model, iter,
204 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
205 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
206 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
207 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
210 if (individual != NULL)
212 visible = individual_view_is_visible_individual (self, individual);
213 g_object_unref (individual);
218 return (show_offline || is_online);
224 /* Not a contact, not a separator, must be a group */
225 g_return_val_if_fail (is_group, FALSE);
227 /* only show groups which are not empty */
228 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
229 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
231 gtk_tree_model_get (model, &child_iter,
232 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
233 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
236 if (individual == NULL)
239 visible = individual_view_is_visible_individual (self, individual);
240 g_object_unref (individual);
242 /* show group if it has at least one visible contact in it */
243 if ((is_searching && visible) ||
244 (!is_searching && (show_offline || is_online)))
252 individual_view_tooltip_destroy_cb (GtkWidget *widget,
253 EmpathyIndividualView *view)
255 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
257 if (priv->tooltip_widget != NULL)
259 DEBUG ("Tooltip destroyed");
260 tp_clear_object (&priv->tooltip_widget);
265 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
268 gboolean keyboard_mode,
272 EmpathyIndividualViewPriv *priv;
273 FolksIndividual *individual;
277 static gint running = 0;
278 gboolean ret = FALSE;
279 EmpathyContact *contact;
281 priv = GET_PRIV (view);
283 /* Avoid an infinite loop. See GNOME bug #574377 */
289 /* Don't show the tooltip if there's already a popup menu */
290 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
293 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
294 keyboard_mode, &model, &path, &iter))
297 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
298 gtk_tree_path_free (path);
300 gtk_tree_model_get (model, &iter,
301 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
303 if (individual == NULL)
306 contact = empathy_contact_dup_from_folks_individual (individual);
307 g_object_unref (individual);
312 if (priv->tooltip_widget == NULL)
314 priv->tooltip_widget = empathy_contact_widget_new (contact,
315 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
316 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
317 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
318 g_object_ref (priv->tooltip_widget);
319 g_signal_connect (priv->tooltip_widget, "destroy",
320 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
321 gtk_widget_show (priv->tooltip_widget);
324 empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
326 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
329 g_object_unref (contact);
337 groups_change_group_cb (GObject *source,
338 GAsyncResult *result,
341 FolksGroups *groups = FOLKS_GROUPS (source);
342 GError *error = NULL;
344 folks_groups_change_group_finish (groups, result, &error);
347 g_warning ("failed to change group: %s", error->message);
348 g_clear_error (&error);
353 individual_view_handle_drag (EmpathyIndividualView *self,
354 FolksIndividual *individual,
355 const gchar *old_group,
356 const gchar *new_group,
357 GdkDragAction action)
359 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
360 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
362 DEBUG ("individual %s dragged from '%s' to '%s'",
363 folks_individual_get_id (individual), old_group, new_group);
365 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
367 /* Mark contact as favourite */
368 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
372 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
374 /* Remove contact as favourite */
375 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
377 /* Don't try to remove it */
381 if (new_group != NULL)
382 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
383 groups_change_group_cb, NULL);
385 if (old_group != NULL && action == GDK_ACTION_MOVE)
386 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE,
387 groups_change_group_cb, NULL);
391 group_can_be_modified (const gchar *name,
392 gboolean is_fake_group,
395 /* Real groups can always be modified */
399 /* The favorite fake group can be modified so users can
400 * add/remove favorites using DnD */
401 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
404 /* We can remove contacts from the 'ungrouped' fake group */
405 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
412 individual_view_contact_drag_received (GtkWidget *self,
413 GdkDragContext *context,
416 GtkSelectionData *selection)
418 EmpathyIndividualViewPriv *priv;
419 EmpathyIndividualManager *manager;
420 FolksIndividual *individual;
421 GtkTreePath *source_path;
422 const gchar *sel_data;
423 gchar *new_group = NULL;
424 gchar *old_group = NULL;
425 gboolean new_group_is_fake, old_group_is_fake = TRUE;
427 priv = GET_PRIV (self);
429 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
430 new_group = empathy_individual_store_get_parent_group (model, path,
431 NULL, &new_group_is_fake);
433 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
436 /* Get source group information. */
439 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
443 empathy_individual_store_get_parent_group (model, source_path,
444 NULL, &old_group_is_fake);
445 gtk_tree_path_free (source_path);
449 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
452 if (!tp_strdiff (old_group, new_group))
459 /* XXX: for contacts, we used to ensure the account, create the contact
460 * factory, and then wait on the contacts. But they should already be
461 * created by this point */
463 manager = empathy_individual_manager_dup_singleton ();
464 individual = empathy_individual_manager_lookup_member (manager, sel_data);
466 if (individual == NULL)
468 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
470 g_object_unref (manager);
475 /* FIXME: We should probably wait for the cb before calling
478 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
479 old_group, new_group, gdk_drag_context_get_selected_action (context));
481 g_object_unref (G_OBJECT (manager));
489 individual_view_file_drag_received (GtkWidget *view,
490 GdkDragContext *context,
493 GtkSelectionData *selection)
496 const gchar *sel_data;
497 FolksIndividual *individual;
498 EmpathyContact *contact;
500 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
502 gtk_tree_model_get_iter (model, &iter, path);
503 gtk_tree_model_get (model, &iter,
504 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
505 if (individual == NULL)
508 contact = empathy_contact_dup_from_folks_individual (individual);
509 empathy_send_file_from_uri_list (contact, sel_data);
511 g_object_unref (individual);
512 tp_clear_object (&contact);
518 individual_view_drag_data_received (GtkWidget *view,
519 GdkDragContext *context,
522 GtkSelectionData *selection,
528 GtkTreeViewDropPosition position;
530 gboolean success = TRUE;
532 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
534 /* Get destination group information. */
535 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
536 x, y, &path, &position);
541 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
542 || info == DND_DRAG_TYPE_STRING)
544 success = individual_view_contact_drag_received (view,
545 context, model, path, selection);
547 else if (info == DND_DRAG_TYPE_URI_LIST)
549 success = individual_view_file_drag_received (view,
550 context, model, path, selection);
553 gtk_tree_path_free (path);
554 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
558 individual_view_drag_motion_cb (DragMotionData *data)
560 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
562 data->timeout_id = 0;
568 individual_view_drag_motion (GtkWidget *widget,
569 GdkDragContext *context,
574 EmpathyIndividualViewPriv *priv;
578 static DragMotionData *dm = NULL;
581 gboolean is_different = FALSE;
582 gboolean cleanup = TRUE;
583 gboolean retval = TRUE;
585 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
586 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
588 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
589 x, y, &path, NULL, NULL, NULL);
591 cleanup &= (dm == NULL);
595 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
596 is_different = ((dm == NULL) || ((dm != NULL)
597 && gtk_tree_path_compare (dm->path, path) != 0));
604 /* Coordinates don't point to an actual row, so make sure the pointer
605 and highlighting don't indicate that a drag is possible.
607 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
608 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
611 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
612 gtk_tree_model_get_iter (model, &iter, path);
614 if (target == GDK_NONE)
616 /* If target == GDK_NONE, then we don't have a target that can be
617 dropped on a contact. This means a contact drag. If we're
618 pointing to a group, highlight it. Otherwise, if the contact
619 we're pointing to is in a group, highlight that. Otherwise,
620 set the drag position to before the first row for a drag into
621 the "non-group" at the top.
623 GtkTreeIter group_iter;
625 GtkTreePath *group_path;
626 gtk_tree_model_get (model, &iter,
627 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
634 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
635 gtk_tree_model_get (model, &group_iter,
636 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
640 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
641 group_path = gtk_tree_model_get_path (model, &group_iter);
642 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
643 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
644 gtk_tree_path_free (group_path);
648 group_path = gtk_tree_path_new_first ();
649 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
650 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
651 group_path, GTK_TREE_VIEW_DROP_BEFORE);
656 /* This is a file drag, and it can only be dropped on contacts,
659 FolksIndividual *individual;
660 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
662 gtk_tree_model_get (model, &iter,
663 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
664 if (individual != NULL)
666 EmpathyContact *contact = NULL;
668 contact = empathy_contact_dup_from_folks_individual (individual);
669 caps = empathy_contact_get_capabilities (contact);
671 tp_clear_object (&contact);
674 if (individual != NULL &&
675 folks_individual_is_online (individual) &&
676 (caps & EMPATHY_CAPABILITIES_FT))
678 gdk_drag_status (context, GDK_ACTION_COPY, time_);
679 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
680 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
684 gdk_drag_status (context, 0, time_);
685 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
689 if (individual != NULL)
690 g_object_unref (individual);
693 if (!is_different && !cleanup)
698 gtk_tree_path_free (dm->path);
701 g_source_remove (dm->timeout_id);
709 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
711 dm = g_new0 (DragMotionData, 1);
713 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
714 dm->path = gtk_tree_path_copy (path);
716 dm->timeout_id = g_timeout_add_seconds (1,
717 (GSourceFunc) individual_view_drag_motion_cb, dm);
724 individual_view_drag_begin (GtkWidget *widget,
725 GdkDragContext *context)
727 EmpathyIndividualViewPriv *priv;
728 GtkTreeSelection *selection;
733 priv = GET_PRIV (widget);
735 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
738 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
739 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
742 path = gtk_tree_model_get_path (model, &iter);
743 priv->drag_row = gtk_tree_row_reference_new (model, path);
744 gtk_tree_path_free (path);
748 individual_view_drag_data_get (GtkWidget *widget,
749 GdkDragContext *context,
750 GtkSelectionData *selection,
754 EmpathyIndividualViewPriv *priv;
755 GtkTreePath *src_path;
758 FolksIndividual *individual;
759 const gchar *individual_id;
761 priv = GET_PRIV (widget);
763 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
764 if (priv->drag_row == NULL)
767 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
768 if (src_path == NULL)
771 if (!gtk_tree_model_get_iter (model, &iter, src_path))
773 gtk_tree_path_free (src_path);
777 gtk_tree_path_free (src_path);
780 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
781 if (individual == NULL)
784 individual_id = folks_individual_get_id (individual);
788 case DND_DRAG_TYPE_INDIVIDUAL_ID:
789 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
790 (guchar *) individual_id, strlen (individual_id) + 1);
794 g_object_unref (individual);
798 individual_view_drag_end (GtkWidget *widget,
799 GdkDragContext *context)
801 EmpathyIndividualViewPriv *priv;
803 priv = GET_PRIV (widget);
805 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
810 gtk_tree_row_reference_free (priv->drag_row);
811 priv->drag_row = NULL;
816 individual_view_drag_drop (GtkWidget *widget,
817 GdkDragContext *drag_context,
827 EmpathyIndividualView *view;
833 individual_view_popup_menu_idle_cb (gpointer user_data)
835 MenuPopupData *data = user_data;
838 menu = empathy_individual_view_get_individual_menu (data->view);
840 menu = empathy_individual_view_get_group_menu (data->view);
844 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
845 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
847 gtk_widget_show (menu);
848 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
850 g_object_ref_sink (menu);
851 g_object_unref (menu);
854 g_slice_free (MenuPopupData, data);
860 individual_view_button_press_event_cb (EmpathyIndividualView *view,
861 GdkEventButton *event,
864 if (event->button == 3)
868 data = g_slice_new (MenuPopupData);
870 data->button = event->button;
871 data->time = event->time;
872 g_idle_add (individual_view_popup_menu_idle_cb, data);
879 individual_view_key_press_event_cb (EmpathyIndividualView *view,
883 if (event->keyval == GDK_Menu)
887 data = g_slice_new (MenuPopupData);
890 data->time = event->time;
891 g_idle_add (individual_view_popup_menu_idle_cb, data);
898 individual_view_row_activated (GtkTreeView *view,
900 GtkTreeViewColumn *column)
902 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
903 FolksIndividual *individual;
904 EmpathyContact *contact = NULL;
908 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
911 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
912 gtk_tree_model_get_iter (model, &iter, path);
913 gtk_tree_model_get (model, &iter,
914 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
916 if (individual == NULL)
919 contact = empathy_contact_dup_from_folks_individual (individual);
922 DEBUG ("Starting a chat");
924 empathy_dispatcher_chat_with_contact (contact,
925 gtk_get_current_event_time (), NULL, NULL);
928 g_object_unref (individual);
929 tp_clear_object (&contact);
933 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
934 const gchar *path_string,
935 EmpathyIndividualView *view)
940 FolksIndividual *individual;
941 GdkEventButton *event;
945 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
946 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
949 gtk_tree_model_get (model, &iter,
950 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
951 if (individual == NULL)
954 event = (GdkEventButton *) gtk_get_current_event ();
956 menu = gtk_menu_new ();
957 shell = GTK_MENU_SHELL (menu);
960 item = empathy_individual_audio_call_menu_item_new (individual);
961 gtk_menu_shell_append (shell, item);
962 gtk_widget_show (item);
965 item = empathy_individual_video_call_menu_item_new (individual);
966 gtk_menu_shell_append (shell, item);
967 gtk_widget_show (item);
969 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
970 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
971 gtk_widget_show (menu);
972 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
973 event->button, event->time);
974 g_object_ref_sink (menu);
975 g_object_unref (menu);
977 g_object_unref (individual);
981 individual_view_cell_set_background (EmpathyIndividualView *view,
982 GtkCellRenderer *cell,
989 style = gtk_widget_get_style (GTK_WIDGET (view));
991 if (!is_group && is_active)
993 color = style->bg[GTK_STATE_SELECTED];
995 /* Here we take the current theme colour and add it to
996 * the colour for white and average the two. This
997 * gives a colour which is inline with the theme but
1000 color.red = (color.red + (style->white).red) / 2;
1001 color.green = (color.green + (style->white).green) / 2;
1002 color.blue = (color.blue + (style->white).blue) / 2;
1004 g_object_set (cell, "cell-background-gdk", &color, NULL);
1007 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1011 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1012 GtkCellRenderer *cell,
1013 GtkTreeModel *model,
1015 EmpathyIndividualView *view)
1021 gtk_tree_model_get (model, iter,
1022 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1023 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1024 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1027 "visible", !is_group,
1031 tp_clear_object (&pixbuf);
1033 individual_view_cell_set_background (view, cell, is_group, is_active);
1037 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1038 GtkCellRenderer *cell,
1039 GtkTreeModel *model,
1041 EmpathyIndividualView *view)
1043 GdkPixbuf *pixbuf = NULL;
1047 gtk_tree_model_get (model, iter,
1048 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1049 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1054 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1056 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1057 GTK_ICON_SIZE_MENU);
1059 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1061 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1062 GTK_ICON_SIZE_MENU);
1067 "visible", pixbuf != NULL,
1071 tp_clear_object (&pixbuf);
1077 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1078 GtkCellRenderer *cell,
1079 GtkTreeModel *model,
1081 EmpathyIndividualView *view)
1085 gboolean can_audio, can_video;
1087 gtk_tree_model_get (model, iter,
1088 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1089 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1090 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1091 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1094 "visible", !is_group && (can_audio || can_video),
1095 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1098 individual_view_cell_set_background (view, cell, is_group, is_active);
1102 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1103 GtkCellRenderer *cell,
1104 GtkTreeModel *model,
1106 EmpathyIndividualView *view)
1109 gboolean show_avatar;
1113 gtk_tree_model_get (model, iter,
1114 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1115 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1116 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1117 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1120 "visible", !is_group && show_avatar,
1124 tp_clear_object (&pixbuf);
1126 individual_view_cell_set_background (view, cell, is_group, is_active);
1130 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1131 GtkCellRenderer *cell,
1132 GtkTreeModel *model,
1134 EmpathyIndividualView *view)
1139 gtk_tree_model_get (model, iter,
1140 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1141 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1143 individual_view_cell_set_background (view, cell, is_group, is_active);
1147 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1148 GtkCellRenderer *cell,
1149 GtkTreeModel *model,
1151 EmpathyIndividualView *view)
1156 gtk_tree_model_get (model, iter,
1157 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1158 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1160 if (gtk_tree_model_iter_has_child (model, iter))
1163 gboolean row_expanded;
1165 path = gtk_tree_model_get_path (model, iter);
1167 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1168 (gtk_tree_view_column_get_tree_view (column)), path);
1169 gtk_tree_path_free (path);
1174 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1178 g_object_set (cell, "visible", FALSE, NULL);
1180 individual_view_cell_set_background (view, cell, is_group, is_active);
1184 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1189 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1190 GtkTreeModel *model;
1194 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1197 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1199 gtk_tree_model_get (model, iter,
1200 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1202 expanded = GPOINTER_TO_INT (user_data);
1203 empathy_contact_group_set_expanded (name, expanded);
1209 individual_view_start_search_cb (EmpathyIndividualView *view,
1212 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1214 if (priv->search_widget == NULL)
1217 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1218 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1220 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1226 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1228 EmpathyIndividualView *view)
1230 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1232 GtkTreeViewColumn *focus_column;
1233 GtkTreeModel *model;
1235 gboolean set_cursor = FALSE;
1237 gtk_tree_model_filter_refilter (priv->filter);
1239 /* Set cursor on the first contact. If it is already set on a group,
1240 * set it on its first child contact. Note that first child of a group
1241 * is its separator, that's why we actually set to the 2nd
1244 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1245 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1249 path = gtk_tree_path_new_from_string ("0:1");
1252 else if (gtk_tree_path_get_depth (path) < 2)
1256 gtk_tree_model_get_iter (model, &iter, path);
1257 gtk_tree_model_get (model, &iter,
1258 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1263 gtk_tree_path_down (path);
1264 gtk_tree_path_next (path);
1271 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1273 if (gtk_tree_model_get_iter (model, &iter, path))
1275 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1280 gtk_tree_path_free (path);
1284 individual_view_search_activate_cb (GtkWidget *search,
1285 EmpathyIndividualView *view)
1288 GtkTreeViewColumn *focus_column;
1290 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1293 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1294 gtk_tree_path_free (path);
1296 gtk_widget_hide (search);
1301 individual_view_search_key_navigation_cb (GtkWidget *search,
1303 EmpathyIndividualView *view)
1305 GdkEventKey *eventkey = ((GdkEventKey *) event);
1306 gboolean ret = FALSE;
1308 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1310 GdkEvent *new_event;
1312 new_event = gdk_event_copy (event);
1313 gtk_widget_grab_focus (GTK_WIDGET (view));
1314 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1315 gtk_widget_grab_focus (search);
1317 gdk_event_free (new_event);
1324 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1325 EmpathyIndividualView *view)
1327 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1328 GtkTreeModel *model;
1329 GtkTreePath *cursor_path;
1331 gboolean valid = FALSE;
1333 /* block expand or collapse handlers, they would write the
1334 * expand or collapsed setting to file otherwise */
1335 g_signal_handlers_block_by_func (view,
1336 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1337 g_signal_handlers_block_by_func (view,
1338 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1340 /* restore which groups are expanded and which are not */
1341 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1342 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1343 valid; valid = gtk_tree_model_iter_next (model, &iter))
1349 gtk_tree_model_get (model, &iter,
1350 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1351 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1360 path = gtk_tree_model_get_path (model, &iter);
1361 if ((priv->view_features &
1362 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1363 empathy_contact_group_get_expanded (name))
1365 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1369 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1372 gtk_tree_path_free (path);
1376 /* unblock expand or collapse handlers */
1377 g_signal_handlers_unblock_by_func (view,
1378 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1379 g_signal_handlers_unblock_by_func (view,
1380 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1382 /* keep the selected contact visible */
1383 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1385 if (cursor_path != NULL)
1386 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1389 gtk_tree_path_free (cursor_path);
1393 individual_view_search_show_cb (EmpathyLiveSearch *search,
1394 EmpathyIndividualView *view)
1396 /* block expand or collapse handlers during expand all, they would
1397 * write the expand or collapsed setting to file otherwise */
1398 g_signal_handlers_block_by_func (view,
1399 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1401 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1403 g_signal_handlers_unblock_by_func (view,
1404 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1408 EmpathyIndividualView *view;
1409 GtkTreeRowReference *row_ref;
1414 individual_view_expand_idle_cb (gpointer user_data)
1416 ExpandData *data = user_data;
1419 path = gtk_tree_row_reference_get_path (data->row_ref);
1423 g_signal_handlers_block_by_func (data->view,
1424 individual_view_row_expand_or_collapse_cb,
1425 GINT_TO_POINTER (data->expand));
1428 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1430 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1432 gtk_tree_path_free (path);
1434 g_signal_handlers_unblock_by_func (data->view,
1435 individual_view_row_expand_or_collapse_cb,
1436 GINT_TO_POINTER (data->expand));
1439 g_object_unref (data->view);
1440 gtk_tree_row_reference_free (data->row_ref);
1441 g_slice_free (ExpandData, data);
1447 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1450 EmpathyIndividualView *view)
1452 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1453 gboolean is_group = FALSE;
1457 gtk_tree_model_get (model, iter,
1458 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1459 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1462 if (!is_group || EMP_STR_EMPTY (name))
1468 data = g_slice_new0 (ExpandData);
1469 data->view = g_object_ref (view);
1470 data->row_ref = gtk_tree_row_reference_new (model, path);
1472 (priv->view_features &
1473 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1474 (priv->search_widget != NULL &&
1475 gtk_widget_get_visible (priv->search_widget)) ||
1476 empathy_contact_group_get_expanded (name);
1478 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1479 * gtk_tree_model_filter_refilter () */
1480 g_idle_add (individual_view_expand_idle_cb, data);
1486 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1489 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1490 GtkTreeModel *model;
1491 GtkTreePath *parent_path;
1492 GtkTreeIter parent_iter;
1494 if (gtk_tree_path_get_depth (path) < 2)
1497 /* A group row is visible if and only if at least one if its child is visible.
1498 * So when a row is inserted/deleted/changed in the base model, that could
1499 * modify the visibility of its parent in the filter model.
1502 model = GTK_TREE_MODEL (priv->store);
1503 parent_path = gtk_tree_path_copy (path);
1504 gtk_tree_path_up (parent_path);
1505 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1507 /* This tells the filter to verify the visibility of that row, and
1508 * show/hide it if necessary */
1509 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1510 parent_path, &parent_iter);
1512 gtk_tree_path_free (parent_path);
1516 individual_view_store_row_changed_cb (GtkTreeModel *model,
1519 EmpathyIndividualView *view)
1521 individual_view_verify_group_visibility (view, path);
1525 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1527 EmpathyIndividualView *view)
1529 individual_view_verify_group_visibility (view, path);
1533 individual_view_constructed (GObject *object)
1535 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1536 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1537 GtkCellRenderer *cell;
1538 GtkTreeViewColumn *col;
1541 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1542 GTK_TREE_MODEL (priv->store), NULL));
1543 gtk_tree_model_filter_set_visible_func (priv->filter,
1544 individual_view_filter_visible_func, view, NULL);
1546 g_signal_connect (priv->filter, "row-has-child-toggled",
1547 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1548 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1549 GTK_TREE_MODEL (priv->filter));
1551 tp_g_signal_connect_object (priv->store, "row-changed",
1552 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1553 tp_g_signal_connect_object (priv->store, "row-inserted",
1554 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1555 tp_g_signal_connect_object (priv->store, "row-deleted",
1556 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1559 /* Setting reorderable is a hack that gets us row previews as drag icons
1560 for free. We override all the drag handlers. It's tricky to get the
1561 position of the drag icon right in drag_begin. GtkTreeView has special
1562 voodoo for it, so we let it do the voodoo that he do.
1565 "headers-visible", FALSE,
1566 "reorderable", TRUE,
1567 "show-expanders", FALSE,
1570 col = gtk_tree_view_column_new ();
1573 cell = gtk_cell_renderer_pixbuf_new ();
1574 gtk_tree_view_column_pack_start (col, cell, FALSE);
1575 gtk_tree_view_column_set_cell_data_func (col, cell,
1576 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1586 cell = gtk_cell_renderer_pixbuf_new ();
1587 gtk_tree_view_column_pack_start (col, cell, FALSE);
1588 gtk_tree_view_column_set_cell_data_func (col, cell,
1589 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1601 cell = empathy_cell_renderer_text_new ();
1602 gtk_tree_view_column_pack_start (col, cell, TRUE);
1603 gtk_tree_view_column_set_cell_data_func (col, cell,
1604 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1606 gtk_tree_view_column_add_attribute (col, cell,
1607 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1608 gtk_tree_view_column_add_attribute (col, cell,
1609 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1610 gtk_tree_view_column_add_attribute (col, cell,
1611 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1612 gtk_tree_view_column_add_attribute (col, cell,
1613 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1614 gtk_tree_view_column_add_attribute (col, cell,
1615 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1616 gtk_tree_view_column_add_attribute (col, cell,
1617 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1619 /* Audio Call Icon */
1620 cell = empathy_cell_renderer_activatable_new ();
1621 gtk_tree_view_column_pack_start (col, cell, FALSE);
1622 gtk_tree_view_column_set_cell_data_func (col, cell,
1623 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1626 g_object_set (cell, "visible", FALSE, NULL);
1628 g_signal_connect (cell, "path-activated",
1629 G_CALLBACK (individual_view_call_activated_cb), view);
1632 cell = gtk_cell_renderer_pixbuf_new ();
1633 gtk_tree_view_column_pack_start (col, cell, FALSE);
1634 gtk_tree_view_column_set_cell_data_func (col, cell,
1635 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1647 cell = empathy_cell_renderer_expander_new ();
1648 gtk_tree_view_column_pack_end (col, cell, FALSE);
1649 gtk_tree_view_column_set_cell_data_func (col, cell,
1650 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1653 /* Actually add the column now we have added all cell renderers */
1654 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1657 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1659 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1662 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1664 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1670 individual_view_set_view_features (EmpathyIndividualView *view,
1671 EmpathyIndividualFeatureFlags features)
1673 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1674 gboolean has_tooltip;
1676 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1678 priv->view_features = features;
1680 /* Update DnD source/dest */
1681 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1683 gtk_drag_source_set (GTK_WIDGET (view),
1686 G_N_ELEMENTS (drag_types_source),
1687 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1691 gtk_drag_source_unset (GTK_WIDGET (view));
1695 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1697 gtk_drag_dest_set (GTK_WIDGET (view),
1698 GTK_DEST_DEFAULT_ALL,
1700 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1704 /* FIXME: URI could still be droped depending on FT feature */
1705 gtk_drag_dest_unset (GTK_WIDGET (view));
1708 /* Update has-tooltip */
1710 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1711 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1715 individual_view_dispose (GObject *object)
1717 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1718 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1720 tp_clear_object (&priv->store);
1721 tp_clear_object (&priv->filter);
1722 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1723 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1725 empathy_individual_view_set_live_search (view, NULL);
1727 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1731 individual_view_get_property (GObject *object,
1736 EmpathyIndividualViewPriv *priv;
1738 priv = GET_PRIV (object);
1743 g_value_set_object (value, priv->store);
1745 case PROP_VIEW_FEATURES:
1746 g_value_set_flags (value, priv->view_features);
1748 case PROP_INDIVIDUAL_FEATURES:
1749 g_value_set_flags (value, priv->individual_features);
1751 case PROP_SHOW_OFFLINE:
1752 g_value_set_boolean (value, priv->show_offline);
1755 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1761 individual_view_set_property (GObject *object,
1763 const GValue *value,
1766 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1767 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1772 priv->store = g_value_dup_object (value);
1774 case PROP_VIEW_FEATURES:
1775 individual_view_set_view_features (view, g_value_get_flags (value));
1777 case PROP_INDIVIDUAL_FEATURES:
1778 priv->individual_features = g_value_get_flags (value);
1780 case PROP_SHOW_OFFLINE:
1781 empathy_individual_view_set_show_offline (view,
1782 g_value_get_boolean (value));
1785 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1791 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1793 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1794 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1795 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1797 object_class->constructed = individual_view_constructed;
1798 object_class->dispose = individual_view_dispose;
1799 object_class->get_property = individual_view_get_property;
1800 object_class->set_property = individual_view_set_property;
1802 widget_class->drag_data_received = individual_view_drag_data_received;
1803 widget_class->drag_drop = individual_view_drag_drop;
1804 widget_class->drag_begin = individual_view_drag_begin;
1805 widget_class->drag_data_get = individual_view_drag_data_get;
1806 widget_class->drag_end = individual_view_drag_end;
1807 widget_class->drag_motion = individual_view_drag_motion;
1809 /* We use the class method to let user of this widget to connect to
1810 * the signal and stop emission of the signal so the default handler
1811 * won't be called. */
1812 tree_view_class->row_activated = individual_view_row_activated;
1814 signals[DRAG_CONTACT_RECEIVED] =
1815 g_signal_new ("drag-contact-received",
1816 G_OBJECT_CLASS_TYPE (klass),
1820 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1821 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1823 g_object_class_install_property (object_class,
1825 g_param_spec_object ("store",
1826 "The store of the view",
1827 "The store of the view",
1828 EMPATHY_TYPE_INDIVIDUAL_STORE,
1829 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1830 g_object_class_install_property (object_class,
1832 g_param_spec_flags ("view-features",
1833 "Features of the view",
1834 "Flags for all enabled features",
1835 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1836 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1837 g_object_class_install_property (object_class,
1838 PROP_INDIVIDUAL_FEATURES,
1839 g_param_spec_flags ("individual-features",
1840 "Features of the contact menu",
1841 "Flags for all enabled features for the menu",
1842 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1843 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1844 g_object_class_install_property (object_class,
1846 g_param_spec_boolean ("show-offline",
1848 "Whether contact list should display "
1849 "offline contacts", FALSE, G_PARAM_READWRITE));
1851 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1855 empathy_individual_view_init (EmpathyIndividualView *view)
1857 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1858 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1861 /* Get saved group states. */
1862 empathy_contact_groups_get_all ();
1864 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1865 empathy_individual_store_row_separator_func, NULL, NULL);
1867 /* Set up drag target lists. */
1868 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1869 G_N_ELEMENTS (drag_types_dest_file));
1871 /* Connect to tree view signals rather than override. */
1872 g_signal_connect (view, "button-press-event",
1873 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1874 g_signal_connect (view, "key-press-event",
1875 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1876 g_signal_connect (view, "row-expanded",
1877 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1878 GINT_TO_POINTER (TRUE));
1879 g_signal_connect (view, "row-collapsed",
1880 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1881 GINT_TO_POINTER (FALSE));
1882 g_signal_connect (view, "query-tooltip",
1883 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1886 EmpathyIndividualView *
1887 empathy_individual_view_new (EmpathyIndividualStore *store,
1888 EmpathyIndividualViewFeatureFlags view_features,
1889 EmpathyIndividualFeatureFlags individual_features)
1891 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1893 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1895 "individual-features", individual_features,
1896 "view-features", view_features, NULL);
1900 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1902 EmpathyIndividualViewPriv *priv;
1903 GtkTreeSelection *selection;
1905 GtkTreeModel *model;
1906 FolksIndividual *individual;
1908 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1910 priv = GET_PRIV (view);
1912 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1913 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1916 gtk_tree_model_get (model, &iter,
1917 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1922 EmpathyIndividualManagerFlags
1923 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1925 EmpathyIndividualViewPriv *priv;
1926 GtkTreeSelection *selection;
1928 GtkTreeModel *model;
1929 EmpathyIndividualFeatureFlags flags;
1931 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1933 priv = GET_PRIV (view);
1935 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1936 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1939 gtk_tree_model_get (model, &iter,
1940 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1946 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1947 gboolean *is_fake_group)
1949 EmpathyIndividualViewPriv *priv;
1950 GtkTreeSelection *selection;
1952 GtkTreeModel *model;
1957 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1959 priv = GET_PRIV (view);
1961 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1962 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1965 gtk_tree_model_get (model, &iter,
1966 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1967 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1968 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1976 if (is_fake_group != NULL)
1977 *is_fake_group = fake;
1983 individual_view_remove_dialog_show (GtkWindow *parent,
1984 const gchar *message,
1985 const gchar *secondary_text)
1990 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1991 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1992 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1993 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1994 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1995 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1996 "%s", secondary_text);
1998 gtk_widget_show (dialog);
2000 res = gtk_dialog_run (GTK_DIALOG (dialog));
2001 gtk_widget_destroy (dialog);
2003 return (res == GTK_RESPONSE_YES);
2007 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2008 EmpathyIndividualView *view)
2012 group = empathy_individual_view_get_selected_group (view, NULL);
2019 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2021 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2022 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2025 EmpathyIndividualManager *manager =
2026 empathy_individual_manager_dup_singleton ();
2027 empathy_individual_manager_remove_group (manager, group);
2028 g_object_unref (G_OBJECT (manager));
2038 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2040 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2045 gboolean is_fake_group;
2047 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2049 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2050 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2053 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2054 if (!group || is_fake_group)
2056 /* We can't alter fake groups */
2060 menu = gtk_menu_new ();
2063 if (priv->view_features &
2064 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2065 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2066 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2067 gtk_widget_show (item);
2068 g_signal_connect (item, "activate",
2069 G_CALLBACK (individual_view_group_rename_activate_cb),
2074 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2076 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2077 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2078 GTK_ICON_SIZE_MENU);
2079 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2080 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2081 gtk_widget_show (item);
2082 g_signal_connect (item, "activate",
2083 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2092 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2093 EmpathyIndividualView *view)
2095 FolksIndividual *individual;
2097 individual = empathy_individual_view_dup_selected (view);
2099 if (individual != NULL)
2104 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2107 ("Do you really want to remove the contact '%s'?"),
2108 folks_individual_get_alias (individual));
2109 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2112 EmpathyIndividualManager *manager;
2114 manager = empathy_individual_manager_dup_singleton ();
2115 empathy_individual_manager_remove (manager, individual, "");
2116 g_object_unref (G_OBJECT (manager));
2120 g_object_unref (individual);
2125 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2127 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2128 FolksIndividual *individual;
2129 GtkWidget *menu = NULL;
2132 EmpathyIndividualManagerFlags flags;
2134 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2136 individual = empathy_individual_view_dup_selected (view);
2137 if (individual == NULL)
2140 flags = empathy_individual_view_get_flags (view);
2142 menu = empathy_individual_menu_new (individual, priv->individual_features);
2144 /* Remove contact */
2145 if (priv->view_features &
2146 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2147 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2150 /* create the menu if required, or just add a separator */
2152 menu = gtk_menu_new ();
2155 item = gtk_separator_menu_item_new ();
2156 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2157 gtk_widget_show (item);
2161 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2162 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2163 GTK_ICON_SIZE_MENU);
2164 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2165 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2166 gtk_widget_show (item);
2167 g_signal_connect (item, "activate",
2168 G_CALLBACK (individual_view_remove_activate_cb), view);
2171 g_object_unref (individual);
2177 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2178 EmpathyLiveSearch *search)
2180 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2182 /* remove old handlers if old search was not null */
2183 if (priv->search_widget != NULL)
2185 g_signal_handlers_disconnect_by_func (view,
2186 individual_view_start_search_cb, NULL);
2188 g_signal_handlers_disconnect_by_func (priv->search_widget,
2189 individual_view_search_text_notify_cb, view);
2190 g_signal_handlers_disconnect_by_func (priv->search_widget,
2191 individual_view_search_activate_cb, view);
2192 g_signal_handlers_disconnect_by_func (priv->search_widget,
2193 individual_view_search_key_navigation_cb, view);
2194 g_signal_handlers_disconnect_by_func (priv->search_widget,
2195 individual_view_search_hide_cb, view);
2196 g_signal_handlers_disconnect_by_func (priv->search_widget,
2197 individual_view_search_show_cb, view);
2198 g_object_unref (priv->search_widget);
2199 priv->search_widget = NULL;
2202 /* connect handlers if new search is not null */
2205 priv->search_widget = g_object_ref (search);
2207 g_signal_connect (view, "start-interactive-search",
2208 G_CALLBACK (individual_view_start_search_cb), NULL);
2210 g_signal_connect (priv->search_widget, "notify::text",
2211 G_CALLBACK (individual_view_search_text_notify_cb), view);
2212 g_signal_connect (priv->search_widget, "activate",
2213 G_CALLBACK (individual_view_search_activate_cb), view);
2214 g_signal_connect (priv->search_widget, "key-navigation",
2215 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2216 g_signal_connect (priv->search_widget, "hide",
2217 G_CALLBACK (individual_view_search_hide_cb), view);
2218 g_signal_connect (priv->search_widget, "show",
2219 G_CALLBACK (individual_view_search_show_cb), view);
2224 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2226 EmpathyIndividualViewPriv *priv;
2228 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2230 priv = GET_PRIV (self);
2232 return priv->show_offline;
2236 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2237 gboolean show_offline)
2239 EmpathyIndividualViewPriv *priv;
2241 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2243 priv = GET_PRIV (self);
2245 priv->show_offline = show_offline;
2247 g_object_notify (G_OBJECT (self), "show-offline");
2248 gtk_tree_model_filter_refilter (priv->filter);