1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
35 #include <folks/folks.h>
36 #include <telepathy-glib/account-manager.h>
37 #include <telepathy-glib/util.h>
39 #include <libempathy/empathy-call-factory.h>
40 #include <libempathy/empathy-individual-manager.h>
41 #include <libempathy/empathy-contact-groups.h>
42 #include <libempathy/empathy-dispatcher.h>
43 #include <libempathy/empathy-utils.h>
45 #include "empathy-individual-view.h"
46 #include "empathy-individual-menu.h"
47 #include "empathy-individual-store.h"
48 #include "empathy-images.h"
49 #include "empathy-cell-renderer-expander.h"
50 #include "empathy-cell-renderer-text.h"
51 #include "empathy-cell-renderer-activatable.h"
52 #include "empathy-ui-utils.h"
53 #include "empathy-gtk-enum-types.h"
54 #include "empathy-gtk-marshal.h"
56 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
57 #include <libempathy/empathy-debug.h>
59 /* Active users are those which have recently changed state
60 * (e.g. online, offline or from normal to a busy state).
63 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
66 EmpathyIndividualStore *store;
67 GtkTreeRowReference *drag_row;
68 EmpathyIndividualViewFeatureFlags view_features;
69 EmpathyContactFeatureFlags individual_features;
70 GtkWidget *tooltip_widget;
71 GtkTargetList *file_targets;
73 GtkTreeModelFilter *filter;
74 GtkWidget *search_widget;
75 } EmpathyIndividualViewPriv;
79 EmpathyIndividualView *view;
86 EmpathyIndividualView *view;
87 FolksIndividual *individual;
96 PROP_INDIVIDUAL_FEATURES,
99 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
100 * specific EmpathyContacts (between/in/out of Individuals) */
103 DND_DRAG_TYPE_INDIVIDUAL_ID,
104 DND_DRAG_TYPE_URI_LIST,
105 DND_DRAG_TYPE_STRING,
108 static const GtkTargetEntry drag_types_dest[] = {
109 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
110 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
111 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
112 {"text/plain", 0, DND_DRAG_TYPE_STRING},
113 {"STRING", 0, DND_DRAG_TYPE_STRING},
116 static const GtkTargetEntry drag_types_dest_file[] = {
117 {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
118 {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
121 static const GtkTargetEntry drag_types_source[] = {
122 {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
125 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
126 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
130 DRAG_CONTACT_RECEIVED,
134 static guint signals[LAST_SIGNAL];
136 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
140 individual_view_is_visible_individual (EmpathyIndividualView *self,
141 FolksIndividual *individual)
143 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
144 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
147 gchar *dup_str = NULL;
151 g_assert (live != NULL);
153 /* check alias name */
154 str = folks_individual_get_alias (individual);
155 if (empathy_live_search_match (live, str))
158 /* check contact id, remove the @server.com part */
159 personas = folks_individual_get_personas (individual);
160 for (l = personas; l; l = l->next)
162 str = folks_persona_get_uid (l->data);
163 p = strstr (str, "@");
165 str = dup_str = g_strndup (str, p - str);
167 visible = empathy_live_search_match (live, str);
173 /* FIXME: Add more rules here, we could check phone numbers in
174 * contact's vCard for example. */
180 individual_view_filter_visible_func (GtkTreeModel *model,
184 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
185 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
186 FolksIndividual *individual = NULL;
187 gboolean is_group, is_separator, valid;
188 GtkTreeIter child_iter;
191 if (priv->search_widget == NULL ||
192 !gtk_widget_get_visible (priv->search_widget))
195 gtk_tree_model_get (model, iter,
196 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
197 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
198 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
201 if (individual != NULL)
203 visible = individual_view_is_visible_individual (self, individual);
204 g_object_unref (individual);
211 /* Not a contact, not a separator, must be a group */
212 g_return_val_if_fail (is_group, FALSE);
214 /* only show groups which are not empty */
215 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
216 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
218 gtk_tree_model_get (model, &child_iter,
219 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
222 if (individual == NULL)
225 visible = individual_view_is_visible_individual (self, individual);
226 g_object_unref (individual);
228 /* show group if it has at least one visible contact in it */
237 individual_view_tooltip_destroy_cb (GtkWidget *widget,
238 EmpathyIndividualView *view)
240 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
242 if (priv->tooltip_widget != NULL)
244 DEBUG ("Tooltip destroyed");
245 g_object_unref (priv->tooltip_widget);
246 priv->tooltip_widget = NULL;
251 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
254 gboolean keyboard_mode,
258 EmpathyIndividualViewPriv *priv;
259 FolksIndividual *individual;
263 static gint running = 0;
264 gboolean ret = FALSE;
265 EmpathyContact *contact;
267 priv = GET_PRIV (view);
269 /* Avoid an infinite loop. See GNOME bug #574377 */
276 /* Don't show the tooltip if there's already a popup menu */
277 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
282 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
283 keyboard_mode, &model, &path, &iter))
288 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
289 gtk_tree_path_free (path);
291 gtk_tree_model_get (model, &iter,
292 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
294 if (individual == NULL)
300 contact = empathy_contact_from_folks_individual (individual);
305 if (!priv->tooltip_widget)
307 priv->tooltip_widget = empathy_contact_widget_new (contact,
308 EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
309 EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
310 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
311 g_object_ref (priv->tooltip_widget);
312 g_signal_connect (priv->tooltip_widget, "destroy",
313 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
314 gtk_widget_show (priv->tooltip_widget);
318 empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
321 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
324 g_object_unref (contact);
325 g_object_unref (individual);
333 individual_view_handle_drag (EmpathyIndividualView *self,
334 FolksIndividual *individual,
335 const gchar *old_group,
336 const gchar *new_group,
337 GdkDragAction action)
339 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
340 g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
342 DEBUG ("individual %s dragged from '%s' to '%s'",
343 folks_individual_get_id (individual), old_group, new_group);
345 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
347 /* Mark contact as favourite */
348 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
352 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
354 /* Remove contact as favourite */
355 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
357 /* Don't try to remove it */
361 if (new_group != NULL)
362 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE);
364 if (old_group != NULL && action == GDK_ACTION_MOVE)
365 folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE);
369 group_can_be_modified (const gchar *name,
370 gboolean is_fake_group,
373 /* Real groups can always be modified */
377 /* The favorite fake group can be modified so users can
378 * add/remove favorites using DnD */
379 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
382 /* We can remove contacts from the 'ungrouped' fake group */
383 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
390 individual_view_contact_drag_received (GtkWidget *self,
391 GdkDragContext *context,
394 GtkSelectionData *selection)
396 EmpathyIndividualViewPriv *priv;
397 EmpathyIndividualManager *manager;
398 FolksIndividual *individual;
399 GtkTreePath *source_path;
400 const gchar *sel_data;
401 gchar *new_group = NULL;
402 gchar *old_group = NULL;
403 gboolean new_group_is_fake, old_group_is_fake = TRUE;
405 priv = GET_PRIV (self);
407 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
408 new_group = empathy_individual_store_get_parent_group (model, path,
409 NULL, &new_group_is_fake);
411 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
414 /* Get source group information. */
417 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
421 empathy_individual_store_get_parent_group (model, source_path,
422 NULL, &old_group_is_fake);
423 gtk_tree_path_free (source_path);
427 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
430 if (!tp_strdiff (old_group, new_group))
437 /* XXX: for contacts, we used to ensure the account, create the contact
438 * factory, and then wait on the contacts. But they should already be
439 * created by this point */
441 manager = empathy_individual_manager_dup_singleton ();
442 individual = empathy_individual_manager_lookup_member (manager, sel_data);
444 if (individual == NULL)
446 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
448 g_object_unref (manager);
453 /* FIXME: We should probably wait for the cb before calling
456 individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
457 old_group, new_group, gdk_drag_context_get_selected_action (context));
459 g_object_unref (G_OBJECT (manager));
467 individual_view_file_drag_received (GtkWidget *view,
468 GdkDragContext *context,
471 GtkSelectionData *selection)
474 const gchar *sel_data;
475 FolksIndividual *individual;
476 EmpathyContact *contact;
478 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
480 gtk_tree_model_get_iter (model, &iter, path);
481 gtk_tree_model_get (model, &iter,
482 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
488 contact = empathy_contact_from_folks_individual (individual);
489 empathy_send_file_from_uri_list (contact, sel_data);
491 g_object_unref (individual);
497 individual_view_drag_data_received (GtkWidget *view,
498 GdkDragContext *context,
501 GtkSelectionData *selection,
507 GtkTreeViewDropPosition position;
509 gboolean success = TRUE;
511 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
513 /* Get destination group information. */
514 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
515 x, y, &path, &position);
520 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
521 || info == DND_DRAG_TYPE_STRING)
523 success = individual_view_contact_drag_received (view,
524 context, model, path, selection);
526 else if (info == DND_DRAG_TYPE_URI_LIST)
528 success = individual_view_file_drag_received (view,
529 context, model, path, selection);
532 gtk_tree_path_free (path);
533 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
537 individual_view_drag_motion_cb (DragMotionData *data)
539 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
541 data->timeout_id = 0;
547 individual_view_drag_motion (GtkWidget *widget,
548 GdkDragContext *context,
553 EmpathyIndividualViewPriv *priv;
557 static DragMotionData *dm = NULL;
560 gboolean is_different = FALSE;
561 gboolean cleanup = TRUE;
562 gboolean retval = TRUE;
564 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
565 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
567 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
568 x, y, &path, NULL, NULL, NULL);
574 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
575 is_different = (!dm || (dm
576 && gtk_tree_path_compare (dm->path, path) != 0));
585 /* Coordinates don't point to an actual row, so make sure the pointer
586 and highlighting don't indicate that a drag is possible.
588 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
589 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
592 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
593 gtk_tree_model_get_iter (model, &iter, path);
595 if (target == GDK_NONE)
597 /* If target == GDK_NONE, then we don't have a target that can be
598 dropped on a contact. This means a contact drag. If we're
599 pointing to a group, highlight it. Otherwise, if the contact
600 we're pointing to is in a group, highlight that. Otherwise,
601 set the drag position to before the first row for a drag into
602 the "non-group" at the top.
604 GtkTreeIter group_iter;
606 GtkTreePath *group_path;
607 gtk_tree_model_get (model, &iter,
608 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
615 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
616 gtk_tree_model_get (model, &group_iter,
617 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
621 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
622 group_path = gtk_tree_model_get_path (model, &group_iter);
623 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
624 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
625 gtk_tree_path_free (group_path);
629 group_path = gtk_tree_path_new_first ();
630 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
631 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
632 group_path, GTK_TREE_VIEW_DROP_BEFORE);
637 /* This is a file drag, and it can only be dropped on contacts,
640 FolksIndividual *individual;
641 gtk_tree_model_get (model, &iter,
642 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
643 if (individual != NULL &&
644 folks_individual_is_online (individual) &&
645 (folks_individual_get_capabilities (individual) &
646 FOLKS_CAPABILITIES_FLAGS_FILE_TRANSFER))
648 gdk_drag_status (context, GDK_ACTION_COPY, time_);
649 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
650 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
651 g_object_unref (individual);
655 gdk_drag_status (context, 0, time_);
656 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
661 if (!is_different && !cleanup)
668 gtk_tree_path_free (dm->path);
671 g_source_remove (dm->timeout_id);
679 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
681 dm = g_new0 (DragMotionData, 1);
683 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
684 dm->path = gtk_tree_path_copy (path);
686 dm->timeout_id = g_timeout_add_seconds (1,
687 (GSourceFunc) individual_view_drag_motion_cb, dm);
694 individual_view_drag_begin (GtkWidget *widget,
695 GdkDragContext *context)
697 EmpathyIndividualViewPriv *priv;
698 GtkTreeSelection *selection;
703 priv = GET_PRIV (widget);
705 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
708 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
709 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
714 path = gtk_tree_model_get_path (model, &iter);
715 priv->drag_row = gtk_tree_row_reference_new (model, path);
716 gtk_tree_path_free (path);
720 individual_view_drag_data_get (GtkWidget *widget,
721 GdkDragContext *context,
722 GtkSelectionData *selection,
726 EmpathyIndividualViewPriv *priv;
727 GtkTreePath *src_path;
730 FolksIndividual *individual;
731 const gchar *individual_id;
733 priv = GET_PRIV (widget);
735 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
741 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
747 if (!gtk_tree_model_get_iter (model, &iter, src_path))
749 gtk_tree_path_free (src_path);
753 gtk_tree_path_free (src_path);
756 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
762 individual_id = folks_individual_get_id (individual);
766 case DND_DRAG_TYPE_INDIVIDUAL_ID:
767 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
768 (guchar *) individual_id, strlen (individual_id) + 1);
774 individual_view_drag_end (GtkWidget *widget,
775 GdkDragContext *context)
777 EmpathyIndividualViewPriv *priv;
779 priv = GET_PRIV (widget);
781 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
786 gtk_tree_row_reference_free (priv->drag_row);
787 priv->drag_row = NULL;
792 individual_view_drag_drop (GtkWidget *widget,
793 GdkDragContext *drag_context,
803 EmpathyIndividualView *view;
809 individual_view_popup_menu_idle_cb (gpointer user_data)
811 MenuPopupData *data = user_data;
814 menu = empathy_individual_view_get_individual_menu (data->view);
816 menu = empathy_individual_view_get_group_menu (data->view);
820 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
821 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
823 gtk_widget_show (menu);
824 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
826 g_object_ref_sink (menu);
827 g_object_unref (menu);
830 g_slice_free (MenuPopupData, data);
836 individual_view_button_press_event_cb (EmpathyIndividualView *view,
837 GdkEventButton *event,
840 if (event->button == 3)
844 data = g_slice_new (MenuPopupData);
846 data->button = event->button;
847 data->time = event->time;
848 g_idle_add (individual_view_popup_menu_idle_cb, data);
855 individual_view_key_press_event_cb (EmpathyIndividualView *view,
859 if (event->keyval == GDK_Menu)
863 data = g_slice_new (MenuPopupData);
866 data->time = event->time;
867 g_idle_add (individual_view_popup_menu_idle_cb, data);
874 individual_view_row_activated (GtkTreeView *view,
876 GtkTreeViewColumn *column)
878 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
879 FolksIndividual *individual;
880 EmpathyContact *contact = NULL;
884 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
889 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
890 gtk_tree_model_get_iter (model, &iter, path);
891 gtk_tree_model_get (model, &iter,
892 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
897 contact = empathy_contact_from_folks_individual (individual);
901 DEBUG ("Starting a chat");
903 empathy_dispatcher_chat_with_contact (contact,
904 gtk_get_current_event_time (), NULL, NULL);
907 g_object_unref (individual);
911 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
912 const gchar *path_string,
913 EmpathyIndividualView *view)
918 FolksIndividual *individual;
919 GdkEventButton *event;
923 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
924 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
927 gtk_tree_model_get (model, &iter,
928 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
929 if (individual == NULL)
932 event = (GdkEventButton *) gtk_get_current_event ();
934 menu = gtk_menu_new ();
935 shell = GTK_MENU_SHELL (menu);
938 item = empathy_individual_audio_call_menu_item_new (individual);
939 gtk_menu_shell_append (shell, item);
940 gtk_widget_show (item);
943 item = empathy_individual_video_call_menu_item_new (individual);
944 gtk_menu_shell_append (shell, item);
945 gtk_widget_show (item);
947 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
948 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
949 gtk_widget_show (menu);
950 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
951 event->button, event->time);
952 g_object_ref_sink (menu);
953 g_object_unref (menu);
955 g_object_unref (individual);
959 individual_view_cell_set_background (EmpathyIndividualView *view,
960 GtkCellRenderer *cell,
967 style = gtk_widget_get_style (GTK_WIDGET (view));
969 if (!is_group && is_active)
971 color = style->bg[GTK_STATE_SELECTED];
973 /* Here we take the current theme colour and add it to
974 * the colour for white and average the two. This
975 * gives a colour which is inline with the theme but
978 color.red = (color.red + (style->white).red) / 2;
979 color.green = (color.green + (style->white).green) / 2;
980 color.blue = (color.blue + (style->white).blue) / 2;
982 g_object_set (cell, "cell-background-gdk", &color, NULL);
986 g_object_set (cell, "cell-background-gdk", NULL, NULL);
991 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
992 GtkCellRenderer *cell,
995 EmpathyIndividualView *view)
1001 gtk_tree_model_get (model, iter,
1002 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1003 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1004 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1007 "visible", !is_group,
1013 g_object_unref (pixbuf);
1016 individual_view_cell_set_background (view, cell, is_group, is_active);
1020 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1021 GtkCellRenderer *cell,
1022 GtkTreeModel *model,
1024 EmpathyIndividualView *view)
1026 GdkPixbuf *pixbuf = NULL;
1030 gtk_tree_model_get (model, iter,
1031 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1032 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1037 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1039 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1040 GTK_ICON_SIZE_MENU);
1042 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1044 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1045 GTK_ICON_SIZE_MENU);
1050 "visible", pixbuf != NULL,
1055 g_object_unref (pixbuf);
1061 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1062 GtkCellRenderer *cell,
1063 GtkTreeModel *model,
1065 EmpathyIndividualView *view)
1069 gboolean can_audio, can_video;
1071 gtk_tree_model_get (model, iter,
1072 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1073 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1074 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1075 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1078 "visible", !is_group && (can_audio || can_video),
1079 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1082 individual_view_cell_set_background (view, cell, is_group, is_active);
1086 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1087 GtkCellRenderer *cell,
1088 GtkTreeModel *model,
1090 EmpathyIndividualView *view)
1093 gboolean show_avatar;
1097 gtk_tree_model_get (model, iter,
1098 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1099 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1100 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1101 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1104 "visible", !is_group && show_avatar,
1110 g_object_unref (pixbuf);
1113 individual_view_cell_set_background (view, cell, is_group, is_active);
1117 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1118 GtkCellRenderer *cell,
1119 GtkTreeModel *model,
1121 EmpathyIndividualView *view)
1126 gtk_tree_model_get (model, iter,
1127 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1128 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1130 individual_view_cell_set_background (view, cell, is_group, is_active);
1134 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1135 GtkCellRenderer *cell,
1136 GtkTreeModel *model,
1138 EmpathyIndividualView *view)
1143 gtk_tree_model_get (model, iter,
1144 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1145 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1147 if (gtk_tree_model_iter_has_child (model, iter))
1150 gboolean row_expanded;
1152 path = gtk_tree_model_get_path (model, iter);
1154 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1155 (gtk_tree_view_column_get_tree_view (column)), path);
1156 gtk_tree_path_free (path);
1161 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1166 g_object_set (cell, "visible", FALSE, NULL);
1169 individual_view_cell_set_background (view, cell, is_group, is_active);
1173 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1178 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1179 GtkTreeModel *model;
1183 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1186 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1188 gtk_tree_model_get (model, iter,
1189 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1191 expanded = GPOINTER_TO_INT (user_data);
1192 empathy_contact_group_set_expanded (name, expanded);
1198 individual_view_start_search_cb (EmpathyIndividualView *view,
1201 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1203 if (priv->search_widget == NULL)
1206 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1207 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1209 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1215 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1217 EmpathyIndividualView *view)
1219 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1221 GtkTreeViewColumn *focus_column;
1222 GtkTreeModel *model;
1224 gboolean set_cursor = FALSE;
1226 gtk_tree_model_filter_refilter (priv->filter);
1228 /* Set cursor on the first contact. If it is already set on a group,
1229 * set it on its first child contact. Note that first child of a group
1230 * is its separator, that's why we actually set to the 2nd
1233 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1234 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1238 path = gtk_tree_path_new_from_string ("0:1");
1241 else if (gtk_tree_path_get_depth (path) < 2)
1245 gtk_tree_model_get_iter (model, &iter, path);
1246 gtk_tree_model_get (model, &iter,
1247 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1252 gtk_tree_path_down (path);
1253 gtk_tree_path_next (path);
1260 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1262 if (gtk_tree_model_get_iter (model, &iter, path))
1264 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1269 gtk_tree_path_free (path);
1273 individual_view_search_activate_cb (GtkWidget *search,
1274 EmpathyIndividualView *view)
1277 GtkTreeViewColumn *focus_column;
1279 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1282 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1283 gtk_tree_path_free (path);
1285 gtk_widget_hide (search);
1290 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1291 EmpathyIndividualView *view)
1293 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1294 GtkTreeModel *model;
1296 gboolean valid = FALSE;
1298 /* block expand or collapse handlers, they would write the
1299 * expand or collapsed setting to file otherwise */
1300 g_signal_handlers_block_by_func (view,
1301 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1302 g_signal_handlers_block_by_func (view,
1303 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1305 /* restore which groups are expanded and which are not */
1306 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1307 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1308 valid; valid = gtk_tree_model_iter_next (model, &iter))
1314 gtk_tree_model_get (model, &iter,
1315 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1316 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1325 path = gtk_tree_model_get_path (model, &iter);
1326 if ((priv->view_features &
1327 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1328 empathy_contact_group_get_expanded (name))
1330 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1334 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1337 gtk_tree_path_free (path);
1341 /* unblock expand or collapse handlers */
1342 g_signal_handlers_unblock_by_func (view,
1343 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1344 g_signal_handlers_unblock_by_func (view,
1345 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1349 individual_view_search_show_cb (EmpathyLiveSearch *search,
1350 EmpathyIndividualView *view)
1352 /* block expand or collapse handlers during expand all, they would
1353 * write the expand or collapsed setting to file otherwise */
1354 g_signal_handlers_block_by_func (view,
1355 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1357 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1359 g_signal_handlers_unblock_by_func (view,
1360 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1364 EmpathyIndividualView *view;
1365 GtkTreeRowReference *row_ref;
1370 individual_view_expand_idle_cb (gpointer user_data)
1372 ExpandData *data = user_data;
1375 path = gtk_tree_row_reference_get_path (data->row_ref);
1379 g_signal_handlers_block_by_func (data->view,
1380 individual_view_row_expand_or_collapse_cb,
1381 GINT_TO_POINTER (data->expand));
1384 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1386 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1388 gtk_tree_path_free (path);
1390 g_signal_handlers_unblock_by_func (data->view,
1391 individual_view_row_expand_or_collapse_cb,
1392 GINT_TO_POINTER (data->expand));
1395 g_object_unref (data->view);
1396 gtk_tree_row_reference_free (data->row_ref);
1397 g_slice_free (ExpandData, data);
1403 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1406 EmpathyIndividualView *view)
1408 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1409 gboolean is_group = FALSE;
1413 gtk_tree_model_get (model, iter,
1414 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1415 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1418 if (!is_group || EMP_STR_EMPTY (name))
1424 data = g_slice_new0 (ExpandData);
1425 data->view = g_object_ref (view);
1426 data->row_ref = gtk_tree_row_reference_new (model, path);
1428 (priv->view_features &
1429 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1430 (priv->search_widget != NULL &&
1431 gtk_widget_get_visible (priv->search_widget)) ||
1432 empathy_contact_group_get_expanded (name);
1434 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1435 * gtk_tree_model_filter_refilter () */
1436 g_idle_add (individual_view_expand_idle_cb, data);
1442 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1445 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1446 GtkTreeModel *model;
1447 GtkTreePath *parent_path;
1448 GtkTreeIter parent_iter;
1450 if (gtk_tree_path_get_depth (path) < 2)
1453 /* A group row is visible if and only if at least one if its child is visible.
1454 * So when a row is inserted/deleted/changed in the base model, that could
1455 * modify the visibility of its parent in the filter model.
1458 model = GTK_TREE_MODEL (priv->store);
1459 parent_path = gtk_tree_path_copy (path);
1460 gtk_tree_path_up (parent_path);
1461 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1463 /* This tells the filter to verify the visibility of that row, and
1464 * show/hide it if necessary */
1465 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1466 parent_path, &parent_iter);
1468 gtk_tree_path_free (parent_path);
1472 individual_view_store_row_changed_cb (GtkTreeModel *model,
1475 EmpathyIndividualView *view)
1477 individual_view_verify_group_visibility (view, path);
1481 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1483 EmpathyIndividualView *view)
1485 individual_view_verify_group_visibility (view, path);
1489 individual_view_constructed (GObject *object)
1491 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1492 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1493 GtkCellRenderer *cell;
1494 GtkTreeViewColumn *col;
1497 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1498 GTK_TREE_MODEL (priv->store), NULL));
1499 gtk_tree_model_filter_set_visible_func (priv->filter,
1500 individual_view_filter_visible_func, view, NULL);
1502 g_signal_connect (priv->store, "row-has-child-toggled",
1503 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1504 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1505 GTK_TREE_MODEL (priv->filter));
1507 tp_g_signal_connect_object (priv->store, "row-changed",
1508 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1509 tp_g_signal_connect_object (priv->store, "row-inserted",
1510 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1511 tp_g_signal_connect_object (priv->store, "row-deleted",
1512 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1514 tp_g_signal_connect_object (priv->store, "row-changed",
1515 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1516 tp_g_signal_connect_object (priv->store, "row-inserted",
1517 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1518 tp_g_signal_connect_object (priv->store, "row-deleted",
1519 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1522 /* Setting reorderable is a hack that gets us row previews as drag icons
1523 for free. We override all the drag handlers. It's tricky to get the
1524 position of the drag icon right in drag_begin. GtkTreeView has special
1525 voodoo for it, so we let it do the voodoo that he do.
1528 "headers-visible", FALSE,
1529 "reorderable", TRUE,
1530 "show-expanders", FALSE,
1533 col = gtk_tree_view_column_new ();
1536 cell = gtk_cell_renderer_pixbuf_new ();
1537 gtk_tree_view_column_pack_start (col, cell, FALSE);
1538 gtk_tree_view_column_set_cell_data_func (col, cell,
1539 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1549 cell = gtk_cell_renderer_pixbuf_new ();
1550 gtk_tree_view_column_pack_start (col, cell, FALSE);
1551 gtk_tree_view_column_set_cell_data_func (col, cell,
1552 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1564 cell = empathy_cell_renderer_text_new ();
1565 gtk_tree_view_column_pack_start (col, cell, TRUE);
1566 gtk_tree_view_column_set_cell_data_func (col, cell,
1567 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1569 gtk_tree_view_column_add_attribute (col, cell,
1570 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1571 gtk_tree_view_column_add_attribute (col, cell,
1572 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1573 gtk_tree_view_column_add_attribute (col, cell,
1574 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1575 gtk_tree_view_column_add_attribute (col, cell,
1576 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1577 gtk_tree_view_column_add_attribute (col, cell,
1578 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1579 gtk_tree_view_column_add_attribute (col, cell,
1580 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1582 /* Audio Call Icon */
1583 cell = empathy_cell_renderer_activatable_new ();
1584 gtk_tree_view_column_pack_start (col, cell, FALSE);
1585 gtk_tree_view_column_set_cell_data_func (col, cell,
1586 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1589 g_object_set (cell, "visible", FALSE, NULL);
1591 g_signal_connect (cell, "path-activated",
1592 G_CALLBACK (individual_view_call_activated_cb), view);
1595 cell = gtk_cell_renderer_pixbuf_new ();
1596 gtk_tree_view_column_pack_start (col, cell, FALSE);
1597 gtk_tree_view_column_set_cell_data_func (col, cell,
1598 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1610 cell = empathy_cell_renderer_expander_new ();
1611 gtk_tree_view_column_pack_end (col, cell, FALSE);
1612 gtk_tree_view_column_set_cell_data_func (col, cell,
1613 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1616 /* Actually add the column now we have added all cell renderers */
1617 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1620 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1622 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1625 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1627 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1633 individual_view_set_view_features (EmpathyIndividualView *view,
1634 EmpathyIndividualFeatureFlags features)
1636 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1637 gboolean has_tooltip;
1639 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1641 priv->view_features = features;
1643 /* Update DnD source/dest */
1644 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1646 gtk_drag_source_set (GTK_WIDGET (view),
1649 G_N_ELEMENTS (drag_types_source),
1650 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1654 gtk_drag_source_unset (GTK_WIDGET (view));
1658 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1660 gtk_drag_dest_set (GTK_WIDGET (view),
1661 GTK_DEST_DEFAULT_ALL,
1663 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1667 /* FIXME: URI could still be droped depending on FT feature */
1668 gtk_drag_dest_unset (GTK_WIDGET (view));
1671 /* Update has-tooltip */
1673 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1674 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1678 individual_view_dispose (GObject *object)
1680 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1681 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1683 if (priv->store != NULL)
1685 g_object_unref (priv->store);
1688 if (priv->filter != NULL)
1690 g_object_unref (priv->filter);
1691 priv->filter = NULL;
1693 if (priv->tooltip_widget != NULL)
1695 gtk_widget_destroy (priv->tooltip_widget);
1696 priv->tooltip_widget = NULL;
1698 if (priv->file_targets != NULL)
1700 gtk_target_list_unref (priv->file_targets);
1701 priv->file_targets = NULL;
1704 empathy_individual_view_set_live_search (view, NULL);
1706 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1710 individual_view_get_property (GObject *object,
1715 EmpathyIndividualViewPriv *priv;
1717 priv = GET_PRIV (object);
1722 g_value_set_object (value, priv->store);
1724 case PROP_VIEW_FEATURES:
1725 g_value_set_flags (value, priv->view_features);
1727 case PROP_INDIVIDUAL_FEATURES:
1728 g_value_set_flags (value, priv->individual_features);
1731 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1737 individual_view_set_property (GObject *object,
1739 const GValue *value,
1742 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1743 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1748 priv->store = g_value_dup_object (value);
1750 case PROP_VIEW_FEATURES:
1751 individual_view_set_view_features (view, g_value_get_flags (value));
1753 case PROP_INDIVIDUAL_FEATURES:
1754 priv->individual_features = g_value_get_flags (value);
1757 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1763 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1765 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1766 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1767 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1769 object_class->constructed = individual_view_constructed;
1770 object_class->dispose = individual_view_dispose;
1771 object_class->get_property = individual_view_get_property;
1772 object_class->set_property = individual_view_set_property;
1774 widget_class->drag_data_received = individual_view_drag_data_received;
1775 widget_class->drag_drop = individual_view_drag_drop;
1776 widget_class->drag_begin = individual_view_drag_begin;
1777 widget_class->drag_data_get = individual_view_drag_data_get;
1778 widget_class->drag_end = individual_view_drag_end;
1779 widget_class->drag_motion = individual_view_drag_motion;
1781 /* We use the class method to let user of this widget to connect to
1782 * the signal and stop emission of the signal so the default handler
1783 * won't be called. */
1784 tree_view_class->row_activated = individual_view_row_activated;
1786 signals[DRAG_CONTACT_RECEIVED] =
1787 g_signal_new ("drag-contact-received",
1788 G_OBJECT_CLASS_TYPE (klass),
1792 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1793 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1795 g_object_class_install_property (object_class,
1797 g_param_spec_object ("store",
1798 "The store of the view",
1799 "The store of the view",
1800 EMPATHY_TYPE_INDIVIDUAL_STORE,
1801 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1802 g_object_class_install_property (object_class,
1804 g_param_spec_flags ("view-features",
1805 "Features of the view",
1806 "Flags for all enabled features",
1807 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1808 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1809 g_object_class_install_property (object_class,
1810 PROP_INDIVIDUAL_FEATURES,
1811 g_param_spec_flags ("individual-features",
1812 "Features of the contact menu",
1813 "Flags for all enabled features for the menu",
1814 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1815 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1817 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1821 empathy_individual_view_init (EmpathyIndividualView *view)
1823 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1824 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1827 /* Get saved group states. */
1828 empathy_contact_groups_get_all ();
1830 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1831 empathy_individual_store_row_separator_func, NULL, NULL);
1833 /* Set up drag target lists. */
1834 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1835 G_N_ELEMENTS (drag_types_dest_file));
1837 /* Connect to tree view signals rather than override. */
1838 g_signal_connect (view, "button-press-event",
1839 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1840 g_signal_connect (view, "key-press-event",
1841 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1842 g_signal_connect (view, "row-expanded",
1843 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1844 GINT_TO_POINTER (TRUE));
1845 g_signal_connect (view, "row-collapsed",
1846 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1847 GINT_TO_POINTER (FALSE));
1848 g_signal_connect (view, "query-tooltip",
1849 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1852 EmpathyIndividualView *
1853 empathy_individual_view_new (EmpathyIndividualStore *store,
1854 EmpathyIndividualViewFeatureFlags view_features,
1855 EmpathyIndividualFeatureFlags individual_features)
1857 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1859 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1861 "individual-features", individual_features,
1862 "view-features", view_features, NULL);
1866 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1868 EmpathyIndividualViewPriv *priv;
1869 GtkTreeSelection *selection;
1871 GtkTreeModel *model;
1872 FolksIndividual *individual;
1874 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1876 priv = GET_PRIV (view);
1878 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1879 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1884 gtk_tree_model_get (model, &iter,
1885 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1890 EmpathyIndividualManagerFlags
1891 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1893 EmpathyIndividualViewPriv *priv;
1894 GtkTreeSelection *selection;
1896 GtkTreeModel *model;
1897 EmpathyIndividualFeatureFlags flags;
1899 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1901 priv = GET_PRIV (view);
1903 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1904 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1909 gtk_tree_model_get (model, &iter,
1910 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1916 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1917 gboolean *is_fake_group)
1919 EmpathyIndividualViewPriv *priv;
1920 GtkTreeSelection *selection;
1922 GtkTreeModel *model;
1927 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1929 priv = GET_PRIV (view);
1931 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1932 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1937 gtk_tree_model_get (model, &iter,
1938 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1939 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1940 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1948 if (is_fake_group != NULL)
1949 *is_fake_group = fake;
1955 individual_view_remove_dialog_show (GtkWindow *parent,
1956 const gchar *message,
1957 const gchar *secondary_text)
1962 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1963 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1964 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1965 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1966 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1967 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1968 "%s", secondary_text);
1970 gtk_widget_show (dialog);
1972 res = gtk_dialog_run (GTK_DIALOG (dialog));
1973 gtk_widget_destroy (dialog);
1975 return (res == GTK_RESPONSE_YES);
1979 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1980 EmpathyIndividualView *view)
1984 group = empathy_individual_view_get_selected_group (view, NULL);
1991 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1993 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1994 if (individual_view_remove_dialog_show (parent, _("Removing group"),
1997 /* TODO: implement */
1998 DEBUG ("removing group unimplemented");
2008 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2010 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2015 gboolean is_fake_group;
2017 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2019 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2020 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2025 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2026 if (!group || is_fake_group)
2028 /* We can't alter fake groups */
2032 menu = gtk_menu_new ();
2035 if (priv->view_features &
2036 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2037 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2038 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2039 gtk_widget_show (item);
2040 g_signal_connect (item, "activate",
2041 G_CALLBACK (individual_view_group_rename_activate_cb),
2046 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2048 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2049 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2050 GTK_ICON_SIZE_MENU);
2051 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2052 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2053 gtk_widget_show (item);
2054 g_signal_connect (item, "activate",
2055 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2064 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2065 EmpathyIndividualView *view)
2067 FolksIndividual *individual;
2069 individual = empathy_individual_view_dup_selected (view);
2076 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2079 ("Do you really want to remove the contact '%s'?"),
2080 folks_individual_get_alias (individual));
2081 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2084 EmpathyIndividualManager *manager;
2086 manager = empathy_individual_manager_dup_singleton ();
2087 empathy_individual_manager_remove (manager, individual, "");
2088 g_object_unref (G_OBJECT (manager));
2092 g_object_unref (individual);
2097 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2099 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2100 FolksIndividual *individual;
2101 GtkWidget *menu = NULL;
2104 EmpathyIndividualManagerFlags flags;
2106 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2108 individual = empathy_individual_view_dup_selected (view);
2113 flags = empathy_individual_view_get_flags (view);
2115 menu = empathy_individual_menu_new (individual, priv->individual_features);
2117 /* Remove contact */
2118 if (priv->view_features &
2119 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2120 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2123 /* create the menu if required, or just add a separator */
2126 menu = gtk_menu_new ();
2130 item = gtk_separator_menu_item_new ();
2131 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2132 gtk_widget_show (item);
2136 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2137 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2138 GTK_ICON_SIZE_MENU);
2139 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2140 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2141 gtk_widget_show (item);
2142 g_signal_connect (item, "activate",
2143 G_CALLBACK (individual_view_remove_activate_cb), view);
2146 g_object_unref (individual);
2152 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2153 EmpathyLiveSearch *search)
2155 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2157 /* remove old handlers if old search was not null */
2158 if (priv->search_widget != NULL)
2160 g_signal_handlers_disconnect_by_func (view,
2161 individual_view_start_search_cb, NULL);
2163 g_signal_handlers_disconnect_by_func (priv->search_widget,
2164 individual_view_search_text_notify_cb, view);
2165 g_signal_handlers_disconnect_by_func (priv->search_widget,
2166 individual_view_search_activate_cb, view);
2167 g_signal_handlers_disconnect_by_func (priv->search_widget,
2168 individual_view_search_hide_cb, view);
2169 g_signal_handlers_disconnect_by_func (priv->search_widget,
2170 individual_view_search_show_cb, view);
2171 g_object_unref (priv->search_widget);
2172 priv->search_widget = NULL;
2175 /* connect handlers if new search is not null */
2178 priv->search_widget = g_object_ref (search);
2180 g_signal_connect (view, "start-interactive-search",
2181 G_CALLBACK (individual_view_start_search_cb), NULL);
2183 g_signal_connect (priv->search_widget, "notify::text",
2184 G_CALLBACK (individual_view_search_text_notify_cb), view);
2185 g_signal_connect (priv->search_widget, "activate",
2186 G_CALLBACK (individual_view_search_activate_cb), view);
2187 g_signal_connect (priv->search_widget, "hide",
2188 G_CALLBACK (individual_view_search_hide_cb), view);
2189 g_signal_connect (priv->search_widget, "show",
2190 G_CALLBACK (individual_view_search_show_cb), view);