1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <folks/folks.h>
36 #include <telepathy-glib/account-manager.h>
37 #include <telepathy-glib/util.h>
39 #include <libempathy/empathy-call-factory.h>
40 #include <libempathy/empathy-individual-manager.h>
41 #include <libempathy/empathy-contact-groups.h>
42 #include <libempathy/empathy-dispatcher.h>
43 #include <libempathy/empathy-utils.h>
45 #include "empathy-individual-view.h"
46 #include "empathy-individual-menu.h"
47 #include "empathy-individual-store.h"
48 #include "empathy-images.h"
49 #include "empathy-cell-renderer-expander.h"
50 #include "empathy-cell-renderer-text.h"
51 #include "empathy-cell-renderer-activatable.h"
52 #include "empathy-ui-utils.h"
53 #include "empathy-gtk-enum-types.h"
54 #include "empathy-gtk-marshal.h"
56 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
57 #include <libempathy/empathy-debug.h>
59 /* Active users are those which have recently changed state
60 * (e.g. online, offline or from normal to a busy state).
63 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
66 EmpathyIndividualStore *store;
67 GtkTreeRowReference *drag_row;
68 EmpathyIndividualViewFeatureFlags view_features;
69 EmpathyContactFeatureFlags individual_features;
70 GtkWidget *tooltip_widget;
71 GtkTargetList *file_targets;
73 GtkTreeModelFilter *filter;
74 GtkWidget *search_widget;
75 } EmpathyIndividualViewPriv;
79 EmpathyIndividualView *view;
86 EmpathyIndividualView *view;
87 FolksIndividual *individual;
96 PROP_INDIVIDUAL_FEATURES,
99 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
100 * specific EmpathyContacts (between/in/out of Individuals) */
103 DND_DRAG_TYPE_INDIVIDUAL_ID,
104 DND_DRAG_TYPE_URI_LIST,
105 DND_DRAG_TYPE_STRING,
108 static const GtkTargetEntry drag_types_dest[] = {
109 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
110 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
111 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
112 {"text/plain", 0, DND_DRAG_TYPE_STRING},
113 {"STRING", 0, DND_DRAG_TYPE_STRING},
116 static const GtkTargetEntry drag_types_dest_file[] = {
117 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
118 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
121 static const GtkTargetEntry drag_types_source[] = {
122 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
125 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
126 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
130 DRAG_CONTACT_RECEIVED,
134 static guint signals[LAST_SIGNAL];
136 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
140 individual_view_is_visible_individual (EmpathyIndividualView *self,
141 FolksIndividual *individual)
143 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
144 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
148 g_assert (live != NULL);
150 /* check alias name */
151 str = folks_individual_get_alias (individual);
152 if (empathy_live_search_match (live, str))
155 /* check contact id, remove the @server.com part */
156 personas = folks_individual_get_personas (individual);
157 for (l = personas; l; l = l->next)
160 gchar *dup_str = NULL;
163 str = folks_persona_get_uid (l->data);
164 p = strstr (str, "@");
166 str = dup_str = g_strndup (str, p - str);
168 visible = empathy_live_search_match (live, str);
174 /* FIXME: Add more rules here, we could check phone numbers in
175 * contact's vCard for example. */
181 individual_view_filter_visible_func (GtkTreeModel *model,
185 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
186 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
187 FolksIndividual *individual = NULL;
188 gboolean is_group, is_separator, valid;
189 GtkTreeIter child_iter;
192 if (priv->search_widget == NULL ||
193 !gtk_widget_get_visible (priv->search_widget))
196 gtk_tree_model_get (model, iter,
197 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
198 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
199 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
202 if (individual != NULL)
204 visible = individual_view_is_visible_individual (self, individual);
205 g_object_unref (individual);
212 /* Not a contact, not a separator, must be a group */
213 g_return_val_if_fail (is_group, FALSE);
215 /* only show groups which are not empty */
216 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
217 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
219 gtk_tree_model_get (model, &child_iter,
220 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
223 if (individual == NULL)
226 visible = individual_view_is_visible_individual (self, individual);
227 g_object_unref (individual);
229 /* show group if it has at least one visible contact in it */
238 individual_view_tooltip_destroy_cb (GtkWidget *widget,
239 EmpathyIndividualView *view)
241 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
243 if (priv->tooltip_widget != NULL)
245 DEBUG ("Tooltip destroyed");
246 tp_clear_object (&priv->tooltip_widget);
251 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
254 gboolean keyboard_mode,
258 EmpathyIndividualViewPriv *priv;
259 FolksIndividual *individual;
263 static gint running = 0;
264 gboolean ret = FALSE;
265 EmpathyContact *contact;
267 priv = GET_PRIV (view);
269 /* Avoid an infinite loop. See GNOME bug #574377 */
275 /* Don't show the tooltip if there's already a popup menu */
276 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
279 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
280 keyboard_mode, &model, &path, &iter))
283 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
284 gtk_tree_path_free (path);
286 gtk_tree_model_get (model, &iter,
287 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
289 if (individual == NULL)
292 contact = empathy_contact_dup_from_folks_individual (individual);
293 g_object_unref (individual);
298 if (priv->tooltip_widget == NULL)
300 priv->tooltip_widget = empathy_contact_widget_new (contact,
301 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
302 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
303 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
304 g_object_ref (priv->tooltip_widget);
305 g_signal_connect (priv->tooltip_widget, "destroy",
306 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
307 gtk_widget_show (priv->tooltip_widget);
310 empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
312 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
315 g_object_unref (contact);
323 individual_view_handle_drag (EmpathyIndividualView *self,
324 FolksIndividual *individual,
325 const gchar *old_group,
326 const gchar *new_group,
327 GdkDragAction action)
329 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
330 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
332 DEBUG ("individual %s dragged from '%s' to '%s'",
333 folks_individual_get_id (individual), old_group, new_group);
335 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
337 /* Mark contact as favourite */
338 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
342 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
344 /* Remove contact as favourite */
345 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
347 /* Don't try to remove it */
351 if (new_group != NULL)
352 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE);
354 if (old_group != NULL && action == GDK_ACTION_MOVE)
355 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE);
359 group_can_be_modified (const gchar *name,
360 gboolean is_fake_group,
363 /* Real groups can always be modified */
367 /* The favorite fake group can be modified so users can
368 * add/remove favorites using DnD */
369 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
372 /* We can remove contacts from the 'ungrouped' fake group */
373 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
380 individual_view_contact_drag_received (GtkWidget *self,
381 GdkDragContext *context,
384 GtkSelectionData *selection)
386 EmpathyIndividualViewPriv *priv;
387 EmpathyIndividualManager *manager;
388 FolksIndividual *individual;
389 GtkTreePath *source_path;
390 const gchar *sel_data;
391 gchar *new_group = NULL;
392 gchar *old_group = NULL;
393 gboolean new_group_is_fake, old_group_is_fake = TRUE;
395 priv = GET_PRIV (self);
397 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
398 new_group = empathy_individual_store_get_parent_group (model, path,
399 NULL, &new_group_is_fake);
401 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
404 /* Get source group information. */
407 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
411 empathy_individual_store_get_parent_group (model, source_path,
412 NULL, &old_group_is_fake);
413 gtk_tree_path_free (source_path);
417 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
420 if (!tp_strdiff (old_group, new_group))
427 /* XXX: for contacts, we used to ensure the account, create the contact
428 * factory, and then wait on the contacts. But they should already be
429 * created by this point */
431 manager = empathy_individual_manager_dup_singleton ();
432 individual = empathy_individual_manager_lookup_member (manager, sel_data);
434 if (individual == NULL)
436 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
438 g_object_unref (manager);
443 /* FIXME: We should probably wait for the cb before calling
446 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
447 old_group, new_group, gdk_drag_context_get_selected_action (context));
449 g_object_unref (G_OBJECT (manager));
457 individual_view_file_drag_received (GtkWidget *view,
458 GdkDragContext *context,
461 GtkSelectionData *selection)
464 const gchar *sel_data;
465 FolksIndividual *individual;
466 EmpathyContact *contact;
468 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
470 gtk_tree_model_get_iter (model, &iter, path);
471 gtk_tree_model_get (model, &iter,
472 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
473 if (individual == NULL)
476 contact = empathy_contact_dup_from_folks_individual (individual);
477 empathy_send_file_from_uri_list (contact, sel_data);
479 g_object_unref (individual);
480 tp_clear_object (&contact);
486 individual_view_drag_data_received (GtkWidget *view,
487 GdkDragContext *context,
490 GtkSelectionData *selection,
496 GtkTreeViewDropPosition position;
498 gboolean success = TRUE;
500 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
502 /* Get destination group information. */
503 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
504 x, y, &path, &position);
509 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
510 || info == DND_DRAG_TYPE_STRING)
512 success = individual_view_contact_drag_received (view,
513 context, model, path, selection);
515 else if (info == DND_DRAG_TYPE_URI_LIST)
517 success = individual_view_file_drag_received (view,
518 context, model, path, selection);
521 gtk_tree_path_free (path);
522 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
526 individual_view_drag_motion_cb (DragMotionData *data)
528 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
530 data->timeout_id = 0;
536 individual_view_drag_motion (GtkWidget *widget,
537 GdkDragContext *context,
542 EmpathyIndividualViewPriv *priv;
546 static DragMotionData *dm = NULL;
549 gboolean is_different = FALSE;
550 gboolean cleanup = TRUE;
551 gboolean retval = TRUE;
553 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
554 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
556 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
557 x, y, &path, NULL, NULL, NULL);
559 cleanup &= (dm == NULL);
563 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
564 is_different = ((dm == NULL) || ((dm != NULL)
565 && gtk_tree_path_compare (dm->path, path) != 0));
572 /* Coordinates don't point to an actual row, so make sure the pointer
573 and highlighting don't indicate that a drag is possible.
575 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
576 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
579 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
580 gtk_tree_model_get_iter (model, &iter, path);
582 if (target == GDK_NONE)
584 /* If target == GDK_NONE, then we don't have a target that can be
585 dropped on a contact. This means a contact drag. If we're
586 pointing to a group, highlight it. Otherwise, if the contact
587 we're pointing to is in a group, highlight that. Otherwise,
588 set the drag position to before the first row for a drag into
589 the "non-group" at the top.
591 GtkTreeIter group_iter;
593 GtkTreePath *group_path;
594 gtk_tree_model_get (model, &iter,
595 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
602 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
603 gtk_tree_model_get (model, &group_iter,
604 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
608 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
609 group_path = gtk_tree_model_get_path (model, &group_iter);
610 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
611 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
612 gtk_tree_path_free (group_path);
616 group_path = gtk_tree_path_new_first ();
617 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
618 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
619 group_path, GTK_TREE_VIEW_DROP_BEFORE);
624 /* This is a file drag, and it can only be dropped on contacts,
627 FolksIndividual *individual;
628 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
630 gtk_tree_model_get (model, &iter,
631 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
632 if (individual != NULL)
634 EmpathyContact *contact = NULL;
636 contact = empathy_contact_dup_from_folks_individual (individual);
637 caps = empathy_contact_get_capabilities (contact);
639 tp_clear_object (&contact);
642 if (individual != NULL &&
643 folks_individual_is_online (individual) &&
644 (caps & EMPATHY_CAPABILITIES_FT))
646 gdk_drag_status (context, GDK_ACTION_COPY, time_);
647 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
648 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
652 gdk_drag_status (context, 0, time_);
653 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
657 if (individual != NULL)
658 g_object_unref (individual);
661 if (!is_different && !cleanup)
666 gtk_tree_path_free (dm->path);
669 g_source_remove (dm->timeout_id);
677 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
679 dm = g_new0 (DragMotionData, 1);
681 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
682 dm->path = gtk_tree_path_copy (path);
684 dm->timeout_id = g_timeout_add_seconds (1,
685 (GSourceFunc) individual_view_drag_motion_cb, dm);
692 individual_view_drag_begin (GtkWidget *widget,
693 GdkDragContext *context)
695 EmpathyIndividualViewPriv *priv;
696 GtkTreeSelection *selection;
701 priv = GET_PRIV (widget);
703 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
706 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
707 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
710 path = gtk_tree_model_get_path (model, &iter);
711 priv->drag_row = gtk_tree_row_reference_new (model, path);
712 gtk_tree_path_free (path);
716 individual_view_drag_data_get (GtkWidget *widget,
717 GdkDragContext *context,
718 GtkSelectionData *selection,
722 EmpathyIndividualViewPriv *priv;
723 GtkTreePath *src_path;
726 FolksIndividual *individual;
727 const gchar *individual_id;
729 priv = GET_PRIV (widget);
731 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
732 if (priv->drag_row == NULL)
735 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
736 if (src_path == NULL)
739 if (!gtk_tree_model_get_iter (model, &iter, src_path))
741 gtk_tree_path_free (src_path);
745 gtk_tree_path_free (src_path);
748 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
749 if (individual == NULL)
752 individual_id = folks_individual_get_id (individual);
756 case DND_DRAG_TYPE_INDIVIDUAL_ID:
757 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
758 (guchar *) individual_id, strlen (individual_id) + 1);
762 g_object_unref (individual);
766 individual_view_drag_end (GtkWidget *widget,
767 GdkDragContext *context)
769 EmpathyIndividualViewPriv *priv;
771 priv = GET_PRIV (widget);
773 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
778 gtk_tree_row_reference_free (priv->drag_row);
779 priv->drag_row = NULL;
784 individual_view_drag_drop (GtkWidget *widget,
785 GdkDragContext *drag_context,
795 EmpathyIndividualView *view;
801 individual_view_popup_menu_idle_cb (gpointer user_data)
803 MenuPopupData *data = user_data;
806 menu = empathy_individual_view_get_individual_menu (data->view);
808 menu = empathy_individual_view_get_group_menu (data->view);
812 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
813 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
815 gtk_widget_show (menu);
816 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
818 g_object_ref_sink (menu);
819 g_object_unref (menu);
822 g_slice_free (MenuPopupData, data);
828 individual_view_button_press_event_cb (EmpathyIndividualView *view,
829 GdkEventButton *event,
832 if (event->button == 3)
836 data = g_slice_new (MenuPopupData);
838 data->button = event->button;
839 data->time = event->time;
840 g_idle_add (individual_view_popup_menu_idle_cb, data);
847 individual_view_key_press_event_cb (EmpathyIndividualView *view,
851 if (event->keyval == GDK_Menu)
855 data = g_slice_new (MenuPopupData);
858 data->time = event->time;
859 g_idle_add (individual_view_popup_menu_idle_cb, data);
866 individual_view_row_activated (GtkTreeView *view,
868 GtkTreeViewColumn *column)
870 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
871 FolksIndividual *individual;
872 EmpathyContact *contact = NULL;
876 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
879 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
880 gtk_tree_model_get_iter (model, &iter, path);
881 gtk_tree_model_get (model, &iter,
882 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
884 if (individual == NULL)
887 contact = empathy_contact_dup_from_folks_individual (individual);
890 DEBUG ("Starting a chat");
892 empathy_dispatcher_chat_with_contact (contact,
893 gtk_get_current_event_time (), NULL, NULL);
896 g_object_unref (individual);
897 tp_clear_object (&contact);
901 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
902 const gchar *path_string,
903 EmpathyIndividualView *view)
908 FolksIndividual *individual;
909 GdkEventButton *event;
913 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
914 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
917 gtk_tree_model_get (model, &iter,
918 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
919 if (individual == NULL)
922 event = (GdkEventButton *) gtk_get_current_event ();
924 menu = gtk_menu_new ();
925 shell = GTK_MENU_SHELL (menu);
928 item = empathy_individual_audio_call_menu_item_new (individual);
929 gtk_menu_shell_append (shell, item);
930 gtk_widget_show (item);
933 item = empathy_individual_video_call_menu_item_new (individual);
934 gtk_menu_shell_append (shell, item);
935 gtk_widget_show (item);
937 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
938 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
939 gtk_widget_show (menu);
940 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
941 event->button, event->time);
942 g_object_ref_sink (menu);
943 g_object_unref (menu);
945 g_object_unref (individual);
949 individual_view_cell_set_background (EmpathyIndividualView *view,
950 GtkCellRenderer *cell,
957 style = gtk_widget_get_style (GTK_WIDGET (view));
959 if (!is_group && is_active)
961 color = style->bg[GTK_STATE_SELECTED];
963 /* Here we take the current theme colour and add it to
964 * the colour for white and average the two. This
965 * gives a colour which is inline with the theme but
968 color.red = (color.red + (style->white).red) / 2;
969 color.green = (color.green + (style->white).green) / 2;
970 color.blue = (color.blue + (style->white).blue) / 2;
972 g_object_set (cell, "cell-background-gdk", &color, NULL);
975 g_object_set (cell, "cell-background-gdk", NULL, NULL);
979 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
980 GtkCellRenderer *cell,
983 EmpathyIndividualView *view)
989 gtk_tree_model_get (model, iter,
990 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
991 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
992 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
995 "visible", !is_group,
999 tp_clear_object (&pixbuf);
1001 individual_view_cell_set_background (view, cell, is_group, is_active);
1005 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1006 GtkCellRenderer *cell,
1007 GtkTreeModel *model,
1009 EmpathyIndividualView *view)
1011 GdkPixbuf *pixbuf = NULL;
1015 gtk_tree_model_get (model, iter,
1016 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1017 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1022 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1024 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1025 GTK_ICON_SIZE_MENU);
1027 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1029 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1030 GTK_ICON_SIZE_MENU);
1035 "visible", pixbuf != NULL,
1039 tp_clear_object (&pixbuf);
1045 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1046 GtkCellRenderer *cell,
1047 GtkTreeModel *model,
1049 EmpathyIndividualView *view)
1053 gboolean can_audio, can_video;
1055 gtk_tree_model_get (model, iter,
1056 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1057 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1058 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1059 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1062 "visible", !is_group && (can_audio || can_video),
1063 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1066 individual_view_cell_set_background (view, cell, is_group, is_active);
1070 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1071 GtkCellRenderer *cell,
1072 GtkTreeModel *model,
1074 EmpathyIndividualView *view)
1077 gboolean show_avatar;
1081 gtk_tree_model_get (model, iter,
1082 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1083 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1084 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1085 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1088 "visible", !is_group && show_avatar,
1092 tp_clear_object (&pixbuf);
1094 individual_view_cell_set_background (view, cell, is_group, is_active);
1098 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1099 GtkCellRenderer *cell,
1100 GtkTreeModel *model,
1102 EmpathyIndividualView *view)
1107 gtk_tree_model_get (model, iter,
1108 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1109 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1111 individual_view_cell_set_background (view, cell, is_group, is_active);
1115 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1116 GtkCellRenderer *cell,
1117 GtkTreeModel *model,
1119 EmpathyIndividualView *view)
1124 gtk_tree_model_get (model, iter,
1125 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1126 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1128 if (gtk_tree_model_iter_has_child (model, iter))
1131 gboolean row_expanded;
1133 path = gtk_tree_model_get_path (model, iter);
1135 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1136 (gtk_tree_view_column_get_tree_view (column)), path);
1137 gtk_tree_path_free (path);
1142 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1146 g_object_set (cell, "visible", FALSE, NULL);
1148 individual_view_cell_set_background (view, cell, is_group, is_active);
1152 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1157 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1158 GtkTreeModel *model;
1162 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1165 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1167 gtk_tree_model_get (model, iter,
1168 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1170 expanded = GPOINTER_TO_INT (user_data);
1171 empathy_contact_group_set_expanded (name, expanded);
1177 individual_view_start_search_cb (EmpathyIndividualView *view,
1180 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1182 if (priv->search_widget == NULL)
1185 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1186 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1188 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1194 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1196 EmpathyIndividualView *view)
1198 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1200 GtkTreeViewColumn *focus_column;
1201 GtkTreeModel *model;
1203 gboolean set_cursor = FALSE;
1205 gtk_tree_model_filter_refilter (priv->filter);
1207 /* Set cursor on the first contact. If it is already set on a group,
1208 * set it on its first child contact. Note that first child of a group
1209 * is its separator, that's why we actually set to the 2nd
1212 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1213 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1217 path = gtk_tree_path_new_from_string ("0:1");
1220 else if (gtk_tree_path_get_depth (path) < 2)
1224 gtk_tree_model_get_iter (model, &iter, path);
1225 gtk_tree_model_get (model, &iter,
1226 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1231 gtk_tree_path_down (path);
1232 gtk_tree_path_next (path);
1239 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1241 if (gtk_tree_model_get_iter (model, &iter, path))
1243 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1248 gtk_tree_path_free (path);
1252 individual_view_search_activate_cb (GtkWidget *search,
1253 EmpathyIndividualView *view)
1256 GtkTreeViewColumn *focus_column;
1258 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1261 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1262 gtk_tree_path_free (path);
1264 gtk_widget_hide (search);
1269 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1270 EmpathyIndividualView *view)
1272 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1273 GtkTreeModel *model;
1275 gboolean valid = FALSE;
1277 /* block expand or collapse handlers, they would write the
1278 * expand or collapsed setting to file otherwise */
1279 g_signal_handlers_block_by_func (view,
1280 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1281 g_signal_handlers_block_by_func (view,
1282 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1284 /* restore which groups are expanded and which are not */
1285 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1286 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1287 valid; valid = gtk_tree_model_iter_next (model, &iter))
1293 gtk_tree_model_get (model, &iter,
1294 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1295 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1304 path = gtk_tree_model_get_path (model, &iter);
1305 if ((priv->view_features &
1306 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1307 empathy_contact_group_get_expanded (name))
1309 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1313 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1316 gtk_tree_path_free (path);
1320 /* unblock expand or collapse handlers */
1321 g_signal_handlers_unblock_by_func (view,
1322 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1323 g_signal_handlers_unblock_by_func (view,
1324 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1328 individual_view_search_show_cb (EmpathyLiveSearch *search,
1329 EmpathyIndividualView *view)
1331 /* block expand or collapse handlers during expand all, they would
1332 * write the expand or collapsed setting to file otherwise */
1333 g_signal_handlers_block_by_func (view,
1334 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1336 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1338 g_signal_handlers_unblock_by_func (view,
1339 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1343 EmpathyIndividualView *view;
1344 GtkTreeRowReference *row_ref;
1349 individual_view_expand_idle_cb (gpointer user_data)
1351 ExpandData *data = user_data;
1354 path = gtk_tree_row_reference_get_path (data->row_ref);
1358 g_signal_handlers_block_by_func (data->view,
1359 individual_view_row_expand_or_collapse_cb,
1360 GINT_TO_POINTER (data->expand));
1363 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1365 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1367 gtk_tree_path_free (path);
1369 g_signal_handlers_unblock_by_func (data->view,
1370 individual_view_row_expand_or_collapse_cb,
1371 GINT_TO_POINTER (data->expand));
1374 g_object_unref (data->view);
1375 gtk_tree_row_reference_free (data->row_ref);
1376 g_slice_free (ExpandData, data);
1382 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1385 EmpathyIndividualView *view)
1387 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1388 gboolean is_group = FALSE;
1392 gtk_tree_model_get (model, iter,
1393 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1394 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1397 if (!is_group || EMP_STR_EMPTY (name))
1403 data = g_slice_new0 (ExpandData);
1404 data->view = g_object_ref (view);
1405 data->row_ref = gtk_tree_row_reference_new (model, path);
1407 (priv->view_features &
1408 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1409 (priv->search_widget != NULL &&
1410 gtk_widget_get_visible (priv->search_widget)) ||
1411 empathy_contact_group_get_expanded (name);
1413 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1414 * gtk_tree_model_filter_refilter () */
1415 g_idle_add (individual_view_expand_idle_cb, data);
1421 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1424 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1425 GtkTreeModel *model;
1426 GtkTreePath *parent_path;
1427 GtkTreeIter parent_iter;
1429 if (gtk_tree_path_get_depth (path) < 2)
1432 /* A group row is visible if and only if at least one if its child is visible.
1433 * So when a row is inserted/deleted/changed in the base model, that could
1434 * modify the visibility of its parent in the filter model.
1437 model = GTK_TREE_MODEL (priv->store);
1438 parent_path = gtk_tree_path_copy (path);
1439 gtk_tree_path_up (parent_path);
1440 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1442 /* This tells the filter to verify the visibility of that row, and
1443 * show/hide it if necessary */
1444 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1445 parent_path, &parent_iter);
1447 gtk_tree_path_free (parent_path);
1451 individual_view_store_row_changed_cb (GtkTreeModel *model,
1454 EmpathyIndividualView *view)
1456 individual_view_verify_group_visibility (view, path);
1460 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1462 EmpathyIndividualView *view)
1464 individual_view_verify_group_visibility (view, path);
1468 individual_view_constructed (GObject *object)
1470 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1471 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1472 GtkCellRenderer *cell;
1473 GtkTreeViewColumn *col;
1476 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1477 GTK_TREE_MODEL (priv->store), NULL));
1478 gtk_tree_model_filter_set_visible_func (priv->filter,
1479 individual_view_filter_visible_func, view, NULL);
1481 g_signal_connect (priv->store, "row-has-child-toggled",
1482 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1483 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1484 GTK_TREE_MODEL (priv->filter));
1486 tp_g_signal_connect_object (priv->store, "row-changed",
1487 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1488 tp_g_signal_connect_object (priv->store, "row-inserted",
1489 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1490 tp_g_signal_connect_object (priv->store, "row-deleted",
1491 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1493 tp_g_signal_connect_object (priv->store, "row-changed",
1494 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1495 tp_g_signal_connect_object (priv->store, "row-inserted",
1496 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1497 tp_g_signal_connect_object (priv->store, "row-deleted",
1498 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1501 /* Setting reorderable is a hack that gets us row previews as drag icons
1502 for free. We override all the drag handlers. It's tricky to get the
1503 position of the drag icon right in drag_begin. GtkTreeView has special
1504 voodoo for it, so we let it do the voodoo that he do.
1507 "headers-visible", FALSE,
1508 "reorderable", TRUE,
1509 "show-expanders", FALSE,
1512 col = gtk_tree_view_column_new ();
1515 cell = gtk_cell_renderer_pixbuf_new ();
1516 gtk_tree_view_column_pack_start (col, cell, FALSE);
1517 gtk_tree_view_column_set_cell_data_func (col, cell,
1518 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1528 cell = gtk_cell_renderer_pixbuf_new ();
1529 gtk_tree_view_column_pack_start (col, cell, FALSE);
1530 gtk_tree_view_column_set_cell_data_func (col, cell,
1531 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1543 cell = empathy_cell_renderer_text_new ();
1544 gtk_tree_view_column_pack_start (col, cell, TRUE);
1545 gtk_tree_view_column_set_cell_data_func (col, cell,
1546 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1548 gtk_tree_view_column_add_attribute (col, cell,
1549 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1550 gtk_tree_view_column_add_attribute (col, cell,
1551 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1552 gtk_tree_view_column_add_attribute (col, cell,
1553 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1554 gtk_tree_view_column_add_attribute (col, cell,
1555 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1556 gtk_tree_view_column_add_attribute (col, cell,
1557 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1558 gtk_tree_view_column_add_attribute (col, cell,
1559 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1561 /* Audio Call Icon */
1562 cell = empathy_cell_renderer_activatable_new ();
1563 gtk_tree_view_column_pack_start (col, cell, FALSE);
1564 gtk_tree_view_column_set_cell_data_func (col, cell,
1565 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1568 g_object_set (cell, "visible", FALSE, NULL);
1570 g_signal_connect (cell, "path-activated",
1571 G_CALLBACK (individual_view_call_activated_cb), view);
1574 cell = gtk_cell_renderer_pixbuf_new ();
1575 gtk_tree_view_column_pack_start (col, cell, FALSE);
1576 gtk_tree_view_column_set_cell_data_func (col, cell,
1577 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1589 cell = empathy_cell_renderer_expander_new ();
1590 gtk_tree_view_column_pack_end (col, cell, FALSE);
1591 gtk_tree_view_column_set_cell_data_func (col, cell,
1592 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1595 /* Actually add the column now we have added all cell renderers */
1596 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1599 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1601 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1604 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1606 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1612 individual_view_set_view_features (EmpathyIndividualView *view,
1613 EmpathyIndividualFeatureFlags features)
1615 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1616 gboolean has_tooltip;
1618 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1620 priv->view_features = features;
1622 /* Update DnD source/dest */
1623 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1625 gtk_drag_source_set (GTK_WIDGET (view),
1628 G_N_ELEMENTS (drag_types_source),
1629 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1633 gtk_drag_source_unset (GTK_WIDGET (view));
1637 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1639 gtk_drag_dest_set (GTK_WIDGET (view),
1640 GTK_DEST_DEFAULT_ALL,
1642 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1646 /* FIXME: URI could still be droped depending on FT feature */
1647 gtk_drag_dest_unset (GTK_WIDGET (view));
1650 /* Update has-tooltip */
1652 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1653 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1657 individual_view_dispose (GObject *object)
1659 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1660 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1662 tp_clear_object (&priv->store);
1663 tp_clear_object (&priv->filter);
1664 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1665 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1667 empathy_individual_view_set_live_search (view, NULL);
1669 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1673 individual_view_get_property (GObject *object,
1678 EmpathyIndividualViewPriv *priv;
1680 priv = GET_PRIV (object);
1685 g_value_set_object (value, priv->store);
1687 case PROP_VIEW_FEATURES:
1688 g_value_set_flags (value, priv->view_features);
1690 case PROP_INDIVIDUAL_FEATURES:
1691 g_value_set_flags (value, priv->individual_features);
1694 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1700 individual_view_set_property (GObject *object,
1702 const GValue *value,
1705 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1706 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1711 priv->store = g_value_dup_object (value);
1713 case PROP_VIEW_FEATURES:
1714 individual_view_set_view_features (view, g_value_get_flags (value));
1716 case PROP_INDIVIDUAL_FEATURES:
1717 priv->individual_features = g_value_get_flags (value);
1720 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1726 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1728 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1729 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1730 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1732 object_class->constructed = individual_view_constructed;
1733 object_class->dispose = individual_view_dispose;
1734 object_class->get_property = individual_view_get_property;
1735 object_class->set_property = individual_view_set_property;
1737 widget_class->drag_data_received = individual_view_drag_data_received;
1738 widget_class->drag_drop = individual_view_drag_drop;
1739 widget_class->drag_begin = individual_view_drag_begin;
1740 widget_class->drag_data_get = individual_view_drag_data_get;
1741 widget_class->drag_end = individual_view_drag_end;
1742 widget_class->drag_motion = individual_view_drag_motion;
1744 /* We use the class method to let user of this widget to connect to
1745 * the signal and stop emission of the signal so the default handler
1746 * won't be called. */
1747 tree_view_class->row_activated = individual_view_row_activated;
1749 signals[DRAG_CONTACT_RECEIVED] =
1750 g_signal_new ("drag-contact-received",
1751 G_OBJECT_CLASS_TYPE (klass),
1755 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1756 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1758 g_object_class_install_property (object_class,
1760 g_param_spec_object ("store",
1761 "The store of the view",
1762 "The store of the view",
1763 EMPATHY_TYPE_INDIVIDUAL_STORE,
1764 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1765 g_object_class_install_property (object_class,
1767 g_param_spec_flags ("view-features",
1768 "Features of the view",
1769 "Flags for all enabled features",
1770 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1771 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1772 g_object_class_install_property (object_class,
1773 PROP_INDIVIDUAL_FEATURES,
1774 g_param_spec_flags ("individual-features",
1775 "Features of the contact menu",
1776 "Flags for all enabled features for the menu",
1777 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1778 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1780 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1784 empathy_individual_view_init (EmpathyIndividualView *view)
1786 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1787 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1790 /* Get saved group states. */
1791 empathy_contact_groups_get_all ();
1793 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1794 empathy_individual_store_row_separator_func, NULL, NULL);
1796 /* Set up drag target lists. */
1797 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1798 G_N_ELEMENTS (drag_types_dest_file));
1800 /* Connect to tree view signals rather than override. */
1801 g_signal_connect (view, "button-press-event",
1802 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1803 g_signal_connect (view, "key-press-event",
1804 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1805 g_signal_connect (view, "row-expanded",
1806 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1807 GINT_TO_POINTER (TRUE));
1808 g_signal_connect (view, "row-collapsed",
1809 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1810 GINT_TO_POINTER (FALSE));
1811 g_signal_connect (view, "query-tooltip",
1812 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1815 EmpathyIndividualView *
1816 empathy_individual_view_new (EmpathyIndividualStore *store,
1817 EmpathyIndividualViewFeatureFlags view_features,
1818 EmpathyIndividualFeatureFlags individual_features)
1820 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1822 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1824 "individual-features", individual_features,
1825 "view-features", view_features, NULL);
1829 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1831 EmpathyIndividualViewPriv *priv;
1832 GtkTreeSelection *selection;
1834 GtkTreeModel *model;
1835 FolksIndividual *individual;
1837 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1839 priv = GET_PRIV (view);
1841 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1842 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1845 gtk_tree_model_get (model, &iter,
1846 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1851 EmpathyIndividualManagerFlags
1852 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1854 EmpathyIndividualViewPriv *priv;
1855 GtkTreeSelection *selection;
1857 GtkTreeModel *model;
1858 EmpathyIndividualFeatureFlags flags;
1860 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1862 priv = GET_PRIV (view);
1864 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1865 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1868 gtk_tree_model_get (model, &iter,
1869 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1875 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1876 gboolean *is_fake_group)
1878 EmpathyIndividualViewPriv *priv;
1879 GtkTreeSelection *selection;
1881 GtkTreeModel *model;
1886 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1888 priv = GET_PRIV (view);
1890 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1891 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1894 gtk_tree_model_get (model, &iter,
1895 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1896 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1897 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1905 if (is_fake_group != NULL)
1906 *is_fake_group = fake;
1912 individual_view_remove_dialog_show (GtkWindow *parent,
1913 const gchar *message,
1914 const gchar *secondary_text)
1919 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1920 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1921 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1922 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1923 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1924 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1925 "%s", secondary_text);
1927 gtk_widget_show (dialog);
1929 res = gtk_dialog_run (GTK_DIALOG (dialog));
1930 gtk_widget_destroy (dialog);
1932 return (res == GTK_RESPONSE_YES);
1936 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1937 EmpathyIndividualView *view)
1941 group = empathy_individual_view_get_selected_group (view, NULL);
1948 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1950 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1951 if (individual_view_remove_dialog_show (parent, _("Removing group"),
1954 EmpathyIndividualManager *manager =
1955 empathy_individual_manager_dup_singleton ();
1956 empathy_individual_manager_remove_group (manager, group);
1957 g_object_unref (G_OBJECT (manager));
1967 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
1969 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1974 gboolean is_fake_group;
1976 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1978 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
1979 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
1982 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
1983 if (!group || is_fake_group)
1985 /* We can't alter fake groups */
1989 menu = gtk_menu_new ();
1992 if (priv->view_features &
1993 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
1994 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1995 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1996 gtk_widget_show (item);
1997 g_signal_connect (item, "activate",
1998 G_CALLBACK (individual_view_group_rename_activate_cb),
2003 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2005 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2006 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2007 GTK_ICON_SIZE_MENU);
2008 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2009 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2010 gtk_widget_show (item);
2011 g_signal_connect (item, "activate",
2012 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2021 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2022 EmpathyIndividualView *view)
2024 FolksIndividual *individual;
2026 individual = empathy_individual_view_dup_selected (view);
2028 if (individual != NULL)
2033 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2036 ("Do you really want to remove the contact '%s'?"),
2037 folks_individual_get_alias (individual));
2038 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2041 EmpathyIndividualManager *manager;
2043 manager = empathy_individual_manager_dup_singleton ();
2044 empathy_individual_manager_remove (manager, individual, "");
2045 g_object_unref (G_OBJECT (manager));
2049 g_object_unref (individual);
2054 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2056 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2057 FolksIndividual *individual;
2058 GtkWidget *menu = NULL;
2061 EmpathyIndividualManagerFlags flags;
2063 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2065 individual = empathy_individual_view_dup_selected (view);
2066 if (individual == NULL)
2069 flags = empathy_individual_view_get_flags (view);
2071 menu = empathy_individual_menu_new (individual, priv->individual_features);
2073 /* Remove contact */
2074 if (priv->view_features &
2075 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2076 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2079 /* create the menu if required, or just add a separator */
2081 menu = gtk_menu_new ();
2084 item = gtk_separator_menu_item_new ();
2085 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2086 gtk_widget_show (item);
2090 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2091 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2092 GTK_ICON_SIZE_MENU);
2093 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2094 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2095 gtk_widget_show (item);
2096 g_signal_connect (item, "activate",
2097 G_CALLBACK (individual_view_remove_activate_cb), view);
2100 g_object_unref (individual);
2106 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2107 EmpathyLiveSearch *search)
2109 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2111 /* remove old handlers if old search was not null */
2112 if (priv->search_widget != NULL)
2114 g_signal_handlers_disconnect_by_func (view,
2115 individual_view_start_search_cb, NULL);
2117 g_signal_handlers_disconnect_by_func (priv->search_widget,
2118 individual_view_search_text_notify_cb, view);
2119 g_signal_handlers_disconnect_by_func (priv->search_widget,
2120 individual_view_search_activate_cb, view);
2121 g_signal_handlers_disconnect_by_func (priv->search_widget,
2122 individual_view_search_hide_cb, view);
2123 g_signal_handlers_disconnect_by_func (priv->search_widget,
2124 individual_view_search_show_cb, view);
2125 g_object_unref (priv->search_widget);
2126 priv->search_widget = NULL;
2129 /* connect handlers if new search is not null */
2132 priv->search_widget = g_object_ref (search);
2134 g_signal_connect (view, "start-interactive-search",
2135 G_CALLBACK (individual_view_start_search_cb), NULL);
2137 g_signal_connect (priv->search_widget, "notify::text",
2138 G_CALLBACK (individual_view_search_text_notify_cb), view);
2139 g_signal_connect (priv->search_widget, "activate",
2140 G_CALLBACK (individual_view_search_activate_cb), view);
2141 g_signal_connect (priv->search_widget, "hide",
2142 G_CALLBACK (individual_view_search_hide_cb), view);
2143 g_signal_connect (priv->search_widget, "show",
2144 G_CALLBACK (individual_view_search_show_cb), view);