]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-chooser.c
Implement filtering logic 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 /* Return the TpContact of @individual which is on the same connection as the
196  * EmpathyTpChat */
197 static TpContact *
198 get_tp_contact_for_chat (EmpathyContactChooser *self,
199     FolksIndividual *individual)
200 {
201   TpContact *contact = NULL;
202   TpConnection *chat_conn;
203   GeeSet *personas;
204   GeeIterator *iter;
205
206   chat_conn = tp_channel_borrow_connection (TP_CHANNEL (self->priv->tp_chat));
207
208   personas = folks_individual_get_personas (individual);
209   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
210   while (contact == FALSE && gee_iterator_next (iter))
211     {
212       TpfPersona *persona = gee_iterator_get (iter);
213       TpConnection *contact_conn;
214       TpContact *contact_cur = NULL;
215
216       if (TPF_IS_PERSONA (persona))
217         {
218           contact_cur = tpf_persona_get_contact (persona);
219           if (contact_cur != NULL)
220             {
221               contact_conn = tp_contact_get_connection (contact_cur);
222
223               if (!tp_strdiff (tp_proxy_get_object_path (contact_conn),
224                     tp_proxy_get_object_path (chat_conn)))
225                 contact = contact_cur;
226             }
227         }
228
229       g_clear_object (&persona);
230     }
231   g_clear_object (&iter);
232
233   return contact;
234 }
235
236 static gboolean
237 filter_func (GtkTreeModel *model,
238     GtkTreeIter *iter,
239     gpointer user_data)
240 {
241   EmpathyContactChooser *self = user_data;
242   FolksIndividual *individual;
243   gboolean is_online;
244   gboolean display = FALSE;
245   gboolean searching = FALSE;
246
247   gtk_tree_model_get (model, iter,
248       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
249       EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
250       -1);
251
252   if (individual == NULL)
253     goto out;
254
255   if (self->priv->search_words != NULL)
256     {
257       searching = TRUE;
258
259       /* Filter out the contact if we are searching and it doesn't match */
260       if (!empathy_individual_match_string (individual,
261             self->priv->search_str, self->priv->search_words))
262         goto out;
263     }
264
265   if (self->priv->filter_func == NULL)
266     display = TRUE;
267   else
268     display = self->priv->filter_func (self, individual, is_online, searching,
269       self->priv->filter_data);
270 out:
271   tp_clear_object (&individual);
272   return display;
273 }
274
275 static void
276 get_contacts_cb (TpConnection *connection,
277     guint n_contacts,
278     TpContact * const *contacts,
279     const gchar * const *requested_ids,
280     GHashTable *failed_id_errors,
281     const GError *error,
282     gpointer user_data,
283     GObject *weak_object)
284 {
285   EmpathyContactChooser *self =
286     (EmpathyContactChooser *) weak_object;
287   AddTemporaryIndividualCtx *ctx = user_data;
288   TpAccount *account;
289   TpfPersonaStore *store;
290   FolksIndividual *individual;
291   TpfPersona *persona_new;
292   GeeSet *personas;
293
294   if (self->priv->add_temp_ctx != ctx)
295     /* another request has been started */
296     return;
297
298   if (n_contacts != 1)
299     return;
300
301   account = g_object_get_data (G_OBJECT (connection), "account");
302
303   store = tpf_persona_store_new (account);
304   personas = GEE_SET (
305       gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref, g_object_unref,
306       g_direct_hash, g_direct_equal));
307   persona_new = tpf_persona_new (contacts[0], store);
308   gee_collection_add (GEE_COLLECTION (personas),
309       tpf_persona_new (contacts[0], store));
310
311   individual = folks_individual_new (personas);
312
313   /* Pass ownership to the list */
314   ctx->individuals = g_list_prepend (ctx->individuals, individual);
315
316   individual_store_add_individual_and_connect (self->priv->store, individual);
317
318   g_clear_object (&persona_new);
319   g_clear_object (&personas);
320   g_object_unref (store);
321 }
322
323 static void
324 add_temporary_individuals (EmpathyContactChooser *self,
325     const gchar *id)
326 {
327   GList *accounts, *l;
328
329   tp_clear_pointer (&self->priv->add_temp_ctx,
330       add_temporary_individual_ctx_free);
331
332   if (tp_str_empty (id))
333     return;
334
335   self->priv->add_temp_ctx = add_temporary_individual_ctx_new (self);
336
337   /* Try to add an individual for each connected account */
338   accounts = tp_account_manager_get_valid_accounts (self->priv->account_mgr);
339   for (l = accounts; l != NULL; l = g_list_next (l))
340     {
341       TpAccount *account = l->data;
342       TpConnection *conn;
343       TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS,
344           TP_CONTACT_FEATURE_AVATAR_DATA,
345           TP_CONTACT_FEATURE_PRESENCE,
346           TP_CONTACT_FEATURE_CAPABILITIES };
347
348       conn = tp_account_get_connection (account);
349       if (conn == NULL)
350         continue;
351
352       /* One day we'll have tp_connection_get_account()... */
353       g_object_set_data_full (G_OBJECT (conn), "account",
354           g_object_ref (account), g_object_unref);
355
356       tp_connection_get_contacts_by_id (conn, 1, &id, G_N_ELEMENTS (features),
357           features, get_contacts_cb, self->priv->add_temp_ctx, NULL,
358           G_OBJECT (self));
359     }
360
361   g_list_free (accounts);
362 }
363
364 static void
365 search_text_changed (GtkEntry *entry,
366     EmpathyContactChooser *self)
367 {
368   const gchar *id;
369
370   tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
371   tp_clear_pointer (&self->priv->search_str, g_free);
372
373   id = gtk_entry_get_text (entry);
374
375   self->priv->search_words = empathy_live_search_strip_utf8_string (id);
376   self->priv->search_str = g_strdup (id);
377
378   add_temporary_individuals (self, id);
379
380   empathy_individual_view_refilter (self->priv->view);
381 }
382
383 static void
384 empathy_contact_chooser_init (EmpathyContactChooser *self)
385 {
386   EmpathyIndividualManager *mgr;
387   GtkTreeSelection *selection;
388   GtkWidget *scroll;
389   GtkWidget *search_entry;
390   GQuark features[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE, 0 };
391
392   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EMPATHY_TYPE_CONTACT_CHOOSER,
393       EmpathyContactChooserPrivate);
394
395   self->priv->account_mgr = tp_account_manager_dup ();
396
397   /* We don't wait for the CORE feature to be prepared, which is fine as we
398    * won't use the account manager until user starts searching. Furthermore,
399    * the AM has probably already been prepared by another Empathy
400    * component. */
401   tp_proxy_prepare_async (self->priv->account_mgr, features, NULL, NULL);
402
403   /* Search entry */
404   search_entry = gtk_entry_new ();
405   gtk_box_pack_start (GTK_BOX (self), search_entry, FALSE, TRUE, 6);
406   gtk_widget_show (search_entry);
407
408   g_signal_connect (search_entry, "changed",
409       G_CALLBACK (search_text_changed), self);
410
411   /* Add the treeview */
412   mgr = empathy_individual_manager_dup_singleton ();
413   self->priv->store = empathy_individual_store_new (mgr);
414   g_object_unref (mgr);
415
416   empathy_individual_store_set_show_groups (self->priv->store, FALSE);
417
418   self->priv->view = empathy_individual_view_new (self->priv->store,
419       EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE);
420
421   empathy_individual_view_set_custom_filter (self->priv->view,
422       filter_func, self);
423
424   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view));
425
426   g_signal_connect (selection, "changed",
427       G_CALLBACK (view_selection_changed_cb), self);
428
429   scroll = gtk_scrolled_window_new (NULL, NULL);
430
431   gtk_container_add (GTK_CONTAINER (scroll), GTK_WIDGET (self->priv->view));
432
433   gtk_box_pack_start (GTK_BOX (self), scroll, TRUE, TRUE, 6);
434   gtk_widget_show (GTK_WIDGET (self->priv->view));
435   gtk_widget_show (scroll);
436 }
437
438 GtkWidget *
439 empathy_contact_chooser_new (EmpathyTpChat *tp_chat)
440 {
441   g_return_val_if_fail (EMPATHY_IS_TP_CHAT (tp_chat), NULL);
442
443   return g_object_new (EMPATHY_TYPE_CONTACT_CHOOSER,
444       "orientation", GTK_ORIENTATION_VERTICAL,
445       "tp-chat", tp_chat,
446       NULL);
447 }
448
449 TpContact *
450 empathy_contact_chooser_get_selected (EmpathyContactChooser *self)
451 {
452   FolksIndividual *individual;
453   TpContact *contact;
454
455   individual = empathy_individual_view_dup_selected (self->priv->view);
456   if (individual == NULL)
457     return NULL;
458
459   contact = get_tp_contact_for_chat (self, individual);
460
461   g_object_unref (individual);
462   return contact;
463 }
464
465 void
466 empathy_contact_chooser_set_filter_func (EmpathyContactChooser *self,
467     EmpathyContactChooserFilterFunc func,
468     gpointer user_data)
469 {
470   g_assert (self->priv->filter_func == NULL);
471
472   self->priv->filter_func = func;
473   self->priv->filter_data = user_data;
474 }