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 */
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 gtk_tree_model_get (model, &iter,
629 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
630 if (individual != NULL &&
631 folks_individual_is_online (individual) &&
632 (folks_individual_get_capabilities (individual) &
633 FOLKS_CAPABILITIES_FLAGS_FILE_TRANSFER))
635 gdk_drag_status (context, GDK_ACTION_COPY, time_);
636 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
637 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
641 gdk_drag_status (context, 0, time_);
642 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
646 if (individual != NULL)
647 g_object_unref (individual);
650 if (!is_different && !cleanup)
655 gtk_tree_path_free (dm->path);
658 g_source_remove (dm->timeout_id);
666 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
668 dm = g_new0 (DragMotionData, 1);
670 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
671 dm->path = gtk_tree_path_copy (path);
673 dm->timeout_id = g_timeout_add_seconds (1,
674 (GSourceFunc) individual_view_drag_motion_cb, dm);
681 individual_view_drag_begin (GtkWidget *widget,
682 GdkDragContext *context)
684 EmpathyIndividualViewPriv *priv;
685 GtkTreeSelection *selection;
690 priv = GET_PRIV (widget);
692 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
695 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
696 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
699 path = gtk_tree_model_get_path (model, &iter);
700 priv->drag_row = gtk_tree_row_reference_new (model, path);
701 gtk_tree_path_free (path);
705 individual_view_drag_data_get (GtkWidget *widget,
706 GdkDragContext *context,
707 GtkSelectionData *selection,
711 EmpathyIndividualViewPriv *priv;
712 GtkTreePath *src_path;
715 FolksIndividual *individual;
716 const gchar *individual_id;
718 priv = GET_PRIV (widget);
720 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
721 if (priv->drag_row == NULL)
724 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
725 if (src_path == NULL)
728 if (!gtk_tree_model_get_iter (model, &iter, src_path))
730 gtk_tree_path_free (src_path);
734 gtk_tree_path_free (src_path);
737 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
738 if (individual == NULL)
741 individual_id = folks_individual_get_id (individual);
745 case DND_DRAG_TYPE_INDIVIDUAL_ID:
746 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
747 (guchar *) individual_id, strlen (individual_id) + 1);
751 g_object_unref (individual);
755 individual_view_drag_end (GtkWidget *widget,
756 GdkDragContext *context)
758 EmpathyIndividualViewPriv *priv;
760 priv = GET_PRIV (widget);
762 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
767 gtk_tree_row_reference_free (priv->drag_row);
768 priv->drag_row = NULL;
773 individual_view_drag_drop (GtkWidget *widget,
774 GdkDragContext *drag_context,
784 EmpathyIndividualView *view;
790 individual_view_popup_menu_idle_cb (gpointer user_data)
792 MenuPopupData *data = user_data;
795 menu = empathy_individual_view_get_individual_menu (data->view);
797 menu = empathy_individual_view_get_group_menu (data->view);
801 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
802 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
804 gtk_widget_show (menu);
805 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
807 g_object_ref_sink (menu);
808 g_object_unref (menu);
811 g_slice_free (MenuPopupData, data);
817 individual_view_button_press_event_cb (EmpathyIndividualView *view,
818 GdkEventButton *event,
821 if (event->button == 3)
825 data = g_slice_new (MenuPopupData);
827 data->button = event->button;
828 data->time = event->time;
829 g_idle_add (individual_view_popup_menu_idle_cb, data);
836 individual_view_key_press_event_cb (EmpathyIndividualView *view,
840 if (event->keyval == GDK_Menu)
844 data = g_slice_new (MenuPopupData);
847 data->time = event->time;
848 g_idle_add (individual_view_popup_menu_idle_cb, data);
855 individual_view_row_activated (GtkTreeView *view,
857 GtkTreeViewColumn *column)
859 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
860 FolksIndividual *individual;
861 EmpathyContact *contact = NULL;
865 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
868 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
869 gtk_tree_model_get_iter (model, &iter, path);
870 gtk_tree_model_get (model, &iter,
871 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
873 if (individual == NULL)
876 contact = empathy_contact_dup_from_folks_individual (individual);
879 DEBUG ("Starting a chat");
881 empathy_dispatcher_chat_with_contact (contact,
882 gtk_get_current_event_time (), NULL, NULL);
885 g_object_unref (individual);
886 tp_clear_object (&contact);
890 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
891 const gchar *path_string,
892 EmpathyIndividualView *view)
897 FolksIndividual *individual;
898 GdkEventButton *event;
902 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
903 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
906 gtk_tree_model_get (model, &iter,
907 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
908 if (individual == NULL)
911 event = (GdkEventButton *) gtk_get_current_event ();
913 menu = gtk_menu_new ();
914 shell = GTK_MENU_SHELL (menu);
917 item = empathy_individual_audio_call_menu_item_new (individual);
918 gtk_menu_shell_append (shell, item);
919 gtk_widget_show (item);
922 item = empathy_individual_video_call_menu_item_new (individual);
923 gtk_menu_shell_append (shell, item);
924 gtk_widget_show (item);
926 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
927 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
928 gtk_widget_show (menu);
929 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
930 event->button, event->time);
931 g_object_ref_sink (menu);
932 g_object_unref (menu);
934 g_object_unref (individual);
938 individual_view_cell_set_background (EmpathyIndividualView *view,
939 GtkCellRenderer *cell,
946 style = gtk_widget_get_style (GTK_WIDGET (view));
948 if (!is_group && is_active)
950 color = style->bg[GTK_STATE_SELECTED];
952 /* Here we take the current theme colour and add it to
953 * the colour for white and average the two. This
954 * gives a colour which is inline with the theme but
957 color.red = (color.red + (style->white).red) / 2;
958 color.green = (color.green + (style->white).green) / 2;
959 color.blue = (color.blue + (style->white).blue) / 2;
961 g_object_set (cell, "cell-background-gdk", &color, NULL);
964 g_object_set (cell, "cell-background-gdk", NULL, NULL);
968 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
969 GtkCellRenderer *cell,
972 EmpathyIndividualView *view)
978 gtk_tree_model_get (model, iter,
979 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
980 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
981 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
984 "visible", !is_group,
988 tp_clear_object (&pixbuf);
990 individual_view_cell_set_background (view, cell, is_group, is_active);
994 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
995 GtkCellRenderer *cell,
998 EmpathyIndividualView *view)
1000 GdkPixbuf *pixbuf = NULL;
1004 gtk_tree_model_get (model, iter,
1005 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1006 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1011 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1013 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1014 GTK_ICON_SIZE_MENU);
1016 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1018 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1019 GTK_ICON_SIZE_MENU);
1024 "visible", pixbuf != NULL,
1028 tp_clear_object (&pixbuf);
1034 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1035 GtkCellRenderer *cell,
1036 GtkTreeModel *model,
1038 EmpathyIndividualView *view)
1042 gboolean can_audio, can_video;
1044 gtk_tree_model_get (model, iter,
1045 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1046 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1047 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1048 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1051 "visible", !is_group && (can_audio || can_video),
1052 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1055 individual_view_cell_set_background (view, cell, is_group, is_active);
1059 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1060 GtkCellRenderer *cell,
1061 GtkTreeModel *model,
1063 EmpathyIndividualView *view)
1066 gboolean show_avatar;
1070 gtk_tree_model_get (model, iter,
1071 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1072 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1073 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1074 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1077 "visible", !is_group && show_avatar,
1081 tp_clear_object (&pixbuf);
1083 individual_view_cell_set_background (view, cell, is_group, is_active);
1087 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1088 GtkCellRenderer *cell,
1089 GtkTreeModel *model,
1091 EmpathyIndividualView *view)
1096 gtk_tree_model_get (model, iter,
1097 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1098 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1100 individual_view_cell_set_background (view, cell, is_group, is_active);
1104 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1105 GtkCellRenderer *cell,
1106 GtkTreeModel *model,
1108 EmpathyIndividualView *view)
1113 gtk_tree_model_get (model, iter,
1114 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1115 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1117 if (gtk_tree_model_iter_has_child (model, iter))
1120 gboolean row_expanded;
1122 path = gtk_tree_model_get_path (model, iter);
1124 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1125 (gtk_tree_view_column_get_tree_view (column)), path);
1126 gtk_tree_path_free (path);
1131 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1135 g_object_set (cell, "visible", FALSE, NULL);
1137 individual_view_cell_set_background (view, cell, is_group, is_active);
1141 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1146 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1147 GtkTreeModel *model;
1151 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1154 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1156 gtk_tree_model_get (model, iter,
1157 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1159 expanded = GPOINTER_TO_INT (user_data);
1160 empathy_contact_group_set_expanded (name, expanded);
1166 individual_view_start_search_cb (EmpathyIndividualView *view,
1169 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1171 if (priv->search_widget == NULL)
1174 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1175 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1177 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1183 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1185 EmpathyIndividualView *view)
1187 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1189 GtkTreeViewColumn *focus_column;
1190 GtkTreeModel *model;
1192 gboolean set_cursor = FALSE;
1194 gtk_tree_model_filter_refilter (priv->filter);
1196 /* Set cursor on the first contact. If it is already set on a group,
1197 * set it on its first child contact. Note that first child of a group
1198 * is its separator, that's why we actually set to the 2nd
1201 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1202 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1206 path = gtk_tree_path_new_from_string ("0:1");
1209 else if (gtk_tree_path_get_depth (path) < 2)
1213 gtk_tree_model_get_iter (model, &iter, path);
1214 gtk_tree_model_get (model, &iter,
1215 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1220 gtk_tree_path_down (path);
1221 gtk_tree_path_next (path);
1228 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1230 if (gtk_tree_model_get_iter (model, &iter, path))
1232 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1237 gtk_tree_path_free (path);
1241 individual_view_search_activate_cb (GtkWidget *search,
1242 EmpathyIndividualView *view)
1245 GtkTreeViewColumn *focus_column;
1247 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1250 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1251 gtk_tree_path_free (path);
1253 gtk_widget_hide (search);
1258 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1259 EmpathyIndividualView *view)
1261 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1262 GtkTreeModel *model;
1264 gboolean valid = FALSE;
1266 /* block expand or collapse handlers, they would write the
1267 * expand or collapsed setting to file otherwise */
1268 g_signal_handlers_block_by_func (view,
1269 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1270 g_signal_handlers_block_by_func (view,
1271 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1273 /* restore which groups are expanded and which are not */
1274 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1275 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1276 valid; valid = gtk_tree_model_iter_next (model, &iter))
1282 gtk_tree_model_get (model, &iter,
1283 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1284 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1293 path = gtk_tree_model_get_path (model, &iter);
1294 if ((priv->view_features &
1295 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1296 empathy_contact_group_get_expanded (name))
1298 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1302 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1305 gtk_tree_path_free (path);
1309 /* unblock expand or collapse handlers */
1310 g_signal_handlers_unblock_by_func (view,
1311 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1312 g_signal_handlers_unblock_by_func (view,
1313 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1317 individual_view_search_show_cb (EmpathyLiveSearch *search,
1318 EmpathyIndividualView *view)
1320 /* block expand or collapse handlers during expand all, they would
1321 * write the expand or collapsed setting to file otherwise */
1322 g_signal_handlers_block_by_func (view,
1323 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1325 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1327 g_signal_handlers_unblock_by_func (view,
1328 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1332 EmpathyIndividualView *view;
1333 GtkTreeRowReference *row_ref;
1338 individual_view_expand_idle_cb (gpointer user_data)
1340 ExpandData *data = user_data;
1343 path = gtk_tree_row_reference_get_path (data->row_ref);
1347 g_signal_handlers_block_by_func (data->view,
1348 individual_view_row_expand_or_collapse_cb,
1349 GINT_TO_POINTER (data->expand));
1352 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1354 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1356 gtk_tree_path_free (path);
1358 g_signal_handlers_unblock_by_func (data->view,
1359 individual_view_row_expand_or_collapse_cb,
1360 GINT_TO_POINTER (data->expand));
1363 g_object_unref (data->view);
1364 gtk_tree_row_reference_free (data->row_ref);
1365 g_slice_free (ExpandData, data);
1371 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1374 EmpathyIndividualView *view)
1376 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1377 gboolean is_group = FALSE;
1381 gtk_tree_model_get (model, iter,
1382 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1383 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1386 if (!is_group || EMP_STR_EMPTY (name))
1392 data = g_slice_new0 (ExpandData);
1393 data->view = g_object_ref (view);
1394 data->row_ref = gtk_tree_row_reference_new (model, path);
1396 (priv->view_features &
1397 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1398 (priv->search_widget != NULL &&
1399 gtk_widget_get_visible (priv->search_widget)) ||
1400 empathy_contact_group_get_expanded (name);
1402 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1403 * gtk_tree_model_filter_refilter () */
1404 g_idle_add (individual_view_expand_idle_cb, data);
1410 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1413 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1414 GtkTreeModel *model;
1415 GtkTreePath *parent_path;
1416 GtkTreeIter parent_iter;
1418 if (gtk_tree_path_get_depth (path) < 2)
1421 /* A group row is visible if and only if at least one if its child is visible.
1422 * So when a row is inserted/deleted/changed in the base model, that could
1423 * modify the visibility of its parent in the filter model.
1426 model = GTK_TREE_MODEL (priv->store);
1427 parent_path = gtk_tree_path_copy (path);
1428 gtk_tree_path_up (parent_path);
1429 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1431 /* This tells the filter to verify the visibility of that row, and
1432 * show/hide it if necessary */
1433 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1434 parent_path, &parent_iter);
1436 gtk_tree_path_free (parent_path);
1440 individual_view_store_row_changed_cb (GtkTreeModel *model,
1443 EmpathyIndividualView *view)
1445 individual_view_verify_group_visibility (view, path);
1449 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1451 EmpathyIndividualView *view)
1453 individual_view_verify_group_visibility (view, path);
1457 individual_view_constructed (GObject *object)
1459 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1460 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1461 GtkCellRenderer *cell;
1462 GtkTreeViewColumn *col;
1465 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1466 GTK_TREE_MODEL (priv->store), NULL));
1467 gtk_tree_model_filter_set_visible_func (priv->filter,
1468 individual_view_filter_visible_func, view, NULL);
1470 g_signal_connect (priv->store, "row-has-child-toggled",
1471 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1472 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1473 GTK_TREE_MODEL (priv->filter));
1475 tp_g_signal_connect_object (priv->store, "row-changed",
1476 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1477 tp_g_signal_connect_object (priv->store, "row-inserted",
1478 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1479 tp_g_signal_connect_object (priv->store, "row-deleted",
1480 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1482 tp_g_signal_connect_object (priv->store, "row-changed",
1483 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1484 tp_g_signal_connect_object (priv->store, "row-inserted",
1485 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1486 tp_g_signal_connect_object (priv->store, "row-deleted",
1487 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1490 /* Setting reorderable is a hack that gets us row previews as drag icons
1491 for free. We override all the drag handlers. It's tricky to get the
1492 position of the drag icon right in drag_begin. GtkTreeView has special
1493 voodoo for it, so we let it do the voodoo that he do.
1496 "headers-visible", FALSE,
1497 "reorderable", TRUE,
1498 "show-expanders", FALSE,
1501 col = gtk_tree_view_column_new ();
1504 cell = gtk_cell_renderer_pixbuf_new ();
1505 gtk_tree_view_column_pack_start (col, cell, FALSE);
1506 gtk_tree_view_column_set_cell_data_func (col, cell,
1507 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1517 cell = gtk_cell_renderer_pixbuf_new ();
1518 gtk_tree_view_column_pack_start (col, cell, FALSE);
1519 gtk_tree_view_column_set_cell_data_func (col, cell,
1520 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1532 cell = empathy_cell_renderer_text_new ();
1533 gtk_tree_view_column_pack_start (col, cell, TRUE);
1534 gtk_tree_view_column_set_cell_data_func (col, cell,
1535 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1537 gtk_tree_view_column_add_attribute (col, cell,
1538 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1539 gtk_tree_view_column_add_attribute (col, cell,
1540 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1541 gtk_tree_view_column_add_attribute (col, cell,
1542 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1543 gtk_tree_view_column_add_attribute (col, cell,
1544 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1545 gtk_tree_view_column_add_attribute (col, cell,
1546 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1547 gtk_tree_view_column_add_attribute (col, cell,
1548 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1550 /* Audio Call Icon */
1551 cell = empathy_cell_renderer_activatable_new ();
1552 gtk_tree_view_column_pack_start (col, cell, FALSE);
1553 gtk_tree_view_column_set_cell_data_func (col, cell,
1554 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1557 g_object_set (cell, "visible", FALSE, NULL);
1559 g_signal_connect (cell, "path-activated",
1560 G_CALLBACK (individual_view_call_activated_cb), view);
1563 cell = gtk_cell_renderer_pixbuf_new ();
1564 gtk_tree_view_column_pack_start (col, cell, FALSE);
1565 gtk_tree_view_column_set_cell_data_func (col, cell,
1566 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1578 cell = empathy_cell_renderer_expander_new ();
1579 gtk_tree_view_column_pack_end (col, cell, FALSE);
1580 gtk_tree_view_column_set_cell_data_func (col, cell,
1581 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1584 /* Actually add the column now we have added all cell renderers */
1585 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1588 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1590 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1593 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1595 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1601 individual_view_set_view_features (EmpathyIndividualView *view,
1602 EmpathyIndividualFeatureFlags features)
1604 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1605 gboolean has_tooltip;
1607 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1609 priv->view_features = features;
1611 /* Update DnD source/dest */
1612 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1614 gtk_drag_source_set (GTK_WIDGET (view),
1617 G_N_ELEMENTS (drag_types_source),
1618 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1622 gtk_drag_source_unset (GTK_WIDGET (view));
1626 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1628 gtk_drag_dest_set (GTK_WIDGET (view),
1629 GTK_DEST_DEFAULT_ALL,
1631 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1635 /* FIXME: URI could still be droped depending on FT feature */
1636 gtk_drag_dest_unset (GTK_WIDGET (view));
1639 /* Update has-tooltip */
1641 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1642 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1646 individual_view_dispose (GObject *object)
1648 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1649 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1651 tp_clear_object (&priv->store);
1652 tp_clear_object (&priv->filter);
1653 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1654 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1656 empathy_individual_view_set_live_search (view, NULL);
1658 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1662 individual_view_get_property (GObject *object,
1667 EmpathyIndividualViewPriv *priv;
1669 priv = GET_PRIV (object);
1674 g_value_set_object (value, priv->store);
1676 case PROP_VIEW_FEATURES:
1677 g_value_set_flags (value, priv->view_features);
1679 case PROP_INDIVIDUAL_FEATURES:
1680 g_value_set_flags (value, priv->individual_features);
1683 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1689 individual_view_set_property (GObject *object,
1691 const GValue *value,
1694 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1695 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1700 priv->store = g_value_dup_object (value);
1702 case PROP_VIEW_FEATURES:
1703 individual_view_set_view_features (view, g_value_get_flags (value));
1705 case PROP_INDIVIDUAL_FEATURES:
1706 priv->individual_features = g_value_get_flags (value);
1709 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1715 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1717 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1718 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1719 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1721 object_class->constructed = individual_view_constructed;
1722 object_class->dispose = individual_view_dispose;
1723 object_class->get_property = individual_view_get_property;
1724 object_class->set_property = individual_view_set_property;
1726 widget_class->drag_data_received = individual_view_drag_data_received;
1727 widget_class->drag_drop = individual_view_drag_drop;
1728 widget_class->drag_begin = individual_view_drag_begin;
1729 widget_class->drag_data_get = individual_view_drag_data_get;
1730 widget_class->drag_end = individual_view_drag_end;
1731 widget_class->drag_motion = individual_view_drag_motion;
1733 /* We use the class method to let user of this widget to connect to
1734 * the signal and stop emission of the signal so the default handler
1735 * won't be called. */
1736 tree_view_class->row_activated = individual_view_row_activated;
1738 signals[DRAG_CONTACT_RECEIVED] =
1739 g_signal_new ("drag-contact-received",
1740 G_OBJECT_CLASS_TYPE (klass),
1744 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1745 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1747 g_object_class_install_property (object_class,
1749 g_param_spec_object ("store",
1750 "The store of the view",
1751 "The store of the view",
1752 EMPATHY_TYPE_INDIVIDUAL_STORE,
1753 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1754 g_object_class_install_property (object_class,
1756 g_param_spec_flags ("view-features",
1757 "Features of the view",
1758 "Flags for all enabled features",
1759 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1760 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1761 g_object_class_install_property (object_class,
1762 PROP_INDIVIDUAL_FEATURES,
1763 g_param_spec_flags ("individual-features",
1764 "Features of the contact menu",
1765 "Flags for all enabled features for the menu",
1766 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1767 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1769 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1773 empathy_individual_view_init (EmpathyIndividualView *view)
1775 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1776 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1779 /* Get saved group states. */
1780 empathy_contact_groups_get_all ();
1782 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1783 empathy_individual_store_row_separator_func, NULL, NULL);
1785 /* Set up drag target lists. */
1786 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1787 G_N_ELEMENTS (drag_types_dest_file));
1789 /* Connect to tree view signals rather than override. */
1790 g_signal_connect (view, "button-press-event",
1791 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1792 g_signal_connect (view, "key-press-event",
1793 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1794 g_signal_connect (view, "row-expanded",
1795 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1796 GINT_TO_POINTER (TRUE));
1797 g_signal_connect (view, "row-collapsed",
1798 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1799 GINT_TO_POINTER (FALSE));
1800 g_signal_connect (view, "query-tooltip",
1801 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1804 EmpathyIndividualView *
1805 empathy_individual_view_new (EmpathyIndividualStore *store,
1806 EmpathyIndividualViewFeatureFlags view_features,
1807 EmpathyIndividualFeatureFlags individual_features)
1809 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1811 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1813 "individual-features", individual_features,
1814 "view-features", view_features, NULL);
1818 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1820 EmpathyIndividualViewPriv *priv;
1821 GtkTreeSelection *selection;
1823 GtkTreeModel *model;
1824 FolksIndividual *individual;
1826 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1828 priv = GET_PRIV (view);
1830 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1831 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1834 gtk_tree_model_get (model, &iter,
1835 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1840 EmpathyIndividualManagerFlags
1841 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1843 EmpathyIndividualViewPriv *priv;
1844 GtkTreeSelection *selection;
1846 GtkTreeModel *model;
1847 EmpathyIndividualFeatureFlags flags;
1849 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1851 priv = GET_PRIV (view);
1853 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1854 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1857 gtk_tree_model_get (model, &iter,
1858 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1864 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1865 gboolean *is_fake_group)
1867 EmpathyIndividualViewPriv *priv;
1868 GtkTreeSelection *selection;
1870 GtkTreeModel *model;
1875 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1877 priv = GET_PRIV (view);
1879 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1880 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1883 gtk_tree_model_get (model, &iter,
1884 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1885 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1886 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1894 if (is_fake_group != NULL)
1895 *is_fake_group = fake;
1901 individual_view_remove_dialog_show (GtkWindow *parent,
1902 const gchar *message,
1903 const gchar *secondary_text)
1908 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1909 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1910 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1911 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1912 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1913 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1914 "%s", secondary_text);
1916 gtk_widget_show (dialog);
1918 res = gtk_dialog_run (GTK_DIALOG (dialog));
1919 gtk_widget_destroy (dialog);
1921 return (res == GTK_RESPONSE_YES);
1925 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1926 EmpathyIndividualView *view)
1930 group = empathy_individual_view_get_selected_group (view, NULL);
1937 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1939 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1940 if (individual_view_remove_dialog_show (parent, _("Removing group"),
1943 EmpathyIndividualManager *manager =
1944 empathy_individual_manager_dup_singleton ();
1945 empathy_individual_manager_remove_group (manager, group);
1946 g_object_unref (G_OBJECT (manager));
1956 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
1958 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1963 gboolean is_fake_group;
1965 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1967 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
1968 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
1971 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
1972 if (!group || is_fake_group)
1974 /* We can't alter fake groups */
1978 menu = gtk_menu_new ();
1981 if (priv->view_features &
1982 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
1983 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1984 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1985 gtk_widget_show (item);
1986 g_signal_connect (item, "activate",
1987 G_CALLBACK (individual_view_group_rename_activate_cb),
1992 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
1994 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1995 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1996 GTK_ICON_SIZE_MENU);
1997 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1998 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1999 gtk_widget_show (item);
2000 g_signal_connect (item, "activate",
2001 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2010 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2011 EmpathyIndividualView *view)
2013 FolksIndividual *individual;
2015 individual = empathy_individual_view_dup_selected (view);
2017 if (individual != NULL)
2022 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2025 ("Do you really want to remove the contact '%s'?"),
2026 folks_individual_get_alias (individual));
2027 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2030 EmpathyIndividualManager *manager;
2032 manager = empathy_individual_manager_dup_singleton ();
2033 empathy_individual_manager_remove (manager, individual, "");
2034 g_object_unref (G_OBJECT (manager));
2038 g_object_unref (individual);
2043 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2045 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2046 FolksIndividual *individual;
2047 GtkWidget *menu = NULL;
2050 EmpathyIndividualManagerFlags flags;
2052 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2054 individual = empathy_individual_view_dup_selected (view);
2055 if (individual == NULL)
2058 flags = empathy_individual_view_get_flags (view);
2060 menu = empathy_individual_menu_new (individual, priv->individual_features);
2062 /* Remove contact */
2063 if (priv->view_features &
2064 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2065 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2068 /* create the menu if required, or just add a separator */
2070 menu = gtk_menu_new ();
2073 item = gtk_separator_menu_item_new ();
2074 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2075 gtk_widget_show (item);
2079 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2080 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2081 GTK_ICON_SIZE_MENU);
2082 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2083 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2084 gtk_widget_show (item);
2085 g_signal_connect (item, "activate",
2086 G_CALLBACK (individual_view_remove_activate_cb), view);
2089 g_object_unref (individual);
2095 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2096 EmpathyLiveSearch *search)
2098 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2100 /* remove old handlers if old search was not null */
2101 if (priv->search_widget != NULL)
2103 g_signal_handlers_disconnect_by_func (view,
2104 individual_view_start_search_cb, NULL);
2106 g_signal_handlers_disconnect_by_func (priv->search_widget,
2107 individual_view_search_text_notify_cb, view);
2108 g_signal_handlers_disconnect_by_func (priv->search_widget,
2109 individual_view_search_activate_cb, view);
2110 g_signal_handlers_disconnect_by_func (priv->search_widget,
2111 individual_view_search_hide_cb, view);
2112 g_signal_handlers_disconnect_by_func (priv->search_widget,
2113 individual_view_search_show_cb, view);
2114 g_object_unref (priv->search_widget);
2115 priv->search_widget = NULL;
2118 /* connect handlers if new search is not null */
2121 priv->search_widget = g_object_ref (search);
2123 g_signal_connect (view, "start-interactive-search",
2124 G_CALLBACK (individual_view_start_search_cb), NULL);
2126 g_signal_connect (priv->search_widget, "notify::text",
2127 G_CALLBACK (individual_view_search_text_notify_cb), view);
2128 g_signal_connect (priv->search_widget, "activate",
2129 G_CALLBACK (individual_view_search_activate_cb), view);
2130 g_signal_connect (priv->search_widget, "hide",
2131 G_CALLBACK (individual_view_search_hide_cb), view);
2132 g_signal_connect (priv->search_widget, "show",
2133 G_CALLBACK (individual_view_search_show_cb), view);