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