2 * empathy-contact-chooser.c
4 * EmpathyContactChooser
6 * (c) 2009, Collabora Ltd.
9 * Danielle Madeley <danielle.madeley@collabora.co.uk>
13 #include "empathy-contact-chooser.h"
15 #include "empathy-client-factory.h"
16 #include "empathy-individual-store-manager.h"
17 #include "empathy-individual-view.h"
18 #include "empathy-ui-utils.h"
19 #include "empathy-utils.h"
21 G_DEFINE_TYPE (EmpathyContactChooser
,
22 empathy_contact_chooser
, GTK_TYPE_BOX
);
25 SIG_SELECTION_CHANGED
,
30 static guint signals
[LAST_SIGNAL
];
32 typedef struct _AddTemporaryIndividualCtx AddTemporaryIndividualCtx
;
34 struct _EmpathyContactChooserPrivate
36 TpAccountManager
*account_mgr
;
38 EmpathyIndividualStore
*store
;
39 EmpathyIndividualView
*view
;
40 GtkWidget
*search_entry
;
41 GtkWidget
*scroll_view
;
43 GPtrArray
*search_words
;
46 /* Context representing the FolksIndividual which are added because of the
47 * current search from the user. */
48 AddTemporaryIndividualCtx
*add_temp_ctx
;
50 EmpathyContactChooserFilterFunc filter_func
;
53 /* list of reffed TpContact */
57 struct _AddTemporaryIndividualCtx
59 EmpathyContactChooser
*self
;
60 /* List of owned FolksIndividual */
64 static AddTemporaryIndividualCtx
*
65 add_temporary_individual_ctx_new (EmpathyContactChooser
*self
)
67 AddTemporaryIndividualCtx
*ctx
= g_slice_new0 (AddTemporaryIndividualCtx
);
74 add_temporary_individual_ctx_free (AddTemporaryIndividualCtx
*ctx
)
78 /* Remove all the individuals from the model */
79 for (l
= ctx
->individuals
; l
!= NULL
; l
= g_list_next (l
))
81 FolksIndividual
*individual
= l
->data
;
83 individual_store_remove_individual_and_disconnect (ctx
->self
->priv
->store
,
86 g_object_unref (individual
);
89 g_list_free (ctx
->individuals
);
90 g_slice_free (AddTemporaryIndividualCtx
, ctx
);
94 contact_chooser_dispose (GObject
*object
)
96 EmpathyContactChooser
*self
= (EmpathyContactChooser
*)
99 tp_clear_pointer (&self
->priv
->add_temp_ctx
,
100 add_temporary_individual_ctx_free
);
102 tp_clear_object (&self
->priv
->store
);
103 tp_clear_pointer (&self
->priv
->search_words
, g_ptr_array_unref
);
104 tp_clear_pointer (&self
->priv
->search_str
, g_free
);
106 tp_clear_object (&self
->priv
->account_mgr
);
108 g_list_free_full (self
->priv
->tp_contacts
, g_object_unref
);
109 self
->priv
->tp_contacts
= NULL
;
111 G_OBJECT_CLASS (empathy_contact_chooser_parent_class
)->dispose (
116 empathy_contact_chooser_class_init (
117 EmpathyContactChooserClass
*klass
)
119 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
121 object_class
->dispose
= contact_chooser_dispose
;
123 g_type_class_add_private (object_class
,
124 sizeof (EmpathyContactChooserPrivate
));
126 signals
[SIG_SELECTION_CHANGED
] = g_signal_new ("selection-changed",
127 G_TYPE_FROM_CLASS (klass
),
131 g_cclosure_marshal_generic
,
133 1, FOLKS_TYPE_INDIVIDUAL
);
135 signals
[SIG_ACTIVATE
] = g_signal_new ("activate",
136 G_TYPE_FROM_CLASS (klass
),
140 g_cclosure_marshal_generic
,
146 view_selection_changed_cb (GtkWidget
*treeview
,
147 EmpathyContactChooser
*self
)
149 FolksIndividual
*individual
;
151 individual
= empathy_individual_view_dup_selected (self
->priv
->view
);
153 g_signal_emit (self
, signals
[SIG_SELECTION_CHANGED
], 0, individual
);
155 tp_clear_object (&individual
);
159 filter_func (GtkTreeModel
*model
,
163 EmpathyContactChooser
*self
= user_data
;
164 FolksIndividual
*individual
;
166 gboolean display
= FALSE
;
167 gboolean searching
= FALSE
;
169 gtk_tree_model_get (model
, iter
,
170 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL
, &individual
,
171 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE
, &is_online
,
174 if (individual
== NULL
)
177 if (self
->priv
->search_words
!= NULL
)
181 /* Filter out the contact if we are searching and it doesn't match */
182 if (!empathy_individual_match_string (individual
,
183 self
->priv
->search_str
, self
->priv
->search_words
))
187 if (self
->priv
->filter_func
== NULL
)
190 display
= self
->priv
->filter_func (self
, individual
, is_online
, searching
,
191 self
->priv
->filter_data
);
193 tp_clear_object (&individual
);
198 contact_capabilities_changed (TpContact
*contact
,
200 EmpathyContactChooser
*self
)
202 empathy_individual_view_refilter (self
->priv
->view
);
206 get_contacts_cb (GObject
*source
,
207 GAsyncResult
*result
,
210 TpWeakRef
*wr
= user_data
;
211 AddTemporaryIndividualCtx
*ctx
;
212 EmpathyContactChooser
*self
;
213 GError
*error
= NULL
;
214 FolksIndividual
*individual
;
216 EmpathyContact
*emp_contact
= NULL
;
218 self
= tp_weak_ref_dup_object (wr
);
222 ctx
= tp_weak_ref_get_user_data (wr
);
224 emp_contact
= empathy_client_factory_dup_contact_by_id_finish (
225 EMPATHY_CLIENT_FACTORY (source
), result
, &error
);
226 if (emp_contact
== NULL
)
229 contact
= empathy_contact_get_tp_contact (emp_contact
);
231 if (self
->priv
->add_temp_ctx
!= ctx
)
232 /* another request has been started */
235 individual
= empathy_ensure_individual_from_tp_contact (contact
);
236 if (individual
== NULL
)
239 /* tp-glib will unref the TpContact once we return from this callback
240 * but folks expect us to keep a reference on the TpContact.
241 * Ideally folks shouldn't force us to do that: bgo #666580 */
242 self
->priv
->tp_contacts
= g_list_prepend (self
->priv
->tp_contacts
,
243 g_object_ref (contact
));
245 /* listen for updates to the capabilities */
246 tp_g_signal_connect_object (contact
, "notify::capabilities",
247 G_CALLBACK (contact_capabilities_changed
), self
, 0);
249 /* Pass ownership to the list */
250 ctx
->individuals
= g_list_prepend (ctx
->individuals
, individual
);
252 individual_store_add_individual_and_connect (self
->priv
->store
, individual
);
254 /* if nothing is selected, select the first matching node */
255 if (!gtk_tree_selection_get_selected (
256 gtk_tree_view_get_selection (GTK_TREE_VIEW (self
->priv
->view
)),
258 empathy_individual_view_select_first (self
->priv
->view
);
261 g_clear_object (&emp_contact
);
262 g_clear_object (&self
);
263 tp_weak_ref_destroy (wr
);
267 add_temporary_individuals (EmpathyContactChooser
*self
,
272 tp_clear_pointer (&self
->priv
->add_temp_ctx
,
273 add_temporary_individual_ctx_free
);
275 if (tp_str_empty (id
))
278 self
->priv
->add_temp_ctx
= add_temporary_individual_ctx_new (self
);
280 /* Try to add an individual for each connected account */
281 accounts
= tp_account_manager_dup_valid_accounts (self
->priv
->account_mgr
);
282 for (l
= accounts
; l
!= NULL
; l
= g_list_next (l
))
284 TpAccount
*account
= l
->data
;
286 EmpathyClientFactory
*factory
;
288 conn
= tp_account_get_connection (account
);
292 factory
= empathy_client_factory_dup ();
294 empathy_client_factory_dup_contact_by_id_async (factory
, conn
, id
,
296 tp_weak_ref_new (self
, self
->priv
->add_temp_ctx
, NULL
));
298 g_object_unref (factory
);
301 g_list_free_full (accounts
, g_object_unref
);
305 search_text_changed (GtkEntry
*entry
,
306 EmpathyContactChooser
*self
)
310 tp_clear_pointer (&self
->priv
->search_words
, g_ptr_array_unref
);
311 tp_clear_pointer (&self
->priv
->search_str
, g_free
);
313 id
= gtk_entry_get_text (entry
);
315 self
->priv
->search_words
= tpaw_live_search_strip_utf8_string (id
);
316 self
->priv
->search_str
= g_strdup (id
);
318 add_temporary_individuals (self
, id
);
320 empathy_individual_view_refilter (self
->priv
->view
);
324 search_activate_cb (GtkEntry
*entry
,
325 EmpathyContactChooser
*self
)
327 g_signal_emit (self
, signals
[SIG_ACTIVATE
], 0);
331 view_activate_cb (GtkTreeView
*view
,
333 GtkTreeViewColumn
*column
,
334 EmpathyContactChooser
*self
)
336 g_signal_emit (self
, signals
[SIG_ACTIVATE
], 0);
340 search_key_press_cb (GtkEntry
*entry
,
342 EmpathyContactChooser
*self
)
344 GtkTreeSelection
*selection
;
348 if (event
->state
!= 0)
351 switch (event
->keyval
)
354 case GDK_KEY_KP_Down
:
363 selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (self
->priv
->view
));
365 if (!gtk_tree_selection_get_selected (selection
, &model
, &iter
))
368 switch (event
->keyval
)
371 case GDK_KEY_KP_Down
:
372 if (!gtk_tree_model_iter_next (model
, &iter
))
379 if (!gtk_tree_model_iter_previous (model
, &iter
))
385 g_assert_not_reached ();
388 gtk_tree_selection_select_iter (selection
, &iter
);
394 empathy_contact_chooser_init (EmpathyContactChooser
*self
)
396 EmpathyIndividualManager
*mgr
;
397 GtkTreeSelection
*selection
;
398 GQuark features
[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE
, 0 };
400 self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
, EMPATHY_TYPE_CONTACT_CHOOSER
,
401 EmpathyContactChooserPrivate
);
403 self
->priv
->account_mgr
= tp_account_manager_dup ();
405 /* We don't wait for the CORE feature to be prepared, which is fine as we
406 * won't use the account manager until user starts searching. Furthermore,
407 * the AM has probably already been prepared by another Empathy
409 tp_proxy_prepare_async (self
->priv
->account_mgr
, features
, NULL
, NULL
);
412 self
->priv
->search_entry
= gtk_entry_new ();
413 gtk_box_pack_start (GTK_BOX (self
), self
->priv
->search_entry
, FALSE
, TRUE
, 6);
414 gtk_widget_show (self
->priv
->search_entry
);
416 g_signal_connect (self
->priv
->search_entry
, "changed",
417 G_CALLBACK (search_text_changed
), self
);
418 g_signal_connect (self
->priv
->search_entry
, "activate",
419 G_CALLBACK (search_activate_cb
), self
);
420 g_signal_connect (self
->priv
->search_entry
, "key-press-event",
421 G_CALLBACK (search_key_press_cb
), self
);
423 /* Add the treeview */
424 mgr
= empathy_individual_manager_dup_singleton ();
425 self
->priv
->store
= EMPATHY_INDIVIDUAL_STORE (
426 empathy_individual_store_manager_new (mgr
));
427 g_object_unref (mgr
);
429 empathy_individual_store_set_show_groups (self
->priv
->store
, FALSE
);
431 self
->priv
->view
= empathy_individual_view_new (self
->priv
->store
,
432 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE
, EMPATHY_INDIVIDUAL_FEATURE_NONE
);
434 empathy_individual_view_set_custom_filter (self
->priv
->view
,
437 selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (self
->priv
->view
));
439 g_signal_connect (selection
, "changed",
440 G_CALLBACK (view_selection_changed_cb
), self
);
441 g_signal_connect (self
->priv
->view
, "row-activated",
442 G_CALLBACK (view_activate_cb
), self
);
444 self
->priv
->scroll_view
= gtk_scrolled_window_new (NULL
, NULL
);
446 gtk_container_add (GTK_CONTAINER (self
->priv
->scroll_view
),
447 GTK_WIDGET (self
->priv
->view
));
449 gtk_box_pack_start (GTK_BOX (self
), self
->priv
->scroll_view
, TRUE
, TRUE
, 6);
450 gtk_widget_show (GTK_WIDGET (self
->priv
->view
));
451 gtk_widget_show (self
->priv
->scroll_view
);
455 empathy_contact_chooser_new (void)
457 return g_object_new (EMPATHY_TYPE_CONTACT_CHOOSER
,
458 "orientation", GTK_ORIENTATION_VERTICAL
,
462 /* Note that because of bgo #666580 the returned invidivdual is valid until
463 * @self is destroyed. To keep it around after that, the TpContact associated
464 * with the individual needs to be manually reffed. */
466 empathy_contact_chooser_dup_selected (EmpathyContactChooser
*self
)
468 return empathy_individual_view_dup_selected (self
->priv
->view
);
472 empathy_contact_chooser_set_filter_func (EmpathyContactChooser
*self
,
473 EmpathyContactChooserFilterFunc func
,
476 g_assert (self
->priv
->filter_func
== NULL
);
478 self
->priv
->filter_func
= func
;
479 self
->priv
->filter_data
= user_data
;
483 empathy_contact_chooser_show_search_entry (EmpathyContactChooser
*self
,
486 gtk_widget_set_visible (self
->priv
->search_entry
, show
);
490 empathy_contact_chooser_show_tree_view (EmpathyContactChooser
*self
,
493 gtk_widget_set_visible (GTK_WIDGET (self
->priv
->scroll_view
), show
);