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 <glib/gi18n-lib.h>
17 #include "empathy-client-factory.h"
18 #include "empathy-individual-store-manager.h"
19 #include "empathy-individual-view.h"
20 #include "empathy-ui-utils.h"
21 #include "empathy-utils.h"
23 G_DEFINE_TYPE (EmpathyContactChooser
,
24 empathy_contact_chooser
, GTK_TYPE_BOX
);
27 SIG_SELECTION_CHANGED
,
32 static guint signals
[LAST_SIGNAL
];
34 typedef struct _AddTemporaryIndividualCtx AddTemporaryIndividualCtx
;
36 struct _EmpathyContactChooserPrivate
38 TpAccountManager
*account_mgr
;
40 EmpathyIndividualStore
*store
;
41 EmpathyIndividualView
*view
;
42 GtkWidget
*search_entry
;
43 GtkWidget
*scroll_view
;
45 GPtrArray
*search_words
;
48 /* Context representing the FolksIndividual which are added because of the
49 * current search from the user. */
50 AddTemporaryIndividualCtx
*add_temp_ctx
;
52 EmpathyContactChooserFilterFunc filter_func
;
55 /* list of reffed TpContact */
59 struct _AddTemporaryIndividualCtx
61 EmpathyContactChooser
*self
;
62 /* List of owned FolksIndividual */
66 static AddTemporaryIndividualCtx
*
67 add_temporary_individual_ctx_new (EmpathyContactChooser
*self
)
69 AddTemporaryIndividualCtx
*ctx
= g_slice_new0 (AddTemporaryIndividualCtx
);
76 add_temporary_individual_ctx_free (AddTemporaryIndividualCtx
*ctx
)
80 /* Remove all the individuals from the model */
81 for (l
= ctx
->individuals
; l
!= NULL
; l
= g_list_next (l
))
83 FolksIndividual
*individual
= l
->data
;
85 individual_store_remove_individual_and_disconnect (ctx
->self
->priv
->store
,
88 g_object_unref (individual
);
91 g_list_free (ctx
->individuals
);
92 g_slice_free (AddTemporaryIndividualCtx
, ctx
);
96 contact_chooser_dispose (GObject
*object
)
98 EmpathyContactChooser
*self
= (EmpathyContactChooser
*)
101 tp_clear_pointer (&self
->priv
->add_temp_ctx
,
102 add_temporary_individual_ctx_free
);
104 tp_clear_object (&self
->priv
->store
);
105 tp_clear_pointer (&self
->priv
->search_words
, g_ptr_array_unref
);
106 tp_clear_pointer (&self
->priv
->search_str
, g_free
);
108 tp_clear_object (&self
->priv
->account_mgr
);
110 g_list_free_full (self
->priv
->tp_contacts
, g_object_unref
);
111 self
->priv
->tp_contacts
= NULL
;
113 G_OBJECT_CLASS (empathy_contact_chooser_parent_class
)->dispose (
118 empathy_contact_chooser_class_init (
119 EmpathyContactChooserClass
*klass
)
121 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
123 object_class
->dispose
= contact_chooser_dispose
;
125 g_type_class_add_private (object_class
,
126 sizeof (EmpathyContactChooserPrivate
));
128 signals
[SIG_SELECTION_CHANGED
] = g_signal_new ("selection-changed",
129 G_TYPE_FROM_CLASS (klass
),
133 g_cclosure_marshal_generic
,
135 1, FOLKS_TYPE_INDIVIDUAL
);
137 signals
[SIG_ACTIVATE
] = g_signal_new ("activate",
138 G_TYPE_FROM_CLASS (klass
),
142 g_cclosure_marshal_generic
,
148 view_selection_changed_cb (GtkWidget
*treeview
,
149 EmpathyContactChooser
*self
)
151 FolksIndividual
*individual
;
153 individual
= empathy_individual_view_dup_selected (self
->priv
->view
);
155 g_signal_emit (self
, signals
[SIG_SELECTION_CHANGED
], 0, individual
);
157 tp_clear_object (&individual
);
161 filter_func (GtkTreeModel
*model
,
165 EmpathyContactChooser
*self
= user_data
;
166 FolksIndividual
*individual
;
168 gboolean display
= FALSE
;
169 gboolean searching
= FALSE
;
171 gtk_tree_model_get (model
, iter
,
172 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL
, &individual
,
173 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE
, &is_online
,
176 if (individual
== NULL
)
179 if (self
->priv
->search_words
!= NULL
)
183 /* Filter out the contact if we are searching and it doesn't match */
184 if (!empathy_individual_match_string (individual
,
185 self
->priv
->search_str
, self
->priv
->search_words
))
189 if (self
->priv
->filter_func
== NULL
)
192 display
= self
->priv
->filter_func (self
, individual
, is_online
, searching
,
193 self
->priv
->filter_data
);
195 tp_clear_object (&individual
);
200 contact_capabilities_changed (TpContact
*contact
,
202 EmpathyContactChooser
*self
)
204 empathy_individual_view_refilter (self
->priv
->view
);
208 get_contacts_cb (GObject
*source
,
209 GAsyncResult
*result
,
212 TpWeakRef
*wr
= user_data
;
213 AddTemporaryIndividualCtx
*ctx
;
214 EmpathyContactChooser
*self
;
215 GError
*error
= NULL
;
216 FolksIndividual
*individual
;
218 EmpathyContact
*emp_contact
= NULL
;
220 self
= tp_weak_ref_dup_object (wr
);
224 ctx
= tp_weak_ref_get_user_data (wr
);
226 emp_contact
= empathy_client_factory_dup_contact_by_id_finish (
227 EMPATHY_CLIENT_FACTORY (source
), result
, &error
);
228 if (emp_contact
== NULL
)
231 contact
= empathy_contact_get_tp_contact (emp_contact
);
233 if (self
->priv
->add_temp_ctx
!= ctx
)
234 /* another request has been started */
237 individual
= empathy_ensure_individual_from_tp_contact (contact
);
238 if (individual
== NULL
)
241 /* tp-glib will unref the TpContact once we return from this callback
242 * but folks expect us to keep a reference on the TpContact.
243 * Ideally folks shouldn't force us to do that: bgo #666580 */
244 self
->priv
->tp_contacts
= g_list_prepend (self
->priv
->tp_contacts
,
245 g_object_ref (contact
));
247 /* listen for updates to the capabilities */
248 tp_g_signal_connect_object (contact
, "notify::capabilities",
249 G_CALLBACK (contact_capabilities_changed
), self
, 0);
251 /* Pass ownership to the list */
252 ctx
->individuals
= g_list_prepend (ctx
->individuals
, individual
);
254 individual_store_add_individual_and_connect (self
->priv
->store
, individual
);
256 /* if nothing is selected, select the first matching node */
257 if (!gtk_tree_selection_get_selected (
258 gtk_tree_view_get_selection (GTK_TREE_VIEW (self
->priv
->view
)),
260 empathy_individual_view_select_first (self
->priv
->view
);
263 g_clear_object (&emp_contact
);
264 g_clear_object (&self
);
265 tp_weak_ref_destroy (wr
);
269 add_temporary_individuals (EmpathyContactChooser
*self
,
274 tp_clear_pointer (&self
->priv
->add_temp_ctx
,
275 add_temporary_individual_ctx_free
);
277 if (tp_str_empty (id
))
280 self
->priv
->add_temp_ctx
= add_temporary_individual_ctx_new (self
);
282 /* Try to add an individual for each connected account */
283 accounts
= tp_account_manager_dup_valid_accounts (self
->priv
->account_mgr
);
284 for (l
= accounts
; l
!= NULL
; l
= g_list_next (l
))
286 TpAccount
*account
= l
->data
;
288 EmpathyClientFactory
*factory
;
290 conn
= tp_account_get_connection (account
);
294 factory
= empathy_client_factory_dup ();
296 empathy_client_factory_dup_contact_by_id_async (factory
, conn
, id
,
298 tp_weak_ref_new (self
, self
->priv
->add_temp_ctx
, NULL
));
300 g_object_unref (factory
);
303 g_list_free_full (accounts
, g_object_unref
);
307 search_text_changed (GtkEntry
*entry
,
308 EmpathyContactChooser
*self
)
312 tp_clear_pointer (&self
->priv
->search_words
, g_ptr_array_unref
);
313 tp_clear_pointer (&self
->priv
->search_str
, g_free
);
315 id
= gtk_entry_get_text (entry
);
317 self
->priv
->search_words
= tpaw_live_search_strip_utf8_string (id
);
318 self
->priv
->search_str
= g_strdup (id
);
320 add_temporary_individuals (self
, id
);
322 empathy_individual_view_refilter (self
->priv
->view
);
326 search_activate_cb (GtkEntry
*entry
,
327 EmpathyContactChooser
*self
)
329 g_signal_emit (self
, signals
[SIG_ACTIVATE
], 0);
333 view_activate_cb (GtkTreeView
*view
,
335 GtkTreeViewColumn
*column
,
336 EmpathyContactChooser
*self
)
338 g_signal_emit (self
, signals
[SIG_ACTIVATE
], 0);
342 search_key_press_cb (GtkEntry
*entry
,
344 EmpathyContactChooser
*self
)
346 GtkTreeSelection
*selection
;
350 if (event
->state
!= 0)
353 switch (event
->keyval
)
356 case GDK_KEY_KP_Down
:
365 selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (self
->priv
->view
));
367 if (!gtk_tree_selection_get_selected (selection
, &model
, &iter
))
370 switch (event
->keyval
)
373 case GDK_KEY_KP_Down
:
374 if (!gtk_tree_model_iter_next (model
, &iter
))
381 if (!gtk_tree_model_iter_previous (model
, &iter
))
387 g_assert_not_reached ();
390 gtk_tree_selection_select_iter (selection
, &iter
);
396 empathy_contact_chooser_init (EmpathyContactChooser
*self
)
398 EmpathyIndividualManager
*mgr
;
399 GtkTreeSelection
*selection
;
400 GQuark features
[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE
, 0 };
402 self
->priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
, EMPATHY_TYPE_CONTACT_CHOOSER
,
403 EmpathyContactChooserPrivate
);
405 self
->priv
->account_mgr
= tp_account_manager_dup ();
407 /* We don't wait for the CORE feature to be prepared, which is fine as we
408 * won't use the account manager until user starts searching. Furthermore,
409 * the AM has probably already been prepared by another Empathy
411 tp_proxy_prepare_async (self
->priv
->account_mgr
, features
, NULL
, NULL
);
414 self
->priv
->search_entry
= gtk_search_entry_new ();
415 gtk_entry_set_placeholder_text ( GTK_ENTRY(self
->priv
->search_entry
),
416 _("Type to search a contact…"));
417 gtk_box_pack_start (GTK_BOX (self
), self
->priv
->search_entry
, FALSE
, TRUE
, 0);
418 gtk_widget_show (self
->priv
->search_entry
);
420 g_signal_connect (self
->priv
->search_entry
, "changed",
421 G_CALLBACK (search_text_changed
), self
);
422 g_signal_connect (self
->priv
->search_entry
, "activate",
423 G_CALLBACK (search_activate_cb
), self
);
424 g_signal_connect (self
->priv
->search_entry
, "key-press-event",
425 G_CALLBACK (search_key_press_cb
), self
);
427 /* Add the treeview */
428 mgr
= empathy_individual_manager_dup_singleton ();
429 self
->priv
->store
= EMPATHY_INDIVIDUAL_STORE (
430 empathy_individual_store_manager_new (mgr
));
431 g_object_unref (mgr
);
433 empathy_individual_store_set_show_groups (self
->priv
->store
, FALSE
);
435 self
->priv
->view
= empathy_individual_view_new (self
->priv
->store
,
436 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE
, EMPATHY_INDIVIDUAL_FEATURE_NONE
);
438 empathy_individual_view_set_custom_filter (self
->priv
->view
,
441 selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (self
->priv
->view
));
443 g_signal_connect (selection
, "changed",
444 G_CALLBACK (view_selection_changed_cb
), self
);
445 g_signal_connect (self
->priv
->view
, "row-activated",
446 G_CALLBACK (view_activate_cb
), self
);
448 self
->priv
->scroll_view
= gtk_scrolled_window_new (NULL
, NULL
);
450 gtk_container_add (GTK_CONTAINER (self
->priv
->scroll_view
),
451 GTK_WIDGET (self
->priv
->view
));
453 gtk_box_pack_start (GTK_BOX (self
), self
->priv
->scroll_view
, TRUE
, TRUE
, 0);
454 gtk_widget_show (GTK_WIDGET (self
->priv
->view
));
455 gtk_widget_show (self
->priv
->scroll_view
);
459 empathy_contact_chooser_new (void)
461 return g_object_new (EMPATHY_TYPE_CONTACT_CHOOSER
,
462 "orientation", GTK_ORIENTATION_VERTICAL
,
466 /* Note that because of bgo #666580 the returned invidivdual is valid until
467 * @self is destroyed. To keep it around after that, the TpContact associated
468 * with the individual needs to be manually reffed. */
470 empathy_contact_chooser_dup_selected (EmpathyContactChooser
*self
)
472 return empathy_individual_view_dup_selected (self
->priv
->view
);
476 empathy_contact_chooser_set_filter_func (EmpathyContactChooser
*self
,
477 EmpathyContactChooserFilterFunc func
,
480 g_assert (self
->priv
->filter_func
== NULL
);
482 self
->priv
->filter_func
= func
;
483 self
->priv
->filter_data
= user_data
;
487 empathy_contact_chooser_show_search_entry (EmpathyContactChooser
*self
,
490 gtk_widget_set_visible (self
->priv
->search_entry
, show
);
494 empathy_contact_chooser_show_tree_view (EmpathyContactChooser
*self
,
497 gtk_widget_set_visible (GTK_WIDGET (self
->priv
->scroll_view
), show
);