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 #define DRAG_TYPE(T,I) \
112 { (gchar *) T, 0, I }
114 static const GtkTargetEntry drag_types_dest[] = {
115 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
116 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
117 DRAG_TYPE ("text/contact-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
118 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
119 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
122 static const GtkTargetEntry drag_types_dest_file[] = {
123 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
124 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
127 static const GtkTargetEntry drag_types_source[] = {
128 DRAG_TYPE ("text/contact-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
133 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
134 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
138 DRAG_CONTACT_RECEIVED,
142 static guint signals[LAST_SIGNAL];
144 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
148 individual_view_is_visible_individual (EmpathyIndividualView *self,
149 FolksIndividual *individual)
151 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
152 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
156 /* We're only giving the visibility wrt filtering here, not things like
158 if (live == NULL || gtk_widget_get_visible (GTK_WIDGET (live)) == FALSE)
161 /* check alias name */
162 str = folks_individual_get_alias (individual);
163 if (empathy_live_search_match (live, str))
166 /* check contact id, remove the @server.com part */
167 personas = folks_individual_get_personas (individual);
168 for (l = personas; l; l = l->next)
171 gchar *dup_str = NULL;
174 str = folks_persona_get_uid (l->data);
175 p = strstr (str, "@");
177 str = dup_str = g_strndup (str, p - str);
179 visible = empathy_live_search_match (live, str);
185 /* FIXME: Add more rules here, we could check phone numbers in
186 * contact's vCard for example. */
192 individual_view_filter_visible_func (GtkTreeModel *model,
196 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
197 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
198 FolksIndividual *individual = NULL;
199 gboolean is_group, is_separator, valid;
200 GtkTreeIter child_iter;
201 gboolean visible, is_online;
202 gboolean is_searching = TRUE;
204 if (priv->search_widget == NULL ||
205 !gtk_widget_get_visible (priv->search_widget))
206 is_searching = FALSE;
208 gtk_tree_model_get (model, iter,
209 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
210 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
211 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
212 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
215 if (individual != NULL)
217 visible = individual_view_is_visible_individual (self, individual);
218 g_object_unref (individual);
223 return (priv->show_offline || is_online);
229 /* Not a contact, not a separator, must be a group */
230 g_return_val_if_fail (is_group, FALSE);
232 /* only show groups which are not empty */
233 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
234 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
236 gtk_tree_model_get (model, &child_iter,
237 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
238 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
241 if (individual == NULL)
244 visible = individual_view_is_visible_individual (self, individual);
245 g_object_unref (individual);
247 /* show group if it has at least one visible contact in it */
248 if ((is_searching && visible) ||
249 (!is_searching && (priv->show_offline || is_online)))
257 individual_view_tooltip_destroy_cb (GtkWidget *widget,
258 EmpathyIndividualView *view)
260 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
262 if (priv->tooltip_widget != NULL)
264 DEBUG ("Tooltip destroyed");
265 tp_clear_object (&priv->tooltip_widget);
270 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
273 gboolean keyboard_mode,
277 EmpathyIndividualViewPriv *priv;
278 FolksIndividual *individual;
282 static gint running = 0;
283 gboolean ret = FALSE;
284 EmpathyContact *contact;
286 priv = GET_PRIV (view);
288 /* Avoid an infinite loop. See GNOME bug #574377 */
294 /* Don't show the tooltip if there's already a popup menu */
295 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
298 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
299 keyboard_mode, &model, &path, &iter))
302 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
303 gtk_tree_path_free (path);
305 gtk_tree_model_get (model, &iter,
306 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
308 if (individual == NULL)
311 contact = empathy_contact_dup_from_folks_individual (individual);
312 g_object_unref (individual);
317 if (priv->tooltip_widget == NULL)
319 priv->tooltip_widget = empathy_contact_widget_new (contact,
320 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
321 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
322 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
323 g_object_ref (priv->tooltip_widget);
324 g_signal_connect (priv->tooltip_widget, "destroy",
325 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
326 gtk_widget_show (priv->tooltip_widget);
329 empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
331 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
334 g_object_unref (contact);
342 groups_change_group_cb (GObject *source,
343 GAsyncResult *result,
346 FolksGroups *groups = FOLKS_GROUPS (source);
347 GError *error = NULL;
349 folks_groups_change_group_finish (groups, result, &error);
352 g_warning ("failed to change group: %s", error->message);
353 g_clear_error (&error);
358 individual_view_handle_drag (EmpathyIndividualView *self,
359 FolksIndividual *individual,
360 const gchar *old_group,
361 const gchar *new_group,
362 GdkDragAction action)
364 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
365 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
367 DEBUG ("individual %s dragged from '%s' to '%s'",
368 folks_individual_get_id (individual), old_group, new_group);
370 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
372 /* Mark contact as favourite */
373 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
377 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
379 /* Remove contact as favourite */
380 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
382 /* Don't try to remove it */
386 if (new_group != NULL)
387 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
388 groups_change_group_cb, NULL);
390 if (old_group != NULL && action == GDK_ACTION_MOVE)
391 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE,
392 groups_change_group_cb, NULL);
396 group_can_be_modified (const gchar *name,
397 gboolean is_fake_group,
400 /* Real groups can always be modified */
404 /* The favorite fake group can be modified so users can
405 * add/remove favorites using DnD */
406 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
409 /* We can remove contacts from the 'ungrouped' fake group */
410 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
417 individual_view_contact_drag_received (GtkWidget *self,
418 GdkDragContext *context,
421 GtkSelectionData *selection)
423 EmpathyIndividualViewPriv *priv;
424 EmpathyIndividualManager *manager;
425 FolksIndividual *individual;
426 GtkTreePath *source_path;
427 const gchar *sel_data;
428 gchar *new_group = NULL;
429 gchar *old_group = NULL;
430 gboolean new_group_is_fake, old_group_is_fake = TRUE;
432 priv = GET_PRIV (self);
434 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
435 new_group = empathy_individual_store_get_parent_group (model, path,
436 NULL, &new_group_is_fake);
438 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
441 /* Get source group information. */
444 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
448 empathy_individual_store_get_parent_group (model, source_path,
449 NULL, &old_group_is_fake);
450 gtk_tree_path_free (source_path);
454 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
457 if (!tp_strdiff (old_group, new_group))
464 /* XXX: for contacts, we used to ensure the account, create the contact
465 * factory, and then wait on the contacts. But they should already be
466 * created by this point */
468 manager = empathy_individual_manager_dup_singleton ();
469 individual = empathy_individual_manager_lookup_member (manager, sel_data);
471 if (individual == NULL)
473 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
475 g_object_unref (manager);
480 /* FIXME: We should probably wait for the cb before calling
483 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
484 old_group, new_group, gdk_drag_context_get_selected_action (context));
486 g_object_unref (G_OBJECT (manager));
494 individual_view_file_drag_received (GtkWidget *view,
495 GdkDragContext *context,
498 GtkSelectionData *selection)
501 const gchar *sel_data;
502 FolksIndividual *individual;
503 EmpathyContact *contact;
505 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
507 gtk_tree_model_get_iter (model, &iter, path);
508 gtk_tree_model_get (model, &iter,
509 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
510 if (individual == NULL)
513 contact = empathy_contact_dup_from_folks_individual (individual);
514 empathy_send_file_from_uri_list (contact, sel_data);
516 g_object_unref (individual);
517 tp_clear_object (&contact);
523 individual_view_drag_data_received (GtkWidget *view,
524 GdkDragContext *context,
527 GtkSelectionData *selection,
533 GtkTreeViewDropPosition position;
535 gboolean success = TRUE;
537 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
539 /* Get destination group information. */
540 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
541 x, y, &path, &position);
546 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
547 || info == DND_DRAG_TYPE_STRING)
549 success = individual_view_contact_drag_received (view,
550 context, model, path, selection);
552 else if (info == DND_DRAG_TYPE_URI_LIST)
554 success = individual_view_file_drag_received (view,
555 context, model, path, selection);
558 gtk_tree_path_free (path);
559 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
563 individual_view_drag_motion_cb (DragMotionData *data)
565 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
567 data->timeout_id = 0;
573 individual_view_drag_motion (GtkWidget *widget,
574 GdkDragContext *context,
579 EmpathyIndividualViewPriv *priv;
583 static DragMotionData *dm = NULL;
586 gboolean is_different = FALSE;
587 gboolean cleanup = TRUE;
588 gboolean retval = TRUE;
590 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
591 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
593 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
594 x, y, &path, NULL, NULL, NULL);
596 cleanup &= (dm == NULL);
600 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
601 is_different = ((dm == NULL) || ((dm != NULL)
602 && gtk_tree_path_compare (dm->path, path) != 0));
609 /* Coordinates don't point to an actual row, so make sure the pointer
610 and highlighting don't indicate that a drag is possible.
612 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
613 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
616 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
617 gtk_tree_model_get_iter (model, &iter, path);
619 if (target == GDK_NONE)
621 /* If target == GDK_NONE, then we don't have a target that can be
622 dropped on a contact. This means a contact drag. If we're
623 pointing to a group, highlight it. Otherwise, if the contact
624 we're pointing to is in a group, highlight that. Otherwise,
625 set the drag position to before the first row for a drag into
626 the "non-group" at the top.
628 GtkTreeIter group_iter;
630 GtkTreePath *group_path;
631 gtk_tree_model_get (model, &iter,
632 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
639 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
640 gtk_tree_model_get (model, &group_iter,
641 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
645 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
646 group_path = gtk_tree_model_get_path (model, &group_iter);
647 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
648 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
649 gtk_tree_path_free (group_path);
653 group_path = gtk_tree_path_new_first ();
654 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
655 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
656 group_path, GTK_TREE_VIEW_DROP_BEFORE);
661 /* This is a file drag, and it can only be dropped on contacts,
664 FolksIndividual *individual;
665 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
667 gtk_tree_model_get (model, &iter,
668 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
669 if (individual != NULL)
671 EmpathyContact *contact = NULL;
673 contact = empathy_contact_dup_from_folks_individual (individual);
674 caps = empathy_contact_get_capabilities (contact);
676 tp_clear_object (&contact);
679 if (individual != NULL &&
680 folks_individual_is_online (individual) &&
681 (caps & EMPATHY_CAPABILITIES_FT))
683 gdk_drag_status (context, GDK_ACTION_COPY, time_);
684 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
685 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
689 gdk_drag_status (context, 0, time_);
690 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
694 if (individual != NULL)
695 g_object_unref (individual);
698 if (!is_different && !cleanup)
703 gtk_tree_path_free (dm->path);
706 g_source_remove (dm->timeout_id);
714 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
716 dm = g_new0 (DragMotionData, 1);
718 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
719 dm->path = gtk_tree_path_copy (path);
721 dm->timeout_id = g_timeout_add_seconds (1,
722 (GSourceFunc) individual_view_drag_motion_cb, dm);
729 individual_view_drag_begin (GtkWidget *widget,
730 GdkDragContext *context)
732 EmpathyIndividualViewPriv *priv;
733 GtkTreeSelection *selection;
738 priv = GET_PRIV (widget);
740 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
743 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
744 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
747 path = gtk_tree_model_get_path (model, &iter);
748 priv->drag_row = gtk_tree_row_reference_new (model, path);
749 gtk_tree_path_free (path);
753 individual_view_drag_data_get (GtkWidget *widget,
754 GdkDragContext *context,
755 GtkSelectionData *selection,
759 EmpathyIndividualViewPriv *priv;
760 GtkTreePath *src_path;
763 FolksIndividual *individual;
764 const gchar *individual_id;
766 priv = GET_PRIV (widget);
768 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
769 if (priv->drag_row == NULL)
772 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
773 if (src_path == NULL)
776 if (!gtk_tree_model_get_iter (model, &iter, src_path))
778 gtk_tree_path_free (src_path);
782 gtk_tree_path_free (src_path);
785 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
786 if (individual == NULL)
789 individual_id = folks_individual_get_id (individual);
791 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
793 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
794 (guchar *) individual_id, strlen (individual_id) + 1);
797 g_object_unref (individual);
801 individual_view_drag_end (GtkWidget *widget,
802 GdkDragContext *context)
804 EmpathyIndividualViewPriv *priv;
806 priv = GET_PRIV (widget);
808 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
813 gtk_tree_row_reference_free (priv->drag_row);
814 priv->drag_row = NULL;
819 individual_view_drag_drop (GtkWidget *widget,
820 GdkDragContext *drag_context,
830 EmpathyIndividualView *view;
836 individual_view_popup_menu_idle_cb (gpointer user_data)
838 MenuPopupData *data = user_data;
841 menu = empathy_individual_view_get_individual_menu (data->view);
843 menu = empathy_individual_view_get_group_menu (data->view);
847 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
848 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
850 gtk_widget_show (menu);
851 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
853 g_object_ref_sink (menu);
854 g_object_unref (menu);
857 g_slice_free (MenuPopupData, data);
863 individual_view_button_press_event_cb (EmpathyIndividualView *view,
864 GdkEventButton *event,
867 if (event->button == 3)
871 data = g_slice_new (MenuPopupData);
873 data->button = event->button;
874 data->time = event->time;
875 g_idle_add (individual_view_popup_menu_idle_cb, data);
882 individual_view_key_press_event_cb (EmpathyIndividualView *view,
886 if (event->keyval == GDK_Menu)
890 data = g_slice_new (MenuPopupData);
893 data->time = event->time;
894 g_idle_add (individual_view_popup_menu_idle_cb, data);
901 individual_view_row_activated (GtkTreeView *view,
903 GtkTreeViewColumn *column)
905 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
906 FolksIndividual *individual;
907 EmpathyContact *contact = NULL;
911 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
914 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
915 gtk_tree_model_get_iter (model, &iter, path);
916 gtk_tree_model_get (model, &iter,
917 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
919 if (individual == NULL)
922 contact = empathy_contact_dup_from_folks_individual (individual);
925 DEBUG ("Starting a chat");
927 empathy_dispatcher_chat_with_contact (contact,
928 gtk_get_current_event_time (), NULL, NULL);
931 g_object_unref (individual);
932 tp_clear_object (&contact);
936 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
937 const gchar *path_string,
938 EmpathyIndividualView *view)
943 FolksIndividual *individual;
944 GdkEventButton *event;
948 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
949 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
952 gtk_tree_model_get (model, &iter,
953 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
954 if (individual == NULL)
957 event = (GdkEventButton *) gtk_get_current_event ();
959 menu = gtk_menu_new ();
960 shell = GTK_MENU_SHELL (menu);
963 item = empathy_individual_audio_call_menu_item_new (individual);
964 gtk_menu_shell_append (shell, item);
965 gtk_widget_show (item);
968 item = empathy_individual_video_call_menu_item_new (individual);
969 gtk_menu_shell_append (shell, item);
970 gtk_widget_show (item);
972 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
973 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
974 gtk_widget_show (menu);
975 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
976 event->button, event->time);
977 g_object_ref_sink (menu);
978 g_object_unref (menu);
980 g_object_unref (individual);
984 individual_view_cell_set_background (EmpathyIndividualView *view,
985 GtkCellRenderer *cell,
992 style = gtk_widget_get_style (GTK_WIDGET (view));
994 if (!is_group && is_active)
996 color = style->bg[GTK_STATE_SELECTED];
998 /* Here we take the current theme colour and add it to
999 * the colour for white and average the two. This
1000 * gives a colour which is inline with the theme but
1003 color.red = (color.red + (style->white).red) / 2;
1004 color.green = (color.green + (style->white).green) / 2;
1005 color.blue = (color.blue + (style->white).blue) / 2;
1007 g_object_set (cell, "cell-background-gdk", &color, NULL);
1010 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1014 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1015 GtkCellRenderer *cell,
1016 GtkTreeModel *model,
1018 EmpathyIndividualView *view)
1024 gtk_tree_model_get (model, iter,
1025 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1026 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1027 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1030 "visible", !is_group,
1034 tp_clear_object (&pixbuf);
1036 individual_view_cell_set_background (view, cell, is_group, is_active);
1040 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1041 GtkCellRenderer *cell,
1042 GtkTreeModel *model,
1044 EmpathyIndividualView *view)
1046 GdkPixbuf *pixbuf = NULL;
1050 gtk_tree_model_get (model, iter,
1051 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1052 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1057 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1059 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1060 GTK_ICON_SIZE_MENU);
1062 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1064 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1065 GTK_ICON_SIZE_MENU);
1070 "visible", pixbuf != NULL,
1074 tp_clear_object (&pixbuf);
1080 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1081 GtkCellRenderer *cell,
1082 GtkTreeModel *model,
1084 EmpathyIndividualView *view)
1088 gboolean can_audio, can_video;
1090 gtk_tree_model_get (model, iter,
1091 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1092 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1093 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1094 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1097 "visible", !is_group && (can_audio || can_video),
1098 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1101 individual_view_cell_set_background (view, cell, is_group, is_active);
1105 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1106 GtkCellRenderer *cell,
1107 GtkTreeModel *model,
1109 EmpathyIndividualView *view)
1112 gboolean show_avatar;
1116 gtk_tree_model_get (model, iter,
1117 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1118 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1119 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1120 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1123 "visible", !is_group && show_avatar,
1127 tp_clear_object (&pixbuf);
1129 individual_view_cell_set_background (view, cell, is_group, is_active);
1133 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_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 individual_view_cell_set_background (view, cell, is_group, is_active);
1150 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1151 GtkCellRenderer *cell,
1152 GtkTreeModel *model,
1154 EmpathyIndividualView *view)
1159 gtk_tree_model_get (model, iter,
1160 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1161 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1163 if (gtk_tree_model_iter_has_child (model, iter))
1166 gboolean row_expanded;
1168 path = gtk_tree_model_get_path (model, iter);
1170 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1171 (gtk_tree_view_column_get_tree_view (column)), path);
1172 gtk_tree_path_free (path);
1177 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1181 g_object_set (cell, "visible", FALSE, NULL);
1183 individual_view_cell_set_background (view, cell, is_group, is_active);
1187 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1192 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1193 GtkTreeModel *model;
1197 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1200 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1202 gtk_tree_model_get (model, iter,
1203 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1205 expanded = GPOINTER_TO_INT (user_data);
1206 empathy_contact_group_set_expanded (name, expanded);
1212 individual_view_start_search_cb (EmpathyIndividualView *view,
1215 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1217 if (priv->search_widget == NULL)
1220 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1221 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1223 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1229 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1231 EmpathyIndividualView *view)
1233 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1235 GtkTreeViewColumn *focus_column;
1236 GtkTreeModel *model;
1238 gboolean set_cursor = FALSE;
1240 gtk_tree_model_filter_refilter (priv->filter);
1242 /* Set cursor on the first contact. If it is already set on a group,
1243 * set it on its first child contact. Note that first child of a group
1244 * is its separator, that's why we actually set to the 2nd
1247 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1248 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1252 path = gtk_tree_path_new_from_string ("0:1");
1255 else if (gtk_tree_path_get_depth (path) < 2)
1259 gtk_tree_model_get_iter (model, &iter, path);
1260 gtk_tree_model_get (model, &iter,
1261 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1266 gtk_tree_path_down (path);
1267 gtk_tree_path_next (path);
1274 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1276 if (gtk_tree_model_get_iter (model, &iter, path))
1278 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1283 gtk_tree_path_free (path);
1287 individual_view_search_activate_cb (GtkWidget *search,
1288 EmpathyIndividualView *view)
1291 GtkTreeViewColumn *focus_column;
1293 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1296 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1297 gtk_tree_path_free (path);
1299 gtk_widget_hide (search);
1304 individual_view_search_key_navigation_cb (GtkWidget *search,
1306 EmpathyIndividualView *view)
1308 GdkEventKey *eventkey = ((GdkEventKey *) event);
1309 gboolean ret = FALSE;
1311 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1313 GdkEvent *new_event;
1315 new_event = gdk_event_copy (event);
1316 gtk_widget_grab_focus (GTK_WIDGET (view));
1317 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1318 gtk_widget_grab_focus (search);
1320 gdk_event_free (new_event);
1327 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1328 EmpathyIndividualView *view)
1330 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1331 GtkTreeModel *model;
1332 GtkTreePath *cursor_path;
1334 gboolean valid = FALSE;
1336 /* block expand or collapse handlers, they would write the
1337 * expand or collapsed setting to file otherwise */
1338 g_signal_handlers_block_by_func (view,
1339 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1340 g_signal_handlers_block_by_func (view,
1341 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1343 /* restore which groups are expanded and which are not */
1344 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1345 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1346 valid; valid = gtk_tree_model_iter_next (model, &iter))
1352 gtk_tree_model_get (model, &iter,
1353 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1354 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1363 path = gtk_tree_model_get_path (model, &iter);
1364 if ((priv->view_features &
1365 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1366 empathy_contact_group_get_expanded (name))
1368 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1372 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1375 gtk_tree_path_free (path);
1379 /* unblock expand or collapse handlers */
1380 g_signal_handlers_unblock_by_func (view,
1381 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1382 g_signal_handlers_unblock_by_func (view,
1383 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1385 /* keep the selected contact visible */
1386 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1388 if (cursor_path != NULL)
1389 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1392 gtk_tree_path_free (cursor_path);
1396 individual_view_search_show_cb (EmpathyLiveSearch *search,
1397 EmpathyIndividualView *view)
1399 /* block expand or collapse handlers during expand all, they would
1400 * write the expand or collapsed setting to file otherwise */
1401 g_signal_handlers_block_by_func (view,
1402 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1404 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1406 g_signal_handlers_unblock_by_func (view,
1407 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1411 EmpathyIndividualView *view;
1412 GtkTreeRowReference *row_ref;
1417 individual_view_expand_idle_cb (gpointer user_data)
1419 ExpandData *data = user_data;
1422 path = gtk_tree_row_reference_get_path (data->row_ref);
1426 g_signal_handlers_block_by_func (data->view,
1427 individual_view_row_expand_or_collapse_cb,
1428 GINT_TO_POINTER (data->expand));
1431 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1433 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1435 gtk_tree_path_free (path);
1437 g_signal_handlers_unblock_by_func (data->view,
1438 individual_view_row_expand_or_collapse_cb,
1439 GINT_TO_POINTER (data->expand));
1442 g_object_unref (data->view);
1443 gtk_tree_row_reference_free (data->row_ref);
1444 g_slice_free (ExpandData, data);
1450 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1453 EmpathyIndividualView *view)
1455 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1456 gboolean is_group = FALSE;
1460 gtk_tree_model_get (model, iter,
1461 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1462 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1465 if (!is_group || EMP_STR_EMPTY (name))
1471 data = g_slice_new0 (ExpandData);
1472 data->view = g_object_ref (view);
1473 data->row_ref = gtk_tree_row_reference_new (model, path);
1475 (priv->view_features &
1476 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1477 (priv->search_widget != NULL &&
1478 gtk_widget_get_visible (priv->search_widget)) ||
1479 empathy_contact_group_get_expanded (name);
1481 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1482 * gtk_tree_model_filter_refilter () */
1483 g_idle_add (individual_view_expand_idle_cb, data);
1489 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1492 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1493 GtkTreeModel *model;
1494 GtkTreePath *parent_path;
1495 GtkTreeIter parent_iter;
1497 if (gtk_tree_path_get_depth (path) < 2)
1500 /* A group row is visible if and only if at least one if its child is visible.
1501 * So when a row is inserted/deleted/changed in the base model, that could
1502 * modify the visibility of its parent in the filter model.
1505 model = GTK_TREE_MODEL (priv->store);
1506 parent_path = gtk_tree_path_copy (path);
1507 gtk_tree_path_up (parent_path);
1508 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1510 /* This tells the filter to verify the visibility of that row, and
1511 * show/hide it if necessary */
1512 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1513 parent_path, &parent_iter);
1515 gtk_tree_path_free (parent_path);
1519 individual_view_store_row_changed_cb (GtkTreeModel *model,
1522 EmpathyIndividualView *view)
1524 individual_view_verify_group_visibility (view, path);
1528 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1530 EmpathyIndividualView *view)
1532 individual_view_verify_group_visibility (view, path);
1536 individual_view_constructed (GObject *object)
1538 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1539 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1540 GtkCellRenderer *cell;
1541 GtkTreeViewColumn *col;
1544 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1545 GTK_TREE_MODEL (priv->store), NULL));
1546 gtk_tree_model_filter_set_visible_func (priv->filter,
1547 individual_view_filter_visible_func, view, NULL);
1549 g_signal_connect (priv->filter, "row-has-child-toggled",
1550 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1551 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1552 GTK_TREE_MODEL (priv->filter));
1554 tp_g_signal_connect_object (priv->store, "row-changed",
1555 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1556 tp_g_signal_connect_object (priv->store, "row-inserted",
1557 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1558 tp_g_signal_connect_object (priv->store, "row-deleted",
1559 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1562 /* Setting reorderable is a hack that gets us row previews as drag icons
1563 for free. We override all the drag handlers. It's tricky to get the
1564 position of the drag icon right in drag_begin. GtkTreeView has special
1565 voodoo for it, so we let it do the voodoo that he do.
1568 "headers-visible", FALSE,
1569 "reorderable", TRUE,
1570 "show-expanders", FALSE,
1573 col = gtk_tree_view_column_new ();
1576 cell = gtk_cell_renderer_pixbuf_new ();
1577 gtk_tree_view_column_pack_start (col, cell, FALSE);
1578 gtk_tree_view_column_set_cell_data_func (col, cell,
1579 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1589 cell = gtk_cell_renderer_pixbuf_new ();
1590 gtk_tree_view_column_pack_start (col, cell, FALSE);
1591 gtk_tree_view_column_set_cell_data_func (col, cell,
1592 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1604 cell = empathy_cell_renderer_text_new ();
1605 gtk_tree_view_column_pack_start (col, cell, TRUE);
1606 gtk_tree_view_column_set_cell_data_func (col, cell,
1607 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1609 gtk_tree_view_column_add_attribute (col, cell,
1610 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1611 gtk_tree_view_column_add_attribute (col, cell,
1612 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1613 gtk_tree_view_column_add_attribute (col, cell,
1614 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1615 gtk_tree_view_column_add_attribute (col, cell,
1616 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1617 gtk_tree_view_column_add_attribute (col, cell,
1618 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1619 gtk_tree_view_column_add_attribute (col, cell,
1620 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1622 /* Audio Call Icon */
1623 cell = empathy_cell_renderer_activatable_new ();
1624 gtk_tree_view_column_pack_start (col, cell, FALSE);
1625 gtk_tree_view_column_set_cell_data_func (col, cell,
1626 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1629 g_object_set (cell, "visible", FALSE, NULL);
1631 g_signal_connect (cell, "path-activated",
1632 G_CALLBACK (individual_view_call_activated_cb), view);
1635 cell = gtk_cell_renderer_pixbuf_new ();
1636 gtk_tree_view_column_pack_start (col, cell, FALSE);
1637 gtk_tree_view_column_set_cell_data_func (col, cell,
1638 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1650 cell = empathy_cell_renderer_expander_new ();
1651 gtk_tree_view_column_pack_end (col, cell, FALSE);
1652 gtk_tree_view_column_set_cell_data_func (col, cell,
1653 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1656 /* Actually add the column now we have added all cell renderers */
1657 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1660 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1662 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1665 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1667 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1673 individual_view_set_view_features (EmpathyIndividualView *view,
1674 EmpathyIndividualFeatureFlags features)
1676 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1677 gboolean has_tooltip;
1679 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1681 priv->view_features = features;
1683 /* Update DnD source/dest */
1684 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1686 gtk_drag_source_set (GTK_WIDGET (view),
1689 G_N_ELEMENTS (drag_types_source),
1690 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1694 gtk_drag_source_unset (GTK_WIDGET (view));
1698 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1700 gtk_drag_dest_set (GTK_WIDGET (view),
1701 GTK_DEST_DEFAULT_ALL,
1703 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1707 /* FIXME: URI could still be droped depending on FT feature */
1708 gtk_drag_dest_unset (GTK_WIDGET (view));
1711 /* Update has-tooltip */
1713 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1714 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1718 individual_view_dispose (GObject *object)
1720 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1721 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1723 tp_clear_object (&priv->store);
1724 tp_clear_object (&priv->filter);
1725 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1726 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1728 empathy_individual_view_set_live_search (view, NULL);
1730 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1734 individual_view_get_property (GObject *object,
1739 EmpathyIndividualViewPriv *priv;
1741 priv = GET_PRIV (object);
1746 g_value_set_object (value, priv->store);
1748 case PROP_VIEW_FEATURES:
1749 g_value_set_flags (value, priv->view_features);
1751 case PROP_INDIVIDUAL_FEATURES:
1752 g_value_set_flags (value, priv->individual_features);
1754 case PROP_SHOW_OFFLINE:
1755 g_value_set_boolean (value, priv->show_offline);
1758 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1764 individual_view_set_property (GObject *object,
1766 const GValue *value,
1769 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1770 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1775 priv->store = g_value_dup_object (value);
1777 case PROP_VIEW_FEATURES:
1778 individual_view_set_view_features (view, g_value_get_flags (value));
1780 case PROP_INDIVIDUAL_FEATURES:
1781 priv->individual_features = g_value_get_flags (value);
1783 case PROP_SHOW_OFFLINE:
1784 empathy_individual_view_set_show_offline (view,
1785 g_value_get_boolean (value));
1788 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1794 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1796 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1797 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1798 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1800 object_class->constructed = individual_view_constructed;
1801 object_class->dispose = individual_view_dispose;
1802 object_class->get_property = individual_view_get_property;
1803 object_class->set_property = individual_view_set_property;
1805 widget_class->drag_data_received = individual_view_drag_data_received;
1806 widget_class->drag_drop = individual_view_drag_drop;
1807 widget_class->drag_begin = individual_view_drag_begin;
1808 widget_class->drag_data_get = individual_view_drag_data_get;
1809 widget_class->drag_end = individual_view_drag_end;
1810 widget_class->drag_motion = individual_view_drag_motion;
1812 /* We use the class method to let user of this widget to connect to
1813 * the signal and stop emission of the signal so the default handler
1814 * won't be called. */
1815 tree_view_class->row_activated = individual_view_row_activated;
1817 signals[DRAG_CONTACT_RECEIVED] =
1818 g_signal_new ("drag-contact-received",
1819 G_OBJECT_CLASS_TYPE (klass),
1823 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1824 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1826 g_object_class_install_property (object_class,
1828 g_param_spec_object ("store",
1829 "The store of the view",
1830 "The store of the view",
1831 EMPATHY_TYPE_INDIVIDUAL_STORE,
1832 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1833 g_object_class_install_property (object_class,
1835 g_param_spec_flags ("view-features",
1836 "Features of the view",
1837 "Flags for all enabled features",
1838 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1839 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1840 g_object_class_install_property (object_class,
1841 PROP_INDIVIDUAL_FEATURES,
1842 g_param_spec_flags ("individual-features",
1843 "Features of the contact menu",
1844 "Flags for all enabled features for the menu",
1845 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1846 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1847 g_object_class_install_property (object_class,
1849 g_param_spec_boolean ("show-offline",
1851 "Whether contact list should display "
1852 "offline contacts", FALSE, G_PARAM_READWRITE));
1854 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1858 empathy_individual_view_init (EmpathyIndividualView *view)
1860 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1861 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1864 /* Get saved group states. */
1865 empathy_contact_groups_get_all ();
1867 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1868 empathy_individual_store_row_separator_func, NULL, NULL);
1870 /* Set up drag target lists. */
1871 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1872 G_N_ELEMENTS (drag_types_dest_file));
1874 /* Connect to tree view signals rather than override. */
1875 g_signal_connect (view, "button-press-event",
1876 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1877 g_signal_connect (view, "key-press-event",
1878 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1879 g_signal_connect (view, "row-expanded",
1880 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1881 GINT_TO_POINTER (TRUE));
1882 g_signal_connect (view, "row-collapsed",
1883 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1884 GINT_TO_POINTER (FALSE));
1885 g_signal_connect (view, "query-tooltip",
1886 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1889 EmpathyIndividualView *
1890 empathy_individual_view_new (EmpathyIndividualStore *store,
1891 EmpathyIndividualViewFeatureFlags view_features,
1892 EmpathyIndividualFeatureFlags individual_features)
1894 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1896 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1898 "individual-features", individual_features,
1899 "view-features", view_features, NULL);
1903 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1905 EmpathyIndividualViewPriv *priv;
1906 GtkTreeSelection *selection;
1908 GtkTreeModel *model;
1909 FolksIndividual *individual;
1911 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1913 priv = GET_PRIV (view);
1915 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1916 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1919 gtk_tree_model_get (model, &iter,
1920 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1925 EmpathyIndividualManagerFlags
1926 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1928 EmpathyIndividualViewPriv *priv;
1929 GtkTreeSelection *selection;
1931 GtkTreeModel *model;
1932 EmpathyIndividualFeatureFlags flags;
1934 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1936 priv = GET_PRIV (view);
1938 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1939 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1942 gtk_tree_model_get (model, &iter,
1943 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1949 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1950 gboolean *is_fake_group)
1952 EmpathyIndividualViewPriv *priv;
1953 GtkTreeSelection *selection;
1955 GtkTreeModel *model;
1960 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1962 priv = GET_PRIV (view);
1964 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1965 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1968 gtk_tree_model_get (model, &iter,
1969 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1970 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1971 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1979 if (is_fake_group != NULL)
1980 *is_fake_group = fake;
1986 individual_view_remove_dialog_show (GtkWindow *parent,
1987 const gchar *message,
1988 const gchar *secondary_text)
1993 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1994 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1995 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1996 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1997 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1998 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1999 "%s", secondary_text);
2001 gtk_widget_show (dialog);
2003 res = gtk_dialog_run (GTK_DIALOG (dialog));
2004 gtk_widget_destroy (dialog);
2006 return (res == GTK_RESPONSE_YES);
2010 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2011 EmpathyIndividualView *view)
2015 group = empathy_individual_view_get_selected_group (view, NULL);
2022 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2024 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2025 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2028 EmpathyIndividualManager *manager =
2029 empathy_individual_manager_dup_singleton ();
2030 empathy_individual_manager_remove_group (manager, group);
2031 g_object_unref (G_OBJECT (manager));
2041 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2043 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2048 gboolean is_fake_group;
2050 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2052 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2053 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2056 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2057 if (!group || is_fake_group)
2059 /* We can't alter fake groups */
2063 menu = gtk_menu_new ();
2066 if (priv->view_features &
2067 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2068 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2069 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2070 gtk_widget_show (item);
2071 g_signal_connect (item, "activate",
2072 G_CALLBACK (individual_view_group_rename_activate_cb),
2077 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2079 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2080 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2081 GTK_ICON_SIZE_MENU);
2082 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2083 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2084 gtk_widget_show (item);
2085 g_signal_connect (item, "activate",
2086 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2095 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2096 EmpathyIndividualView *view)
2098 FolksIndividual *individual;
2100 individual = empathy_individual_view_dup_selected (view);
2102 if (individual != NULL)
2107 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2110 ("Do you really want to remove the contact '%s'?"),
2111 folks_individual_get_alias (individual));
2112 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2115 EmpathyIndividualManager *manager;
2117 manager = empathy_individual_manager_dup_singleton ();
2118 empathy_individual_manager_remove (manager, individual, "");
2119 g_object_unref (G_OBJECT (manager));
2123 g_object_unref (individual);
2128 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2130 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2131 FolksIndividual *individual;
2132 GtkWidget *menu = NULL;
2135 EmpathyIndividualManagerFlags flags;
2137 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2139 individual = empathy_individual_view_dup_selected (view);
2140 if (individual == NULL)
2143 flags = empathy_individual_view_get_flags (view);
2145 menu = empathy_individual_menu_new (individual, priv->individual_features);
2147 /* Remove contact */
2148 if (priv->view_features &
2149 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2150 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2153 /* create the menu if required, or just add a separator */
2155 menu = gtk_menu_new ();
2158 item = gtk_separator_menu_item_new ();
2159 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2160 gtk_widget_show (item);
2164 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2165 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2166 GTK_ICON_SIZE_MENU);
2167 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2168 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2169 gtk_widget_show (item);
2170 g_signal_connect (item, "activate",
2171 G_CALLBACK (individual_view_remove_activate_cb), view);
2174 g_object_unref (individual);
2180 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2181 EmpathyLiveSearch *search)
2183 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2185 /* remove old handlers if old search was not null */
2186 if (priv->search_widget != NULL)
2188 g_signal_handlers_disconnect_by_func (view,
2189 individual_view_start_search_cb, NULL);
2191 g_signal_handlers_disconnect_by_func (priv->search_widget,
2192 individual_view_search_text_notify_cb, view);
2193 g_signal_handlers_disconnect_by_func (priv->search_widget,
2194 individual_view_search_activate_cb, view);
2195 g_signal_handlers_disconnect_by_func (priv->search_widget,
2196 individual_view_search_key_navigation_cb, view);
2197 g_signal_handlers_disconnect_by_func (priv->search_widget,
2198 individual_view_search_hide_cb, view);
2199 g_signal_handlers_disconnect_by_func (priv->search_widget,
2200 individual_view_search_show_cb, view);
2201 g_object_unref (priv->search_widget);
2202 priv->search_widget = NULL;
2205 /* connect handlers if new search is not null */
2208 priv->search_widget = g_object_ref (search);
2210 g_signal_connect (view, "start-interactive-search",
2211 G_CALLBACK (individual_view_start_search_cb), NULL);
2213 g_signal_connect (priv->search_widget, "notify::text",
2214 G_CALLBACK (individual_view_search_text_notify_cb), view);
2215 g_signal_connect (priv->search_widget, "activate",
2216 G_CALLBACK (individual_view_search_activate_cb), view);
2217 g_signal_connect (priv->search_widget, "key-navigation",
2218 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2219 g_signal_connect (priv->search_widget, "hide",
2220 G_CALLBACK (individual_view_search_hide_cb), view);
2221 g_signal_connect (priv->search_widget, "show",
2222 G_CALLBACK (individual_view_search_show_cb), view);
2227 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2229 EmpathyIndividualViewPriv *priv;
2231 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2233 priv = GET_PRIV (self);
2235 return (priv->search_widget != NULL &&
2236 gtk_widget_get_visible (priv->search_widget));
2240 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2242 EmpathyIndividualViewPriv *priv;
2244 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2246 priv = GET_PRIV (self);
2248 return priv->show_offline;
2252 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2253 gboolean show_offline)
2255 EmpathyIndividualViewPriv *priv;
2257 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2259 priv = GET_PRIV (self);
2261 priv->show_offline = show_offline;
2263 g_object_notify (G_OBJECT (self), "show-offline");
2264 gtk_tree_model_filter_refilter (priv->filter);