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;
477 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
479 gtk_tree_model_get_iter (model, &iter, path);
480 gtk_tree_model_get (model, &iter,
481 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
487 /* TODO: implement this */
488 DEBUG ("file transfer not implemented");
490 g_object_unref (individual);
496 individual_view_drag_data_received (GtkWidget *view,
497 GdkDragContext *context,
500 GtkSelectionData *selection,
506 GtkTreeViewDropPosition position;
508 gboolean success = TRUE;
510 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
512 /* Get destination group information. */
513 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
514 x, y, &path, &position);
519 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
520 || info == DND_DRAG_TYPE_STRING)
522 success = individual_view_contact_drag_received (view,
523 context, model, path, selection);
525 else if (info == DND_DRAG_TYPE_URI_LIST)
527 success = individual_view_file_drag_received (view,
528 context, model, path, selection);
531 gtk_tree_path_free (path);
532 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
536 individual_view_drag_motion_cb (DragMotionData *data)
538 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
540 data->timeout_id = 0;
546 individual_view_drag_motion (GtkWidget *widget,
547 GdkDragContext *context,
552 EmpathyIndividualViewPriv *priv;
556 static DragMotionData *dm = NULL;
559 gboolean is_different = FALSE;
560 gboolean cleanup = TRUE;
561 gboolean retval = TRUE;
563 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
564 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
566 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
567 x, y, &path, NULL, NULL, NULL);
573 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
574 is_different = (!dm || (dm
575 && gtk_tree_path_compare (dm->path, path) != 0));
584 /* Coordinates don't point to an actual row, so make sure the pointer
585 and highlighting don't indicate that a drag is possible.
587 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
588 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
591 target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
592 gtk_tree_model_get_iter (model, &iter, path);
594 if (target == GDK_NONE)
596 /* If target == GDK_NONE, then we don't have a target that can be
597 dropped on a contact. This means a contact drag. If we're
598 pointing to a group, highlight it. Otherwise, if the contact
599 we're pointing to is in a group, highlight that. Otherwise,
600 set the drag position to before the first row for a drag into
601 the "non-group" at the top.
603 GtkTreeIter group_iter;
605 GtkTreePath *group_path;
606 gtk_tree_model_get (model, &iter,
607 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
614 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
615 gtk_tree_model_get (model, &group_iter,
616 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
620 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
621 group_path = gtk_tree_model_get_path (model, &group_iter);
622 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
623 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
624 gtk_tree_path_free (group_path);
628 group_path = gtk_tree_path_new_first ();
629 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
630 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
631 group_path, GTK_TREE_VIEW_DROP_BEFORE);
636 /* This is a file drag, and it can only be dropped on contacts,
639 FolksIndividual *individual;
640 gtk_tree_model_get (model, &iter,
641 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
642 if (individual != NULL &&
643 folks_individual_is_online (individual) &&
644 (folks_individual_get_capabilities (individual) &
645 FOLKS_CAPABILITIES_FLAGS_FILE_TRANSFER))
647 gdk_drag_status (context, GDK_ACTION_COPY, time_);
648 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
649 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
650 g_object_unref (individual);
654 gdk_drag_status (context, 0, time_);
655 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
660 if (!is_different && !cleanup)
667 gtk_tree_path_free (dm->path);
670 g_source_remove (dm->timeout_id);
678 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
680 dm = g_new0 (DragMotionData, 1);
682 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
683 dm->path = gtk_tree_path_copy (path);
685 dm->timeout_id = g_timeout_add_seconds (1,
686 (GSourceFunc) individual_view_drag_motion_cb, dm);
693 individual_view_drag_begin (GtkWidget *widget,
694 GdkDragContext *context)
696 EmpathyIndividualViewPriv *priv;
697 GtkTreeSelection *selection;
702 priv = GET_PRIV (widget);
704 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
707 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
708 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
713 path = gtk_tree_model_get_path (model, &iter);
714 priv->drag_row = gtk_tree_row_reference_new (model, path);
715 gtk_tree_path_free (path);
719 individual_view_drag_data_get (GtkWidget *widget,
720 GdkDragContext *context,
721 GtkSelectionData *selection,
725 EmpathyIndividualViewPriv *priv;
726 GtkTreePath *src_path;
729 FolksIndividual *individual;
730 const gchar *individual_id;
732 priv = GET_PRIV (widget);
734 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
740 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
746 if (!gtk_tree_model_get_iter (model, &iter, src_path))
748 gtk_tree_path_free (src_path);
752 gtk_tree_path_free (src_path);
755 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
761 individual_id = folks_individual_get_id (individual);
765 case DND_DRAG_TYPE_INDIVIDUAL_ID:
766 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
767 (guchar *) individual_id, strlen (individual_id) + 1);
773 individual_view_drag_end (GtkWidget *widget,
774 GdkDragContext *context)
776 EmpathyIndividualViewPriv *priv;
778 priv = GET_PRIV (widget);
780 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
785 gtk_tree_row_reference_free (priv->drag_row);
786 priv->drag_row = NULL;
791 individual_view_drag_drop (GtkWidget *widget,
792 GdkDragContext *drag_context,
802 EmpathyIndividualView *view;
808 individual_view_popup_menu_idle_cb (gpointer user_data)
810 MenuPopupData *data = user_data;
813 menu = empathy_individual_view_get_individual_menu (data->view);
815 menu = empathy_individual_view_get_group_menu (data->view);
819 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
820 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
822 gtk_widget_show (menu);
823 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
825 g_object_ref_sink (menu);
826 g_object_unref (menu);
829 g_slice_free (MenuPopupData, data);
835 individual_view_button_press_event_cb (EmpathyIndividualView *view,
836 GdkEventButton *event,
839 if (event->button == 3)
843 data = g_slice_new (MenuPopupData);
845 data->button = event->button;
846 data->time = event->time;
847 g_idle_add (individual_view_popup_menu_idle_cb, data);
854 individual_view_key_press_event_cb (EmpathyIndividualView *view,
858 if (event->keyval == GDK_Menu)
862 data = g_slice_new (MenuPopupData);
865 data->time = event->time;
866 g_idle_add (individual_view_popup_menu_idle_cb, data);
873 individual_view_row_activated (GtkTreeView *view,
875 GtkTreeViewColumn *column)
877 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
878 FolksIndividual *individual;
879 EmpathyContact *contact = NULL;
883 if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
888 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
889 gtk_tree_model_get_iter (model, &iter, path);
890 gtk_tree_model_get (model, &iter,
891 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
896 contact = empathy_contact_from_folks_individual (individual);
900 DEBUG ("Starting a chat");
902 empathy_dispatcher_chat_with_contact (contact,
903 gtk_get_current_event_time (), NULL, NULL);
906 g_object_unref (individual);
910 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
911 const gchar *path_string,
912 EmpathyIndividualView *view)
917 FolksIndividual *individual;
918 GdkEventButton *event;
922 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
923 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
926 gtk_tree_model_get (model, &iter,
927 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
928 if (individual == NULL)
931 event = (GdkEventButton *) gtk_get_current_event ();
933 menu = gtk_menu_new ();
934 shell = GTK_MENU_SHELL (menu);
937 item = empathy_individual_audio_call_menu_item_new (individual);
938 gtk_menu_shell_append (shell, item);
939 gtk_widget_show (item);
942 item = empathy_individual_video_call_menu_item_new (individual);
943 gtk_menu_shell_append (shell, item);
944 gtk_widget_show (item);
946 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
947 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
948 gtk_widget_show (menu);
949 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
950 event->button, event->time);
951 g_object_ref_sink (menu);
952 g_object_unref (menu);
954 g_object_unref (individual);
958 individual_view_cell_set_background (EmpathyIndividualView *view,
959 GtkCellRenderer *cell,
966 style = gtk_widget_get_style (GTK_WIDGET (view));
968 if (!is_group && is_active)
970 color = style->bg[GTK_STATE_SELECTED];
972 /* Here we take the current theme colour and add it to
973 * the colour for white and average the two. This
974 * gives a colour which is inline with the theme but
977 color.red = (color.red + (style->white).red) / 2;
978 color.green = (color.green + (style->white).green) / 2;
979 color.blue = (color.blue + (style->white).blue) / 2;
981 g_object_set (cell, "cell-background-gdk", &color, NULL);
985 g_object_set (cell, "cell-background-gdk", NULL, NULL);
990 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
991 GtkCellRenderer *cell,
994 EmpathyIndividualView *view)
1000 gtk_tree_model_get (model, iter,
1001 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1002 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1003 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1006 "visible", !is_group,
1012 g_object_unref (pixbuf);
1015 individual_view_cell_set_background (view, cell, is_group, is_active);
1019 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1020 GtkCellRenderer *cell,
1021 GtkTreeModel *model,
1023 EmpathyIndividualView *view)
1025 GdkPixbuf *pixbuf = NULL;
1029 gtk_tree_model_get (model, iter,
1030 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1031 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1036 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1038 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1039 GTK_ICON_SIZE_MENU);
1041 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1043 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1044 GTK_ICON_SIZE_MENU);
1049 "visible", pixbuf != NULL,
1054 g_object_unref (pixbuf);
1060 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1061 GtkCellRenderer *cell,
1062 GtkTreeModel *model,
1064 EmpathyIndividualView *view)
1068 gboolean can_audio, can_video;
1070 gtk_tree_model_get (model, iter,
1071 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1072 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1073 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1074 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1077 "visible", !is_group && (can_audio || can_video),
1078 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1081 individual_view_cell_set_background (view, cell, is_group, is_active);
1085 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1086 GtkCellRenderer *cell,
1087 GtkTreeModel *model,
1089 EmpathyIndividualView *view)
1092 gboolean show_avatar;
1096 gtk_tree_model_get (model, iter,
1097 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1098 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1099 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1100 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1103 "visible", !is_group && show_avatar,
1109 g_object_unref (pixbuf);
1112 individual_view_cell_set_background (view, cell, is_group, is_active);
1116 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1117 GtkCellRenderer *cell,
1118 GtkTreeModel *model,
1120 EmpathyIndividualView *view)
1125 gtk_tree_model_get (model, iter,
1126 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1127 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1129 individual_view_cell_set_background (view, cell, is_group, is_active);
1133 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1134 GtkCellRenderer *cell,
1135 GtkTreeModel *model,
1137 EmpathyIndividualView *view)
1142 gtk_tree_model_get (model, iter,
1143 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1144 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1146 if (gtk_tree_model_iter_has_child (model, iter))
1149 gboolean row_expanded;
1151 path = gtk_tree_model_get_path (model, iter);
1153 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1154 (gtk_tree_view_column_get_tree_view (column)), path);
1155 gtk_tree_path_free (path);
1160 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1165 g_object_set (cell, "visible", FALSE, NULL);
1168 individual_view_cell_set_background (view, cell, is_group, is_active);
1172 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1177 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1178 GtkTreeModel *model;
1182 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1185 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1187 gtk_tree_model_get (model, iter,
1188 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1190 expanded = GPOINTER_TO_INT (user_data);
1191 empathy_contact_group_set_expanded (name, expanded);
1197 individual_view_start_search_cb (EmpathyIndividualView *view,
1200 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1202 if (priv->search_widget == NULL)
1205 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1206 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1208 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1214 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1216 EmpathyIndividualView *view)
1218 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1220 GtkTreeViewColumn *focus_column;
1221 GtkTreeModel *model;
1223 gboolean set_cursor = FALSE;
1225 gtk_tree_model_filter_refilter (priv->filter);
1227 /* Set cursor on the first contact. If it is already set on a group,
1228 * set it on its first child contact. Note that first child of a group
1229 * is its separator, that's why we actually set to the 2nd
1232 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1233 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1237 path = gtk_tree_path_new_from_string ("0:1");
1240 else if (gtk_tree_path_get_depth (path) < 2)
1244 gtk_tree_model_get_iter (model, &iter, path);
1245 gtk_tree_model_get (model, &iter,
1246 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1251 gtk_tree_path_down (path);
1252 gtk_tree_path_next (path);
1259 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1261 if (gtk_tree_model_get_iter (model, &iter, path))
1263 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1268 gtk_tree_path_free (path);
1272 individual_view_search_activate_cb (GtkWidget *search,
1273 EmpathyIndividualView *view)
1276 GtkTreeViewColumn *focus_column;
1278 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1281 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1282 gtk_tree_path_free (path);
1284 gtk_widget_hide (search);
1289 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1290 EmpathyIndividualView *view)
1292 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1293 GtkTreeModel *model;
1295 gboolean valid = FALSE;
1297 /* block expand or collapse handlers, they would write the
1298 * expand or collapsed setting to file otherwise */
1299 g_signal_handlers_block_by_func (view,
1300 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1301 g_signal_handlers_block_by_func (view,
1302 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1304 /* restore which groups are expanded and which are not */
1305 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1306 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1307 valid; valid = gtk_tree_model_iter_next (model, &iter))
1313 gtk_tree_model_get (model, &iter,
1314 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1315 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1324 path = gtk_tree_model_get_path (model, &iter);
1325 if ((priv->view_features &
1326 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1327 empathy_contact_group_get_expanded (name))
1329 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1333 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1336 gtk_tree_path_free (path);
1340 /* unblock expand or collapse handlers */
1341 g_signal_handlers_unblock_by_func (view,
1342 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1343 g_signal_handlers_unblock_by_func (view,
1344 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1348 individual_view_search_show_cb (EmpathyLiveSearch *search,
1349 EmpathyIndividualView *view)
1351 /* block expand or collapse handlers during expand all, they would
1352 * write the expand or collapsed setting to file otherwise */
1353 g_signal_handlers_block_by_func (view,
1354 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1356 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1358 g_signal_handlers_unblock_by_func (view,
1359 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1363 EmpathyIndividualView *view;
1364 GtkTreeRowReference *row_ref;
1369 individual_view_expand_idle_cb (gpointer user_data)
1371 ExpandData *data = user_data;
1374 path = gtk_tree_row_reference_get_path (data->row_ref);
1378 g_signal_handlers_block_by_func (data->view,
1379 individual_view_row_expand_or_collapse_cb,
1380 GINT_TO_POINTER (data->expand));
1383 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1385 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1387 gtk_tree_path_free (path);
1389 g_signal_handlers_unblock_by_func (data->view,
1390 individual_view_row_expand_or_collapse_cb,
1391 GINT_TO_POINTER (data->expand));
1394 g_object_unref (data->view);
1395 gtk_tree_row_reference_free (data->row_ref);
1396 g_slice_free (ExpandData, data);
1402 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1405 EmpathyIndividualView *view)
1407 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1408 gboolean is_group = FALSE;
1412 gtk_tree_model_get (model, iter,
1413 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1414 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1417 if (!is_group || EMP_STR_EMPTY (name))
1423 data = g_slice_new0 (ExpandData);
1424 data->view = g_object_ref (view);
1425 data->row_ref = gtk_tree_row_reference_new (model, path);
1427 (priv->view_features &
1428 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1429 (priv->search_widget != NULL &&
1430 gtk_widget_get_visible (priv->search_widget)) ||
1431 empathy_contact_group_get_expanded (name);
1433 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1434 * gtk_tree_model_filter_refilter () */
1435 g_idle_add (individual_view_expand_idle_cb, data);
1441 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1444 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1445 GtkTreeModel *model;
1446 GtkTreePath *parent_path;
1447 GtkTreeIter parent_iter;
1449 if (gtk_tree_path_get_depth (path) < 2)
1452 /* A group row is visible if and only if at least one if its child is visible.
1453 * So when a row is inserted/deleted/changed in the base model, that could
1454 * modify the visibility of its parent in the filter model.
1457 model = GTK_TREE_MODEL (priv->store);
1458 parent_path = gtk_tree_path_copy (path);
1459 gtk_tree_path_up (parent_path);
1460 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1462 /* This tells the filter to verify the visibility of that row, and
1463 * show/hide it if necessary */
1464 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1465 parent_path, &parent_iter);
1467 gtk_tree_path_free (parent_path);
1471 individual_view_store_row_changed_cb (GtkTreeModel *model,
1474 EmpathyIndividualView *view)
1476 individual_view_verify_group_visibility (view, path);
1480 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1482 EmpathyIndividualView *view)
1484 individual_view_verify_group_visibility (view, path);
1488 individual_view_constructed (GObject *object)
1490 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1491 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);
1515 /* Setting reorderable is a hack that gets us row previews as drag icons
1516 for free. We override all the drag handlers. It's tricky to get the
1517 position of the drag icon right in drag_begin. GtkTreeView has special
1518 voodoo for it, so we let it do the voodoo that he do.
1521 "headers-visible", FALSE,
1522 "reorderable", TRUE,
1523 "show-expanders", FALSE,
1526 col = gtk_tree_view_column_new ();
1529 cell = gtk_cell_renderer_pixbuf_new ();
1530 gtk_tree_view_column_pack_start (col, cell, FALSE);
1531 gtk_tree_view_column_set_cell_data_func (col, cell,
1532 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1542 cell = gtk_cell_renderer_pixbuf_new ();
1543 gtk_tree_view_column_pack_start (col, cell, FALSE);
1544 gtk_tree_view_column_set_cell_data_func (col, cell,
1545 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1557 cell = empathy_cell_renderer_text_new ();
1558 gtk_tree_view_column_pack_start (col, cell, TRUE);
1559 gtk_tree_view_column_set_cell_data_func (col, cell,
1560 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1562 gtk_tree_view_column_add_attribute (col, cell,
1563 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1564 gtk_tree_view_column_add_attribute (col, cell,
1565 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1566 gtk_tree_view_column_add_attribute (col, cell,
1567 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1568 gtk_tree_view_column_add_attribute (col, cell,
1569 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1570 gtk_tree_view_column_add_attribute (col, cell,
1571 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1572 gtk_tree_view_column_add_attribute (col, cell,
1573 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1575 /* Audio Call Icon */
1576 cell = empathy_cell_renderer_activatable_new ();
1577 gtk_tree_view_column_pack_start (col, cell, FALSE);
1578 gtk_tree_view_column_set_cell_data_func (col, cell,
1579 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1582 g_object_set (cell, "visible", FALSE, NULL);
1584 g_signal_connect (cell, "path-activated",
1585 G_CALLBACK (individual_view_call_activated_cb), view);
1588 cell = gtk_cell_renderer_pixbuf_new ();
1589 gtk_tree_view_column_pack_start (col, cell, FALSE);
1590 gtk_tree_view_column_set_cell_data_func (col, cell,
1591 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1603 cell = empathy_cell_renderer_expander_new ();
1604 gtk_tree_view_column_pack_end (col, cell, FALSE);
1605 gtk_tree_view_column_set_cell_data_func (col, cell,
1606 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1609 /* Actually add the column now we have added all cell renderers */
1610 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1613 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1615 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1618 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1620 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1626 individual_view_set_view_features (EmpathyIndividualView *view,
1627 EmpathyIndividualFeatureFlags features)
1629 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1630 gboolean has_tooltip;
1632 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1634 priv->view_features = features;
1636 /* Update DnD source/dest */
1637 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1639 gtk_drag_source_set (GTK_WIDGET (view),
1642 G_N_ELEMENTS (drag_types_source),
1643 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1647 gtk_drag_source_unset (GTK_WIDGET (view));
1651 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1653 gtk_drag_dest_set (GTK_WIDGET (view),
1654 GTK_DEST_DEFAULT_ALL,
1656 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1660 /* FIXME: URI could still be droped depending on FT feature */
1661 gtk_drag_dest_unset (GTK_WIDGET (view));
1664 /* Update has-tooltip */
1666 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1667 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1671 individual_view_dispose (GObject *object)
1673 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1674 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1676 if (priv->store != NULL)
1678 g_object_unref (priv->store);
1681 if (priv->filter != NULL)
1683 g_object_unref (priv->filter);
1684 priv->filter = NULL;
1686 if (priv->tooltip_widget != NULL)
1688 gtk_widget_destroy (priv->tooltip_widget);
1689 priv->tooltip_widget = NULL;
1691 if (priv->file_targets != NULL)
1693 gtk_target_list_unref (priv->file_targets);
1694 priv->file_targets = NULL;
1697 empathy_individual_view_set_live_search (view, NULL);
1699 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1703 individual_view_get_property (GObject *object,
1708 EmpathyIndividualViewPriv *priv;
1710 priv = GET_PRIV (object);
1715 g_value_set_object (value, priv->store);
1717 case PROP_VIEW_FEATURES:
1718 g_value_set_flags (value, priv->view_features);
1720 case PROP_INDIVIDUAL_FEATURES:
1721 g_value_set_flags (value, priv->individual_features);
1724 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1730 individual_view_set_property (GObject *object,
1732 const GValue *value,
1735 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1736 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1741 priv->store = g_value_dup_object (value);
1743 case PROP_VIEW_FEATURES:
1744 individual_view_set_view_features (view, g_value_get_flags (value));
1746 case PROP_INDIVIDUAL_FEATURES:
1747 priv->individual_features = g_value_get_flags (value);
1750 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1756 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1758 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1759 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1760 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1762 object_class->constructed = individual_view_constructed;
1763 object_class->dispose = individual_view_dispose;
1764 object_class->get_property = individual_view_get_property;
1765 object_class->set_property = individual_view_set_property;
1767 widget_class->drag_data_received = individual_view_drag_data_received;
1768 widget_class->drag_drop = individual_view_drag_drop;
1769 widget_class->drag_begin = individual_view_drag_begin;
1770 widget_class->drag_data_get = individual_view_drag_data_get;
1771 widget_class->drag_end = individual_view_drag_end;
1772 widget_class->drag_motion = individual_view_drag_motion;
1774 /* We use the class method to let user of this widget to connect to
1775 * the signal and stop emission of the signal so the default handler
1776 * won't be called. */
1777 tree_view_class->row_activated = individual_view_row_activated;
1779 signals[DRAG_CONTACT_RECEIVED] =
1780 g_signal_new ("drag-contact-received",
1781 G_OBJECT_CLASS_TYPE (klass),
1785 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1786 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1788 g_object_class_install_property (object_class,
1790 g_param_spec_object ("store",
1791 "The store of the view",
1792 "The store of the view",
1793 EMPATHY_TYPE_INDIVIDUAL_STORE,
1794 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1795 g_object_class_install_property (object_class,
1797 g_param_spec_flags ("view-features",
1798 "Features of the view",
1799 "Flags for all enabled features",
1800 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1801 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1802 g_object_class_install_property (object_class,
1803 PROP_INDIVIDUAL_FEATURES,
1804 g_param_spec_flags ("individual-features",
1805 "Features of the contact menu",
1806 "Flags for all enabled features for the menu",
1807 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1808 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1810 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1814 empathy_individual_view_init (EmpathyIndividualView *view)
1816 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1817 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1820 /* Get saved group states. */
1821 empathy_contact_groups_get_all ();
1823 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1824 empathy_individual_store_row_separator_func, NULL, NULL);
1826 /* Set up drag target lists. */
1827 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1828 G_N_ELEMENTS (drag_types_dest_file));
1830 /* Connect to tree view signals rather than override. */
1831 g_signal_connect (view, "button-press-event",
1832 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1833 g_signal_connect (view, "key-press-event",
1834 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1835 g_signal_connect (view, "row-expanded",
1836 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1837 GINT_TO_POINTER (TRUE));
1838 g_signal_connect (view, "row-collapsed",
1839 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1840 GINT_TO_POINTER (FALSE));
1841 g_signal_connect (view, "query-tooltip",
1842 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1845 EmpathyIndividualView *
1846 empathy_individual_view_new (EmpathyIndividualStore *store,
1847 EmpathyIndividualViewFeatureFlags view_features,
1848 EmpathyIndividualFeatureFlags individual_features)
1850 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1852 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1854 "individual-features", individual_features,
1855 "view-features", view_features, NULL);
1859 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1861 EmpathyIndividualViewPriv *priv;
1862 GtkTreeSelection *selection;
1864 GtkTreeModel *model;
1865 FolksIndividual *individual;
1867 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1869 priv = GET_PRIV (view);
1871 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1872 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1877 gtk_tree_model_get (model, &iter,
1878 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1883 EmpathyIndividualManagerFlags
1884 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1886 EmpathyIndividualViewPriv *priv;
1887 GtkTreeSelection *selection;
1889 GtkTreeModel *model;
1890 EmpathyIndividualFeatureFlags flags;
1892 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1894 priv = GET_PRIV (view);
1896 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1897 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1902 gtk_tree_model_get (model, &iter,
1903 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1909 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1910 gboolean *is_fake_group)
1912 EmpathyIndividualViewPriv *priv;
1913 GtkTreeSelection *selection;
1915 GtkTreeModel *model;
1920 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1922 priv = GET_PRIV (view);
1924 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1925 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1930 gtk_tree_model_get (model, &iter,
1931 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1932 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1933 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1941 if (is_fake_group != NULL)
1942 *is_fake_group = fake;
1948 individual_view_remove_dialog_show (GtkWindow *parent,
1949 const gchar *message,
1950 const gchar *secondary_text)
1955 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1956 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1957 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1958 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1959 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1960 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1961 "%s", secondary_text);
1963 gtk_widget_show (dialog);
1965 res = gtk_dialog_run (GTK_DIALOG (dialog));
1966 gtk_widget_destroy (dialog);
1968 return (res == GTK_RESPONSE_YES);
1972 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1973 EmpathyIndividualView *view)
1977 group = empathy_individual_view_get_selected_group (view, NULL);
1984 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1986 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1987 if (individual_view_remove_dialog_show (parent, _("Removing group"),
1990 /* TODO: implement */
1991 DEBUG ("removing group unimplemented");
2001 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2003 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2008 gboolean is_fake_group;
2010 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2012 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2013 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2018 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2019 if (!group || is_fake_group)
2021 /* We can't alter fake groups */
2025 menu = gtk_menu_new ();
2028 if (priv->view_features &
2029 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2030 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2031 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2032 gtk_widget_show (item);
2033 g_signal_connect (item, "activate",
2034 G_CALLBACK (individual_view_group_rename_activate_cb),
2039 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2041 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2042 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2043 GTK_ICON_SIZE_MENU);
2044 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2045 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2046 gtk_widget_show (item);
2047 g_signal_connect (item, "activate",
2048 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2057 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2058 EmpathyIndividualView *view)
2060 FolksIndividual *individual;
2062 individual = empathy_individual_view_dup_selected (view);
2069 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2072 ("Do you really want to remove the contact '%s'?"),
2073 folks_individual_get_alias (individual));
2074 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2077 EmpathyIndividualManager *manager;
2079 manager = empathy_individual_manager_dup_singleton ();
2080 empathy_individual_manager_remove (manager, individual, "");
2081 g_object_unref (G_OBJECT (manager));
2085 g_object_unref (individual);
2090 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2092 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2093 FolksIndividual *individual;
2094 GtkWidget *menu = NULL;
2097 EmpathyIndividualManagerFlags flags;
2099 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2101 individual = empathy_individual_view_dup_selected (view);
2106 flags = empathy_individual_view_get_flags (view);
2108 menu = empathy_individual_menu_new (individual, priv->individual_features);
2110 /* Remove contact */
2111 if (priv->view_features &
2112 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2113 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2116 /* create the menu if required, or just add a separator */
2119 menu = gtk_menu_new ();
2123 item = gtk_separator_menu_item_new ();
2124 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2125 gtk_widget_show (item);
2129 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2130 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2131 GTK_ICON_SIZE_MENU);
2132 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2133 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2134 gtk_widget_show (item);
2135 g_signal_connect (item, "activate",
2136 G_CALLBACK (individual_view_remove_activate_cb), view);
2139 g_object_unref (individual);
2145 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2146 EmpathyLiveSearch *search)
2148 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2150 /* remove old handlers if old search was not null */
2151 if (priv->search_widget != NULL)
2153 g_signal_handlers_disconnect_by_func (view,
2154 individual_view_start_search_cb, NULL);
2156 g_signal_handlers_disconnect_by_func (priv->search_widget,
2157 individual_view_search_text_notify_cb, view);
2158 g_signal_handlers_disconnect_by_func (priv->search_widget,
2159 individual_view_search_activate_cb, view);
2160 g_signal_handlers_disconnect_by_func (priv->search_widget,
2161 individual_view_search_hide_cb, view);
2162 g_signal_handlers_disconnect_by_func (priv->search_widget,
2163 individual_view_search_show_cb, view);
2164 g_object_unref (priv->search_widget);
2165 priv->search_widget = NULL;
2168 /* connect handlers if new search is not null */
2171 priv->search_widget = g_object_ref (search);
2173 g_signal_connect (view, "start-interactive-search",
2174 G_CALLBACK (individual_view_start_search_cb), NULL);
2176 g_signal_connect (priv->search_widget, "notify::text",
2177 G_CALLBACK (individual_view_search_text_notify_cb), view);
2178 g_signal_connect (priv->search_widget, "activate",
2179 G_CALLBACK (individual_view_search_activate_cb), view);
2180 g_signal_connect (priv->search_widget, "hide",
2181 G_CALLBACK (individual_view_search_hide_cb), view);
2182 g_signal_connect (priv->search_widget, "show",
2183 G_CALLBACK (individual_view_search_show_cb), view);