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);
964 g_object_set (cell, "visible", !is_group, "pixbuf", pixbuf, NULL);
968 g_object_unref (pixbuf);
971 individual_view_cell_set_background (view, cell, is_group, is_active);
975 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
976 GtkCellRenderer *cell,
979 EmpathyIndividualView *view)
981 GdkPixbuf *pixbuf = NULL;
985 gtk_tree_model_get (model, iter,
986 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
987 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
992 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
994 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
997 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
999 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1000 GTK_ICON_SIZE_MENU);
1004 g_object_set (cell, "visible", pixbuf != NULL, "pixbuf", pixbuf, NULL);
1007 g_object_unref (pixbuf);
1013 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1014 GtkCellRenderer *cell,
1015 GtkTreeModel *model,
1017 EmpathyIndividualView *view)
1021 gboolean can_audio, can_video;
1023 gtk_tree_model_get (model, iter,
1024 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1025 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1026 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1027 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1030 "visible", !is_group && (can_audio || can_video),
1031 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1034 individual_view_cell_set_background (view, cell, is_group, is_active);
1038 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1039 GtkCellRenderer *cell,
1040 GtkTreeModel *model,
1042 EmpathyIndividualView *view)
1045 gboolean show_avatar;
1049 gtk_tree_model_get (model, iter,
1050 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1051 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1052 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1053 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1056 "visible", !is_group && show_avatar, "pixbuf", pixbuf, NULL);
1060 g_object_unref (pixbuf);
1063 individual_view_cell_set_background (view, cell, is_group, is_active);
1067 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1068 GtkCellRenderer *cell,
1069 GtkTreeModel *model,
1071 EmpathyIndividualView *view)
1076 gtk_tree_model_get (model, iter,
1077 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1078 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1080 individual_view_cell_set_background (view, cell, is_group, is_active);
1084 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1085 GtkCellRenderer *cell,
1086 GtkTreeModel *model,
1088 EmpathyIndividualView *view)
1093 gtk_tree_model_get (model, iter,
1094 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1095 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1097 if (gtk_tree_model_iter_has_child (model, iter))
1100 gboolean row_expanded;
1102 path = gtk_tree_model_get_path (model, iter);
1104 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1105 (gtk_tree_view_column_get_tree_view (column)), path);
1106 gtk_tree_path_free (path);
1111 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1116 g_object_set (cell, "visible", FALSE, NULL);
1119 individual_view_cell_set_background (view, cell, is_group, is_active);
1123 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1128 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1129 GtkTreeModel *model;
1133 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1136 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1138 gtk_tree_model_get (model, iter,
1139 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1141 expanded = GPOINTER_TO_INT (user_data);
1142 empathy_contact_group_set_expanded (name, expanded);
1148 individual_view_start_search_cb (EmpathyIndividualView *view,
1151 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1153 if (priv->search_widget == NULL)
1156 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1157 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1159 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1165 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1167 EmpathyIndividualView *view)
1169 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1171 GtkTreeViewColumn *focus_column;
1172 GtkTreeModel *model;
1174 gboolean set_cursor = FALSE;
1176 gtk_tree_model_filter_refilter (priv->filter);
1178 /* Set cursor on the first contact. If it is already set on a group,
1179 * set it on its first child contact. Note that first child of a group
1180 * is its separator, that's why we actually set to the 2nd
1183 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1184 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1188 path = gtk_tree_path_new_from_string ("0:1");
1191 else if (gtk_tree_path_get_depth (path) < 2)
1195 gtk_tree_model_get_iter (model, &iter, path);
1196 gtk_tree_model_get (model, &iter,
1197 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1202 gtk_tree_path_down (path);
1203 gtk_tree_path_next (path);
1210 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1212 if (gtk_tree_model_get_iter (model, &iter, path))
1214 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1219 gtk_tree_path_free (path);
1223 individual_view_search_activate_cb (GtkWidget *search,
1224 EmpathyIndividualView *view)
1227 GtkTreeViewColumn *focus_column;
1229 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1232 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1233 gtk_tree_path_free (path);
1235 gtk_widget_hide (search);
1240 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1241 EmpathyIndividualView *view)
1243 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1244 GtkTreeModel *model;
1246 gboolean valid = FALSE;
1248 /* block expand or collapse handlers, they would write the
1249 * expand or collapsed setting to file otherwise */
1250 g_signal_handlers_block_by_func (view,
1251 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1252 g_signal_handlers_block_by_func (view,
1253 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1255 /* restore which groups are expanded and which are not */
1256 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1257 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1258 valid; valid = gtk_tree_model_iter_next (model, &iter))
1264 gtk_tree_model_get (model, &iter,
1265 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1266 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1275 path = gtk_tree_model_get_path (model, &iter);
1276 if ((priv->view_features &
1277 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1278 empathy_contact_group_get_expanded (name))
1280 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1284 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1287 gtk_tree_path_free (path);
1291 /* unblock expand or collapse handlers */
1292 g_signal_handlers_unblock_by_func (view,
1293 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1294 g_signal_handlers_unblock_by_func (view,
1295 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1299 individual_view_search_show_cb (EmpathyLiveSearch *search,
1300 EmpathyIndividualView *view)
1302 /* block expand or collapse handlers during expand all, they would
1303 * write the expand or collapsed setting to file otherwise */
1304 g_signal_handlers_block_by_func (view,
1305 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1307 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1309 g_signal_handlers_unblock_by_func (view,
1310 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1314 EmpathyIndividualView *view;
1315 GtkTreeRowReference *row_ref;
1320 individual_view_expand_idle_cb (gpointer user_data)
1322 ExpandData *data = user_data;
1325 path = gtk_tree_row_reference_get_path (data->row_ref);
1330 g_signal_handlers_block_by_func (data->view,
1331 individual_view_row_expand_or_collapse_cb,
1332 GINT_TO_POINTER (data->expand));
1335 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1337 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1339 gtk_tree_path_free (path);
1341 g_signal_handlers_unblock_by_func (data->view,
1342 individual_view_row_expand_or_collapse_cb,
1343 GINT_TO_POINTER (data->expand));
1346 g_object_unref (data->view);
1347 gtk_tree_row_reference_free (data->row_ref);
1348 g_slice_free (ExpandData, data);
1354 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1357 EmpathyIndividualView *view)
1359 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1360 gboolean is_group = FALSE;
1364 gtk_tree_model_get (model, iter,
1365 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1366 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1369 if (!is_group || EMP_STR_EMPTY (name))
1375 data = g_slice_new0 (ExpandData);
1376 data->view = g_object_ref (view);
1377 data->row_ref = gtk_tree_row_reference_new (model, path);
1379 (priv->view_features &
1380 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1381 (priv->search_widget != NULL &&
1382 gtk_widget_get_visible (priv->search_widget)) ||
1383 empathy_contact_group_get_expanded (name);
1385 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1386 * gtk_tree_model_filter_refilter () */
1387 g_idle_add (individual_view_expand_idle_cb, data);
1393 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1396 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1397 GtkTreeModel *model;
1398 GtkTreePath *parent_path;
1399 GtkTreeIter parent_iter;
1401 if (gtk_tree_path_get_depth (path) < 2)
1404 /* A group row is visible if and only if at least one if its child is visible.
1405 * So when a row is inserted/deleted/changed in the base model, that could
1406 * modify the visibility of its parent in the filter model.
1409 model = GTK_TREE_MODEL (priv->store);
1410 parent_path = gtk_tree_path_copy (path);
1411 gtk_tree_path_up (parent_path);
1412 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1414 /* This tells the filter to verify the visibility of that row, and
1415 * show/hide it if necessary */
1416 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1417 parent_path, &parent_iter);
1419 gtk_tree_path_free (parent_path);
1423 individual_view_store_row_changed_cb (GtkTreeModel *model,
1426 EmpathyIndividualView *view)
1428 individual_view_verify_group_visibility (view, path);
1432 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1434 EmpathyIndividualView *view)
1436 individual_view_verify_group_visibility (view, path);
1440 individual_view_constructed (GObject *object)
1442 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1443 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1445 GtkCellRenderer *cell;
1446 GtkTreeViewColumn *col;
1449 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1450 GTK_TREE_MODEL (priv->store), NULL));
1451 gtk_tree_model_filter_set_visible_func (priv->filter,
1452 individual_view_filter_visible_func, view, NULL);
1454 g_signal_connect (priv->store, "row-has-child-toggled",
1455 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1456 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1457 GTK_TREE_MODEL (priv->filter));
1459 tp_g_signal_connect_object (priv->store, "row-changed",
1460 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1461 tp_g_signal_connect_object (priv->store, "row-inserted",
1462 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1463 tp_g_signal_connect_object (priv->store, "row-deleted",
1464 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1467 /* Setting reorderable is a hack that gets us row previews as drag icons
1468 for free. We override all the drag handlers. It's tricky to get the
1469 position of the drag icon right in drag_begin. GtkTreeView has special
1470 voodoo for it, so we let it do the voodoo that he do.
1473 "headers-visible", FALSE,
1474 "reorderable", TRUE,
1475 "show-expanders", FALSE,
1478 col = gtk_tree_view_column_new ();
1481 cell = gtk_cell_renderer_pixbuf_new ();
1482 gtk_tree_view_column_pack_start (col, cell, FALSE);
1483 gtk_tree_view_column_set_cell_data_func (col, cell,
1484 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1487 g_object_set (cell, "xpad", 5, "ypad", 1, "visible", FALSE, NULL);
1490 cell = gtk_cell_renderer_pixbuf_new ();
1491 gtk_tree_view_column_pack_start (col, cell, FALSE);
1492 gtk_tree_view_column_set_cell_data_func (col, cell,
1493 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1498 "ypad", 0, "visible", FALSE, "width", 16, "height", 16, NULL);
1501 cell = empathy_cell_renderer_text_new ();
1502 gtk_tree_view_column_pack_start (col, cell, TRUE);
1503 gtk_tree_view_column_set_cell_data_func (col, cell,
1504 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1506 gtk_tree_view_column_add_attribute (col, cell,
1507 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1508 gtk_tree_view_column_add_attribute (col, cell,
1509 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1510 gtk_tree_view_column_add_attribute (col, cell,
1511 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1512 gtk_tree_view_column_add_attribute (col, cell,
1513 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1514 gtk_tree_view_column_add_attribute (col, cell,
1515 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1516 gtk_tree_view_column_add_attribute (col, cell,
1517 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1519 /* Audio Call Icon */
1520 cell = empathy_cell_renderer_activatable_new ();
1521 gtk_tree_view_column_pack_start (col, cell, FALSE);
1522 gtk_tree_view_column_set_cell_data_func (col, cell,
1523 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1526 g_object_set (cell, "visible", FALSE, NULL);
1528 g_signal_connect (cell, "path-activated",
1529 G_CALLBACK (individual_view_call_activated_cb), view);
1532 cell = gtk_cell_renderer_pixbuf_new ();
1533 gtk_tree_view_column_pack_start (col, cell, FALSE);
1534 gtk_tree_view_column_set_cell_data_func (col, cell,
1535 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1540 "ypad", 0, "visible", FALSE, "width", 32, "height", 32, NULL);
1543 cell = empathy_cell_renderer_expander_new ();
1544 gtk_tree_view_column_pack_end (col, cell, FALSE);
1545 gtk_tree_view_column_set_cell_data_func (col, cell,
1546 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1549 /* Actually add the column now we have added all cell renderers */
1550 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1553 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1555 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1558 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1560 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1566 individual_view_set_view_features (EmpathyIndividualView *view,
1567 EmpathyIndividualFeatureFlags features)
1569 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1570 gboolean has_tooltip;
1572 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1574 priv->view_features = features;
1576 /* Update DnD source/dest */
1577 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1579 gtk_drag_source_set (GTK_WIDGET (view),
1582 G_N_ELEMENTS (drag_types_source),
1583 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1587 gtk_drag_source_unset (GTK_WIDGET (view));
1591 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1593 gtk_drag_dest_set (GTK_WIDGET (view),
1594 GTK_DEST_DEFAULT_ALL,
1596 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1600 /* FIXME: URI could still be droped depending on FT feature */
1601 gtk_drag_dest_unset (GTK_WIDGET (view));
1604 /* Update has-tooltip */
1606 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1607 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1611 individual_view_dispose (GObject *object)
1613 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1614 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1616 if (priv->store != NULL)
1618 g_object_unref (priv->store);
1621 if (priv->filter != NULL)
1623 g_object_unref (priv->filter);
1624 priv->filter = NULL;
1626 if (priv->tooltip_widget != NULL)
1628 gtk_widget_destroy (priv->tooltip_widget);
1629 priv->tooltip_widget = NULL;
1631 if (priv->file_targets != NULL)
1633 gtk_target_list_unref (priv->file_targets);
1634 priv->file_targets = NULL;
1637 empathy_individual_view_set_live_search (view, NULL);
1639 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1643 individual_view_get_property (GObject *object,
1648 EmpathyIndividualViewPriv *priv;
1650 priv = GET_PRIV (object);
1655 g_value_set_object (value, priv->store);
1657 case PROP_VIEW_FEATURES:
1658 g_value_set_flags (value, priv->view_features);
1660 case PROP_INDIVIDUAL_FEATURES:
1661 g_value_set_flags (value, priv->individual_features);
1664 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1670 individual_view_set_property (GObject *object,
1672 const GValue *value,
1675 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1676 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1681 priv->store = g_value_dup_object (value);
1683 case PROP_VIEW_FEATURES:
1684 individual_view_set_view_features (view, g_value_get_flags (value));
1686 case PROP_INDIVIDUAL_FEATURES:
1687 priv->individual_features = g_value_get_flags (value);
1690 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1696 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1698 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1699 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1700 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1702 object_class->constructed = individual_view_constructed;
1703 object_class->dispose = individual_view_dispose;
1704 object_class->get_property = individual_view_get_property;
1705 object_class->set_property = individual_view_set_property;
1707 widget_class->drag_data_received = individual_view_drag_data_received;
1708 widget_class->drag_drop = individual_view_drag_drop;
1709 widget_class->drag_begin = individual_view_drag_begin;
1710 widget_class->drag_data_get = individual_view_drag_data_get;
1711 widget_class->drag_end = individual_view_drag_end;
1712 widget_class->drag_motion = individual_view_drag_motion;
1714 /* We use the class method to let user of this widget to connect to
1715 * the signal and stop emission of the signal so the default handler
1716 * won't be called. */
1717 tree_view_class->row_activated = individual_view_row_activated;
1719 signals[DRAG_CONTACT_RECEIVED] =
1720 g_signal_new ("drag-contact-received",
1721 G_OBJECT_CLASS_TYPE (klass),
1725 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1726 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1728 g_object_class_install_property (object_class,
1730 g_param_spec_object ("store",
1731 "The store of the view",
1732 "The store of the view",
1733 EMPATHY_TYPE_INDIVIDUAL_STORE,
1734 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1735 g_object_class_install_property (object_class,
1737 g_param_spec_flags ("view-features",
1738 "Features of the view",
1739 "Flags for all enabled features",
1740 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1741 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1742 g_object_class_install_property (object_class,
1743 PROP_INDIVIDUAL_FEATURES,
1744 g_param_spec_flags ("individual-features",
1745 "Features of the contact menu",
1746 "Flags for all enabled features for the menu",
1747 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1748 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1750 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1754 empathy_individual_view_init (EmpathyIndividualView *view)
1756 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1757 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1760 /* Get saved group states. */
1761 empathy_contact_groups_get_all ();
1763 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1764 empathy_individual_store_row_separator_func, NULL, NULL);
1766 /* Set up drag target lists. */
1767 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1768 G_N_ELEMENTS (drag_types_dest_file));
1770 /* Connect to tree view signals rather than override. */
1771 g_signal_connect (view, "button-press-event",
1772 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1773 g_signal_connect (view, "key-press-event",
1774 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1775 g_signal_connect (view, "row-expanded",
1776 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1777 GINT_TO_POINTER (TRUE));
1778 g_signal_connect (view, "row-collapsed",
1779 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1780 GINT_TO_POINTER (FALSE));
1781 g_signal_connect (view, "query-tooltip",
1782 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1785 EmpathyIndividualView *
1786 empathy_individual_view_new (EmpathyIndividualStore *store,
1787 EmpathyIndividualViewFeatureFlags view_features,
1788 EmpathyIndividualFeatureFlags individual_features)
1790 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1792 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1794 "individual-features", individual_features,
1795 "view-features", view_features, NULL);
1799 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1801 EmpathyIndividualViewPriv *priv;
1802 GtkTreeSelection *selection;
1804 GtkTreeModel *model;
1805 FolksIndividual *individual;
1807 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1809 priv = GET_PRIV (view);
1811 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1812 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1817 gtk_tree_model_get (model, &iter,
1818 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1823 EmpathyIndividualManagerFlags
1824 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1826 EmpathyIndividualViewPriv *priv;
1827 GtkTreeSelection *selection;
1829 GtkTreeModel *model;
1830 EmpathyIndividualFeatureFlags flags;
1832 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1834 priv = GET_PRIV (view);
1836 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1837 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1842 gtk_tree_model_get (model, &iter,
1843 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1849 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1850 gboolean *is_fake_group)
1852 EmpathyIndividualViewPriv *priv;
1853 GtkTreeSelection *selection;
1855 GtkTreeModel *model;
1860 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1862 priv = GET_PRIV (view);
1864 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1865 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1870 gtk_tree_model_get (model, &iter,
1871 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1872 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1873 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1881 if (is_fake_group != NULL)
1882 *is_fake_group = fake;
1888 individual_view_remove_dialog_show (GtkWindow *parent,
1889 const gchar *message,
1890 const gchar *secondary_text)
1895 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1896 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1897 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1898 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1899 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1900 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1901 "%s", secondary_text);
1903 gtk_widget_show (dialog);
1905 res = gtk_dialog_run (GTK_DIALOG (dialog));
1906 gtk_widget_destroy (dialog);
1908 return (res == GTK_RESPONSE_YES);
1912 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1913 EmpathyIndividualView *view)
1917 group = empathy_individual_view_get_selected_group (view, NULL);
1924 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1926 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1927 if (individual_view_remove_dialog_show (parent, _("Removing group"),
1930 /* TODO: implement */
1931 DEBUG ("removing group unimplemented");
1941 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
1943 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1948 gboolean is_fake_group;
1950 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1952 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
1953 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
1958 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
1959 if (!group || is_fake_group)
1961 /* We can't alter fake groups */
1965 menu = gtk_menu_new ();
1968 if (priv->view_features &
1969 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
1970 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1971 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1972 gtk_widget_show (item);
1973 g_signal_connect (item, "activate",
1974 G_CALLBACK (individual_view_group_rename_activate_cb),
1979 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
1981 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1982 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1983 GTK_ICON_SIZE_MENU);
1984 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1985 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1986 gtk_widget_show (item);
1987 g_signal_connect (item, "activate",
1988 G_CALLBACK (individual_view_group_remove_activate_cb), view);
1997 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
1998 EmpathyIndividualView *view)
2000 FolksIndividual *individual;
2002 individual = empathy_individual_view_dup_selected (view);
2009 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2012 ("Do you really want to remove the contact '%s'?"),
2013 folks_individual_get_alias (individual));
2014 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2017 EmpathyIndividualManager *manager;
2019 manager = empathy_individual_manager_dup_singleton ();
2020 empathy_individual_manager_remove (manager, individual, "");
2021 g_object_unref (G_OBJECT (manager));
2025 g_object_unref (individual);
2030 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2032 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2033 FolksIndividual *individual;
2034 GtkWidget *menu = NULL;
2037 EmpathyIndividualManagerFlags flags;
2039 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2041 individual = empathy_individual_view_dup_selected (view);
2046 flags = empathy_individual_view_get_flags (view);
2048 /* TODO: implement (create the menu here */
2049 DEBUG ("individual menu not implemented");
2051 /* Remove contact */
2052 if (priv->view_features &
2053 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2054 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2057 /* create the menu if required, or just add a separator */
2060 menu = gtk_menu_new ();
2064 item = gtk_separator_menu_item_new ();
2065 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2066 gtk_widget_show (item);
2070 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2071 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2072 GTK_ICON_SIZE_MENU);
2073 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2074 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2075 gtk_widget_show (item);
2076 g_signal_connect (item, "activate",
2077 G_CALLBACK (individual_view_remove_activate_cb), view);
2080 g_object_unref (individual);
2086 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2087 EmpathyLiveSearch *search)
2089 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2091 /* remove old handlers if old search was not null */
2092 if (priv->search_widget != NULL)
2094 g_signal_handlers_disconnect_by_func (view,
2095 individual_view_start_search_cb, NULL);
2097 g_signal_handlers_disconnect_by_func (priv->search_widget,
2098 individual_view_search_text_notify_cb, view);
2099 g_signal_handlers_disconnect_by_func (priv->search_widget,
2100 individual_view_search_activate_cb, view);
2101 g_signal_handlers_disconnect_by_func (priv->search_widget,
2102 individual_view_search_hide_cb, view);
2103 g_signal_handlers_disconnect_by_func (priv->search_widget,
2104 individual_view_search_show_cb, view);
2105 g_object_unref (priv->search_widget);
2106 priv->search_widget = NULL;
2109 /* connect handlers if new search is not null */
2112 priv->search_widget = g_object_ref (search);
2114 g_signal_connect (view, "start-interactive-search",
2115 G_CALLBACK (individual_view_start_search_cb), NULL);
2117 g_signal_connect (priv->search_widget, "notify::text",
2118 G_CALLBACK (individual_view_search_text_notify_cb), view);
2119 g_signal_connect (priv->search_widget, "activate",
2120 G_CALLBACK (individual_view_search_activate_cb), view);
2121 g_signal_connect (priv->search_widget, "hide",
2122 G_CALLBACK (individual_view_search_hide_cb), view);
2123 g_signal_connect (priv->search_widget, "show",
2124 G_CALLBACK (individual_view_search_show_cb), view);