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);
147 gchar *dup_str = NULL;
151 g_assert (live != NULL);
153 /* check alias name */
154 str = folks_individual_get_alias (individual);
155 if (empathy_live_search_match (live, str))
158 /* check contact id, remove the @server.com part */
159 personas = folks_individual_get_personas (individual);
160 for (l = personas; l; l = l->next)
162 str = folks_persona_get_uid (l->data);
163 p = strstr (str, "@");
165 str = dup_str = g_strndup (str, p - str);
167 visible = empathy_live_search_match (live, str);
173 /* FIXME: Add more rules here, we could check phone numbers in
174 * contact's vCard for example. */
180 individual_view_filter_visible_func (GtkTreeModel *model,
184 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
185 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
186 FolksIndividual *individual = NULL;
187 gboolean is_group, is_separator, valid;
188 GtkTreeIter child_iter;
191 if (priv->search_widget == NULL ||
192 !gtk_widget_get_visible (priv->search_widget))
195 gtk_tree_model_get (model, iter,
196 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
197 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
198 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
201 if (individual != NULL)
203 visible = individual_view_is_visible_individual (self, individual);
204 g_object_unref (individual);
211 /* Not a contact, not a separator, must be a group */
212 g_return_val_if_fail (is_group, FALSE);
214 /* only show groups which are not empty */
215 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
216 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
218 gtk_tree_model_get (model, &child_iter,
219 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
222 if (individual == NULL)
225 visible = individual_view_is_visible_individual (self, individual);
226 g_object_unref (individual);
228 /* show group if it has at least one visible contact in it */
237 individual_view_tooltip_destroy_cb (GtkWidget *widget,
238 EmpathyIndividualView *view)
240 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
242 if (priv->tooltip_widget != NULL)
244 DEBUG ("Tooltip destroyed");
245 g_object_unref (priv->tooltip_widget);
246 priv->tooltip_widget = NULL;
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 */
276 /* Don't show the tooltip if there's already a popup menu */
277 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
282 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
283 keyboard_mode, &model, &path, &iter))
288 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
289 gtk_tree_path_free (path);
291 gtk_tree_model_get (model, &iter,
292 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
294 if (individual == NULL)
299 contact = empathy_contact_dup_from_folks_individual (individual);
300 g_object_unref (individual);
305 if (priv->tooltip_widget == NULL)
307 priv->tooltip_widget = empathy_contact_widget_new (contact,
308 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
309 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
310 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
311 g_object_ref (priv->tooltip_widget);
312 g_signal_connect (priv->tooltip_widget, "destroy",
313 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
314 gtk_widget_show (priv->tooltip_widget);
318 empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
321 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
324 g_object_unref (contact);
332 individual_view_handle_drag (EmpathyIndividualView *self,
333 FolksIndividual *individual,
334 const gchar *old_group,
335 const gchar *new_group,
336 GdkDragAction action)
338 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
339 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
341 DEBUG ("individual %s dragged from '%s' to '%s'",
342 folks_individual_get_id (individual), old_group, new_group);
344 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
346 /* Mark contact as favourite */
347 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
351 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
353 /* Remove contact as favourite */
354 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
356 /* Don't try to remove it */
360 if (new_group != NULL)
361 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE);
363 if (old_group != NULL && action == GDK_ACTION_MOVE)
364 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE);
368 group_can_be_modified (const gchar *name,
369 gboolean is_fake_group,
372 /* Real groups can always be modified */
376 /* The favorite fake group can be modified so users can
377 * add/remove favorites using DnD */
378 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
381 /* We can remove contacts from the 'ungrouped' fake group */
382 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
389 individual_view_contact_drag_received (GtkWidget *self,
390 GdkDragContext *context,
393 GtkSelectionData *selection)
395 EmpathyIndividualViewPriv *priv;
396 EmpathyIndividualManager *manager;
397 FolksIndividual *individual;
398 GtkTreePath *source_path;
399 const gchar *sel_data;
400 gchar *new_group = NULL;
401 gchar *old_group = NULL;
402 gboolean new_group_is_fake, old_group_is_fake = TRUE;
404 priv = GET_PRIV (self);
406 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
407 new_group = empathy_individual_store_get_parent_group (model, path,
408 NULL, &new_group_is_fake);
410 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
413 /* Get source group information. */
416 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
420 empathy_individual_store_get_parent_group (model, source_path,
421 NULL, &old_group_is_fake);
422 gtk_tree_path_free (source_path);
426 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
429 if (!tp_strdiff (old_group, new_group))
436 /* XXX: for contacts, we used to ensure the account, create the contact
437 * factory, and then wait on the contacts. But they should already be
438 * created by this point */
440 manager = empathy_individual_manager_dup_singleton ();
441 individual = empathy_individual_manager_lookup_member (manager, sel_data);
443 if (individual == NULL)
445 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
447 g_object_unref (manager);
452 /* FIXME: We should probably wait for the cb before calling
455 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
456 old_group, new_group, gdk_drag_context_get_selected_action (context));
458 g_object_unref (G_OBJECT (manager));
466 individual_view_file_drag_received (GtkWidget *view,
467 GdkDragContext *context,
470 GtkSelectionData *selection)
473 const gchar *sel_data;
474 FolksIndividual *individual;
475 EmpathyContact *contact;
477 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
479 gtk_tree_model_get_iter (model, &iter, path);
480 gtk_tree_model_get (model, &iter,
481 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
482 if (individual == NULL)
487 contact = empathy_contact_dup_from_folks_individual (individual);
488 empathy_send_file_from_uri_list (contact, sel_data);
490 g_object_unref (individual);
491 tp_clear_object (&contact);
497 individual_view_drag_data_received (GtkWidget *view,
498 GdkDragContext *context,
501 GtkSelectionData *selection,
507 GtkTreeViewDropPosition position;
509 gboolean success = TRUE;
511 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
513 /* Get destination group information. */
514 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
515 x, y, &path, &position);
520 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
521 || info == DND_DRAG_TYPE_STRING)
523 success = individual_view_contact_drag_received (view,
524 context, model, path, selection);
526 else if (info == DND_DRAG_TYPE_URI_LIST)
528 success = individual_view_file_drag_received (view,
529 context, model, path, selection);
532 gtk_tree_path_free (path);
533 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
537 individual_view_drag_motion_cb (DragMotionData *data)
539 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
541 data->timeout_id = 0;
547 individual_view_drag_motion (GtkWidget *widget,
548 GdkDragContext *context,
553 EmpathyIndividualViewPriv *priv;
557 static DragMotionData *dm = NULL;
560 gboolean is_different = FALSE;
561 gboolean cleanup = TRUE;
562 gboolean retval = TRUE;
564 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
565 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
567 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
568 x, y, &path, NULL, NULL, NULL);
570 cleanup &= (dm == NULL);
574 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
575 is_different = ((dm == NULL) || ((dm != NULL)
576 && gtk_tree_path_compare (dm->path, path) != 0));
585 /* Coordinates don't point to an actual row, so make sure the pointer
586 and highlighting don't indicate that a drag is possible.
588 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
589 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
592 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
593 gtk_tree_model_get_iter (model, &iter, path);
595 if (target == GDK_NONE)
597 /* If target == GDK_NONE, then we don't have a target that can be
598 dropped on a contact. This means a contact drag. If we're
599 pointing to a group, highlight it. Otherwise, if the contact
600 we're pointing to is in a group, highlight that. Otherwise,
601 set the drag position to before the first row for a drag into
602 the "non-group" at the top.
604 GtkTreeIter group_iter;
606 GtkTreePath *group_path;
607 gtk_tree_model_get (model, &iter,
608 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
615 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
616 gtk_tree_model_get (model, &group_iter,
617 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
621 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
622 group_path = gtk_tree_model_get_path (model, &group_iter);
623 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
624 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
625 gtk_tree_path_free (group_path);
629 group_path = gtk_tree_path_new_first ();
630 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
631 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
632 group_path, GTK_TREE_VIEW_DROP_BEFORE);
637 /* This is a file drag, and it can only be dropped on contacts,
640 FolksIndividual *individual;
641 gtk_tree_model_get (model, &iter,
642 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
643 if (individual != NULL &&
644 folks_individual_is_online (individual) &&
645 (folks_individual_get_capabilities (individual) &
646 FOLKS_CAPABILITIES_FLAGS_FILE_TRANSFER))
648 gdk_drag_status (context, GDK_ACTION_COPY, time_);
649 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
650 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
654 gdk_drag_status (context, 0, time_);
655 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
659 if (individual != NULL)
660 g_object_unref (individual);
663 if (!is_different && !cleanup)
670 gtk_tree_path_free (dm->path);
673 g_source_remove (dm->timeout_id);
681 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
683 dm = g_new0 (DragMotionData, 1);
685 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
686 dm->path = gtk_tree_path_copy (path);
688 dm->timeout_id = g_timeout_add_seconds (1,
689 (GSourceFunc) individual_view_drag_motion_cb, dm);
696 individual_view_drag_begin (GtkWidget *widget,
697 GdkDragContext *context)
699 EmpathyIndividualViewPriv *priv;
700 GtkTreeSelection *selection;
705 priv = GET_PRIV (widget);
707 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
710 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
711 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
716 path = gtk_tree_model_get_path (model, &iter);
717 priv->drag_row = gtk_tree_row_reference_new (model, path);
718 gtk_tree_path_free (path);
722 individual_view_drag_data_get (GtkWidget *widget,
723 GdkDragContext *context,
724 GtkSelectionData *selection,
728 EmpathyIndividualViewPriv *priv;
729 GtkTreePath *src_path;
732 FolksIndividual *individual;
733 const gchar *individual_id;
735 priv = GET_PRIV (widget);
737 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
738 if (priv->drag_row == NULL)
743 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
744 if (src_path == NULL)
749 if (!gtk_tree_model_get_iter (model, &iter, src_path))
751 gtk_tree_path_free (src_path);
755 gtk_tree_path_free (src_path);
758 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
759 if (individual == NULL)
764 individual_id = folks_individual_get_id (individual);
768 case DND_DRAG_TYPE_INDIVIDUAL_ID:
769 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
770 (guchar *) individual_id, strlen (individual_id) + 1);
774 g_object_unref (individual);
778 individual_view_drag_end (GtkWidget *widget,
779 GdkDragContext *context)
781 EmpathyIndividualViewPriv *priv;
783 priv = GET_PRIV (widget);
785 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
790 gtk_tree_row_reference_free (priv->drag_row);
791 priv->drag_row = NULL;
796 individual_view_drag_drop (GtkWidget *widget,
797 GdkDragContext *drag_context,
807 EmpathyIndividualView *view;
813 individual_view_popup_menu_idle_cb (gpointer user_data)
815 MenuPopupData *data = user_data;
818 menu = empathy_individual_view_get_individual_menu (data->view);
820 menu = empathy_individual_view_get_group_menu (data->view);
824 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
825 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
827 gtk_widget_show (menu);
828 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
830 g_object_ref_sink (menu);
831 g_object_unref (menu);
834 g_slice_free (MenuPopupData, data);
840 individual_view_button_press_event_cb (EmpathyIndividualView *view,
841 GdkEventButton *event,
844 if (event->button == 3)
848 data = g_slice_new (MenuPopupData);
850 data->button = event->button;
851 data->time = event->time;
852 g_idle_add (individual_view_popup_menu_idle_cb, data);
859 individual_view_key_press_event_cb (EmpathyIndividualView *view,
863 if (event->keyval == GDK_Menu)
867 data = g_slice_new (MenuPopupData);
870 data->time = event->time;
871 g_idle_add (individual_view_popup_menu_idle_cb, data);
878 individual_view_row_activated (GtkTreeView *view,
880 GtkTreeViewColumn *column)
882 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
883 FolksIndividual *individual;
884 EmpathyContact *contact = NULL;
888 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
893 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
894 gtk_tree_model_get_iter (model, &iter, path);
895 gtk_tree_model_get (model, &iter,
896 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
898 if (individual == NULL)
901 contact = empathy_contact_dup_from_folks_individual (individual);
904 DEBUG ("Starting a chat");
906 empathy_dispatcher_chat_with_contact (contact,
907 gtk_get_current_event_time (), NULL, NULL);
910 g_object_unref (individual);
911 tp_clear_object (&contact);
915 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
916 const gchar *path_string,
917 EmpathyIndividualView *view)
922 FolksIndividual *individual;
923 GdkEventButton *event;
927 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
928 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
931 gtk_tree_model_get (model, &iter,
932 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
933 if (individual == NULL)
936 event = (GdkEventButton *) gtk_get_current_event ();
938 menu = gtk_menu_new ();
939 shell = GTK_MENU_SHELL (menu);
942 item = empathy_individual_audio_call_menu_item_new (individual);
943 gtk_menu_shell_append (shell, item);
944 gtk_widget_show (item);
947 item = empathy_individual_video_call_menu_item_new (individual);
948 gtk_menu_shell_append (shell, item);
949 gtk_widget_show (item);
951 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
952 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
953 gtk_widget_show (menu);
954 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
955 event->button, event->time);
956 g_object_ref_sink (menu);
957 g_object_unref (menu);
959 g_object_unref (individual);
963 individual_view_cell_set_background (EmpathyIndividualView *view,
964 GtkCellRenderer *cell,
971 style = gtk_widget_get_style (GTK_WIDGET (view));
973 if (!is_group && is_active)
975 color = style->bg[GTK_STATE_SELECTED];
977 /* Here we take the current theme colour and add it to
978 * the colour for white and average the two. This
979 * gives a colour which is inline with the theme but
982 color.red = (color.red + (style->white).red) / 2;
983 color.green = (color.green + (style->white).green) / 2;
984 color.blue = (color.blue + (style->white).blue) / 2;
986 g_object_set (cell, "cell-background-gdk", &color, NULL);
990 g_object_set (cell, "cell-background-gdk", NULL, NULL);
995 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
996 GtkCellRenderer *cell,
999 EmpathyIndividualView *view)
1005 gtk_tree_model_get (model, iter,
1006 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1007 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1008 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1011 "visible", !is_group,
1015 tp_clear_object (&pixbuf);
1017 individual_view_cell_set_background (view, cell, is_group, is_active);
1021 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1022 GtkCellRenderer *cell,
1023 GtkTreeModel *model,
1025 EmpathyIndividualView *view)
1027 GdkPixbuf *pixbuf = NULL;
1031 gtk_tree_model_get (model, iter,
1032 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1033 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1038 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1040 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1041 GTK_ICON_SIZE_MENU);
1043 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1045 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1046 GTK_ICON_SIZE_MENU);
1051 "visible", pixbuf != NULL,
1055 tp_clear_object (&pixbuf);
1061 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1062 GtkCellRenderer *cell,
1063 GtkTreeModel *model,
1065 EmpathyIndividualView *view)
1069 gboolean can_audio, can_video;
1071 gtk_tree_model_get (model, iter,
1072 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1073 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1074 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1075 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1078 "visible", !is_group && (can_audio || can_video),
1079 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1082 individual_view_cell_set_background (view, cell, is_group, is_active);
1086 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1087 GtkCellRenderer *cell,
1088 GtkTreeModel *model,
1090 EmpathyIndividualView *view)
1093 gboolean show_avatar;
1097 gtk_tree_model_get (model, iter,
1098 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1099 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1100 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1101 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1104 "visible", !is_group && show_avatar,
1108 tp_clear_object (&pixbuf);
1110 individual_view_cell_set_background (view, cell, is_group, is_active);
1114 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1115 GtkCellRenderer *cell,
1116 GtkTreeModel *model,
1118 EmpathyIndividualView *view)
1123 gtk_tree_model_get (model, iter,
1124 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1125 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1127 individual_view_cell_set_background (view, cell, is_group, is_active);
1131 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1132 GtkCellRenderer *cell,
1133 GtkTreeModel *model,
1135 EmpathyIndividualView *view)
1140 gtk_tree_model_get (model, iter,
1141 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1142 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1144 if (gtk_tree_model_iter_has_child (model, iter))
1147 gboolean row_expanded;
1149 path = gtk_tree_model_get_path (model, iter);
1151 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1152 (gtk_tree_view_column_get_tree_view (column)), path);
1153 gtk_tree_path_free (path);
1158 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1163 g_object_set (cell, "visible", FALSE, NULL);
1166 individual_view_cell_set_background (view, cell, is_group, is_active);
1170 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1175 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1176 GtkTreeModel *model;
1180 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1183 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1185 gtk_tree_model_get (model, iter,
1186 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1188 expanded = GPOINTER_TO_INT (user_data);
1189 empathy_contact_group_set_expanded (name, expanded);
1195 individual_view_start_search_cb (EmpathyIndividualView *view,
1198 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1200 if (priv->search_widget == NULL)
1203 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1204 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1206 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1212 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1214 EmpathyIndividualView *view)
1216 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1218 GtkTreeViewColumn *focus_column;
1219 GtkTreeModel *model;
1221 gboolean set_cursor = FALSE;
1223 gtk_tree_model_filter_refilter (priv->filter);
1225 /* Set cursor on the first contact. If it is already set on a group,
1226 * set it on its first child contact. Note that first child of a group
1227 * is its separator, that's why we actually set to the 2nd
1230 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1231 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1235 path = gtk_tree_path_new_from_string ("0:1");
1238 else if (gtk_tree_path_get_depth (path) < 2)
1242 gtk_tree_model_get_iter (model, &iter, path);
1243 gtk_tree_model_get (model, &iter,
1244 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1249 gtk_tree_path_down (path);
1250 gtk_tree_path_next (path);
1257 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1259 if (gtk_tree_model_get_iter (model, &iter, path))
1261 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1266 gtk_tree_path_free (path);
1270 individual_view_search_activate_cb (GtkWidget *search,
1271 EmpathyIndividualView *view)
1274 GtkTreeViewColumn *focus_column;
1276 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1279 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1280 gtk_tree_path_free (path);
1282 gtk_widget_hide (search);
1287 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1288 EmpathyIndividualView *view)
1290 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1291 GtkTreeModel *model;
1293 gboolean valid = FALSE;
1295 /* block expand or collapse handlers, they would write the
1296 * expand or collapsed setting to file otherwise */
1297 g_signal_handlers_block_by_func (view,
1298 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1299 g_signal_handlers_block_by_func (view,
1300 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1302 /* restore which groups are expanded and which are not */
1303 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1304 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1305 valid; valid = gtk_tree_model_iter_next (model, &iter))
1311 gtk_tree_model_get (model, &iter,
1312 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1313 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1322 path = gtk_tree_model_get_path (model, &iter);
1323 if ((priv->view_features &
1324 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1325 empathy_contact_group_get_expanded (name))
1327 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1331 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1334 gtk_tree_path_free (path);
1338 /* unblock expand or collapse handlers */
1339 g_signal_handlers_unblock_by_func (view,
1340 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1341 g_signal_handlers_unblock_by_func (view,
1342 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1346 individual_view_search_show_cb (EmpathyLiveSearch *search,
1347 EmpathyIndividualView *view)
1349 /* block expand or collapse handlers during expand all, they would
1350 * write the expand or collapsed setting to file otherwise */
1351 g_signal_handlers_block_by_func (view,
1352 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1354 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1356 g_signal_handlers_unblock_by_func (view,
1357 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1361 EmpathyIndividualView *view;
1362 GtkTreeRowReference *row_ref;
1367 individual_view_expand_idle_cb (gpointer user_data)
1369 ExpandData *data = user_data;
1372 path = gtk_tree_row_reference_get_path (data->row_ref);
1376 g_signal_handlers_block_by_func (data->view,
1377 individual_view_row_expand_or_collapse_cb,
1378 GINT_TO_POINTER (data->expand));
1381 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1383 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1385 gtk_tree_path_free (path);
1387 g_signal_handlers_unblock_by_func (data->view,
1388 individual_view_row_expand_or_collapse_cb,
1389 GINT_TO_POINTER (data->expand));
1392 g_object_unref (data->view);
1393 gtk_tree_row_reference_free (data->row_ref);
1394 g_slice_free (ExpandData, data);
1400 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1403 EmpathyIndividualView *view)
1405 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1406 gboolean is_group = FALSE;
1410 gtk_tree_model_get (model, iter,
1411 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1412 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1415 if (!is_group || EMP_STR_EMPTY (name))
1421 data = g_slice_new0 (ExpandData);
1422 data->view = g_object_ref (view);
1423 data->row_ref = gtk_tree_row_reference_new (model, path);
1425 (priv->view_features &
1426 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1427 (priv->search_widget != NULL &&
1428 gtk_widget_get_visible (priv->search_widget)) ||
1429 empathy_contact_group_get_expanded (name);
1431 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1432 * gtk_tree_model_filter_refilter () */
1433 g_idle_add (individual_view_expand_idle_cb, data);
1439 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1442 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1443 GtkTreeModel *model;
1444 GtkTreePath *parent_path;
1445 GtkTreeIter parent_iter;
1447 if (gtk_tree_path_get_depth (path) < 2)
1450 /* A group row is visible if and only if at least one if its child is visible.
1451 * So when a row is inserted/deleted/changed in the base model, that could
1452 * modify the visibility of its parent in the filter model.
1455 model = GTK_TREE_MODEL (priv->store);
1456 parent_path = gtk_tree_path_copy (path);
1457 gtk_tree_path_up (parent_path);
1458 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1460 /* This tells the filter to verify the visibility of that row, and
1461 * show/hide it if necessary */
1462 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1463 parent_path, &parent_iter);
1465 gtk_tree_path_free (parent_path);
1469 individual_view_store_row_changed_cb (GtkTreeModel *model,
1472 EmpathyIndividualView *view)
1474 individual_view_verify_group_visibility (view, path);
1478 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1480 EmpathyIndividualView *view)
1482 individual_view_verify_group_visibility (view, path);
1486 individual_view_constructed (GObject *object)
1488 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1489 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1490 GtkCellRenderer *cell;
1491 GtkTreeViewColumn *col;
1494 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1495 GTK_TREE_MODEL (priv->store), NULL));
1496 gtk_tree_model_filter_set_visible_func (priv->filter,
1497 individual_view_filter_visible_func, view, NULL);
1499 g_signal_connect (priv->store, "row-has-child-toggled",
1500 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1501 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1502 GTK_TREE_MODEL (priv->filter));
1504 tp_g_signal_connect_object (priv->store, "row-changed",
1505 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1506 tp_g_signal_connect_object (priv->store, "row-inserted",
1507 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1508 tp_g_signal_connect_object (priv->store, "row-deleted",
1509 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1511 tp_g_signal_connect_object (priv->store, "row-changed",
1512 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1513 tp_g_signal_connect_object (priv->store, "row-inserted",
1514 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1515 tp_g_signal_connect_object (priv->store, "row-deleted",
1516 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1519 /* Setting reorderable is a hack that gets us row previews as drag icons
1520 for free. We override all the drag handlers. It's tricky to get the
1521 position of the drag icon right in drag_begin. GtkTreeView has special
1522 voodoo for it, so we let it do the voodoo that he do.
1525 "headers-visible", FALSE,
1526 "reorderable", TRUE,
1527 "show-expanders", FALSE,
1530 col = gtk_tree_view_column_new ();
1533 cell = gtk_cell_renderer_pixbuf_new ();
1534 gtk_tree_view_column_pack_start (col, cell, FALSE);
1535 gtk_tree_view_column_set_cell_data_func (col, cell,
1536 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1546 cell = gtk_cell_renderer_pixbuf_new ();
1547 gtk_tree_view_column_pack_start (col, cell, FALSE);
1548 gtk_tree_view_column_set_cell_data_func (col, cell,
1549 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1561 cell = empathy_cell_renderer_text_new ();
1562 gtk_tree_view_column_pack_start (col, cell, TRUE);
1563 gtk_tree_view_column_set_cell_data_func (col, cell,
1564 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1566 gtk_tree_view_column_add_attribute (col, cell,
1567 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1568 gtk_tree_view_column_add_attribute (col, cell,
1569 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1570 gtk_tree_view_column_add_attribute (col, cell,
1571 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1572 gtk_tree_view_column_add_attribute (col, cell,
1573 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1574 gtk_tree_view_column_add_attribute (col, cell,
1575 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1576 gtk_tree_view_column_add_attribute (col, cell,
1577 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1579 /* Audio Call Icon */
1580 cell = empathy_cell_renderer_activatable_new ();
1581 gtk_tree_view_column_pack_start (col, cell, FALSE);
1582 gtk_tree_view_column_set_cell_data_func (col, cell,
1583 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1586 g_object_set (cell, "visible", FALSE, NULL);
1588 g_signal_connect (cell, "path-activated",
1589 G_CALLBACK (individual_view_call_activated_cb), view);
1592 cell = gtk_cell_renderer_pixbuf_new ();
1593 gtk_tree_view_column_pack_start (col, cell, FALSE);
1594 gtk_tree_view_column_set_cell_data_func (col, cell,
1595 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1607 cell = empathy_cell_renderer_expander_new ();
1608 gtk_tree_view_column_pack_end (col, cell, FALSE);
1609 gtk_tree_view_column_set_cell_data_func (col, cell,
1610 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1613 /* Actually add the column now we have added all cell renderers */
1614 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1617 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1619 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1622 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1624 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1630 individual_view_set_view_features (EmpathyIndividualView *view,
1631 EmpathyIndividualFeatureFlags features)
1633 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1634 gboolean has_tooltip;
1636 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1638 priv->view_features = features;
1640 /* Update DnD source/dest */
1641 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1643 gtk_drag_source_set (GTK_WIDGET (view),
1646 G_N_ELEMENTS (drag_types_source),
1647 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1651 gtk_drag_source_unset (GTK_WIDGET (view));
1655 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1657 gtk_drag_dest_set (GTK_WIDGET (view),
1658 GTK_DEST_DEFAULT_ALL,
1660 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1664 /* FIXME: URI could still be droped depending on FT feature */
1665 gtk_drag_dest_unset (GTK_WIDGET (view));
1668 /* Update has-tooltip */
1670 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1671 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1675 individual_view_dispose (GObject *object)
1677 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1678 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1680 tp_clear_object (&priv->store);
1681 tp_clear_object (&priv->filter);
1682 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1683 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1685 empathy_individual_view_set_live_search (view, NULL);
1687 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1691 individual_view_get_property (GObject *object,
1696 EmpathyIndividualViewPriv *priv;
1698 priv = GET_PRIV (object);
1703 g_value_set_object (value, priv->store);
1705 case PROP_VIEW_FEATURES:
1706 g_value_set_flags (value, priv->view_features);
1708 case PROP_INDIVIDUAL_FEATURES:
1709 g_value_set_flags (value, priv->individual_features);
1712 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1718 individual_view_set_property (GObject *object,
1720 const GValue *value,
1723 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1724 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1729 priv->store = g_value_dup_object (value);
1731 case PROP_VIEW_FEATURES:
1732 individual_view_set_view_features (view, g_value_get_flags (value));
1734 case PROP_INDIVIDUAL_FEATURES:
1735 priv->individual_features = g_value_get_flags (value);
1738 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1744 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1746 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1747 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1748 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1750 object_class->constructed = individual_view_constructed;
1751 object_class->dispose = individual_view_dispose;
1752 object_class->get_property = individual_view_get_property;
1753 object_class->set_property = individual_view_set_property;
1755 widget_class->drag_data_received = individual_view_drag_data_received;
1756 widget_class->drag_drop = individual_view_drag_drop;
1757 widget_class->drag_begin = individual_view_drag_begin;
1758 widget_class->drag_data_get = individual_view_drag_data_get;
1759 widget_class->drag_end = individual_view_drag_end;
1760 widget_class->drag_motion = individual_view_drag_motion;
1762 /* We use the class method to let user of this widget to connect to
1763 * the signal and stop emission of the signal so the default handler
1764 * won't be called. */
1765 tree_view_class->row_activated = individual_view_row_activated;
1767 signals[DRAG_CONTACT_RECEIVED] =
1768 g_signal_new ("drag-contact-received",
1769 G_OBJECT_CLASS_TYPE (klass),
1773 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1774 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1776 g_object_class_install_property (object_class,
1778 g_param_spec_object ("store",
1779 "The store of the view",
1780 "The store of the view",
1781 EMPATHY_TYPE_INDIVIDUAL_STORE,
1782 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1783 g_object_class_install_property (object_class,
1785 g_param_spec_flags ("view-features",
1786 "Features of the view",
1787 "Flags for all enabled features",
1788 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1789 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1790 g_object_class_install_property (object_class,
1791 PROP_INDIVIDUAL_FEATURES,
1792 g_param_spec_flags ("individual-features",
1793 "Features of the contact menu",
1794 "Flags for all enabled features for the menu",
1795 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1796 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1798 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1802 empathy_individual_view_init (EmpathyIndividualView *view)
1804 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1805 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1808 /* Get saved group states. */
1809 empathy_contact_groups_get_all ();
1811 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1812 empathy_individual_store_row_separator_func, NULL, NULL);
1814 /* Set up drag target lists. */
1815 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1816 G_N_ELEMENTS (drag_types_dest_file));
1818 /* Connect to tree view signals rather than override. */
1819 g_signal_connect (view, "button-press-event",
1820 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1821 g_signal_connect (view, "key-press-event",
1822 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1823 g_signal_connect (view, "row-expanded",
1824 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1825 GINT_TO_POINTER (TRUE));
1826 g_signal_connect (view, "row-collapsed",
1827 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1828 GINT_TO_POINTER (FALSE));
1829 g_signal_connect (view, "query-tooltip",
1830 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1833 EmpathyIndividualView *
1834 empathy_individual_view_new (EmpathyIndividualStore *store,
1835 EmpathyIndividualViewFeatureFlags view_features,
1836 EmpathyIndividualFeatureFlags individual_features)
1838 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1840 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1842 "individual-features", individual_features,
1843 "view-features", view_features, NULL);
1847 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1849 EmpathyIndividualViewPriv *priv;
1850 GtkTreeSelection *selection;
1852 GtkTreeModel *model;
1853 FolksIndividual *individual;
1855 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1857 priv = GET_PRIV (view);
1859 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1860 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1865 gtk_tree_model_get (model, &iter,
1866 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1871 EmpathyIndividualManagerFlags
1872 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1874 EmpathyIndividualViewPriv *priv;
1875 GtkTreeSelection *selection;
1877 GtkTreeModel *model;
1878 EmpathyIndividualFeatureFlags flags;
1880 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1882 priv = GET_PRIV (view);
1884 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1885 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1890 gtk_tree_model_get (model, &iter,
1891 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1897 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1898 gboolean *is_fake_group)
1900 EmpathyIndividualViewPriv *priv;
1901 GtkTreeSelection *selection;
1903 GtkTreeModel *model;
1908 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1910 priv = GET_PRIV (view);
1912 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1913 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1918 gtk_tree_model_get (model, &iter,
1919 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1920 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1921 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1929 if (is_fake_group != NULL)
1930 *is_fake_group = fake;
1936 individual_view_remove_dialog_show (GtkWindow *parent,
1937 const gchar *message,
1938 const gchar *secondary_text)
1943 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1944 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1945 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1946 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1947 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1948 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1949 "%s", secondary_text);
1951 gtk_widget_show (dialog);
1953 res = gtk_dialog_run (GTK_DIALOG (dialog));
1954 gtk_widget_destroy (dialog);
1956 return (res == GTK_RESPONSE_YES);
1960 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1961 EmpathyIndividualView *view)
1965 group = empathy_individual_view_get_selected_group (view, NULL);
1972 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1974 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1975 if (individual_view_remove_dialog_show (parent, _("Removing group"),
1978 EmpathyIndividualManager *manager =
1979 empathy_individual_manager_dup_singleton ();
1980 empathy_individual_manager_remove_group (manager, group);
1981 g_object_unref (G_OBJECT (manager));
1991 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
1993 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1998 gboolean is_fake_group;
2000 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2002 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2003 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2008 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2009 if (!group || is_fake_group)
2011 /* We can't alter fake groups */
2015 menu = gtk_menu_new ();
2018 if (priv->view_features &
2019 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2020 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2021 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2022 gtk_widget_show (item);
2023 g_signal_connect (item, "activate",
2024 G_CALLBACK (individual_view_group_rename_activate_cb),
2029 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2031 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2032 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2033 GTK_ICON_SIZE_MENU);
2034 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2035 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2036 gtk_widget_show (item);
2037 g_signal_connect (item, "activate",
2038 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2047 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2048 EmpathyIndividualView *view)
2050 FolksIndividual *individual;
2052 individual = empathy_individual_view_dup_selected (view);
2054 if (individual != NULL)
2059 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2062 ("Do you really want to remove the contact '%s'?"),
2063 folks_individual_get_alias (individual));
2064 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2067 EmpathyIndividualManager *manager;
2069 manager = empathy_individual_manager_dup_singleton ();
2070 empathy_individual_manager_remove (manager, individual, "");
2071 g_object_unref (G_OBJECT (manager));
2075 g_object_unref (individual);
2080 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2082 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2083 FolksIndividual *individual;
2084 GtkWidget *menu = NULL;
2087 EmpathyIndividualManagerFlags flags;
2089 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2091 individual = empathy_individual_view_dup_selected (view);
2092 if (individual == NULL)
2097 flags = empathy_individual_view_get_flags (view);
2099 menu = empathy_individual_menu_new (individual, priv->individual_features);
2101 /* Remove contact */
2102 if (priv->view_features &
2103 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2104 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2107 /* create the menu if required, or just add a separator */
2110 menu = gtk_menu_new ();
2114 item = gtk_separator_menu_item_new ();
2115 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2116 gtk_widget_show (item);
2120 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2121 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2122 GTK_ICON_SIZE_MENU);
2123 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2124 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2125 gtk_widget_show (item);
2126 g_signal_connect (item, "activate",
2127 G_CALLBACK (individual_view_remove_activate_cb), view);
2130 g_object_unref (individual);
2136 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2137 EmpathyLiveSearch *search)
2139 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2141 /* remove old handlers if old search was not null */
2142 if (priv->search_widget != NULL)
2144 g_signal_handlers_disconnect_by_func (view,
2145 individual_view_start_search_cb, NULL);
2147 g_signal_handlers_disconnect_by_func (priv->search_widget,
2148 individual_view_search_text_notify_cb, view);
2149 g_signal_handlers_disconnect_by_func (priv->search_widget,
2150 individual_view_search_activate_cb, view);
2151 g_signal_handlers_disconnect_by_func (priv->search_widget,
2152 individual_view_search_hide_cb, view);
2153 g_signal_handlers_disconnect_by_func (priv->search_widget,
2154 individual_view_search_show_cb, view);
2155 g_object_unref (priv->search_widget);
2156 priv->search_widget = NULL;
2159 /* connect handlers if new search is not null */
2162 priv->search_widget = g_object_ref (search);
2164 g_signal_connect (view, "start-interactive-search",
2165 G_CALLBACK (individual_view_start_search_cb), NULL);
2167 g_signal_connect (priv->search_widget, "notify::text",
2168 G_CALLBACK (individual_view_search_text_notify_cb), view);
2169 g_signal_connect (priv->search_widget, "activate",
2170 G_CALLBACK (individual_view_search_activate_cb), view);
2171 g_signal_connect (priv->search_widget, "hide",
2172 G_CALLBACK (individual_view_search_hide_cb), view);
2173 g_signal_connect (priv->search_widget, "show",
2174 G_CALLBACK (individual_view_search_show_cb), view);