Updated Spanish translation
[empathy-mirror.git] / libempathy-gtk / empathy-individual-view.c
blob2945c3d443745988fd80c116dd5c4b3bda8d70ee
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"
28 #include "empathy-individual-view.h"
30 #include <glib/gi18n-lib.h>
31 #include <tp-account-widgets/tpaw-pixbuf-utils.h>
32 #include <tp-account-widgets/tpaw-utils.h>
34 #include "empathy-cell-renderer-activatable.h"
35 #include "empathy-cell-renderer-expander.h"
36 #include "empathy-cell-renderer-text.h"
37 #include "empathy-connection-aggregator.h"
38 #include "empathy-contact-groups.h"
39 #include "empathy-gtk-enum-types.h"
40 #include "empathy-images.h"
41 #include "empathy-individual-edit-dialog.h"
42 #include "empathy-individual-manager.h"
43 #include "empathy-request-util.h"
44 #include "empathy-ui-utils.h"
45 #include "empathy-utils.h"
47 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
48 #include "empathy-debug.h"
50 /* Active users are those which have recently changed state
51 * (e.g. online, offline or from normal to a busy state).
54 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
55 typedef struct
57 EmpathyIndividualStore *store;
58 GtkTreeRowReference *drag_row;
59 EmpathyIndividualViewFeatureFlags view_features;
60 EmpathyIndividualFeatureFlags individual_features;
61 GtkWidget *tooltip_widget;
63 gboolean show_offline;
64 gboolean show_untrusted;
65 gboolean show_uninteresting;
67 GtkTreeModelFilter *filter;
68 GtkWidget *search_widget;
70 guint expand_groups_idle_handler;
71 /* owned string (group name) -> bool (whether to expand/contract) */
72 GHashTable *expand_groups;
74 /* Auto scroll */
75 guint auto_scroll_timeout_id;
76 /* Distance between mouse pointer and the nearby border. Negative when
77 scrolling updards.*/
78 gint distance;
80 GtkTreeModelFilterVisibleFunc custom_filter;
81 gpointer custom_filter_data;
83 GtkCellRenderer *text_renderer;
84 } EmpathyIndividualViewPriv;
86 typedef struct
88 EmpathyIndividualView *view;
89 GtkTreePath *path;
90 guint timeout_id;
91 } DragMotionData;
93 typedef struct
95 EmpathyIndividualView *view;
96 FolksIndividual *individual;
97 gboolean remove;
98 } ShowActiveData;
100 enum
102 PROP_0,
103 PROP_STORE,
104 PROP_VIEW_FEATURES,
105 PROP_INDIVIDUAL_FEATURES,
106 PROP_SHOW_OFFLINE,
107 PROP_SHOW_UNTRUSTED,
108 PROP_SHOW_UNINTERESTING,
111 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
112 * specific EmpathyContacts (between/in/out of Individuals) */
113 typedef enum
115 DND_DRAG_TYPE_UNKNOWN = -1,
116 DND_DRAG_TYPE_INDIVIDUAL_ID = 0,
117 DND_DRAG_TYPE_PERSONA_ID,
118 DND_DRAG_TYPE_URI_LIST,
119 DND_DRAG_TYPE_STRING,
120 } DndDragType;
122 #define DRAG_TYPE(T,I) \
123 { (gchar *) T, 0, I }
125 static const GtkTargetEntry drag_types_dest[] = {
126 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
127 DRAG_TYPE ("text/x-persona-id", DND_DRAG_TYPE_PERSONA_ID),
128 DRAG_TYPE ("text/path-list", DND_DRAG_TYPE_URI_LIST),
129 DRAG_TYPE ("text/uri-list", DND_DRAG_TYPE_URI_LIST),
130 DRAG_TYPE ("text/plain", DND_DRAG_TYPE_STRING),
131 DRAG_TYPE ("STRING", DND_DRAG_TYPE_STRING),
134 static const GtkTargetEntry drag_types_source[] = {
135 DRAG_TYPE ("text/x-individual-id", DND_DRAG_TYPE_INDIVIDUAL_ID),
138 #undef DRAG_TYPE
140 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
142 enum
144 DRAG_INDIVIDUAL_RECEIVED,
145 DRAG_PERSONA_RECEIVED,
146 LAST_SIGNAL
149 static guint signals[LAST_SIGNAL];
151 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
152 GTK_TYPE_TREE_VIEW);
154 static void
155 individual_view_tooltip_destroy_cb (GtkWidget *widget,
156 EmpathyIndividualView *view)
158 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
160 tp_clear_object (&priv->tooltip_widget);
163 static gboolean
164 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
165 gint x,
166 gint y,
167 gboolean keyboard_mode,
168 GtkTooltip *tooltip,
169 gpointer user_data)
171 EmpathyIndividualViewPriv *priv;
172 FolksIndividual *individual;
173 GtkTreeModel *model;
174 GtkTreeIter iter;
175 GtkTreePath *path;
176 static gint running = 0;
177 gboolean ret = FALSE;
179 priv = GET_PRIV (view);
181 /* Avoid an infinite loop. See GNOME bug #574377 */
182 if (running > 0)
183 return FALSE;
185 running++;
187 /* Don't show the tooltip if there's already a popup menu */
188 if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
189 goto OUT;
191 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
192 keyboard_mode, &model, &path, &iter))
193 goto OUT;
195 gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
196 gtk_tree_path_free (path);
198 gtk_tree_model_get (model, &iter,
199 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
200 -1);
201 if (individual == NULL)
202 goto OUT;
204 if (priv->tooltip_widget == NULL)
206 priv->tooltip_widget = empathy_individual_widget_new (individual,
207 EMPATHY_INDIVIDUAL_WIDGET_FOR_TOOLTIP |
208 EMPATHY_INDIVIDUAL_WIDGET_SHOW_LOCATION |
209 EMPATHY_INDIVIDUAL_WIDGET_SHOW_CLIENT_TYPES);
210 gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
211 g_object_ref (priv->tooltip_widget);
213 tp_g_signal_connect_object (priv->tooltip_widget, "destroy",
214 G_CALLBACK (individual_view_tooltip_destroy_cb), view, 0);
216 gtk_widget_show (priv->tooltip_widget);
218 else
220 empathy_individual_widget_set_individual (
221 EMPATHY_INDIVIDUAL_WIDGET (priv->tooltip_widget), individual);
224 gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
225 ret = TRUE;
227 g_object_unref (individual);
228 OUT:
229 running--;
231 return ret;
234 static void
235 groups_change_group_cb (GObject *source,
236 GAsyncResult *result,
237 gpointer user_data)
239 FolksGroupDetails *group_details = FOLKS_GROUP_DETAILS (source);
240 GError *error = NULL;
242 folks_group_details_change_group_finish (group_details, result, &error);
243 if (error != NULL)
245 g_warning ("failed to change group: %s", error->message);
246 g_clear_error (&error);
250 static gboolean
251 group_can_be_modified (const gchar *name,
252 gboolean is_fake_group,
253 gboolean adding)
255 /* Real groups can always be modified */
256 if (!is_fake_group)
257 return TRUE;
259 /* The favorite fake group can be modified so users can
260 * add/remove favorites using DnD */
261 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
262 return TRUE;
264 /* We can remove contacts from the 'ungrouped' fake group */
265 if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
266 return TRUE;
268 return FALSE;
271 static gboolean
272 individual_view_individual_drag_received (GtkWidget *self,
273 GdkDragContext *context,
274 GtkTreeModel *model,
275 GtkTreePath *path,
276 GtkSelectionData *selection)
278 EmpathyIndividualViewPriv *priv;
279 EmpathyIndividualManager *manager = NULL;
280 FolksIndividual *individual;
281 GtkTreePath *source_path;
282 const gchar *sel_data;
283 gchar *new_group = NULL;
284 gchar *old_group = NULL;
285 gboolean new_group_is_fake, old_group_is_fake = TRUE, retval = FALSE;
287 priv = GET_PRIV (self);
289 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
290 new_group = empathy_individual_store_get_parent_group (model, path,
291 NULL, &new_group_is_fake);
293 if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
294 goto finished;
296 /* Get source group information iff the view has the FEATURE_GROUPS_CHANGE
297 * feature. Otherwise, we just add the dropped contact to whichever group
298 * they were dropped in, and don't remove them from their old group. This
299 * allows for Individual views which shouldn't allow Individuals to have
300 * their groups changed, and also for dragging Individuals between Individual
301 * views. */
302 if ((priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE) &&
303 priv->drag_row != NULL)
305 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
306 if (source_path)
308 old_group =
309 empathy_individual_store_get_parent_group (model, source_path,
310 NULL, &old_group_is_fake);
311 gtk_tree_path_free (source_path);
314 if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
315 goto finished;
317 if (!tp_strdiff (old_group, new_group))
318 goto finished;
320 else if (priv->drag_row != NULL)
322 /* We don't allow changing Individuals' groups, and this Individual was
323 * dragged from another group in *this* Individual view, so we disallow
324 * the drop. */
325 goto finished;
328 /* XXX: for contacts, we used to ensure the account, create the contact
329 * factory, and then wait on the contacts. But they should already be
330 * created by this point */
332 manager = empathy_individual_manager_dup_singleton ();
333 individual = empathy_individual_manager_lookup_member (manager, sel_data);
335 if (individual == NULL)
337 DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
338 goto finished;
341 /* FIXME: We should probably wait for the cb before calling
342 * gtk_drag_finish */
344 /* Emit a signal notifying of the drag. We change the Individual's groups in
345 * the default signal handler. */
346 g_signal_emit (self, signals[DRAG_INDIVIDUAL_RECEIVED], 0,
347 gdk_drag_context_get_selected_action (context), individual, new_group,
348 old_group);
350 retval = TRUE;
352 finished:
353 tp_clear_object (&manager);
354 g_free (old_group);
355 g_free (new_group);
357 return retval;
360 static void
361 real_drag_individual_received_cb (EmpathyIndividualView *self,
362 GdkDragAction action,
363 FolksIndividual *individual,
364 const gchar *new_group,
365 const gchar *old_group)
367 DEBUG ("individual %s dragged from '%s' to '%s'",
368 folks_individual_get_id (individual), old_group, new_group);
370 if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
372 /* Mark contact as favourite */
373 folks_favourite_details_set_is_favourite (
374 FOLKS_FAVOURITE_DETAILS (individual), TRUE);
375 return;
378 if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
380 /* Remove contact as favourite */
381 folks_favourite_details_set_is_favourite (
382 FOLKS_FAVOURITE_DETAILS (individual), FALSE);
384 /* Don't try to remove it */
385 old_group = NULL;
388 if (new_group != NULL)
390 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
391 new_group, TRUE, groups_change_group_cb, NULL);
394 if (old_group != NULL && action == GDK_ACTION_MOVE)
396 folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
397 old_group, FALSE, groups_change_group_cb, NULL);
401 static gboolean
402 individual_view_persona_drag_received (GtkWidget *self,
403 GdkDragContext *context,
404 GtkTreeModel *model,
405 GtkTreePath *path,
406 GtkSelectionData *selection)
408 EmpathyIndividualManager *manager = NULL;
409 FolksIndividual *individual = NULL;
410 FolksPersona *persona = NULL;
411 const gchar *persona_uid;
412 GList *individuals, *l;
413 GeeIterator *iter = NULL;
414 gboolean retval = FALSE;
416 persona_uid = (const gchar *) gtk_selection_data_get_data (selection);
418 /* FIXME: This is slow, but the only way to find the Persona we're having
419 * dropped on us. */
420 manager = empathy_individual_manager_dup_singleton ();
421 individuals = empathy_individual_manager_get_members (manager);
423 for (l = individuals; l != NULL; l = l->next)
425 GeeSet *personas;
427 personas = folks_individual_get_personas (FOLKS_INDIVIDUAL (l->data));
428 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
429 while (gee_iterator_next (iter))
431 FolksPersona *persona_cur = gee_iterator_get (iter);
433 if (!tp_strdiff (folks_persona_get_uid (persona), persona_uid))
435 /* takes ownership of the ref */
436 persona = persona_cur;
437 individual = g_object_ref (l->data);
438 goto got_persona;
440 g_clear_object (&persona_cur);
442 g_clear_object (&iter);
445 got_persona:
446 g_clear_object (&iter);
447 g_list_free (individuals);
449 if (persona == NULL || individual == NULL)
451 DEBUG ("Failed to find drag event persona with UID '%s'", persona_uid);
453 else
455 /* Emit a signal notifying of the drag. We change the Individual's groups in
456 * the default signal handler. */
457 g_signal_emit (self, signals[DRAG_PERSONA_RECEIVED], 0,
458 gdk_drag_context_get_selected_action (context), persona, individual,
459 &retval);
462 tp_clear_object (&manager);
463 tp_clear_object (&persona);
464 tp_clear_object (&individual);
466 return retval;
469 static gboolean
470 individual_view_file_drag_received (GtkWidget *view,
471 GdkDragContext *context,
472 GtkTreeModel *model,
473 GtkTreePath *path,
474 GtkSelectionData *selection)
476 GtkTreeIter iter;
477 const gchar *sel_data;
478 FolksIndividual *individual;
479 EmpathyContact *contact;
481 sel_data = (const gchar *) gtk_selection_data_get_data (selection);
483 gtk_tree_model_get_iter (model, &iter, path);
484 gtk_tree_model_get (model, &iter,
485 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
486 if (individual == NULL)
487 return FALSE;
489 contact = empathy_contact_dup_from_folks_individual (individual);
490 empathy_send_file_from_uri_list (contact, sel_data);
492 g_object_unref (individual);
493 tp_clear_object (&contact);
495 return TRUE;
498 static void
499 individual_view_drag_data_received (GtkWidget *view,
500 GdkDragContext *context,
501 gint x,
502 gint y,
503 GtkSelectionData *selection,
504 guint info,
505 guint time_)
507 GtkTreeModel *model;
508 gboolean is_row;
509 GtkTreeViewDropPosition position;
510 GtkTreePath *path;
511 gboolean success = TRUE;
513 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
515 /* Get destination group information. */
516 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
517 x, y, &path, &position);
518 if (!is_row)
520 success = FALSE;
522 else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
524 success = individual_view_individual_drag_received (view,
525 context, model, path, selection);
527 else if (info == DND_DRAG_TYPE_PERSONA_ID)
529 success = individual_view_persona_drag_received (view, context, model,
530 path, selection);
532 else if (info == DND_DRAG_TYPE_URI_LIST || info == DND_DRAG_TYPE_STRING)
534 success = individual_view_file_drag_received (view,
535 context, model, path, selection);
538 gtk_tree_path_free (path);
539 gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
542 static gboolean
543 individual_view_drag_motion_cb (DragMotionData *data)
545 if (data->view != NULL)
547 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
548 g_object_remove_weak_pointer (G_OBJECT (data->view),
549 (gpointer *) &data->view);
552 data->timeout_id = 0;
554 return FALSE;
557 /* Minimum distance between the mouse pointer and a horizontal border when we
558 start auto scrolling. */
559 #define AUTO_SCROLL_MARGIN_SIZE 20
560 /* How far to scroll per one tick. */
561 #define AUTO_SCROLL_PITCH 10
563 static gboolean
564 individual_view_auto_scroll_cb (EmpathyIndividualView *self)
566 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
567 GtkAdjustment *adj;
568 gdouble new_value;
570 adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
572 if (priv->distance < 0)
573 new_value = gtk_adjustment_get_value (adj) - AUTO_SCROLL_PITCH;
574 else
575 new_value = gtk_adjustment_get_value (adj) + AUTO_SCROLL_PITCH;
577 new_value = CLAMP (new_value, gtk_adjustment_get_lower (adj),
578 gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj));
580 gtk_adjustment_set_value (adj, new_value);
582 return TRUE;
585 static gboolean
586 individual_view_drag_motion (GtkWidget *widget,
587 GdkDragContext *context,
588 gint x,
589 gint y,
590 guint time_)
592 EmpathyIndividualViewPriv *priv;
593 GtkTreeModel *model;
594 GdkAtom target;
595 GtkTreeIter iter;
596 static DragMotionData *dm = NULL;
597 GtkTreePath *path;
598 gboolean is_row;
599 gboolean is_different = FALSE;
600 gboolean cleanup = TRUE;
601 gboolean retval = TRUE;
602 GtkAllocation allocation;
603 guint i;
604 DndDragType drag_type = DND_DRAG_TYPE_UNKNOWN;
606 priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
607 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
610 if (priv->auto_scroll_timeout_id != 0)
612 g_source_remove (priv->auto_scroll_timeout_id);
613 priv->auto_scroll_timeout_id = 0;
616 gtk_widget_get_allocation (widget, &allocation);
618 if (y < AUTO_SCROLL_MARGIN_SIZE ||
619 y > (allocation.height - AUTO_SCROLL_MARGIN_SIZE))
621 if (y < AUTO_SCROLL_MARGIN_SIZE)
622 priv->distance = MIN (-y, -1);
623 else
624 priv->distance = MAX (allocation.height - y, 1);
626 priv->auto_scroll_timeout_id = g_timeout_add (10 * ABS (priv->distance),
627 (GSourceFunc) individual_view_auto_scroll_cb, widget);
630 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
631 x, y, &path, NULL, NULL, NULL);
633 cleanup &= (dm == NULL);
635 if (is_row)
637 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
638 is_different = ((dm == NULL) || ((dm != NULL)
639 && gtk_tree_path_compare (dm->path, path) != 0));
641 else
642 cleanup &= FALSE;
644 if (path == NULL)
646 /* Coordinates don't point to an actual row, so make sure the pointer
647 and highlighting don't indicate that a drag is possible.
649 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
650 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
651 return FALSE;
653 target = gtk_drag_dest_find_target (widget, context, NULL);
654 gtk_tree_model_get_iter (model, &iter, path);
656 /* Determine the DndDragType of the data */
657 for (i = 0; i < G_N_ELEMENTS (drag_atoms_dest); i++)
659 if (target == drag_atoms_dest[i])
661 drag_type = drag_types_dest[i].info;
662 break;
666 if (drag_type == DND_DRAG_TYPE_URI_LIST ||
667 drag_type == DND_DRAG_TYPE_STRING)
669 /* This is a file drag, and it can only be dropped on contacts,
670 * not groups.
671 * If we don't have FEATURE_FILE_DROP, disallow the drop completely,
672 * even if we have a valid target. */
673 FolksIndividual *individual = NULL;
674 EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
676 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_FILE_DROP)
678 gtk_tree_model_get (model, &iter,
679 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
680 -1);
683 if (individual != NULL)
685 EmpathyContact *contact = NULL;
687 contact = empathy_contact_dup_from_folks_individual (individual);
688 if (contact != NULL)
689 caps = empathy_contact_get_capabilities (contact);
691 tp_clear_object (&contact);
694 if (individual != NULL &&
695 folks_presence_details_is_online (
696 FOLKS_PRESENCE_DETAILS (individual)) &&
697 (caps & EMPATHY_CAPABILITIES_FT))
699 gdk_drag_status (context, GDK_ACTION_COPY, time_);
700 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
701 path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
703 else
705 gdk_drag_status (context, 0, time_);
706 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
707 retval = FALSE;
710 if (individual != NULL)
711 g_object_unref (individual);
713 else if ((drag_type == DND_DRAG_TYPE_INDIVIDUAL_ID &&
714 (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_CHANGE ||
715 priv->drag_row == NULL)) ||
716 (drag_type == DND_DRAG_TYPE_PERSONA_ID &&
717 priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_PERSONA_DROP))
719 /* If target != GDK_NONE, then we have a contact (individual or persona)
720 drag. If we're pointing to a group, highlight it. Otherwise, if the
721 contact we're pointing to is in a group, highlight that. Otherwise,
722 set the drag position to before the first row for a drag into
723 the "non-group" at the top.
724 If it's an Individual:
725 We only highlight things if the contact is from a different
726 Individual view, or if this Individual view has
727 FEATURE_GROUPS_CHANGE. This prevents highlighting in Individual views
728 which don't have FEATURE_GROUPS_CHANGE, but do have
729 FEATURE_INDIVIDUAL_DRAG and FEATURE_INDIVIDUAL_DROP.
730 If it's a Persona:
731 We only highlight things if we have FEATURE_PERSONA_DROP.
733 GtkTreeIter group_iter;
734 gboolean is_group;
735 GtkTreePath *group_path;
736 gtk_tree_model_get (model, &iter,
737 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
738 if (is_group)
740 group_iter = iter;
742 else
744 if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
745 gtk_tree_model_get (model, &group_iter,
746 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
748 if (is_group)
750 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
751 group_path = gtk_tree_model_get_path (model, &group_iter);
752 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
753 group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
754 gtk_tree_path_free (group_path);
756 else
758 group_path = gtk_tree_path_new_first ();
759 gdk_drag_status (context, GDK_ACTION_MOVE, time_);
760 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
761 group_path, GTK_TREE_VIEW_DROP_BEFORE);
765 if (!is_different && !cleanup)
766 return retval;
768 if (dm)
770 gtk_tree_path_free (dm->path);
771 if (dm->timeout_id)
773 g_source_remove (dm->timeout_id);
776 g_free (dm);
778 dm = NULL;
781 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
783 dm = g_new0 (DragMotionData, 1);
785 dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
786 g_object_add_weak_pointer (G_OBJECT (widget), (gpointer *) &dm->view);
787 dm->path = gtk_tree_path_copy (path);
789 dm->timeout_id = g_timeout_add_seconds (1,
790 (GSourceFunc) individual_view_drag_motion_cb, dm);
793 return retval;
796 static void
797 individual_view_drag_begin (GtkWidget *widget,
798 GdkDragContext *context)
800 EmpathyIndividualViewPriv *priv;
801 GtkTreeSelection *selection;
802 GtkTreeModel *model;
803 GtkTreePath *path;
804 GtkTreeIter iter;
806 priv = GET_PRIV (widget);
808 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
809 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
810 return;
812 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
813 context);
815 path = gtk_tree_model_get_path (model, &iter);
816 priv->drag_row = gtk_tree_row_reference_new (model, path);
817 gtk_tree_path_free (path);
820 static void
821 individual_view_drag_data_get (GtkWidget *widget,
822 GdkDragContext *context,
823 GtkSelectionData *selection,
824 guint info,
825 guint time_)
827 EmpathyIndividualViewPriv *priv;
828 GtkTreePath *src_path;
829 GtkTreeIter iter;
830 GtkTreeModel *model;
831 FolksIndividual *individual;
832 const gchar *individual_id;
834 priv = GET_PRIV (widget);
836 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
837 if (priv->drag_row == NULL)
838 return;
840 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
841 if (src_path == NULL)
842 return;
844 if (!gtk_tree_model_get_iter (model, &iter, src_path))
846 gtk_tree_path_free (src_path);
847 return;
850 gtk_tree_path_free (src_path);
852 individual =
853 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
854 if (individual == NULL)
855 return;
857 individual_id = folks_individual_get_id (individual);
859 if (info == DND_DRAG_TYPE_INDIVIDUAL_ID)
861 gtk_selection_data_set (selection,
862 gdk_atom_intern ("text/x-individual-id", FALSE), 8,
863 (guchar *) individual_id, strlen (individual_id) + 1);
866 g_object_unref (individual);
869 static void
870 individual_view_drag_end (GtkWidget *widget,
871 GdkDragContext *context)
873 EmpathyIndividualViewPriv *priv;
875 priv = GET_PRIV (widget);
877 GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
878 context);
880 if (priv->drag_row)
882 gtk_tree_row_reference_free (priv->drag_row);
883 priv->drag_row = NULL;
886 if (priv->auto_scroll_timeout_id != 0)
888 g_source_remove (priv->auto_scroll_timeout_id);
889 priv->auto_scroll_timeout_id = 0;
893 static gboolean
894 individual_view_drag_drop (GtkWidget *widget,
895 GdkDragContext *drag_context,
896 gint x,
897 gint y,
898 guint time_)
900 return FALSE;
903 typedef struct
905 EmpathyIndividualView *view;
906 guint button;
907 guint32 time;
908 } MenuPopupData;
910 static void
911 menu_deactivate_cb (GtkMenuShell *menushell,
912 gpointer user_data)
914 /* FIXME: we shouldn't have to disconnec the signal (bgo #641327) */
915 g_signal_handlers_disconnect_by_func (menushell,
916 menu_deactivate_cb, user_data);
918 gtk_menu_detach (GTK_MENU (menushell));
921 static gboolean
922 individual_view_popup_menu_idle_cb (gpointer user_data)
924 MenuPopupData *data = user_data;
925 GtkWidget *menu;
927 menu = empathy_individual_view_get_individual_menu (data->view);
928 if (menu == NULL)
929 menu = empathy_individual_view_get_group_menu (data->view);
931 if (menu != NULL)
933 gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
934 NULL);
935 gtk_widget_show (menu);
936 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
937 data->time);
939 /* menu is initially unowned but gtk_menu_attach_to_widget() taked its
940 * floating ref. We can either wait that the treeview releases its ref
941 * when it will be destroyed (when leaving Empathy) or explicitely
942 * detach the menu when it's not displayed any more.
943 * We go for the latter as we don't want to keep useless menus in memory
944 * during the whole lifetime of Empathy. */
945 g_signal_connect (menu, "deactivate", G_CALLBACK (menu_deactivate_cb),
946 NULL);
949 g_slice_free (MenuPopupData, data);
951 return FALSE;
954 static gboolean
955 individual_view_button_press_event_cb (EmpathyIndividualView *view,
956 GdkEventButton *event,
957 gpointer user_data)
959 if (event->button == 3)
961 MenuPopupData *data;
963 data = g_slice_new (MenuPopupData);
964 data->view = view;
965 data->button = event->button;
966 data->time = event->time;
967 g_idle_add (individual_view_popup_menu_idle_cb, data);
970 return FALSE;
973 static gboolean
974 individual_view_key_press_event_cb (EmpathyIndividualView *view,
975 GdkEventKey *event,
976 gpointer user_data)
978 if (event->keyval == GDK_KEY_Menu)
980 MenuPopupData *data;
982 data = g_slice_new (MenuPopupData);
983 data->view = view;
984 data->button = 0;
985 data->time = event->time;
986 g_idle_add (individual_view_popup_menu_idle_cb, data);
987 } else if (event->keyval == GDK_KEY_F2) {
988 FolksIndividual *individual;
990 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), FALSE);
992 individual = empathy_individual_view_dup_selected (view);
993 if (individual == NULL)
994 return FALSE;
996 empathy_individual_edit_dialog_show (individual, NULL);
998 g_object_unref (individual);
1001 return FALSE;
1004 static void
1005 individual_view_row_activated (GtkTreeView *view,
1006 GtkTreePath *path,
1007 GtkTreeViewColumn *column)
1009 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1010 FolksIndividual *individual;
1011 EmpathyContact *contact;
1012 GtkTreeModel *model;
1013 GtkTreeIter iter;
1015 if (!(priv->individual_features & EMPATHY_INDIVIDUAL_FEATURE_CHAT))
1016 return;
1018 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1019 gtk_tree_model_get_iter (model, &iter, path);
1020 gtk_tree_model_get (model, &iter,
1021 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1023 if (individual == NULL)
1024 return;
1026 /* Determine which Persona to chat to, by choosing the most available one. */
1027 contact = empathy_contact_dup_best_for_action (individual,
1028 EMPATHY_ACTION_CHAT);
1030 if (contact != NULL)
1032 DEBUG ("Starting a chat");
1034 empathy_chat_with_contact (contact,
1035 gtk_get_current_event_time ());
1038 g_object_unref (individual);
1039 tp_clear_object (&contact);
1042 static void
1043 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
1044 const gchar *path_string,
1045 EmpathyIndividualView *view)
1047 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1048 GtkWidget *menu;
1049 GtkTreeModel *model;
1050 GtkTreeIter iter;
1051 FolksIndividual *individual;
1052 GdkEventButton *event;
1053 GtkMenuShell *shell;
1054 GtkWidget *item;
1056 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL))
1057 return;
1059 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1060 if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1061 return;
1063 gtk_tree_model_get (model, &iter,
1064 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1065 if (individual == NULL)
1066 return;
1068 event = (GdkEventButton *) gtk_get_current_event ();
1070 menu = empathy_context_menu_new (GTK_WIDGET (view));
1071 shell = GTK_MENU_SHELL (menu);
1073 /* audio */
1074 item = empathy_individual_audio_call_menu_item_new_individual (NULL,
1075 individual);
1076 gtk_menu_shell_append (shell, item);
1077 gtk_widget_show (item);
1079 /* video */
1080 item = empathy_individual_video_call_menu_item_new_individual (NULL,
1081 individual);
1082 gtk_menu_shell_append (shell, item);
1083 gtk_widget_show (item);
1085 gtk_widget_show (menu);
1086 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1087 event->button, event->time);
1089 g_object_unref (individual);
1092 static void
1093 individual_view_cell_set_background (EmpathyIndividualView *view,
1094 GtkCellRenderer *cell,
1095 gboolean is_group,
1096 gboolean is_active)
1098 if (!is_group && is_active)
1100 GtkStyleContext *style;
1101 GdkRGBA color;
1103 style = gtk_widget_get_style_context (GTK_WIDGET (view));
1105 gtk_style_context_save (style);
1106 gtk_style_context_set_state (style, GTK_STATE_FLAG_SELECTED);
1107 gtk_style_context_get_background_color (style, GTK_STATE_FLAG_SELECTED,
1108 &color);
1109 gtk_style_context_restore (style);
1111 /* Here we take the current theme colour and add it to
1112 * the colour for white and average the two. This
1113 * gives a colour which is inline with the theme but
1114 * slightly whiter.
1116 empathy_make_color_whiter (&color);
1118 g_object_set (cell, "cell-background-rgba", &color, NULL);
1120 else
1121 g_object_set (cell, "cell-background-rgba", NULL, NULL);
1124 static void
1125 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1126 GtkCellRenderer *cell,
1127 GtkTreeModel *model,
1128 GtkTreeIter *iter,
1129 EmpathyIndividualView *view)
1131 GdkPixbuf *pixbuf;
1132 gboolean is_group;
1133 gboolean is_active;
1135 gtk_tree_model_get (model, iter,
1136 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1137 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1138 EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
1140 g_object_set (cell,
1141 "visible", !is_group,
1142 "pixbuf", pixbuf,
1143 NULL);
1145 tp_clear_object (&pixbuf);
1147 individual_view_cell_set_background (view, cell, is_group, is_active);
1150 static void
1151 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
1152 GtkCellRenderer *cell,
1153 GtkTreeModel *model,
1154 GtkTreeIter *iter,
1155 EmpathyIndividualView *view)
1157 GdkPixbuf *pixbuf = NULL;
1158 gboolean is_group;
1159 gchar *name;
1161 gtk_tree_model_get (model, iter,
1162 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1163 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1165 if (!is_group)
1166 goto out;
1168 if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1170 pixbuf = tpaw_pixbuf_from_icon_name ("emblem-favorite",
1171 GTK_ICON_SIZE_MENU);
1173 else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1175 pixbuf = tpaw_pixbuf_from_icon_name ("im-local-xmpp",
1176 GTK_ICON_SIZE_MENU);
1179 out:
1180 g_object_set (cell,
1181 "visible", pixbuf != NULL,
1182 "pixbuf", pixbuf,
1183 NULL);
1185 tp_clear_object (&pixbuf);
1187 g_free (name);
1190 static void
1191 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1192 GtkCellRenderer *cell,
1193 GtkTreeModel *model,
1194 GtkTreeIter *iter,
1195 EmpathyIndividualView *view)
1197 gboolean is_group;
1198 gboolean is_active;
1199 gboolean can_audio, can_video;
1201 gtk_tree_model_get (model, iter,
1202 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1203 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1204 EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1205 EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1207 g_object_set (cell,
1208 "visible", !is_group && (can_audio || can_video),
1209 "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1210 NULL);
1212 individual_view_cell_set_background (view, cell, is_group, is_active);
1215 static void
1216 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1217 GtkCellRenderer *cell,
1218 GtkTreeModel *model,
1219 GtkTreeIter *iter,
1220 EmpathyIndividualView *view)
1222 GdkPixbuf *pixbuf;
1223 gboolean show_avatar;
1224 gboolean is_group;
1225 gboolean is_active;
1227 gtk_tree_model_get (model, iter,
1228 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1229 EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1230 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1231 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1233 g_object_set (cell,
1234 "visible", !is_group && show_avatar,
1235 "pixbuf", pixbuf,
1236 NULL);
1238 tp_clear_object (&pixbuf);
1240 individual_view_cell_set_background (view, cell, is_group, is_active);
1243 static void
1244 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1245 GtkCellRenderer *cell,
1246 GtkTreeModel *model,
1247 GtkTreeIter *iter,
1248 EmpathyIndividualView *view)
1250 gboolean is_group;
1251 gboolean is_active;
1253 gtk_tree_model_get (model, iter,
1254 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1255 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1257 individual_view_cell_set_background (view, cell, is_group, is_active);
1260 static void
1261 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1262 GtkCellRenderer *cell,
1263 GtkTreeModel *model,
1264 GtkTreeIter *iter,
1265 EmpathyIndividualView *view)
1267 gboolean is_group;
1268 gboolean is_active;
1270 gtk_tree_model_get (model, iter,
1271 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1272 EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1274 if (gtk_tree_model_iter_has_child (model, iter))
1276 GtkTreePath *path;
1277 gboolean row_expanded;
1279 path = gtk_tree_model_get_path (model, iter);
1280 row_expanded =
1281 gtk_tree_view_row_expanded (GTK_TREE_VIEW
1282 (gtk_tree_view_column_get_tree_view (column)), path);
1283 gtk_tree_path_free (path);
1285 g_object_set (cell,
1286 "visible", TRUE,
1287 "expander-style",
1288 row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1289 NULL);
1291 else
1292 g_object_set (cell, "visible", FALSE, NULL);
1294 individual_view_cell_set_background (view, cell, is_group, is_active);
1297 static void
1298 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1299 GtkTreeIter *iter,
1300 GtkTreePath *path,
1301 gpointer user_data)
1303 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1304 GtkTreeModel *model;
1305 gchar *name;
1306 gboolean expanded;
1308 if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1309 return;
1311 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1313 gtk_tree_model_get (model, iter,
1314 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1316 expanded = GPOINTER_TO_INT (user_data);
1317 empathy_contact_group_set_expanded (name, expanded);
1319 g_free (name);
1322 static gboolean
1323 individual_view_start_search_cb (EmpathyIndividualView *view,
1324 gpointer data)
1326 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1328 if (priv->search_widget == NULL)
1329 return FALSE;
1331 empathy_individual_view_start_search (view);
1333 return TRUE;
1336 static void
1337 individual_view_search_text_notify_cb (TpawLiveSearch *search,
1338 GParamSpec *pspec,
1339 EmpathyIndividualView *view)
1341 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1342 GtkTreePath *path;
1343 GtkTreeViewColumn *focus_column;
1344 GtkTreeModel *model;
1345 GtkTreeIter iter;
1346 gboolean set_cursor = FALSE;
1348 gtk_tree_model_filter_refilter (priv->filter);
1350 /* Set cursor on the first contact. If it is already set on a group,
1351 * set it on its first child contact. Note that first child of a group
1352 * is its separator, that's why we actually set to the 2nd
1355 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1356 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1358 if (path == NULL)
1360 path = gtk_tree_path_new_from_string ("0:1");
1361 set_cursor = TRUE;
1363 else if (gtk_tree_path_get_depth (path) < 2)
1365 gboolean is_group;
1367 gtk_tree_model_get_iter (model, &iter, path);
1368 gtk_tree_model_get (model, &iter,
1369 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1370 -1);
1372 if (is_group)
1374 gtk_tree_path_down (path);
1375 gtk_tree_path_next (path);
1376 set_cursor = TRUE;
1380 if (set_cursor)
1382 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1383 * valid. */
1384 if (gtk_tree_model_get_iter (model, &iter, path))
1386 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1387 FALSE);
1391 gtk_tree_path_free (path);
1394 static void
1395 individual_view_search_activate_cb (GtkWidget *search,
1396 EmpathyIndividualView *view)
1398 GtkTreePath *path;
1399 GtkTreeViewColumn *focus_column;
1401 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1402 if (path != NULL)
1404 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1405 gtk_tree_path_free (path);
1407 gtk_widget_hide (search);
1411 static gboolean
1412 individual_view_search_key_navigation_cb (GtkWidget *search,
1413 GdkEvent *event,
1414 EmpathyIndividualView *view)
1416 GdkEvent *new_event;
1417 gboolean ret = FALSE;
1419 new_event = gdk_event_copy (event);
1420 gtk_widget_grab_focus (GTK_WIDGET (view));
1421 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1422 gtk_widget_grab_focus (search);
1424 gdk_event_free (new_event);
1426 return ret;
1429 static void
1430 individual_view_search_hide_cb (TpawLiveSearch *search,
1431 EmpathyIndividualView *view)
1433 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1434 GtkTreeModel *model;
1435 GtkTreePath *cursor_path;
1436 GtkTreeIter iter;
1437 gboolean valid = FALSE;
1439 /* block expand or collapse handlers, they would write the
1440 * expand or collapsed setting to file otherwise */
1441 g_signal_handlers_block_by_func (view,
1442 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1443 g_signal_handlers_block_by_func (view,
1444 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1446 /* restore which groups are expanded and which are not */
1447 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1448 for (valid = gtk_tree_model_get_iter_first (model, &iter);
1449 valid; valid = gtk_tree_model_iter_next (model, &iter))
1451 gboolean is_group;
1452 gchar *name = NULL;
1453 GtkTreePath *path;
1455 gtk_tree_model_get (model, &iter,
1456 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1457 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1458 -1);
1460 if (!is_group)
1462 g_free (name);
1463 continue;
1466 path = gtk_tree_model_get_path (model, &iter);
1467 if ((priv->view_features &
1468 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1469 empathy_contact_group_get_expanded (name))
1471 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1473 else
1475 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1478 gtk_tree_path_free (path);
1479 g_free (name);
1482 /* unblock expand or collapse handlers */
1483 g_signal_handlers_unblock_by_func (view,
1484 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1485 g_signal_handlers_unblock_by_func (view,
1486 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1488 /* keep the selected contact visible */
1489 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &cursor_path, NULL);
1491 if (cursor_path != NULL)
1492 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view), cursor_path, NULL,
1493 FALSE, 0, 0);
1495 gtk_tree_path_free (cursor_path);
1498 static void
1499 individual_view_search_show_cb (TpawLiveSearch *search,
1500 EmpathyIndividualView *view)
1502 /* block expand or collapse handlers during expand all, they would
1503 * write the expand or collapsed setting to file otherwise */
1504 g_signal_handlers_block_by_func (view,
1505 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1507 gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1509 g_signal_handlers_unblock_by_func (view,
1510 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1513 static gboolean
1514 expand_idle_foreach_cb (GtkTreeModel *model,
1515 GtkTreePath *path,
1516 GtkTreeIter *iter,
1517 EmpathyIndividualView *self)
1519 EmpathyIndividualViewPriv *priv;
1520 gboolean is_group;
1521 gpointer should_expand;
1522 gchar *name;
1524 /* We only want groups */
1525 if (gtk_tree_path_get_depth (path) > 1)
1526 return FALSE;
1528 gtk_tree_model_get (model, iter,
1529 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1530 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1531 -1);
1533 if (!is_group)
1535 g_free (name);
1536 return FALSE;
1539 priv = GET_PRIV (self);
1541 if (g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1542 &should_expand))
1544 if (GPOINTER_TO_INT (should_expand))
1545 gtk_tree_view_expand_row (GTK_TREE_VIEW (self), path, FALSE);
1546 else
1547 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self), path);
1549 g_hash_table_remove (priv->expand_groups, name);
1552 g_free (name);
1554 return FALSE;
1557 static gboolean
1558 individual_view_expand_idle_cb (EmpathyIndividualView *self)
1560 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1562 g_signal_handlers_block_by_func (self,
1563 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1564 g_signal_handlers_block_by_func (self,
1565 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1567 /* The store/filter could've been removed while we were in the idle queue */
1568 if (priv->filter != NULL)
1570 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->filter),
1571 (GtkTreeModelForeachFunc) expand_idle_foreach_cb, self);
1574 g_signal_handlers_unblock_by_func (self,
1575 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1576 g_signal_handlers_unblock_by_func (self,
1577 individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1579 /* Empty the table of groups to expand/contract, since it may contain groups
1580 * which no longer exist in the tree view. This can happen after going
1581 * offline, for example. */
1582 g_hash_table_remove_all (priv->expand_groups);
1583 priv->expand_groups_idle_handler = 0;
1584 g_object_unref (self);
1586 return FALSE;
1589 static void
1590 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1591 GtkTreePath *path,
1592 GtkTreeIter *iter,
1593 EmpathyIndividualView *view)
1595 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1596 gboolean should_expand, is_group = FALSE;
1597 gchar *name = NULL;
1598 gpointer will_expand;
1600 gtk_tree_model_get (model, iter,
1601 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1602 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1603 -1);
1605 if (!is_group || TPAW_STR_EMPTY (name))
1607 g_free (name);
1608 return;
1611 should_expand = (priv->view_features &
1612 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1613 (priv->search_widget != NULL &&
1614 gtk_widget_get_visible (priv->search_widget)) ||
1615 empathy_contact_group_get_expanded (name);
1617 /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1618 * gtk_tree_model_filter_refilter (). We add the rows to expand/contract to
1619 * a hash table, and expand or contract them as appropriate all at once in
1620 * an idle handler which iterates over all the group rows. */
1621 if (!g_hash_table_lookup_extended (priv->expand_groups, name, NULL,
1622 &will_expand) ||
1623 GPOINTER_TO_INT (will_expand) != should_expand)
1625 g_hash_table_insert (priv->expand_groups, g_strdup (name),
1626 GINT_TO_POINTER (should_expand));
1628 if (priv->expand_groups_idle_handler == 0)
1630 priv->expand_groups_idle_handler =
1631 g_idle_add ((GSourceFunc) individual_view_expand_idle_cb,
1632 g_object_ref (view));
1636 g_free (name);
1639 static gboolean
1640 individual_view_is_visible_individual (EmpathyIndividualView *self,
1641 FolksIndividual *individual,
1642 gboolean is_online,
1643 gboolean is_searching,
1644 const gchar *group,
1645 gboolean is_fake_group,
1646 guint event_count)
1648 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1649 TpawLiveSearch *live = TPAW_LIVE_SEARCH (priv->search_widget);
1650 GeeSet *personas;
1651 GeeIterator *iter;
1652 gboolean is_favorite;
1654 /* Always display individuals having pending events */
1655 if (event_count > 0)
1656 return TRUE;
1658 /* We're only giving the visibility wrt filtering here, not things like
1659 * presence. */
1660 if (!priv->show_untrusted &&
1661 folks_individual_get_trust_level (individual) == FOLKS_TRUST_LEVEL_NONE)
1663 return FALSE;
1666 if (!priv->show_uninteresting)
1668 gboolean contains_interesting_persona = FALSE;
1670 /* Hide all individuals which consist entirely of uninteresting
1671 * personas */
1672 personas = folks_individual_get_personas (individual);
1673 iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1674 while (!contains_interesting_persona && gee_iterator_next (iter))
1676 FolksPersona *persona = gee_iterator_get (iter);
1678 if (empathy_folks_persona_is_interesting (persona))
1679 contains_interesting_persona = TRUE;
1681 g_clear_object (&persona);
1683 g_clear_object (&iter);
1685 if (!contains_interesting_persona)
1686 return FALSE;
1689 is_favorite = folks_favourite_details_get_is_favourite (
1690 FOLKS_FAVOURITE_DETAILS (individual));
1691 if (!is_searching) {
1692 if (is_favorite && is_fake_group &&
1693 !tp_strdiff (group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1694 /* Always display favorite contacts in the favorite group */
1695 return TRUE;
1697 return (priv->show_offline || is_online);
1700 return empathy_individual_match_string (individual,
1701 tpaw_live_search_get_text (live),
1702 tpaw_live_search_get_words (live));
1705 static gchar *
1706 get_group (GtkTreeModel *model,
1707 GtkTreeIter *iter,
1708 gboolean *is_fake)
1710 GtkTreeIter parent_iter;
1711 gchar *name = NULL;
1713 *is_fake = FALSE;
1715 if (!gtk_tree_model_iter_parent (model, &parent_iter, iter))
1716 return NULL;
1718 gtk_tree_model_get (model, &parent_iter,
1719 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1720 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake,
1721 -1);
1723 return name;
1727 static gboolean
1728 individual_view_filter_visible_func (GtkTreeModel *model,
1729 GtkTreeIter *iter,
1730 gpointer user_data)
1732 EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
1733 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1734 FolksIndividual *individual = NULL;
1735 gboolean is_group, is_separator, valid;
1736 GtkTreeIter child_iter;
1737 gboolean visible, is_online;
1738 gboolean is_searching = TRUE;
1739 guint event_count;
1741 if (priv->custom_filter != NULL)
1742 return priv->custom_filter (model, iter, priv->custom_filter_data);
1744 if (priv->search_widget == NULL ||
1745 !gtk_widget_get_visible (priv->search_widget))
1746 is_searching = FALSE;
1748 gtk_tree_model_get (model, iter,
1749 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1750 EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
1751 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1752 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1753 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1754 -1);
1756 if (individual != NULL)
1758 gchar *group;
1759 gboolean is_fake_group;
1761 group = get_group (model, iter, &is_fake_group);
1763 visible = individual_view_is_visible_individual (self, individual,
1764 is_online, is_searching, group, is_fake_group, event_count);
1766 g_object_unref (individual);
1767 g_free (group);
1769 return visible;
1772 if (is_separator)
1773 return TRUE;
1775 /* Not a contact, not a separator, must be a group */
1776 g_return_val_if_fail (is_group, FALSE);
1778 /* only show groups which are not empty */
1779 for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
1780 valid; valid = gtk_tree_model_iter_next (model, &child_iter))
1782 gchar *group;
1783 gboolean is_fake_group;
1785 gtk_tree_model_get (model, &child_iter,
1786 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
1787 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
1788 EMPATHY_INDIVIDUAL_STORE_COL_EVENT_COUNT, &event_count,
1789 -1);
1791 if (individual == NULL)
1792 continue;
1794 group = get_group (model, &child_iter, &is_fake_group);
1796 visible = individual_view_is_visible_individual (self, individual,
1797 is_online, is_searching, group, is_fake_group, event_count);
1799 g_object_unref (individual);
1800 g_free (group);
1802 /* show group if it has at least one visible contact in it */
1803 if (visible)
1804 return TRUE;
1807 return FALSE;
1810 static gchar * empathy_individual_view_dup_selected_group (
1811 EmpathyIndividualView *view,
1812 gboolean *is_fake_group);
1814 static void
1815 text_edited_cb (GtkCellRendererText *cellrenderertext,
1816 gchar *path,
1817 gchar *name,
1818 EmpathyIndividualView *self)
1820 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1821 gchar *old_name, *new_name;
1823 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1825 new_name = g_strdup (name);
1826 g_strstrip (new_name);
1828 if (tp_str_empty (new_name))
1829 goto out;
1831 old_name = empathy_individual_view_dup_selected_group (self, NULL);
1832 g_return_if_fail (old_name != NULL);
1834 if (tp_strdiff (old_name, new_name))
1836 EmpathyConnectionAggregator *aggregator;
1838 DEBUG ("rename group '%s' to '%s'", old_name, new_name);
1840 aggregator = empathy_connection_aggregator_dup_singleton ();
1842 empathy_connection_aggregator_rename_group (aggregator, old_name,
1843 new_name);
1844 g_object_unref (aggregator);
1847 g_free (old_name);
1848 out:
1849 g_free (new_name);
1852 static void
1853 text_renderer_editing_cancelled_cb (GtkCellRenderer *renderer,
1854 EmpathyIndividualView *self)
1856 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
1858 g_object_set (priv->text_renderer, "editable", FALSE, NULL);
1861 static void
1862 individual_view_constructed (GObject *object)
1864 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1865 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1866 GtkCellRenderer *cell;
1867 GtkTreeViewColumn *col;
1868 guint i;
1870 /* Setup view */
1871 g_object_set (view,
1872 "headers-visible", FALSE,
1873 "show-expanders", FALSE,
1874 NULL);
1876 col = gtk_tree_view_column_new ();
1878 /* State */
1879 cell = gtk_cell_renderer_pixbuf_new ();
1880 gtk_tree_view_column_pack_start (col, cell, FALSE);
1881 gtk_tree_view_column_set_cell_data_func (col, cell,
1882 (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1883 view, NULL);
1885 g_object_set (cell,
1886 "xpad", 5,
1887 "ypad", 1,
1888 "visible", FALSE,
1889 NULL);
1891 /* Group icon */
1892 cell = gtk_cell_renderer_pixbuf_new ();
1893 gtk_tree_view_column_pack_start (col, cell, FALSE);
1894 gtk_tree_view_column_set_cell_data_func (col, cell,
1895 (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1896 view, NULL);
1898 g_object_set (cell,
1899 "xpad", 0,
1900 "ypad", 0,
1901 "visible", FALSE,
1902 "width", 16,
1903 "height", 16,
1904 NULL);
1906 /* Name */
1907 priv->text_renderer = empathy_cell_renderer_text_new ();
1908 gtk_tree_view_column_pack_start (col, priv->text_renderer, TRUE);
1909 gtk_tree_view_column_set_cell_data_func (col, priv->text_renderer,
1910 (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1912 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1913 "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1914 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1915 "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1916 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1917 "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1918 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1919 "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1920 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1921 "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1922 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1923 "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1924 gtk_tree_view_column_add_attribute (col, priv->text_renderer,
1925 "client-types", EMPATHY_INDIVIDUAL_STORE_COL_CLIENT_TYPES);
1927 g_signal_connect (priv->text_renderer, "editing-canceled",
1928 G_CALLBACK (text_renderer_editing_cancelled_cb), view);
1929 g_signal_connect (priv->text_renderer, "edited",
1930 G_CALLBACK (text_edited_cb), view);
1932 /* Audio Call Icon */
1933 cell = empathy_cell_renderer_activatable_new ();
1934 gtk_tree_view_column_pack_start (col, cell, FALSE);
1935 gtk_tree_view_column_set_cell_data_func (col, cell,
1936 (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1937 view, NULL);
1939 g_object_set (cell, "visible", FALSE, NULL);
1941 g_signal_connect (cell, "path-activated",
1942 G_CALLBACK (individual_view_call_activated_cb), view);
1944 /* Avatar */
1945 cell = gtk_cell_renderer_pixbuf_new ();
1946 gtk_tree_view_column_pack_start (col, cell, FALSE);
1947 gtk_tree_view_column_set_cell_data_func (col, cell,
1948 (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1949 view, NULL);
1951 g_object_set (cell,
1952 "xpad", 0,
1953 "ypad", 0,
1954 "visible", FALSE,
1955 "width", 32,
1956 "height", 32,
1957 NULL);
1959 /* Expander */
1960 cell = empathy_cell_renderer_expander_new ();
1961 gtk_tree_view_column_pack_end (col, cell, FALSE);
1962 gtk_tree_view_column_set_cell_data_func (col, cell,
1963 (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1964 view, NULL);
1966 /* Actually add the column now we have added all cell renderers */
1967 gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1969 /* Drag & Drop. */
1970 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1972 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1976 static void
1977 individual_view_set_view_features (EmpathyIndividualView *view,
1978 EmpathyIndividualViewFeatureFlags features)
1980 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1981 gboolean has_tooltip;
1983 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1985 priv->view_features = features;
1987 /* Setting reorderable is a hack that gets us row previews as drag icons
1988 for free. We override all the drag handlers. It's tricky to get the
1989 position of the drag icon right in drag_begin. GtkTreeView has special
1990 voodoo for it, so we let it do the voodoo that he do (but only if dragging
1991 is enabled).
1993 gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view),
1994 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG));
1996 /* Update DnD source/dest */
1997 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DRAG)
1999 gtk_drag_source_set (GTK_WIDGET (view),
2000 GDK_BUTTON1_MASK,
2001 drag_types_source,
2002 G_N_ELEMENTS (drag_types_source),
2003 GDK_ACTION_MOVE | GDK_ACTION_COPY);
2005 else
2007 gtk_drag_source_unset (GTK_WIDGET (view));
2011 if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_DROP)
2013 gtk_drag_dest_set (GTK_WIDGET (view),
2014 GTK_DEST_DEFAULT_ALL,
2015 drag_types_dest,
2016 G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
2018 else
2020 /* FIXME: URI could still be droped depending on FT feature */
2021 gtk_drag_dest_unset (GTK_WIDGET (view));
2024 /* Update has-tooltip */
2025 has_tooltip =
2026 (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP) != 0;
2027 gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
2030 static void
2031 individual_view_dispose (GObject *object)
2033 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2034 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2036 tp_clear_object (&priv->store);
2037 tp_clear_object (&priv->filter);
2038 tp_clear_object (&priv->tooltip_widget);
2040 empathy_individual_view_set_live_search (view, NULL);
2042 G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
2045 static void
2046 individual_view_finalize (GObject *object)
2048 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2050 if (priv->expand_groups_idle_handler != 0)
2051 g_source_remove (priv->expand_groups_idle_handler);
2052 g_hash_table_unref (priv->expand_groups);
2054 G_OBJECT_CLASS (empathy_individual_view_parent_class)->finalize (object);
2057 static void
2058 individual_view_get_property (GObject *object,
2059 guint param_id,
2060 GValue *value,
2061 GParamSpec *pspec)
2063 EmpathyIndividualViewPriv *priv;
2065 priv = GET_PRIV (object);
2067 switch (param_id)
2069 case PROP_STORE:
2070 g_value_set_object (value, priv->store);
2071 break;
2072 case PROP_VIEW_FEATURES:
2073 g_value_set_flags (value, priv->view_features);
2074 break;
2075 case PROP_INDIVIDUAL_FEATURES:
2076 g_value_set_flags (value, priv->individual_features);
2077 break;
2078 case PROP_SHOW_OFFLINE:
2079 g_value_set_boolean (value, priv->show_offline);
2080 break;
2081 case PROP_SHOW_UNTRUSTED:
2082 g_value_set_boolean (value, priv->show_untrusted);
2083 break;
2084 case PROP_SHOW_UNINTERESTING:
2085 g_value_set_boolean (value, priv->show_uninteresting);
2086 break;
2087 default:
2088 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2089 break;
2093 static void
2094 individual_view_set_property (GObject *object,
2095 guint param_id,
2096 const GValue *value,
2097 GParamSpec *pspec)
2099 EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
2100 EmpathyIndividualViewPriv *priv = GET_PRIV (object);
2102 switch (param_id)
2104 case PROP_STORE:
2105 empathy_individual_view_set_store (view, g_value_get_object (value));
2106 break;
2107 case PROP_VIEW_FEATURES:
2108 individual_view_set_view_features (view, g_value_get_flags (value));
2109 break;
2110 case PROP_INDIVIDUAL_FEATURES:
2111 priv->individual_features = g_value_get_flags (value);
2112 break;
2113 case PROP_SHOW_OFFLINE:
2114 empathy_individual_view_set_show_offline (view,
2115 g_value_get_boolean (value));
2116 break;
2117 case PROP_SHOW_UNTRUSTED:
2118 empathy_individual_view_set_show_untrusted (view,
2119 g_value_get_boolean (value));
2120 break;
2121 case PROP_SHOW_UNINTERESTING:
2122 empathy_individual_view_set_show_uninteresting (view,
2123 g_value_get_boolean (value));
2124 default:
2125 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2126 break;
2130 static void
2131 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
2133 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2134 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2135 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
2137 object_class->constructed = individual_view_constructed;
2138 object_class->dispose = individual_view_dispose;
2139 object_class->finalize = individual_view_finalize;
2140 object_class->get_property = individual_view_get_property;
2141 object_class->set_property = individual_view_set_property;
2143 widget_class->drag_data_received = individual_view_drag_data_received;
2144 widget_class->drag_drop = individual_view_drag_drop;
2145 widget_class->drag_begin = individual_view_drag_begin;
2146 widget_class->drag_data_get = individual_view_drag_data_get;
2147 widget_class->drag_end = individual_view_drag_end;
2148 widget_class->drag_motion = individual_view_drag_motion;
2150 /* We use the class method to let user of this widget to connect to
2151 * the signal and stop emission of the signal so the default handler
2152 * won't be called. */
2153 tree_view_class->row_activated = individual_view_row_activated;
2155 klass->drag_individual_received = real_drag_individual_received_cb;
2157 signals[DRAG_INDIVIDUAL_RECEIVED] =
2158 g_signal_new ("drag-individual-received",
2159 G_OBJECT_CLASS_TYPE (klass),
2160 G_SIGNAL_RUN_LAST,
2161 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_individual_received),
2162 NULL, NULL,
2163 g_cclosure_marshal_generic,
2164 G_TYPE_NONE, 4, G_TYPE_UINT, FOLKS_TYPE_INDIVIDUAL,
2165 G_TYPE_STRING, G_TYPE_STRING);
2167 signals[DRAG_PERSONA_RECEIVED] =
2168 g_signal_new ("drag-persona-received",
2169 G_OBJECT_CLASS_TYPE (klass),
2170 G_SIGNAL_RUN_LAST,
2171 G_STRUCT_OFFSET (EmpathyIndividualViewClass, drag_persona_received),
2172 NULL, NULL,
2173 g_cclosure_marshal_generic,
2174 G_TYPE_BOOLEAN, 3, G_TYPE_UINT, FOLKS_TYPE_PERSONA, FOLKS_TYPE_INDIVIDUAL);
2176 g_object_class_install_property (object_class,
2177 PROP_STORE,
2178 g_param_spec_object ("store",
2179 "The store of the view",
2180 "The store of the view",
2181 EMPATHY_TYPE_INDIVIDUAL_STORE,
2182 G_PARAM_READWRITE));
2183 g_object_class_install_property (object_class,
2184 PROP_VIEW_FEATURES,
2185 g_param_spec_flags ("view-features",
2186 "Features of the view",
2187 "Flags for all enabled features",
2188 EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
2189 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
2190 g_object_class_install_property (object_class,
2191 PROP_INDIVIDUAL_FEATURES,
2192 g_param_spec_flags ("individual-features",
2193 "Features of the individual menu",
2194 "Flags for all enabled features for the menu",
2195 EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
2196 EMPATHY_INDIVIDUAL_FEATURE_NONE, G_PARAM_READWRITE));
2197 g_object_class_install_property (object_class,
2198 PROP_SHOW_OFFLINE,
2199 g_param_spec_boolean ("show-offline",
2200 "Show Offline",
2201 "Whether contact list should display "
2202 "offline contacts", FALSE, G_PARAM_READWRITE));
2203 g_object_class_install_property (object_class,
2204 PROP_SHOW_UNTRUSTED,
2205 g_param_spec_boolean ("show-untrusted",
2206 "Show Untrusted Individuals",
2207 "Whether the view should display untrusted individuals; "
2208 "those who could not be who they say they are.",
2209 TRUE, G_PARAM_READWRITE));
2210 g_object_class_install_property (object_class,
2211 PROP_SHOW_UNINTERESTING,
2212 g_param_spec_boolean ("show-uninteresting",
2213 "Show Uninteresting Individuals",
2214 "Whether the view should not filter out individuals using "
2215 "empathy_folks_persona_is_interesting.",
2216 FALSE, G_PARAM_READWRITE));
2218 g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
2221 static void
2222 empathy_individual_view_init (EmpathyIndividualView *view)
2224 EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
2225 EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
2227 view->priv = priv;
2229 priv->show_untrusted = TRUE;
2230 priv->show_uninteresting = FALSE;
2232 /* Get saved group states. */
2233 empathy_contact_groups_get_all ();
2235 priv->expand_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
2236 (GDestroyNotify) g_free, NULL);
2238 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
2239 empathy_individual_store_row_separator_func, NULL, NULL);
2241 /* Connect to tree view signals rather than override. */
2242 g_signal_connect (view, "button-press-event",
2243 G_CALLBACK (individual_view_button_press_event_cb), NULL);
2244 g_signal_connect (view, "key-press-event",
2245 G_CALLBACK (individual_view_key_press_event_cb), NULL);
2246 g_signal_connect (view, "row-expanded",
2247 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2248 GINT_TO_POINTER (TRUE));
2249 g_signal_connect (view, "row-collapsed",
2250 G_CALLBACK (individual_view_row_expand_or_collapse_cb),
2251 GINT_TO_POINTER (FALSE));
2252 g_signal_connect (view, "query-tooltip",
2253 G_CALLBACK (individual_view_query_tooltip_cb), NULL);
2256 EmpathyIndividualView *
2257 empathy_individual_view_new (EmpathyIndividualStore *store,
2258 EmpathyIndividualViewFeatureFlags view_features,
2259 EmpathyIndividualFeatureFlags individual_features)
2261 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
2263 return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
2264 "store", store,
2265 "individual-features", individual_features,
2266 "view-features", view_features, NULL);
2269 FolksIndividual *
2270 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
2272 GtkTreeSelection *selection;
2273 GtkTreeIter iter;
2274 GtkTreeModel *model;
2275 FolksIndividual *individual;
2277 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2279 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2280 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2281 return NULL;
2283 gtk_tree_model_get (model, &iter,
2284 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
2286 return individual;
2289 static gchar *
2290 empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
2291 gboolean *is_fake_group)
2293 GtkTreeSelection *selection;
2294 GtkTreeIter iter;
2295 GtkTreeModel *model;
2296 gboolean is_group;
2297 gchar *name;
2298 gboolean fake;
2300 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2302 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
2303 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2304 return NULL;
2306 gtk_tree_model_get (model, &iter,
2307 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
2308 EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
2309 EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
2311 if (!is_group)
2313 g_free (name);
2314 return NULL;
2317 if (is_fake_group != NULL)
2318 *is_fake_group = fake;
2320 return name;
2323 enum
2325 REMOVE_DIALOG_RESPONSE_CANCEL = 0,
2326 REMOVE_DIALOG_RESPONSE_DELETE,
2329 static int
2330 individual_view_remove_dialog_show (GtkWindow *parent,
2331 const gchar *message,
2332 const gchar *secondary_text)
2334 GtkWidget *dialog;
2335 gboolean res;
2337 dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
2338 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
2340 gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2341 GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
2342 GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
2343 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
2344 "%s", secondary_text);
2346 gtk_widget_show (dialog);
2348 res = gtk_dialog_run (GTK_DIALOG (dialog));
2349 gtk_widget_destroy (dialog);
2351 return res;
2354 static void
2355 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
2356 EmpathyIndividualView *view)
2358 gchar *group;
2360 group = empathy_individual_view_dup_selected_group (view, NULL);
2361 if (group != NULL)
2363 gchar *text;
2364 GtkWindow *parent;
2366 text =
2367 g_strdup_printf (_("Do you really want to remove the group “%s”?"),
2368 group);
2369 parent = tpaw_get_toplevel_window (GTK_WIDGET (view));
2370 if (individual_view_remove_dialog_show (parent, _("Removing group"),
2371 text) == REMOVE_DIALOG_RESPONSE_DELETE)
2373 EmpathyIndividualManager *manager =
2374 empathy_individual_manager_dup_singleton ();
2375 empathy_individual_manager_remove_group (manager, group);
2376 g_object_unref (G_OBJECT (manager));
2379 g_free (text);
2382 g_free (group);
2385 static void
2386 individual_view_group_rename_activate_cb (GtkMenuItem *menuitem,
2387 EmpathyIndividualView *self)
2389 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2390 GtkTreePath *path;
2391 GtkTreeIter iter;
2392 GtkTreeSelection *selection;
2393 GtkTreeModel *model;
2395 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
2396 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
2397 return;
2398 path = gtk_tree_model_get_path (model, &iter);
2400 g_object_set (G_OBJECT (priv->text_renderer), "editable", TRUE, NULL);
2402 gtk_tree_view_set_enable_search (GTK_TREE_VIEW (self), FALSE);
2403 gtk_widget_grab_focus (GTK_WIDGET (self));
2404 gtk_tree_view_set_cursor (GTK_TREE_VIEW (self), path,
2405 gtk_tree_view_get_column (GTK_TREE_VIEW (self), 0), TRUE);
2407 gtk_tree_path_free (path);
2410 GtkWidget *
2411 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
2413 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2414 gchar *group;
2415 GtkWidget *menu;
2416 GtkWidget *item;
2417 GtkWidget *image;
2418 gboolean is_fake_group;
2420 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2422 if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
2423 EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
2424 return NULL;
2426 group = empathy_individual_view_dup_selected_group (view, &is_fake_group);
2427 if (!group || is_fake_group)
2429 /* We can't alter fake groups */
2430 g_free (group);
2431 return NULL;
2434 menu = gtk_menu_new ();
2436 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME)
2438 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2439 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2440 gtk_widget_show (item);
2441 g_signal_connect (item, "activate",
2442 G_CALLBACK (individual_view_group_rename_activate_cb), view);
2445 if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
2447 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2448 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2449 GTK_ICON_SIZE_MENU);
2450 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2451 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2452 gtk_widget_show (item);
2453 g_signal_connect (item, "activate",
2454 G_CALLBACK (individual_view_group_remove_activate_cb), view);
2457 g_free (group);
2459 return menu;
2462 GtkWidget *
2463 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2465 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2466 FolksIndividual *individual;
2467 GtkWidget *menu = NULL;
2469 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2471 if (priv->individual_features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
2472 /* No need to create a context menu */
2473 return NULL;
2475 individual = empathy_individual_view_dup_selected (view);
2476 if (individual == NULL)
2477 return NULL;
2479 if (!empathy_folks_individual_contains_contact (individual))
2480 goto out;
2482 menu = empathy_individual_menu_new (individual, NULL,
2483 priv->individual_features, priv->store);
2485 out:
2486 g_object_unref (individual);
2488 return menu;
2491 void
2492 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2493 TpawLiveSearch *search)
2495 EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2497 /* remove old handlers if old search was not null */
2498 if (priv->search_widget != NULL)
2500 g_signal_handlers_disconnect_by_func (view,
2501 individual_view_start_search_cb, NULL);
2503 g_signal_handlers_disconnect_by_func (priv->search_widget,
2504 individual_view_search_text_notify_cb, view);
2505 g_signal_handlers_disconnect_by_func (priv->search_widget,
2506 individual_view_search_activate_cb, view);
2507 g_signal_handlers_disconnect_by_func (priv->search_widget,
2508 individual_view_search_key_navigation_cb, view);
2509 g_signal_handlers_disconnect_by_func (priv->search_widget,
2510 individual_view_search_hide_cb, view);
2511 g_signal_handlers_disconnect_by_func (priv->search_widget,
2512 individual_view_search_show_cb, view);
2513 g_object_unref (priv->search_widget);
2514 priv->search_widget = NULL;
2517 /* connect handlers if new search is not null */
2518 if (search != NULL)
2520 priv->search_widget = g_object_ref (search);
2522 g_signal_connect (view, "start-interactive-search",
2523 G_CALLBACK (individual_view_start_search_cb), NULL);
2525 g_signal_connect (priv->search_widget, "notify::text",
2526 G_CALLBACK (individual_view_search_text_notify_cb), view);
2527 g_signal_connect (priv->search_widget, "activate",
2528 G_CALLBACK (individual_view_search_activate_cb), view);
2529 g_signal_connect (priv->search_widget, "key-navigation",
2530 G_CALLBACK (individual_view_search_key_navigation_cb), view);
2531 g_signal_connect (priv->search_widget, "hide",
2532 G_CALLBACK (individual_view_search_hide_cb), view);
2533 g_signal_connect (priv->search_widget, "show",
2534 G_CALLBACK (individual_view_search_show_cb), view);
2538 gboolean
2539 empathy_individual_view_is_searching (EmpathyIndividualView *self)
2541 EmpathyIndividualViewPriv *priv;
2543 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2545 priv = GET_PRIV (self);
2547 return (priv->search_widget != NULL &&
2548 gtk_widget_get_visible (priv->search_widget));
2551 gboolean
2552 empathy_individual_view_get_show_offline (EmpathyIndividualView *self)
2554 EmpathyIndividualViewPriv *priv;
2556 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2558 priv = GET_PRIV (self);
2560 return priv->show_offline;
2563 void
2564 empathy_individual_view_set_show_offline (EmpathyIndividualView *self,
2565 gboolean show_offline)
2567 EmpathyIndividualViewPriv *priv;
2569 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2571 priv = GET_PRIV (self);
2573 priv->show_offline = show_offline;
2575 g_object_notify (G_OBJECT (self), "show-offline");
2576 gtk_tree_model_filter_refilter (priv->filter);
2579 gboolean
2580 empathy_individual_view_get_show_untrusted (EmpathyIndividualView *self)
2582 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), FALSE);
2584 return GET_PRIV (self)->show_untrusted;
2587 void
2588 empathy_individual_view_set_show_untrusted (EmpathyIndividualView *self,
2589 gboolean show_untrusted)
2591 EmpathyIndividualViewPriv *priv;
2593 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2595 priv = GET_PRIV (self);
2597 priv->show_untrusted = show_untrusted;
2599 g_object_notify (G_OBJECT (self), "show-untrusted");
2600 gtk_tree_model_filter_refilter (priv->filter);
2603 EmpathyIndividualStore *
2604 empathy_individual_view_get_store (EmpathyIndividualView *self)
2606 g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self), NULL);
2608 return GET_PRIV (self)->store;
2611 void
2612 empathy_individual_view_set_store (EmpathyIndividualView *self,
2613 EmpathyIndividualStore *store)
2615 EmpathyIndividualViewPriv *priv;
2617 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2618 g_return_if_fail (store == NULL || EMPATHY_IS_INDIVIDUAL_STORE (store));
2620 priv = GET_PRIV (self);
2622 /* Destroy the old filter and remove the old store */
2623 if (priv->store != NULL)
2625 g_signal_handlers_disconnect_by_func (priv->filter,
2626 individual_view_row_has_child_toggled_cb, self);
2628 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
2631 tp_clear_object (&priv->filter);
2632 tp_clear_object (&priv->store);
2634 /* Set the new store */
2635 priv->store = store;
2637 if (store != NULL)
2639 g_object_ref (store);
2641 /* Create a new filter */
2642 priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
2643 GTK_TREE_MODEL (priv->store), NULL));
2644 gtk_tree_model_filter_set_visible_func (priv->filter,
2645 individual_view_filter_visible_func, self, NULL);
2647 g_signal_connect (priv->filter, "row-has-child-toggled",
2648 G_CALLBACK (individual_view_row_has_child_toggled_cb), self);
2649 gtk_tree_view_set_model (GTK_TREE_VIEW (self),
2650 GTK_TREE_MODEL (priv->filter));
2654 void
2655 empathy_individual_view_start_search (EmpathyIndividualView *self)
2657 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2659 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2660 g_return_if_fail (priv->search_widget != NULL);
2662 if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
2663 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
2664 else
2665 gtk_widget_show (GTK_WIDGET (priv->search_widget));
2668 void
2669 empathy_individual_view_set_custom_filter (EmpathyIndividualView *self,
2670 GtkTreeModelFilterVisibleFunc filter,
2671 gpointer data)
2673 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2675 priv->custom_filter = filter;
2676 priv->custom_filter_data = data;
2679 void
2680 empathy_individual_view_refilter (EmpathyIndividualView *self)
2682 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2684 gtk_tree_model_filter_refilter (priv->filter);
2687 void
2688 empathy_individual_view_select_first (EmpathyIndividualView *self)
2690 EmpathyIndividualViewPriv *priv = GET_PRIV (self);
2691 GtkTreeIter iter;
2693 gtk_tree_model_filter_refilter (priv->filter);
2695 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->filter), &iter))
2697 GtkTreeSelection *selection = gtk_tree_view_get_selection (
2698 GTK_TREE_VIEW (self));
2700 gtk_tree_selection_select_iter (selection, &iter);
2704 void
2705 empathy_individual_view_set_show_uninteresting (EmpathyIndividualView *self,
2706 gboolean show_uninteresting)
2708 EmpathyIndividualViewPriv *priv;
2710 g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
2712 priv = GET_PRIV (self);
2714 priv->show_uninteresting = show_uninteresting;
2716 g_object_notify (G_OBJECT (self), "show-uninteresting");
2717 gtk_tree_model_filter_refilter (priv->filter);