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);
1492 GtkCellRenderer *cell;
1493 GtkTreeViewColumn *col;
1496 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1497 GTK_TREE_MODEL (priv->store), NULL));
1498 gtk_tree_model_filter_set_visible_func (priv->filter,
1499 individual_view_filter_visible_func, view, NULL);
1501 g_signal_connect (priv->store, "row-has-child-toggled",
1502 G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1503 gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1504 GTK_TREE_MODEL (priv->filter));
1506 tp_g_signal_connect_object (priv->store, "row-changed",
1507 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1508 tp_g_signal_connect_object (priv->store, "row-inserted",
1509 G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1510 tp_g_signal_connect_object (priv->store, "row-deleted",
1511 G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1514 /* Setting reorderable is a hack that gets us row previews as drag icons
1515 for free. We override all the drag handlers. It's tricky to get the
1516 position of the drag icon right in drag_begin. GtkTreeView has special
1517 voodoo for it, so we let it do the voodoo that he do.
1520 "headers-visible", FALSE,
1521 "reorderable", TRUE,
1522 "show-expanders", FALSE,
1525 col = gtk_tree_view_column_new ();
1528 cell = gtk_cell_renderer_pixbuf_new ();
1529 gtk_tree_view_column_pack_start (col, cell, FALSE);
1530 gtk_tree_view_column_set_cell_data_func (col, cell,
1531 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1541 cell = gtk_cell_renderer_pixbuf_new ();
1542 gtk_tree_view_column_pack_start (col, cell, FALSE);
1543 gtk_tree_view_column_set_cell_data_func (col, cell,
1544 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1556 cell = empathy_cell_renderer_text_new ();
1557 gtk_tree_view_column_pack_start (col, cell, TRUE);
1558 gtk_tree_view_column_set_cell_data_func (col, cell,
1559 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1561 gtk_tree_view_column_add_attribute (col, cell,
1562 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1563 gtk_tree_view_column_add_attribute (col, cell,
1564 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1565 gtk_tree_view_column_add_attribute (col, cell,
1566 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1567 gtk_tree_view_column_add_attribute (col, cell,
1568 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1569 gtk_tree_view_column_add_attribute (col, cell,
1570 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1571 gtk_tree_view_column_add_attribute (col, cell,
1572 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1574 /* Audio Call Icon */
1575 cell = empathy_cell_renderer_activatable_new ();
1576 gtk_tree_view_column_pack_start (col, cell, FALSE);
1577 gtk_tree_view_column_set_cell_data_func (col, cell,
1578 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1581 g_object_set (cell, "visible", FALSE, NULL);
1583 g_signal_connect (cell, "path-activated",
1584 G_CALLBACK (individual_view_call_activated_cb), view);
1587 cell = gtk_cell_renderer_pixbuf_new ();
1588 gtk_tree_view_column_pack_start (col, cell, FALSE);
1589 gtk_tree_view_column_set_cell_data_func (col, cell,
1590 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1602 cell = empathy_cell_renderer_expander_new ();
1603 gtk_tree_view_column_pack_end (col, cell, FALSE);
1604 gtk_tree_view_column_set_cell_data_func (col, cell,
1605 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1608 /* Actually add the column now we have added all cell renderers */
1609 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1612 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1614 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1617 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1619 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1625 individual_view_set_view_features (EmpathyIndividualView *view,
1626 EmpathyIndividualFeatureFlags features)
1628 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1629 gboolean has_tooltip;
1631 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1633 priv->view_features = features;
1635 /* Update DnD source/dest */
1636 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1638 gtk_drag_source_set (GTK_WIDGET (view),
1641 G_N_ELEMENTS (drag_types_source),
1642 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1646 gtk_drag_source_unset (GTK_WIDGET (view));
1650 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1652 gtk_drag_dest_set (GTK_WIDGET (view),
1653 GTK_DEST_DEFAULT_ALL,
1655 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1659 /* FIXME: URI could still be droped depending on FT feature */
1660 gtk_drag_dest_unset (GTK_WIDGET (view));
1663 /* Update has-tooltip */
1665 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1666 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1670 individual_view_dispose (GObject *object)
1672 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1673 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1675 if (priv->store != NULL)
1677 g_object_unref (priv->store);
1680 if (priv->filter != NULL)
1682 g_object_unref (priv->filter);
1683 priv->filter = NULL;
1685 if (priv->tooltip_widget != NULL)
1687 gtk_widget_destroy (priv->tooltip_widget);
1688 priv->tooltip_widget = NULL;
1690 if (priv->file_targets != NULL)
1692 gtk_target_list_unref (priv->file_targets);
1693 priv->file_targets = NULL;
1696 empathy_individual_view_set_live_search (view, NULL);
1698 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1702 individual_view_get_property (GObject *object,
1707 EmpathyIndividualViewPriv *priv;
1709 priv = GET_PRIV (object);
1714 g_value_set_object (value, priv->store);
1716 case PROP_VIEW_FEATURES:
1717 g_value_set_flags (value, priv->view_features);
1719 case PROP_INDIVIDUAL_FEATURES:
1720 g_value_set_flags (value, priv->individual_features);
1723 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1729 individual_view_set_property (GObject *object,
1731 const GValue *value,
1734 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1735 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1740 priv->store = g_value_dup_object (value);
1742 case PROP_VIEW_FEATURES:
1743 individual_view_set_view_features (view, g_value_get_flags (value));
1745 case PROP_INDIVIDUAL_FEATURES:
1746 priv->individual_features = g_value_get_flags (value);
1749 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1755 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1757 GObjectClass *object_class = G_OBJECT_CLASS (klass);
1758 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1759 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1761 object_class->constructed = individual_view_constructed;
1762 object_class->dispose = individual_view_dispose;
1763 object_class->get_property = individual_view_get_property;
1764 object_class->set_property = individual_view_set_property;
1766 widget_class->drag_data_received = individual_view_drag_data_received;
1767 widget_class->drag_drop = individual_view_drag_drop;
1768 widget_class->drag_begin = individual_view_drag_begin;
1769 widget_class->drag_data_get = individual_view_drag_data_get;
1770 widget_class->drag_end = individual_view_drag_end;
1771 widget_class->drag_motion = individual_view_drag_motion;
1773 /* We use the class method to let user of this widget to connect to
1774 * the signal and stop emission of the signal so the default handler
1775 * won't be called. */
1776 tree_view_class->row_activated = individual_view_row_activated;
1778 signals[DRAG_CONTACT_RECEIVED] =
1779 g_signal_new ("drag-contact-received",
1780 G_OBJECT_CLASS_TYPE (klass),
1784 _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1785 G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1787 g_object_class_install_property (object_class,
1789 g_param_spec_object ("store",
1790 "The store of the view",
1791 "The store of the view",
1792 EMPATHY_TYPE_INDIVIDUAL_STORE,
1793 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1794 g_object_class_install_property (object_class,
1796 g_param_spec_flags ("view-features",
1797 "Features of the view",
1798 "Flags for all enabled features",
1799 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1800 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1801 g_object_class_install_property (object_class,
1802 PROP_INDIVIDUAL_FEATURES,
1803 g_param_spec_flags ("individual-features",
1804 "Features of the contact menu",
1805 "Flags for all enabled features for the menu",
1806 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1807 EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1809 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1813 empathy_individual_view_init (EmpathyIndividualView *view)
1815 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1816 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1819 /* Get saved group states. */
1820 empathy_contact_groups_get_all ();
1822 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1823 empathy_individual_store_row_separator_func, NULL, NULL);
1825 /* Set up drag target lists. */
1826 priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1827 G_N_ELEMENTS (drag_types_dest_file));
1829 /* Connect to tree view signals rather than override. */
1830 g_signal_connect (view, "button-press-event",
1831 G_CALLBACK (individual_view_button_press_event_cb), NULL);
1832 g_signal_connect (view, "key-press-event",
1833 G_CALLBACK (individual_view_key_press_event_cb), NULL);
1834 g_signal_connect (view, "row-expanded",
1835 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1836 GINT_TO_POINTER (TRUE));
1837 g_signal_connect (view, "row-collapsed",
1838 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1839 GINT_TO_POINTER (FALSE));
1840 g_signal_connect (view, "query-tooltip",
1841 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1844 EmpathyIndividualView *
1845 empathy_individual_view_new (EmpathyIndividualStore *store,
1846 EmpathyIndividualViewFeatureFlags view_features,
1847 EmpathyIndividualFeatureFlags individual_features)
1849 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1851 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1853 "individual-features", individual_features,
1854 "view-features", view_features, NULL);
1858 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1860 EmpathyIndividualViewPriv *priv;
1861 GtkTreeSelection *selection;
1863 GtkTreeModel *model;
1864 FolksIndividual *individual;
1866 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1868 priv = GET_PRIV (view);
1870 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1871 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1876 gtk_tree_model_get (model, &iter,
1877 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1882 EmpathyIndividualManagerFlags
1883 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1885 EmpathyIndividualViewPriv *priv;
1886 GtkTreeSelection *selection;
1888 GtkTreeModel *model;
1889 EmpathyIndividualFeatureFlags flags;
1891 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1893 priv = GET_PRIV (view);
1895 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1896 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1901 gtk_tree_model_get (model, &iter,
1902 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1908 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1909 gboolean *is_fake_group)
1911 EmpathyIndividualViewPriv *priv;
1912 GtkTreeSelection *selection;
1914 GtkTreeModel *model;
1919 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1921 priv = GET_PRIV (view);
1923 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1924 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1929 gtk_tree_model_get (model, &iter,
1930 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1931 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1932 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1940 if (is_fake_group != NULL)
1941 *is_fake_group = fake;
1947 individual_view_remove_dialog_show (GtkWindow *parent,
1948 const gchar *message,
1949 const gchar *secondary_text)
1954 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1955 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1956 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1957 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1958 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1959 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1960 "%s", secondary_text);
1962 gtk_widget_show (dialog);
1964 res = gtk_dialog_run (GTK_DIALOG (dialog));
1965 gtk_widget_destroy (dialog);
1967 return (res == GTK_RESPONSE_YES);
1971 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1972 EmpathyIndividualView *view)
1976 group = empathy_individual_view_get_selected_group (view, NULL);
1983 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1985 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1986 if (individual_view_remove_dialog_show (parent, _("Removing group"),
1989 /* TODO: implement */
1990 DEBUG ("removing group unimplemented");
2000 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2002 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2007 gboolean is_fake_group;
2009 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2011 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2012 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2017 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2018 if (!group || is_fake_group)
2020 /* We can't alter fake groups */
2024 menu = gtk_menu_new ();
2027 if (priv->view_features &
2028 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2029 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2030 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2031 gtk_widget_show (item);
2032 g_signal_connect (item, "activate",
2033 G_CALLBACK (individual_view_group_rename_activate_cb),
2038 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2040 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2041 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2042 GTK_ICON_SIZE_MENU);
2043 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2044 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2045 gtk_widget_show (item);
2046 g_signal_connect (item, "activate",
2047 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2056 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2057 EmpathyIndividualView *view)
2059 FolksIndividual *individual;
2061 individual = empathy_individual_view_dup_selected (view);
2068 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2071 ("Do you really want to remove the contact '%s'?"),
2072 folks_individual_get_alias (individual));
2073 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2076 EmpathyIndividualManager *manager;
2078 manager = empathy_individual_manager_dup_singleton ();
2079 empathy_individual_manager_remove (manager, individual, "");
2080 g_object_unref (G_OBJECT (manager));
2084 g_object_unref (individual);
2089 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2091 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2092 FolksIndividual *individual;
2093 GtkWidget *menu = NULL;
2096 EmpathyIndividualManagerFlags flags;
2098 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2100 individual = empathy_individual_view_dup_selected (view);
2105 flags = empathy_individual_view_get_flags (view);
2107 menu = empathy_individual_menu_new (individual, priv->individual_features);
2109 /* Remove contact */
2110 if (priv->view_features &
2111 EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2112 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2115 /* create the menu if required, or just add a separator */
2118 menu = gtk_menu_new ();
2122 item = gtk_separator_menu_item_new ();
2123 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2124 gtk_widget_show (item);
2128 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2129 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2130 GTK_ICON_SIZE_MENU);
2131 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2132 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2133 gtk_widget_show (item);
2134 g_signal_connect (item, "activate",
2135 G_CALLBACK (individual_view_remove_activate_cb), view);
2138 g_object_unref (individual);
2144 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2145 EmpathyLiveSearch *search)
2147 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2149 /* remove old handlers if old search was not null */
2150 if (priv->search_widget != NULL)
2152 g_signal_handlers_disconnect_by_func (view,
2153 individual_view_start_search_cb, NULL);
2155 g_signal_handlers_disconnect_by_func (priv->search_widget,
2156 individual_view_search_text_notify_cb, view);
2157 g_signal_handlers_disconnect_by_func (priv->search_widget,
2158 individual_view_search_activate_cb, view);
2159 g_signal_handlers_disconnect_by_func (priv->search_widget,
2160 individual_view_search_hide_cb, view);
2161 g_signal_handlers_disconnect_by_func (priv->search_widget,
2162 individual_view_search_show_cb, view);
2163 g_object_unref (priv->search_widget);
2164 priv->search_widget = NULL;
2167 /* connect handlers if new search is not null */
2170 priv->search_widget = g_object_ref (search);
2172 g_signal_connect (view, "start-interactive-search",
2173 G_CALLBACK (individual_view_start_search_cb), NULL);
2175 g_signal_connect (priv->search_widget, "notify::text",
2176 G_CALLBACK (individual_view_search_text_notify_cb), view);
2177 g_signal_connect (priv->search_widget, "activate",
2178 G_CALLBACK (individual_view_search_activate_cb), view);
2179 g_signal_connect (priv->search_widget, "hide",
2180 G_CALLBACK (individual_view_search_hide_cb), view);
2181 g_signal_connect (priv->search_widget, "show",
2182 G_CALLBACK (individual_view_search_show_cb), view);