Add an INDIVIDUAL_CALL feature to EmpathyIndividualView
[empathy-mirror.git] / libempathy-gtk / empathy-individual-view.c
blobd18fb2a1088d1cd452dac20e6967ee59e03d6b86
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
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>
27 #include "config.h"
29 #include <string.h>
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
38 #include <folks/folks.h>
39 #include <folks/folks-telepathy.h>
41 #include <libempathy/empathy-call-factory.h>
42 #include <libempathy/empathy-individual-manager.h>
43 #include <libempathy/empathy-contact-groups.h>
44 #include <libempathy/empathy-dispatcher.h>
45 #include <libempathy/empathy-utils.h>
47 #include "empathy-individual-view.h"
48 #include "empathy-individual-menu.h"
49 #include "empathy-individual-store.h"
50 #include "empathy-images.h"
51 #include "empathy-cell-renderer-expander.h"
52 #include "empathy-cell-renderer-text.h"
53 #include "empathy-cell-renderer-activatable.h"
54 #include "empathy-ui-utils.h"
55 #include "empathy-gtk-enum-types.h"
56 #include "empathy-gtk-marshal.h"
58 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
59 #include <libempathy/empathy-debug.h>
61 /* Active users are those which have recently changed state
62 * (e.g. online, offline or from normal to a busy state).
65 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
66 typedef struct
68 EmpathyIndividualStore *store;
69 GtkTreeRowReference *drag_row;
70 EmpathyIndividualViewFeatureFlags view_features;
71 EmpathyIndividualFeatureFlags individual_features;
72 GtkWidget *tooltip_widget;
74 gboolean show_offline;
75 gboolean show_untrusted;
77 GtkTreeModelFilter *filter;
78 GtkWidget *search_widget;
80 guint expand_groups_idle_handler;
81 /* owned string (group name) -> bool (whether to expand/contract) */
82 GHashTable *expand_groups;
83 } EmpathyIndividualViewPriv;
85 typedef struct
87 EmpathyIndividualView *view;
88 GtkTreePath *path;
89 guint timeout_id;
90 } DragMotionData;
92 typedef struct
94 EmpathyIndividualView *view;
95 FolksIndividual *individual;
96 gboolean remove;
97 } ShowActiveData;
99 enum
101 PROP_0,
102 PROP_STORE,
103 PROP_VIEW_FEATURES,
104 PROP_INDIVIDUAL_FEATURES,
105 PROP_SHOW_OFFLINE,
106 PROP_SHOW_UNTRUSTED,
109 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
110 * specific EmpathyContacts (between/in/out of Individuals) */
111 enum DndDragType
113 DND_DRAG_TYPE_INDIVIDUAL_ID,
114 DND_DRAG_TYPE_PERSONA_ID,
115 DND_DRAG_TYPE_URI_LIST,
116 DND_DRAG_TYPE_STRING,
119 #define DRAG_TYPE(T,I) \
120 { (gchar *) T, 0, I }
122 static const GtkTargetEntry drag_types_dest[] = {
123 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
124 DRAG_TYPE ("text/persona-id", DND_DRAG_TYPE_PERSONA_ID),
125 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
126 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
127 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
128 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
131 static const GtkTargetEntry drag_types_source[] = {
132 DRAG_TYPE ("text/individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
135 #undef DRAG_TYPE
137 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
138 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
140 enum
142 DRAG_INDIVIDUAL_RECEIVED,
143 DRAG_PERSONA_RECEIVED,
144 LAST_SIGNAL
147 static guint signals[LAST_SIGNAL];
149 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
150 GTK_TYPE_TREE_VIEW);
152 static void
153 individual_view_tooltip_destroy_cb (GtkWidget *widget,
154 EmpathyIndividualView *view)
156 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
158 if (priv->tooltip_widget != NULL)
160 DEBUG ("Tooltip destroyed");
161 tp_clear_object (&priv->tooltip_widget);
165 static gboolean
166 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
167 gint x,
168 gint y,
169 gboolean keyboard_mode,
170 GtkTooltip *tooltip,
171 gpointer user_data)
173 EmpathyIndividualViewPriv *priv;
174 FolksIndividual *individual;
175 GtkTreeModel *model;
176 GtkTreeIter iter;
177 GtkTreePath *path;
178 static gint running = 0;
179 gboolean ret = FALSE;
181 priv = GET_PRIV (view);
183 /* Avoid an infinite loop. See GNOME bug #574377 */
184 if (running > 0)
185 return FALSE;
187 running++;
189 /* Don't show the tooltip if there's already a popup menu */
190 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
191 goto OUT;
193 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
194 keyboard_mode, &model, &path, &iter))
195 goto OUT;
197 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
198 gtk_tree_path_free (path);
200 gtk_tree_model_get (model, &iter,
201 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
202 -1);
203 if (individual == NULL)
204 goto OUT;
206 if (priv->tooltip_widget == NULL)
208 priv->tooltip_widget = empathy_individual_widget_new (individual,
209 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
210 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION);
211 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
212 g_object_ref (priv->tooltip_widget);
213 g_signal_connect (priv->tooltip_widget, "destroy",
214 G_CALLBACK (individual_view_tooltip_destroy_cb), view);
215 gtk_widget_show (priv->tooltip_widget);
217 else
219 empathy_individual_widget_set_individual (
220 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
223 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
224 ret = TRUE;
226 g_object_unref (individual);
227 OUT:
228 running--;
230 return ret;
233 static void
234 groups_change_group_cb (GObject *source,
235 GAsyncResult *result,
236 gpointer user_data)
238 FolksGroups *groups = FOLKS_GROUPS (source);
239 GError *error = NULL;
241 folks_groups_change_group_finish (groups, result, &error);
242 if (error != NULL)
244 g_warning ("failed to change group: %s", error->message);
245 g_clear_error (&error);
249 static gboolean
250 group_can_be_modified (const gchar *name,
251 gboolean is_fake_group,
252 gboolean adding)
254 /* Real groups can always be modified */
255 if (!is_fake_group)
256 return TRUE;
258 /* The favorite fake group can be modified so users can
259 * add/remove favorites using DnD */
260 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
261 return TRUE;
263 /* We can remove contacts from the 'ungrouped' fake group */
264 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
265 return TRUE;
267 return FALSE;
270 static gboolean
271 individual_view_individual_drag_received (GtkWidget *self,
272 GdkDragContext *context,
273 GtkTreeModel *model,
274 GtkTreePath *path,
275 GtkSelectionData *selection)
277 EmpathyIndividualViewPriv *priv;
278 EmpathyIndividualManager *manager = NULL;
279 FolksIndividual *individual;
280 GtkTreePath *source_path;
281 const gchar *sel_data;
282 gchar *new_group = NULL;
283 gchar *old_group = NULL;
284 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
286 priv = GET_PRIV (self);
288 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
289 new_group = empathy_individual_store_get_parent_group (model, path,
290 NULL, &new_group_is_fake);
292 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
293 goto finished;
295 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
296 * feature. Otherwise, we just add the dropped contact to whichever group
297 * they were dropped in, and don't remove them from their old group. This
298 * allows for Individual views which shouldn't allow Individuals to have
299 * their groups changed, and also for dragging Individuals between Individual
300 * views. */
301 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
302 priv->drag_row != NULL)
304 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
305 if (source_path)
307 old_group =
308 empathy_individual_store_get_parent_group (model, source_path,
309 NULL, &old_group_is_fake);
310 gtk_tree_path_free (source_path);
313 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
314 goto finished;
316 if (!tp_strdiff (old_group, new_group))
317 goto finished;
319 else if (priv->drag_row != NULL)
321 /* We don't allow changing Individuals' groups, and this Individual was
322 * dragged from another group in *this* Individual view, so we disallow
323 * the drop. */
324 goto finished;
327 /* XXX: for contacts, we used to ensure the account, create the contact
328 * factory, and then wait on the contacts. But they should already be
329 * created by this point */
331 manager = empathy_individual_manager_dup_singleton ();
332 individual = empathy_individual_manager_lookup_member (manager, sel_data);
334 if (individual == NULL)
336 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
337 goto finished;
340 /* FIXME: We should probably wait for the cb before calling
341 * gtk_drag_finish */
343 /* Emit a signal notifying of the drag. We change the Individual's groups in
344 * the default signal handler. */
345 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
346 gdk_drag_context_get_selected_action (context), individual, new_group,
347 old_group);
349 retval = TRUE;
351 finished:
352 tp_clear_object (&manager);
353 g_free (old_group);
354 g_free (new_group);
356 return retval;
359 static void
360 real_drag_individual_received_cb (EmpathyIndividualView *self,
361 GdkDragAction action,
362 FolksIndividual *individual,
363 const gchar *new_group,
364 const gchar *old_group)
366 DEBUG ("individual %s dragged from '%s' to '%s'",
367 folks_individual_get_id (individual), old_group, new_group);
369 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
371 /* Mark contact as favourite */
372 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
373 return;
376 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
378 /* Remove contact as favourite */
379 folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
381 /* Don't try to remove it */
382 old_group = NULL;
385 if (new_group != NULL)
387 folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE,
388 groups_change_group_cb, NULL);
391 if (old_group != NULL && action == GDK_ACTION_MOVE)
393 folks_groups_change_group (FOLKS_GROUPS (individual), old_group,
394 FALSE, groups_change_group_cb, NULL);
398 static gboolean
399 individual_view_persona_drag_received (GtkWidget *self,
400 GdkDragContext *context,
401 GtkTreeModel *model,
402 GtkTreePath *path,
403 GtkSelectionData *selection)
405 EmpathyIndividualViewPriv *priv;
406 EmpathyIndividualManager *manager = NULL;
407 FolksIndividual *individual = NULL;
408 FolksPersona *persona = NULL;
409 const gchar *persona_uid;
410 GList *individuals, *l;
411 gboolean retval = FALSE;
413 priv = GET_PRIV (self);
415 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
417 /* FIXME: This is slow, but the only way to find the Persona we're having
418 * dropped on us. */
419 manager = empathy_individual_manager_dup_singleton ();
420 individuals = empathy_individual_manager_get_members (manager);
422 for (l = individuals; l != NULL; l = l->next)
424 GList *personas, *p;
426 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
428 for (p = personas; p != NULL; p = p->next)
430 if (!tp_strdiff (folks_persona_get_uid (FOLKS_PERSONA (p->data)),
431 persona_uid))
433 persona = g_object_ref (p->data);
434 individual = g_object_ref (l->data);
435 goto got_persona;
440 got_persona:
441 g_list_free (individuals);
443 if (persona == NULL || individual == NULL)
445 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
447 else
449 /* Emit a signal notifying of the drag. We change the Individual's groups in
450 * the default signal handler. */
451 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
452 gdk_drag_context_get_selected_action (context), persona, individual,
453 &retval);
456 tp_clear_object (&manager);
457 tp_clear_object (&persona);
458 tp_clear_object (&individual);
460 return retval;
463 static gboolean
464 individual_view_file_drag_received (GtkWidget *view,
465 GdkDragContext *context,
466 GtkTreeModel *model,
467 GtkTreePath *path,
468 GtkSelectionData *selection)
470 GtkTreeIter iter;
471 const gchar *sel_data;
472 FolksIndividual *individual;
473 EmpathyContact *contact;
475 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
477 gtk_tree_model_get_iter (model, &iter, path);
478 gtk_tree_model_get (model, &iter,
479 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
480 if (individual == NULL)
481 return FALSE;
483 contact = empathy_contact_dup_from_folks_individual (individual);
484 empathy_send_file_from_uri_list (contact, sel_data);
486 g_object_unref (individual);
487 tp_clear_object (&contact);
489 return TRUE;
492 static void
493 individual_view_drag_data_received (GtkWidget *view,
494 GdkDragContext *context,
495 gint x,
496 gint y,
497 GtkSelectionData *selection,
498 guint info,
499 guint time_)
501 GtkTreeModel *model;
502 gboolean is_row;
503 GtkTreeViewDropPosition position;
504 GtkTreePath *path;
505 gboolean success = TRUE;
507 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
509 /* Get destination group information. */
510 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
511 x, y, &path, &position);
512 if (!is_row)
514 success = FALSE;
516 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
518 success = individual_view_individual_drag_received (view,
519 context, model, path, selection);
521 else if (info == DND_DRAG_TYPE_PERSONA_ID)
523 success = individual_view_persona_drag_received (view, context, model,
524 path, selection);
526 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
528 success = individual_view_file_drag_received (view,
529 context, model, path, selection);
532 gtk_tree_path_free (path);
533 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
536 static gboolean
537 individual_view_drag_motion_cb (DragMotionData *data)
539 if (data->view != NULL)
541 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
542 g_object_remove_weak_pointer (G_OBJECT (data->view),
543 (gpointer *) &data->view);
546 data->timeout_id = 0;
548 return FALSE;
551 static gboolean
552 individual_view_drag_motion (GtkWidget *widget,
553 GdkDragContext *context,
554 gint x,
555 gint y,
556 guint time_)
558 EmpathyIndividualViewPriv *priv;
559 GtkTreeModel *model;
560 GdkAtom target;
561 GtkTreeIter iter;
562 static DragMotionData *dm = NULL;
563 GtkTreePath *path;
564 gboolean is_row;
565 gboolean is_different = FALSE;
566 gboolean cleanup = TRUE;
567 gboolean retval = TRUE;
569 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
570 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
572 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
573 x, y, &path, NULL, NULL, NULL);
575 cleanup &= (dm == NULL);
577 if (is_row)
579 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
580 is_different = ((dm == NULL) || ((dm != NULL)
581 && gtk_tree_path_compare (dm->path, path) != 0));
583 else
584 cleanup &= FALSE;
586 if (path == NULL)
588 /* Coordinates don't point to an actual row, so make sure the pointer
589 and highlighting don't indicate that a drag is possible.
591 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
592 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
593 return FALSE;
595 target = gtk_drag_dest_find_target (widget, context, NULL);
596 gtk_tree_model_get_iter (model, &iter, path);
598 if (target == drag_atoms_dest[DND_DRAG_TYPE_URI_LIST] ||
599 target == drag_atoms_dest[DND_DRAG_TYPE_STRING])
601 /* This is a file drag, and it can only be dropped on contacts,
602 * not groups.
603 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
604 * even if we have a valid target. */
605 FolksIndividual *individual = NULL;
606 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
608 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
610 gtk_tree_model_get (model, &iter,
611 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
612 -1);
615 if (individual != NULL)
617 EmpathyContact *contact = NULL;
619 contact = empathy_contact_dup_from_folks_individual (individual);
620 caps = empathy_contact_get_capabilities (contact);
622 tp_clear_object (&contact);
625 if (individual != NULL &&
626 folks_individual_is_online (individual) &&
627 (caps & EMPATHY_CAPABILITIES_FT))
629 gdk_drag_status (context, GDK_ACTION_COPY, time_);
630 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
631 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
633 else
635 gdk_drag_status (context, 0, time_);
636 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
637 retval = FALSE;
640 if (individual != NULL)
641 g_object_unref (individual);
643 else if ((target == drag_atoms_dest[DND_DRAG_TYPE_INDIVIDUAL_ID] &&
644 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
645 priv->drag_row == NULL)) ||
646 (target == drag_atoms_dest[DND_DRAG_TYPE_PERSONA_ID] &&
647 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
649 /* If target != GDK_NONE, then we have a contact (individual or persona)
650 drag. If we're pointing to a group, highlight it. Otherwise, if the
651 contact we're pointing to is in a group, highlight that. Otherwise,
652 set the drag position to before the first row for a drag into
653 the "non-group" at the top.
654 If it's an Individual:
655 We only highlight things if the contact is from a different
656 Individual view, or if this Individual view has
657 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
658 which don't have FEATURE_GROUPS_CHANGE, but do have
659 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
660 If it's a Persona:
661 We only highlight things if we have FEATURE_PERSONA_DROP.
663 GtkTreeIter group_iter;
664 gboolean is_group;
665 GtkTreePath *group_path;
666 gtk_tree_model_get (model, &iter,
667 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
668 if (is_group)
670 group_iter = iter;
672 else
674 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
675 gtk_tree_model_get (model, &group_iter,
676 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
678 if (is_group)
680 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
681 group_path = gtk_tree_model_get_path (model, &group_iter);
682 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
683 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
684 gtk_tree_path_free (group_path);
686 else
688 group_path = gtk_tree_path_new_first ();
689 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
690 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
691 group_path, GTK_TREE_VIEW_DROP_BEFORE);
695 if (!is_different && !cleanup)
696 return retval;
698 if (dm)
700 gtk_tree_path_free (dm->path);
701 if (dm->timeout_id)
703 g_source_remove (dm->timeout_id);
706 g_free (dm);
708 dm = NULL;
711 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
713 dm = g_new0 (DragMotionData, 1);
715 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
716 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
717 dm->path = gtk_tree_path_copy (path);
719 dm->timeout_id = g_timeout_add_seconds (1,
720 (GSourceFunc) individual_view_drag_motion_cb, dm);
723 return retval;
726 static void
727 individual_view_drag_begin (GtkWidget *widget,
728 GdkDragContext *context)
730 EmpathyIndividualViewPriv *priv;
731 GtkTreeSelection *selection;
732 GtkTreeModel *model;
733 GtkTreePath *path;
734 GtkTreeIter iter;
736 priv = GET_PRIV (widget);
738 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
739 context);
741 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
742 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
743 return;
745 path = gtk_tree_model_get_path (model, &iter);
746 priv->drag_row = gtk_tree_row_reference_new (model, path);
747 gtk_tree_path_free (path);
750 static void
751 individual_view_drag_data_get (GtkWidget *widget,
752 GdkDragContext *context,
753 GtkSelectionData *selection,
754 guint info,
755 guint time_)
757 EmpathyIndividualViewPriv *priv;
758 GtkTreePath *src_path;
759 GtkTreeIter iter;
760 GtkTreeModel *model;
761 FolksIndividual *individual;
762 const gchar *individual_id;
764 priv = GET_PRIV (widget);
766 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
767 if (priv->drag_row == NULL)
768 return;
770 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
771 if (src_path == NULL)
772 return;
774 if (!gtk_tree_model_get_iter (model, &iter, src_path))
776 gtk_tree_path_free (src_path);
777 return;
780 gtk_tree_path_free (src_path);
782 individual =
783 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
784 if (individual == NULL)
785 return;
787 individual_id = folks_individual_get_id (individual);
789 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
791 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
792 (guchar *) individual_id, strlen (individual_id) + 1);
795 g_object_unref (individual);
798 static void
799 individual_view_drag_end (GtkWidget *widget,
800 GdkDragContext *context)
802 EmpathyIndividualViewPriv *priv;
804 priv = GET_PRIV (widget);
806 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
807 context);
809 if (priv->drag_row)
811 gtk_tree_row_reference_free (priv->drag_row);
812 priv->drag_row = NULL;
816 static gboolean
817 individual_view_drag_drop (GtkWidget *widget,
818 GdkDragContext *drag_context,
819 gint x,
820 gint y,
821 guint time_)
823 return FALSE;
826 typedef struct
828 EmpathyIndividualView *view;
829 guint button;
830 guint32 time;
831 } MenuPopupData;
833 static gboolean
834 individual_view_popup_menu_idle_cb (gpointer user_data)
836 MenuPopupData *data = user_data;
837 GtkWidget *menu;
839 menu = empathy_individual_view_get_individual_menu (data->view);
840 if (menu == NULL)
841 menu = empathy_individual_view_get_group_menu (data->view);
843 if (menu != NULL)
845 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
846 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
847 NULL);
848 gtk_widget_show (menu);
849 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
850 data->time);
851 g_object_ref_sink (menu);
852 g_object_unref (menu);
855 g_slice_free (MenuPopupData, data);
857 return FALSE;
860 static gboolean
861 individual_view_button_press_event_cb (EmpathyIndividualView *view,
862 GdkEventButton *event,
863 gpointer user_data)
865 if (event->button == 3)
867 MenuPopupData *data;
869 data = g_slice_new (MenuPopupData);
870 data->view = view;
871 data->button = event->button;
872 data->time = event->time;
873 g_idle_add (individual_view_popup_menu_idle_cb, data);
876 return FALSE;
879 static gboolean
880 individual_view_key_press_event_cb (EmpathyIndividualView *view,
881 GdkEventKey *event,
882 gpointer user_data)
884 if (event->keyval == GDK_Menu)
886 MenuPopupData *data;
888 data = g_slice_new (MenuPopupData);
889 data->view = view;
890 data->button = 0;
891 data->time = event->time;
892 g_idle_add (individual_view_popup_menu_idle_cb, data);
895 return FALSE;
898 static void
899 individual_view_row_activated (GtkTreeView *view,
900 GtkTreePath *path,
901 GtkTreeViewColumn *column)
903 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
904 FolksIndividual *individual;
905 EmpathyContact *contact = NULL;
906 FolksPresenceType best_presence = FOLKS_PRESENCE_TYPE_UNSET;
907 GtkTreeModel *model;
908 GtkTreeIter iter;
909 GList *personas, *l;
911 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
912 return;
914 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
915 gtk_tree_model_get_iter (model, &iter, path);
916 gtk_tree_model_get (model, &iter,
917 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
919 if (individual == NULL)
920 return;
922 /* Determine which Persona to chat to, by choosing the most available one. */
923 personas = folks_individual_get_personas (individual);
924 for (l = personas; l != NULL; l = l->next)
926 FolksPresenceType presence;
928 if (!TPF_IS_PERSONA (l->data))
929 continue;
931 /* Only choose the contact if it has a higher presence than our current
932 * best choice of contact. */
933 presence = folks_presence_get_presence_type (FOLKS_PRESENCE (l->data));
934 if (folks_presence_typecmp (presence, best_presence) > 0)
936 TpContact *tp_contact;
938 tp_clear_object (&contact);
939 tp_contact = tpf_persona_get_contact (TPF_PERSONA (l->data));
940 contact = empathy_contact_dup_from_tp_contact (tp_contact);
941 empathy_contact_set_persona (contact, FOLKS_PERSONA (l->data));
943 best_presence = presence;
947 if (contact != NULL)
949 DEBUG ("Starting a chat");
951 empathy_dispatcher_chat_with_contact (contact,
952 gtk_get_current_event_time ());
955 g_object_unref (individual);
956 tp_clear_object (&contact);
959 static void
960 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
961 const gchar *path_string,
962 EmpathyIndividualView *view)
964 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
965 GtkWidget *menu;
966 GtkTreeModel *model;
967 GtkTreeIter iter;
968 FolksIndividual *individual;
969 GdkEventButton *event;
970 GtkMenuShell *shell;
971 GtkWidget *item;
973 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
974 return;
976 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
977 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
978 return;
980 gtk_tree_model_get (model, &iter,
981 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
982 if (individual == NULL)
983 return;
985 event = (GdkEventButton *) gtk_get_current_event ();
987 menu = gtk_menu_new ();
988 shell = GTK_MENU_SHELL (menu);
990 /* audio */
991 item = empathy_individual_audio_call_menu_item_new (individual, NULL);
992 gtk_menu_shell_append (shell, item);
993 gtk_widget_show (item);
995 /* video */
996 item = empathy_individual_video_call_menu_item_new (individual, NULL);
997 gtk_menu_shell_append (shell, item);
998 gtk_widget_show (item);
1000 g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
1001 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
1002 gtk_widget_show (menu);
1003 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1004 event->button, event->time);
1005 g_object_ref_sink (menu);
1006 g_object_unref (menu);
1008 g_object_unref (individual);
1011 static void
1012 individual_view_cell_set_background (EmpathyIndividualView *view,
1013 GtkCellRenderer *cell,
1014 gboolean is_group,
1015 gboolean is_active)
1017 GdkColor color;
1018 GtkStyle *style;
1020 style = gtk_widget_get_style (GTK_WIDGET (view));
1022 if (!is_group && is_active)
1024 color = style->bg[GTK_STATE_SELECTED];
1026 /* Here we take the current theme colour and add it to
1027 * the colour for white and average the two. This
1028 * gives a colour which is inline with the theme but
1029 * slightly whiter.
1031 color.red = (color.red + (style->white).red) / 2;
1032 color.green = (color.green + (style->white).green) / 2;
1033 color.blue = (color.blue + (style->white).blue) / 2;
1035 g_object_set (cell, "cell-background-gdk", &color, NULL);
1037 else
1038 g_object_set (cell, "cell-background-gdk", NULL, NULL);
1041 static void
1042 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1043 GtkCellRenderer *cell,
1044 GtkTreeModel *model,
1045 GtkTreeIter *iter,
1046 EmpathyIndividualView *view)
1048 GdkPixbuf *pixbuf;
1049 gboolean is_group;
1050 gboolean is_active;
1052 gtk_tree_model_get (model, iter,
1053 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1054 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1055 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1057 g_object_set (cell,
1058 "visible", !is_group,
1059 "pixbuf", pixbuf,
1060 NULL);
1062 tp_clear_object (&pixbuf);
1064 individual_view_cell_set_background (view, cell, is_group, is_active);
1067 static void
1068 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1069 GtkCellRenderer *cell,
1070 GtkTreeModel *model,
1071 GtkTreeIter *iter,
1072 EmpathyIndividualView *view)
1074 GdkPixbuf *pixbuf = NULL;
1075 gboolean is_group;
1076 gchar *name;
1078 gtk_tree_model_get (model, iter,
1079 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1080 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1082 if (!is_group)
1083 goto out;
1085 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1087 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1088 GTK_ICON_SIZE_MENU);
1090 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1092 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1093 GTK_ICON_SIZE_MENU);
1096 out:
1097 g_object_set (cell,
1098 "visible", pixbuf != NULL,
1099 "pixbuf", pixbuf,
1100 NULL);
1102 tp_clear_object (&pixbuf);
1104 g_free (name);
1107 static void
1108 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1109 GtkCellRenderer *cell,
1110 GtkTreeModel *model,
1111 GtkTreeIter *iter,
1112 EmpathyIndividualView *view)
1114 gboolean is_group;
1115 gboolean is_active;
1116 gboolean can_audio, can_video;
1118 gtk_tree_model_get (model, iter,
1119 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1120 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1121 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1122 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1124 g_object_set (cell,
1125 "visible", !is_group && (can_audio || can_video),
1126 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1127 NULL);
1129 individual_view_cell_set_background (view, cell, is_group, is_active);
1132 static void
1133 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1134 GtkCellRenderer *cell,
1135 GtkTreeModel *model,
1136 GtkTreeIter *iter,
1137 EmpathyIndividualView *view)
1139 GdkPixbuf *pixbuf;
1140 gboolean show_avatar;
1141 gboolean is_group;
1142 gboolean is_active;
1144 gtk_tree_model_get (model, iter,
1145 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1146 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1147 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1148 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1150 g_object_set (cell,
1151 "visible", !is_group && show_avatar,
1152 "pixbuf", pixbuf,
1153 NULL);
1155 tp_clear_object (&pixbuf);
1157 individual_view_cell_set_background (view, cell, is_group, is_active);
1160 static void
1161 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1162 GtkCellRenderer *cell,
1163 GtkTreeModel *model,
1164 GtkTreeIter *iter,
1165 EmpathyIndividualView *view)
1167 gboolean is_group;
1168 gboolean is_active;
1170 gtk_tree_model_get (model, iter,
1171 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1172 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1174 individual_view_cell_set_background (view, cell, is_group, is_active);
1177 static void
1178 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1179 GtkCellRenderer *cell,
1180 GtkTreeModel *model,
1181 GtkTreeIter *iter,
1182 EmpathyIndividualView *view)
1184 gboolean is_group;
1185 gboolean is_active;
1187 gtk_tree_model_get (model, iter,
1188 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1189 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1191 if (gtk_tree_model_iter_has_child (model, iter))
1193 GtkTreePath *path;
1194 gboolean row_expanded;
1196 path = gtk_tree_model_get_path (model, iter);
1197 row_expanded =
1198 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1199 (gtk_tree_view_column_get_tree_view (column)), path);
1200 gtk_tree_path_free (path);
1202 g_object_set (cell,
1203 "visible", TRUE,
1204 "expander-style",
1205 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1206 NULL);
1208 else
1209 g_object_set (cell, "visible", FALSE, NULL);
1211 individual_view_cell_set_background (view, cell, is_group, is_active);
1214 static void
1215 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1216 GtkTreeIter *iter,
1217 GtkTreePath *path,
1218 gpointer user_data)
1220 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1221 GtkTreeModel *model;
1222 gchar *name;
1223 gboolean expanded;
1225 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1226 return;
1228 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1230 gtk_tree_model_get (model, iter,
1231 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1233 expanded = GPOINTER_TO_INT (user_data);
1234 empathy_contact_group_set_expanded (name, expanded);
1236 g_free (name);
1239 static gboolean
1240 individual_view_start_search_cb (EmpathyIndividualView *view,
1241 gpointer data)
1243 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1245 if (priv->search_widget == NULL)
1246 return FALSE;
1248 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1249 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1250 else
1251 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1253 return TRUE;
1256 static void
1257 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1258 GParamSpec *pspec,
1259 EmpathyIndividualView *view)
1261 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1262 GtkTreePath *path;
1263 GtkTreeViewColumn *focus_column;
1264 GtkTreeModel *model;
1265 GtkTreeIter iter;
1266 gboolean set_cursor = FALSE;
1268 gtk_tree_model_filter_refilter (priv->filter);
1270 /* Set cursor on the first contact. If it is already set on a group,
1271 * set it on its first child contact. Note that first child of a group
1272 * is its separator, that's why we actually set to the 2nd
1275 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1276 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1278 if (path == NULL)
1280 path = gtk_tree_path_new_from_string ("0:1");
1281 set_cursor = TRUE;
1283 else if (gtk_tree_path_get_depth (path) < 2)
1285 gboolean is_group;
1287 gtk_tree_model_get_iter (model, &iter, path);
1288 gtk_tree_model_get (model, &iter,
1289 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1290 -1);
1292 if (is_group)
1294 gtk_tree_path_down (path);
1295 gtk_tree_path_next (path);
1296 set_cursor = TRUE;
1300 if (set_cursor)
1302 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1303 * valid. */
1304 if (gtk_tree_model_get_iter (model, &iter, path))
1306 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1307 FALSE);
1311 gtk_tree_path_free (path);
1314 static void
1315 individual_view_search_activate_cb (GtkWidget *search,
1316 EmpathyIndividualView *view)
1318 GtkTreePath *path;
1319 GtkTreeViewColumn *focus_column;
1321 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1322 if (path != NULL)
1324 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1325 gtk_tree_path_free (path);
1327 gtk_widget_hide (search);
1331 static gboolean
1332 individual_view_search_key_navigation_cb (GtkWidget *search,
1333 GdkEvent *event,
1334 EmpathyIndividualView *view)
1336 GdkEventKey *eventkey = ((GdkEventKey *) event);
1337 gboolean ret = FALSE;
1339 if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down)
1341 GdkEvent *new_event;
1343 new_event = gdk_event_copy (event);
1344 gtk_widget_grab_focus (GTK_WIDGET (view));
1345 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1346 gtk_widget_grab_focus (search);
1348 gdk_event_free (new_event);
1351 return ret;
1354 static void
1355 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1356 EmpathyIndividualView *view)
1358 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1359 GtkTreeModel *model;
1360 GtkTreePath *cursor_path;
1361 GtkTreeIter iter;
1362 gboolean valid = FALSE;
1364 /* block expand or collapse handlers, they would write the
1365 * expand or collapsed setting to file otherwise */
1366 g_signal_handlers_block_by_func (view,
1367 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1368 g_signal_handlers_block_by_func (view,
1369 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1371 /* restore which groups are expanded and which are not */
1372 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1373 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1374 valid; valid = gtk_tree_model_iter_next (model, &iter))
1376 gboolean is_group;
1377 gchar *name = NULL;
1378 GtkTreePath *path;
1380 gtk_tree_model_get (model, &iter,
1381 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1382 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1383 -1);
1385 if (!is_group)
1387 g_free (name);
1388 continue;
1391 path = gtk_tree_model_get_path (model, &iter);
1392 if ((priv->view_features &
1393 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1394 empathy_contact_group_get_expanded (name))
1396 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1398 else
1400 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1403 gtk_tree_path_free (path);
1404 g_free (name);
1407 /* unblock expand or collapse handlers */
1408 g_signal_handlers_unblock_by_func (view,
1409 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1410 g_signal_handlers_unblock_by_func (view,
1411 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1413 /* keep the selected contact visible */
1414 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1416 if (cursor_path != NULL)
1417 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1418 FALSE, 0, 0);
1420 gtk_tree_path_free (cursor_path);
1423 static void
1424 individual_view_search_show_cb (EmpathyLiveSearch *search,
1425 EmpathyIndividualView *view)
1427 /* block expand or collapse handlers during expand all, they would
1428 * write the expand or collapsed setting to file otherwise */
1429 g_signal_handlers_block_by_func (view,
1430 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1432 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1434 g_signal_handlers_unblock_by_func (view,
1435 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1438 static gboolean
1439 expand_idle_foreach_cb (GtkTreeModel *model,
1440 GtkTreePath *path,
1441 GtkTreeIter *iter,
1442 EmpathyIndividualView *self)
1444 EmpathyIndividualViewPriv *priv;
1445 gboolean is_group;
1446 gpointer should_expand;
1447 gchar *name;
1449 /* We only want groups */
1450 if (gtk_tree_path_get_depth (path) > 1)
1451 return FALSE;
1453 gtk_tree_model_get (model, iter,
1454 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1455 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1456 -1);
1458 if (is_group == FALSE)
1460 g_free (name);
1461 return FALSE;
1464 priv = GET_PRIV (self);
1466 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1467 &should_expand) == TRUE)
1469 if (GPOINTER_TO_INT (should_expand) == TRUE)
1470 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1471 else
1472 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1474 g_hash_table_remove (priv->expand_groups, name);
1477 g_free (name);
1479 return FALSE;
1482 static gboolean
1483 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1485 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1487 DEBUG ("individual_view_expand_idle_cb");
1489 g_signal_handlers_block_by_func (self,
1490 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1491 g_signal_handlers_block_by_func (self,
1492 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1494 /* The store/filter could've been removed while we were in the idle queue */
1495 if (priv->filter != NULL)
1497 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1498 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1501 g_signal_handlers_unblock_by_func (self,
1502 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1503 g_signal_handlers_unblock_by_func (self,
1504 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1506 g_object_unref (self);
1507 priv->expand_groups_idle_handler = 0;
1509 return FALSE;
1512 static void
1513 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1514 GtkTreePath *path,
1515 GtkTreeIter *iter,
1516 EmpathyIndividualView *view)
1518 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1519 gboolean should_expand, is_group = FALSE;
1520 gchar *name = NULL;
1521 gpointer will_expand;
1523 gtk_tree_model_get (model, iter,
1524 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1525 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1526 -1);
1528 if (!is_group || EMP_STR_EMPTY (name))
1530 g_free (name);
1531 return;
1534 should_expand = (priv->view_features &
1535 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1536 (priv->search_widget != NULL &&
1537 gtk_widget_get_visible (priv->search_widget)) ||
1538 empathy_contact_group_get_expanded (name);
1540 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1541 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1542 * a hash table, and expand or contract them as appropriate all at once in
1543 * an idle handler which iterates over all the group rows. */
1544 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1545 &will_expand) == FALSE ||
1546 GPOINTER_TO_INT (will_expand) != should_expand)
1548 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1549 GINT_TO_POINTER (should_expand));
1551 if (priv->expand_groups_idle_handler == 0)
1553 priv->expand_groups_idle_handler =
1554 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1555 g_object_ref (view));
1559 g_free (name);
1562 /* FIXME: This is a workaround for bgo#621076 */
1563 static void
1564 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1565 GtkTreePath *path)
1567 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1568 GtkTreeModel *model;
1569 GtkTreePath *parent_path;
1570 GtkTreeIter parent_iter;
1572 if (gtk_tree_path_get_depth (path) < 2)
1573 return;
1575 /* A group row is visible if and only if at least one if its child is visible.
1576 * So when a row is inserted/deleted/changed in the base model, that could
1577 * modify the visibility of its parent in the filter model.
1580 model = GTK_TREE_MODEL (priv->store);
1581 parent_path = gtk_tree_path_copy (path);
1582 gtk_tree_path_up (parent_path);
1583 if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1585 /* This tells the filter to verify the visibility of that row, and
1586 * show/hide it if necessary */
1587 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1588 parent_path, &parent_iter);
1590 gtk_tree_path_free (parent_path);
1593 static void
1594 individual_view_store_row_changed_cb (GtkTreeModel *model,
1595 GtkTreePath *path,
1596 GtkTreeIter *iter,
1597 EmpathyIndividualView *view)
1599 individual_view_verify_group_visibility (view, path);
1602 static void
1603 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1604 GtkTreePath *path,
1605 EmpathyIndividualView *view)
1607 individual_view_verify_group_visibility (view, path);
1610 static gboolean
1611 individual_view_is_visible_individual (EmpathyIndividualView *self,
1612 FolksIndividual *individual,
1613 gboolean is_online,
1614 gboolean is_searching)
1616 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1617 EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
1618 const gchar *str;
1619 GList *personas, *l;
1621 /* We're only giving the visibility wrt filtering here, not things like
1622 * presence. */
1623 if (priv->show_untrusted == FALSE &&
1624 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1626 return FALSE;
1629 if (is_searching == FALSE)
1630 return (priv->show_offline || is_online);
1632 /* check alias name */
1633 str = folks_individual_get_alias (individual);
1635 if (empathy_live_search_match (live, str))
1636 return TRUE;
1638 /* check contact id, remove the @server.com part */
1639 personas = folks_individual_get_personas (individual);
1640 for (l = personas; l; l = l->next)
1642 const gchar *p;
1643 gchar *dup_str = NULL;
1644 gboolean visible;
1646 if (!TPF_IS_PERSONA (l->data))
1647 continue;
1649 str = folks_persona_get_display_id (l->data);
1650 p = strstr (str, "@");
1651 if (p != NULL)
1652 str = dup_str = g_strndup (str, p - str);
1654 visible = empathy_live_search_match (live, str);
1655 g_free (dup_str);
1656 if (visible)
1657 return TRUE;
1660 /* FIXME: Add more rules here, we could check phone numbers in
1661 * contact's vCard for example. */
1663 return FALSE;
1666 static gboolean
1667 individual_view_filter_visible_func (GtkTreeModel *model,
1668 GtkTreeIter *iter,
1669 gpointer user_data)
1671 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1672 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1673 FolksIndividual *individual = NULL;
1674 gboolean is_group, is_separator, valid;
1675 GtkTreeIter child_iter;
1676 gboolean visible, is_online;
1677 gboolean is_searching = TRUE;
1679 if (priv->search_widget == NULL ||
1680 !gtk_widget_get_visible (priv->search_widget))
1681 is_searching = FALSE;
1683 gtk_tree_model_get (model, iter,
1684 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1685 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1686 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1687 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1688 -1);
1690 if (individual != NULL)
1692 visible = individual_view_is_visible_individual (self, individual,
1693 is_online, is_searching);
1695 g_object_unref (individual);
1697 /* FIXME: Work around bgo#626552/bgo#621076 */
1698 if (visible == TRUE)
1700 GtkTreePath *path = gtk_tree_model_get_path (model, iter);
1701 individual_view_verify_group_visibility (self, path);
1702 gtk_tree_path_free (path);
1705 return visible;
1708 if (is_separator)
1709 return TRUE;
1711 /* Not a contact, not a separator, must be a group */
1712 g_return_val_if_fail (is_group, FALSE);
1714 /* only show groups which are not empty */
1715 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1716 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1718 gtk_tree_model_get (model, &child_iter,
1719 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1720 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1721 -1);
1723 if (individual == NULL)
1724 continue;
1726 visible = individual_view_is_visible_individual (self, individual,
1727 is_online, is_searching);
1728 g_object_unref (individual);
1730 /* show group if it has at least one visible contact in it */
1731 if (visible == TRUE)
1732 return TRUE;
1735 return FALSE;
1738 static void
1739 individual_view_constructed (GObject *object)
1741 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1742 GtkCellRenderer *cell;
1743 GtkTreeViewColumn *col;
1744 guint i;
1746 /* Setup view */
1747 g_object_set (view,
1748 "headers-visible", FALSE,
1749 "show-expanders", FALSE,
1750 NULL);
1752 col = gtk_tree_view_column_new ();
1754 /* State */
1755 cell = gtk_cell_renderer_pixbuf_new ();
1756 gtk_tree_view_column_pack_start (col, cell, FALSE);
1757 gtk_tree_view_column_set_cell_data_func (col, cell,
1758 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1759 view, NULL);
1761 g_object_set (cell,
1762 "xpad", 5,
1763 "ypad", 1,
1764 "visible", FALSE,
1765 NULL);
1767 /* Group icon */
1768 cell = gtk_cell_renderer_pixbuf_new ();
1769 gtk_tree_view_column_pack_start (col, cell, FALSE);
1770 gtk_tree_view_column_set_cell_data_func (col, cell,
1771 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1772 view, NULL);
1774 g_object_set (cell,
1775 "xpad", 0,
1776 "ypad", 0,
1777 "visible", FALSE,
1778 "width", 16,
1779 "height", 16,
1780 NULL);
1782 /* Name */
1783 cell = empathy_cell_renderer_text_new ();
1784 gtk_tree_view_column_pack_start (col, cell, TRUE);
1785 gtk_tree_view_column_set_cell_data_func (col, cell,
1786 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1788 gtk_tree_view_column_add_attribute (col, cell,
1789 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1790 gtk_tree_view_column_add_attribute (col, cell,
1791 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1792 gtk_tree_view_column_add_attribute (col, cell,
1793 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1794 gtk_tree_view_column_add_attribute (col, cell,
1795 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1796 gtk_tree_view_column_add_attribute (col, cell,
1797 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1798 gtk_tree_view_column_add_attribute (col, cell,
1799 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1801 /* Audio Call Icon */
1802 cell = empathy_cell_renderer_activatable_new ();
1803 gtk_tree_view_column_pack_start (col, cell, FALSE);
1804 gtk_tree_view_column_set_cell_data_func (col, cell,
1805 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1806 view, NULL);
1808 g_object_set (cell, "visible", FALSE, NULL);
1810 g_signal_connect (cell, "path-activated",
1811 G_CALLBACK (individual_view_call_activated_cb), view);
1813 /* Avatar */
1814 cell = gtk_cell_renderer_pixbuf_new ();
1815 gtk_tree_view_column_pack_start (col, cell, FALSE);
1816 gtk_tree_view_column_set_cell_data_func (col, cell,
1817 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1818 view, NULL);
1820 g_object_set (cell,
1821 "xpad", 0,
1822 "ypad", 0,
1823 "visible", FALSE,
1824 "width", 32,
1825 "height", 32,
1826 NULL);
1828 /* Expander */
1829 cell = empathy_cell_renderer_expander_new ();
1830 gtk_tree_view_column_pack_end (col, cell, FALSE);
1831 gtk_tree_view_column_set_cell_data_func (col, cell,
1832 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1833 view, NULL);
1835 /* Actually add the column now we have added all cell renderers */
1836 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1838 /* Drag & Drop. */
1839 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1841 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1844 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1846 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1847 FALSE);
1851 static void
1852 individual_view_set_view_features (EmpathyIndividualView *view,
1853 EmpathyIndividualFeatureFlags features)
1855 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1856 gboolean has_tooltip;
1858 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1860 priv->view_features = features;
1862 /* Setting reorderable is a hack that gets us row previews as drag icons
1863 for free. We override all the drag handlers. It's tricky to get the
1864 position of the drag icon right in drag_begin. GtkTreeView has special
1865 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1866 is enabled).
1868 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1869 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1871 /* Update DnD source/dest */
1872 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1874 gtk_drag_source_set (GTK_WIDGET (view),
1875 GDK_BUTTON1_MASK,
1876 drag_types_source,
1877 G_N_ELEMENTS (drag_types_source),
1878 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1880 else
1882 gtk_drag_source_unset (GTK_WIDGET (view));
1886 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
1888 gtk_drag_dest_set (GTK_WIDGET (view),
1889 GTK_DEST_DEFAULT_ALL,
1890 drag_types_dest,
1891 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1893 else
1895 /* FIXME: URI could still be droped depending on FT feature */
1896 gtk_drag_dest_unset (GTK_WIDGET (view));
1899 /* Update has-tooltip */
1900 has_tooltip =
1901 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
1902 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1905 static void
1906 individual_view_dispose (GObject *object)
1908 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1909 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1911 tp_clear_object (&priv->store);
1912 tp_clear_object (&priv->filter);
1913 tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1915 empathy_individual_view_set_live_search (view, NULL);
1917 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1920 static void
1921 individual_view_finalize (GObject *object)
1923 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1925 g_hash_table_destroy (priv->expand_groups);
1927 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
1930 static void
1931 individual_view_get_property (GObject *object,
1932 guint param_id,
1933 GValue *value,
1934 GParamSpec *pspec)
1936 EmpathyIndividualViewPriv *priv;
1938 priv = GET_PRIV (object);
1940 switch (param_id)
1942 case PROP_STORE:
1943 g_value_set_object (value, priv->store);
1944 break;
1945 case PROP_VIEW_FEATURES:
1946 g_value_set_flags (value, priv->view_features);
1947 break;
1948 case PROP_INDIVIDUAL_FEATURES:
1949 g_value_set_flags (value, priv->individual_features);
1950 break;
1951 case PROP_SHOW_OFFLINE:
1952 g_value_set_boolean (value, priv->show_offline);
1953 break;
1954 case PROP_SHOW_UNTRUSTED:
1955 g_value_set_boolean (value, priv->show_untrusted);
1956 break;
1957 default:
1958 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1959 break;
1963 static void
1964 individual_view_set_property (GObject *object,
1965 guint param_id,
1966 const GValue *value,
1967 GParamSpec *pspec)
1969 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1970 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1972 switch (param_id)
1974 case PROP_STORE:
1975 empathy_individual_view_set_store (view, g_value_get_object (value));
1976 break;
1977 case PROP_VIEW_FEATURES:
1978 individual_view_set_view_features (view, g_value_get_flags (value));
1979 break;
1980 case PROP_INDIVIDUAL_FEATURES:
1981 priv->individual_features = g_value_get_flags (value);
1982 break;
1983 case PROP_SHOW_OFFLINE:
1984 empathy_individual_view_set_show_offline (view,
1985 g_value_get_boolean (value));
1986 break;
1987 case PROP_SHOW_UNTRUSTED:
1988 empathy_individual_view_set_show_untrusted (view,
1989 g_value_get_boolean (value));
1990 break;
1991 default:
1992 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1993 break;
1997 static void
1998 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2000 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2001 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2002 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2004 object_class->constructed = individual_view_constructed;
2005 object_class->dispose = individual_view_dispose;
2006 object_class->finalize = individual_view_finalize;
2007 object_class->get_property = individual_view_get_property;
2008 object_class->set_property = individual_view_set_property;
2010 widget_class->drag_data_received = individual_view_drag_data_received;
2011 widget_class->drag_drop = individual_view_drag_drop;
2012 widget_class->drag_begin = individual_view_drag_begin;
2013 widget_class->drag_data_get = individual_view_drag_data_get;
2014 widget_class->drag_end = individual_view_drag_end;
2015 widget_class->drag_motion = individual_view_drag_motion;
2017 /* We use the class method to let user of this widget to connect to
2018 * the signal and stop emission of the signal so the default handler
2019 * won't be called. */
2020 tree_view_class->row_activated = individual_view_row_activated;
2022 klass->drag_individual_received = real_drag_individual_received_cb;
2024 signals[DRAG_INDIVIDUAL_RECEIVED] =
2025 g_signal_new ("drag-individual-received",
2026 G_OBJECT_CLASS_TYPE (klass),
2027 G_SIGNAL_RUN_LAST,
2028 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2029 NULL, NULL,
2030 _empathy_gtk_marshal_VOID__UINT_OBJECT_STRING_STRING,
2031 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2032 G_TYPE_STRING, G_TYPE_STRING);
2034 signals[DRAG_PERSONA_RECEIVED] =
2035 g_signal_new ("drag-persona-received",
2036 G_OBJECT_CLASS_TYPE (klass),
2037 G_SIGNAL_RUN_LAST,
2038 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2039 NULL, NULL,
2040 _empathy_gtk_marshal_BOOLEAN__UINT_OBJECT_OBJECT,
2041 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2043 g_object_class_install_property (object_class,
2044 PROP_STORE,
2045 g_param_spec_object ("store",
2046 "The store of the view",
2047 "The store of the view",
2048 EMPATHY_TYPE_INDIVIDUAL_STORE,
2049 G_PARAM_READWRITE));
2050 g_object_class_install_property (object_class,
2051 PROP_VIEW_FEATURES,
2052 g_param_spec_flags ("view-features",
2053 "Features of the view",
2054 "Flags for all enabled features",
2055 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2056 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2057 g_object_class_install_property (object_class,
2058 PROP_INDIVIDUAL_FEATURES,
2059 g_param_spec_flags ("individual-features",
2060 "Features of the individual menu",
2061 "Flags for all enabled features for the menu",
2062 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2063 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2064 g_object_class_install_property (object_class,
2065 PROP_SHOW_OFFLINE,
2066 g_param_spec_boolean ("show-offline",
2067 "Show Offline",
2068 "Whether contact list should display "
2069 "offline contacts", FALSE, G_PARAM_READWRITE));
2070 g_object_class_install_property (object_class,
2071 PROP_SHOW_UNTRUSTED,
2072 g_param_spec_boolean ("show-untrusted",
2073 "Show Untrusted Individuals",
2074 "Whether the view should display untrusted individuals; "
2075 "those who could not be who they say they are.",
2076 TRUE, G_PARAM_READWRITE));
2078 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2081 static void
2082 empathy_individual_view_init (EmpathyIndividualView *view)
2084 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2085 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2087 view->priv = priv;
2089 priv->show_untrusted = TRUE;
2091 /* Get saved group states. */
2092 empathy_contact_groups_get_all ();
2094 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2095 (GDestroyNotify) g_free, NULL);
2097 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2098 empathy_individual_store_row_separator_func, NULL, NULL);
2100 /* Connect to tree view signals rather than override. */
2101 g_signal_connect (view, "button-press-event",
2102 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2103 g_signal_connect (view, "key-press-event",
2104 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2105 g_signal_connect (view, "row-expanded",
2106 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2107 GINT_TO_POINTER (TRUE));
2108 g_signal_connect (view, "row-collapsed",
2109 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2110 GINT_TO_POINTER (FALSE));
2111 g_signal_connect (view, "query-tooltip",
2112 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2115 EmpathyIndividualView *
2116 empathy_individual_view_new (EmpathyIndividualStore *store,
2117 EmpathyIndividualViewFeatureFlags view_features,
2118 EmpathyIndividualFeatureFlags individual_features)
2120 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2122 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2123 "store", store,
2124 "individual-features", individual_features,
2125 "view-features", view_features, NULL);
2128 FolksIndividual *
2129 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2131 EmpathyIndividualViewPriv *priv;
2132 GtkTreeSelection *selection;
2133 GtkTreeIter iter;
2134 GtkTreeModel *model;
2135 FolksIndividual *individual;
2137 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2139 priv = GET_PRIV (view);
2141 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2142 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2143 return NULL;
2145 gtk_tree_model_get (model, &iter,
2146 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2148 return individual;
2151 EmpathyIndividualManagerFlags
2152 empathy_individual_view_get_flags (EmpathyIndividualView *view)
2154 EmpathyIndividualViewPriv *priv;
2155 GtkTreeSelection *selection;
2156 GtkTreeIter iter;
2157 GtkTreeModel *model;
2158 EmpathyIndividualFeatureFlags flags;
2160 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
2162 priv = GET_PRIV (view);
2164 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2165 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2166 return 0;
2168 gtk_tree_model_get (model, &iter,
2169 EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
2171 return flags;
2174 gchar *
2175 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
2176 gboolean *is_fake_group)
2178 EmpathyIndividualViewPriv *priv;
2179 GtkTreeSelection *selection;
2180 GtkTreeIter iter;
2181 GtkTreeModel *model;
2182 gboolean is_group;
2183 gchar *name;
2184 gboolean fake;
2186 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2188 priv = GET_PRIV (view);
2190 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2191 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2192 return NULL;
2194 gtk_tree_model_get (model, &iter,
2195 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2196 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2197 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2199 if (!is_group)
2201 g_free (name);
2202 return NULL;
2205 if (is_fake_group != NULL)
2206 *is_fake_group = fake;
2208 return name;
2211 static gboolean
2212 individual_view_remove_dialog_show (GtkWindow *parent,
2213 const gchar *message,
2214 const gchar *secondary_text)
2216 GtkWidget *dialog;
2217 gboolean res;
2219 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2220 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2221 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2222 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2223 GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
2224 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2225 "%s", secondary_text);
2227 gtk_widget_show (dialog);
2229 res = gtk_dialog_run (GTK_DIALOG (dialog));
2230 gtk_widget_destroy (dialog);
2232 return (res == GTK_RESPONSE_YES);
2235 static void
2236 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2237 EmpathyIndividualView *view)
2239 gchar *group;
2241 group = empathy_individual_view_get_selected_group (view, NULL);
2242 if (group != NULL)
2244 gchar *text;
2245 GtkWindow *parent;
2247 text =
2248 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
2249 group);
2250 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2251 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2252 text))
2254 EmpathyIndividualManager *manager =
2255 empathy_individual_manager_dup_singleton ();
2256 empathy_individual_manager_remove_group (manager, group);
2257 g_object_unref (G_OBJECT (manager));
2260 g_free (text);
2263 g_free (group);
2266 GtkWidget *
2267 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2269 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2270 gchar *group;
2271 GtkWidget *menu;
2272 GtkWidget *item;
2273 GtkWidget *image;
2274 gboolean is_fake_group;
2276 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2278 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2279 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2280 return NULL;
2282 group = empathy_individual_view_get_selected_group (view, &is_fake_group);
2283 if (!group || is_fake_group)
2285 /* We can't alter fake groups */
2286 return NULL;
2289 menu = gtk_menu_new ();
2291 /* TODO: implement
2292 if (priv->view_features &
2293 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
2294 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2295 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2296 gtk_widget_show (item);
2297 g_signal_connect (item, "activate",
2298 G_CALLBACK (individual_view_group_rename_activate_cb),
2299 view);
2303 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2305 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2306 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2307 GTK_ICON_SIZE_MENU);
2308 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2309 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2310 gtk_widget_show (item);
2311 g_signal_connect (item, "activate",
2312 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2315 g_free (group);
2317 return menu;
2320 static void
2321 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2322 EmpathyIndividualView *view)
2324 FolksIndividual *individual;
2326 individual = empathy_individual_view_dup_selected (view);
2328 if (individual != NULL)
2330 gchar *text;
2331 GtkWindow *parent;
2333 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2334 text =
2335 g_strdup_printf (_
2336 ("Do you really want to remove the contact '%s'?"),
2337 folks_individual_get_alias (individual));
2338 if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2339 text))
2341 EmpathyIndividualManager *manager;
2343 manager = empathy_individual_manager_dup_singleton ();
2344 empathy_individual_manager_remove (manager, individual, "");
2345 g_object_unref (G_OBJECT (manager));
2348 g_free (text);
2349 g_object_unref (individual);
2353 GtkWidget *
2354 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2356 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2357 FolksIndividual *individual;
2358 GtkWidget *menu = NULL;
2359 GtkWidget *item;
2360 GtkWidget *image;
2361 EmpathyIndividualManagerFlags flags;
2363 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2365 individual = empathy_individual_view_dup_selected (view);
2366 if (individual == NULL)
2367 return NULL;
2369 flags = empathy_individual_view_get_flags (view);
2371 menu = empathy_individual_menu_new (individual, priv->individual_features);
2373 /* Remove contact */
2374 if (priv->view_features &
2375 EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_REMOVE &&
2376 flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2379 /* create the menu if required, or just add a separator */
2380 if (menu == NULL)
2381 menu = gtk_menu_new ();
2382 else
2384 item = gtk_separator_menu_item_new ();
2385 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2386 gtk_widget_show (item);
2389 /* Remove */
2390 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2391 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2392 GTK_ICON_SIZE_MENU);
2393 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2394 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2395 gtk_widget_show (item);
2396 g_signal_connect (item, "activate",
2397 G_CALLBACK (individual_view_remove_activate_cb), view);
2400 g_object_unref (individual);
2402 return menu;
2405 void
2406 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2407 EmpathyLiveSearch *search)
2409 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2411 /* remove old handlers if old search was not null */
2412 if (priv->search_widget != NULL)
2414 g_signal_handlers_disconnect_by_func (view,
2415 individual_view_start_search_cb, NULL);
2417 g_signal_handlers_disconnect_by_func (priv->search_widget,
2418 individual_view_search_text_notify_cb, view);
2419 g_signal_handlers_disconnect_by_func (priv->search_widget,
2420 individual_view_search_activate_cb, view);
2421 g_signal_handlers_disconnect_by_func (priv->search_widget,
2422 individual_view_search_key_navigation_cb, view);
2423 g_signal_handlers_disconnect_by_func (priv->search_widget,
2424 individual_view_search_hide_cb, view);
2425 g_signal_handlers_disconnect_by_func (priv->search_widget,
2426 individual_view_search_show_cb, view);
2427 g_object_unref (priv->search_widget);
2428 priv->search_widget = NULL;
2431 /* connect handlers if new search is not null */
2432 if (search != NULL)
2434 priv->search_widget = g_object_ref (search);
2436 g_signal_connect (view, "start-interactive-search",
2437 G_CALLBACK (individual_view_start_search_cb), NULL);
2439 g_signal_connect (priv->search_widget, "notify::text",
2440 G_CALLBACK (individual_view_search_text_notify_cb), view);
2441 g_signal_connect (priv->search_widget, "activate",
2442 G_CALLBACK (individual_view_search_activate_cb), view);
2443 g_signal_connect (priv->search_widget, "key-navigation",
2444 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2445 g_signal_connect (priv->search_widget, "hide",
2446 G_CALLBACK (individual_view_search_hide_cb), view);
2447 g_signal_connect (priv->search_widget, "show",
2448 G_CALLBACK (individual_view_search_show_cb), view);
2452 gboolean
2453 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2455 EmpathyIndividualViewPriv *priv;
2457 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2459 priv = GET_PRIV (self);
2461 return (priv->search_widget != NULL &&
2462 gtk_widget_get_visible (priv->search_widget));
2465 gboolean
2466 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2468 EmpathyIndividualViewPriv *priv;
2470 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2472 priv = GET_PRIV (self);
2474 return priv->show_offline;
2477 void
2478 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2479 gboolean show_offline)
2481 EmpathyIndividualViewPriv *priv;
2483 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2485 priv = GET_PRIV (self);
2487 priv->show_offline = show_offline;
2489 g_object_notify (G_OBJECT (self), "show-offline");
2490 gtk_tree_model_filter_refilter (priv->filter);
2493 gboolean
2494 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2496 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2498 return GET_PRIV (self)->show_untrusted;
2501 void
2502 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2503 gboolean show_untrusted)
2505 EmpathyIndividualViewPriv *priv;
2507 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2509 priv = GET_PRIV (self);
2511 priv->show_untrusted = show_untrusted;
2513 g_object_notify (G_OBJECT (self), "show-untrusted");
2514 gtk_tree_model_filter_refilter (priv->filter);
2517 EmpathyIndividualStore *
2518 empathy_individual_view_get_store (EmpathyIndividualView *self)
2520 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2522 return GET_PRIV (self)->store;
2525 void
2526 empathy_individual_view_set_store (EmpathyIndividualView *self,
2527 EmpathyIndividualStore *store)
2529 EmpathyIndividualViewPriv *priv;
2531 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2532 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2534 priv = GET_PRIV (self);
2536 /* Destroy the old filter and remove the old store */
2537 if (priv->store != NULL)
2539 g_signal_handlers_disconnect_by_func (priv->store,
2540 individual_view_store_row_changed_cb, self);
2541 g_signal_handlers_disconnect_by_func (priv->store,
2542 individual_view_store_row_deleted_cb, self);
2544 g_signal_handlers_disconnect_by_func (priv->filter,
2545 individual_view_row_has_child_toggled_cb, self);
2547 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2550 tp_clear_object (&priv->filter);
2551 tp_clear_object (&priv->store);
2553 /* Set the new store */
2554 priv->store = store;
2556 if (store != NULL)
2558 g_object_ref (store);
2560 /* Create a new filter */
2561 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2562 GTK_TREE_MODEL (priv->store), NULL));
2563 gtk_tree_model_filter_set_visible_func (priv->filter,
2564 individual_view_filter_visible_func, self, NULL);
2566 g_signal_connect (priv->filter, "row-has-child-toggled",
2567 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2568 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2569 GTK_TREE_MODEL (priv->filter));
2571 tp_g_signal_connect_object (priv->store, "row-changed",
2572 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2573 tp_g_signal_connect_object (priv->store, "row-inserted",
2574 G_CALLBACK (individual_view_store_row_changed_cb), self, 0);
2575 tp_g_signal_connect_object (priv->store, "row-deleted",
2576 G_CALLBACK (individual_view_store_row_deleted_cb), self, 0);