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