]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-chooser.c
contact-chooser: add API to hide the search entry
[empathy.git] / libempathy-gtk / empathy-contact-chooser.c
1 /*
2  * empathy-contact-chooser.c
3  *
4  * EmpathyContactChooser
5  *
6  * (c) 2009, Collabora Ltd.
7  *
8  * Authors:
9  *    Danielle Madeley <danielle.madeley@collabora.co.uk>
10  */
11
12 #include <glib/gi18n.h>
13 #include <folks/folks-telepathy.h>
14
15 #include "empathy-contact-chooser.h"
16
17 #include <libempathy-gtk/empathy-individual-view.h>
18 #include <libempathy-gtk/empathy-ui-utils.h>
19
20 G_DEFINE_TYPE (EmpathyContactChooser,
21     empathy_contact_chooser, GTK_TYPE_BOX);
22
23 enum {
24   SIG_SELECTION_CHANGED,
25   LAST_SIGNAL
26 };
27
28 static guint signals[LAST_SIGNAL];
29
30 typedef struct _AddTemporaryIndividualCtx AddTemporaryIndividualCtx;
31
32 struct _EmpathyContactChooserPrivate
33 {
34   TpAccountManager *account_mgr;
35
36   EmpathyIndividualStore *store;
37   EmpathyIndividualView *view;
38   GtkWidget *search_entry;
39
40   GPtrArray *search_words;
41   gchar *search_str;
42
43   /* Context representing the FolksIndividual which are added because of the
44    * current search from the user. */
45   AddTemporaryIndividualCtx *add_temp_ctx;
46
47   EmpathyContactChooserFilterFunc filter_func;
48   gpointer filter_data;
49 };
50
51 struct _AddTemporaryIndividualCtx
52 {
53   EmpathyContactChooser *self;
54   /* List of owned FolksIndividual */
55   GList *individuals;
56 };
57
58 static AddTemporaryIndividualCtx *
59 add_temporary_individual_ctx_new (EmpathyContactChooser *self)
60 {
61   AddTemporaryIndividualCtx *ctx = g_slice_new0 (AddTemporaryIndividualCtx);
62
63   ctx->self = self;
64   return ctx;
65 }
66
67 static void
68 add_temporary_individual_ctx_free (AddTemporaryIndividualCtx *ctx)
69 {
70   GList *l;
71
72   /* Remove all the individuals from the model */
73   for (l = ctx->individuals; l != NULL; l = g_list_next (l))
74     {
75       FolksIndividual *individual = l->data;
76
77       individual_store_remove_individual_and_disconnect (ctx->self->priv->store,
78           individual);
79
80       g_object_unref (individual);
81     }
82
83   g_list_free (ctx->individuals);
84   g_slice_free (AddTemporaryIndividualCtx, ctx);
85 }
86
87 static void
88 contact_chooser_dispose (GObject *object)
89 {
90   EmpathyContactChooser *self = (EmpathyContactChooser *)
91     object;
92
93   tp_clear_pointer (&self->priv->add_temp_ctx,
94       add_temporary_individual_ctx_free);
95
96   tp_clear_object (&self->priv->store);
97   tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
98   tp_clear_pointer (&self->priv->search_str, g_free);
99
100   tp_clear_object (&self->priv->account_mgr);
101
102   G_OBJECT_CLASS (empathy_contact_chooser_parent_class)->dispose (
103       object);
104 }
105
106 static void
107 empathy_contact_chooser_class_init (
108     EmpathyContactChooserClass *klass)
109 {
110   GObjectClass *object_class = G_OBJECT_CLASS (klass);
111
112   object_class->dispose = contact_chooser_dispose;
113
114   g_type_class_add_private (object_class,
115       sizeof (EmpathyContactChooserPrivate));
116
117   signals[SIG_SELECTION_CHANGED] = g_signal_new ("selection-changed",
118             G_TYPE_FROM_CLASS (klass),
119             G_SIGNAL_RUN_LAST,
120             0,
121             NULL, NULL,
122             g_cclosure_marshal_generic,
123             G_TYPE_NONE,
124             1, FOLKS_TYPE_INDIVIDUAL);
125 }
126
127 static void
128 view_selection_changed_cb (GtkWidget *treeview,
129     EmpathyContactChooser *self)
130 {
131   FolksIndividual *individual;
132
133   individual = empathy_individual_view_dup_selected (self->priv->view);
134
135   g_signal_emit (self, signals[SIG_SELECTION_CHANGED], 0, individual);
136
137   tp_clear_object (&individual);
138 }
139
140 static gboolean
141 filter_func (GtkTreeModel *model,
142     GtkTreeIter *iter,
143     gpointer user_data)
144 {
145   EmpathyContactChooser *self = user_data;
146   FolksIndividual *individual;
147   gboolean is_online;
148   gboolean display = FALSE;
149   gboolean searching = FALSE;
150
151   gtk_tree_model_get (model, iter,
152       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
153       EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
154       -1);
155
156   if (individual == NULL)
157     goto out;
158
159   if (self->priv->search_words != NULL)
160     {
161       searching = TRUE;
162
163       /* Filter out the contact if we are searching and it doesn't match */
164       if (!empathy_individual_match_string (individual,
165             self->priv->search_str, self->priv->search_words))
166         goto out;
167     }
168
169   if (self->priv->filter_func == NULL)
170     display = TRUE;
171   else
172     display = self->priv->filter_func (self, individual, is_online, searching,
173       self->priv->filter_data);
174 out:
175   tp_clear_object (&individual);
176   return display;
177 }
178
179 static void
180 get_contacts_cb (TpConnection *connection,
181     guint n_contacts,
182     TpContact * const *contacts,
183     const gchar * const *requested_ids,
184     GHashTable *failed_id_errors,
185     const GError *error,
186     gpointer user_data,
187     GObject *weak_object)
188 {
189   EmpathyContactChooser *self =
190     (EmpathyContactChooser *) weak_object;
191   AddTemporaryIndividualCtx *ctx = user_data;
192   TpAccount *account;
193   TpfPersonaStore *store;
194   FolksIndividual *individual;
195   TpfPersona *persona_new;
196   GeeSet *personas;
197
198   if (self->priv->add_temp_ctx != ctx)
199     /* another request has been started */
200     return;
201
202   if (n_contacts != 1)
203     return;
204
205   account = tp_connection_get_account (connection);
206
207   store = tpf_persona_store_new (account);
208   personas = GEE_SET (
209       gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref, g_object_unref,
210       g_direct_hash, g_direct_equal));
211   persona_new = tpf_persona_new (contacts[0], store);
212   gee_collection_add (GEE_COLLECTION (personas),
213       tpf_persona_new (contacts[0], store));
214
215   individual = folks_individual_new (personas);
216
217   /* Pass ownership to the list */
218   ctx->individuals = g_list_prepend (ctx->individuals, individual);
219
220   individual_store_add_individual_and_connect (self->priv->store, individual);
221
222   g_clear_object (&persona_new);
223   g_clear_object (&personas);
224   g_object_unref (store);
225 }
226
227 static void
228 add_temporary_individuals (EmpathyContactChooser *self,
229     const gchar *id)
230 {
231   GList *accounts, *l;
232
233   tp_clear_pointer (&self->priv->add_temp_ctx,
234       add_temporary_individual_ctx_free);
235
236   if (tp_str_empty (id))
237     return;
238
239   self->priv->add_temp_ctx = add_temporary_individual_ctx_new (self);
240
241   /* Try to add an individual for each connected account */
242   accounts = tp_account_manager_get_valid_accounts (self->priv->account_mgr);
243   for (l = accounts; l != NULL; l = g_list_next (l))
244     {
245       TpAccount *account = l->data;
246       TpConnection *conn;
247       TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS,
248           TP_CONTACT_FEATURE_AVATAR_DATA,
249           TP_CONTACT_FEATURE_PRESENCE,
250           TP_CONTACT_FEATURE_CAPABILITIES };
251
252       conn = tp_account_get_connection (account);
253       if (conn == NULL)
254         continue;
255
256       tp_connection_get_contacts_by_id (conn, 1, &id, G_N_ELEMENTS (features),
257           features, get_contacts_cb, self->priv->add_temp_ctx, NULL,
258           G_OBJECT (self));
259     }
260
261   g_list_free (accounts);
262 }
263
264 static void
265 search_text_changed (GtkEntry *entry,
266     EmpathyContactChooser *self)
267 {
268   const gchar *id;
269
270   tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
271   tp_clear_pointer (&self->priv->search_str, g_free);
272
273   id = gtk_entry_get_text (entry);
274
275   self->priv->search_words = empathy_live_search_strip_utf8_string (id);
276   self->priv->search_str = g_strdup (id);
277
278   add_temporary_individuals (self, id);
279
280   empathy_individual_view_refilter (self->priv->view);
281 }
282
283 static void
284 empathy_contact_chooser_init (EmpathyContactChooser *self)
285 {
286   EmpathyIndividualManager *mgr;
287   GtkTreeSelection *selection;
288   GtkWidget *scroll;
289   GQuark features[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE, 0 };
290
291   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EMPATHY_TYPE_CONTACT_CHOOSER,
292       EmpathyContactChooserPrivate);
293
294   self->priv->account_mgr = tp_account_manager_dup ();
295
296   /* We don't wait for the CORE feature to be prepared, which is fine as we
297    * won't use the account manager until user starts searching. Furthermore,
298    * the AM has probably already been prepared by another Empathy
299    * component. */
300   tp_proxy_prepare_async (self->priv->account_mgr, features, NULL, NULL);
301
302   /* Search entry */
303   self->priv->search_entry = gtk_entry_new ();
304   gtk_box_pack_start (GTK_BOX (self), self->priv->search_entry, FALSE, TRUE, 6);
305   gtk_widget_show (self->priv->search_entry);
306
307   g_signal_connect (self->priv->search_entry, "changed",
308       G_CALLBACK (search_text_changed), self);
309
310   /* Add the treeview */
311   mgr = empathy_individual_manager_dup_singleton ();
312   self->priv->store = empathy_individual_store_new (mgr);
313   g_object_unref (mgr);
314
315   empathy_individual_store_set_show_groups (self->priv->store, FALSE);
316
317   self->priv->view = empathy_individual_view_new (self->priv->store,
318       EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE);
319
320   empathy_individual_view_set_custom_filter (self->priv->view,
321       filter_func, self);
322
323   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view));
324
325   g_signal_connect (selection, "changed",
326       G_CALLBACK (view_selection_changed_cb), self);
327
328   scroll = gtk_scrolled_window_new (NULL, NULL);
329
330   gtk_container_add (GTK_CONTAINER (scroll), GTK_WIDGET (self->priv->view));
331
332   gtk_box_pack_start (GTK_BOX (self), scroll, TRUE, TRUE, 6);
333   gtk_widget_show (GTK_WIDGET (self->priv->view));
334   gtk_widget_show (scroll);
335 }
336
337 GtkWidget *
338 empathy_contact_chooser_new (void)
339 {
340   return g_object_new (EMPATHY_TYPE_CONTACT_CHOOSER,
341       "orientation", GTK_ORIENTATION_VERTICAL,
342       NULL);
343 }
344
345 FolksIndividual *
346 empathy_contact_chooser_dup_selected (EmpathyContactChooser *self)
347 {
348   return empathy_individual_view_dup_selected (self->priv->view);
349 }
350
351 void
352 empathy_contact_chooser_set_filter_func (EmpathyContactChooser *self,
353     EmpathyContactChooserFilterFunc func,
354     gpointer user_data)
355 {
356   g_assert (self->priv->filter_func == NULL);
357
358   self->priv->filter_func = func;
359   self->priv->filter_data = user_data;
360 }
361
362 void
363 empathy_contact_chooser_show_search_entry (EmpathyContactChooser *self,
364     gboolean show)
365 {
366   gtk_widget_set_visible (self->priv->search_entry, show);
367 }