1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2010 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
24 * Travis Reitter <travis.reitter@collabora.co.uk>
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)
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
;
75 guint auto_scroll_timeout_id
;
76 /* Distance between mouse pointer and the nearby border. Negative when
80 GtkTreeModelFilterVisibleFunc custom_filter
;
81 gpointer custom_filter_data
;
83 GtkCellRenderer
*text_renderer
;
84 } EmpathyIndividualViewPriv
;
88 EmpathyIndividualView
*view
;
95 EmpathyIndividualView
*view
;
96 FolksIndividual
*individual
;
105 PROP_INDIVIDUAL_FEATURES
,
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) */
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
,
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
),
140 static GdkAtom drag_atoms_dest
[G_N_ELEMENTS (drag_types_dest
)];
144 DRAG_INDIVIDUAL_RECEIVED
,
145 DRAG_PERSONA_RECEIVED
,
149 static guint signals
[LAST_SIGNAL
];
151 G_DEFINE_TYPE (EmpathyIndividualView
, empathy_individual_view
,
155 individual_view_tooltip_destroy_cb (GtkWidget
*widget
,
156 EmpathyIndividualView
*view
)
158 EmpathyIndividualViewPriv
*priv
= GET_PRIV (view
);
160 tp_clear_object (&priv
->tooltip_widget
);
164 individual_view_query_tooltip_cb (EmpathyIndividualView
*view
,
167 gboolean keyboard_mode
,
171 EmpathyIndividualViewPriv
*priv
;
172 FolksIndividual
*individual
;
176 static gint running
= 0;
177 gboolean ret
= FALSE
;
179 priv
= GET_PRIV (view
);
181 /* Avoid an infinite loop. See GNOME bug #574377 */
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
)
191 if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view
), &x
, &y
,
192 keyboard_mode
, &model
, &path
, &iter
))
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
,
201 if (individual
== NULL
)
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
);
220 empathy_individual_widget_set_individual (
221 EMPATHY_INDIVIDUAL_WIDGET (priv
->tooltip_widget
), individual
);
224 gtk_tooltip_set_custom (tooltip
, priv
->tooltip_widget
);
227 g_object_unref (individual
);
235 groups_change_group_cb (GObject
*source
,
236 GAsyncResult
*result
,
239 FolksGroupDetails
*group_details
= FOLKS_GROUP_DETAILS (source
);
240 GError
*error
= NULL
;
242 folks_group_details_change_group_finish (group_details
, result
, &error
);
245 g_warning ("failed to change group: %s", error
->message
);
246 g_clear_error (&error
);
251 group_can_be_modified (const gchar
*name
,
252 gboolean is_fake_group
,
255 /* Real groups can always be modified */
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
))
264 /* We can remove contacts from the 'ungrouped' fake group */
265 if (!adding
&& !tp_strdiff (name
, EMPATHY_INDIVIDUAL_STORE_UNGROUPED
))
272 individual_view_individual_drag_received (GtkWidget
*self
,
273 GdkDragContext
*context
,
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
))
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
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
);
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
))
317 if (!tp_strdiff (old_group
, new_group
))
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
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
);
341 /* FIXME: We should probably wait for the cb before calling
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
,
353 tp_clear_object (&manager
);
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
);
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 */
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
);
402 individual_view_persona_drag_received (GtkWidget
*self
,
403 GdkDragContext
*context
,
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
420 manager
= empathy_individual_manager_dup_singleton ();
421 individuals
= empathy_individual_manager_get_members (manager
);
423 for (l
= individuals
; l
!= NULL
; l
= l
->next
)
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
);
440 g_clear_object (&persona_cur
);
442 g_clear_object (&iter
);
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
);
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
,
462 tp_clear_object (&manager
);
463 tp_clear_object (&persona
);
464 tp_clear_object (&individual
);
470 individual_view_file_drag_received (GtkWidget
*view
,
471 GdkDragContext
*context
,
474 GtkSelectionData
*selection
)
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
)
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
);
499 individual_view_drag_data_received (GtkWidget
*view
,
500 GdkDragContext
*context
,
503 GtkSelectionData
*selection
,
509 GtkTreeViewDropPosition position
;
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
);
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
,
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
);
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;
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
564 individual_view_auto_scroll_cb (EmpathyIndividualView
*self
)
566 EmpathyIndividualViewPriv
*priv
= GET_PRIV (self
);
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
;
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
);
586 individual_view_drag_motion (GtkWidget
*widget
,
587 GdkDragContext
*context
,
592 EmpathyIndividualViewPriv
*priv
;
596 static DragMotionData
*dm
= NULL
;
599 gboolean is_different
= FALSE
;
600 gboolean cleanup
= TRUE
;
601 gboolean retval
= TRUE
;
602 GtkAllocation allocation
;
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);
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
);
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));
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);
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
;
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,
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
,
683 if (individual
!= NULL
)
685 EmpathyContact
*contact
= NULL
;
687 contact
= empathy_contact_dup_from_folks_individual (individual
);
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
);
705 gdk_drag_status (context
, 0, time_
);
706 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget
), NULL
, 0);
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.
731 We only highlight things if we have FEATURE_PERSONA_DROP.
733 GtkTreeIter group_iter
;
735 GtkTreePath
*group_path
;
736 gtk_tree_model_get (model
, &iter
,
737 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP
, &is_group
, -1);
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);
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
);
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
)
770 gtk_tree_path_free (dm
->path
);
773 g_source_remove (dm
->timeout_id
);
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
);
797 individual_view_drag_begin (GtkWidget
*widget
,
798 GdkDragContext
*context
)
800 EmpathyIndividualViewPriv
*priv
;
801 GtkTreeSelection
*selection
;
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
))
812 GTK_WIDGET_CLASS (empathy_individual_view_parent_class
)->drag_begin (widget
,
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
);
821 individual_view_drag_data_get (GtkWidget
*widget
,
822 GdkDragContext
*context
,
823 GtkSelectionData
*selection
,
827 EmpathyIndividualViewPriv
*priv
;
828 GtkTreePath
*src_path
;
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
)
840 src_path
= gtk_tree_row_reference_get_path (priv
->drag_row
);
841 if (src_path
== NULL
)
844 if (!gtk_tree_model_get_iter (model
, &iter
, src_path
))
846 gtk_tree_path_free (src_path
);
850 gtk_tree_path_free (src_path
);
853 empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget
));
854 if (individual
== NULL
)
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
);
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
,
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;
894 individual_view_drag_drop (GtkWidget
*widget
,
895 GdkDragContext
*drag_context
,
905 EmpathyIndividualView
*view
;
911 menu_deactivate_cb (GtkMenuShell
*menushell
,
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
));
922 individual_view_popup_menu_idle_cb (gpointer user_data
)
924 MenuPopupData
*data
= user_data
;
927 menu
= empathy_individual_view_get_individual_menu (data
->view
);
929 menu
= empathy_individual_view_get_group_menu (data
->view
);
933 gtk_menu_attach_to_widget (GTK_MENU (menu
), GTK_WIDGET (data
->view
),
935 gtk_widget_show (menu
);
936 gtk_menu_popup (GTK_MENU (menu
), NULL
, NULL
, NULL
, NULL
, data
->button
,
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
),
949 g_slice_free (MenuPopupData
, data
);
955 individual_view_button_press_event_cb (EmpathyIndividualView
*view
,
956 GdkEventButton
*event
,
959 if (event
->button
== 3)
963 data
= g_slice_new (MenuPopupData
);
965 data
->button
= event
->button
;
966 data
->time
= event
->time
;
967 g_idle_add (individual_view_popup_menu_idle_cb
, data
);
974 individual_view_key_press_event_cb (EmpathyIndividualView
*view
,
978 if (event
->keyval
== GDK_KEY_Menu
)
982 data
= g_slice_new (MenuPopupData
);
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
)
996 empathy_individual_edit_dialog_show (individual
, NULL
);
998 g_object_unref (individual
);
1005 individual_view_row_activated (GtkTreeView
*view
,
1007 GtkTreeViewColumn
*column
)
1009 EmpathyIndividualViewPriv
*priv
= GET_PRIV (view
);
1010 FolksIndividual
*individual
;
1011 EmpathyContact
*contact
;
1012 GtkTreeModel
*model
;
1015 if (!(priv
->individual_features
& EMPATHY_INDIVIDUAL_FEATURE_CHAT
))
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
)
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
);
1043 individual_view_call_activated_cb (EmpathyCellRendererActivatable
*cell
,
1044 const gchar
*path_string
,
1045 EmpathyIndividualView
*view
)
1047 EmpathyIndividualViewPriv
*priv
= GET_PRIV (view
);
1049 GtkTreeModel
*model
;
1051 FolksIndividual
*individual
;
1052 GdkEventButton
*event
;
1053 GtkMenuShell
*shell
;
1056 if (!(priv
->view_features
& EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_CALL
))
1059 model
= gtk_tree_view_get_model (GTK_TREE_VIEW (view
));
1060 if (!gtk_tree_model_get_iter_from_string (model
, &iter
, path_string
))
1063 gtk_tree_model_get (model
, &iter
,
1064 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL
, &individual
, -1);
1065 if (individual
== NULL
)
1068 event
= (GdkEventButton
*) gtk_get_current_event ();
1070 menu
= empathy_context_menu_new (GTK_WIDGET (view
));
1071 shell
= GTK_MENU_SHELL (menu
);
1074 item
= empathy_individual_audio_call_menu_item_new_individual (NULL
,
1076 gtk_menu_shell_append (shell
, item
);
1077 gtk_widget_show (item
);
1080 item
= empathy_individual_video_call_menu_item_new_individual (NULL
,
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
);
1093 individual_view_cell_set_background (EmpathyIndividualView
*view
,
1094 GtkCellRenderer
*cell
,
1098 if (!is_group
&& is_active
)
1100 GtkStyleContext
*style
;
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
,
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
1116 empathy_make_color_whiter (&color
);
1118 g_object_set (cell
, "cell-background-rgba", &color
, NULL
);
1121 g_object_set (cell
, "cell-background-rgba", NULL
, NULL
);
1125 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn
*tree_column
,
1126 GtkCellRenderer
*cell
,
1127 GtkTreeModel
*model
,
1129 EmpathyIndividualView
*view
)
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);
1141 "visible", !is_group
,
1145 tp_clear_object (&pixbuf
);
1147 individual_view_cell_set_background (view
, cell
, is_group
, is_active
);
1151 individual_view_group_icon_cell_data_func (GtkTreeViewColumn
*tree_column
,
1152 GtkCellRenderer
*cell
,
1153 GtkTreeModel
*model
,
1155 EmpathyIndividualView
*view
)
1157 GdkPixbuf
*pixbuf
= NULL
;
1161 gtk_tree_model_get (model
, iter
,
1162 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP
, &is_group
,
1163 EMPATHY_INDIVIDUAL_STORE_COL_NAME
, &name
, -1);
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
);
1181 "visible", pixbuf
!= NULL
,
1185 tp_clear_object (&pixbuf
);
1191 individual_view_audio_call_cell_data_func (GtkTreeViewColumn
*tree_column
,
1192 GtkCellRenderer
*cell
,
1193 GtkTreeModel
*model
,
1195 EmpathyIndividualView
*view
)
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);
1208 "visible", !is_group
&& (can_audio
|| can_video
),
1209 "icon-name", can_video
? EMPATHY_IMAGE_VIDEO_CALL
: EMPATHY_IMAGE_VOIP
,
1212 individual_view_cell_set_background (view
, cell
, is_group
, is_active
);
1216 individual_view_avatar_cell_data_func (GtkTreeViewColumn
*tree_column
,
1217 GtkCellRenderer
*cell
,
1218 GtkTreeModel
*model
,
1220 EmpathyIndividualView
*view
)
1223 gboolean show_avatar
;
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);
1234 "visible", !is_group
&& show_avatar
,
1238 tp_clear_object (&pixbuf
);
1240 individual_view_cell_set_background (view
, cell
, is_group
, is_active
);
1244 individual_view_text_cell_data_func (GtkTreeViewColumn
*tree_column
,
1245 GtkCellRenderer
*cell
,
1246 GtkTreeModel
*model
,
1248 EmpathyIndividualView
*view
)
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
);
1261 individual_view_expander_cell_data_func (GtkTreeViewColumn
*column
,
1262 GtkCellRenderer
*cell
,
1263 GtkTreeModel
*model
,
1265 EmpathyIndividualView
*view
)
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
))
1277 gboolean row_expanded
;
1279 path
= gtk_tree_model_get_path (model
, iter
);
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
);
1288 row_expanded
? GTK_EXPANDER_EXPANDED
: GTK_EXPANDER_COLLAPSED
,
1292 g_object_set (cell
, "visible", FALSE
, NULL
);
1294 individual_view_cell_set_background (view
, cell
, is_group
, is_active
);
1298 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView
*view
,
1303 EmpathyIndividualViewPriv
*priv
= GET_PRIV (view
);
1304 GtkTreeModel
*model
;
1308 if (!(priv
->view_features
& EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE
))
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
);
1323 individual_view_start_search_cb (EmpathyIndividualView
*view
,
1326 EmpathyIndividualViewPriv
*priv
= GET_PRIV (view
);
1328 if (priv
->search_widget
== NULL
)
1331 empathy_individual_view_start_search (view
);
1337 individual_view_search_text_notify_cb (TpawLiveSearch
*search
,
1339 EmpathyIndividualView
*view
)
1341 EmpathyIndividualViewPriv
*priv
= GET_PRIV (view
);
1343 GtkTreeViewColumn
*focus_column
;
1344 GtkTreeModel
*model
;
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
);
1360 path
= gtk_tree_path_new_from_string ("0:1");
1363 else if (gtk_tree_path_get_depth (path
) < 2)
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
,
1374 gtk_tree_path_down (path
);
1375 gtk_tree_path_next (path
);
1382 /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1384 if (gtk_tree_model_get_iter (model
, &iter
, path
))
1386 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view
), path
, focus_column
,
1391 gtk_tree_path_free (path
);
1395 individual_view_search_activate_cb (GtkWidget
*search
,
1396 EmpathyIndividualView
*view
)
1399 GtkTreeViewColumn
*focus_column
;
1401 gtk_tree_view_get_cursor (GTK_TREE_VIEW (view
), &path
, &focus_column
);
1404 gtk_tree_view_row_activated (GTK_TREE_VIEW (view
), path
, focus_column
);
1405 gtk_tree_path_free (path
);
1407 gtk_widget_hide (search
);
1412 individual_view_search_key_navigation_cb (GtkWidget
*search
,
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
);
1430 individual_view_search_hide_cb (TpawLiveSearch
*search
,
1431 EmpathyIndividualView
*view
)
1433 EmpathyIndividualViewPriv
*priv
= GET_PRIV (view
);
1434 GtkTreeModel
*model
;
1435 GtkTreePath
*cursor_path
;
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
))
1455 gtk_tree_model_get (model
, &iter
,
1456 EMPATHY_INDIVIDUAL_STORE_COL_NAME
, &name
,
1457 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP
, &is_group
,
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
);
1475 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view
), path
);
1478 gtk_tree_path_free (path
);
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
,
1495 gtk_tree_path_free (cursor_path
);
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
));
1514 expand_idle_foreach_cb (GtkTreeModel
*model
,
1517 EmpathyIndividualView
*self
)
1519 EmpathyIndividualViewPriv
*priv
;
1521 gpointer should_expand
;
1524 /* We only want groups */
1525 if (gtk_tree_path_get_depth (path
) > 1)
1528 gtk_tree_model_get (model
, iter
,
1529 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP
, &is_group
,
1530 EMPATHY_INDIVIDUAL_STORE_COL_NAME
, &name
,
1539 priv
= GET_PRIV (self
);
1541 if (g_hash_table_lookup_extended (priv
->expand_groups
, name
, NULL
,
1544 if (GPOINTER_TO_INT (should_expand
))
1545 gtk_tree_view_expand_row (GTK_TREE_VIEW (self
), path
, FALSE
);
1547 gtk_tree_view_collapse_row (GTK_TREE_VIEW (self
), path
);
1549 g_hash_table_remove (priv
->expand_groups
, name
);
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
);
1590 individual_view_row_has_child_toggled_cb (GtkTreeModel
*model
,
1593 EmpathyIndividualView
*view
)
1595 EmpathyIndividualViewPriv
*priv
= GET_PRIV (view
);
1596 gboolean should_expand
, is_group
= FALSE
;
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
,
1605 if (!is_group
|| TPAW_STR_EMPTY (name
))
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
,
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
));
1640 individual_view_is_visible_individual (EmpathyIndividualView
*self
,
1641 FolksIndividual
*individual
,
1643 gboolean is_searching
,
1645 gboolean is_fake_group
,
1648 EmpathyIndividualViewPriv
*priv
= GET_PRIV (self
);
1649 TpawLiveSearch
*live
= TPAW_LIVE_SEARCH (priv
->search_widget
);
1652 gboolean is_favorite
;
1654 /* Always display individuals having pending events */
1655 if (event_count
> 0)
1658 /* We're only giving the visibility wrt filtering here, not things like
1660 if (!priv
->show_untrusted
&&
1661 folks_individual_get_trust_level (individual
) == FOLKS_TRUST_LEVEL_NONE
)
1666 if (!priv
->show_uninteresting
)
1668 gboolean contains_interesting_persona
= FALSE
;
1670 /* Hide all individuals which consist entirely of uninteresting
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
)
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 */
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
));
1706 get_group (GtkTreeModel
*model
,
1710 GtkTreeIter parent_iter
;
1715 if (!gtk_tree_model_iter_parent (model
, &parent_iter
, iter
))
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
,
1728 individual_view_filter_visible_func (GtkTreeModel
*model
,
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
;
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
,
1756 if (individual
!= NULL
)
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
);
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
))
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
,
1791 if (individual
== NULL
)
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
);
1802 /* show group if it has at least one visible contact in it */
1810 static gchar
* empathy_individual_view_dup_selected_group (
1811 EmpathyIndividualView
*view
,
1812 gboolean
*is_fake_group
);
1815 text_edited_cb (GtkCellRendererText
*cellrenderertext
,
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
))
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
,
1844 g_object_unref (aggregator
);
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
);
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
;
1872 "headers-visible", FALSE
,
1873 "show-expanders", FALSE
,
1876 col
= gtk_tree_view_column_new ();
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
,
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
,
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
,
1939 g_object_set (cell
, "visible", FALSE
, NULL
);
1941 g_signal_connect (cell
, "path-activated",
1942 G_CALLBACK (individual_view_call_activated_cb
), view
);
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
,
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
,
1966 /* Actually add the column now we have added all cell renderers */
1967 gtk_tree_view_append_column (GTK_TREE_VIEW (view
), col
);
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
);
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
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
),
2002 G_N_ELEMENTS (drag_types_source
),
2003 GDK_ACTION_MOVE
| GDK_ACTION_COPY
);
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
,
2016 G_N_ELEMENTS (drag_types_dest
), GDK_ACTION_MOVE
| GDK_ACTION_COPY
);
2020 /* FIXME: URI could still be droped depending on FT feature */
2021 gtk_drag_dest_unset (GTK_WIDGET (view
));
2024 /* Update has-tooltip */
2026 (features
& EMPATHY_INDIVIDUAL_VIEW_FEATURE_INDIVIDUAL_TOOLTIP
) != 0;
2027 gtk_widget_set_has_tooltip (GTK_WIDGET (view
), has_tooltip
);
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
);
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
);
2058 individual_view_get_property (GObject
*object
,
2063 EmpathyIndividualViewPriv
*priv
;
2065 priv
= GET_PRIV (object
);
2070 g_value_set_object (value
, priv
->store
);
2072 case PROP_VIEW_FEATURES
:
2073 g_value_set_flags (value
, priv
->view_features
);
2075 case PROP_INDIVIDUAL_FEATURES
:
2076 g_value_set_flags (value
, priv
->individual_features
);
2078 case PROP_SHOW_OFFLINE
:
2079 g_value_set_boolean (value
, priv
->show_offline
);
2081 case PROP_SHOW_UNTRUSTED
:
2082 g_value_set_boolean (value
, priv
->show_untrusted
);
2084 case PROP_SHOW_UNINTERESTING
:
2085 g_value_set_boolean (value
, priv
->show_uninteresting
);
2088 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
2094 individual_view_set_property (GObject
*object
,
2096 const GValue
*value
,
2099 EmpathyIndividualView
*view
= EMPATHY_INDIVIDUAL_VIEW (object
);
2100 EmpathyIndividualViewPriv
*priv
= GET_PRIV (object
);
2105 empathy_individual_view_set_store (view
, g_value_get_object (value
));
2107 case PROP_VIEW_FEATURES
:
2108 individual_view_set_view_features (view
, g_value_get_flags (value
));
2110 case PROP_INDIVIDUAL_FEATURES
:
2111 priv
->individual_features
= g_value_get_flags (value
);
2113 case PROP_SHOW_OFFLINE
:
2114 empathy_individual_view_set_show_offline (view
,
2115 g_value_get_boolean (value
));
2117 case PROP_SHOW_UNTRUSTED
:
2118 empathy_individual_view_set_show_untrusted (view
,
2119 g_value_get_boolean (value
));
2121 case PROP_SHOW_UNINTERESTING
:
2122 empathy_individual_view_set_show_uninteresting (view
,
2123 g_value_get_boolean (value
));
2125 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
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
),
2161 G_STRUCT_OFFSET (EmpathyIndividualViewClass
, drag_individual_received
),
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
),
2171 G_STRUCT_OFFSET (EmpathyIndividualViewClass
, drag_persona_received
),
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
,
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
,
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
,
2199 g_param_spec_boolean ("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
));
2222 empathy_individual_view_init (EmpathyIndividualView
*view
)
2224 EmpathyIndividualViewPriv
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (view
,
2225 EMPATHY_TYPE_INDIVIDUAL_VIEW
, EmpathyIndividualViewPriv
);
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
,
2265 "individual-features", individual_features
,
2266 "view-features", view_features
, NULL
);
2270 empathy_individual_view_dup_selected (EmpathyIndividualView
*view
)
2272 GtkTreeSelection
*selection
;
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
))
2283 gtk_tree_model_get (model
, &iter
,
2284 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL
, &individual
, -1);
2290 empathy_individual_view_dup_selected_group (EmpathyIndividualView
*view
,
2291 gboolean
*is_fake_group
)
2293 GtkTreeSelection
*selection
;
2295 GtkTreeModel
*model
;
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
))
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);
2317 if (is_fake_group
!= NULL
)
2318 *is_fake_group
= fake
;
2325 REMOVE_DIALOG_RESPONSE_CANCEL
= 0,
2326 REMOVE_DIALOG_RESPONSE_DELETE
,
2330 individual_view_remove_dialog_show (GtkWindow
*parent
,
2331 const gchar
*message
,
2332 const gchar
*secondary_text
)
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
);
2355 individual_view_group_remove_activate_cb (GtkMenuItem
*menuitem
,
2356 EmpathyIndividualView
*view
)
2360 group
= empathy_individual_view_dup_selected_group (view
, NULL
);
2367 g_strdup_printf (_("Do you really want to remove the group '%s'?"),
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
));
2386 individual_view_group_rename_activate_cb (GtkMenuItem
*menuitem
,
2387 EmpathyIndividualView
*self
)
2389 EmpathyIndividualViewPriv
*priv
= GET_PRIV (self
);
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
))
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
);
2411 empathy_individual_view_get_group_menu (EmpathyIndividualView
*view
)
2413 EmpathyIndividualViewPriv
*priv
= GET_PRIV (view
);
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
)))
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 */
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
);
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 */
2475 individual
= empathy_individual_view_dup_selected (view
);
2476 if (individual
== NULL
)
2479 if (!empathy_folks_individual_contains_contact (individual
))
2482 menu
= empathy_individual_menu_new (individual
, NULL
,
2483 priv
->individual_features
, priv
->store
);
2486 g_object_unref (individual
);
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 */
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
);
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
));
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
;
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
);
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
;
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
;
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
;
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
));
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
));
2665 gtk_widget_show (GTK_WIDGET (priv
->search_widget
));
2669 empathy_individual_view_set_custom_filter (EmpathyIndividualView
*self
,
2670 GtkTreeModelFilterVisibleFunc filter
,
2673 EmpathyIndividualViewPriv
*priv
= GET_PRIV (self
);
2675 priv
->custom_filter
= filter
;
2676 priv
->custom_filter_data
= data
;
2680 empathy_individual_view_refilter (EmpathyIndividualView
*self
)
2682 EmpathyIndividualViewPriv
*priv
= GET_PRIV (self
);
2684 gtk_tree_model_filter_refilter (priv
->filter
);
2688 empathy_individual_view_select_first (EmpathyIndividualView
*self
)
2690 EmpathyIndividualViewPriv
*priv
= GET_PRIV (self
);
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
);
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
);