]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-blocking-dialog.c
703ca8b89c03707d8ae28078f8cb9d3e94a49478
[empathy.git] / libempathy-gtk / empathy-contact-blocking-dialog.c
1 /*
2  * empathy-contact-blocking-dialog.c
3  *
4  * EmpathyContactBlockingDialog
5  *
6  * Copyright (C) 2011 Collabora Ltd.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21  *
22  * Authors: Danielle Madeley <danielle.madeley@collabora.co.uk>
23  */
24
25 #include <glib/gi18n.h>
26
27 #include <libempathy/empathy-utils.h>
28
29 #include <libempathy-gtk/empathy-account-chooser.h>
30 #include <libempathy-gtk/empathy-ui-utils.h>
31
32 #include "empathy-contact-blocking-dialog.h"
33
34 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
35 #include <libempathy/empathy-debug.h>
36
37 #define GET_PRIVATE(o) (EMPATHY_CONTACT_BLOCKING_DIALOG (o)->priv)
38 #define DECLARE_CALLBACK(func) \
39   static void func (GObject *, GAsyncResult *, gpointer);
40
41 G_DEFINE_TYPE (EmpathyContactBlockingDialog, empathy_contact_blocking_dialog,
42     GTK_TYPE_DIALOG);
43
44 struct _EmpathyContactBlockingDialogPrivate
45 {
46   GHashTable *channels; /* TpConnection* -> TpChannel* */
47   GtkListStore *blocked_contacts;
48   GtkTreeSelection *selection;
49
50   GtkWidget *account_chooser;
51   GtkWidget *add_contact_entry;
52   GtkWidget *remove_button;
53 };
54
55 enum /* blocked-contacts columns */
56 {
57   COL_IDENTIFIER,
58   COL_HANDLE,
59   N_COLUMNS
60 };
61
62 static const char *
63 get_pretty_conn_name (TpConnection *conn)
64 {
65   return tp_proxy_get_object_path (conn) + strlen (TP_CONN_OBJECT_PATH_BASE);
66 }
67
68 static void
69 contact_blocking_dialog_filter_account_chooser (TpAccount *account,
70     EmpathyAccountChooserFilterResultCallback callback,
71     gpointer callback_data,
72     gpointer user_data)
73 {
74   EmpathyContactBlockingDialog *self = user_data;
75   TpConnection *conn = tp_account_get_connection (account);
76   gboolean enable;
77
78   enable =
79     conn != NULL &&
80     g_hash_table_lookup (self->priv->channels, conn) != NULL;
81
82   callback (enable, callback_data);
83 }
84
85 static void contact_blocking_dialog_inspected_handles (TpConnection *,
86     const char **, const GError *, gpointer, GObject *);
87
88 static void
89 contact_blocking_dialog_add_contacts_to_list (
90     EmpathyContactBlockingDialog *self,
91     TpConnection *conn,
92     GArray *handles)
93 {
94   if (handles->len > 0)
95     tp_cli_connection_call_inspect_handles (conn, -1,
96         TP_HANDLE_TYPE_CONTACT, handles,
97         contact_blocking_dialog_inspected_handles,
98         g_boxed_copy (DBUS_TYPE_G_UINT_ARRAY, handles),
99         (GDestroyNotify) g_array_unref, G_OBJECT (self));
100 }
101
102 static void
103 contact_blocking_dialog_inspected_handles (TpConnection *conn,
104     const char **identifiers,
105     const GError *in_error,
106     gpointer user_data,
107     GObject *self)
108 {
109   EmpathyContactBlockingDialogPrivate *priv = GET_PRIVATE (self);
110   GArray *handles = user_data;
111   guint i;
112
113   if (in_error != NULL)
114     {
115       DEBUG ("Failed to inspect handles: %s", in_error->message);
116       return;
117     }
118
119   DEBUG ("Adding %u identifiers", handles->len);
120
121   for (i = 0; i < handles->len; i++)
122     {
123       const char *identifier = identifiers[i];
124       TpHandle handle = g_array_index (handles, TpHandle, i);
125
126       gtk_list_store_insert_with_values (priv->blocked_contacts, NULL, -1,
127           COL_IDENTIFIER, identifier,
128           COL_HANDLE, handle,
129           -1);
130     }
131 }
132
133 DECLARE_CALLBACK (contact_blocking_dialog_connection_prepared);
134
135 static void
136 contact_blocking_dialog_connection_status_changed (TpAccount *account,
137     guint old_status,
138     guint new_status,
139     guint reason,
140     const char *dbus_reason,
141     GHashTable *details,
142     EmpathyContactBlockingDialog *self)
143 {
144   TpConnection *conn = tp_account_get_connection (account);
145
146   switch (new_status)
147     {
148       case TP_CONNECTION_STATUS_DISCONNECTED:
149         DEBUG ("Connection %s invalidated", get_pretty_conn_name (conn));
150
151         /* remove the channel from the hash table */
152         g_hash_table_remove (self->priv->channels, conn);
153
154         /* set the filter again to refilter the account list */
155         empathy_account_chooser_set_filter (
156             EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser),
157             contact_blocking_dialog_filter_account_chooser, self);
158         break;
159
160       case TP_CONNECTION_STATUS_CONNECTING:
161         break;
162
163       case TP_CONNECTION_STATUS_CONNECTED:
164         DEBUG ("Connection %s reconnected", get_pretty_conn_name (conn));
165
166         tp_proxy_prepare_async (conn, NULL,
167             contact_blocking_dialog_connection_prepared, self);
168     }
169 }
170
171 static void
172 contact_blocking_dialog_deny_channel_members_changed (TpChannel *channel,
173     const char *message,
174     GArray *added,
175     GArray *removed,
176     GArray *local_pending,
177     GArray *remote_pending,
178     TpHandle actor,
179     guint reason,
180     EmpathyContactBlockingDialog *self)
181 {
182   TpConnection *conn = tp_channel_borrow_connection (channel);
183   GtkTreeModel *model = GTK_TREE_MODEL (self->priv->blocked_contacts);
184   GtkTreeIter iter;
185   TpIntset *removed_set;
186   gboolean valid;
187
188   /* we only care about changes to the selected connection */
189   /* FIXME: can we compare proxy pointers directly? */
190   if (tp_strdiff (
191         tp_proxy_get_object_path (tp_channel_borrow_connection (channel)),
192         tp_proxy_get_object_path (empathy_account_chooser_get_connection (
193             EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser)))))
194     return;
195
196   DEBUG ("deny list changed: %u added, %u removed", added->len, removed->len);
197
198   /* add contacts */
199   contact_blocking_dialog_add_contacts_to_list (self, conn, added);
200
201   /* remove contacts */
202   removed_set = tp_intset_from_array (removed);
203
204   valid = gtk_tree_model_get_iter_first (model, &iter);
205   while (valid)
206     {
207       TpHandle handle;
208
209       gtk_tree_model_get (model, &iter,
210           COL_HANDLE, &handle,
211           -1);
212
213       if (tp_intset_is_member (removed_set, handle))
214         valid = gtk_list_store_remove (self->priv->blocked_contacts, &iter);
215       else
216         valid = gtk_tree_model_iter_next (model, &iter);
217     }
218
219   tp_intset_destroy (removed_set);
220 }
221
222 DECLARE_CALLBACK (contact_blocking_dialog_account_prepared);
223
224 static void
225 contact_blocking_dialog_am_prepared (GObject *am,
226     GAsyncResult *result,
227     gpointer user_data)
228 {
229   EmpathyContactBlockingDialog *self = user_data;
230   GList *accounts, *ptr;
231   GError *error = NULL;
232
233   if (!tp_proxy_prepare_finish (am, result, &error))
234     {
235       g_critical ("Could not prepare Account Manager: %s", error->message);
236       g_error_free (error);
237       return;
238     }
239
240   accounts = tp_account_manager_get_valid_accounts (TP_ACCOUNT_MANAGER (am));
241
242   for (ptr = accounts; ptr != NULL; ptr = ptr->next)
243     {
244       TpAccount *account = ptr->data;
245
246       tp_proxy_prepare_async (account, NULL,
247           contact_blocking_dialog_account_prepared, self);
248     }
249
250   g_list_free (accounts);
251 }
252
253 static void
254 contact_blocking_dialog_account_prepared (GObject *account,
255     GAsyncResult *result,
256     gpointer user_data)
257 {
258   EmpathyContactBlockingDialog *self = user_data;
259   TpConnection *conn;
260   GError *error = NULL;
261
262   if (!tp_proxy_prepare_finish (account, result, &error))
263     {
264       DEBUG ("Could not prepare Account: %s", error->message);
265       g_error_free (error);
266       return;
267     }
268
269   g_signal_connect (account, "status-changed",
270       G_CALLBACK (contact_blocking_dialog_connection_status_changed), self);
271
272   conn = tp_account_get_connection (TP_ACCOUNT (account));
273
274   if (conn != NULL)
275     {
276       tp_proxy_prepare_async (conn, NULL,
277           contact_blocking_dialog_connection_prepared, self);
278     }
279 }
280
281 static void contact_blocking_dialog_got_deny_channel (TpConnection *,
282     gboolean, const char *, GHashTable *, const GError *, gpointer, GObject *);
283
284 static void
285 contact_blocking_dialog_connection_prepared (GObject *conn,
286     GAsyncResult *result,
287     gpointer user_data)
288 {
289   EmpathyContactBlockingDialog *self = user_data;
290   GHashTable *request;
291   GError *error = NULL;
292
293   if (!tp_proxy_prepare_finish (conn, result, &error))
294     {
295       DEBUG ("Failed to prepare connection: %s", error->message);
296       g_error_free (error);
297       return;
298     }
299
300   /* request the deny channel */
301   request = tp_asv_new (
302       TP_PROP_CHANNEL_CHANNEL_TYPE,
303       G_TYPE_STRING,
304       TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
305
306       TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
307       G_TYPE_UINT,
308       TP_HANDLE_TYPE_LIST,
309
310       TP_PROP_CHANNEL_TARGET_ID,
311       G_TYPE_STRING,
312       "deny",
313
314       NULL);
315
316   tp_cli_connection_interface_requests_call_ensure_channel (
317       TP_CONNECTION (conn), -1, request,
318       contact_blocking_dialog_got_deny_channel, NULL, NULL, G_OBJECT (self));
319
320   g_hash_table_destroy (request);
321 }
322
323 DECLARE_CALLBACK (contact_blocking_dialog_deny_channel_prepared);
324
325 static void
326 contact_blocking_dialog_got_deny_channel (TpConnection *conn,
327     gboolean yours,
328     const char *channel_path,
329     GHashTable *props,
330     const GError *in_error,
331     gpointer user_data,
332     GObject *self)
333 {
334   TpChannel *channel;
335   GError *error = NULL;
336
337   const GQuark features[] = {
338       TP_CHANNEL_FEATURE_CORE,
339       TP_CHANNEL_FEATURE_GROUP,
340       0 };
341
342   if (in_error != NULL)
343     {
344       DEBUG ("Failed to get 'deny' channel: %s", in_error->message);
345       return;
346     }
347
348   channel = tp_channel_new_from_properties (conn, channel_path, props, &error);
349
350   if (error != NULL)
351     {
352       DEBUG ("Failed to create channel proxy: %s", error->message);
353       g_error_free (error);
354       return;
355     }
356
357   tp_proxy_prepare_async (channel, features,
358       contact_blocking_dialog_deny_channel_prepared, self);
359 }
360
361 static void
362 contact_blocking_dialog_deny_channel_prepared (GObject *channel,
363     GAsyncResult *result,
364     gpointer user_data)
365 {
366   EmpathyContactBlockingDialog *self = user_data;
367   TpConnection *conn;
368   GError *error = NULL;
369
370   if (!tp_proxy_prepare_finish (channel, result, &error))
371     {
372       DEBUG ("Failed to prepare channel: %s", error->message);
373       g_error_free (error);
374       return;
375     }
376
377   conn = tp_channel_borrow_connection (TP_CHANNEL (channel));
378
379   DEBUG ("Channel prepared for connection %s", get_pretty_conn_name (conn));
380
381   g_hash_table_insert (self->priv->channels,
382       g_object_ref (conn), channel);
383
384   /* set the filter again to refilter the account list */
385   empathy_account_chooser_set_filter (
386       EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser),
387       contact_blocking_dialog_filter_account_chooser, self);
388
389   g_signal_connect (channel, "group-members-changed",
390       G_CALLBACK (contact_blocking_dialog_deny_channel_members_changed), self);
391 }
392
393 static void contact_blocking_dialog_add_contact_got_handle (TpConnection *,
394     const GArray *, const GError *, gpointer, GObject *);
395
396 static void
397 contact_blocking_dialog_add_contact (GtkWidget *widget,
398     EmpathyContactBlockingDialog *self)
399 {
400   TpConnection *conn = empathy_account_chooser_get_connection (
401       EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser));
402   const char *identifiers[2] = { NULL, };
403
404   identifiers[0] = gtk_entry_get_text (
405       GTK_ENTRY (self->priv->add_contact_entry));
406
407   DEBUG ("Looking up handle for '%s'", identifiers[0]);
408
409   tp_cli_connection_call_request_handles (conn, -1,
410       TP_HANDLE_TYPE_CONTACT, identifiers,
411       contact_blocking_dialog_add_contact_got_handle,
412       NULL, NULL, G_OBJECT (self));
413
414   gtk_entry_set_text (GTK_ENTRY (self->priv->add_contact_entry), "");
415 }
416
417 static void
418 contact_blocking_dialog_added_contact (TpChannel *, const GError *,
419     gpointer, GObject *);
420
421 static void
422 contact_blocking_dialog_add_contact_got_handle (TpConnection *conn,
423     const GArray *handles,
424     const GError *in_error,
425     gpointer user_data,
426     GObject *self)
427 {
428   EmpathyContactBlockingDialogPrivate *priv = GET_PRIVATE (self);
429   TpChannel *channel = g_hash_table_lookup (priv->channels, conn);
430
431   if (in_error != NULL)
432     {
433       DEBUG ("Error getting handle: %s", in_error->message);
434       /* FIXME: expose error to user */
435       return;
436     }
437
438   g_return_if_fail (handles->len == 1);
439
440   DEBUG ("Adding handle %u to deny channel",
441       g_array_index (handles, TpHandle, 0));
442
443   tp_cli_channel_interface_group_call_add_members (channel, -1,
444       handles, "",
445       contact_blocking_dialog_added_contact, NULL, NULL, self);
446 }
447
448 static void
449 contact_blocking_dialog_added_contact (TpChannel *channel,
450     const GError *in_error,
451     gpointer user_data,
452     GObject *self)
453 {
454   if (in_error != NULL)
455     {
456       DEBUG ("Error adding contact to deny list: %s", in_error->message);
457       /* FIXME: expose error to user */
458       return;
459     }
460
461   DEBUG ("Contact added");
462 }
463
464 static void
465 contact_blocking_dialog_removed_contacts (TpChannel *,
466     const GError *, gpointer, GObject *);
467
468 static void
469 contact_blocking_dialog_remove_contacts (GtkWidget *button,
470     EmpathyContactBlockingDialog *self)
471 {
472   TpConnection *conn = empathy_account_chooser_get_connection (
473       EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser));
474   TpChannel *channel = g_hash_table_lookup (self->priv->channels, conn);
475   GtkTreeModel *model;
476   GList *rows, *ptr;
477   GArray *handles = g_array_new (FALSE, FALSE, sizeof (TpHandle));
478
479   rows = gtk_tree_selection_get_selected_rows (self->priv->selection, &model);
480
481   for (ptr = rows; ptr != NULL; ptr = ptr->next)
482     {
483       GtkTreePath *path = ptr->data;
484       GtkTreeIter iter;
485       TpHandle handle;
486
487       if (!gtk_tree_model_get_iter (model, &iter, path))
488         continue;
489
490       gtk_tree_model_get (model, &iter,
491           COL_HANDLE, &handle,
492           -1);
493
494       g_array_append_val (handles, handle);
495       gtk_tree_path_free (path);
496     }
497
498   g_list_free (rows);
499
500   if (handles->len > 0)
501     {
502       DEBUG ("Removing %u handles", handles->len);
503
504       tp_cli_channel_interface_group_call_remove_members (channel, -1,
505           handles, "",
506           contact_blocking_dialog_removed_contacts,
507           NULL, NULL, G_OBJECT (self));
508     }
509
510   g_array_unref (handles);
511 }
512
513 static void
514 contact_blocking_dialog_removed_contacts (TpChannel *channel,
515     const GError *in_error,
516     gpointer user_data,
517     GObject *self)
518 {
519   if (in_error != NULL)
520     {
521       DEBUG ("Error removing contacts from deny list: %s", in_error->message);
522       /* FIXME: expose error to user */
523       return;
524     }
525
526   DEBUG ("Contacts removed");
527 }
528
529 static void
530 contact_blocking_dialog_account_changed (GtkWidget *account_chooser,
531     EmpathyContactBlockingDialog *self)
532 {
533   TpConnection *conn = empathy_account_chooser_get_connection (
534       EMPATHY_ACCOUNT_CHOOSER (account_chooser));
535   TpChannel *channel;
536   GArray *blocked;
537
538   if (conn == NULL)
539     return;
540
541   DEBUG ("Account changed: %s", get_pretty_conn_name (conn));
542
543   /* FIXME: clear the completion, get the new blocked list */
544
545   /* clear the list of blocked contacts */
546   gtk_list_store_clear (self->priv->blocked_contacts);
547
548   /* load the deny list */
549   channel = g_hash_table_lookup (self->priv->channels, conn);
550
551   g_return_if_fail (TP_IS_CHANNEL (channel));
552
553   blocked = tp_intset_to_array (tp_channel_group_get_members (channel));
554
555   DEBUG ("%u contacts on blocked list", blocked->len);
556
557   contact_blocking_dialog_add_contacts_to_list (self, conn, blocked);
558
559   g_array_unref (blocked);
560 }
561
562 static void
563 contact_blocking_dialog_view_selection_changed (GtkTreeSelection *selection,
564     EmpathyContactBlockingDialog *self)
565 {
566   GList *rows = gtk_tree_selection_get_selected_rows (selection, NULL);
567
568   /* update the sensitivity of the remove button */
569   gtk_widget_set_sensitive (self->priv->remove_button, rows != NULL);
570
571   g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
572   g_list_free (rows);
573 }
574
575 static void
576 contact_blocking_dialog_dispose (GObject *self)
577 {
578   EmpathyContactBlockingDialogPrivate *priv = GET_PRIVATE (self);
579
580   tp_clear_pointer (&priv->channels, g_hash_table_destroy);
581 }
582
583 static void
584 empathy_contact_blocking_dialog_class_init (
585     EmpathyContactBlockingDialogClass *klass)
586 {
587   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
588
589   gobject_class->dispose = contact_blocking_dialog_dispose;
590
591   g_type_class_add_private (gobject_class,
592       sizeof (EmpathyContactBlockingDialogPrivate));
593 }
594
595 static void
596 empathy_contact_blocking_dialog_init (EmpathyContactBlockingDialog *self)
597 {
598   GtkBuilder *gui;
599   char *filename;
600   GtkWidget *contents;
601   GtkWidget *account_hbox, *blocked_contacts_view;
602   TpAccountManager *am;
603
604   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
605       EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG,
606       EmpathyContactBlockingDialogPrivate);
607
608   self->priv->channels = g_hash_table_new_full (NULL, NULL,
609       g_object_unref, g_object_unref);
610
611   gtk_window_set_title (GTK_WINDOW (self), _("Edit Blocked Contacts"));
612   gtk_dialog_add_button (GTK_DIALOG (self),
613       GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
614
615   filename = empathy_file_lookup ("empathy-contact-blocking-dialog.ui",
616       "libempathy-gtk");
617
618   gui = empathy_builder_get_file (filename,
619       "contents", &contents,
620       "account-hbox", &account_hbox,
621       "add-contact-entry", &self->priv->add_contact_entry,
622       "blocked-contacts", &self->priv->blocked_contacts,
623       "blocked-contacts-view", &blocked_contacts_view,
624       "remove-button", &self->priv->remove_button,
625       NULL);
626
627   empathy_builder_connect (gui, self,
628       "add-button", "clicked", contact_blocking_dialog_add_contact,
629       "add-contact-entry", "activate", contact_blocking_dialog_add_contact,
630       "remove-button", "clicked", contact_blocking_dialog_remove_contacts,
631       NULL);
632
633   /* add the contents to the dialog */
634   gtk_container_add (
635       GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (self))),
636       contents);
637   gtk_widget_show (contents);
638
639   /* add the account chooser */
640   self->priv->account_chooser = empathy_account_chooser_new ();
641   empathy_account_chooser_set_filter (
642       EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser),
643       contact_blocking_dialog_filter_account_chooser, self);
644   g_signal_connect (self->priv->account_chooser, "changed",
645       G_CALLBACK (contact_blocking_dialog_account_changed), self);
646
647   gtk_box_pack_start (GTK_BOX (account_hbox), self->priv->account_chooser,
648       TRUE, TRUE, 0);
649   gtk_widget_show (self->priv->account_chooser);
650
651   /* set up the tree selection */
652   self->priv->selection = gtk_tree_view_get_selection (
653       GTK_TREE_VIEW (blocked_contacts_view));
654   gtk_tree_selection_set_mode (self->priv->selection, GTK_SELECTION_MULTIPLE);
655   g_signal_connect (self->priv->selection, "changed",
656       G_CALLBACK (contact_blocking_dialog_view_selection_changed), self);
657
658   /* build the contact entry */
659   // FIXME
660
661   /* prepare the account manager */
662   am = tp_account_manager_dup ();
663   tp_proxy_prepare_async (am, NULL, contact_blocking_dialog_am_prepared, self);
664
665   g_free (filename);
666   g_object_unref (gui);
667 }
668
669 GtkWidget *
670 empathy_contact_blocking_dialog_new (GtkWindow *parent)
671 {
672   GtkWidget *self = g_object_new (EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG,
673       NULL);
674
675   if (parent != NULL)
676     {
677       gtk_window_set_transient_for (GTK_WINDOW (self), parent);
678     }
679
680   return self;
681 }