]> git.0d.be Git - empathy.git/blob - src/empathy-invite-participant-dialog.c
Merge branch 'change-audio'
[empathy.git] / src / empathy-invite-participant-dialog.c
1 /*
2  * empathy-invite-participant-dialog.c
3  *
4  * EmpathyInviteParticipantDialog
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-invite-participant-dialog.h"
16
17 #include <libempathy-gtk/empathy-individual-view.h>
18 #include <libempathy-gtk/empathy-ui-utils.h>
19
20 G_DEFINE_TYPE (EmpathyInviteParticipantDialog,
21     empathy_invite_participant_dialog, GTK_TYPE_DIALOG);
22
23 enum
24 {
25   PROP_TP_CHAT = 1
26 };
27
28 typedef struct _AddTemporaryIndividualCtx AddTemporaryIndividualCtx;
29
30 struct _EmpathyInviteParticipantDialogPrivate
31 {
32   EmpathyTpChat *tp_chat;
33   TpAccountManager *account_mgr;
34
35   EmpathyIndividualStore *store;
36   EmpathyIndividualView *view;
37
38   GtkWidget *invite_button;
39
40   GPtrArray *search_words;
41   gchar *search_str;
42
43   /* Context representing the FolksIndividual which are added because of the
44    * current search from the user. */
45   AddTemporaryIndividualCtx *add_temp_ctx;
46 };
47
48 static void
49 invite_participant_dialog_get_property (GObject *object,
50     guint param_id,
51     GValue *value,
52     GParamSpec *pspec)
53 {
54   EmpathyInviteParticipantDialog *self = (EmpathyInviteParticipantDialog *)
55     object;
56
57   switch (param_id)
58     {
59     case PROP_TP_CHAT:
60       g_value_set_object (value, self->priv->tp_chat);
61       break;
62     default:
63       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
64       break;
65     };
66 }
67
68 static void
69 invite_participant_dialog_set_property (GObject *object,
70     guint param_id,
71     const GValue *value,
72     GParamSpec *pspec)
73 {
74   EmpathyInviteParticipantDialog *self = (EmpathyInviteParticipantDialog *)
75     object;
76
77   switch (param_id)
78     {
79     case PROP_TP_CHAT:
80       g_assert (self->priv->tp_chat == NULL); /* construct-only */
81       self->priv->tp_chat = g_value_dup_object (value);
82       break;
83     default:
84       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
85       break;
86     };
87 }
88
89 struct _AddTemporaryIndividualCtx
90 {
91   EmpathyInviteParticipantDialog *self;
92   /* List of owned FolksIndividual */
93   GList *individuals;
94 };
95
96 static AddTemporaryIndividualCtx *
97 add_temporary_individual_ctx_new (EmpathyInviteParticipantDialog *self)
98 {
99   AddTemporaryIndividualCtx *ctx = g_slice_new0 (AddTemporaryIndividualCtx);
100
101   ctx->self = self;
102   return ctx;
103 }
104
105 static void
106 add_temporary_individual_ctx_free (AddTemporaryIndividualCtx *ctx)
107 {
108   GList *l;
109
110   /* Remove all the individuals from the model */
111   for (l = ctx->individuals; l != NULL; l = g_list_next (l))
112     {
113       FolksIndividual *individual = l->data;
114
115       individual_store_remove_individual_and_disconnect (ctx->self->priv->store,
116           individual);
117
118       g_object_unref (individual);
119     }
120
121   g_list_free (ctx->individuals);
122   g_slice_free (AddTemporaryIndividualCtx, ctx);
123 }
124
125 static void
126 invite_participant_dialog_dispose (GObject *object)
127 {
128   EmpathyInviteParticipantDialog *self = (EmpathyInviteParticipantDialog *)
129     object;
130
131   tp_clear_pointer (&self->priv->add_temp_ctx,
132       add_temporary_individual_ctx_free);
133
134   tp_clear_object (&self->priv->tp_chat);
135   tp_clear_object (&self->priv->store);
136   tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
137   tp_clear_pointer (&self->priv->search_str, g_free);
138
139   tp_clear_object (&self->priv->account_mgr);
140
141   G_OBJECT_CLASS (empathy_invite_participant_dialog_parent_class)->dispose (
142       object);
143 }
144
145 static void
146 empathy_invite_participant_dialog_class_init (
147     EmpathyInviteParticipantDialogClass *klass)
148 {
149   GObjectClass *object_class = G_OBJECT_CLASS (klass);
150
151   object_class->get_property = invite_participant_dialog_get_property;
152   object_class->set_property = invite_participant_dialog_set_property;
153   object_class->dispose = invite_participant_dialog_dispose;
154
155   g_type_class_add_private (object_class,
156       sizeof (EmpathyInviteParticipantDialogPrivate));
157
158   g_object_class_install_property (object_class,
159       PROP_TP_CHAT,
160       g_param_spec_object ("tp-chat", "EmpathyTpChat", "EmpathyTpChat",
161           EMPATHY_TYPE_TP_CHAT,
162           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
163 }
164
165 static void
166 view_selection_changed_cb (GtkWidget *treeview,
167     EmpathyInviteParticipantDialog *self)
168 {
169   FolksIndividual *individual;
170
171   individual = empathy_individual_view_dup_selected (self->priv->view);
172
173   gtk_widget_set_sensitive (self->priv->invite_button, individual != NULL);
174
175   tp_clear_object (&individual);
176 }
177
178 /* Return the TpContact of @individual which is on the same connection as the
179  * EmpathyTpChat */
180 static TpContact *
181 get_tp_contact_for_chat (EmpathyInviteParticipantDialog *self,
182     FolksIndividual *individual)
183 {
184   TpContact *contact = NULL;
185   TpConnection *chat_conn;
186   GeeSet *personas;
187   GeeIterator *iter;
188
189   chat_conn = tp_channel_borrow_connection ((TpChannel *) self->priv->tp_chat);
190
191   personas = folks_individual_get_personas (individual);
192   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
193   while (contact == FALSE && gee_iterator_next (iter))
194     {
195       TpfPersona *persona = gee_iterator_get (iter);
196       TpConnection *contact_conn;
197       TpContact *contact_cur = NULL;
198
199       if (TPF_IS_PERSONA (persona))
200         {
201           contact_cur = tpf_persona_get_contact (persona);
202           if (contact_cur != NULL)
203             {
204               contact_conn = tp_contact_get_connection (contact_cur);
205
206               if (!tp_strdiff (tp_proxy_get_object_path (contact_conn),
207                     tp_proxy_get_object_path (chat_conn)))
208                 contact = contact_cur;
209             }
210         }
211
212       g_clear_object (&persona);
213     }
214   g_clear_object (&iter);
215
216   return contact;
217 }
218
219 static gboolean
220 filter_func (GtkTreeModel *model,
221     GtkTreeIter *iter,
222     gpointer user_data)
223 {
224   EmpathyInviteParticipantDialog *self = user_data;
225   FolksIndividual *individual;
226   TpContact *contact;
227   gboolean is_online;
228   GList *members, *l;
229   gboolean display = FALSE;
230
231   gtk_tree_model_get (model, iter,
232       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
233       EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &is_online,
234       -1);
235
236   if (individual == NULL)
237     goto out;
238
239   if (self->priv->search_words == NULL)
240     {
241       /* Not searching, display online contacts */
242       if (!is_online)
243         goto out;
244     }
245   else
246     {
247       if (!empathy_individual_match_string (individual,
248             self->priv->search_str, self->priv->search_words))
249         goto out;
250     }
251
252   /* Filter out individuals not having a persona on the same connection as the
253    * EmpathyTpChat. */
254   contact = get_tp_contact_for_chat (self, individual);
255
256   if (contact == NULL)
257     goto out;
258
259   /* Filter out contacts which are already in the chat */
260   members = empathy_contact_list_get_members (EMPATHY_CONTACT_LIST (
261         self->priv->tp_chat));
262
263   display = TRUE;
264
265   for (l = members; l != NULL; l = g_list_next (l))
266     {
267       EmpathyContact *member = l->data;
268       TpHandle handle;
269
270       /* Try to get the non-channel specific handle. */
271       handle = tp_channel_group_get_handle_owner (
272           TP_CHANNEL (self->priv->tp_chat),
273           empathy_contact_get_handle (member));
274       if (handle == 0)
275         handle = empathy_contact_get_handle (member);
276
277       if (handle == tp_contact_get_handle (contact))
278         {
279           display = FALSE;
280           break;
281         }
282     }
283
284   g_list_free_full (members, g_object_unref);
285
286 out:
287   tp_clear_object (&individual);
288   return display;
289 }
290
291 static void
292 get_contacts_cb (TpConnection *connection,
293     guint n_contacts,
294     TpContact * const *contacts,
295     const gchar * const *requested_ids,
296     GHashTable *failed_id_errors,
297     const GError *error,
298     gpointer user_data,
299     GObject *weak_object)
300 {
301   EmpathyInviteParticipantDialog *self =
302     (EmpathyInviteParticipantDialog *) weak_object;
303   AddTemporaryIndividualCtx *ctx = user_data;
304   TpAccount *account;
305   TpfPersonaStore *store;
306   FolksIndividual *individual;
307   TpfPersona *persona_new;
308   GeeSet *personas;
309
310   if (self->priv->add_temp_ctx != ctx)
311     /* another request has been started */
312     return;
313
314   if (n_contacts != 1)
315     return;
316
317   account = g_object_get_data (G_OBJECT (connection), "account");
318
319   store = tpf_persona_store_new (account);
320   personas = GEE_SET (
321       gee_hash_set_new (FOLKS_TYPE_PERSONA, g_object_ref, g_object_unref,
322       g_direct_hash, g_direct_equal));
323   persona_new = tpf_persona_new (contacts[0], store);
324   gee_collection_add (GEE_COLLECTION (personas),
325       tpf_persona_new (contacts[0], store));
326
327   individual = folks_individual_new (personas);
328
329   /* Pass ownership to the list */
330   ctx->individuals = g_list_prepend (ctx->individuals, individual);
331
332   individual_store_add_individual_and_connect (self->priv->store, individual);
333
334   g_clear_object (&persona_new);
335   g_clear_object (&personas);
336   g_object_unref (store);
337 }
338
339 static void
340 add_temporary_individuals (EmpathyInviteParticipantDialog *self,
341     const gchar *id)
342 {
343   GList *accounts, *l;
344
345   tp_clear_pointer (&self->priv->add_temp_ctx,
346       add_temporary_individual_ctx_free);
347
348   if (tp_str_empty (id))
349     return;
350
351   self->priv->add_temp_ctx = add_temporary_individual_ctx_new (self);
352
353   /* Try to add an individual for each connected account */
354   accounts = tp_account_manager_get_valid_accounts (self->priv->account_mgr);
355   for (l = accounts; l != NULL; l = g_list_next (l))
356     {
357       TpAccount *account = l->data;
358       TpConnection *conn;
359       TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS,
360           TP_CONTACT_FEATURE_AVATAR_DATA,
361           TP_CONTACT_FEATURE_PRESENCE,
362           TP_CONTACT_FEATURE_CAPABILITIES };
363
364       conn = tp_account_get_connection (account);
365       if (conn == NULL)
366         continue;
367
368       /* One day we'll have tp_connection_get_account()... */
369       g_object_set_data_full (G_OBJECT (conn), "account",
370           g_object_ref (account), g_object_unref);
371
372       tp_connection_get_contacts_by_id (conn, 1, &id, G_N_ELEMENTS (features),
373           features, get_contacts_cb, self->priv->add_temp_ctx, NULL,
374           G_OBJECT (self));
375     }
376
377   g_list_free (accounts);
378 }
379
380 static void
381 search_text_changed (GtkEntry *entry,
382     EmpathyInviteParticipantDialog *self)
383 {
384   const gchar *id;
385
386   tp_clear_pointer (&self->priv->search_words, g_ptr_array_unref);
387   tp_clear_pointer (&self->priv->search_str, g_free);
388
389   id = gtk_entry_get_text (entry);
390
391   self->priv->search_words = empathy_live_search_strip_utf8_string (id);
392   self->priv->search_str = g_strdup (id);
393
394   add_temporary_individuals (self, id);
395
396   empathy_individual_view_refilter (self->priv->view);
397 }
398
399 static void
400 empathy_invite_participant_dialog_init (EmpathyInviteParticipantDialog *self)
401 {
402   GtkDialog *dialog = GTK_DIALOG (self);
403   GtkWidget *label;
404   char *str;
405   GtkWidget *content;
406   EmpathyIndividualManager *mgr;
407   GtkTreeSelection *selection;
408   GtkWidget *scroll;
409   GtkWidget *search_entry;
410   GQuark features[] = { TP_ACCOUNT_MANAGER_FEATURE_CORE, 0 };
411
412   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (
413       self, EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG,
414       EmpathyInviteParticipantDialogPrivate);
415
416   self->priv->account_mgr = tp_account_manager_dup ();
417
418   /* We don't wait for the CORE feature to be prepared, which is fine as we
419    * won't use the account manager until user starts searching. Furthermore,
420    * the AM has probably already been prepared by another Empathy
421    * component. */
422   tp_proxy_prepare_async (self->priv->account_mgr, features, NULL, NULL);
423
424   content = gtk_dialog_get_content_area (dialog);
425
426   label = gtk_label_new (NULL);
427   str = g_strdup_printf (
428       "<span size=\"x-large\" weight=\"bold\">%s</span>\n\n%s",
429       _("Invite Participant"),
430       _("Choose a contact to invite into the conversation:"));
431   gtk_label_set_markup (GTK_LABEL (label), str);
432   g_free (str);
433
434   gtk_box_pack_start (GTK_BOX (content), label, FALSE, TRUE, 6);
435   gtk_widget_show (label);
436
437   gtk_dialog_add_button (dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
438
439   /* Search entry */
440   search_entry = gtk_entry_new ();
441   gtk_box_pack_start (GTK_BOX (content), search_entry, FALSE, TRUE, 6);
442   gtk_widget_show (search_entry);
443
444   g_signal_connect (search_entry, "changed",
445       G_CALLBACK (search_text_changed), self);
446
447   /* Add the treeview */
448   mgr = empathy_individual_manager_dup_singleton ();
449   self->priv->store = empathy_individual_store_new (mgr);
450   g_object_unref (mgr);
451
452   empathy_individual_store_set_show_groups (self->priv->store, FALSE);
453
454   self->priv->view = empathy_individual_view_new (self->priv->store,
455       EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, EMPATHY_INDIVIDUAL_FEATURE_NONE);
456
457   empathy_individual_view_set_custom_filter (self->priv->view,
458       filter_func, self);
459
460   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->view));
461
462   g_signal_connect (selection, "changed",
463       G_CALLBACK (view_selection_changed_cb), self);
464
465   scroll = gtk_scrolled_window_new (NULL, NULL);
466
467   gtk_container_add (GTK_CONTAINER (scroll), GTK_WIDGET (self->priv->view));
468
469   gtk_box_pack_start (GTK_BOX (content), scroll, TRUE, TRUE, 6);
470   gtk_widget_show (GTK_WIDGET (self->priv->view));
471   gtk_widget_show (scroll);
472
473   self->priv->invite_button = gtk_dialog_add_button (dialog, _("Invite"),
474       GTK_RESPONSE_ACCEPT);
475   gtk_widget_set_sensitive (self->priv->invite_button, FALSE);
476
477   gtk_window_set_title (GTK_WINDOW (self), _("Invite Participant"));
478   gtk_window_set_role (GTK_WINDOW (self), "invite_participant");
479
480   /* Set a default height so a few contacts are displayed */
481   gtk_window_set_default_size (GTK_WINDOW (self), -1, 400);
482 }
483
484 GtkWidget *
485 empathy_invite_participant_dialog_new (GtkWindow *parent,
486     EmpathyTpChat *tp_chat)
487 {
488   GtkWidget *self = g_object_new (EMPATHY_TYPE_INVITE_PARTICIPANT_DIALOG,
489       "tp-chat", tp_chat,
490       NULL);
491
492   if (parent != NULL)
493     {
494       gtk_window_set_transient_for (GTK_WINDOW (self), parent);
495     }
496
497   return self;
498 }
499
500 TpContact *
501 empathy_invite_participant_dialog_get_selected (
502     EmpathyInviteParticipantDialog *self)
503 {
504   FolksIndividual *individual;
505   TpContact *contact;
506
507   individual = empathy_individual_view_dup_selected (self->priv->view);
508   if (individual == NULL)
509     return NULL;
510
511   contact = get_tp_contact_for_chat (self, individual);
512
513   g_object_unref (individual);
514   return contact;
515 }