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 individual_view_handle_drag (EmpathyIndividualView *self,
252 FolksIndividual *individual,
253 const gchar *old_group,
254 const gchar *new_group,
255 GdkDragAction action)
257 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
258 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
260 DEBUG ("individual %s dragged from '%s' to '%s'",
261 folks_individual_get_id (individual), old_group, new_group);
263 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
265 /* Mark contact as favourite */
266 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
270 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
272 /* Remove contact as favourite */
273 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
275 /* Don't try to remove it */
279 if (new_group != NULL)
280 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
281 groups_change_group_cb, NULL);
283 if (old_group != NULL && action == GDK_ACTION_MOVE)
284 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE,
285 groups_change_group_cb, NULL);
289 group_can_be_modified (const gchar *name,
290 gboolean is_fake_group,
293 /* Real groups can always be modified */
297 /* The favorite fake group can be modified so users can
298 * add/remove favorites using DnD */
299 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
302 /* We can remove contacts from the 'ungrouped' fake group */
303 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
310 individual_view_contact_drag_received (GtkWidget *self,
311 GdkDragContext *context,
314 GtkSelectionData *selection)
316 EmpathyIndividualViewPriv *priv;
317 EmpathyIndividualManager *manager;
318 FolksIndividual *individual;
319 GtkTreePath *source_path;
320 const gchar *sel_data;
321 gchar *new_group = NULL;
322 gchar *old_group = NULL;
323 gboolean new_group_is_fake, old_group_is_fake = TRUE;
325 priv = GET_PRIV (self);
327 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
328 new_group = empathy_individual_store_get_parent_group (model, path,
329 NULL, &new_group_is_fake);
331 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
334 /* Get source group information. */
337 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
341 empathy_individual_store_get_parent_group (model, source_path,
342 NULL, &old_group_is_fake);
343 gtk_tree_path_free (source_path);
347 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
350 if (!tp_strdiff (old_group, new_group))
357 /* XXX: for contacts, we used to ensure the account, create the contact
358 * factory, and then wait on the contacts. But they should already be
359 * created by this point */
361 manager = empathy_individual_manager_dup_singleton ();
362 individual = empathy_individual_manager_lookup_member (manager, sel_data);
364 if (individual == NULL)
366 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
368 g_object_unref (manager);
373 /* FIXME: We should probably wait for the cb before calling
376 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
377 old_group, new_group, gdk_drag_context_get_selected_action (context));
379 g_object_unref (G_OBJECT (manager));
387 individual_view_file_drag_received (GtkWidget *view,
388 GdkDragContext *context,
391 GtkSelectionData *selection)
394 const gchar *sel_data;
395 FolksIndividual *individual;
396 EmpathyContact *contact;
398 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
400 gtk_tree_model_get_iter (model, &iter, path);
401 gtk_tree_model_get (model, &iter,
402 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
403 if (individual == NULL)
406 contact = empathy_contact_dup_from_folks_individual (individual);
407 empathy_send_file_from_uri_list (contact, sel_data);
409 g_object_unref (individual);
410 tp_clear_object (&contact);
416 individual_view_drag_data_received (GtkWidget *view,
417 GdkDragContext *context,
420 GtkSelectionData *selection,
426 GtkTreeViewDropPosition position;
428 gboolean success = TRUE;
430 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
432 /* Get destination group information. */
433 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
434 x, y, &path, &position);
439 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
440 || info == DND_DRAG_TYPE_STRING)
442 success = individual_view_contact_drag_received (view,
443 context, model, path, selection);
445 else if (info == DND_DRAG_TYPE_URI_LIST)
447 success = individual_view_file_drag_received (view,
448 context, model, path, selection);
451 gtk_tree_path_free (path);
452 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
456 individual_view_drag_motion_cb (DragMotionData *data)
458 if (data->view != NULL)
460 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
461 g_object_remove_weak_pointer (G_OBJECT (data->view),
462 (gpointer *) &data->view);
465 data->timeout_id = 0;
471 individual_view_drag_motion (GtkWidget *widget,
472 GdkDragContext *context,
477 EmpathyIndividualViewPriv *priv;
481 static DragMotionData *dm = NULL;
484 gboolean is_different = FALSE;
485 gboolean cleanup = TRUE;
486 gboolean retval = TRUE;
488 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
489 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
491 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
492 x, y, &path, NULL, NULL, NULL);
494 cleanup &= (dm == NULL);
498 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
499 is_different = ((dm == NULL) || ((dm != NULL)
500 && gtk_tree_path_compare (dm->path, path) != 0));
507 /* Coordinates don't point to an actual row, so make sure the pointer
508 and highlighting don't indicate that a drag is possible.
510 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
511 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
514 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
515 gtk_tree_model_get_iter (model, &iter, path);
517 if (target == GDK_NONE)
519 /* If target == GDK_NONE, then we don't have a target that can be
520 dropped on a contact. This means a contact drag. If we're
521 pointing to a group, highlight it. Otherwise, if the contact
522 we're pointing to is in a group, highlight that. Otherwise,
523 set the drag position to before the first row for a drag into
524 the "non-group" at the top.
526 GtkTreeIter group_iter;
528 GtkTreePath *group_path;
529 gtk_tree_model_get (model, &iter,
530 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
537 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
538 gtk_tree_model_get (model, &group_iter,
539 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
543 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
544 group_path = gtk_tree_model_get_path (model, &group_iter);
545 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
546 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
547 gtk_tree_path_free (group_path);
551 group_path = gtk_tree_path_new_first ();
552 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
553 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
554 group_path, GTK_TREE_VIEW_DROP_BEFORE);
559 /* This is a file drag, and it can only be dropped on contacts,
562 FolksIndividual *individual;
563 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
565 gtk_tree_model_get (model, &iter,
566 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
567 if (individual != NULL)
569 EmpathyContact *contact = NULL;
571 contact = empathy_contact_dup_from_folks_individual (individual);
572 caps = empathy_contact_get_capabilities (contact);
574 tp_clear_object (&contact);
577 if (individual != NULL &&
578 folks_individual_is_online (individual) &&
579 (caps & EMPATHY_CAPABILITIES_FT))
581 gdk_drag_status (context, GDK_ACTION_COPY, time_);
582 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
583 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
587 gdk_drag_status (context, 0, time_);
588 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
592 if (individual != NULL)
593 g_object_unref (individual);
596 if (!is_different && !cleanup)
601 gtk_tree_path_free (dm->path);
604 g_source_remove (dm->timeout_id);
612 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
614 dm = g_new0 (DragMotionData, 1);
616 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
617 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
618 dm->path = gtk_tree_path_copy (path);
620 dm->timeout_id = g_timeout_add_seconds (1,
621 (GSourceFunc) individual_view_drag_motion_cb, dm);
628 individual_view_drag_begin (GtkWidget *widget,
629 GdkDragContext *context)
631 EmpathyIndividualViewPriv *priv;
632 GtkTreeSelection *selection;
637 priv = GET_PRIV (widget);
639 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
642 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
643 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
646 path = gtk_tree_model_get_path (model, &iter);
647 priv->drag_row = gtk_tree_row_reference_new (model, path);
648 gtk_tree_path_free (path);
652 individual_view_drag_data_get (GtkWidget *widget,
653 GdkDragContext *context,
654 GtkSelectionData *selection,
658 EmpathyIndividualViewPriv *priv;
659 GtkTreePath *src_path;
662 FolksIndividual *individual;
663 const gchar *individual_id;
665 priv = GET_PRIV (widget);
667 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
668 if (priv->drag_row == NULL)
671 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
672 if (src_path == NULL)
675 if (!gtk_tree_model_get_iter (model, &iter, src_path))
677 gtk_tree_path_free (src_path);
681 gtk_tree_path_free (src_path);
684 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
685 if (individual == NULL)
688 individual_id = folks_individual_get_id (individual);
690 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
692 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
693 (guchar *) individual_id, strlen (individual_id) + 1);
696 g_object_unref (individual);
700 individual_view_drag_end (GtkWidget *widget,
701 GdkDragContext *context)
703 EmpathyIndividualViewPriv *priv;
705 priv = GET_PRIV (widget);
707 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
712 gtk_tree_row_reference_free (priv->drag_row);
713 priv->drag_row = NULL;
718 individual_view_drag_drop (GtkWidget *widget,
719 GdkDragContext *drag_context,
729 EmpathyIndividualView *view;
735 individual_view_popup_menu_idle_cb (gpointer user_data)
737 MenuPopupData *data = user_data;
740 menu = empathy_individual_view_get_individual_menu (data->view);
742 menu = empathy_individual_view_get_group_menu (data->view);
746 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
747 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
749 gtk_widget_show (menu);
750 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
752 g_object_ref_sink (menu);
753 g_object_unref (menu);
756 g_slice_free (MenuPopupData, data);
762 individual_view_button_press_event_cb (EmpathyIndividualView *view,
763 GdkEventButton *event,
766 if (event->button == 3)
770 data = g_slice_new (MenuPopupData);
772 data->button = event->button;
773 data->time = event->time;
774 g_idle_add (individual_view_popup_menu_idle_cb, data);
781 individual_view_key_press_event_cb (EmpathyIndividualView *view,
785 if (event->keyval == GDK_Menu)
789 data = g_slice_new (MenuPopupData);
792 data->time = event->time;
793 g_idle_add (individual_view_popup_menu_idle_cb, data);
800 individual_view_row_activated (GtkTreeView *view,
802 GtkTreeViewColumn *column)
804 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
805 FolksIndividual *individual;
806 EmpathyContact *contact = NULL;
810 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
813 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
814 gtk_tree_model_get_iter (model, &iter, path);
815 gtk_tree_model_get (model, &iter,
816 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
818 if (individual == NULL)
821 contact = empathy_contact_dup_from_folks_individual (individual);
824 DEBUG ("Starting a chat");
826 empathy_dispatcher_chat_with_contact (contact,
827 gtk_get_current_event_time ());
830 g_object_unref (individual);
831 tp_clear_object (&contact);
835 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
836 const gchar *path_string,
837 EmpathyIndividualView *view)
842 FolksIndividual *individual;
843 GdkEventButton *event;
847 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
848 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
851 gtk_tree_model_get (model, &iter,
852 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
853 if (individual == NULL)
856 event = (GdkEventButton *) gtk_get_current_event ();
858 menu = gtk_menu_new ();
859 shell = GTK_MENU_SHELL (menu);
862 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
863 gtk_menu_shell_append (shell, item);
864 gtk_widget_show (item);
867 item = empathy_individual_video_call_menu_item_new (individual, NULL);
868 gtk_menu_shell_append (shell, item);
869 gtk_widget_show (item);
871 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
872 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
873 gtk_widget_show (menu);
874 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
875 event->button, event->time);
876 g_object_ref_sink (menu);
877 g_object_unref (menu);
879 g_object_unref (individual);
883 individual_view_cell_set_background (EmpathyIndividualView *view,
884 GtkCellRenderer *cell,
891 style = gtk_widget_get_style (GTK_WIDGET (view));
893 if (!is_group && is_active)
895 color = style->bg[GTK_STATE_SELECTED];
897 /* Here we take the current theme colour and add it to
898 * the colour for white and average the two. This
899 * gives a colour which is inline with the theme but
902 color.red = (color.red + (style->white).red) / 2;
903 color.green = (color.green + (style->white).green) / 2;
904 color.blue = (color.blue + (style->white).blue) / 2;
906 g_object_set (cell, "cell-background-gdk", &color, NULL);
909 g_object_set (cell, "cell-background-gdk", NULL, NULL);
913 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
914 GtkCellRenderer *cell,
917 EmpathyIndividualView *view)
923 gtk_tree_model_get (model, iter,
924 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
925 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
926 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
929 "visible", !is_group,
933 tp_clear_object (&pixbuf);
935 individual_view_cell_set_background (view, cell, is_group, is_active);
939 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
940 GtkCellRenderer *cell,
943 EmpathyIndividualView *view)
945 GdkPixbuf *pixbuf = NULL;
949 gtk_tree_model_get (model, iter,
950 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
951 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
956 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
958 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
961 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
963 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
969 "visible", pixbuf != NULL,
973 tp_clear_object (&pixbuf);
979 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
980 GtkCellRenderer *cell,
983 EmpathyIndividualView *view)
987 gboolean can_audio, can_video;
989 gtk_tree_model_get (model, iter,
990 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
991 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
992 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
993 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
996 "visible", !is_group && (can_audio || can_video),
997 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1000 individual_view_cell_set_background (view, cell, is_group, is_active);
1004 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1005 GtkCellRenderer *cell,
1006 GtkTreeModel *model,
1008 EmpathyIndividualView *view)
1011 gboolean show_avatar;
1015 gtk_tree_model_get (model, iter,
1016 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1017 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1018 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1019 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1022 "visible", !is_group && show_avatar,
1026 tp_clear_object (&pixbuf);
1028 individual_view_cell_set_background (view, cell, is_group, is_active);
1032 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1033 GtkCellRenderer *cell,
1034 GtkTreeModel *model,
1036 EmpathyIndividualView *view)
1041 gtk_tree_model_get (model, iter,
1042 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1043 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1045 individual_view_cell_set_background (view, cell, is_group, is_active);
1049 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1050 GtkCellRenderer *cell,
1051 GtkTreeModel *model,
1053 EmpathyIndividualView *view)
1058 gtk_tree_model_get (model, iter,
1059 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1060 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1062 if (gtk_tree_model_iter_has_child (model, iter))
1065 gboolean row_expanded;
1067 path = gtk_tree_model_get_path (model, iter);
1069 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1070 (gtk_tree_view_column_get_tree_view (column)), path);
1071 gtk_tree_path_free (path);
1076 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1080 g_object_set (cell, "visible", FALSE, NULL);
1082 individual_view_cell_set_background (view, cell, is_group, is_active);
1086 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1091 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1092 GtkTreeModel *model;
1096 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1099 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1101 gtk_tree_model_get (model, iter,
1102 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1104 expanded = GPOINTER_TO_INT (user_data);
1105 empathy_contact_group_set_expanded (name, expanded);
1111 individual_view_start_search_cb (EmpathyIndividualView *view,
1114 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1116 if (priv->search_widget == NULL)
1119 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1120 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1122 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1128 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1130 EmpathyIndividualView *view)
1132 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1134 GtkTreeViewColumn *focus_column;
1135 GtkTreeModel *model;
1137 gboolean set_cursor = FALSE;
1139 gtk_tree_model_filter_refilter (priv->filter);
1141 /* Set cursor on the first contact. If it is already set on a group,
1142 * set it on its first child contact. Note that first child of a group
1143 * is its separator, that's why we actually set to the 2nd
1146 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1147 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1151 path = gtk_tree_path_new_from_string ("0:1");
1154 else if (gtk_tree_path_get_depth (path) < 2)
1158 gtk_tree_model_get_iter (model, &iter, path);
1159 gtk_tree_model_get (model, &iter,
1160 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1165 gtk_tree_path_down (path);
1166 gtk_tree_path_next (path);
1173 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1175 if (gtk_tree_model_get_iter (model, &iter, path))
1177 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1182 gtk_tree_path_free (path);
1186 individual_view_search_activate_cb (GtkWidget *search,
1187 EmpathyIndividualView *view)
1190 GtkTreeViewColumn *focus_column;
1192 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1195 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1196 gtk_tree_path_free (path);
1198 gtk_widget_hide (search);
1203 individual_view_search_key_navigation_cb (GtkWidget *search,
1205 EmpathyIndividualView *view)
1207 GdkEventKey *eventkey = ((GdkEventKey *) event);
1208 gboolean ret = FALSE;
1210 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1212 GdkEvent *new_event;
1214 new_event = gdk_event_copy (event);
1215 gtk_widget_grab_focus (GTK_WIDGET (view));
1216 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1217 gtk_widget_grab_focus (search);
1219 gdk_event_free (new_event);
1226 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1227 EmpathyIndividualView *view)
1229 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1230 GtkTreeModel *model;
1231 GtkTreePath *cursor_path;
1233 gboolean valid = FALSE;
1235 /* block expand or collapse handlers, they would write the
1236 * expand or collapsed setting to file otherwise */
1237 g_signal_handlers_block_by_func (view,
1238 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1239 g_signal_handlers_block_by_func (view,
1240 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1242 /* restore which groups are expanded and which are not */
1243 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1244 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1245 valid; valid = gtk_tree_model_iter_next (model, &iter))
1251 gtk_tree_model_get (model, &iter,
1252 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1253 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1262 path = gtk_tree_model_get_path (model, &iter);
1263 if ((priv->view_features &
1264 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1265 empathy_contact_group_get_expanded (name))
1267 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1271 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1274 gtk_tree_path_free (path);
1278 /* unblock expand or collapse handlers */
1279 g_signal_handlers_unblock_by_func (view,
1280 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1281 g_signal_handlers_unblock_by_func (view,
1282 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1284 /* keep the selected contact visible */
1285 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1287 if (cursor_path != NULL)
1288 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1291 gtk_tree_path_free (cursor_path);
1295 individual_view_search_show_cb (EmpathyLiveSearch *search,
1296 EmpathyIndividualView *view)
1298 /* block expand or collapse handlers during expand all, they would
1299 * write the expand or collapsed setting to file otherwise */
1300 g_signal_handlers_block_by_func (view,
1301 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1303 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1305 g_signal_handlers_unblock_by_func (view,
1306 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1310 expand_idle_foreach_cb (GtkTreeModel *model,
1313 EmpathyIndividualView *self)
1315 EmpathyIndividualViewPriv *priv;
1317 gpointer should_expand;
1320 /* We only want groups */
1321 if (gtk_tree_path_get_depth (path) > 1)
1324 gtk_tree_model_get (model, iter,
1325 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1326 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1329 if (is_group == FALSE)
1335 priv = GET_PRIV (self);
1337 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1338 &should_expand) == TRUE)
1340 if (GPOINTER_TO_INT (should_expand) == TRUE)
1341 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1343 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1345 g_hash_table_remove (priv->expand_groups, name);
1354 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1356 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1358 DEBUG ("individual_view_expand_idle_cb");
1360 g_signal_handlers_block_by_func (self,
1361 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1362 g_signal_handlers_block_by_func (self,
1363 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1365 /* The store/filter could've been removed while we were in the idle queue */
1366 if (priv->filter != NULL)
1368 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1369 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1372 g_signal_handlers_unblock_by_func (self,
1373 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1374 g_signal_handlers_unblock_by_func (self,
1375 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1377 g_object_unref (self);
1378 priv->expand_groups_idle_handler = 0;
1384 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1387 EmpathyIndividualView *view)
1389 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1390 gboolean should_expand, is_group = FALSE;
1392 gpointer will_expand;
1394 gtk_tree_model_get (model, iter,
1395 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1396 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1399 if (!is_group || EMP_STR_EMPTY (name))
1405 should_expand = (priv->view_features &
1406 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1407 (priv->search_widget != NULL &&
1408 gtk_widget_get_visible (priv->search_widget)) ||
1409 empathy_contact_group_get_expanded (name);
1411 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1412 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1413 * a hash table, and expand or contract them as appropriate all at once in
1414 * an idle handler which iterates over all the group rows. */
1415 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1416 &will_expand) == FALSE &&
1417 GPOINTER_TO_INT (will_expand) != should_expand)
1419 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1420 GINT_TO_POINTER (should_expand));
1422 if (priv->expand_groups_idle_handler == 0)
1424 priv->expand_groups_idle_handler =
1425 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1426 g_object_ref (view));
1433 /* FIXME: This is a workaround for bgo#621076 */
1435 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1438 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1439 GtkTreeModel *model;
1440 GtkTreePath *parent_path;
1441 GtkTreeIter parent_iter;
1443 if (gtk_tree_path_get_depth (path) < 2)
1446 /* A group row is visible if and only if at least one if its child is visible.
1447 * So when a row is inserted/deleted/changed in the base model, that could
1448 * modify the visibility of its parent in the filter model.
1451 model = GTK_TREE_MODEL (priv->store);
1452 parent_path = gtk_tree_path_copy (path);
1453 gtk_tree_path_up (parent_path);
1454 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1456 /* This tells the filter to verify the visibility of that row, and
1457 * show/hide it if necessary */
1458 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1459 parent_path, &parent_iter);
1461 gtk_tree_path_free (parent_path);
1465 individual_view_store_row_changed_cb (GtkTreeModel *model,
1468 EmpathyIndividualView *view)
1470 individual_view_verify_group_visibility (view, path);
1474 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1476 EmpathyIndividualView *view)
1478 individual_view_verify_group_visibility (view, path);
1482 individual_view_is_visible_individual (EmpathyIndividualView *self,
1483 FolksIndividual *individual)
1485 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1486 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1488 GList *personas, *l;
1490 /* We're only giving the visibility wrt filtering here, not things like
1492 if (live == NULL || gtk_widget_get_visible (GTK_WIDGET (live)) == FALSE)
1495 /* check alias name */
1496 str = folks_individual_get_alias (individual);
1498 if (empathy_live_search_match (live, str))
1501 /* check contact id, remove the @server.com part */
1502 personas = folks_individual_get_personas (individual);
1503 for (l = personas; l; l = l->next)
1506 gchar *dup_str = NULL;
1509 if (!TPF_IS_PERSONA (l->data))
1512 str = folks_persona_get_display_id (l->data);
1513 p = strstr (str, "@");
1515 str = dup_str = g_strndup (str, p - str);
1517 visible = empathy_live_search_match (live, str);
1523 /* FIXME: Add more rules here, we could check phone numbers in
1524 * contact's vCard for example. */
1530 individual_view_filter_visible_func (GtkTreeModel *model,
1534 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1535 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1536 FolksIndividual *individual = NULL;
1537 gboolean is_group, is_separator, valid;
1538 GtkTreeIter child_iter;
1539 gboolean visible, is_online;
1540 gboolean is_searching = TRUE;
1542 if (priv->search_widget == NULL ||
1543 !gtk_widget_get_visible (priv->search_widget))
1544 is_searching = FALSE;
1546 gtk_tree_model_get (model, iter,
1547 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1548 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1549 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1550 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1553 if (individual != NULL)
1555 if (is_searching == TRUE)
1556 visible = individual_view_is_visible_individual (self, individual);
1558 visible = (priv->show_offline || is_online);
1560 g_object_unref (individual);
1562 /* FIXME: Work around bgo#626552/bgo#621076 */
1563 if (visible == TRUE)
1565 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1566 individual_view_verify_group_visibility (self, path);
1567 gtk_tree_path_free (path);
1576 /* Not a contact, not a separator, must be a group */
1577 g_return_val_if_fail (is_group, FALSE);
1579 /* only show groups which are not empty */
1580 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1581 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1583 gtk_tree_model_get (model, &child_iter,
1584 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1585 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1588 if (individual == NULL)
1591 visible = individual_view_is_visible_individual (self, individual);
1592 g_object_unref (individual);
1594 /* show group if it has at least one visible contact in it */
1595 if ((is_searching && visible) ||
1596 (!is_searching && (priv->show_offline || is_online)))
1604 individual_view_constructed (GObject *object)
1606 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1607 GtkCellRenderer *cell;
1608 GtkTreeViewColumn *col;
1613 "headers-visible", FALSE,
1614 "show-expanders", FALSE,
1617 col = gtk_tree_view_column_new ();
1620 cell = gtk_cell_renderer_pixbuf_new ();
1621 gtk_tree_view_column_pack_start (col, cell, FALSE);
1622 gtk_tree_view_column_set_cell_data_func (col, cell,
1623 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1633 cell = gtk_cell_renderer_pixbuf_new ();
1634 gtk_tree_view_column_pack_start (col, cell, FALSE);
1635 gtk_tree_view_column_set_cell_data_func (col, cell,
1636 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1648 cell = empathy_cell_renderer_text_new ();
1649 gtk_tree_view_column_pack_start (col, cell, TRUE);
1650 gtk_tree_view_column_set_cell_data_func (col, cell,
1651 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1653 gtk_tree_view_column_add_attribute (col, cell,
1654 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1655 gtk_tree_view_column_add_attribute (col, cell,
1656 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1657 gtk_tree_view_column_add_attribute (col, cell,
1658 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1659 gtk_tree_view_column_add_attribute (col, cell,
1660 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1661 gtk_tree_view_column_add_attribute (col, cell,
1662 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1663 gtk_tree_view_column_add_attribute (col, cell,
1664 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1666 /* Audio Call Icon */
1667 cell = empathy_cell_renderer_activatable_new ();
1668 gtk_tree_view_column_pack_start (col, cell, FALSE);
1669 gtk_tree_view_column_set_cell_data_func (col, cell,
1670 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1673 g_object_set (cell, "visible", FALSE, NULL);
1675 g_signal_connect (cell, "path-activated",
1676 G_CALLBACK (individual_view_call_activated_cb), view);
1679 cell = gtk_cell_renderer_pixbuf_new ();
1680 gtk_tree_view_column_pack_start (col, cell, FALSE);
1681 gtk_tree_view_column_set_cell_data_func (col, cell,
1682 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1694 cell = empathy_cell_renderer_expander_new ();
1695 gtk_tree_view_column_pack_end (col, cell, FALSE);
1696 gtk_tree_view_column_set_cell_data_func (col, cell,
1697 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1700 /* Actually add the column now we have added all cell renderers */
1701 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1704 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1706 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1709 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1711 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1717 individual_view_set_view_features (EmpathyIndividualView *view,
1718 EmpathyIndividualFeatureFlags features)
1720 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1721 gboolean has_tooltip;
1723 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1725 priv->view_features = features;
1727 /* Setting reorderable is a hack that gets us row previews as drag icons
1728 for free. We override all the drag handlers. It's tricky to get the
1729 position of the drag icon right in drag_begin. GtkTreeView has special
1730 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1733 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1734 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG));
1736 /* Update DnD source/dest */
1737 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1739 gtk_drag_source_set (GTK_WIDGET (view),
1742 G_N_ELEMENTS (drag_types_source),
1743 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1747 gtk_drag_source_unset (GTK_WIDGET (view));
1751 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1753 gtk_drag_dest_set (GTK_WIDGET (view),
1754 GTK_DEST_DEFAULT_ALL,
1756 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1760 /* FIXME: URI could still be droped depending on FT feature */
1761 gtk_drag_dest_unset (GTK_WIDGET (view));
1764 /* Update has-tooltip */
1766 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1767 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1771 individual_view_dispose (GObject *object)
1773 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1774 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1776 tp_clear_object (&priv->store);
1777 tp_clear_object (&priv->filter);
1778 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1779 tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1781 empathy_individual_view_set_live_search (view, NULL);
1783 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1787 individual_view_finalize (GObject *object)
1789 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1791 g_hash_table_destroy (priv->expand_groups);
1793 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1797 individual_view_get_property (GObject *object,
1802 EmpathyIndividualViewPriv *priv;
1804 priv = GET_PRIV (object);
1809 g_value_set_object (value, priv->store);
1811 case PROP_VIEW_FEATURES:
1812 g_value_set_flags (value, priv->view_features);
1814 case PROP_INDIVIDUAL_FEATURES:
1815 g_value_set_flags (value, priv->individual_features);
1817 case PROP_SHOW_OFFLINE:
1818 g_value_set_boolean (value, priv->show_offline);
1821 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1827 individual_view_set_property (GObject *object,
1829 const GValue *value,
1832 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1833 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1838 empathy_individual_view_set_store (view, g_value_get_object (value));
1840 case PROP_VIEW_FEATURES:
1841 individual_view_set_view_features (view, g_value_get_flags (value));
1843 case PROP_INDIVIDUAL_FEATURES:
1844 priv->individual_features = g_value_get_flags (value);
1846 case PROP_SHOW_OFFLINE:
1847 empathy_individual_view_set_show_offline (view,
1848 g_value_get_boolean (value));
1851 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1857 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1859 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1860 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1861 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1863 object_class->constructed = individual_view_constructed;
1864 object_class->dispose = individual_view_dispose;
1865 object_class->finalize = individual_view_finalize;
1866 object_class->get_property = individual_view_get_property;
1867 object_class->set_property = individual_view_set_property;
1869 widget_class->drag_data_received = individual_view_drag_data_received;
1870 widget_class->drag_drop = individual_view_drag_drop;
1871 widget_class->drag_begin = individual_view_drag_begin;
1872 widget_class->drag_data_get = individual_view_drag_data_get;
1873 widget_class->drag_end = individual_view_drag_end;
1874 widget_class->drag_motion = individual_view_drag_motion;
1876 /* We use the class method to let user of this widget to connect to
1877 * the signal and stop emission of the signal so the default handler
1878 * won't be called. */
1879 tree_view_class->row_activated = individual_view_row_activated;
1881 signals[DRAG_CONTACT_RECEIVED] =
1882 g_signal_new ("drag-contact-received",
1883 G_OBJECT_CLASS_TYPE (klass),
1887 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1888 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1890 g_object_class_install_property (object_class,
1892 g_param_spec_object ("store",
1893 "The store of the view",
1894 "The store of the view",
1895 EMPATHY_TYPE_INDIVIDUAL_STORE,
1896 G_PARAM_READWRITE));
1897 g_object_class_install_property (object_class,
1899 g_param_spec_flags ("view-features",
1900 "Features of the view",
1901 "Flags for all enabled features",
1902 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1903 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1904 g_object_class_install_property (object_class,
1905 PROP_INDIVIDUAL_FEATURES,
1906 g_param_spec_flags ("individual-features",
1907 "Features of the contact menu",
1908 "Flags for all enabled features for the menu",
1909 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1910 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1911 g_object_class_install_property (object_class,
1913 g_param_spec_boolean ("show-offline",
1915 "Whether contact list should display "
1916 "offline contacts", FALSE, G_PARAM_READWRITE));
1918 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1922 empathy_individual_view_init (EmpathyIndividualView *view)
1924 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1925 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1928 /* Get saved group states. */
1929 empathy_contact_groups_get_all ();
1931 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
1932 (GDestroyNotify) g_free, NULL);
1934 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1935 empathy_individual_store_row_separator_func, NULL, NULL);
1937 /* Set up drag target lists. */
1938 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1939 G_N_ELEMENTS (drag_types_dest_file));
1941 /* Connect to tree view signals rather than override. */
1942 g_signal_connect (view, "button-press-event",
1943 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1944 g_signal_connect (view, "key-press-event",
1945 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1946 g_signal_connect (view, "row-expanded",
1947 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1948 GINT_TO_POINTER (TRUE));
1949 g_signal_connect (view, "row-collapsed",
1950 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1951 GINT_TO_POINTER (FALSE));
1952 g_signal_connect (view, "query-tooltip",
1953 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1956 EmpathyIndividualView *
1957 empathy_individual_view_new (EmpathyIndividualStore *store,
1958 EmpathyIndividualViewFeatureFlags view_features,
1959 EmpathyIndividualFeatureFlags individual_features)
1961 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1963 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1965 "individual-features", individual_features,
1966 "view-features", view_features, NULL);
1970 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1972 EmpathyIndividualViewPriv *priv;
1973 GtkTreeSelection *selection;
1975 GtkTreeModel *model;
1976 FolksIndividual *individual;
1978 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1980 priv = GET_PRIV (view);
1982 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1983 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1986 gtk_tree_model_get (model, &iter,
1987 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1992 EmpathyIndividualManagerFlags
1993 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1995 EmpathyIndividualViewPriv *priv;
1996 GtkTreeSelection *selection;
1998 GtkTreeModel *model;
1999 EmpathyIndividualFeatureFlags flags;
2001 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
2003 priv = GET_PRIV (view);
2005 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2006 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2009 gtk_tree_model_get (model, &iter,
2010 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2016 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2017 gboolean *is_fake_group)
2019 EmpathyIndividualViewPriv *priv;
2020 GtkTreeSelection *selection;
2022 GtkTreeModel *model;
2027 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2029 priv = GET_PRIV (view);
2031 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2032 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2035 gtk_tree_model_get (model, &iter,
2036 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2037 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2038 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2046 if (is_fake_group != NULL)
2047 *is_fake_group = fake;
2053 individual_view_remove_dialog_show (GtkWindow *parent,
2054 const gchar *message,
2055 const gchar *secondary_text)
2060 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2061 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2062 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2063 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2064 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2065 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2066 "%s", secondary_text);
2068 gtk_widget_show (dialog);
2070 res = gtk_dialog_run (GTK_DIALOG (dialog));
2071 gtk_widget_destroy (dialog);
2073 return (res == GTK_RESPONSE_YES);
2077 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2078 EmpathyIndividualView *view)
2082 group = empathy_individual_view_get_selected_group (view, NULL);
2089 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2091 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2092 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2095 EmpathyIndividualManager *manager =
2096 empathy_individual_manager_dup_singleton ();
2097 empathy_individual_manager_remove_group (manager, group);
2098 g_object_unref (G_OBJECT (manager));
2108 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2110 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2115 gboolean is_fake_group;
2117 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2119 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2120 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2123 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2124 if (!group || is_fake_group)
2126 /* We can't alter fake groups */
2130 menu = gtk_menu_new ();
2133 if (priv->view_features &
2134 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2135 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2136 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2137 gtk_widget_show (item);
2138 g_signal_connect (item, "activate",
2139 G_CALLBACK (individual_view_group_rename_activate_cb),
2144 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2146 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2147 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2148 GTK_ICON_SIZE_MENU);
2149 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2150 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2151 gtk_widget_show (item);
2152 g_signal_connect (item, "activate",
2153 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2162 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2163 EmpathyIndividualView *view)
2165 FolksIndividual *individual;
2167 individual = empathy_individual_view_dup_selected (view);
2169 if (individual != NULL)
2174 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2177 ("Do you really want to remove the contact '%s'?"),
2178 folks_individual_get_alias (individual));
2179 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2182 EmpathyIndividualManager *manager;
2184 manager = empathy_individual_manager_dup_singleton ();
2185 empathy_individual_manager_remove (manager, individual, "");
2186 g_object_unref (G_OBJECT (manager));
2190 g_object_unref (individual);
2195 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2197 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2198 FolksIndividual *individual;
2199 GtkWidget *menu = NULL;
2202 EmpathyIndividualManagerFlags flags;
2204 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2206 individual = empathy_individual_view_dup_selected (view);
2207 if (individual == NULL)
2210 flags = empathy_individual_view_get_flags (view);
2212 menu = empathy_individual_menu_new (individual, priv->individual_features);
2214 /* Remove contact */
2215 if (priv->view_features &
2216 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2217 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2220 /* create the menu if required, or just add a separator */
2222 menu = gtk_menu_new ();
2225 item = gtk_separator_menu_item_new ();
2226 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2227 gtk_widget_show (item);
2231 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2232 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2233 GTK_ICON_SIZE_MENU);
2234 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2235 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2236 gtk_widget_show (item);
2237 g_signal_connect (item, "activate",
2238 G_CALLBACK (individual_view_remove_activate_cb), view);
2241 g_object_unref (individual);
2247 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2248 EmpathyLiveSearch *search)
2250 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2252 /* remove old handlers if old search was not null */
2253 if (priv->search_widget != NULL)
2255 g_signal_handlers_disconnect_by_func (view,
2256 individual_view_start_search_cb, NULL);
2258 g_signal_handlers_disconnect_by_func (priv->search_widget,
2259 individual_view_search_text_notify_cb, view);
2260 g_signal_handlers_disconnect_by_func (priv->search_widget,
2261 individual_view_search_activate_cb, view);
2262 g_signal_handlers_disconnect_by_func (priv->search_widget,
2263 individual_view_search_key_navigation_cb, view);
2264 g_signal_handlers_disconnect_by_func (priv->search_widget,
2265 individual_view_search_hide_cb, view);
2266 g_signal_handlers_disconnect_by_func (priv->search_widget,
2267 individual_view_search_show_cb, view);
2268 g_object_unref (priv->search_widget);
2269 priv->search_widget = NULL;
2272 /* connect handlers if new search is not null */
2275 priv->search_widget = g_object_ref (search);
2277 g_signal_connect (view, "start-interactive-search",
2278 G_CALLBACK (individual_view_start_search_cb), NULL);
2280 g_signal_connect (priv->search_widget, "notify::text",
2281 G_CALLBACK (individual_view_search_text_notify_cb), view);
2282 g_signal_connect (priv->search_widget, "activate",
2283 G_CALLBACK (individual_view_search_activate_cb), view);
2284 g_signal_connect (priv->search_widget, "key-navigation",
2285 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2286 g_signal_connect (priv->search_widget, "hide",
2287 G_CALLBACK (individual_view_search_hide_cb), view);
2288 g_signal_connect (priv->search_widget, "show",
2289 G_CALLBACK (individual_view_search_show_cb), view);
2294 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2296 EmpathyIndividualViewPriv *priv;
2298 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2300 priv = GET_PRIV (self);
2302 return (priv->search_widget != NULL &&
2303 gtk_widget_get_visible (priv->search_widget));
2307 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2309 EmpathyIndividualViewPriv *priv;
2311 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2313 priv = GET_PRIV (self);
2315 return priv->show_offline;
2319 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2320 gboolean show_offline)
2322 EmpathyIndividualViewPriv *priv;
2324 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2326 priv = GET_PRIV (self);
2328 priv->show_offline = show_offline;
2330 g_object_notify (G_OBJECT (self), "show-offline");
2331 gtk_tree_model_filter_refilter (priv->filter);
2334 EmpathyIndividualStore *
2335 empathy_individual_view_get_store (EmpathyIndividualView *self)
2337 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2339 return GET_PRIV (self)->store;
2343 empathy_individual_view_set_store (EmpathyIndividualView *self,
2344 EmpathyIndividualStore *store)
2346 EmpathyIndividualViewPriv *priv;
2348 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2349 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2351 priv = GET_PRIV (self);
2353 /* Destroy the old filter and remove the old store */
2354 if (priv->store != NULL)
2356 g_signal_handlers_disconnect_by_func (priv->store,
2357 individual_view_store_row_changed_cb, self);
2358 g_signal_handlers_disconnect_by_func (priv->store,
2359 individual_view_store_row_deleted_cb, self);
2361 g_signal_handlers_disconnect_by_func (priv->filter,
2362 individual_view_row_has_child_toggled_cb, self);
2364 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2367 tp_clear_object (&priv->filter);
2368 tp_clear_object (&priv->store);
2370 /* Set the new store */
2371 priv->store = store;
2375 g_object_ref (store);
2377 /* Create a new filter */
2378 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2379 GTK_TREE_MODEL (priv->store), NULL));
2380 gtk_tree_model_filter_set_visible_func (priv->filter,
2381 individual_view_filter_visible_func, self, NULL);
2383 g_signal_connect (priv->filter, "row-has-child-toggled",
2384 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2385 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2386 GTK_TREE_MODEL (priv->filter));
2388 tp_g_signal_connect_object (priv->store, "row-changed",
2389 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2390 tp_g_signal_connect_object (priv->store, "row-inserted",
2391 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2392 tp_g_signal_connect_object (priv->store, "row-deleted",
2393 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);