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