3.12.12
[nijm-empathy.git] / libempathy-gtk / empathy-contact-chooser.c
blob05dbe3449a21baa7bc89a2e8e821e22d93e700bf
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 "empathy-client-factory.h"
16 #include "empathy-individual-store-manager.h"
17 #include "empathy-individual-view.h"
18 #include "empathy-ui-utils.h"
19 #include "empathy-utils.h"
21 G_DEFINE_TYPE (EmpathyContactChooser,
22 empathy_contact_chooser, GTK_TYPE_BOX);
24 enum {
25 SIG_SELECTION_CHANGED,
26 SIG_ACTIVATE,
27 LAST_SIGNAL
30 static guint signals[LAST_SIGNAL];
32 typedef struct _AddTemporaryIndividualCtx AddTemporaryIndividualCtx;
34 struct _EmpathyContactChooserPrivate
36 TpAccountManager *account_mgr;
38 EmpathyIndividualStore *store;
39 EmpathyIndividualView *view;
40 GtkWidget *search_entry;
41 GtkWidget *scroll_view;
43 GPtrArray *search_words;
44 gchar *search_str;
46 /* Context representing the FolksIndividual which are added because of the
47 * current search from the user. */
48 AddTemporaryIndividualCtx *add_temp_ctx;
50 EmpathyContactChooserFilterFunc filter_func;
51 gpointer filter_data;
53 /* list of reffed TpContact */
54 GList *tp_contacts;
57 struct _AddTemporaryIndividualCtx
59 EmpathyContactChooser *self;
60 /* List of owned FolksIndividual */
61 GList *individuals;
64 static AddTemporaryIndividualCtx *
65 add_temporary_individual_ctx_new (EmpathyContactChooser *self)
67 AddTemporaryIndividualCtx *ctx = g_slice_new0 (AddTemporaryIndividualCtx);
69 ctx->self = self;
70 return ctx;
73 static void
74 add_temporary_individual_ctx_free (AddTemporaryIndividualCtx *ctx)
76 GList *l;
78 /* Remove all the individuals from the model */
79 for (l = ctx->individuals; l != NULL; l = g_list_next (l))
81 FolksIndividual *individual = l->data;
83 individual_store_remove_individual_and_disconnect (ctx->self->priv->store,
84 individual);
86 g_object_unref (individual);
89 g_list_free (ctx->individuals);
90 g_slice_free (AddTemporaryIndividualCtx, ctx);
93 static void
94 contact_chooser_dispose (GObject *object)
96 EmpathyContactChooser *self = (EmpathyContactChooser *)
97 object;
99 tp_clear_pointer (&self->priv->add_temp_ctx,
100 add_temporary_individual_ctx_free);
102 tp_clear_object (&self->priv->store);
103 tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
104 tp_clear_pointer (&self->priv->search_str, g_free);
106 tp_clear_object (&self->priv->account_mgr);
108 g_list_free_full (self->priv->tp_contacts, g_object_unref);
109 self->priv->tp_contacts = NULL;
111 G_OBJECT_CLASS (empathy_contact_chooser_parent_class)->dispose (
112 object);
115 static void
116 empathy_contact_chooser_class_init (
117 EmpathyContactChooserClass *klass)
119 GObjectClass *object_class = G_OBJECT_CLASS (klass);
121 object_class->dispose = contact_chooser_dispose;
123 g_type_class_add_private (object_class,
124 sizeof (EmpathyContactChooserPrivate));
126 signals[SIG_SELECTION_CHANGED] = g_signal_new ("selection-changed",
127 G_TYPE_FROM_CLASS (klass),
128 G_SIGNAL_RUN_LAST,
130 NULL, NULL,
131 g_cclosure_marshal_generic,
132 G_TYPE_NONE,
133 1, FOLKS_TYPE_INDIVIDUAL);
135 signals[SIG_ACTIVATE] = g_signal_new ("activate",
136 G_TYPE_FROM_CLASS (klass),
137 G_SIGNAL_RUN_LAST,
139 NULL, NULL,
140 g_cclosure_marshal_generic,
141 G_TYPE_NONE,
145 static void
146 view_selection_changed_cb (GtkWidget *treeview,
147 EmpathyContactChooser *self)
149 FolksIndividual *individual;
151 individual = empathy_individual_view_dup_selected (self->priv->view);
153 g_signal_emit (self, signals[SIG_SELECTION_CHANGED], 0, individual);
155 tp_clear_object (&individual);
158 static gboolean
159 filter_func (GtkTreeModel *model,
160 GtkTreeIter *iter,
161 gpointer user_data)
163 EmpathyContactChooser *self = user_data;
164 FolksIndividual *individual;
165 gboolean is_online;
166 gboolean display = FALSE;
167 gboolean searching = FALSE;
169 gtk_tree_model_get (model, iter,
170 EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
171 EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
172 -1);
174 if (individual == NULL)
175 goto out;
177 if (self->priv->search_words != NULL)
179 searching = TRUE;
181 /* Filter out the contact if we are searching and it doesn't match */
182 if (!empathy_individual_match_string (individual,
183 self->priv->search_str, self->priv->search_words))
184 goto out;
187 if (self->priv->filter_func == NULL)
188 display = TRUE;
189 else
190 display = self->priv->filter_func (self, individual, is_online, searching,
191 self->priv->filter_data);
192 out:
193 tp_clear_object (&individual);
194 return display;
197 static void
198 contact_capabilities_changed (TpContact *contact,
199 GParamSpec *pspec,
200 EmpathyContactChooser *self)
202 empathy_individual_view_refilter (self->priv->view);
205 static void
206 get_contacts_cb (GObject *source,
207 GAsyncResult *result,
208 gpointer user_data)
210 TpWeakRef *wr = user_data;
211 AddTemporaryIndividualCtx *ctx;
212 EmpathyContactChooser *self;
213 GError *error = NULL;
214 FolksIndividual *individual;
215 TpContact *contact;
216 EmpathyContact *emp_contact = NULL;
218 self = tp_weak_ref_dup_object (wr);
219 if (self == NULL)
220 goto out;
222 ctx = tp_weak_ref_get_user_data (wr);
224 emp_contact = empathy_client_factory_dup_contact_by_id_finish (
225 EMPATHY_CLIENT_FACTORY (source), result, &error);
226 if (emp_contact == NULL)
227 goto out;
229 contact = empathy_contact_get_tp_contact (emp_contact);
231 if (self->priv->add_temp_ctx != ctx)
232 /* another request has been started */
233 goto out;
235 individual = empathy_ensure_individual_from_tp_contact (contact);
236 if (individual == NULL)
237 goto out;
239 /* tp-glib will unref the TpContact once we return from this callback
240 * but folks expect us to keep a reference on the TpContact.
241 * Ideally folks shouldn't force us to do that: bgo #666580 */
242 self->priv->tp_contacts = g_list_prepend (self->priv->tp_contacts,
243 g_object_ref (contact));
245 /* listen for updates to the capabilities */
246 tp_g_signal_connect_object (contact, "notify::capabilities",
247 G_CALLBACK (contact_capabilities_changed), self, 0);
249 /* Pass ownership to the list */
250 ctx->individuals = g_list_prepend (ctx->individuals, individual);
252 individual_store_add_individual_and_connect (self->priv->store, individual);
254 /* if nothing is selected, select the first matching node */
255 if (!gtk_tree_selection_get_selected (
256 gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view)),
257 NULL, NULL))
258 empathy_individual_view_select_first (self->priv->view);
260 out:
261 g_clear_object (&emp_contact);
262 g_clear_object (&self);
263 tp_weak_ref_destroy (wr);
266 static void
267 add_temporary_individuals (EmpathyContactChooser *self,
268 const gchar *id)
270 GList *accounts, *l;
272 tp_clear_pointer (&self->priv->add_temp_ctx,
273 add_temporary_individual_ctx_free);
275 if (tp_str_empty (id))
276 return;
278 self->priv->add_temp_ctx = add_temporary_individual_ctx_new (self);
280 /* Try to add an individual for each connected account */
281 accounts = tp_account_manager_dup_valid_accounts (self->priv->account_mgr);
282 for (l = accounts; l != NULL; l = g_list_next (l))
284 TpAccount *account = l->data;
285 TpConnection *conn;
286 EmpathyClientFactory *factory;
288 conn = tp_account_get_connection (account);
289 if (conn == NULL)
290 continue;
292 factory = empathy_client_factory_dup ();
294 empathy_client_factory_dup_contact_by_id_async (factory, conn, id,
295 get_contacts_cb,
296 tp_weak_ref_new (self, self->priv->add_temp_ctx, NULL));
298 g_object_unref (factory);
301 g_list_free_full (accounts, g_object_unref);
304 static void
305 search_text_changed (GtkEntry *entry,
306 EmpathyContactChooser *self)
308 const gchar *id;
310 tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
311 tp_clear_pointer (&self->priv->search_str, g_free);
313 id = gtk_entry_get_text (entry);
315 self->priv->search_words = tpaw_live_search_strip_utf8_string (id);
316 self->priv->search_str = g_strdup (id);
318 add_temporary_individuals (self, id);
320 empathy_individual_view_refilter (self->priv->view);
323 static void
324 search_activate_cb (GtkEntry *entry,
325 EmpathyContactChooser *self)
327 g_signal_emit (self, signals[SIG_ACTIVATE], 0);
330 static void
331 view_activate_cb (GtkTreeView *view,
332 GtkTreePath *path,
333 GtkTreeViewColumn *column,
334 EmpathyContactChooser *self)
336 g_signal_emit (self, signals[SIG_ACTIVATE], 0);
339 static gboolean
340 search_key_press_cb (GtkEntry *entry,
341 GdkEventKey *event,
342 EmpathyContactChooser *self)
344 GtkTreeSelection *selection;
345 GtkTreeModel *model;
346 GtkTreeIter iter;
348 if (event->state != 0)
349 return FALSE;
351 switch (event->keyval)
353 case GDK_KEY_Down:
354 case GDK_KEY_KP_Down:
355 case GDK_KEY_Up:
356 case GDK_KEY_KP_Up:
357 break;
359 default:
360 return FALSE;
363 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view));
365 if (!gtk_tree_selection_get_selected (selection, &model, &iter))
366 return TRUE;
368 switch (event->keyval)
370 case GDK_KEY_Down:
371 case GDK_KEY_KP_Down:
372 if (!gtk_tree_model_iter_next (model, &iter))
373 return TRUE;
375 break;
377 case GDK_KEY_Up:
378 case GDK_KEY_KP_Up:
379 if (!gtk_tree_model_iter_previous (model, &iter))
380 return TRUE;
382 break;
384 default:
385 g_assert_not_reached ();
388 gtk_tree_selection_select_iter (selection, &iter);
390 return TRUE;
393 static void
394 empathy_contact_chooser_init (EmpathyContactChooser *self)
396 EmpathyIndividualManager *mgr;
397 GtkTreeSelection *selection;
398 GQuark features[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE, 0 };
400 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EMPATHY_TYPE_CONTACT_CHOOSER,
401 EmpathyContactChooserPrivate);
403 self->priv->account_mgr = tp_account_manager_dup ();
405 /* We don't wait for the CORE feature to be prepared, which is fine as we
406 * won't use the account manager until user starts searching. Furthermore,
407 * the AM has probably already been prepared by another Empathy
408 * component. */
409 tp_proxy_prepare_async (self->priv->account_mgr, features, NULL, NULL);
411 /* Search entry */
412 self->priv->search_entry = gtk_entry_new ();
413 gtk_box_pack_start (GTK_BOX (self), self->priv->search_entry, FALSE, TRUE, 6);
414 gtk_widget_show (self->priv->search_entry);
416 g_signal_connect (self->priv->search_entry, "changed",
417 G_CALLBACK (search_text_changed), self);
418 g_signal_connect (self->priv->search_entry, "activate",
419 G_CALLBACK (search_activate_cb), self);
420 g_signal_connect (self->priv->search_entry, "key-press-event",
421 G_CALLBACK (search_key_press_cb), self);
423 /* Add the treeview */
424 mgr = empathy_individual_manager_dup_singleton ();
425 self->priv->store = EMPATHY_INDIVIDUAL_STORE (
426 empathy_individual_store_manager_new (mgr));
427 g_object_unref (mgr);
429 empathy_individual_store_set_show_groups (self->priv->store, FALSE);
431 self->priv->view = empathy_individual_view_new (self->priv->store,
432 EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE);
434 empathy_individual_view_set_custom_filter (self->priv->view,
435 filter_func, self);
437 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view));
439 g_signal_connect (selection, "changed",
440 G_CALLBACK (view_selection_changed_cb), self);
441 g_signal_connect (self->priv->view, "row-activated",
442 G_CALLBACK (view_activate_cb), self);
444 self->priv->scroll_view = gtk_scrolled_window_new (NULL, NULL);
446 gtk_container_add (GTK_CONTAINER (self->priv->scroll_view),
447 GTK_WIDGET (self->priv->view));
449 gtk_box_pack_start (GTK_BOX (self), self->priv->scroll_view, TRUE, TRUE, 6);
450 gtk_widget_show (GTK_WIDGET (self->priv->view));
451 gtk_widget_show (self->priv->scroll_view);
454 GtkWidget *
455 empathy_contact_chooser_new (void)
457 return g_object_new (EMPATHY_TYPE_CONTACT_CHOOSER,
458 "orientation", GTK_ORIENTATION_VERTICAL,
459 NULL);
462 /* Note that because of bgo #666580 the returned invidivdual is valid until
463 * @self is destroyed. To keep it around after that, the TpContact associated
464 * with the individual needs to be manually reffed. */
465 FolksIndividual *
466 empathy_contact_chooser_dup_selected (EmpathyContactChooser *self)
468 return empathy_individual_view_dup_selected (self->priv->view);
471 void
472 empathy_contact_chooser_set_filter_func (EmpathyContactChooser *self,
473 EmpathyContactChooserFilterFunc func,
474 gpointer user_data)
476 g_assert (self->priv->filter_func == NULL);
478 self->priv->filter_func = func;
479 self->priv->filter_data = user_data;
482 void
483 empathy_contact_chooser_show_search_entry (EmpathyContactChooser *self,
484 gboolean show)
486 gtk_widget_set_visible (self->priv->search_entry, show);
489 void
490 empathy_contact_chooser_show_tree_view (EmpathyContactChooser *self,
491 gboolean show)
493 gtk_widget_set_visible (GTK_WIDGET (self->priv->scroll_view), show);