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