Updated Spanish translation
[empathy-mirror.git] / libempathy-gtk / empathy-contact-chooser.c
blob4a4e4b4fad8eaabdcb14471b227caa4f80735015
1 /*
2 * empathy-contact-chooser.c
4 * EmpathyContactChooser
6 * (c) 2009, Collabora Ltd.
8 * Authors:
9 * Danielle Madeley <danielle.madeley@collabora.co.uk>
12 #include "config.h"
13 #include "empathy-contact-chooser.h"
15 #include <glib/gi18n-lib.h>
17 #include "empathy-client-factory.h"
18 #include "empathy-individual-store-manager.h"
19 #include "empathy-individual-view.h"
20 #include "empathy-ui-utils.h"
21 #include "empathy-utils.h"
23 G_DEFINE_TYPE (EmpathyContactChooser,
24 empathy_contact_chooser, GTK_TYPE_BOX);
26 enum {
27 SIG_SELECTION_CHANGED,
28 SIG_ACTIVATE,
29 LAST_SIGNAL
32 static guint signals[LAST_SIGNAL];
34 typedef struct _AddTemporaryIndividualCtx AddTemporaryIndividualCtx;
36 struct _EmpathyContactChooserPrivate
38 TpAccountManager *account_mgr;
40 EmpathyIndividualStore *store;
41 EmpathyIndividualView *view;
42 GtkWidget *search_entry;
43 GtkWidget *scroll_view;
45 GPtrArray *search_words;
46 gchar *search_str;
48 /* Context representing the FolksIndividual which are added because of the
49 * current search from the user. */
50 AddTemporaryIndividualCtx *add_temp_ctx;
52 EmpathyContactChooserFilterFunc filter_func;
53 gpointer filter_data;
55 /* list of reffed TpContact */
56 GList *tp_contacts;
59 struct _AddTemporaryIndividualCtx
61 EmpathyContactChooser *self;
62 /* List of owned FolksIndividual */
63 GList *individuals;
66 static AddTemporaryIndividualCtx *
67 add_temporary_individual_ctx_new (EmpathyContactChooser *self)
69 AddTemporaryIndividualCtx *ctx = g_slice_new0 (AddTemporaryIndividualCtx);
71 ctx->self = self;
72 return ctx;
75 static void
76 add_temporary_individual_ctx_free (AddTemporaryIndividualCtx *ctx)
78 GList *l;
80 /* Remove all the individuals from the model */
81 for (l = ctx->individuals; l != NULL; l = g_list_next (l))
83 FolksIndividual *individual = l->data;
85 individual_store_remove_individual_and_disconnect (ctx->self->priv->store,
86 individual);
88 g_object_unref (individual);
91 g_list_free (ctx->individuals);
92 g_slice_free (AddTemporaryIndividualCtx, ctx);
95 static void
96 contact_chooser_dispose (GObject *object)
98 EmpathyContactChooser *self = (EmpathyContactChooser *)
99 object;
101 tp_clear_pointer (&self->priv->add_temp_ctx,
102 add_temporary_individual_ctx_free);
104 tp_clear_object (&self->priv->store);
105 tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
106 tp_clear_pointer (&self->priv->search_str, g_free);
108 tp_clear_object (&self->priv->account_mgr);
110 g_list_free_full (self->priv->tp_contacts, g_object_unref);
111 self->priv->tp_contacts = NULL;
113 G_OBJECT_CLASS (empathy_contact_chooser_parent_class)->dispose (
114 object);
117 static void
118 empathy_contact_chooser_class_init (
119 EmpathyContactChooserClass *klass)
121 GObjectClass *object_class = G_OBJECT_CLASS (klass);
123 object_class->dispose = contact_chooser_dispose;
125 g_type_class_add_private (object_class,
126 sizeof (EmpathyContactChooserPrivate));
128 signals[SIG_SELECTION_CHANGED] = g_signal_new ("selection-changed",
129 G_TYPE_FROM_CLASS (klass),
130 G_SIGNAL_RUN_LAST,
132 NULL, NULL,
133 g_cclosure_marshal_generic,
134 G_TYPE_NONE,
135 1, FOLKS_TYPE_INDIVIDUAL);
137 signals[SIG_ACTIVATE] = g_signal_new ("activate",
138 G_TYPE_FROM_CLASS (klass),
139 G_SIGNAL_RUN_LAST,
141 NULL, NULL,
142 g_cclosure_marshal_generic,
143 G_TYPE_NONE,
147 static void
148 view_selection_changed_cb (GtkWidget *treeview,
149 EmpathyContactChooser *self)
151 FolksIndividual *individual;
153 individual = empathy_individual_view_dup_selected (self->priv->view);
155 g_signal_emit (self, signals[SIG_SELECTION_CHANGED], 0, individual);
157 tp_clear_object (&individual);
160 static gboolean
161 filter_func (GtkTreeModel *model,
162 GtkTreeIter *iter,
163 gpointer user_data)
165 EmpathyContactChooser *self = user_data;
166 FolksIndividual *individual;
167 gboolean is_online;
168 gboolean display = FALSE;
169 gboolean searching = FALSE;
171 gtk_tree_model_get (model, iter,
172 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
173 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
174 -1);
176 if (individual == NULL)
177 goto out;
179 if (self->priv->search_words != NULL)
181 searching = TRUE;
183 /* Filter out the contact if we are searching and it doesn't match */
184 if (!empathy_individual_match_string (individual,
185 self->priv->search_str, self->priv->search_words))
186 goto out;
189 if (self->priv->filter_func == NULL)
190 display = TRUE;
191 else
192 display = self->priv->filter_func (self, individual, is_online, searching,
193 self->priv->filter_data);
194 out:
195 tp_clear_object (&individual);
196 return display;
199 static void
200 contact_capabilities_changed (TpContact *contact,
201 GParamSpec *pspec,
202 EmpathyContactChooser *self)
204 empathy_individual_view_refilter (self->priv->view);
207 static void
208 get_contacts_cb (GObject *source,
209 GAsyncResult *result,
210 gpointer user_data)
212 TpWeakRef *wr = user_data;
213 AddTemporaryIndividualCtx *ctx;
214 EmpathyContactChooser *self;
215 GError *error = NULL;
216 FolksIndividual *individual;
217 TpContact *contact;
218 EmpathyContact *emp_contact = NULL;
220 self = tp_weak_ref_dup_object (wr);
221 if (self == NULL)
222 goto out;
224 ctx = tp_weak_ref_get_user_data (wr);
226 emp_contact = empathy_client_factory_dup_contact_by_id_finish (
227 EMPATHY_CLIENT_FACTORY (source), result, &error);
228 if (emp_contact == NULL)
229 goto out;
231 contact = empathy_contact_get_tp_contact (emp_contact);
233 if (self->priv->add_temp_ctx != ctx)
234 /* another request has been started */
235 goto out;
237 individual = empathy_ensure_individual_from_tp_contact (contact);
238 if (individual == NULL)
239 goto out;
241 /* tp-glib will unref the TpContact once we return from this callback
242 * but folks expect us to keep a reference on the TpContact.
243 * Ideally folks shouldn't force us to do that: bgo #666580 */
244 self->priv->tp_contacts = g_list_prepend (self->priv->tp_contacts,
245 g_object_ref (contact));
247 /* listen for updates to the capabilities */
248 tp_g_signal_connect_object (contact, "notify::capabilities",
249 G_CALLBACK (contact_capabilities_changed), self, 0);
251 /* Pass ownership to the list */
252 ctx->individuals = g_list_prepend (ctx->individuals, individual);
254 individual_store_add_individual_and_connect (self->priv->store, individual);
256 /* if nothing is selected, select the first matching node */
257 if (!gtk_tree_selection_get_selected (
258 gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view)),
259 NULL, NULL))
260 empathy_individual_view_select_first (self->priv->view);
262 out:
263 g_clear_object (&emp_contact);
264 g_clear_object (&self);
265 tp_weak_ref_destroy (wr);
268 static void
269 add_temporary_individuals (EmpathyContactChooser *self,
270 const gchar *id)
272 GList *accounts, *l;
274 tp_clear_pointer (&self->priv->add_temp_ctx,
275 add_temporary_individual_ctx_free);
277 if (tp_str_empty (id))
278 return;
280 self->priv->add_temp_ctx = add_temporary_individual_ctx_new (self);
282 /* Try to add an individual for each connected account */
283 accounts = tp_account_manager_dup_valid_accounts (self->priv->account_mgr);
284 for (l = accounts; l != NULL; l = g_list_next (l))
286 TpAccount *account = l->data;
287 TpConnection *conn;
288 EmpathyClientFactory *factory;
290 conn = tp_account_get_connection (account);
291 if (conn == NULL)
292 continue;
294 factory = empathy_client_factory_dup ();
296 empathy_client_factory_dup_contact_by_id_async (factory, conn, id,
297 get_contacts_cb,
298 tp_weak_ref_new (self, self->priv->add_temp_ctx, NULL));
300 g_object_unref (factory);
303 g_list_free_full (accounts, g_object_unref);
306 static void
307 search_text_changed (GtkEntry *entry,
308 EmpathyContactChooser *self)
310 const gchar *id;
312 tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
313 tp_clear_pointer (&self->priv->search_str, g_free);
315 id = gtk_entry_get_text (entry);
317 self->priv->search_words = tpaw_live_search_strip_utf8_string (id);
318 self->priv->search_str = g_strdup (id);
320 add_temporary_individuals (self, id);
322 empathy_individual_view_refilter (self->priv->view);
325 static void
326 search_activate_cb (GtkEntry *entry,
327 EmpathyContactChooser *self)
329 g_signal_emit (self, signals[SIG_ACTIVATE], 0);
332 static void
333 view_activate_cb (GtkTreeView *view,
334 GtkTreePath *path,
335 GtkTreeViewColumn *column,
336 EmpathyContactChooser *self)
338 g_signal_emit (self, signals[SIG_ACTIVATE], 0);
341 static gboolean
342 search_key_press_cb (GtkEntry *entry,
343 GdkEventKey *event,
344 EmpathyContactChooser *self)
346 GtkTreeSelection *selection;
347 GtkTreeModel *model;
348 GtkTreeIter iter;
350 if (event->state != 0)
351 return FALSE;
353 switch (event->keyval)
355 case GDK_KEY_Down:
356 case GDK_KEY_KP_Down:
357 case GDK_KEY_Up:
358 case GDK_KEY_KP_Up:
359 break;
361 default:
362 return FALSE;
365 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view));
367 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
368 return TRUE;
370 switch (event->keyval)
372 case GDK_KEY_Down:
373 case GDK_KEY_KP_Down:
374 if (!gtk_tree_model_iter_next (model, &iter))
375 return TRUE;
377 break;
379 case GDK_KEY_Up:
380 case GDK_KEY_KP_Up:
381 if (!gtk_tree_model_iter_previous (model, &iter))
382 return TRUE;
384 break;
386 default:
387 g_assert_not_reached ();
390 gtk_tree_selection_select_iter (selection, &iter);
392 return TRUE;
395 static void
396 empathy_contact_chooser_init (EmpathyContactChooser *self)
398 EmpathyIndividualManager *mgr;
399 GtkTreeSelection *selection;
400 GQuark features[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE, 0 };
402 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EMPATHY_TYPE_CONTACT_CHOOSER,
403 EmpathyContactChooserPrivate);
405 self->priv->account_mgr = tp_account_manager_dup ();
407 /* We don't wait for the CORE feature to be prepared, which is fine as we
408 * won't use the account manager until user starts searching. Furthermore,
409 * the AM has probably already been prepared by another Empathy
410 * component. */
411 tp_proxy_prepare_async (self->priv->account_mgr, features, NULL, NULL);
413 /* Search entry */
414 self->priv->search_entry = gtk_search_entry_new ();
415 gtk_entry_set_placeholder_text ( GTK_ENTRY(self->priv->search_entry),
416 _("Type to search a contact…"));
417 gtk_box_pack_start (GTK_BOX (self), self->priv->search_entry, FALSE, TRUE, 0);
418 gtk_widget_show (self->priv->search_entry);
420 g_signal_connect (self->priv->search_entry, "changed",
421 G_CALLBACK (search_text_changed), self);
422 g_signal_connect (self->priv->search_entry, "activate",
423 G_CALLBACK (search_activate_cb), self);
424 g_signal_connect (self->priv->search_entry, "key-press-event",
425 G_CALLBACK (search_key_press_cb), self);
427 /* Add the treeview */
428 mgr = empathy_individual_manager_dup_singleton ();
429 self->priv->store = EMPATHY_INDIVIDUAL_STORE (
430 empathy_individual_store_manager_new (mgr));
431 g_object_unref (mgr);
433 empathy_individual_store_set_show_groups (self->priv->store, FALSE);
435 self->priv->view = empathy_individual_view_new (self->priv->store,
436 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE);
438 empathy_individual_view_set_custom_filter (self->priv->view,
439 filter_func, self);
441 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view));
443 g_signal_connect (selection, "changed",
444 G_CALLBACK (view_selection_changed_cb), self);
445 g_signal_connect (self->priv->view, "row-activated",
446 G_CALLBACK (view_activate_cb), self);
448 self->priv->scroll_view = gtk_scrolled_window_new (NULL, NULL);
450 gtk_container_add (GTK_CONTAINER (self->priv->scroll_view),
451 GTK_WIDGET (self->priv->view));
453 gtk_box_pack_start (GTK_BOX (self), self->priv->scroll_view, TRUE, TRUE, 0);
454 gtk_widget_show (GTK_WIDGET (self->priv->view));
455 gtk_widget_show (self->priv->scroll_view);
458 GtkWidget *
459 empathy_contact_chooser_new (void)
461 return g_object_new (EMPATHY_TYPE_CONTACT_CHOOSER,
462 "orientation", GTK_ORIENTATION_VERTICAL,
463 NULL);
466 /* Note that because of bgo #666580 the returned invidivdual is valid until
467 * @self is destroyed. To keep it around after that, the TpContact associated
468 * with the individual needs to be manually reffed. */
469 FolksIndividual *
470 empathy_contact_chooser_dup_selected (EmpathyContactChooser *self)
472 return empathy_individual_view_dup_selected (self->priv->view);
475 void
476 empathy_contact_chooser_set_filter_func (EmpathyContactChooser *self,
477 EmpathyContactChooserFilterFunc func,
478 gpointer user_data)
480 g_assert (self->priv->filter_func == NULL);
482 self->priv->filter_func = func;
483 self->priv->filter_data = user_data;
486 void
487 empathy_contact_chooser_show_search_entry (EmpathyContactChooser *self,
488 gboolean show)
490 gtk_widget_set_visible (self->priv->search_entry, show);
493 void
494 empathy_contact_chooser_show_tree_view (EmpathyContactChooser *self,
495 gboolean show)
497 gtk_widget_set_visible (GTK_WIDGET (self->priv->scroll_view), show);