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