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 <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-call-factory.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-dispatcher.h>
45 #include <libempathy/empathy-utils.h>
47 #include "empathy-individual-view.h"
48 #include "empathy-individual-menu.h"
49 #include "empathy-individual-store.h"
50 #include "empathy-images.h"
51 #include "empathy-cell-renderer-expander.h"
52 #include "empathy-cell-renderer-text.h"
53 #include "empathy-cell-renderer-activatable.h"
54 #include "empathy-ui-utils.h"
55 #include "empathy-gtk-enum-types.h"
56 #include "empathy-gtk-marshal.h"
58 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
59 #include <libempathy/empathy-debug.h>
61 /* Active users are those which have recently changed state
62 * (e.g. online, offline or from normal to a busy state).
65 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
68 EmpathyIndividualStore *store;
69 GtkTreeRowReference *drag_row;
70 EmpathyIndividualViewFeatureFlags view_features;
71 EmpathyContactFeatureFlags individual_features;
72 GtkWidget *tooltip_widget;
73 GtkTargetList *file_targets;
75 gboolean show_offline;
77 GtkTreeModelFilter *filter;
78 GtkWidget *search_widget;
80 guint expand_groups_idle_handler;
81 /* owned string (group name) -> bool (whether to expand/contract) */
82 GHashTable *expand_groups;
83 } EmpathyIndividualViewPriv;
87 EmpathyIndividualView *view;
94 EmpathyIndividualView *view;
95 FolksIndividual *individual;
104 PROP_INDIVIDUAL_FEATURES,
108 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
109 * specific EmpathyContacts (between/in/out of Individuals) */
112 DND_DRAG_TYPE_INDIVIDUAL_ID,
113 DND_DRAG_TYPE_URI_LIST,
114 DND_DRAG_TYPE_STRING,
117 #define DRAG_TYPE(T,I) \
118 { (gchar *) T, 0, I }
120 static const GtkTargetEntry drag_types_dest[] = {
121 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
122 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
123 DRAG_TYPE ("text/contact-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
124 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
125 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
128 static const GtkTargetEntry drag_types_dest_file[] = {
129 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
130 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
133 static const GtkTargetEntry drag_types_source[] = {
134 DRAG_TYPE ("text/contact-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
139 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
140 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
144 DRAG_CONTACT_RECEIVED,
148 static guint signals[LAST_SIGNAL];
150 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
154 individual_view_tooltip_destroy_cb (GtkWidget *widget,
155 EmpathyIndividualView *view)
157 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
159 if (priv->tooltip_widget != NULL)
161 DEBUG ("Tooltip destroyed");
162 tp_clear_object (&priv->tooltip_widget);
167 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
170 gboolean keyboard_mode,
174 EmpathyIndividualViewPriv *priv;
175 FolksIndividual *individual;
179 static gint running = 0;
180 gboolean ret = FALSE;
182 priv = GET_PRIV (view);
184 /* Avoid an infinite loop. See GNOME bug #574377 */
190 /* Don't show the tooltip if there's already a popup menu */
191 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
194 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
195 keyboard_mode, &model, &path, &iter))
198 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
199 gtk_tree_path_free (path);
201 gtk_tree_model_get (model, &iter,
202 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
204 if (individual == NULL)
207 if (priv->tooltip_widget == NULL)
209 priv->tooltip_widget = empathy_individual_widget_new (individual,
210 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
211 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION);
212 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
213 g_object_ref (priv->tooltip_widget);
214 g_signal_connect (priv->tooltip_widget, "destroy",
215 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
216 gtk_widget_show (priv->tooltip_widget);
220 empathy_individual_widget_set_individual (
221 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
224 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
227 g_object_unref (individual);
235 groups_change_group_cb (GObject *source,
236 GAsyncResult *result,
239 FolksGroups *groups = FOLKS_GROUPS (source);
240 GError *error = NULL;
242 folks_groups_change_group_finish (groups, result, &error);
245 g_warning ("failed to change group: %s", error->message);
246 g_clear_error (&error);
251 group_can_be_modified (const gchar *name,
252 gboolean is_fake_group,
255 /* Real groups can always be modified */
259 /* The favorite fake group can be modified so users can
260 * add/remove favorites using DnD */
261 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
264 /* We can remove contacts from the 'ungrouped' fake group */
265 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
272 individual_view_contact_drag_received (GtkWidget *self,
273 GdkDragContext *context,
276 GtkSelectionData *selection)
278 EmpathyIndividualViewPriv *priv;
279 EmpathyIndividualManager *manager = NULL;
280 FolksIndividual *individual;
281 GtkTreePath *source_path;
282 const gchar *sel_data;
283 gchar *new_group = NULL;
284 gchar *old_group = NULL;
285 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
287 priv = GET_PRIV (self);
289 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
290 new_group = empathy_individual_store_get_parent_group (model, path,
291 NULL, &new_group_is_fake);
293 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
296 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
297 * feature. Otherwise, we just add the dropped contact to whichever group
298 * they were dropped in, and don't remove them from their old group. This
299 * allows for Individual views which shouldn't allow Individuals to have
300 * their groups changed, and also for dragging Individuals between Individual
302 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
303 priv->drag_row != NULL)
305 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
309 empathy_individual_store_get_parent_group (model, source_path,
310 NULL, &old_group_is_fake);
311 gtk_tree_path_free (source_path);
314 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
317 if (!tp_strdiff (old_group, new_group))
320 else if (priv->drag_row != NULL)
322 /* We don't allow changing Individuals' groups, and this Individual was
323 * dragged from another group in *this* Individual view, so we disallow
328 /* XXX: for contacts, we used to ensure the account, create the contact
329 * factory, and then wait on the contacts. But they should already be
330 * created by this point */
332 manager = empathy_individual_manager_dup_singleton ();
333 individual = empathy_individual_manager_lookup_member (manager, sel_data);
335 if (individual == NULL)
337 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
341 /* FIXME: We should probably wait for the cb before calling
344 DEBUG ("individual %s dragged from '%s' to '%s'",
345 folks_individual_get_id (individual), old_group, new_group);
347 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
349 /* Mark contact as favourite */
350 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
354 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
356 /* Remove contact as favourite */
357 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual),
360 /* Don't try to remove it */
364 if (new_group != NULL)
366 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
367 groups_change_group_cb, NULL);
370 if (old_group != NULL &&
371 gdk_drag_context_get_selected_action (context) == GDK_ACTION_MOVE)
373 folks_groups_change_group (FOLKS_GROUPS (individual), old_group,
374 FALSE, groups_change_group_cb, NULL);
381 tp_clear_object (&manager);
389 individual_view_file_drag_received (GtkWidget *view,
390 GdkDragContext *context,
393 GtkSelectionData *selection)
396 const gchar *sel_data;
397 FolksIndividual *individual;
398 EmpathyContact *contact;
400 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
402 gtk_tree_model_get_iter (model, &iter, path);
403 gtk_tree_model_get (model, &iter,
404 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
405 if (individual == NULL)
408 contact = empathy_contact_dup_from_folks_individual (individual);
409 empathy_send_file_from_uri_list (contact, sel_data);
411 g_object_unref (individual);
412 tp_clear_object (&contact);
418 individual_view_drag_data_received (GtkWidget *view,
419 GdkDragContext *context,
422 GtkSelectionData *selection,
428 GtkTreeViewDropPosition position;
430 gboolean success = TRUE;
432 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
434 /* Get destination group information. */
435 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
436 x, y, &path, &position);
441 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
442 || info == DND_DRAG_TYPE_STRING)
444 success = individual_view_contact_drag_received (view,
445 context, model, path, selection);
447 else if (info == DND_DRAG_TYPE_URI_LIST)
449 success = individual_view_file_drag_received (view,
450 context, model, path, selection);
453 gtk_tree_path_free (path);
454 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
458 individual_view_drag_motion_cb (DragMotionData *data)
460 if (data->view != NULL)
462 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
463 g_object_remove_weak_pointer (G_OBJECT (data->view),
464 (gpointer *) &data->view);
467 data->timeout_id = 0;
473 individual_view_drag_motion (GtkWidget *widget,
474 GdkDragContext *context,
479 EmpathyIndividualViewPriv *priv;
483 static DragMotionData *dm = NULL;
486 gboolean is_different = FALSE;
487 gboolean cleanup = TRUE;
488 gboolean retval = TRUE;
490 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
491 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
493 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
494 x, y, &path, NULL, NULL, NULL);
496 cleanup &= (dm == NULL);
500 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
501 is_different = ((dm == NULL) || ((dm != NULL)
502 && gtk_tree_path_compare (dm->path, path) != 0));
509 /* Coordinates don't point to an actual row, so make sure the pointer
510 and highlighting don't indicate that a drag is possible.
512 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
513 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
516 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
517 gtk_tree_model_get_iter (model, &iter, path);
519 if (target == GDK_NONE &&
520 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
521 priv->drag_row == NULL))
523 /* If target == GDK_NONE, then we don't have a target that can be
524 dropped on a contact. This means a contact drag. If we're
525 pointing to a group, highlight it. Otherwise, if the contact
526 we're pointing to is in a group, highlight that. Otherwise,
527 set the drag position to before the first row for a drag into
528 the "non-group" at the top.
529 We only highlight things if the contact is from a different Individual
530 view, or if this Individual view has FEATURE_GROUPS_CHANGE. This
531 prevents highlighting in Individual views which don't have
532 FEATURE_GROUPS_CHANGE, but do have FEATURE_INDIVIDUAL_DRAG and
533 FEATURE_INDIVIDUAL_DROP.
535 GtkTreeIter group_iter;
537 GtkTreePath *group_path;
538 gtk_tree_model_get (model, &iter,
539 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
546 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
547 gtk_tree_model_get (model, &group_iter,
548 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
552 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
553 group_path = gtk_tree_model_get_path (model, &group_iter);
554 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
555 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
556 gtk_tree_path_free (group_path);
560 group_path = gtk_tree_path_new_first ();
561 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
562 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
563 group_path, GTK_TREE_VIEW_DROP_BEFORE);
566 else if (target != GDK_NONE)
568 /* This is a file drag, and it can only be dropped on contacts,
570 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
571 * even if we have a valid target. */
572 FolksIndividual *individual = NULL;
573 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
575 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
577 gtk_tree_model_get (model, &iter,
578 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
582 if (individual != NULL)
584 EmpathyContact *contact = NULL;
586 contact = empathy_contact_dup_from_folks_individual (individual);
587 caps = empathy_contact_get_capabilities (contact);
589 tp_clear_object (&contact);
592 if (individual != NULL &&
593 folks_individual_is_online (individual) &&
594 (caps & EMPATHY_CAPABILITIES_FT))
596 gdk_drag_status (context, GDK_ACTION_COPY, time_);
597 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
598 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
602 gdk_drag_status (context, 0, time_);
603 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
607 if (individual != NULL)
608 g_object_unref (individual);
611 if (!is_different && !cleanup)
616 gtk_tree_path_free (dm->path);
619 g_source_remove (dm->timeout_id);
627 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
629 dm = g_new0 (DragMotionData, 1);
631 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
632 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
633 dm->path = gtk_tree_path_copy (path);
635 dm->timeout_id = g_timeout_add_seconds (1,
636 (GSourceFunc) individual_view_drag_motion_cb, dm);
643 individual_view_drag_begin (GtkWidget *widget,
644 GdkDragContext *context)
646 EmpathyIndividualViewPriv *priv;
647 GtkTreeSelection *selection;
652 priv = GET_PRIV (widget);
654 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
657 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
658 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
661 path = gtk_tree_model_get_path (model, &iter);
662 priv->drag_row = gtk_tree_row_reference_new (model, path);
663 gtk_tree_path_free (path);
667 individual_view_drag_data_get (GtkWidget *widget,
668 GdkDragContext *context,
669 GtkSelectionData *selection,
673 EmpathyIndividualViewPriv *priv;
674 GtkTreePath *src_path;
677 FolksIndividual *individual;
678 const gchar *individual_id;
680 priv = GET_PRIV (widget);
682 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
683 if (priv->drag_row == NULL)
686 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
687 if (src_path == NULL)
690 if (!gtk_tree_model_get_iter (model, &iter, src_path))
692 gtk_tree_path_free (src_path);
696 gtk_tree_path_free (src_path);
699 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
700 if (individual == NULL)
703 individual_id = folks_individual_get_id (individual);
705 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
707 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
708 (guchar *) individual_id, strlen (individual_id) + 1);
711 g_object_unref (individual);
715 individual_view_drag_end (GtkWidget *widget,
716 GdkDragContext *context)
718 EmpathyIndividualViewPriv *priv;
720 priv = GET_PRIV (widget);
722 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
727 gtk_tree_row_reference_free (priv->drag_row);
728 priv->drag_row = NULL;
733 individual_view_drag_drop (GtkWidget *widget,
734 GdkDragContext *drag_context,
744 EmpathyIndividualView *view;
750 individual_view_popup_menu_idle_cb (gpointer user_data)
752 MenuPopupData *data = user_data;
755 menu = empathy_individual_view_get_individual_menu (data->view);
757 menu = empathy_individual_view_get_group_menu (data->view);
761 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
762 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
764 gtk_widget_show (menu);
765 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
767 g_object_ref_sink (menu);
768 g_object_unref (menu);
771 g_slice_free (MenuPopupData, data);
777 individual_view_button_press_event_cb (EmpathyIndividualView *view,
778 GdkEventButton *event,
781 if (event->button == 3)
785 data = g_slice_new (MenuPopupData);
787 data->button = event->button;
788 data->time = event->time;
789 g_idle_add (individual_view_popup_menu_idle_cb, data);
796 individual_view_key_press_event_cb (EmpathyIndividualView *view,
800 if (event->keyval == GDK_Menu)
804 data = g_slice_new (MenuPopupData);
807 data->time = event->time;
808 g_idle_add (individual_view_popup_menu_idle_cb, data);
815 individual_view_row_activated (GtkTreeView *view,
817 GtkTreeViewColumn *column)
819 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
820 FolksIndividual *individual;
821 EmpathyContact *contact = NULL;
825 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
828 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
829 gtk_tree_model_get_iter (model, &iter, path);
830 gtk_tree_model_get (model, &iter,
831 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
833 if (individual == NULL)
836 contact = empathy_contact_dup_from_folks_individual (individual);
839 DEBUG ("Starting a chat");
841 empathy_dispatcher_chat_with_contact (contact,
842 gtk_get_current_event_time ());
845 g_object_unref (individual);
846 tp_clear_object (&contact);
850 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
851 const gchar *path_string,
852 EmpathyIndividualView *view)
857 FolksIndividual *individual;
858 GdkEventButton *event;
862 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
863 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
866 gtk_tree_model_get (model, &iter,
867 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
868 if (individual == NULL)
871 event = (GdkEventButton *) gtk_get_current_event ();
873 menu = gtk_menu_new ();
874 shell = GTK_MENU_SHELL (menu);
877 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
878 gtk_menu_shell_append (shell, item);
879 gtk_widget_show (item);
882 item = empathy_individual_video_call_menu_item_new (individual, NULL);
883 gtk_menu_shell_append (shell, item);
884 gtk_widget_show (item);
886 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
887 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
888 gtk_widget_show (menu);
889 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
890 event->button, event->time);
891 g_object_ref_sink (menu);
892 g_object_unref (menu);
894 g_object_unref (individual);
898 individual_view_cell_set_background (EmpathyIndividualView *view,
899 GtkCellRenderer *cell,
906 style = gtk_widget_get_style (GTK_WIDGET (view));
908 if (!is_group && is_active)
910 color = style->bg[GTK_STATE_SELECTED];
912 /* Here we take the current theme colour and add it to
913 * the colour for white and average the two. This
914 * gives a colour which is inline with the theme but
917 color.red = (color.red + (style->white).red) / 2;
918 color.green = (color.green + (style->white).green) / 2;
919 color.blue = (color.blue + (style->white).blue) / 2;
921 g_object_set (cell, "cell-background-gdk", &color, NULL);
924 g_object_set (cell, "cell-background-gdk", NULL, NULL);
928 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
929 GtkCellRenderer *cell,
932 EmpathyIndividualView *view)
938 gtk_tree_model_get (model, iter,
939 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
940 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
941 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
944 "visible", !is_group,
948 tp_clear_object (&pixbuf);
950 individual_view_cell_set_background (view, cell, is_group, is_active);
954 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
955 GtkCellRenderer *cell,
958 EmpathyIndividualView *view)
960 GdkPixbuf *pixbuf = NULL;
964 gtk_tree_model_get (model, iter,
965 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
966 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
971 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
973 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
976 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
978 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
984 "visible", pixbuf != NULL,
988 tp_clear_object (&pixbuf);
994 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
995 GtkCellRenderer *cell,
998 EmpathyIndividualView *view)
1002 gboolean can_audio, can_video;
1004 gtk_tree_model_get (model, iter,
1005 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1006 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1007 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1008 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1011 "visible", !is_group && (can_audio || can_video),
1012 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1015 individual_view_cell_set_background (view, cell, is_group, is_active);
1019 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1020 GtkCellRenderer *cell,
1021 GtkTreeModel *model,
1023 EmpathyIndividualView *view)
1026 gboolean show_avatar;
1030 gtk_tree_model_get (model, iter,
1031 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1032 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1033 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1034 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1037 "visible", !is_group && show_avatar,
1041 tp_clear_object (&pixbuf);
1043 individual_view_cell_set_background (view, cell, is_group, is_active);
1047 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1048 GtkCellRenderer *cell,
1049 GtkTreeModel *model,
1051 EmpathyIndividualView *view)
1056 gtk_tree_model_get (model, iter,
1057 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1058 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1060 individual_view_cell_set_background (view, cell, is_group, is_active);
1064 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1065 GtkCellRenderer *cell,
1066 GtkTreeModel *model,
1068 EmpathyIndividualView *view)
1073 gtk_tree_model_get (model, iter,
1074 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1075 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1077 if (gtk_tree_model_iter_has_child (model, iter))
1080 gboolean row_expanded;
1082 path = gtk_tree_model_get_path (model, iter);
1084 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1085 (gtk_tree_view_column_get_tree_view (column)), path);
1086 gtk_tree_path_free (path);
1091 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1095 g_object_set (cell, "visible", FALSE, NULL);
1097 individual_view_cell_set_background (view, cell, is_group, is_active);
1101 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1106 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1107 GtkTreeModel *model;
1111 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1114 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1116 gtk_tree_model_get (model, iter,
1117 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1119 expanded = GPOINTER_TO_INT (user_data);
1120 empathy_contact_group_set_expanded (name, expanded);
1126 individual_view_start_search_cb (EmpathyIndividualView *view,
1129 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1131 if (priv->search_widget == NULL)
1134 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1135 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1137 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1143 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1145 EmpathyIndividualView *view)
1147 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1149 GtkTreeViewColumn *focus_column;
1150 GtkTreeModel *model;
1152 gboolean set_cursor = FALSE;
1154 gtk_tree_model_filter_refilter (priv->filter);
1156 /* Set cursor on the first contact. If it is already set on a group,
1157 * set it on its first child contact. Note that first child of a group
1158 * is its separator, that's why we actually set to the 2nd
1161 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1162 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1166 path = gtk_tree_path_new_from_string ("0:1");
1169 else if (gtk_tree_path_get_depth (path) < 2)
1173 gtk_tree_model_get_iter (model, &iter, path);
1174 gtk_tree_model_get (model, &iter,
1175 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1180 gtk_tree_path_down (path);
1181 gtk_tree_path_next (path);
1188 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1190 if (gtk_tree_model_get_iter (model, &iter, path))
1192 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1197 gtk_tree_path_free (path);
1201 individual_view_search_activate_cb (GtkWidget *search,
1202 EmpathyIndividualView *view)
1205 GtkTreeViewColumn *focus_column;
1207 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1210 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1211 gtk_tree_path_free (path);
1213 gtk_widget_hide (search);
1218 individual_view_search_key_navigation_cb (GtkWidget *search,
1220 EmpathyIndividualView *view)
1222 GdkEventKey *eventkey = ((GdkEventKey *) event);
1223 gboolean ret = FALSE;
1225 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1227 GdkEvent *new_event;
1229 new_event = gdk_event_copy (event);
1230 gtk_widget_grab_focus (GTK_WIDGET (view));
1231 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1232 gtk_widget_grab_focus (search);
1234 gdk_event_free (new_event);
1241 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1242 EmpathyIndividualView *view)
1244 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1245 GtkTreeModel *model;
1246 GtkTreePath *cursor_path;
1248 gboolean valid = FALSE;
1250 /* block expand or collapse handlers, they would write the
1251 * expand or collapsed setting to file otherwise */
1252 g_signal_handlers_block_by_func (view,
1253 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1254 g_signal_handlers_block_by_func (view,
1255 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1257 /* restore which groups are expanded and which are not */
1258 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1259 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1260 valid; valid = gtk_tree_model_iter_next (model, &iter))
1266 gtk_tree_model_get (model, &iter,
1267 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1268 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1277 path = gtk_tree_model_get_path (model, &iter);
1278 if ((priv->view_features &
1279 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1280 empathy_contact_group_get_expanded (name))
1282 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1286 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1289 gtk_tree_path_free (path);
1293 /* unblock expand or collapse handlers */
1294 g_signal_handlers_unblock_by_func (view,
1295 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1296 g_signal_handlers_unblock_by_func (view,
1297 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1299 /* keep the selected contact visible */
1300 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1302 if (cursor_path != NULL)
1303 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1306 gtk_tree_path_free (cursor_path);
1310 individual_view_search_show_cb (EmpathyLiveSearch *search,
1311 EmpathyIndividualView *view)
1313 /* block expand or collapse handlers during expand all, they would
1314 * write the expand or collapsed setting to file otherwise */
1315 g_signal_handlers_block_by_func (view,
1316 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1318 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1320 g_signal_handlers_unblock_by_func (view,
1321 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1325 expand_idle_foreach_cb (GtkTreeModel *model,
1328 EmpathyIndividualView *self)
1330 EmpathyIndividualViewPriv *priv;
1332 gpointer should_expand;
1335 /* We only want groups */
1336 if (gtk_tree_path_get_depth (path) > 1)
1339 gtk_tree_model_get (model, iter,
1340 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1341 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1344 if (is_group == FALSE)
1350 priv = GET_PRIV (self);
1352 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1353 &should_expand) == TRUE)
1355 if (GPOINTER_TO_INT (should_expand) == TRUE)
1356 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1358 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1360 g_hash_table_remove (priv->expand_groups, name);
1369 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1371 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1373 DEBUG ("individual_view_expand_idle_cb");
1375 g_signal_handlers_block_by_func (self,
1376 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1377 g_signal_handlers_block_by_func (self,
1378 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1380 /* The store/filter could've been removed while we were in the idle queue */
1381 if (priv->filter != NULL)
1383 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1384 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1387 g_signal_handlers_unblock_by_func (self,
1388 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1389 g_signal_handlers_unblock_by_func (self,
1390 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1392 g_object_unref (self);
1393 priv->expand_groups_idle_handler = 0;
1399 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1402 EmpathyIndividualView *view)
1404 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1405 gboolean should_expand, is_group = FALSE;
1407 gpointer will_expand;
1409 gtk_tree_model_get (model, iter,
1410 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1411 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1414 if (!is_group || EMP_STR_EMPTY (name))
1420 should_expand = (priv->view_features &
1421 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1422 (priv->search_widget != NULL &&
1423 gtk_widget_get_visible (priv->search_widget)) ||
1424 empathy_contact_group_get_expanded (name);
1426 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1427 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1428 * a hash table, and expand or contract them as appropriate all at once in
1429 * an idle handler which iterates over all the group rows. */
1430 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1431 &will_expand) == FALSE &&
1432 GPOINTER_TO_INT (will_expand) != should_expand)
1434 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1435 GINT_TO_POINTER (should_expand));
1437 if (priv->expand_groups_idle_handler == 0)
1439 priv->expand_groups_idle_handler =
1440 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1441 g_object_ref (view));
1448 /* FIXME: This is a workaround for bgo#621076 */
1450 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1453 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1454 GtkTreeModel *model;
1455 GtkTreePath *parent_path;
1456 GtkTreeIter parent_iter;
1458 if (gtk_tree_path_get_depth (path) < 2)
1461 /* A group row is visible if and only if at least one if its child is visible.
1462 * So when a row is inserted/deleted/changed in the base model, that could
1463 * modify the visibility of its parent in the filter model.
1466 model = GTK_TREE_MODEL (priv->store);
1467 parent_path = gtk_tree_path_copy (path);
1468 gtk_tree_path_up (parent_path);
1469 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1471 /* This tells the filter to verify the visibility of that row, and
1472 * show/hide it if necessary */
1473 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1474 parent_path, &parent_iter);
1476 gtk_tree_path_free (parent_path);
1480 individual_view_store_row_changed_cb (GtkTreeModel *model,
1483 EmpathyIndividualView *view)
1485 individual_view_verify_group_visibility (view, path);
1489 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1491 EmpathyIndividualView *view)
1493 individual_view_verify_group_visibility (view, path);
1497 individual_view_is_visible_individual (EmpathyIndividualView *self,
1498 FolksIndividual *individual)
1500 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1501 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1503 GList *personas, *l;
1505 /* We're only giving the visibility wrt filtering here, not things like
1507 if (live == NULL || gtk_widget_get_visible (GTK_WIDGET (live)) == FALSE)
1510 /* check alias name */
1511 str = folks_individual_get_alias (individual);
1513 if (empathy_live_search_match (live, str))
1516 /* check contact id, remove the @server.com part */
1517 personas = folks_individual_get_personas (individual);
1518 for (l = personas; l; l = l->next)
1521 gchar *dup_str = NULL;
1524 if (!TPF_IS_PERSONA (l->data))
1527 str = folks_persona_get_display_id (l->data);
1528 p = strstr (str, "@");
1530 str = dup_str = g_strndup (str, p - str);
1532 visible = empathy_live_search_match (live, str);
1538 /* FIXME: Add more rules here, we could check phone numbers in
1539 * contact's vCard for example. */
1545 individual_view_filter_visible_func (GtkTreeModel *model,
1549 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1550 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1551 FolksIndividual *individual = NULL;
1552 gboolean is_group, is_separator, valid;
1553 GtkTreeIter child_iter;
1554 gboolean visible, is_online;
1555 gboolean is_searching = TRUE;
1557 if (priv->search_widget == NULL ||
1558 !gtk_widget_get_visible (priv->search_widget))
1559 is_searching = FALSE;
1561 gtk_tree_model_get (model, iter,
1562 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1563 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1564 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1565 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1568 if (individual != NULL)
1570 if (is_searching == TRUE)
1571 visible = individual_view_is_visible_individual (self, individual);
1573 visible = (priv->show_offline || is_online);
1575 g_object_unref (individual);
1577 /* FIXME: Work around bgo#626552/bgo#621076 */
1578 if (visible == TRUE)
1580 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1581 individual_view_verify_group_visibility (self, path);
1582 gtk_tree_path_free (path);
1591 /* Not a contact, not a separator, must be a group */
1592 g_return_val_if_fail (is_group, FALSE);
1594 /* only show groups which are not empty */
1595 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1596 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1598 gtk_tree_model_get (model, &child_iter,
1599 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1600 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1603 if (individual == NULL)
1606 visible = individual_view_is_visible_individual (self, individual);
1607 g_object_unref (individual);
1609 /* show group if it has at least one visible contact in it */
1610 if ((is_searching && visible) ||
1611 (!is_searching && (priv->show_offline || is_online)))
1619 individual_view_constructed (GObject *object)
1621 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1622 GtkCellRenderer *cell;
1623 GtkTreeViewColumn *col;
1628 "headers-visible", FALSE,
1629 "show-expanders", FALSE,
1632 col = gtk_tree_view_column_new ();
1635 cell = gtk_cell_renderer_pixbuf_new ();
1636 gtk_tree_view_column_pack_start (col, cell, FALSE);
1637 gtk_tree_view_column_set_cell_data_func (col, cell,
1638 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1648 cell = gtk_cell_renderer_pixbuf_new ();
1649 gtk_tree_view_column_pack_start (col, cell, FALSE);
1650 gtk_tree_view_column_set_cell_data_func (col, cell,
1651 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1663 cell = empathy_cell_renderer_text_new ();
1664 gtk_tree_view_column_pack_start (col, cell, TRUE);
1665 gtk_tree_view_column_set_cell_data_func (col, cell,
1666 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1668 gtk_tree_view_column_add_attribute (col, cell,
1669 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1670 gtk_tree_view_column_add_attribute (col, cell,
1671 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1672 gtk_tree_view_column_add_attribute (col, cell,
1673 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1674 gtk_tree_view_column_add_attribute (col, cell,
1675 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1676 gtk_tree_view_column_add_attribute (col, cell,
1677 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1678 gtk_tree_view_column_add_attribute (col, cell,
1679 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1681 /* Audio Call Icon */
1682 cell = empathy_cell_renderer_activatable_new ();
1683 gtk_tree_view_column_pack_start (col, cell, FALSE);
1684 gtk_tree_view_column_set_cell_data_func (col, cell,
1685 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1688 g_object_set (cell, "visible", FALSE, NULL);
1690 g_signal_connect (cell, "path-activated",
1691 G_CALLBACK (individual_view_call_activated_cb), view);
1694 cell = gtk_cell_renderer_pixbuf_new ();
1695 gtk_tree_view_column_pack_start (col, cell, FALSE);
1696 gtk_tree_view_column_set_cell_data_func (col, cell,
1697 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1709 cell = empathy_cell_renderer_expander_new ();
1710 gtk_tree_view_column_pack_end (col, cell, FALSE);
1711 gtk_tree_view_column_set_cell_data_func (col, cell,
1712 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1715 /* Actually add the column now we have added all cell renderers */
1716 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1719 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1721 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1724 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1726 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1732 individual_view_set_view_features (EmpathyIndividualView *view,
1733 EmpathyIndividualFeatureFlags features)
1735 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1736 gboolean has_tooltip;
1738 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1740 priv->view_features = features;
1742 /* Setting reorderable is a hack that gets us row previews as drag icons
1743 for free. We override all the drag handlers. It's tricky to get the
1744 position of the drag icon right in drag_begin. GtkTreeView has special
1745 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1748 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1749 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1751 /* Update DnD source/dest */
1752 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1754 gtk_drag_source_set (GTK_WIDGET (view),
1757 G_N_ELEMENTS (drag_types_source),
1758 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1762 gtk_drag_source_unset (GTK_WIDGET (view));
1766 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1768 gtk_drag_dest_set (GTK_WIDGET (view),
1769 GTK_DEST_DEFAULT_ALL,
1771 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1775 /* FIXME: URI could still be droped depending on FT feature */
1776 gtk_drag_dest_unset (GTK_WIDGET (view));
1779 /* Update has-tooltip */
1781 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1782 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1786 individual_view_dispose (GObject *object)
1788 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1789 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1791 tp_clear_object (&priv->store);
1792 tp_clear_object (&priv->filter);
1793 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1794 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1796 empathy_individual_view_set_live_search (view, NULL);
1798 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1802 individual_view_finalize (GObject *object)
1804 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1806 g_hash_table_destroy (priv->expand_groups);
1808 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1812 individual_view_get_property (GObject *object,
1817 EmpathyIndividualViewPriv *priv;
1819 priv = GET_PRIV (object);
1824 g_value_set_object (value, priv->store);
1826 case PROP_VIEW_FEATURES:
1827 g_value_set_flags (value, priv->view_features);
1829 case PROP_INDIVIDUAL_FEATURES:
1830 g_value_set_flags (value, priv->individual_features);
1832 case PROP_SHOW_OFFLINE:
1833 g_value_set_boolean (value, priv->show_offline);
1836 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1842 individual_view_set_property (GObject *object,
1844 const GValue *value,
1847 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1848 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1853 empathy_individual_view_set_store (view, g_value_get_object (value));
1855 case PROP_VIEW_FEATURES:
1856 individual_view_set_view_features (view, g_value_get_flags (value));
1858 case PROP_INDIVIDUAL_FEATURES:
1859 priv->individual_features = g_value_get_flags (value);
1861 case PROP_SHOW_OFFLINE:
1862 empathy_individual_view_set_show_offline (view,
1863 g_value_get_boolean (value));
1866 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1872 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1874 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1875 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1876 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1878 object_class->constructed = individual_view_constructed;
1879 object_class->dispose = individual_view_dispose;
1880 object_class->finalize = individual_view_finalize;
1881 object_class->get_property = individual_view_get_property;
1882 object_class->set_property = individual_view_set_property;
1884 widget_class->drag_data_received = individual_view_drag_data_received;
1885 widget_class->drag_drop = individual_view_drag_drop;
1886 widget_class->drag_begin = individual_view_drag_begin;
1887 widget_class->drag_data_get = individual_view_drag_data_get;
1888 widget_class->drag_end = individual_view_drag_end;
1889 widget_class->drag_motion = individual_view_drag_motion;
1891 /* We use the class method to let user of this widget to connect to
1892 * the signal and stop emission of the signal so the default handler
1893 * won't be called. */
1894 tree_view_class->row_activated = individual_view_row_activated;
1896 signals[DRAG_CONTACT_RECEIVED] =
1897 g_signal_new ("drag-contact-received",
1898 G_OBJECT_CLASS_TYPE (klass),
1902 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1903 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1905 g_object_class_install_property (object_class,
1907 g_param_spec_object ("store",
1908 "The store of the view",
1909 "The store of the view",
1910 EMPATHY_TYPE_INDIVIDUAL_STORE,
1911 G_PARAM_READWRITE));
1912 g_object_class_install_property (object_class,
1914 g_param_spec_flags ("view-features",
1915 "Features of the view",
1916 "Flags for all enabled features",
1917 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1918 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1919 g_object_class_install_property (object_class,
1920 PROP_INDIVIDUAL_FEATURES,
1921 g_param_spec_flags ("individual-features",
1922 "Features of the contact menu",
1923 "Flags for all enabled features for the menu",
1924 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1925 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1926 g_object_class_install_property (object_class,
1928 g_param_spec_boolean ("show-offline",
1930 "Whether contact list should display "
1931 "offline contacts", FALSE, G_PARAM_READWRITE));
1933 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1937 empathy_individual_view_init (EmpathyIndividualView *view)
1939 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1940 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1943 /* Get saved group states. */
1944 empathy_contact_groups_get_all ();
1946 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
1947 (GDestroyNotify) g_free, NULL);
1949 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1950 empathy_individual_store_row_separator_func, NULL, NULL);
1952 /* Set up drag target lists. */
1953 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1954 G_N_ELEMENTS (drag_types_dest_file));
1956 /* Connect to tree view signals rather than override. */
1957 g_signal_connect (view, "button-press-event",
1958 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1959 g_signal_connect (view, "key-press-event",
1960 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1961 g_signal_connect (view, "row-expanded",
1962 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1963 GINT_TO_POINTER (TRUE));
1964 g_signal_connect (view, "row-collapsed",
1965 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1966 GINT_TO_POINTER (FALSE));
1967 g_signal_connect (view, "query-tooltip",
1968 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1971 EmpathyIndividualView *
1972 empathy_individual_view_new (EmpathyIndividualStore *store,
1973 EmpathyIndividualViewFeatureFlags view_features,
1974 EmpathyIndividualFeatureFlags individual_features)
1976 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1978 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1980 "individual-features", individual_features,
1981 "view-features", view_features, NULL);
1985 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1987 EmpathyIndividualViewPriv *priv;
1988 GtkTreeSelection *selection;
1990 GtkTreeModel *model;
1991 FolksIndividual *individual;
1993 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1995 priv = GET_PRIV (view);
1997 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1998 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2001 gtk_tree_model_get (model, &iter,
2002 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2007 EmpathyIndividualManagerFlags
2008 empathy_individual_view_get_flags (EmpathyIndividualView *view)
2010 EmpathyIndividualViewPriv *priv;
2011 GtkTreeSelection *selection;
2013 GtkTreeModel *model;
2014 EmpathyIndividualFeatureFlags flags;
2016 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
2018 priv = GET_PRIV (view);
2020 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2021 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2024 gtk_tree_model_get (model, &iter,
2025 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2031 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2032 gboolean *is_fake_group)
2034 EmpathyIndividualViewPriv *priv;
2035 GtkTreeSelection *selection;
2037 GtkTreeModel *model;
2042 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2044 priv = GET_PRIV (view);
2046 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2047 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2050 gtk_tree_model_get (model, &iter,
2051 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2052 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2053 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2061 if (is_fake_group != NULL)
2062 *is_fake_group = fake;
2068 individual_view_remove_dialog_show (GtkWindow *parent,
2069 const gchar *message,
2070 const gchar *secondary_text)
2075 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2076 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2077 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2078 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2079 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2080 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2081 "%s", secondary_text);
2083 gtk_widget_show (dialog);
2085 res = gtk_dialog_run (GTK_DIALOG (dialog));
2086 gtk_widget_destroy (dialog);
2088 return (res == GTK_RESPONSE_YES);
2092 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2093 EmpathyIndividualView *view)
2097 group = empathy_individual_view_get_selected_group (view, NULL);
2104 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2106 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2107 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2110 EmpathyIndividualManager *manager =
2111 empathy_individual_manager_dup_singleton ();
2112 empathy_individual_manager_remove_group (manager, group);
2113 g_object_unref (G_OBJECT (manager));
2123 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2125 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2130 gboolean is_fake_group;
2132 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2134 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2135 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2138 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2139 if (!group || is_fake_group)
2141 /* We can't alter fake groups */
2145 menu = gtk_menu_new ();
2148 if (priv->view_features &
2149 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2150 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2151 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2152 gtk_widget_show (item);
2153 g_signal_connect (item, "activate",
2154 G_CALLBACK (individual_view_group_rename_activate_cb),
2159 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2161 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2162 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2163 GTK_ICON_SIZE_MENU);
2164 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2165 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2166 gtk_widget_show (item);
2167 g_signal_connect (item, "activate",
2168 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2177 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2178 EmpathyIndividualView *view)
2180 FolksIndividual *individual;
2182 individual = empathy_individual_view_dup_selected (view);
2184 if (individual != NULL)
2189 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2192 ("Do you really want to remove the contact '%s'?"),
2193 folks_individual_get_alias (individual));
2194 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2197 EmpathyIndividualManager *manager;
2199 manager = empathy_individual_manager_dup_singleton ();
2200 empathy_individual_manager_remove (manager, individual, "");
2201 g_object_unref (G_OBJECT (manager));
2205 g_object_unref (individual);
2210 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2212 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2213 FolksIndividual *individual;
2214 GtkWidget *menu = NULL;
2217 EmpathyIndividualManagerFlags flags;
2219 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2221 individual = empathy_individual_view_dup_selected (view);
2222 if (individual == NULL)
2225 flags = empathy_individual_view_get_flags (view);
2227 menu = empathy_individual_menu_new (individual, priv->individual_features);
2229 /* Remove contact */
2230 if (priv->view_features &
2231 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE &&
2232 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2235 /* create the menu if required, or just add a separator */
2237 menu = gtk_menu_new ();
2240 item = gtk_separator_menu_item_new ();
2241 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2242 gtk_widget_show (item);
2246 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2247 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2248 GTK_ICON_SIZE_MENU);
2249 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2250 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2251 gtk_widget_show (item);
2252 g_signal_connect (item, "activate",
2253 G_CALLBACK (individual_view_remove_activate_cb), view);
2256 g_object_unref (individual);
2262 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2263 EmpathyLiveSearch *search)
2265 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2267 /* remove old handlers if old search was not null */
2268 if (priv->search_widget != NULL)
2270 g_signal_handlers_disconnect_by_func (view,
2271 individual_view_start_search_cb, NULL);
2273 g_signal_handlers_disconnect_by_func (priv->search_widget,
2274 individual_view_search_text_notify_cb, view);
2275 g_signal_handlers_disconnect_by_func (priv->search_widget,
2276 individual_view_search_activate_cb, view);
2277 g_signal_handlers_disconnect_by_func (priv->search_widget,
2278 individual_view_search_key_navigation_cb, view);
2279 g_signal_handlers_disconnect_by_func (priv->search_widget,
2280 individual_view_search_hide_cb, view);
2281 g_signal_handlers_disconnect_by_func (priv->search_widget,
2282 individual_view_search_show_cb, view);
2283 g_object_unref (priv->search_widget);
2284 priv->search_widget = NULL;
2287 /* connect handlers if new search is not null */
2290 priv->search_widget = g_object_ref (search);
2292 g_signal_connect (view, "start-interactive-search",
2293 G_CALLBACK (individual_view_start_search_cb), NULL);
2295 g_signal_connect (priv->search_widget, "notify::text",
2296 G_CALLBACK (individual_view_search_text_notify_cb), view);
2297 g_signal_connect (priv->search_widget, "activate",
2298 G_CALLBACK (individual_view_search_activate_cb), view);
2299 g_signal_connect (priv->search_widget, "key-navigation",
2300 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2301 g_signal_connect (priv->search_widget, "hide",
2302 G_CALLBACK (individual_view_search_hide_cb), view);
2303 g_signal_connect (priv->search_widget, "show",
2304 G_CALLBACK (individual_view_search_show_cb), view);
2309 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2311 EmpathyIndividualViewPriv *priv;
2313 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2315 priv = GET_PRIV (self);
2317 return (priv->search_widget != NULL &&
2318 gtk_widget_get_visible (priv->search_widget));
2322 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2324 EmpathyIndividualViewPriv *priv;
2326 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2328 priv = GET_PRIV (self);
2330 return priv->show_offline;
2334 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2335 gboolean show_offline)
2337 EmpathyIndividualViewPriv *priv;
2339 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2341 priv = GET_PRIV (self);
2343 priv->show_offline = show_offline;
2345 g_object_notify (G_OBJECT (self), "show-offline");
2346 gtk_tree_model_filter_refilter (priv->filter);
2349 EmpathyIndividualStore *
2350 empathy_individual_view_get_store (EmpathyIndividualView *self)
2352 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2354 return GET_PRIV (self)->store;
2358 empathy_individual_view_set_store (EmpathyIndividualView *self,
2359 EmpathyIndividualStore *store)
2361 EmpathyIndividualViewPriv *priv;
2363 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2364 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2366 priv = GET_PRIV (self);
2368 /* Destroy the old filter and remove the old store */
2369 if (priv->store != NULL)
2371 g_signal_handlers_disconnect_by_func (priv->store,
2372 individual_view_store_row_changed_cb, self);
2373 g_signal_handlers_disconnect_by_func (priv->store,
2374 individual_view_store_row_deleted_cb, self);
2376 g_signal_handlers_disconnect_by_func (priv->filter,
2377 individual_view_row_has_child_toggled_cb, self);
2379 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2382 tp_clear_object (&priv->filter);
2383 tp_clear_object (&priv->store);
2385 /* Set the new store */
2386 priv->store = store;
2390 g_object_ref (store);
2392 /* Create a new filter */
2393 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2394 GTK_TREE_MODEL (priv->store), NULL));
2395 gtk_tree_model_filter_set_visible_func (priv->filter,
2396 individual_view_filter_visible_func, self, NULL);
2398 g_signal_connect (priv->filter, "row-has-child-toggled",
2399 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2400 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2401 GTK_TREE_MODEL (priv->filter));
2403 tp_g_signal_connect_object (priv->store, "row-changed",
2404 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2405 tp_g_signal_connect_object (priv->store, "row-inserted",
2406 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2407 tp_g_signal_connect_object (priv->store, "row-deleted",
2408 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);