UI and string freeze break request for blocking contacts in Empathy



Hi everyone,

We recently implemented contacts blocking in Empathy and are wondering
if that's something we could merge to master.

The branch adds some UI and strings so it needs a freeze exception.

That's a feature that loads of users have been asking for a while so
this may be a cool addition to 3.0.

See https://bugzilla.gnome.org/show_bug.cgi?id=618787 and
http://git.collabora.co.uk/?p=user/danni/empathy.git;a=shortlog;h=refs/heads/glassrose-contact-blocking-rebase

I'm attaching the diff but it's probably easier to look at the git
branch directly.

Have a good week-end,


	G.

-- 
Guillaume Desmottes <gdesmott gnome org>
Jabber <cassidy jabber belnet be>
GPG 1024D/711E31B1 | 1B5A 1BA8 11AA F0F1 2169  E28A AC55 8671 711E 31B1
diff --git a/extensions/Connection_Interface_Contact_Blocking.xml b/extensions/Connection_Interface_Contact_Blocking.xml
new file mode 100644
index 0000000..543d43a
--- /dev/null
+++ b/extensions/Connection_Interface_Contact_Blocking.xml
@@ -0,0 +1,172 @@
+<?xml version="1.0" ?>
+<node name="/Connection_Interface_Contact_Blocking" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0";>
+  <tp:copyright>Copyright © 2009-2010 Collabora Ltd.</tp:copyright>
+  <tp:copyright>Copyright © 2009 Nokia Corporation</tp:copyright>
+  <tp:license xmlns="http://www.w3.org/1999/xhtml";>
+    <p>This library is free software; you can redistribute it and/or
+      modify it under the terms of the GNU Lesser General Public
+      License as published by the Free Software Foundation; either
+      version 2.1 of the License, or (at your option) any later version.</p>
+
+    <p>This library is distributed in the hope that it will be useful,
+      but WITHOUT ANY WARRANTY; without even the implied warranty of
+      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+      Lesser General Public License for more details.</p>
+
+    <p>You should have received a copy of the GNU Lesser General Public
+      License along with this library; if not, write to the Free Software
+      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+      USA.</p>
+  </tp:license>
+  <interface name="org.freedesktop.Telepathy.Connection.Interface.ContactBlocking.DRAFT"
+    tp:causes-havoc="experimental">
+    <tp:requires interface="org.freedesktop.Telepathy.Connection"/>
+
+    <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+      <p>An interface for connections where contacts can be blocked from
+        communicating with this user and receiving this user's presence.</p>
+
+      <p>When this interface becomes stable, it will replace the <tp:dbus-ref
+          namespace="org.freedesktop.Telepathy.Channel.Type"
+          >ContactList</tp:dbus-ref> channel with TargetHandleType
+        Handle_Type_List and TargetID 'deny'.</p>
+    </tp:docstring>
+
+    <method name="BlockContacts" tp:name-for-bindings="Block_Contacts">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Direct the server to block some contacts. The precise effect is
+          protocol-dependent, but SHOULD include ignoring all current and
+          subsequent communications from the given contacts, avoiding sending
+          presence to them in future, and if they were already receiving the
+          local user's presence, behaving as if the local user went
+          offline.</p>
+
+        <p><em>FIXME: do we need to allow this on protocols where it won't
+            persist, or where we can't edit the block lists?</em></p>
+      </tp:docstring>
+
+      <arg name="Contacts" type="au" direction="in" tp:type="Contact_Handle[]">
+        <tp:docstring>Some contacts to block.</tp:docstring>
+      </arg>
+
+      <arg name="Report_Abusive" type="b" direction="in">
+        <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+          <p>In addition to blocking, report these contacts as abusive to the
+            server administrators.</p>
+
+          <p>Clients can determine whether this capability is available by
+            checking the
+            <tp:member-ref>ContactBlockingCapabilities</tp:member-ref>
+            property. If the capability is not present and this argument is
+            true, the error <tp:error-ref>NotCapable</tp:error-ref> SHOULD
+            be raised.</p>
+
+          <tp:rationale>
+            Some protocol libraries, in their conformance requirements,
+            require the ability to report blocked contacts as abusive.
+          </tp:rationale>
+        </tp:docstring>
+      </arg>
+
+      <tp:possible-errors>
+        <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.NotCapable"/>
+      </tp:possible-errors>
+    </method>
+
+    <method name="UnblockContacts" tp:name-for-bindings="Unblock_Contacts">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Reverse the effects of a previous call to
+          <tp:member-ref>BlockContacts</tp:member-ref>.</p>
+      </tp:docstring>
+
+      <arg name="Contacts" type="au" direction="in" tp:type="Contact_Handle[]">
+        <tp:docstring>Some contacts to unblock.</tp:docstring>
+      </arg>
+
+      <tp:possible-errors>
+        <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+      </tp:possible-errors>
+    </method>
+
+    <method name="RequestBlockedContacts"
+      tp:name-for-bindings="Request_Blocked_Contacts">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>List the contacts that are blocked.</p>
+
+        <p>Clients SHOULD allow a relatively long timeout for calls to this
+          method, since on some protocols contact blocking is part of the
+          contact list, which can take a significant time to retrieve.</p>
+      </tp:docstring>
+
+      <arg name="Contacts" type="au" direction="out" tp:type="Contact_Handle[]">
+        <tp:docstring>The list of blocked contacts.</tp:docstring>
+      </arg>
+
+      <tp:possible-errors>
+        <tp:error name="org.freedesktop.Telepathy.Error.Disconnected"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.InvalidHandle"/>
+        <tp:error name="org.freedesktop.Telepathy.Error.NetworkError"/>
+      </tp:possible-errors>
+    </method>
+
+    <signal name="BlockedContactsChanged"
+      tp:name-for-bindings="Blocked_Contacts_Changed">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Emitted when the list of blocked contacts is first retrieved
+          (before returning from any pending calls to
+          <tp:member-ref>RequestBlockedContacts</tp:member-ref>), and
+          whenever the list of blocked contacts subsequently changes.</p>
+      </tp:docstring>
+
+      <arg name="BlockedContacts" type="au" tp:type="Contact_Handle[]">
+        <tp:docstring>Contacts added to the result of
+          <tp:member-ref>RequestBlockedContacts</tp:member-ref>.</tp:docstring>
+      </arg>
+
+      <arg name="UnblockedContacts" type="au" tp:type="Contact_Handle[]">
+        <tp:docstring>Contacts removed from the result of
+          <tp:member-ref>RequestBlockedContacts</tp:member-ref>.</tp:docstring>
+      </arg>
+    </signal>
+
+    <tp:contact-attribute name="blocked" type="b">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>True if the contact would be in the result of
+          <tp:member-ref>RequestBlockedContacts</tp:member-ref>;
+          False or omitted if the contact is not blocked, or if it is
+          unknown whether the contact is blocked.</p>
+      </tp:docstring>
+    </tp:contact-attribute>
+
+    <property name="ContactBlockingCapabilities"
+              tp:name-for-bindings="Contact_Blocking_Capabilities"
+              tp:type="Contact_Blocking_Capabilities" type="u" access="read"
+              tp:immutable="yes">
+      <tp:docstring xmlns="http://www.w3.org/1999/xhtml";>
+        <p>Additional capabilities for contact blocking (i.e. whether we can
+          report abusive contacts).</p>
+
+        <p><b>Note:</b> there is no capability for supporting blocking itself,
+          the presence of this interface indicates that contact blocking is
+          supported.</p>
+      </tp:docstring>
+    </property>
+
+    <tp:flags name="Contact_Blocking_Capabilities" type="u"
+              value-prefix="Contact_Blocking_Capability">
+      <tp:flag suffix="Can_Report_Abusive" value="1">
+        <tp:docstring>
+          This protocol is capable of reporting abusive contacts to the server
+          administrators.
+        </tp:docstring>
+      </tp:flag>
+    </tp:flags>
+
+  </interface>
+</node>
+<!-- vim:set sw=2 sts=2 et ft=xml: -->
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
index d0f1048..b3147f3 100644
--- a/extensions/Makefile.am
+++ b/extensions/Makefile.am
@@ -18,6 +18,7 @@ EXTRA_DIST = \
     Authentication_TLS_Certificate.xml \
     Channel_Interface_Credentials_Storage.xml \
     Channel_Type_Server_TLS_Connection.xml \
+    Connection_Interface_Contact_Blocking.xml \
     $(NULL)
 
 noinst_LTLIBRARIES = libemp-extensions.la
diff --git a/extensions/misc.xml b/extensions/misc.xml
index b1f6e88..9b153f1 100644
--- a/extensions/misc.xml
+++ b/extensions/misc.xml
@@ -10,5 +10,6 @@
 <xi:include href="Authentication_TLS_Certificate.xml" />
 <xi:include href="Channel_Interface_Credentials_Storage.xml" />
 <xi:include href="Channel_Type_Server_TLS_Connection.xml" />
+<xi:include href="Connection_Interface_Contact_Blocking.xml" />
 
 </tp:spec>
diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am
index 47b416f..18f9a1c 100644
--- a/libempathy-gtk/Makefile.am
+++ b/libempathy-gtk/Makefile.am
@@ -39,6 +39,7 @@ libempathy_gtk_handwritten_source =            	\
 	empathy-chat-text-view.c		\
 	empathy-chat-view.c			\
 	empathy-chat.c				\
+	empathy-contact-blocking-dialog.c	\
 	empathy-contact-dialogs.c		\
 	empathy-contact-list-store.c		\
 	empathy-contact-list-view.c		\
@@ -98,6 +99,7 @@ libempathy_gtk_headers =			\
 	empathy-chat-text-view.h		\
 	empathy-chat-view.h			\
 	empathy-chat.h				\
+	empathy-contact-blocking-dialog.h	\
 	empathy-contact-dialogs.h		\
 	empathy-contact-list-store.h		\
 	empathy-contact-list-view.h		\
@@ -177,6 +179,7 @@ uidir = $(datadir)/empathy
 ui_DATA = 					\
 	empathy-contact-widget.ui		\
 	empathy-contact-dialogs.ui		\
+	empathy-contact-blocking-dialog.ui	\
 	empathy-account-widget-generic.ui	\
 	empathy-account-widget-jabber.ui	\
 	empathy-account-widget-msn.ui		\
diff --git a/libempathy-gtk/empathy-chat.c b/libempathy-gtk/empathy-chat.c
index 72e9060..b696963 100644
--- a/libempathy-gtk/empathy-chat.c
+++ b/libempathy-gtk/empathy-chat.c
@@ -3590,7 +3590,8 @@ empathy_chat_get_contact_menu (EmpathyChat *chat)
 		menu = empathy_contact_menu_new (priv->remote_contact,
 						 EMPATHY_CONTACT_FEATURE_CALL |
 						 EMPATHY_CONTACT_FEATURE_LOG |
-						 EMPATHY_CONTACT_FEATURE_INFO);
+						 EMPATHY_CONTACT_FEATURE_INFO |
+						 EMPATHY_CONTACT_FEATURE_BLOCK);
 	}
 
 	return menu;
diff --git a/libempathy-gtk/empathy-contact-blocking-dialog.c b/libempathy-gtk/empathy-contact-blocking-dialog.c
new file mode 100644
index 0000000..a46820c
--- /dev/null
+++ b/libempathy-gtk/empathy-contact-blocking-dialog.c
@@ -0,0 +1,791 @@
+/*
+ * empathy-contact-blocking-dialog.c
+ *
+ * EmpathyContactBlockingDialog
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Danielle Madeley <danielle madeley collabora co uk>
+ */
+
+#include <glib/gi18n.h>
+
+#include <libempathy/empathy-utils.h>
+
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/empathy-tp-contact-list.h>
+
+#include <libempathy-gtk/empathy-account-chooser.h>
+#include <libempathy-gtk/empathy-ui-utils.h>
+
+#include "empathy-contact-blocking-dialog.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_OTHER
+#include <libempathy/empathy-debug.h>
+
+#define GET_PRIVATE(o) (EMPATHY_CONTACT_BLOCKING_DIALOG (o)->priv)
+#define DECLARE_CALLBACK(func) \
+  static void func (GObject *, GAsyncResult *, gpointer);
+
+G_DEFINE_TYPE (EmpathyContactBlockingDialog, empathy_contact_blocking_dialog,
+    GTK_TYPE_DIALOG);
+
+struct _EmpathyContactBlockingDialogPrivate
+{
+  /* a map of all active connections to their 'deny' channel */
+  GHashTable *channels; /* reffed TpConnection* -> reffed TpChannel* */
+
+  guint block_account_changed;
+
+  GtkListStore *blocked_contacts;
+  GtkListStore *completion_contacts;
+  GtkTreeSelection *selection;
+
+  GtkWidget *account_chooser;
+  GtkWidget *add_button;
+  GtkWidget *add_contact_entry;
+  GtkWidget *info_bar;
+  GtkWidget *info_bar_label;
+  GtkWidget *remove_button;
+};
+
+enum /* blocked-contacts columns */
+{
+  COL_IDENTIFIER,
+  COL_HANDLE,
+  N_COLUMNS
+};
+
+static const char *
+get_pretty_conn_name (TpConnection *conn)
+{
+  return tp_proxy_get_object_path (conn) + strlen (TP_CONN_OBJECT_PATH_BASE);
+}
+
+static void
+contact_blocking_dialog_filter_account_chooser (TpAccount *account,
+    EmpathyAccountChooserFilterResultCallback callback,
+    gpointer callback_data,
+    gpointer user_data)
+{
+  EmpathyContactBlockingDialog *self = user_data;
+  TpConnection *conn = tp_account_get_connection (account);
+  gboolean enable;
+
+  enable =
+    conn != NULL &&
+    g_hash_table_lookup (self->priv->channels, conn) != NULL;
+
+  callback (enable, callback_data);
+}
+
+static void contact_blocking_dialog_account_changed (GtkWidget *,
+    EmpathyContactBlockingDialog *);
+
+static void
+contact_blocking_dialog_refilter_account_chooser (
+    EmpathyContactBlockingDialog *self)
+{
+  EmpathyAccountChooser *chooser =
+    EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser);
+  TpConnection *conn;
+  gboolean enabled;
+
+  DEBUG ("Refiltering account chooser");
+
+  /* set the filter to refilter the account chooser */
+  self->priv->block_account_changed++;
+  empathy_account_chooser_set_filter (chooser,
+      contact_blocking_dialog_filter_account_chooser, self);
+  self->priv->block_account_changed--;
+
+  conn = empathy_account_chooser_get_connection (chooser);
+  enabled = (empathy_account_chooser_get_account (chooser) != NULL &&
+             conn != NULL &&
+             g_hash_table_lookup (self->priv->channels, conn) != NULL);
+
+  if (!enabled)
+    DEBUG ("No account selected");
+
+  gtk_widget_set_sensitive (self->priv->add_button, enabled);
+  gtk_widget_set_sensitive (self->priv->add_contact_entry, enabled);
+
+  contact_blocking_dialog_account_changed (self->priv->account_chooser, self);
+}
+
+static void contact_blocking_dialog_inspected_handles (TpConnection *,
+    const char **, const GError *, gpointer, GObject *);
+
+static void
+contact_blocking_dialog_add_contacts_to_list (
+    EmpathyContactBlockingDialog *self,
+    TpConnection *conn,
+    GArray *handles)
+{
+  if (handles->len > 0)
+    tp_cli_connection_call_inspect_handles (conn, -1,
+        TP_HANDLE_TYPE_CONTACT, handles,
+        contact_blocking_dialog_inspected_handles,
+        g_boxed_copy (DBUS_TYPE_G_UINT_ARRAY, handles),
+        (GDestroyNotify) g_array_unref, G_OBJECT (self));
+}
+
+static void
+contact_blocking_dialog_inspected_handles (TpConnection *conn,
+    const char **identifiers,
+    const GError *in_error,
+    gpointer user_data,
+    GObject *self)
+{
+  EmpathyContactBlockingDialogPrivate *priv = GET_PRIVATE (self);
+  GArray *handles = user_data;
+  guint i;
+
+  if (in_error != NULL)
+    {
+      DEBUG ("Failed to inspect handles: %s", in_error->message);
+      return;
+    }
+
+  DEBUG ("Adding %u identifiers", handles->len);
+
+  for (i = 0; i < handles->len; i++)
+    {
+      const char *identifier = identifiers[i];
+      TpHandle handle = g_array_index (handles, TpHandle, i);
+
+      gtk_list_store_insert_with_values (priv->blocked_contacts, NULL, -1,
+          COL_IDENTIFIER, identifier,
+          COL_HANDLE, handle,
+          -1);
+    }
+}
+
+DECLARE_CALLBACK (contact_blocking_dialog_connection_prepared);
+
+static void
+contact_blocking_dialog_connection_status_changed (TpAccount *account,
+    guint old_status,
+    guint new_status,
+    guint reason,
+    const char *dbus_reason,
+    GHashTable *details,
+    EmpathyContactBlockingDialog *self)
+{
+  TpConnection *conn = tp_account_get_connection (account);
+
+  switch (new_status)
+    {
+      case TP_CONNECTION_STATUS_DISCONNECTED:
+        DEBUG ("Connection %s invalidated", get_pretty_conn_name (conn));
+
+        /* remove the channel from the hash table */
+        g_hash_table_remove (self->priv->channels, conn);
+        contact_blocking_dialog_refilter_account_chooser (self);
+        break;
+
+      case TP_CONNECTION_STATUS_CONNECTING:
+        break;
+
+      case TP_CONNECTION_STATUS_CONNECTED:
+        DEBUG ("Connection %s reconnected", get_pretty_conn_name (conn));
+
+        tp_proxy_prepare_async (conn, NULL,
+            contact_blocking_dialog_connection_prepared, self);
+    }
+}
+
+static void
+contact_blocking_dialog_deny_channel_members_changed (TpChannel *channel,
+    const char *message,
+    GArray *added,
+    GArray *removed,
+    GArray *local_pending,
+    GArray *remote_pending,
+    TpHandle actor,
+    guint reason,
+    EmpathyContactBlockingDialog *self)
+{
+  TpConnection *conn = tp_channel_borrow_connection (channel);
+  GtkTreeModel *model = GTK_TREE_MODEL (self->priv->blocked_contacts);
+  GtkTreeIter iter;
+  TpIntset *removed_set;
+  gboolean valid;
+
+  /* we only care about changes to the selected connection */
+  /* FIXME: can we compare proxy pointers directly? */
+  if (tp_strdiff (
+        tp_proxy_get_object_path (tp_channel_borrow_connection (channel)),
+        tp_proxy_get_object_path (empathy_account_chooser_get_connection (
+            EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser)))))
+    return;
+
+  DEBUG ("deny list changed: %u added, %u removed", added->len, removed->len);
+
+  /* add contacts */
+  contact_blocking_dialog_add_contacts_to_list (self, conn, added);
+
+  /* remove contacts */
+  removed_set = tp_intset_from_array (removed);
+
+  valid = gtk_tree_model_get_iter_first (model, &iter);
+  while (valid)
+    {
+      TpHandle handle;
+
+      gtk_tree_model_get (model, &iter,
+          COL_HANDLE, &handle,
+          -1);
+
+      if (tp_intset_is_member (removed_set, handle))
+        valid = gtk_list_store_remove (self->priv->blocked_contacts, &iter);
+      else
+        valid = gtk_tree_model_iter_next (model, &iter);
+    }
+
+  tp_intset_destroy (removed_set);
+}
+
+DECLARE_CALLBACK (contact_blocking_dialog_connection_prepared);
+
+static void
+contact_blocking_dialog_am_prepared (GObject *am,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  EmpathyContactBlockingDialog *self = user_data;
+  GList *accounts, *ptr;
+  GError *error = NULL;
+
+  if (!tp_proxy_prepare_finish (am, result, &error))
+    {
+      g_critical ("Could not prepare Account Manager: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  accounts = tp_account_manager_get_valid_accounts (TP_ACCOUNT_MANAGER (am));
+
+  for (ptr = accounts; ptr != NULL; ptr = ptr->next)
+    {
+      TpAccount *account = ptr->data;
+      TpConnection *conn;
+
+      tp_g_signal_connect_object (account, "status-changed",
+          G_CALLBACK (contact_blocking_dialog_connection_status_changed),
+          self, 0);
+
+      conn = tp_account_get_connection (TP_ACCOUNT (account));
+
+      if (conn != NULL)
+        {
+          tp_proxy_prepare_async (conn, NULL,
+              contact_blocking_dialog_connection_prepared, self);
+        }
+    }
+
+  g_list_free (accounts);
+}
+
+static void contact_blocking_dialog_got_deny_channel (TpConnection *,
+    gboolean, const char *, GHashTable *, const GError *, gpointer, GObject *);
+
+static void
+contact_blocking_dialog_connection_prepared (GObject *conn,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  EmpathyContactBlockingDialog *self = user_data;
+  GHashTable *request;
+  GError *error = NULL;
+
+  if (!tp_proxy_prepare_finish (conn, result, &error))
+    {
+      DEBUG ("Failed to prepare connection: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  /* request the deny channel */
+  request = tp_asv_new (
+      TP_PROP_CHANNEL_CHANNEL_TYPE,
+      G_TYPE_STRING,
+      TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
+
+      TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
+      G_TYPE_UINT,
+      TP_HANDLE_TYPE_LIST,
+
+      TP_PROP_CHANNEL_TARGET_ID,
+      G_TYPE_STRING,
+      "deny",
+
+      NULL);
+
+  tp_cli_connection_interface_requests_call_ensure_channel (
+      TP_CONNECTION (conn), -1, request,
+      contact_blocking_dialog_got_deny_channel, NULL, NULL, G_OBJECT (self));
+
+  g_hash_table_destroy (request);
+}
+
+DECLARE_CALLBACK (contact_blocking_dialog_deny_channel_prepared);
+
+static void
+contact_blocking_dialog_got_deny_channel (TpConnection *conn,
+    gboolean yours,
+    const char *channel_path,
+    GHashTable *props,
+    const GError *in_error,
+    gpointer user_data,
+    GObject *self)
+{
+  TpChannel *channel;
+  GError *error = NULL;
+
+  const GQuark features[] = {
+      TP_CHANNEL_FEATURE_CORE,
+      TP_CHANNEL_FEATURE_GROUP,
+      0 };
+
+  if (in_error != NULL)
+    {
+      DEBUG ("Failed to get 'deny' channel: %s", in_error->message);
+      return;
+    }
+
+  channel = tp_channel_new_from_properties (conn, channel_path, props, &error);
+
+  if (error != NULL)
+    {
+      DEBUG ("Failed to create channel proxy: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  tp_proxy_prepare_async (channel, features,
+      contact_blocking_dialog_deny_channel_prepared, self);
+}
+
+static void
+contact_blocking_dialog_deny_channel_prepared (GObject *channel,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  EmpathyContactBlockingDialog *self = user_data;
+  TpConnection *conn;
+  GError *error = NULL;
+
+  if (!tp_proxy_prepare_finish (channel, result, &error))
+    {
+      DEBUG ("Failed to prepare channel: %s", error->message);
+      g_error_free (error);
+      return;
+    }
+
+  conn = tp_channel_borrow_connection (TP_CHANNEL (channel));
+
+  DEBUG ("Channel prepared for connection %s", get_pretty_conn_name (conn));
+
+  g_hash_table_insert (self->priv->channels,
+      g_object_ref (conn), channel);
+  contact_blocking_dialog_refilter_account_chooser (self);
+
+  tp_g_signal_connect_object (channel, "group-members-changed",
+      G_CALLBACK (contact_blocking_dialog_deny_channel_members_changed),
+      self, 0);
+}
+
+static void
+contact_blocking_dialog_set_error (EmpathyContactBlockingDialog *self,
+    const GError *error)
+{
+  const char *msg = NULL;
+
+  if (error->domain == TP_ERRORS)
+    {
+      if (error->code == TP_ERROR_INVALID_HANDLE)
+        msg = _("Unknown or invalid identifier");
+      else if (error->code == TP_ERROR_NOT_AVAILABLE)
+        msg = _("Contact blocking temporarily unavailable");
+      else if (error->code == TP_ERROR_NOT_CAPABLE)
+        msg = _("Contact blocking unavailable");
+      else if (error->code == TP_ERROR_PERMISSION_DENIED)
+        msg = _("Permission Denied");
+    }
+
+  if (msg == NULL)
+    msg = _("Could not block contact");
+
+  gtk_label_set_text (GTK_LABEL (self->priv->info_bar_label), msg);
+  gtk_widget_show (self->priv->info_bar);
+}
+
+static void contact_blocking_dialog_add_contact_got_handle (TpConnection *,
+    const GArray *, const GError *, gpointer, GObject *);
+
+static void
+contact_blocking_dialog_add_contact (GtkWidget *widget,
+    EmpathyContactBlockingDialog *self)
+{
+  TpConnection *conn = empathy_account_chooser_get_connection (
+      EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser));
+  const char *identifiers[2] = { NULL, };
+
+  identifiers[0] = gtk_entry_get_text (
+      GTK_ENTRY (self->priv->add_contact_entry));
+
+  DEBUG ("Looking up handle for '%s'", identifiers[0]);
+
+  tp_cli_connection_call_request_handles (conn, -1,
+      TP_HANDLE_TYPE_CONTACT, identifiers,
+      contact_blocking_dialog_add_contact_got_handle,
+      NULL, NULL, G_OBJECT (self));
+
+  gtk_entry_set_text (GTK_ENTRY (self->priv->add_contact_entry), "");
+  gtk_widget_hide (self->priv->info_bar);
+}
+
+static void
+contact_blocking_dialog_added_contact (TpChannel *, const GError *,
+    gpointer, GObject *);
+
+static void
+contact_blocking_dialog_add_contact_got_handle (TpConnection *conn,
+    const GArray *handles,
+    const GError *in_error,
+    gpointer user_data,
+    GObject *self)
+{
+  EmpathyContactBlockingDialogPrivate *priv = GET_PRIVATE (self);
+  TpChannel *channel = g_hash_table_lookup (priv->channels, conn);
+
+  if (in_error != NULL)
+    {
+      DEBUG ("Error getting handle: %s", in_error->message);
+
+      contact_blocking_dialog_set_error (
+          EMPATHY_CONTACT_BLOCKING_DIALOG (self), in_error);
+
+      return;
+    }
+
+  g_return_if_fail (handles->len == 1);
+
+  DEBUG ("Adding handle %u to deny channel",
+      g_array_index (handles, TpHandle, 0));
+
+  tp_cli_channel_interface_group_call_add_members (channel, -1,
+      handles, "",
+      contact_blocking_dialog_added_contact, NULL, NULL, self);
+}
+
+static void
+contact_blocking_dialog_added_contact (TpChannel *channel,
+    const GError *in_error,
+    gpointer user_data,
+    GObject *self)
+{
+  if (in_error != NULL)
+    {
+      DEBUG ("Error adding contact to deny list: %s", in_error->message);
+
+      contact_blocking_dialog_set_error (
+          EMPATHY_CONTACT_BLOCKING_DIALOG (self), in_error);
+
+      return;
+    }
+
+  DEBUG ("Contact added");
+}
+
+static void
+contact_blocking_dialog_removed_contacts (TpChannel *,
+    const GError *, gpointer, GObject *);
+
+static void
+contact_blocking_dialog_remove_contacts (GtkWidget *button,
+    EmpathyContactBlockingDialog *self)
+{
+  TpConnection *conn = empathy_account_chooser_get_connection (
+      EMPATHY_ACCOUNT_CHOOSER (self->priv->account_chooser));
+  TpChannel *channel = g_hash_table_lookup (self->priv->channels, conn);
+  GtkTreeModel *model;
+  GList *rows, *ptr;
+  GArray *handles = g_array_new (FALSE, FALSE, sizeof (TpHandle));
+
+  rows = gtk_tree_selection_get_selected_rows (self->priv->selection, &model);
+
+  for (ptr = rows; ptr != NULL; ptr = ptr->next)
+    {
+      GtkTreePath *path = ptr->data;
+      GtkTreeIter iter;
+      TpHandle handle;
+
+      if (!gtk_tree_model_get_iter (model, &iter, path))
+        continue;
+
+      gtk_tree_model_get (model, &iter,
+          COL_HANDLE, &handle,
+          -1);
+
+      g_array_append_val (handles, handle);
+      gtk_tree_path_free (path);
+    }
+
+  g_list_free (rows);
+
+  if (handles->len > 0)
+    {
+      DEBUG ("Removing %u handles", handles->len);
+
+      tp_cli_channel_interface_group_call_remove_members (channel, -1,
+          handles, "",
+          contact_blocking_dialog_removed_contacts,
+          NULL, NULL, G_OBJECT (self));
+    }
+
+  g_array_unref (handles);
+}
+
+static void
+contact_blocking_dialog_removed_contacts (TpChannel *channel,
+    const GError *in_error,
+    gpointer user_data,
+    GObject *self)
+{
+  if (in_error != NULL)
+    {
+      DEBUG ("Error removing contacts from deny list: %s", in_error->message);
+
+      contact_blocking_dialog_set_error (
+          EMPATHY_CONTACT_BLOCKING_DIALOG (self), in_error);
+
+      return;
+    }
+
+  DEBUG ("Contacts removed");
+}
+
+static void
+contact_blocking_dialog_account_changed (GtkWidget *account_chooser,
+    EmpathyContactBlockingDialog *self)
+{
+  TpConnection *conn = empathy_account_chooser_get_connection (
+      EMPATHY_ACCOUNT_CHOOSER (account_chooser));
+  TpChannel *channel;
+  GArray *blocked;
+  EmpathyContactManager *contact_manager;
+  EmpathyTpContactList *contact_list;
+  GList *members, *ptr;
+
+  if (self->priv->block_account_changed > 0)
+    return;
+
+  /* clear the lists of contacts */
+  gtk_list_store_clear (self->priv->blocked_contacts);
+  gtk_list_store_clear (self->priv->completion_contacts);
+
+  if (conn == NULL)
+    return;
+
+  DEBUG ("Account changed: %s", get_pretty_conn_name (conn));
+
+  /* load the deny list */
+  channel = g_hash_table_lookup (self->priv->channels, conn);
+
+  if (channel == NULL)
+    return;
+
+  g_return_if_fail (TP_IS_CHANNEL (channel));
+
+  blocked = tp_intset_to_array (tp_channel_group_get_members (channel));
+
+  DEBUG ("%u contacts on blocked list", blocked->len);
+
+  contact_blocking_dialog_add_contacts_to_list (self, conn, blocked);
+  g_array_unref (blocked);
+
+  /* load the completion list */
+  g_return_if_fail (empathy_contact_manager_initialized ());
+
+  DEBUG ("Loading contacts");
+
+  contact_manager = empathy_contact_manager_dup_singleton ();
+  contact_list = empathy_contact_manager_get_list (contact_manager, conn);
+  members = empathy_contact_list_get_members (
+      EMPATHY_CONTACT_LIST (contact_list));
+
+  for (ptr = members; ptr != NULL; ptr = ptr->next)
+    {
+      EmpathyContact *contact = ptr->data;
+
+      gtk_list_store_insert_with_values (self->priv->completion_contacts,
+          NULL, -1,
+          COL_IDENTIFIER, empathy_contact_get_id (contact),
+          -1);
+
+      g_object_unref (contact);
+    }
+
+  g_list_free (members);
+  g_object_unref (contact_manager);
+}
+
+static void
+contact_blocking_dialog_view_selection_changed (GtkTreeSelection *selection,
+    EmpathyContactBlockingDialog *self)
+{
+  GList *rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+
+  /* update the sensitivity of the remove button */
+  gtk_widget_set_sensitive (self->priv->remove_button, rows != NULL);
+
+  g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
+  g_list_free (rows);
+}
+
+static void
+contact_blocking_dialog_dispose (GObject *self)
+{
+  EmpathyContactBlockingDialogPrivate *priv = GET_PRIVATE (self);
+
+  tp_clear_pointer (&priv->channels, g_hash_table_destroy);
+
+  G_OBJECT_CLASS (empathy_contact_blocking_dialog_parent_class)->dispose (self);
+}
+
+static void
+empathy_contact_blocking_dialog_class_init (
+    EmpathyContactBlockingDialogClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->dispose = contact_blocking_dialog_dispose;
+
+  g_type_class_add_private (gobject_class,
+      sizeof (EmpathyContactBlockingDialogPrivate));
+}
+
+static void
+empathy_contact_blocking_dialog_init (EmpathyContactBlockingDialog *self)
+{
+  GtkBuilder *gui;
+  char *filename;
+  GtkWidget *contents;
+  GtkWidget *account_hbox, *blocked_contacts_view;
+  GtkEntryCompletion *completion;
+  TpAccountManager *am;
+
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG,
+      EmpathyContactBlockingDialogPrivate);
+
+  self->priv->channels = g_hash_table_new_full (NULL, NULL,
+      g_object_unref, g_object_unref);
+
+  gtk_window_set_title (GTK_WINDOW (self), _("Edit Blocked Contacts"));
+  gtk_dialog_add_button (GTK_DIALOG (self),
+      GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
+
+  filename = empathy_file_lookup ("empathy-contact-blocking-dialog.ui",
+      "libempathy-gtk");
+
+  gui = empathy_builder_get_file (filename,
+      "contents", &contents,
+      "account-hbox", &account_hbox,
+      "add-button", &self->priv->add_button,
+      "add-contact-entry", &self->priv->add_contact_entry,
+      "blocked-contacts", &self->priv->blocked_contacts,
+      "blocked-contacts-view", &blocked_contacts_view,
+      "remove-button", &self->priv->remove_button,
+      NULL);
+
+  empathy_builder_connect (gui, self,
+      "add-button", "clicked", contact_blocking_dialog_add_contact,
+      "add-contact-entry", "activate", contact_blocking_dialog_add_contact,
+      "remove-button", "clicked", contact_blocking_dialog_remove_contacts,
+      NULL);
+
+  /* add the contents to the dialog */
+  gtk_container_add (
+      GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (self))),
+      contents);
+  gtk_widget_show (contents);
+
+  /* set up the tree selection */
+  self->priv->selection = gtk_tree_view_get_selection (
+      GTK_TREE_VIEW (blocked_contacts_view));
+  gtk_tree_selection_set_mode (self->priv->selection, GTK_SELECTION_MULTIPLE);
+  g_signal_connect (self->priv->selection, "changed",
+      G_CALLBACK (contact_blocking_dialog_view_selection_changed), self);
+
+  /* build the contact entry */
+  self->priv->completion_contacts = gtk_list_store_new (1, G_TYPE_STRING);
+  completion = gtk_entry_completion_new ();
+  gtk_entry_completion_set_model (completion,
+      GTK_TREE_MODEL (self->priv->completion_contacts));
+  gtk_entry_completion_set_text_column (completion, COL_IDENTIFIER);
+  gtk_entry_set_completion (GTK_ENTRY (self->priv->add_contact_entry),
+      completion);
+  g_object_unref (completion);
+  g_object_unref (self->priv->completion_contacts);
+
+  /* add the account chooser */
+  self->priv->account_chooser = empathy_account_chooser_new ();
+  contact_blocking_dialog_refilter_account_chooser (self);
+  g_signal_connect (self->priv->account_chooser, "changed",
+      G_CALLBACK (contact_blocking_dialog_account_changed), self);
+
+  gtk_box_pack_start (GTK_BOX (account_hbox), self->priv->account_chooser,
+      TRUE, TRUE, 0);
+  gtk_widget_show (self->priv->account_chooser);
+
+  /* add an error warning info bar */
+  self->priv->info_bar = gtk_info_bar_new ();
+  gtk_box_pack_start (GTK_BOX (contents), self->priv->info_bar, FALSE, TRUE, 0);
+  gtk_info_bar_set_message_type (GTK_INFO_BAR (self->priv->info_bar),
+      GTK_MESSAGE_ERROR);
+
+  self->priv->info_bar_label = gtk_label_new ("");
+  gtk_container_add (GTK_CONTAINER (
+        gtk_info_bar_get_content_area (GTK_INFO_BAR (self->priv->info_bar))),
+      self->priv->info_bar_label);
+  gtk_widget_show (self->priv->info_bar_label);
+
+  /* prepare the account manager */
+  am = tp_account_manager_dup ();
+  tp_proxy_prepare_async (am, NULL, contact_blocking_dialog_am_prepared, self);
+  g_object_unref (am);
+
+  g_free (filename);
+  g_object_unref (gui);
+}
+
+GtkWidget *
+empathy_contact_blocking_dialog_new (GtkWindow *parent)
+{
+  GtkWidget *self = g_object_new (EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG,
+      NULL);
+
+  if (parent != NULL)
+    {
+      gtk_window_set_transient_for (GTK_WINDOW (self), parent);
+    }
+
+  return self;
+}
diff --git a/libempathy-gtk/empathy-contact-blocking-dialog.h b/libempathy-gtk/empathy-contact-blocking-dialog.h
new file mode 100644
index 0000000..96451c6
--- /dev/null
+++ b/libempathy-gtk/empathy-contact-blocking-dialog.h
@@ -0,0 +1,60 @@
+/*
+ * empathy-contact-blocking-dialog.h
+ *
+ * EmpathyContactBlockingDialog
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Danielle Madeley <danielle madeley collabora co uk>
+ */
+
+#ifndef __EMPATHY_CONTACT_BLOCKING_DIALOG_H__
+#define __EMPATHY_CONTACT_BLOCKING_DIALOG_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG	(empathy_contact_blocking_dialog_get_type ())
+#define EMPATHY_CONTACT_BLOCKING_DIALOG(obj)	(G_TYPE_CHECK_INSTANCE_CAST ((obj), EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG, EmpathyContactBlockingDialog))
+#define EMPATHY_CONTACT_BLOCKING_DIALOG_CLASS(obj)	(G_TYPE_CHECK_CLASS_CAST ((obj), EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG, EmpathyContactBlockingDialogClass))
+#define EMPATHY_IS_CONTACT_BLOCKING_DIALOG(obj)	(G_TYPE_CHECK_INSTANCE_TYPE ((obj), EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG))
+#define EMPATHY_IS_CONTACT_BLOCKING_DIALOG_CLASS(obj)	(G_TYPE_CHECK_CLASS_TYPE ((obj), EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG))
+#define EMPATHY_CONTACT_BLOCKING_DIALOG_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG, EmpathyContactBlockingDialogClass))
+
+typedef struct _EmpathyContactBlockingDialog EmpathyContactBlockingDialog;
+typedef struct _EmpathyContactBlockingDialogClass EmpathyContactBlockingDialogClass;
+typedef struct _EmpathyContactBlockingDialogPrivate EmpathyContactBlockingDialogPrivate;
+
+struct _EmpathyContactBlockingDialog
+{
+  GtkDialog parent;
+  EmpathyContactBlockingDialogPrivate *priv;
+};
+
+struct _EmpathyContactBlockingDialogClass
+{
+  GtkDialogClass parent_class;
+};
+
+GType empathy_contact_blocking_dialog_get_type (void);
+
+GtkWidget *empathy_contact_blocking_dialog_new (GtkWindow *parent);
+
+G_END_DECLS
+
+#endif
diff --git a/libempathy-gtk/empathy-contact-blocking-dialog.ui b/libempathy-gtk/empathy-contact-blocking-dialog.ui
new file mode 100644
index 0000000..b2ea89b
--- /dev/null
+++ b/libempathy-gtk/empathy-contact-blocking-dialog.ui
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkListStore" id="blocked-contacts">
+    <columns>
+      <!-- column-name identifier -->
+      <column type="gchararray"/>
+      <!-- column-name handle -->
+      <column type="guint"/>
+    </columns>
+  </object>
+  <object class="GtkVBox" id="contents">
+    <property name="visible">True</property>
+    <property name="border_width">6</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkHBox" id="account-hbox">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+            <property name="xalign">0</property>
+            <property name="label" translatable="yes">Account:</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHBox" id="hbox1">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <property name="shadow_type">etched-in</property>
+            <child>
+              <object class="GtkTreeView" id="blocked-contacts-view">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="model">blocked-contacts</property>
+                <property name="headers_clickable">False</property>
+                <property name="search_column">0</property>
+                <child>
+                  <object class="GtkTreeViewColumn" id="treeviewcolumn1">
+                    <property name="title">Blocked Contacts</property>
+                    <property name="sort_indicator">True</property>
+                    <property name="sort_column_id">0</property>
+                    <child>
+                      <object class="GtkCellRendererText" id="cellrenderertext1"/>
+                      <attributes>
+                        <attribute name="text">0</attribute>
+                      </attributes>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVButtonBox" id="vbuttonbox1">
+            <property name="visible">True</property>
+            <property name="layout_style">start</property>
+            <child>
+              <object class="GtkButton" id="remove-button">
+                <property name="label">gtk-remove</property>
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHBox" id="add-contact-hbox">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkEntry" id="add-contact-entry">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="invisible_char">●</property>
+          </object>
+          <packing>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="add-button">
+            <property name="label">gtk-add</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_stock">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkSizeGroup" id="sizegroup1">
+    <widgets>
+      <widget name="add-button"/>
+      <widget name="remove-button"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/libempathy-gtk/empathy-contact-dialogs.c b/libempathy-gtk/empathy-contact-dialogs.c
index fb67c41..d16062e 100644
--- a/libempathy-gtk/empathy-contact-dialogs.c
+++ b/libempathy-gtk/empathy-contact-dialogs.c
@@ -17,6 +17,7 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  *
  * Authors: Xavier Claessens <xclaesse gmail com>
+ *          Danielle Madeley <danielle madeley collabora co uk>
  */
 
 #include <config.h>
@@ -83,9 +84,29 @@ subscription_dialog_response_cb (GtkDialog *dialog,
 		empathy_contact_list_remove (EMPATHY_CONTACT_LIST (manager),
 					     contact, "");
 	}
+	else if (response == GTK_RESPONSE_REJECT) {
+		gboolean abusive;
+
+		/* confirm the blocking */
+		if (empathy_block_contact_dialog_show (GTK_WINDOW (dialog),
+						       contact, &abusive)) {
+			empathy_contact_list_remove (
+					EMPATHY_CONTACT_LIST (manager),
+					contact, "");
+			empathy_contact_list_set_blocked (
+					EMPATHY_CONTACT_LIST (manager),
+					contact, TRUE, abusive);
+		} else {
+			/* if they don't confirm, return back to the
+			 * first dialog */
+			goto finally;
+		}
+	}
 
 	subscription_dialogs = g_list_remove (subscription_dialogs, dialog);
 	gtk_widget_destroy (GTK_WIDGET (dialog));
+
+finally:
 	g_object_unref (manager);
 }
 
@@ -99,8 +120,13 @@ empathy_subscription_dialog_show (EmpathyContact *contact,
 	GtkWidget *hbox_subscription;
 	GtkWidget *vbox;
 	GtkWidget *contact_widget;
+	GtkWidget *block_user_button;
 	GList     *l;
 	gchar     *filename;
+	EmpathyContactManager *manager;
+	EmpathyContactListFlags flags;
+
+	manager = empathy_contact_manager_dup_singleton ();
 
 	g_return_if_fail (EMPATHY_IS_CONTACT (contact));
 
@@ -117,6 +143,7 @@ empathy_subscription_dialog_show (EmpathyContact *contact,
 	gui = empathy_builder_get_file (filename,
 				      "subscription_request_dialog", &dialog,
 				      "hbox_subscription", &hbox_subscription,
+				      "block-user-button", &block_user_button,
 				      NULL);
 	g_free (filename);
 	g_object_unref (gui);
@@ -161,10 +188,17 @@ empathy_subscription_dialog_show (EmpathyContact *contact,
 			  G_CALLBACK (subscription_dialog_response_cb),
 			  contact_widget);
 
+	flags = empathy_contact_manager_get_flags_for_connection (manager,
+				empathy_contact_get_connection (contact));
+
+	if (flags & EMPATHY_CONTACT_LIST_CAN_BLOCK)
+		gtk_widget_show (block_user_button);
+
 	if (parent) {
 		gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
 	}
 
+	g_object_unref (manager);
 	gtk_widget_show (dialog);
 }
 
@@ -468,3 +502,72 @@ empathy_new_contact_dialog_show_with_contact (GtkWindow *parent,
 	gtk_widget_show (dialog);
 }
 
+/**
+ * empathy_block_contact_dialog_show:
+ * @parent: the parent of this dialog (or %NULL)
+ * @contact: the contact for this dialog
+ * @abusive: a pointer to store the value of the abusive contact check box
+ *  (or %NULL)
+ *
+ * Returns: %TRUE if the user wishes to block the contact
+ */
+gboolean
+empathy_block_contact_dialog_show (GtkWindow      *parent,
+				   EmpathyContact *contact,
+				   gboolean       *abusive)
+{
+	EmpathyContactManager *manager;
+	EmpathyContactListFlags flags;
+	GtkWidget *dialog;
+	GtkWidget *abusive_check = NULL;
+	int res;
+
+	manager = empathy_contact_manager_dup_singleton ();
+	flags = empathy_contact_manager_get_flags_for_connection (manager,
+			empathy_contact_get_connection (contact));
+
+	dialog = gtk_message_dialog_new (parent,
+			GTK_DIALOG_MODAL,
+			GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
+			_("Block %s?"),
+			empathy_contact_get_alias (contact));
+
+	gtk_message_dialog_format_secondary_text (
+			GTK_MESSAGE_DIALOG (dialog),
+			_("Are you sure you want to block '%s' from "
+			  "contacting you again?"),
+			empathy_contact_get_alias (contact));
+	gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+			_("_Block"), GTK_RESPONSE_REJECT,
+			NULL);
+
+	/* ask the user if they want to also report the contact as abusive */
+	if (flags & EMPATHY_CONTACT_LIST_CAN_REPORT_ABUSIVE) {
+		GtkWidget *vbox;
+
+		vbox = gtk_message_dialog_get_message_area (
+				GTK_MESSAGE_DIALOG (dialog));
+		abusive_check = gtk_check_button_new_with_mnemonic (
+				_("_Report this contact as abusive"));
+
+		gtk_box_pack_start (GTK_BOX (vbox), abusive_check,
+				    FALSE, TRUE, 0);
+		gtk_widget_show (abusive_check);
+	}
+
+	res = gtk_dialog_run (GTK_DIALOG (dialog));
+	if (abusive != NULL) {
+		if (abusive_check != NULL) {
+			*abusive = gtk_toggle_button_get_active (
+					GTK_TOGGLE_BUTTON (abusive_check));
+		} else {
+			*abusive = FALSE;
+		}
+	}
+
+	gtk_widget_destroy (dialog);
+	g_object_unref (manager);
+
+	return res == GTK_RESPONSE_REJECT;
+}
diff --git a/libempathy-gtk/empathy-contact-dialogs.h b/libempathy-gtk/empathy-contact-dialogs.h
index 8c3ffc6..7dc1db1 100644
--- a/libempathy-gtk/empathy-contact-dialogs.h
+++ b/libempathy-gtk/empathy-contact-dialogs.h
@@ -39,6 +39,9 @@ void empathy_contact_personal_dialog_show    (GtkWindow      *parent);
 void empathy_new_contact_dialog_show         (GtkWindow     *parent);
 void empathy_new_contact_dialog_show_with_contact (GtkWindow     *parent,
                                                    EmpathyContact *contact);
+gboolean empathy_block_contact_dialog_show (GtkWindow      *parent,
+					    EmpathyContact *contact,
+					    gboolean       *abusive);
 
 G_END_DECLS
 
diff --git a/libempathy-gtk/empathy-contact-dialogs.ui b/libempathy-gtk/empathy-contact-dialogs.ui
index c18bfab..ecafddb 100644
--- a/libempathy-gtk/empathy-contact-dialogs.ui
+++ b/libempathy-gtk/empathy-contact-dialogs.ui
@@ -1,13 +1,14 @@
-<?xml version="1.0"?>
-<!--*- mode: xml -*-->
+<?xml version="1.0" encoding="UTF-8"?>
 <interface>
+  <!-- interface-requires gtk+ 2.12 -->
+  <!-- interface-naming-policy toplevel-contextual -->
   <object class="GtkDialog" id="subscription_request_dialog">
     <property name="border_width">5</property>
     <property name="title" translatable="yes">Subscription Request</property>
     <property name="role">subscription_request</property>
     <property name="resizable">False</property>
-    <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
-    <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="type_hint">dialog</property>
     <child internal-child="vbox">
       <object class="GtkVBox" id="dialog-vbox4">
         <property name="visible">True</property>
@@ -22,11 +23,12 @@
                 <property name="visible">True</property>
                 <property name="yalign">0</property>
                 <property name="stock">gtk-dialog-question</property>
-                <property name="icon_size">6</property>
+                <property name="icon-size">6</property>
               </object>
               <packing>
                 <property name="expand">False</property>
                 <property name="fill">False</property>
+                <property name="position">0</property>
               </packing>
             </child>
             <child>
@@ -40,50 +42,77 @@
         <child internal-child="action_area">
           <object class="GtkHButtonBox" id="dialog-action_area4">
             <property name="visible">True</property>
-            <property name="layout_style">GTK_BUTTONBOX_END</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="block-user-button">
+                <property name="label" translatable="yes">_Block User</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
             <child>
               <object class="GtkButton" id="button19">
+                <property name="label" translatable="yes">Decide _Later</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="can_default">True</property>
-                <property name="label" translatable="yes">Decide _Later</property>
+                <property name="receives_default">False</property>
                 <property name="use_underline">True</property>
               </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
             </child>
             <child>
               <object class="GtkButton" id="button20">
+                <property name="label">gtk-no</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="can_default">True</property>
-                <property name="label">gtk-no</property>
+                <property name="receives_default">False</property>
                 <property name="use_stock">True</property>
               </object>
               <packing>
-                <property name="position">1</property>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
               </packing>
             </child>
             <child>
               <object class="GtkButton" id="button21">
+                <property name="label">gtk-yes</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="can_default">True</property>
                 <property name="has_default">True</property>
-                <property name="label">gtk-yes</property>
+                <property name="receives_default">False</property>
                 <property name="use_stock">True</property>
               </object>
               <packing>
-                <property name="position">2</property>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">3</property>
               </packing>
             </child>
           </object>
           <packing>
             <property name="expand">False</property>
-            <property name="pack_type">GTK_PACK_END</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
           </packing>
         </child>
       </object>
     </child>
     <action-widgets>
+      <action-widget response="-2">block-user-button</action-widget>
       <action-widget response="-6">button19</action-widget>
       <action-widget response="-9">button20</action-widget>
       <action-widget response="-8">button21</action-widget>
diff --git a/libempathy-gtk/empathy-contact-menu.c b/libempathy-gtk/empathy-contact-menu.c
index 3bf4157..9f36cf5 100644
--- a/libempathy-gtk/empathy-contact-menu.c
+++ b/libempathy-gtk/empathy-contact-menu.c
@@ -40,6 +40,8 @@
 #include "empathy-ui-utils.h"
 #include "empathy-share-my-desktop.h"
 
+static GtkWidget *empathy_contact_block_menu_item_new (EmpathyContact *);
+
 GtkWidget *
 empathy_contact_menu_new (EmpathyContact             *contact,
 			  EmpathyContactFeatureFlags  features)
@@ -139,6 +141,19 @@ empathy_contact_menu_new (EmpathyContact             *contact,
 		gtk_widget_show (item);
 	}
 
+	/* Separator & Block */
+	if (features & EMPATHY_CONTACT_FEATURE_BLOCK &&
+	    (item = empathy_contact_block_menu_item_new (contact)) != NULL) {
+		GtkWidget *sep;
+
+		sep = gtk_separator_menu_item_new ();
+		gtk_menu_shell_append (shell, sep);
+		gtk_widget_show (sep);
+
+		gtk_menu_shell_append (shell, item);
+		gtk_widget_show (item);
+	}
+
 	return menu;
 }
 
@@ -214,6 +229,85 @@ empathy_contact_add_menu_item_new (EmpathyContact *contact)
 }
 
 static void
+empathy_contact_block_menu_item_toggled (GtkCheckMenuItem *item,
+					 EmpathyContact   *contact)
+{
+	static guint block_signal = 0;
+	EmpathyContactManager *manager;
+	gboolean blocked, abusive;
+
+	if (block_signal > 0)
+		return;
+
+	blocked = gtk_check_menu_item_get_active (item);
+
+	if (blocked) {
+		/* confirm the user really wishes to block the contact */
+		GtkWidget *parent;
+
+		/* gtk_menu_get_attach_widget() doesn't behave properly here
+		 * for some reason */
+		parent = g_object_get_data (
+			G_OBJECT (gtk_widget_get_parent (GTK_WIDGET (item))),
+			"window");
+
+		if (!empathy_block_contact_dialog_show (GTK_WINDOW (parent),
+					contact, &abusive))
+			return;
+	}
+
+	manager = empathy_contact_manager_dup_singleton ();
+	empathy_contact_list_set_blocked (EMPATHY_CONTACT_LIST (manager),
+					  contact, blocked, abusive);
+	g_object_unref (manager);
+
+	/* update the toggle with the blocked status */
+	block_signal++;
+	gtk_check_menu_item_set_active (item, blocked);
+	block_signal--;
+}
+
+static GtkWidget *
+empathy_contact_block_menu_item_new (EmpathyContact *contact)
+{
+	GtkWidget *item;
+	EmpathyContactManager *manager;
+	TpConnection *connection;
+	EmpathyContactListFlags flags;
+	gboolean blocked;
+
+	g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+	manager = empathy_contact_manager_dup_singleton ();
+
+	if (!empathy_contact_manager_initialized ()) {
+		return NULL;
+	}
+
+	connection = empathy_contact_get_connection (contact);
+
+	flags = empathy_contact_manager_get_flags_for_connection (manager,
+			connection);
+
+	if (!(flags & EMPATHY_CONTACT_LIST_CAN_BLOCK)) {
+		return NULL;
+	}
+
+	item = gtk_check_menu_item_new_with_mnemonic (_("_Block Contact"));
+	blocked = empathy_contact_list_get_blocked (
+			EMPATHY_CONTACT_LIST (manager),
+			contact);
+
+	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), blocked);
+
+	g_signal_connect (item, "toggled",
+			G_CALLBACK (empathy_contact_block_menu_item_toggled),
+			contact);
+
+	return item;
+}
+
+static void
 empathy_contact_chat_menu_item_activated (GtkMenuItem *item,
 	EmpathyContact *contact)
 {
diff --git a/libempathy-gtk/empathy-contact-menu.h b/libempathy-gtk/empathy-contact-menu.h
index 2e02474..35d6479 100644
--- a/libempathy-gtk/empathy-contact-menu.h
+++ b/libempathy-gtk/empathy-contact-menu.h
@@ -37,7 +37,8 @@ typedef enum {
 	EMPATHY_CONTACT_FEATURE_INFO = 1 << 4,
 	EMPATHY_CONTACT_FEATURE_FAVOURITE = 1 << 5,
 	EMPATHY_CONTACT_FEATURE_FT = 1 << 6,
-	EMPATHY_CONTACT_FEATURE_ALL = (1 << 7) - 1,
+	EMPATHY_CONTACT_FEATURE_BLOCK = 1 << 7,
+	EMPATHY_CONTACT_FEATURE_ALL = (1 << 8) - 1,
 } EmpathyContactFeatureFlags;
 
 GtkWidget * empathy_contact_menu_new           (EmpathyContact             *contact,
diff --git a/libempathy-gtk/empathy-individual-dialogs.c b/libempathy-gtk/empathy-individual-dialogs.c
index 2c98010..67ec221 100644
--- a/libempathy-gtk/empathy-individual-dialogs.c
+++ b/libempathy-gtk/empathy-individual-dialogs.c
@@ -29,14 +29,18 @@
 
 #include <telepathy-glib/util.h>
 #include <folks/folks.h>
+#include <folks/folks-telepathy.h>
 
 #include <libempathy/empathy-individual-manager.h>
 #include <libempathy/empathy-utils.h>
+#include <libempathy/empathy-contact-manager.h>
 
 #include "empathy-individual-dialogs.h"
 #include "empathy-contact-widget.h"
 #include "empathy-ui-utils.h"
 
+#define BULLET_POINT "\342\200\242"
+
 static GtkWidget *new_individual_dialog = NULL;
 
 /*
@@ -156,3 +160,138 @@ empathy_new_individual_dialog_show_with_individual (GtkWindow *parent,
 
   tp_clear_object (&contact);
 }
+
+static char *
+contact_pretty_name (TpContact *contact)
+{
+  const char *alias = tp_contact_get_alias (contact);
+  const char *identifier = tp_contact_get_identifier (contact);
+
+  if (tp_strdiff (alias, identifier))
+    return g_strdup_printf ("%s (%s)", alias, identifier);
+  else
+    return g_strdup (alias);
+}
+
+/*
+ * Block contact dialog
+ */
+gboolean
+empathy_block_individual_dialog_show (GtkWindow *parent,
+    FolksIndividual *individual,
+    gboolean *abusive)
+{
+  EmpathyContactManager *contact_manager =
+    empathy_contact_manager_dup_singleton ();
+  GtkWidget *dialog;
+  GtkWidget *abusive_check = NULL;
+  GList *personas, *l;
+  GString *text = g_string_new ("");
+  GString *blocked_str = g_string_new ("");
+  GString *notblocked_str = g_string_new ("");
+  guint npersonas_blocked = 0, npersonas_notblocked = 0;
+  gboolean can_report_abuse = FALSE;
+  int res;
+
+  dialog = gtk_message_dialog_new (parent,
+      GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
+      _("Block %s?"),
+      folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
+
+  /* build a list of personas that support blocking */
+  personas = folks_individual_get_personas (individual);
+
+  for (l = personas; l != NULL; l = l->next)
+    {
+      TpfPersona *persona = l->data;
+      TpContact *contact;
+      EmpathyContactListFlags flags;
+      GString *s;
+      char *str;
+
+      if (!TPF_IS_PERSONA (persona))
+          continue;
+
+      contact = tpf_persona_get_contact (persona);
+      flags = empathy_contact_manager_get_flags_for_connection (
+          contact_manager, tp_contact_get_connection (contact));
+
+      if (flags & EMPATHY_CONTACT_LIST_CAN_BLOCK)
+        {
+          s = blocked_str;
+          npersonas_blocked++;
+        }
+      else
+        {
+          s = notblocked_str;
+          npersonas_notblocked++;
+        }
+
+      if (flags & EMPATHY_CONTACT_LIST_CAN_REPORT_ABUSIVE)
+        can_report_abuse = TRUE;
+
+      str = contact_pretty_name (contact);
+      g_string_append_printf (s, "\n " BULLET_POINT " %s", str);
+      g_free (str);
+    }
+
+  g_string_append_printf (text,
+      _("Are you sure you want to block '%s' from contacting you again?"),
+      folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
+
+  if (npersonas_blocked > 0)
+    g_string_append_printf (text, "\n\n%s\n%s",
+        ngettext ("The following identity will be blocked:",
+                  "The following identities will be blocked:",
+                  npersonas_blocked),
+        blocked_str->str);
+
+  if (npersonas_notblocked > 0)
+    g_string_append_printf (text, "\n\n%s\n%s",
+        ngettext ("The following identity can not be blocked:",
+                  "The following identities can not be blocked:",
+                  npersonas_notblocked),
+        notblocked_str->str);
+
+  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+    "%s", text->str);
+
+  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+      _("_Block"), GTK_RESPONSE_REJECT,
+      NULL);
+
+  if (can_report_abuse)
+    {
+      GtkWidget *vbox;
+
+      vbox = gtk_message_dialog_get_message_area (GTK_MESSAGE_DIALOG (dialog));
+      abusive_check = gtk_check_button_new_with_mnemonic (
+          ngettext ("_Report this contact as abusive",
+                    "_Report these contacts as abusive",
+                    npersonas_blocked));
+
+      gtk_box_pack_start (GTK_BOX (vbox), abusive_check, FALSE, TRUE, 0);
+      gtk_widget_show (abusive_check);
+    }
+
+  g_object_unref (contact_manager);
+  g_string_free (text, TRUE);
+  g_string_free (blocked_str, TRUE);
+  g_string_free (notblocked_str, TRUE);
+
+  res = gtk_dialog_run (GTK_DIALOG (dialog));
+
+  if (abusive != NULL)
+    {
+      if (abusive_check != NULL)
+        *abusive = gtk_toggle_button_get_active (
+            GTK_TOGGLE_BUTTON (abusive_check));
+      else
+        *abusive = FALSE;
+    }
+
+  gtk_widget_destroy (dialog);
+
+  return res == GTK_RESPONSE_REJECT;
+}
diff --git a/libempathy-gtk/empathy-individual-dialogs.h b/libempathy-gtk/empathy-individual-dialogs.h
index b34a7ab..1444d5a 100644
--- a/libempathy-gtk/empathy-individual-dialogs.h
+++ b/libempathy-gtk/empathy-individual-dialogs.h
@@ -32,6 +32,9 @@ G_BEGIN_DECLS
 void empathy_new_individual_dialog_show (GtkWindow *parent);
 void empathy_new_individual_dialog_show_with_individual (GtkWindow *parent,
     FolksIndividual *individual);
+gboolean empathy_block_individual_dialog_show (GtkWindow *parent,
+    FolksIndividual *individual,
+    gboolean *abusive);
 
 G_END_DECLS
 
diff --git a/libempathy-gtk/empathy-individual-view.c b/libempathy-gtk/empathy-individual-view.c
index 9be0221..172b1fa 100644
--- a/libempathy-gtk/empathy-individual-view.c
+++ b/libempathy-gtk/empathy-individual-view.c
@@ -47,6 +47,7 @@
 #include "empathy-individual-menu.h"
 #include "empathy-individual-store.h"
 #include "empathy-contact-dialogs.h"
+#include "empathy-individual-dialogs.h"
 #include "empathy-images.h"
 #include "empathy-linking-dialog.h"
 #include "empathy-cell-renderer-expander.h"
@@ -2269,16 +2270,22 @@ empathy_individual_view_dup_selected_group (EmpathyIndividualView *view,
   return name;
 }
 
-static gboolean
+static int
 individual_view_remove_dialog_show (GtkWindow *parent,
     const gchar *message,
-    const gchar *secondary_text)
+    const gchar *secondary_text,
+    gboolean block_button)
 {
   GtkWidget *dialog;
   gboolean res;
 
   dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
+
+  if (block_button)
+    gtk_dialog_add_button (GTK_DIALOG (dialog),
+        _("Delete and Block"), GTK_RESPONSE_REJECT);
+
   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
       GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
@@ -2290,7 +2297,7 @@ individual_view_remove_dialog_show (GtkWindow *parent,
   res = gtk_dialog_run (GTK_DIALOG (dialog));
   gtk_widget_destroy (dialog);
 
-  return (res == GTK_RESPONSE_YES);
+  return res;
 }
 
 static void
@@ -2310,7 +2317,7 @@ individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
           group);
       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
       if (individual_view_remove_dialog_show (parent, _("Removing group"),
-              text))
+              text, FALSE) == GTK_RESPONSE_YES)
         {
           EmpathyIndividualManager *manager =
               empathy_individual_manager_dup_singleton ();
@@ -2389,10 +2396,13 @@ individual_view_remove_activate_cb (GtkMenuItem *menuitem,
 
   if (individual != NULL)
     {
+      EmpathyIndividualManager *manager;
       gchar *text;
       GtkWindow *parent;
       GList *l, *personas;
       guint persona_count = 0;
+      gboolean can_block;
+      int res;
 
       personas = folks_individual_get_personas (individual);
 
@@ -2428,20 +2438,37 @@ individual_view_remove_activate_cb (GtkMenuItem *menuitem,
                   folks_aliasable_get_alias (FOLKS_ALIASABLE (individual)));
         }
 
+
+      manager = empathy_individual_manager_dup_singleton ();
+      can_block = empathy_individual_manager_supports_blocking (manager,
+          individual);
       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
+      res = individual_view_remove_dialog_show (parent, _("Removing contact"),
+              text, can_block);
 
-      if (individual_view_remove_dialog_show (parent, _("Removing contact"),
-              text))
+      if (res == GTK_RESPONSE_YES || res == GTK_RESPONSE_REJECT)
         {
-          EmpathyIndividualManager *manager;
+          gboolean abusive;
+
+          if (res == GTK_RESPONSE_REJECT &&
+              empathy_block_individual_dialog_show (parent, individual,
+                &abusive))
+            {
+              empathy_individual_manager_set_blocked (manager, individual,
+                  TRUE, abusive);
+            }
+          else
+            {
+              goto finally;
+            }
 
-          manager = empathy_individual_manager_dup_singleton ();
           empathy_individual_manager_remove (manager, individual, "");
-          g_object_unref (G_OBJECT (manager));
         }
 
+finally:
       g_free (text);
       g_object_unref (individual);
+      g_object_unref (manager);
     }
 }
 
diff --git a/libempathy/empathy-contact-list.c b/libempathy/empathy-contact-list.c
index 631bb4a..d288667 100644
--- a/libempathy/empathy-contact-list.c
+++ b/libempathy/empathy-contact-list.c
@@ -278,3 +278,27 @@ empathy_contact_list_remove_from_favourites (EmpathyContactList *list,
 			contact);
 	}
 }
+
+void
+empathy_contact_list_set_blocked (EmpathyContactList *list,
+				  EmpathyContact     *contact,
+				  gboolean            blocked,
+				  gboolean            abusive)
+{
+	EmpathyContactListIface *iface = EMPATHY_CONTACT_LIST_GET_IFACE (list);
+
+	if (iface->set_blocked != NULL)
+		iface->set_blocked (list, contact, blocked, abusive);
+}
+
+gboolean
+empathy_contact_list_get_blocked (EmpathyContactList *list,
+				  EmpathyContact     *contact)
+{
+	EmpathyContactListIface *iface = EMPATHY_CONTACT_LIST_GET_IFACE (list);
+
+	if (iface->get_blocked != NULL)
+		return iface->get_blocked (list, contact);
+	else
+		return FALSE;
+}
diff --git a/libempathy/empathy-contact-list.h b/libempathy/empathy-contact-list.h
index 3817af8..cf523bf 100644
--- a/libempathy/empathy-contact-list.h
+++ b/libempathy/empathy-contact-list.h
@@ -39,6 +39,8 @@ typedef enum {
 	EMPATHY_CONTACT_LIST_CAN_REMOVE		= 1 << 1,
 	EMPATHY_CONTACT_LIST_CAN_ALIAS		= 1 << 2,
 	EMPATHY_CONTACT_LIST_CAN_GROUP		= 1 << 3,
+	EMPATHY_CONTACT_LIST_CAN_BLOCK		= 1 << 4,
+	EMPATHY_CONTACT_LIST_CAN_REPORT_ABUSIVE = 1 << 5,
 } EmpathyContactListFlags;
 
 typedef struct _EmpathyContactListIface EmpathyContactListIface;
@@ -77,6 +79,12 @@ struct _EmpathyContactListIface {
 					       EmpathyContact     *contact);
 	void             (*remove_favourite)  (EmpathyContactList *list,
 					       EmpathyContact     *contact);
+	void             (*set_blocked)       (EmpathyContactList *list,
+			                       EmpathyContact     *contact,
+					       gboolean            blocked,
+					       gboolean            abusive);
+	gboolean         (*get_blocked)       (EmpathyContactList *list,
+			                       EmpathyContact     *contact);
 };
 
 GType    empathy_contact_list_get_type          (void) G_GNUC_CONST;
@@ -116,6 +124,13 @@ void     empathy_contact_list_remove_from_favourites
                                                 (EmpathyContactList *list,
 						 EmpathyContact     *contact);
 
+void     empathy_contact_list_set_blocked       (EmpathyContactList *list,
+		                                 EmpathyContact     *contact,
+						 gboolean            blocked,
+						 gboolean            abusive);
+gboolean empathy_contact_list_get_blocked       (EmpathyContactList *list,
+						 EmpathyContact     *contact);
+
 
 G_END_DECLS
 
diff --git a/libempathy/empathy-contact-manager.c b/libempathy/empathy-contact-manager.c
index a900fa6..b00f824 100644
--- a/libempathy/empathy-contact-manager.c
+++ b/libempathy/empathy-contact-manager.c
@@ -865,6 +865,47 @@ contact_manager_remove_group (EmpathyContactList *manager,
 }
 
 static void
+contact_manager_set_blocked (EmpathyContactList *manager,
+			     EmpathyContact     *contact,
+			     gboolean            blocked,
+			     gboolean            abusive)
+{
+	EmpathyContactManagerPriv *priv = GET_PRIV (manager);
+	EmpathyContactList        *list;
+	TpConnection              *connection;
+
+	g_return_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager));
+
+	connection = empathy_contact_get_connection (contact);
+	list = g_hash_table_lookup (priv->lists, connection);
+
+	if (list != NULL) {
+		empathy_contact_list_set_blocked (list, contact,
+						  blocked, abusive);
+	}
+}
+
+static gboolean
+contact_manager_get_blocked (EmpathyContactList *manager,
+			     EmpathyContact     *contact)
+{
+	EmpathyContactManagerPriv *priv = GET_PRIV (manager);
+	EmpathyContactList        *list;
+	TpConnection              *connection;
+
+	g_return_val_if_fail (EMPATHY_IS_CONTACT_MANAGER (manager), FALSE);
+
+	connection = empathy_contact_get_connection (contact);
+	list = g_hash_table_lookup (priv->lists, connection);
+
+	if (list != NULL) {
+		return empathy_contact_list_get_blocked (list, contact);
+	} else {
+		return FALSE;
+	}
+}
+
+static void
 contact_manager_iface_init (EmpathyContactListIface *iface)
 {
 	iface->add               = contact_manager_add;
@@ -880,6 +921,8 @@ contact_manager_iface_init (EmpathyContactListIface *iface)
 	iface->is_favourite      = contact_manager_is_favourite;
 	iface->remove_favourite  = contact_manager_remove_favourite;
 	iface->add_favourite     = contact_manager_add_favourite;
+	iface->set_blocked	 = contact_manager_set_blocked;
+	iface->get_blocked	 = contact_manager_get_blocked;
 }
 
 EmpathyContactListFlags
diff --git a/libempathy/empathy-individual-manager.c b/libempathy/empathy-individual-manager.c
index 894ae64..137d00f 100644
--- a/libempathy/empathy-individual-manager.c
+++ b/libempathy/empathy-individual-manager.c
@@ -30,12 +30,14 @@
 #include <telepathy-glib/util.h>
 
 #include <folks/folks.h>
+#include <folks/folks-telepathy.h>
 
 #include <extensions/extensions.h>
 
 #include "empathy-individual-manager.h"
 #include "empathy-marshal.h"
 #include "empathy-utils.h"
+#include "empathy-contact-manager.h"
 
 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
 #include "empathy-debug.h"
@@ -483,6 +485,89 @@ empathy_individual_manager_remove (EmpathyIndividualManager *self,
       aggregator_remove_individual_cb, self);
 }
 
+/* FIXME: The parameter @self is not required and the method can be placed in
+ * utilities. I left it as it is to stay coherent with empathy-2.34 */
+/**
+ * empathy_individual_manager_supports_blocking
+ * @self: the #EmpathyIndividualManager
+ * @individual: an individual to check
+ *
+ * Indicates whether any personas of an @individual can be blocked.
+ *
+ * Returns: %TRUE if any persona supports contact blocking
+ */
+gboolean
+empathy_individual_manager_supports_blocking (EmpathyIndividualManager *self,
+    FolksIndividual *individual)
+{
+  EmpathyIndividualManagerPriv *priv;
+  GList *personas, *l;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), FALSE);
+
+  priv = GET_PRIV (self);
+
+  personas = folks_individual_get_personas (individual);
+
+  for (l = personas; l != NULL; l = l->next)
+    {
+      TpfPersona *persona = l->data;
+      TpConnection *conn;
+      EmpathyContactManager *manager;
+
+      if (!TPF_IS_PERSONA (persona))
+        continue;
+
+      conn = tp_contact_get_connection (tpf_persona_get_contact (persona));
+      manager = empathy_contact_manager_dup_singleton ();
+
+      if (empathy_contact_manager_get_flags_for_connection (manager, conn) &
+          EMPATHY_CONTACT_LIST_CAN_BLOCK)
+        return TRUE;
+
+      g_object_unref (manager);
+    }
+
+  return FALSE;
+}
+
+void
+empathy_individual_manager_set_blocked (EmpathyIndividualManager *self,
+    FolksIndividual *individual,
+    gboolean blocked,
+    gboolean abusive)
+{
+  EmpathyIndividualManagerPriv *priv;
+  GList *personas, *l;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
+
+  priv = GET_PRIV (self);
+
+  personas = folks_individual_get_personas (individual);
+
+  for (l = personas; l != NULL; l = l->next)
+    {
+      TpfPersona *persona = l->data;
+      EmpathyContact *contact;
+      EmpathyContactManager *manager;
+
+      if (!TPF_IS_PERSONA (persona))
+        continue;
+
+      contact = empathy_contact_dup_from_tp_contact (
+          tpf_persona_get_contact (persona));
+      empathy_contact_set_persona (contact, FOLKS_PERSONA (persona));
+      manager = empathy_contact_manager_dup_singleton ();
+      empathy_contact_list_set_blocked (
+          EMPATHY_CONTACT_LIST (manager),
+          contact, blocked, abusive);
+
+      g_object_unref (manager);
+      g_object_unref (contact);
+    }
+}
+
 static void
 groups_change_group_cb (GObject *source,
     GAsyncResult *result,
diff --git a/libempathy/empathy-individual-manager.h b/libempathy/empathy-individual-manager.h
index f2f5f5b..1fec67d 100644
--- a/libempathy/empathy-individual-manager.h
+++ b/libempathy/empathy-individual-manager.h
@@ -81,5 +81,14 @@ void empathy_individual_manager_unlink_individual (
     EmpathyIndividualManager *self,
     FolksIndividual *individual);
 
+gboolean empathy_individual_manager_supports_blocking (
+    EmpathyIndividualManager *self,
+    FolksIndividual *individual);
+
+void empathy_individual_manager_set_blocked (EmpathyIndividualManager *self,
+    FolksIndividual *individual,
+    gboolean blocked,
+    gboolean abusive);
+
 G_END_DECLS
 #endif /* __EMPATHY_INDIVIDUAL_MANAGER_H__ */
diff --git a/libempathy/empathy-tp-contact-list.c b/libempathy/empathy-tp-contact-list.c
index 263c379..90932a2 100644
--- a/libempathy/empathy-tp-contact-list.c
+++ b/libempathy/empathy-tp-contact-list.c
@@ -31,6 +31,8 @@
 #include <telepathy-glib/dbus.h>
 #include <telepathy-glib/interfaces.h>
 
+#include <extensions/extensions.h>
+
 #include "empathy-tp-contact-list.h"
 #include "empathy-tp-contact-factory.h"
 #include "empathy-contact-list.h"
@@ -46,6 +48,7 @@ typedef struct {
 	TpChannel      *publish;
 	TpChannel      *subscribe;
 	TpChannel      *stored;
+	TpChannel      *deny;
 	/* contact handle (TpHandle) => reffed (EmpathyContact *)
 	 *
 	 * Union of:
@@ -722,6 +725,10 @@ tp_contact_list_finalize (GObject *object)
 		g_object_unref (priv->stored);
 	}
 
+	if (priv->deny) {
+		g_object_unref (priv->deny);
+	}
+
 	if (priv->connection) {
 		g_object_unref (priv->connection);
 	}
@@ -773,6 +780,11 @@ got_list_channel (EmpathyTpContactList *list,
 		g_signal_connect (priv->subscribe, "group-members-changed",
 				  G_CALLBACK (tp_contact_list_subscribe_group_members_changed_cb),
 				  list);
+	} else if (!tp_strdiff (id, "deny")) {
+		if (priv->deny != NULL)
+			return;
+		DEBUG ("Got 'deny' channel");
+		priv->deny = g_object_ref (channel);
 	}
 }
 
@@ -799,6 +811,27 @@ list_ensure_channel_cb (TpConnection *conn,
 }
 
 static void
+list_get_contact_blocking_capabilities_cb (TpProxy *conn,
+					   const GValue *value,
+					   const GError *in_error,
+					   gpointer user_data,
+					   GObject *weak_object)
+{
+	EmpathyTpContactList *list = EMPATHY_TP_CONTACT_LIST (weak_object);
+	EmpathyTpContactListPriv *priv = GET_PRIV (list);
+	EmpContactBlockingCapabilities caps;
+
+	g_return_if_fail (G_VALUE_HOLDS_UINT (value));
+
+	caps = g_value_get_uint (value);
+
+	if (caps & EMP_CONTACT_BLOCKING_CAPABILITY_CAN_REPORT_ABUSIVE) {
+		DEBUG ("Connection can report abusive contacts");
+		priv->flags |= EMPATHY_CONTACT_LIST_CAN_REPORT_ABUSIVE;
+	}
+}
+
+static void
 iterate_on_channels (EmpathyTpContactList *list,
 		     const GPtrArray *channels)
 {
@@ -881,8 +914,8 @@ conn_ready_cb (TpConnection *connection,
 		NULL, NULL, G_OBJECT (list));
 
 	request = tp_asv_new (
-		TP_IFACE_CHANNEL ".ChannelType", G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
-		TP_IFACE_CHANNEL ".TargetHandleType", G_TYPE_UINT, TP_HANDLE_TYPE_LIST,
+		TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
+		TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_LIST,
 		NULL);
 
 	/* Watch the NewChannels signal so if ensuring list channels fails (for
@@ -892,21 +925,39 @@ conn_ready_cb (TpConnection *connection,
 		priv->connection, new_channels_cb, NULL, NULL, G_OBJECT (list), NULL);
 
 	/* Request the 'stored' list. */
-	tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "stored");
+	tp_asv_set_static_string (request, TP_PROP_CHANNEL_TARGET_ID, "stored");
 	tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
 		G_MAXINT, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
 
 	/* Request the 'publish' list. */
-	tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "publish");
+	tp_asv_set_static_string (request, TP_PROP_CHANNEL_TARGET_ID, "publish");
 	tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
 		G_MAXINT, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
 
 	/* Request the 'subscribe' list. */
-	tp_asv_set_static_string (request, TP_IFACE_CHANNEL ".TargetID", "subscribe");
+	tp_asv_set_static_string (request, TP_PROP_CHANNEL_TARGET_ID, "subscribe");
+	tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
+		G_MAXINT, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
+
+	/* Request the 'deny' list */
+	tp_asv_set_static_string (request, TP_PROP_CHANNEL_TARGET_ID, "deny");
 	tp_cli_connection_interface_requests_call_ensure_channel (priv->connection,
 		G_MAXINT, request, list_ensure_channel_cb, list, NULL, G_OBJECT (list));
 
 	g_hash_table_unref (request);
+
+	/* Find out if we support reporting abusive contacts --
+	 * this is done via the new Conn.I.ContactBlocking interface */
+	if (tp_proxy_has_interface_by_id (priv->connection,
+	    EMP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING)) {
+		DEBUG ("Have Conn.I.ContactBlocking");
+
+		tp_cli_dbus_properties_call_get (priv->connection, -1,
+			EMP_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING,
+			"ContactBlockingCapabilities",
+			list_get_contact_blocking_capabilities_cb,
+			NULL, NULL, G_OBJECT (list));
+	}
 out:
 	g_object_unref (list);
 }
@@ -1289,10 +1340,56 @@ tp_contact_list_get_flags (EmpathyContactList *list)
 		}
 	}
 
+	if (priv->deny != NULL)
+		flags |= EMPATHY_CONTACT_LIST_CAN_BLOCK;
+
 	return flags;
 }
 
 static void
+tp_contact_list_set_blocked (EmpathyContactList *list,
+			     EmpathyContact     *contact,
+			     gboolean            blocked,
+			     gboolean            abusive)
+{
+	EmpathyTpContactListPriv *priv = GET_PRIV (list);
+	TpHandle handle = empathy_contact_get_handle (contact);
+	GArray handles = { (char *) &handle, 1 };
+
+	g_return_if_fail (TP_IS_CHANNEL (priv->deny));
+
+	if (blocked && abusive) {
+		/* we have to do this via the new interface */
+		g_return_if_fail (priv->flags &
+				EMPATHY_CONTACT_LIST_CAN_REPORT_ABUSIVE);
+
+		emp_cli_connection_interface_contact_blocking_call_block_contacts (
+			TP_PROXY (priv->connection), -1,
+			&handles, TRUE, NULL, NULL, NULL, NULL);
+	} else if (blocked) {
+		tp_cli_channel_interface_group_call_add_members (
+			priv->deny, -1,
+			&handles, NULL, NULL, NULL, NULL, NULL);
+	} else {
+		tp_cli_channel_interface_group_call_remove_members (
+			priv->deny, -1,
+			&handles, NULL, NULL, NULL, NULL, NULL);
+	}
+}
+
+static gboolean
+tp_contact_list_get_blocked (EmpathyContactList *list,
+			     EmpathyContact     *contact)
+{
+	EmpathyTpContactListPriv *priv = GET_PRIV (list);
+
+	g_return_val_if_fail (TP_IS_CHANNEL (priv->deny), FALSE);
+
+	return tp_intset_is_member (tp_channel_group_get_members (priv->deny),
+				    empathy_contact_get_handle (contact));
+}
+
+static void
 tp_contact_list_iface_init (EmpathyContactListIface *iface)
 {
 	iface->add               = tp_contact_list_add;
@@ -1306,6 +1403,8 @@ tp_contact_list_iface_init (EmpathyContactListIface *iface)
 	iface->rename_group      = tp_contact_list_rename_group;
 	iface->remove_group	 = tp_contact_list_remove_group;
 	iface->get_flags	 = tp_contact_list_get_flags;
+	iface->set_blocked	 = tp_contact_list_set_blocked;
+	iface->get_blocked	 = tp_contact_list_get_blocked;
 }
 
 void
@@ -1334,4 +1433,3 @@ empathy_tp_contact_list_remove_all (EmpathyTpContactList *list)
 	}
 	g_hash_table_remove_all (priv->pendings);
 }
-
diff --git a/libempathy/empathy-tp-contact-list.h b/libempathy/empathy-tp-contact-list.h
index 071fc0b..9a555bc 100644
--- a/libempathy/empathy-tp-contact-list.h
+++ b/libempathy/empathy-tp-contact-list.h
@@ -26,6 +26,7 @@
 #include <glib.h>
 #include <telepathy-glib/connection.h>
 
+#include <libempathy/empathy-contact.h>
 
 G_BEGIN_DECLS
 
diff --git a/src/empathy-chat-window.c b/src/empathy-chat-window.c
index 799a825..23c93a1 100644
--- a/src/empathy-chat-window.c
+++ b/src/empathy-chat-window.c
@@ -417,12 +417,15 @@ chat_window_contact_menu_update (EmpathyChatWindowPriv *priv,
 
 	if (orig_submenu == NULL || !gtk_widget_get_visible (orig_submenu)) {
 		submenu = empathy_chat_get_contact_menu (priv->current_chat);
+
 		if (submenu != NULL) {
+			/* gtk_menu_attach_to_widget() doesn't behave nicely here */
+			g_object_set_data (G_OBJECT (submenu), "window", priv->dialog);
+
 			gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu), submenu);
 			gtk_widget_show (menu);
 			gtk_widget_set_sensitive (menu, TRUE);
-		}
-		else {
+		} else {
 			gtk_widget_set_sensitive (menu, FALSE);
 		}
 	} else {
diff --git a/src/empathy-main-window.c b/src/empathy-main-window.c
index 8e3f2d8..8c76425 100644
--- a/src/empathy-main-window.c
+++ b/src/empathy-main-window.c
@@ -50,6 +50,7 @@
 #include <libempathy-gtk/empathy-contact-list-store.h>
 #include <libempathy-gtk/empathy-contact-list-view.h>
 #include <libempathy-gtk/empathy-live-search.h>
+#include <libempathy-gtk/empathy-contact-blocking-dialog.h>
 #include <libempathy-gtk/empathy-contact-search-dialog.h>
 #include <libempathy-gtk/empathy-geometry.h>
 #include <libempathy-gtk/empathy-gtk-enum-types.h>
@@ -1461,6 +1462,18 @@ main_window_edit_personal_information_cb (GtkAction         *action,
 }
 
 static void
+main_window_edit_blocked_contacts_cb (GtkAction         *action,
+		                      EmpathyMainWindow *window)
+{
+	GtkWidget *dialog;
+
+	dialog = empathy_contact_blocking_dialog_new (GTK_WINDOW (window));
+	gtk_widget_show (dialog);
+	g_signal_connect (dialog, "response",
+			G_CALLBACK (gtk_widget_destroy), NULL);
+}
+
+static void
 main_window_edit_preferences_cb (GtkAction         *action,
 				 EmpathyMainWindow *window)
 {
@@ -1619,7 +1632,8 @@ main_window_connection_items_setup (EmpathyMainWindow *window,
 		"chat_new_message",
 		"chat_new_call",
 		"chat_add_contact",
-		"edit_personal_information"
+		"edit_personal_information",
+		"edit_blocked_contacts"
 	};
 
 	for (i = 0, list = NULL; i < G_N_ELEMENTS (actions_connected); i++) {
@@ -1798,6 +1812,7 @@ empathy_main_window_init (EmpathyMainWindow *window)
 			      "edit", "activate", main_window_edit_cb,
 			      "edit_accounts", "activate", main_window_edit_accounts_cb,
 			      "edit_personal_information", "activate", main_window_edit_personal_information_cb,
+			      "edit_blocked_contacts", "activate", main_window_edit_blocked_contacts_cb,
 			      "edit_preferences", "activate", main_window_edit_preferences_cb,
 			      "edit_search_contacts", "activate", main_window_edit_search_contacts_cb,
 			      "help_about", "activate", main_window_help_about_cb,
diff --git a/src/empathy-main-window.ui b/src/empathy-main-window.ui
index 3c01539..b32878b 100644
--- a/src/empathy-main-window.ui
+++ b/src/empathy-main-window.ui
@@ -107,6 +107,12 @@
           </object>
         </child>
         <child>
+          <object class="GtkAction" id="edit_blocked_contacts">
+            <property name="name">edit_blocked_contacts</property>
+            <property name="label" translatable="yes">_Blocked Contacts</property>
+          </object>
+        </child>
+        <child>
           <object class="GtkAction" id="edit_preferences">
             <property name="stock_id">gtk-preferences</property>
             <property name="name">edit_preferences</property>
@@ -249,6 +255,7 @@
           <menuitem action="edit_accounts"/>
           <menuitem action="edit_personal_information"/>
           <menuitem action="edit_search_contacts"/>
+          <menuitem action="edit_blocked_contacts"/>
           <separator/>
           <menuitem action="edit_preferences"/>
         </menu>
diff --git a/tests/interactive/.gitignore b/tests/interactive/.gitignore
index ebaa4d1..ea32716 100644
--- a/tests/interactive/.gitignore
+++ b/tests/interactive/.gitignore
@@ -4,6 +4,7 @@ contact-run-until-ready
 contact-run-until-ready-2
 empetit
 test-empathy-account-assistant
+test-empathy-contact-blocking-dialog
 test-empathy-presence-chooser
 test-empathy-status-preset-dialog
 test-empathy-protocol-chooser
diff --git a/tests/interactive/Makefile.am b/tests/interactive/Makefile.am
index 24e674c..bf2e839 100644
--- a/tests/interactive/Makefile.am
+++ b/tests/interactive/Makefile.am
@@ -18,6 +18,7 @@ noinst_PROGRAMS =			\
 	empathy-logs			\
 	empetit				\
 	test-empathy-account-assistant \
+	test-empathy-contact-blocking-dialog \
 	test-empathy-presence-chooser	\
 	test-empathy-status-preset-dialog \
 	test-empathy-protocol-chooser \
@@ -26,6 +27,7 @@ noinst_PROGRAMS =			\
 contact_manager_SOURCES = contact-manager.c
 empathy_logs_SOURCES = empathy-logs.c
 empetit_SOURCES = empetit.c
+test_empathy_contact_blocking_dialog_SOURCES = test-empathy-contact-blocking-dialog.c
 test_empathy_presence_chooser_SOURCES = test-empathy-presence-chooser.c
 test_empathy_status_preset_dialog_SOURCES = test-empathy-status-preset-dialog.c
 test_empathy_protocol_chooser_SOURCES = test-empathy-protocol-chooser.c
diff --git a/tests/interactive/test-empathy-contact-blocking-dialog.c b/tests/interactive/test-empathy-contact-blocking-dialog.c
new file mode 100644
index 0000000..6c5615e
--- /dev/null
+++ b/tests/interactive/test-empathy-contact-blocking-dialog.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Danielle Madeley <danielle madeley collabora co uk>
+ */
+
+#include <config.h>
+
+#include <gtk/gtk.h>
+
+#include <libempathy/empathy-contact-manager.h>
+
+#include <libempathy-gtk/empathy-ui-utils.h>
+#include <libempathy-gtk/empathy-contact-blocking-dialog.h>
+
+int
+main (int argc,
+    char **argv)
+  {
+    EmpathyContactManager *manager;
+    GtkWidget *dialog;
+
+    gtk_init (&argc, &argv);
+    empathy_gtk_init ();
+
+    manager = empathy_contact_manager_dup_singleton ();
+    dialog = empathy_contact_blocking_dialog_new (NULL);
+
+    g_signal_connect_swapped (dialog, "response",
+        G_CALLBACK (gtk_main_quit), NULL);
+
+    gtk_widget_show (dialog);
+
+    gtk_main ();
+
+    gtk_widget_destroy (dialog);
+    g_object_unref (manager);
+
+    return 0;
+  }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]