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