1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <folks/folks.h>
36 #include <telepathy-glib/account-manager.h>
37 #include <telepathy-glib/util.h>
39 #include <libempathy/empathy-call-factory.h>
40 #include <libempathy/empathy-individual-manager.h>
41 #include <libempathy/empathy-contact-groups.h>
42 #include <libempathy/empathy-dispatcher.h>
43 #include <libempathy/empathy-utils.h>
45 #include "empathy-individual-view.h"
46 #include "empathy-individual-menu.h"
47 #include "empathy-individual-store.h"
48 #include "empathy-images.h"
49 #include "empathy-cell-renderer-expander.h"
50 #include "empathy-cell-renderer-text.h"
51 #include "empathy-cell-renderer-activatable.h"
52 #include "empathy-ui-utils.h"
53 #include "empathy-gtk-enum-types.h"
54 #include "empathy-gtk-marshal.h"
56 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
57 #include <libempathy/empathy-debug.h>
59 /* Active users are those which have recently changed state
60 * (e.g. online, offline or from normal to a busy state).
63 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
66 EmpathyIndividualStore *store;
67 GtkTreeRowReference *drag_row;
68 EmpathyIndividualViewFeatureFlags view_features;
69 EmpathyContactFeatureFlags individual_features;
70 GtkWidget *tooltip_widget;
71 GtkTargetList *file_targets;
73 GtkTreeModelFilter *filter;
74 GtkWidget *search_widget;
75 } EmpathyIndividualViewPriv;
79 EmpathyIndividualView *view;
86 EmpathyIndividualView *view;
87 FolksIndividual *individual;
96 PROP_INDIVIDUAL_FEATURES,
99 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
100 * specific EmpathyContacts (between/in/out of Individuals) */
103 DND_DRAG_TYPE_INDIVIDUAL_ID,
104 DND_DRAG_TYPE_URI_LIST,
105 DND_DRAG_TYPE_STRING,
108 static const GtkTargetEntry drag_types_dest[] = {
109 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
110 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
111 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
112 {"text/plain", 0, DND_DRAG_TYPE_STRING},
113 {"STRING", 0, DND_DRAG_TYPE_STRING},
116 static const GtkTargetEntry drag_types_dest_file[] = {
117 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
118 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
121 static const GtkTargetEntry drag_types_source[] = {
122 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
125 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
126 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
130 DRAG_CONTACT_RECEIVED,
134 static guint signals[LAST_SIGNAL];
136 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
140 individual_view_is_visible_individual (EmpathyIndividualView *self,
141 FolksIndividual *individual)
143 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
144 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
147 gchar *dup_str = NULL;
151 g_assert (live != NULL);
153 /* check alias name */
154 str = folks_individual_get_alias (individual);
155 if (empathy_live_search_match (live, str))
158 /* check contact id, remove the @server.com part */
159 personas = folks_individual_get_personas (individual);
160 for (l = personas; l; l = l->next)
162 str = folks_persona_get_uid (l->data);
163 p = strstr (str, "@");
165 str = dup_str = g_strndup (str, p - str);
167 visible = empathy_live_search_match (live, str);
173 /* FIXME: Add more rules here, we could check phone numbers in
174 * contact's vCard for example. */
180 individual_view_filter_visible_func (GtkTreeModel *model,
184 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
185 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
186 FolksIndividual *individual = NULL;
187 gboolean is_group, is_separator, valid;
188 GtkTreeIter child_iter;
191 if (priv->search_widget == NULL ||
192 !gtk_widget_get_visible (priv->search_widget))
195 gtk_tree_model_get (model, iter,
196 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
197 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
198 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
201 if (individual != NULL)
203 visible = individual_view_is_visible_individual (self, individual);
204 g_object_unref (individual);
211 /* Not a contact, not a separator, must be a group */
212 g_return_val_if_fail (is_group, FALSE);
214 /* only show groups which are not empty */
215 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
216 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
218 gtk_tree_model_get (model, &child_iter,
219 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
222 if (individual == NULL)
225 visible = individual_view_is_visible_individual (self, individual);
226 g_object_unref (individual);
228 /* show group if it has at least one visible contact in it */
237 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
240 gboolean keyboard_mode,
244 FolksIndividual *individual;
248 static gint running = 0;
249 gboolean ret = FALSE;
251 /* Avoid an infinite loop. See GNOME bug #574377 */
258 /* Don't show the tooltip if there's already a popup menu */
259 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
264 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
265 keyboard_mode, &model, &path, &iter))
270 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
271 gtk_tree_path_free (path);
273 gtk_tree_model_get (model, &iter,
274 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
275 if (individual == NULL)
287 individual_view_handle_drag (EmpathyIndividualView *self,
288 FolksIndividual *individual,
289 const gchar *old_group,
290 const gchar *new_group,
291 GdkDragAction action)
293 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
294 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
296 DEBUG ("individual %s dragged from '%s' to '%s'",
297 folks_individual_get_id (individual), old_group, new_group);
299 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
301 /* Mark contact as favourite */
303 /* TODO: implement this */
304 DEBUG ("adding individual to favourites not fully implemented");
309 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
311 /* Remove contact as favourite */
313 /* TODO: implement this */
314 DEBUG ("removing individual from favourites not fully " "implemented");
316 /* Don't try to remove it */
320 if (new_group != NULL)
321 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE);
323 if (old_group != NULL && action == GDK_ACTION_MOVE)
324 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE);
328 group_can_be_modified (const gchar *name,
329 gboolean is_fake_group,
332 /* Real groups can always be modified */
336 /* The favorite fake group can be modified so users can
337 * add/remove favorites using DnD */
338 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
341 /* We can remove contacts from the 'ungrouped' fake group */
342 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
349 individual_view_contact_drag_received (GtkWidget *self,
350 GdkDragContext *context,
353 GtkSelectionData *selection)
355 EmpathyIndividualViewPriv *priv;
356 EmpathyIndividualManager *manager;
357 FolksIndividual *individual;
358 GtkTreePath *source_path;
359 const gchar *sel_data;
360 gchar *new_group = NULL;
361 gchar *old_group = NULL;
362 gboolean new_group_is_fake, old_group_is_fake = TRUE;
364 priv = GET_PRIV (self);
366 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
367 new_group = empathy_individual_store_get_parent_group (model, path,
368 NULL, &new_group_is_fake);
370 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
373 /* Get source group information. */
376 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
380 empathy_individual_store_get_parent_group (model, source_path,
381 NULL, &old_group_is_fake);
382 gtk_tree_path_free (source_path);
386 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
389 if (!tp_strdiff (old_group, new_group))
396 /* XXX: for contacts, we used to ensure the account, create the contact
397 * factory, and then wait on the contacts. But they should already be
398 * created by this point */
400 manager = empathy_individual_manager_dup_singleton ();
401 individual = empathy_individual_manager_lookup_member (manager, sel_data);
403 if (individual == NULL)
405 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
407 g_object_unref (manager);
412 /* FIXME: We should probably wait for the cb before calling
415 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
416 old_group, new_group, gdk_drag_context_get_selected_action (context));
418 g_object_unref (G_OBJECT (manager));
426 individual_view_file_drag_received (GtkWidget *view,
427 GdkDragContext *context,
430 GtkSelectionData *selection)
433 const gchar *sel_data;
434 FolksIndividual *individual;
436 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
438 gtk_tree_model_get_iter (model, &iter, path);
439 gtk_tree_model_get (model, &iter,
440 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
446 /* TODO: implement this */
447 DEBUG ("file transfer not implemented");
449 g_object_unref (individual);
455 individual_view_drag_data_received (GtkWidget *view,
456 GdkDragContext *context,
459 GtkSelectionData *selection,
465 GtkTreeViewDropPosition position;
467 gboolean success = TRUE;
469 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
471 /* Get destination group information. */
472 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
473 x, y, &path, &position);
478 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
479 || info == DND_DRAG_TYPE_STRING)
481 success = individual_view_contact_drag_received (view,
482 context, model, path, selection);
484 else if (info == DND_DRAG_TYPE_URI_LIST)
486 success = individual_view_file_drag_received (view,
487 context, model, path, selection);
490 gtk_tree_path_free (path);
491 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
495 individual_view_drag_motion_cb (DragMotionData *data)
497 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
499 data->timeout_id = 0;
505 individual_view_drag_motion (GtkWidget *widget,
506 GdkDragContext *context,
511 EmpathyIndividualViewPriv *priv;
515 static DragMotionData *dm = NULL;
518 gboolean is_different = FALSE;
519 gboolean cleanup = TRUE;
520 gboolean retval = TRUE;
522 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
523 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
525 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
526 x, y, &path, NULL, NULL, NULL);
532 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
533 is_different = (!dm || (dm
534 && gtk_tree_path_compare (dm->path, path) != 0));
543 /* Coordinates don't point to an actual row, so make sure the pointer
544 and highlighting don't indicate that a drag is possible.
546 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
547 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
550 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
551 gtk_tree_model_get_iter (model, &iter, path);
553 if (target == GDK_NONE)
555 /* If target == GDK_NONE, then we don't have a target that can be
556 dropped on a contact. This means a contact drag. If we're
557 pointing to a group, highlight it. Otherwise, if the contact
558 we're pointing to is in a group, highlight that. Otherwise,
559 set the drag position to before the first row for a drag into
560 the "non-group" at the top.
562 GtkTreeIter group_iter;
564 GtkTreePath *group_path;
565 gtk_tree_model_get (model, &iter,
566 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
573 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
574 gtk_tree_model_get (model, &group_iter,
575 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
579 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
580 group_path = gtk_tree_model_get_path (model, &group_iter);
581 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
582 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
583 gtk_tree_path_free (group_path);
587 group_path = gtk_tree_path_new_first ();
588 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
589 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
590 group_path, GTK_TREE_VIEW_DROP_BEFORE);
595 /* This is a file drag, and it can only be dropped on contacts,
598 FolksIndividual *individual;
599 gtk_tree_model_get (model, &iter,
600 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
601 if (individual != NULL &&
602 folks_individual_is_online (individual) &&
603 (folks_individual_get_capabilities (individual) &
604 FOLKS_CAPABILITIES_FLAGS_FILE_TRANSFER))
606 gdk_drag_status (context, GDK_ACTION_COPY, time_);
607 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
608 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
609 g_object_unref (individual);
613 gdk_drag_status (context, 0, time_);
614 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
619 if (!is_different && !cleanup)
626 gtk_tree_path_free (dm->path);
629 g_source_remove (dm->timeout_id);
637 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
639 dm = g_new0 (DragMotionData, 1);
641 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
642 dm->path = gtk_tree_path_copy (path);
644 dm->timeout_id = g_timeout_add_seconds (1,
645 (GSourceFunc) individual_view_drag_motion_cb, dm);
652 individual_view_drag_begin (GtkWidget *widget,
653 GdkDragContext *context)
655 EmpathyIndividualViewPriv *priv;
656 GtkTreeSelection *selection;
661 priv = GET_PRIV (widget);
663 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
666 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
667 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
672 path = gtk_tree_model_get_path (model, &iter);
673 priv->drag_row = gtk_tree_row_reference_new (model, path);
674 gtk_tree_path_free (path);
678 individual_view_drag_data_get (GtkWidget *widget,
679 GdkDragContext *context,
680 GtkSelectionData *selection,
684 EmpathyIndividualViewPriv *priv;
685 GtkTreePath *src_path;
688 FolksIndividual *individual;
689 const gchar *individual_id;
691 priv = GET_PRIV (widget);
693 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
699 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
705 if (!gtk_tree_model_get_iter (model, &iter, src_path))
707 gtk_tree_path_free (src_path);
711 gtk_tree_path_free (src_path);
714 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
720 individual_id = folks_individual_get_id (individual);
724 case DND_DRAG_TYPE_INDIVIDUAL_ID:
725 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
726 (guchar *) individual_id, strlen (individual_id) + 1);
732 individual_view_drag_end (GtkWidget *widget,
733 GdkDragContext *context)
735 EmpathyIndividualViewPriv *priv;
737 priv = GET_PRIV (widget);
739 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
744 gtk_tree_row_reference_free (priv->drag_row);
745 priv->drag_row = NULL;
750 individual_view_drag_drop (GtkWidget *widget,
751 GdkDragContext *drag_context,
761 EmpathyIndividualView *view;
767 individual_view_popup_menu_idle_cb (gpointer user_data)
769 MenuPopupData *data = user_data;
772 menu = empathy_individual_view_get_individual_menu (data->view);
775 menu = empathy_individual_view_get_group_menu (data->view);
780 g_signal_connect (menu, "deactivate",
781 G_CALLBACK (gtk_menu_detach), NULL);
782 gtk_menu_attach_to_widget (GTK_MENU (menu),
783 GTK_WIDGET (data->view), NULL);
784 gtk_widget_show (menu);
785 gtk_menu_popup (GTK_MENU (menu),
786 NULL, NULL, NULL, NULL, data->button, data->time);
787 g_object_ref_sink (menu);
788 g_object_unref (menu);
791 g_slice_free (MenuPopupData, data);
797 individual_view_button_press_event_cb (EmpathyIndividualView *view,
798 GdkEventButton *event,
801 if (event->button == 3)
805 data = g_slice_new (MenuPopupData);
807 data->button = event->button;
808 data->time = event->time;
809 g_idle_add (individual_view_popup_menu_idle_cb, data);
816 individual_view_key_press_event_cb (EmpathyIndividualView *view,
820 if (event->keyval == GDK_Menu)
824 data = g_slice_new (MenuPopupData);
827 data->time = event->time;
828 g_idle_add (individual_view_popup_menu_idle_cb, data);
835 individual_view_row_activated (GtkTreeView *view,
837 GtkTreeViewColumn *column)
839 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
840 FolksIndividual *individual;
841 EmpathyContact *contact = NULL;
845 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
850 model = GTK_TREE_MODEL (priv->store);
851 gtk_tree_model_get_iter (model, &iter, path);
852 gtk_tree_model_get (model, &iter,
853 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
858 contact = empathy_contact_from_folks_individual (individual);
862 DEBUG ("Starting a chat");
864 empathy_dispatcher_chat_with_contact (contact,
865 gtk_get_current_event_time (), NULL, NULL);
868 g_object_unref (individual);
872 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
873 const gchar *path_string,
874 EmpathyIndividualView *view)
879 FolksIndividual *individual;
880 GdkEventButton *event;
883 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
884 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
887 gtk_tree_model_get (model, &iter,
888 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
889 if (individual == NULL)
892 event = (GdkEventButton *) gtk_get_current_event ();
894 menu = gtk_menu_new ();
895 shell = GTK_MENU_SHELL (menu);
898 /* TODO: implement */
899 DEBUG ("audio call menu item unimplemented");
902 /* TODO: implement */
903 DEBUG ("video call menu item unimplemented");
905 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
906 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
907 gtk_widget_show (menu);
908 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
909 event->button, event->time);
910 g_object_ref_sink (menu);
911 g_object_unref (menu);
913 g_object_unref (individual);
917 individual_view_cell_set_background (EmpathyIndividualView *view,
918 GtkCellRenderer *cell,
925 style = gtk_widget_get_style (GTK_WIDGET (view));
927 if (!is_group && is_active)
929 color = style->bg[GTK_STATE_SELECTED];
931 /* Here we take the current theme colour and add it to
932 * the colour for white and average the two. This
933 * gives a colour which is inline with the theme but
936 color.red = (color.red + (style->white).red) / 2;
937 color.green = (color.green + (style->white).green) / 2;
938 color.blue = (color.blue + (style->white).blue) / 2;
940 g_object_set (cell, "cell-background-gdk", &color, NULL);
944 g_object_set (cell, "cell-background-gdk", NULL, NULL);
949 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
950 GtkCellRenderer *cell,
953 EmpathyIndividualView *view)
959 gtk_tree_model_get (model, iter,
960 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
961 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
962 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
965 "visible", !is_group,
971 g_object_unref (pixbuf);
974 individual_view_cell_set_background (view, cell, is_group, is_active);
978 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
979 GtkCellRenderer *cell,
982 EmpathyIndividualView *view)
984 GdkPixbuf *pixbuf = NULL;
988 gtk_tree_model_get (model, iter,
989 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
990 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
995 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
997 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1000 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1002 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1003 GTK_ICON_SIZE_MENU);
1008 "visible", pixbuf != NULL,
1013 g_object_unref (pixbuf);
1019 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1020 GtkCellRenderer *cell,
1021 GtkTreeModel *model,
1023 EmpathyIndividualView *view)
1027 gboolean can_audio, can_video;
1029 gtk_tree_model_get (model, iter,
1030 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1031 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1032 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1033 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1036 "visible", !is_group && (can_audio || can_video),
1037 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1040 individual_view_cell_set_background (view, cell, is_group, is_active);
1044 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1045 GtkCellRenderer *cell,
1046 GtkTreeModel *model,
1048 EmpathyIndividualView *view)
1051 gboolean show_avatar;
1055 gtk_tree_model_get (model, iter,
1056 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1057 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1058 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1059 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1062 "visible", !is_group && show_avatar,
1068 g_object_unref (pixbuf);
1071 individual_view_cell_set_background (view, cell, is_group, is_active);
1075 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1076 GtkCellRenderer *cell,
1077 GtkTreeModel *model,
1079 EmpathyIndividualView *view)
1084 gtk_tree_model_get (model, iter,
1085 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1086 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1088 individual_view_cell_set_background (view, cell, is_group, is_active);
1092 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1093 GtkCellRenderer *cell,
1094 GtkTreeModel *model,
1096 EmpathyIndividualView *view)
1101 gtk_tree_model_get (model, iter,
1102 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1103 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1105 if (gtk_tree_model_iter_has_child (model, iter))
1108 gboolean row_expanded;
1110 path = gtk_tree_model_get_path (model, iter);
1112 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1113 (gtk_tree_view_column_get_tree_view (column)), path);
1114 gtk_tree_path_free (path);
1119 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1124 g_object_set (cell, "visible", FALSE, NULL);
1127 individual_view_cell_set_background (view, cell, is_group, is_active);
1131 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1136 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1137 GtkTreeModel *model;
1141 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1144 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1146 gtk_tree_model_get (model, iter,
1147 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1149 expanded = GPOINTER_TO_INT (user_data);
1150 empathy_contact_group_set_expanded (name, expanded);
1156 individual_view_start_search_cb (EmpathyIndividualView *view,
1159 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1161 if (priv->search_widget == NULL)
1164 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1165 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1167 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1173 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1175 EmpathyIndividualView *view)
1177 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1179 GtkTreeViewColumn *focus_column;
1180 GtkTreeModel *model;
1182 gboolean set_cursor = FALSE;
1184 gtk_tree_model_filter_refilter (priv->filter);
1186 /* Set cursor on the first contact. If it is already set on a group,
1187 * set it on its first child contact. Note that first child of a group
1188 * is its separator, that's why we actually set to the 2nd
1191 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1192 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1196 path = gtk_tree_path_new_from_string ("0:1");
1199 else if (gtk_tree_path_get_depth (path) < 2)
1203 gtk_tree_model_get_iter (model, &iter, path);
1204 gtk_tree_model_get (model, &iter,
1205 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1210 gtk_tree_path_down (path);
1211 gtk_tree_path_next (path);
1218 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1220 if (gtk_tree_model_get_iter (model, &iter, path))
1222 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1227 gtk_tree_path_free (path);
1231 individual_view_search_activate_cb (GtkWidget *search,
1232 EmpathyIndividualView *view)
1235 GtkTreeViewColumn *focus_column;
1237 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1240 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1241 gtk_tree_path_free (path);
1243 gtk_widget_hide (search);
1248 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1249 EmpathyIndividualView *view)
1251 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1252 GtkTreeModel *model;
1254 gboolean valid = FALSE;
1256 /* block expand or collapse handlers, they would write the
1257 * expand or collapsed setting to file otherwise */
1258 g_signal_handlers_block_by_func (view,
1259 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1260 g_signal_handlers_block_by_func (view,
1261 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1263 /* restore which groups are expanded and which are not */
1264 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1265 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1266 valid; valid = gtk_tree_model_iter_next (model, &iter))
1272 gtk_tree_model_get (model, &iter,
1273 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1274 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1283 path = gtk_tree_model_get_path (model, &iter);
1284 if ((priv->view_features &
1285 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1286 empathy_contact_group_get_expanded (name))
1288 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1292 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1295 gtk_tree_path_free (path);
1299 /* unblock expand or collapse handlers */
1300 g_signal_handlers_unblock_by_func (view,
1301 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1302 g_signal_handlers_unblock_by_func (view,
1303 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1307 individual_view_search_show_cb (EmpathyLiveSearch *search,
1308 EmpathyIndividualView *view)
1310 /* block expand or collapse handlers during expand all, they would
1311 * write the expand or collapsed setting to file otherwise */
1312 g_signal_handlers_block_by_func (view,
1313 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1315 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1317 g_signal_handlers_unblock_by_func (view,
1318 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1322 EmpathyIndividualView *view;
1323 GtkTreeRowReference *row_ref;
1328 individual_view_expand_idle_cb (gpointer user_data)
1330 ExpandData *data = user_data;
1333 path = gtk_tree_row_reference_get_path (data->row_ref);
1337 g_signal_handlers_block_by_func (data->view,
1338 individual_view_row_expand_or_collapse_cb,
1339 GINT_TO_POINTER (data->expand));
1342 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1344 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1346 gtk_tree_path_free (path);
1348 g_signal_handlers_unblock_by_func (data->view,
1349 individual_view_row_expand_or_collapse_cb,
1350 GINT_TO_POINTER (data->expand));
1353 g_object_unref (data->view);
1354 gtk_tree_row_reference_free (data->row_ref);
1355 g_slice_free (ExpandData, data);
1361 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1364 EmpathyIndividualView *view)
1366 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1367 gboolean is_group = FALSE;
1371 gtk_tree_model_get (model, iter,
1372 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1373 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1376 if (!is_group || EMP_STR_EMPTY (name))
1382 data = g_slice_new0 (ExpandData);
1383 data->view = g_object_ref (view);
1384 data->row_ref = gtk_tree_row_reference_new (model, path);
1386 (priv->view_features &
1387 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1388 (priv->search_widget != NULL &&
1389 gtk_widget_get_visible (priv->search_widget)) ||
1390 empathy_contact_group_get_expanded (name);
1392 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1393 * gtk_tree_model_filter_refilter () */
1394 g_idle_add (individual_view_expand_idle_cb, data);
1400 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1403 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1404 GtkTreeModel *model;
1405 GtkTreePath *parent_path;
1406 GtkTreeIter parent_iter;
1408 if (gtk_tree_path_get_depth (path) < 2)
1411 /* A group row is visible if and only if at least one if its child is visible.
1412 * So when a row is inserted/deleted/changed in the base model, that could
1413 * modify the visibility of its parent in the filter model.
1416 model = GTK_TREE_MODEL (priv->store);
1417 parent_path = gtk_tree_path_copy (path);
1418 gtk_tree_path_up (parent_path);
1419 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1421 /* This tells the filter to verify the visibility of that row, and
1422 * show/hide it if necessary */
1423 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1424 parent_path, &parent_iter);
1426 gtk_tree_path_free (parent_path);
1430 individual_view_store_row_changed_cb (GtkTreeModel *model,
1433 EmpathyIndividualView *view)
1435 individual_view_verify_group_visibility (view, path);
1439 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1441 EmpathyIndividualView *view)
1443 individual_view_verify_group_visibility (view, path);
1447 individual_view_constructed (GObject *object)
1449 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1450 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1452 GtkCellRenderer *cell;
1453 GtkTreeViewColumn *col;
1456 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1457 GTK_TREE_MODEL (priv->store), NULL));
1458 gtk_tree_model_filter_set_visible_func (priv->filter,
1459 individual_view_filter_visible_func, view, NULL);
1461 g_signal_connect (priv->store, "row-has-child-toggled",
1462 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1463 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1464 GTK_TREE_MODEL (priv->filter));
1466 tp_g_signal_connect_object (priv->store, "row-changed",
1467 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1468 tp_g_signal_connect_object (priv->store, "row-inserted",
1469 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1470 tp_g_signal_connect_object (priv->store, "row-deleted",
1471 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1474 /* Setting reorderable is a hack that gets us row previews as drag icons
1475 for free. We override all the drag handlers. It's tricky to get the
1476 position of the drag icon right in drag_begin. GtkTreeView has special
1477 voodoo for it, so we let it do the voodoo that he do.
1480 "headers-visible", FALSE,
1481 "reorderable", TRUE,
1482 "show-expanders", FALSE,
1485 col = gtk_tree_view_column_new ();
1488 cell = gtk_cell_renderer_pixbuf_new ();
1489 gtk_tree_view_column_pack_start (col, cell, FALSE);
1490 gtk_tree_view_column_set_cell_data_func (col, cell,
1491 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1501 cell = gtk_cell_renderer_pixbuf_new ();
1502 gtk_tree_view_column_pack_start (col, cell, FALSE);
1503 gtk_tree_view_column_set_cell_data_func (col, cell,
1504 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1516 cell = empathy_cell_renderer_text_new ();
1517 gtk_tree_view_column_pack_start (col, cell, TRUE);
1518 gtk_tree_view_column_set_cell_data_func (col, cell,
1519 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1521 gtk_tree_view_column_add_attribute (col, cell,
1522 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1523 gtk_tree_view_column_add_attribute (col, cell,
1524 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1525 gtk_tree_view_column_add_attribute (col, cell,
1526 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1527 gtk_tree_view_column_add_attribute (col, cell,
1528 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1529 gtk_tree_view_column_add_attribute (col, cell,
1530 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1531 gtk_tree_view_column_add_attribute (col, cell,
1532 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1534 /* Audio Call Icon */
1535 cell = empathy_cell_renderer_activatable_new ();
1536 gtk_tree_view_column_pack_start (col, cell, FALSE);
1537 gtk_tree_view_column_set_cell_data_func (col, cell,
1538 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1541 g_object_set (cell, "visible", FALSE, NULL);
1543 g_signal_connect (cell, "path-activated",
1544 G_CALLBACK (individual_view_call_activated_cb), view);
1547 cell = gtk_cell_renderer_pixbuf_new ();
1548 gtk_tree_view_column_pack_start (col, cell, FALSE);
1549 gtk_tree_view_column_set_cell_data_func (col, cell,
1550 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1562 cell = empathy_cell_renderer_expander_new ();
1563 gtk_tree_view_column_pack_end (col, cell, FALSE);
1564 gtk_tree_view_column_set_cell_data_func (col, cell,
1565 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1568 /* Actually add the column now we have added all cell renderers */
1569 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1572 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1574 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1577 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1579 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1585 individual_view_set_view_features (EmpathyIndividualView *view,
1586 EmpathyIndividualFeatureFlags features)
1588 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1589 gboolean has_tooltip;
1591 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1593 priv->view_features = features;
1595 /* Update DnD source/dest */
1596 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1598 gtk_drag_source_set (GTK_WIDGET (view),
1601 G_N_ELEMENTS (drag_types_source),
1602 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1606 gtk_drag_source_unset (GTK_WIDGET (view));
1610 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1612 gtk_drag_dest_set (GTK_WIDGET (view),
1613 GTK_DEST_DEFAULT_ALL,
1615 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1619 /* FIXME: URI could still be droped depending on FT feature */
1620 gtk_drag_dest_unset (GTK_WIDGET (view));
1623 /* Update has-tooltip */
1625 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1626 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1630 individual_view_dispose (GObject *object)
1632 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1633 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1635 if (priv->store != NULL)
1637 g_object_unref (priv->store);
1640 if (priv->filter != NULL)
1642 g_object_unref (priv->filter);
1643 priv->filter = NULL;
1645 if (priv->tooltip_widget != NULL)
1647 gtk_widget_destroy (priv->tooltip_widget);
1648 priv->tooltip_widget = NULL;
1650 if (priv->file_targets != NULL)
1652 gtk_target_list_unref (priv->file_targets);
1653 priv->file_targets = NULL;
1656 empathy_individual_view_set_live_search (view, NULL);
1658 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1662 individual_view_get_property (GObject *object,
1667 EmpathyIndividualViewPriv *priv;
1669 priv = GET_PRIV (object);
1674 g_value_set_object (value, priv->store);
1676 case PROP_VIEW_FEATURES:
1677 g_value_set_flags (value, priv->view_features);
1679 case PROP_INDIVIDUAL_FEATURES:
1680 g_value_set_flags (value, priv->individual_features);
1683 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1689 individual_view_set_property (GObject *object,
1691 const GValue *value,
1694 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1695 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1700 priv->store = g_value_dup_object (value);
1702 case PROP_VIEW_FEATURES:
1703 individual_view_set_view_features (view, g_value_get_flags (value));
1705 case PROP_INDIVIDUAL_FEATURES:
1706 priv->individual_features = g_value_get_flags (value);
1709 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1715 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1717 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1718 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1719 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1721 object_class->constructed = individual_view_constructed;
1722 object_class->dispose = individual_view_dispose;
1723 object_class->get_property = individual_view_get_property;
1724 object_class->set_property = individual_view_set_property;
1726 widget_class->drag_data_received = individual_view_drag_data_received;
1727 widget_class->drag_drop = individual_view_drag_drop;
1728 widget_class->drag_begin = individual_view_drag_begin;
1729 widget_class->drag_data_get = individual_view_drag_data_get;
1730 widget_class->drag_end = individual_view_drag_end;
1731 widget_class->drag_motion = individual_view_drag_motion;
1733 /* We use the class method to let user of this widget to connect to
1734 * the signal and stop emission of the signal so the default handler
1735 * won't be called. */
1736 tree_view_class->row_activated = individual_view_row_activated;
1738 signals[DRAG_CONTACT_RECEIVED] =
1739 g_signal_new ("drag-contact-received",
1740 G_OBJECT_CLASS_TYPE (klass),
1744 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1745 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1747 g_object_class_install_property (object_class,
1749 g_param_spec_object ("store",
1750 "The store of the view",
1751 "The store of the view",
1752 EMPATHY_TYPE_INDIVIDUAL_STORE,
1753 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1754 g_object_class_install_property (object_class,
1756 g_param_spec_flags ("view-features",
1757 "Features of the view",
1758 "Flags for all enabled features",
1759 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1760 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1761 g_object_class_install_property (object_class,
1762 PROP_INDIVIDUAL_FEATURES,
1763 g_param_spec_flags ("individual-features",
1764 "Features of the contact menu",
1765 "Flags for all enabled features for the menu",
1766 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1767 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1769 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1773 empathy_individual_view_init (EmpathyIndividualView *view)
1775 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1776 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1779 /* Get saved group states. */
1780 empathy_contact_groups_get_all ();
1782 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1783 empathy_individual_store_row_separator_func, NULL, NULL);
1785 /* Set up drag target lists. */
1786 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1787 G_N_ELEMENTS (drag_types_dest_file));
1789 /* Connect to tree view signals rather than override. */
1790 g_signal_connect (view, "button-press-event",
1791 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1792 g_signal_connect (view, "key-press-event",
1793 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1794 g_signal_connect (view, "row-expanded",
1795 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1796 GINT_TO_POINTER (TRUE));
1797 g_signal_connect (view, "row-collapsed",
1798 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1799 GINT_TO_POINTER (FALSE));
1800 g_signal_connect (view, "query-tooltip",
1801 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1804 EmpathyIndividualView *
1805 empathy_individual_view_new (EmpathyIndividualStore *store,
1806 EmpathyIndividualViewFeatureFlags view_features,
1807 EmpathyIndividualFeatureFlags individual_features)
1809 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1811 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1813 "individual-features", individual_features,
1814 "view-features", view_features, NULL);
1818 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1820 EmpathyIndividualViewPriv *priv;
1821 GtkTreeSelection *selection;
1823 GtkTreeModel *model;
1824 FolksIndividual *individual;
1826 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1828 priv = GET_PRIV (view);
1830 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1831 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1836 gtk_tree_model_get (model, &iter,
1837 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1842 EmpathyIndividualManagerFlags
1843 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1845 EmpathyIndividualViewPriv *priv;
1846 GtkTreeSelection *selection;
1848 GtkTreeModel *model;
1849 EmpathyIndividualFeatureFlags flags;
1851 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1853 priv = GET_PRIV (view);
1855 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1856 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1861 gtk_tree_model_get (model, &iter,
1862 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1868 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1869 gboolean *is_fake_group)
1871 EmpathyIndividualViewPriv *priv;
1872 GtkTreeSelection *selection;
1874 GtkTreeModel *model;
1879 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1881 priv = GET_PRIV (view);
1883 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1884 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1889 gtk_tree_model_get (model, &iter,
1890 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1891 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1892 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1900 if (is_fake_group != NULL)
1901 *is_fake_group = fake;
1907 individual_view_remove_dialog_show (GtkWindow *parent,
1908 const gchar *message,
1909 const gchar *secondary_text)
1914 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1915 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1916 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1917 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1918 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1919 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1920 "%s", secondary_text);
1922 gtk_widget_show (dialog);
1924 res = gtk_dialog_run (GTK_DIALOG (dialog));
1925 gtk_widget_destroy (dialog);
1927 return (res == GTK_RESPONSE_YES);
1931 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1932 EmpathyIndividualView *view)
1936 group = empathy_individual_view_get_selected_group (view, NULL);
1943 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1945 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1946 if (individual_view_remove_dialog_show (parent, _("Removing group"),
1949 /* TODO: implement */
1950 DEBUG ("removing group unimplemented");
1960 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
1962 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1967 gboolean is_fake_group;
1969 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1971 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
1972 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
1977 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
1978 if (!group || is_fake_group)
1980 /* We can't alter fake groups */
1984 menu = gtk_menu_new ();
1987 if (priv->view_features &
1988 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
1989 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1990 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1991 gtk_widget_show (item);
1992 g_signal_connect (item, "activate",
1993 G_CALLBACK (individual_view_group_rename_activate_cb),
1998 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2000 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2001 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2002 GTK_ICON_SIZE_MENU);
2003 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2004 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2005 gtk_widget_show (item);
2006 g_signal_connect (item, "activate",
2007 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2016 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2017 EmpathyIndividualView *view)
2019 FolksIndividual *individual;
2021 individual = empathy_individual_view_dup_selected (view);
2028 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2031 ("Do you really want to remove the contact '%s'?"),
2032 folks_individual_get_alias (individual));
2033 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2036 EmpathyIndividualManager *manager;
2038 manager = empathy_individual_manager_dup_singleton ();
2039 empathy_individual_manager_remove (manager, individual, "");
2040 g_object_unref (G_OBJECT (manager));
2044 g_object_unref (individual);
2049 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2051 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2052 FolksIndividual *individual;
2053 GtkWidget *menu = NULL;
2056 EmpathyIndividualManagerFlags flags;
2058 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2060 individual = empathy_individual_view_dup_selected (view);
2065 flags = empathy_individual_view_get_flags (view);
2067 /* TODO: implement (create the menu here */
2068 DEBUG ("individual menu not implemented");
2070 /* Remove contact */
2071 if (priv->view_features &
2072 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2073 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2076 /* create the menu if required, or just add a separator */
2079 menu = gtk_menu_new ();
2083 item = gtk_separator_menu_item_new ();
2084 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2085 gtk_widget_show (item);
2089 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2090 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2091 GTK_ICON_SIZE_MENU);
2092 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2093 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2094 gtk_widget_show (item);
2095 g_signal_connect (item, "activate",
2096 G_CALLBACK (individual_view_remove_activate_cb), view);
2099 g_object_unref (individual);
2105 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2106 EmpathyLiveSearch *search)
2108 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2110 /* remove old handlers if old search was not null */
2111 if (priv->search_widget != NULL)
2113 g_signal_handlers_disconnect_by_func (view,
2114 individual_view_start_search_cb, NULL);
2116 g_signal_handlers_disconnect_by_func (priv->search_widget,
2117 individual_view_search_text_notify_cb, view);
2118 g_signal_handlers_disconnect_by_func (priv->search_widget,
2119 individual_view_search_activate_cb, view);
2120 g_signal_handlers_disconnect_by_func (priv->search_widget,
2121 individual_view_search_hide_cb, view);
2122 g_signal_handlers_disconnect_by_func (priv->search_widget,
2123 individual_view_search_show_cb, view);
2124 g_object_unref (priv->search_widget);
2125 priv->search_widget = NULL;
2128 /* connect handlers if new search is not null */
2131 priv->search_widget = g_object_ref (search);
2133 g_signal_connect (view, "start-interactive-search",
2134 G_CALLBACK (individual_view_start_search_cb), NULL);
2136 g_signal_connect (priv->search_widget, "notify::text",
2137 G_CALLBACK (individual_view_search_text_notify_cb), view);
2138 g_signal_connect (priv->search_widget, "activate",
2139 G_CALLBACK (individual_view_search_activate_cb), view);
2140 g_signal_connect (priv->search_widget, "hide",
2141 G_CALLBACK (individual_view_search_hide_cb), view);
2142 g_signal_connect (priv->search_widget, "show",
2143 G_CALLBACK (individual_view_search_show_cb), view);