]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-chooser.c
individual-widget: display channels list if available
[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;
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
231   persona = tpf_persona_new (contacts[0], store);
232
233   gee_collection_add (GEE_COLLECTION (personas), persona);
234
235   individual = folks_individual_new (personas);
236
237   /* listen for updates to the capabilities */
238   tp_g_signal_connect_object (contacts[0], "notify::capabilities",
239       G_CALLBACK (contact_capabilities_changed), self, 0);
240
241   /* Pass ownership to the list */
242   ctx->individuals = g_list_prepend (ctx->individuals, individual);
243
244   individual_store_add_individual_and_connect (self->priv->store, individual);
245
246   /* if nothing is selected, select the first matching node */
247   if (!gtk_tree_selection_get_selected (
248         gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view)),
249         NULL, NULL))
250     empathy_individual_view_select_first (self->priv->view);
251
252   g_clear_object (&persona);
253   g_clear_object (&personas);
254   g_object_unref (store);
255 }
256
257 static void
258 add_temporary_individuals (EmpathyContactChooser *self,
259     const gchar *id)
260 {
261   GList *accounts, *l;
262
263   tp_clear_pointer (&self->priv->add_temp_ctx,
264       add_temporary_individual_ctx_free);
265
266   if (tp_str_empty (id))
267     return;
268
269   self->priv->add_temp_ctx = add_temporary_individual_ctx_new (self);
270
271   /* Try to add an individual for each connected account */
272   accounts = tp_account_manager_get_valid_accounts (self->priv->account_mgr);
273   for (l = accounts; l != NULL; l = g_list_next (l))
274     {
275       TpAccount *account = l->data;
276       TpConnection *conn;
277       TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS,
278           TP_CONTACT_FEATURE_AVATAR_DATA,
279           TP_CONTACT_FEATURE_PRESENCE,
280           TP_CONTACT_FEATURE_CAPABILITIES };
281
282       conn = tp_account_get_connection (account);
283       if (conn == NULL)
284         continue;
285
286       tp_connection_get_contacts_by_id (conn, 1, &id, G_N_ELEMENTS (features),
287           features, get_contacts_cb, self->priv->add_temp_ctx, NULL,
288           G_OBJECT (self));
289     }
290
291   g_list_free (accounts);
292 }
293
294 static void
295 search_text_changed (GtkEntry *entry,
296     EmpathyContactChooser *self)
297 {
298   const gchar *id;
299
300   tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
301   tp_clear_pointer (&self->priv->search_str, g_free);
302
303   id = gtk_entry_get_text (entry);
304
305   self->priv->search_words = empathy_live_search_strip_utf8_string (id);
306   self->priv->search_str = g_strdup (id);
307
308   add_temporary_individuals (self, id);
309
310   empathy_individual_view_refilter (self->priv->view);
311 }
312
313 static void
314 search_activate_cb (GtkEntry *entry,
315     EmpathyContactChooser *self)
316 {
317   g_signal_emit (self, signals[SIG_ACTIVATE], 0);
318 }
319
320 static void
321 view_activate_cb (GtkTreeView *view,
322     GtkTreePath *path,
323     GtkTreeViewColumn *column,
324     EmpathyContactChooser *self)
325 {
326   g_signal_emit (self, signals[SIG_ACTIVATE], 0);
327 }
328
329 static gboolean
330 search_key_press_cb (GtkEntry *entry,
331     GdkEventKey *event,
332     EmpathyContactChooser *self)
333 {
334   GtkTreeSelection *selection;
335   GtkTreeModel *model;
336   GtkTreeIter iter;
337
338   if (event->state != 0)
339     return FALSE;
340
341   switch (event->keyval)
342     {
343       case GDK_KEY_Down:
344       case GDK_KEY_KP_Down:
345       case GDK_KEY_Up:
346       case GDK_KEY_KP_Up:
347         break;
348
349       default:
350         return FALSE;
351     }
352
353   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view));
354
355   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
356     return TRUE;
357
358   switch (event->keyval)
359     {
360       case GDK_KEY_Down:
361       case GDK_KEY_KP_Down:
362         if (!gtk_tree_model_iter_next (model, &iter))
363           return TRUE;
364
365         break;
366
367       case GDK_KEY_Up:
368       case GDK_KEY_KP_Up:
369         if (!gtk_tree_model_iter_previous (model, &iter))
370           return TRUE;
371
372         break;
373
374       default:
375         g_assert_not_reached ();
376     }
377
378   gtk_tree_selection_select_iter (selection, &iter);
379
380   return TRUE;
381 }
382
383 static void
384 empathy_contact_chooser_init (EmpathyContactChooser *self)
385 {
386   EmpathyIndividualManager *mgr;
387   GtkTreeSelection *selection;
388   GQuark features[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE, 0 };
389
390   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EMPATHY_TYPE_CONTACT_CHOOSER,
391       EmpathyContactChooserPrivate);
392
393   self->priv->account_mgr = tp_account_manager_dup ();
394
395   /* We don't wait for the CORE feature to be prepared, which is fine as we
396    * won't use the account manager until user starts searching. Furthermore,
397    * the AM has probably already been prepared by another Empathy
398    * component. */
399   tp_proxy_prepare_async (self->priv->account_mgr, features, NULL, NULL);
400
401   /* Search entry */
402   self->priv->search_entry = gtk_entry_new ();
403   gtk_box_pack_start (GTK_BOX (self), self->priv->search_entry, FALSE, TRUE, 6);
404   gtk_widget_show (self->priv->search_entry);
405
406   g_signal_connect (self->priv->search_entry, "changed",
407       G_CALLBACK (search_text_changed), self);
408   g_signal_connect (self->priv->search_entry, "activate",
409       G_CALLBACK (search_activate_cb), self);
410   g_signal_connect (self->priv->search_entry, "key-press-event",
411       G_CALLBACK (search_key_press_cb), self);
412
413   /* Add the treeview */
414   mgr = empathy_individual_manager_dup_singleton ();
415   self->priv->store = empathy_individual_store_new (mgr);
416   g_object_unref (mgr);
417
418   empathy_individual_store_set_show_groups (self->priv->store, FALSE);
419
420   self->priv->view = empathy_individual_view_new (self->priv->store,
421       EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE);
422
423   empathy_individual_view_set_custom_filter (self->priv->view,
424       filter_func, self);
425
426   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view));
427
428   g_signal_connect (selection, "changed",
429       G_CALLBACK (view_selection_changed_cb), self);
430   g_signal_connect (self->priv->view, "row-activated",
431       G_CALLBACK (view_activate_cb), self);
432
433   self->priv->scroll_view = gtk_scrolled_window_new (NULL, NULL);
434
435   gtk_container_add (GTK_CONTAINER (self->priv->scroll_view),
436       GTK_WIDGET (self->priv->view));
437
438   gtk_box_pack_start (GTK_BOX (self), self->priv->scroll_view, TRUE, TRUE, 6);
439   gtk_widget_show (GTK_WIDGET (self->priv->view));
440   gtk_widget_show (self->priv->scroll_view);
441 }
442
443 GtkWidget *
444 empathy_contact_chooser_new (void)
445 {
446   return g_object_new (EMPATHY_TYPE_CONTACT_CHOOSER,
447       "orientation", GTK_ORIENTATION_VERTICAL,
448       NULL);
449 }
450
451 FolksIndividual *
452 empathy_contact_chooser_dup_selected (EmpathyContactChooser *self)
453 {
454   return empathy_individual_view_dup_selected (self->priv->view);
455 }
456
457 void
458 empathy_contact_chooser_set_filter_func (EmpathyContactChooser *self,
459     EmpathyContactChooserFilterFunc func,
460     gpointer user_data)
461 {
462   g_assert (self->priv->filter_func == NULL);
463
464   self->priv->filter_func = func;
465   self->priv->filter_data = user_data;
466 }
467
468 void
469 empathy_contact_chooser_show_search_entry (EmpathyContactChooser *self,
470     gboolean show)
471 {
472   gtk_widget_set_visible (self->priv->search_entry, show);
473 }
474
475 void
476 empathy_contact_chooser_show_tree_view (EmpathyContactChooser *self,
477     gboolean show)
478 {
479   gtk_widget_set_visible (GTK_WIDGET (self->priv->scroll_view), show);
480 }