/* * 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 */ #include "config.h" #include "empathy-contact-blocking-dialog.h" #include #include #include #include "empathy-account-chooser.h" #include "empathy-ui-utils.h" #include "empathy-utils.h" #define DEBUG_FLAG EMPATHY_DEBUG_OTHER #include "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 { 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; TpConnection *current_conn; }; enum /* blocked-contacts columns */ { COL_BLOCKED_IDENTIFIER, COL_BLOCKED_CONTACT, N_BLOCKED_COLUMNS }; enum /* completion_contacts columns */ { COL_COMPLETION_IDENTIFIER, COL_COMPLETION_TEXT, N_COMPLETION_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) { TpConnection *conn = tp_account_get_connection (account); gboolean enable; enable = conn != NULL && tp_proxy_has_interface_by_id (conn, TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING); 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 && tp_proxy_has_interface_by_id (conn, TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING)); 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_add_blocked ( EmpathyContactBlockingDialog *self, GPtrArray *blocked) { EmpathyContactBlockingDialogPrivate *priv = GET_PRIVATE (self); guint i; if (blocked == NULL) return; for (i = 0; i < blocked->len; i++) { TpContact *contact = g_ptr_array_index (blocked, i); gtk_list_store_insert_with_values (priv->blocked_contacts, NULL, -1, COL_BLOCKED_IDENTIFIER, tp_contact_get_identifier (contact), COL_BLOCKED_CONTACT, contact, -1); } } static void blocked_contacts_changed_cb (TpConnection *conn, GPtrArray *added, GPtrArray *removed, EmpathyContactBlockingDialog *self) { GtkTreeModel *model = GTK_TREE_MODEL (self->priv->blocked_contacts); GtkTreeIter iter; gboolean valid; DEBUG ("blocked contacts changed on %s: %u added, %u removed", get_pretty_conn_name (conn), added->len, removed->len); /* add contacts */ contact_blocking_dialog_add_blocked (self, added); /* remove contacts */ valid = gtk_tree_model_get_iter_first (model, &iter); while (valid) { TpContact *contact; gtk_tree_model_get (model, &iter, COL_BLOCKED_CONTACT, &contact, -1); if (tp_g_ptr_array_contains (removed, contact)) valid = gtk_list_store_remove (self->priv->blocked_contacts, &iter); else valid = gtk_tree_model_iter_next (model, &iter); g_object_unref (contact); } } 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)); 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)); contact_blocking_dialog_refilter_account_chooser (self); } } 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_dup_valid_accounts (TP_ACCOUNT_MANAGER (am)); for (ptr = accounts; ptr != NULL; ptr = ptr->next) { TpAccount *account = ptr->data; tp_g_signal_connect_object (account, "status-changed", G_CALLBACK (contact_blocking_dialog_connection_status_changed), self, 0); contact_blocking_dialog_refilter_account_chooser (self); } g_list_free_full (accounts, g_object_unref); } static void contact_blocking_dialog_set_error (EmpathyContactBlockingDialog *self, const GError *error) { const char *msg = NULL; if (error->domain == TP_ERROR) { 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 block_cb (GObject *source, GAsyncResult *result, gpointer user_data) { EmpathyContactBlockingDialog *self = user_data; GError *error = NULL; if (!tp_contact_block_finish (TP_CONTACT (source), result, &error)) { DEBUG ("Error blocking contacts: %s", error->message); contact_blocking_dialog_set_error ( EMPATHY_CONTACT_BLOCKING_DIALOG (self), error); g_error_free (error); return; } DEBUG ("Contact blocked"); } static void block_contact_got_contact (GObject *source, GAsyncResult *result, gpointer user_data) { EmpathyContactBlockingDialog *self; TpConnection *conn = TP_CONNECTION (source); TpWeakRef *wr = user_data; TpContact *contact; GError *error = NULL; self = tp_weak_ref_dup_object (wr); if (self == NULL) goto finally; contact = tp_connection_dup_contact_by_id_finish (conn, result, &error); if (contact == NULL) { DEBUG ("Error getting contact on %s: %s", get_pretty_conn_name (conn), error->message); contact_blocking_dialog_set_error ( EMPATHY_CONTACT_BLOCKING_DIALOG (self), error); g_error_free (error); goto finally; } tp_contact_block_async (contact, FALSE, block_cb, self); g_object_unref (contact); finally: g_clear_object (&self); tp_weak_ref_destroy (wr); } 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 *identifier; identifier = gtk_entry_get_text ( GTK_ENTRY (self->priv->add_contact_entry)); DEBUG ("Looking up handle for '%s' on %s", identifier, get_pretty_conn_name (conn)); tp_connection_dup_contact_by_id_async (conn, identifier, 0, NULL, block_contact_got_contact, tp_weak_ref_new (self, NULL, NULL)); gtk_entry_set_text (GTK_ENTRY (self->priv->add_contact_entry), ""); gtk_widget_hide (self->priv->info_bar); } static void unblock_cb (GObject *source, GAsyncResult *result, gpointer user_data) { EmpathyContactBlockingDialog *self = user_data; GError *error = NULL; if (!tp_connection_unblock_contacts_finish (TP_CONNECTION (source), result, &error)) { DEBUG ("Error unblocking contacts: %s", error->message); contact_blocking_dialog_set_error ( EMPATHY_CONTACT_BLOCKING_DIALOG (self), error); g_error_free (error); return; } DEBUG ("Contacts unblocked"); } 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)); GtkTreeModel *model; GList *rows, *ptr; GPtrArray *contacts; rows = gtk_tree_selection_get_selected_rows (self->priv->selection, &model); contacts = g_ptr_array_new_with_free_func (g_object_unref); for (ptr = rows; ptr != NULL; ptr = ptr->next) { GtkTreePath *path = ptr->data; GtkTreeIter iter; TpContact *contact; if (!gtk_tree_model_get_iter (model, &iter, path)) continue; gtk_tree_model_get (model, &iter, COL_BLOCKED_CONTACT, &contact, -1); g_ptr_array_add (contacts, contact); gtk_tree_path_free (path); } g_list_free (rows); if (contacts->len > 0) { DEBUG ("Unblocking %u contacts", contacts->len); tp_connection_unblock_contacts_async (conn, contacts->len, (TpContact * const *) contacts->pdata, unblock_cb, self); } g_ptr_array_unref (contacts); } static void contact_blocking_dialog_account_changed (GtkWidget *account_chooser, EmpathyContactBlockingDialog *self) { TpConnection *conn = empathy_account_chooser_get_connection ( EMPATHY_ACCOUNT_CHOOSER (account_chooser)); GPtrArray *blocked; GPtrArray *members; guint i; if (self->priv->block_account_changed > 0) return; if (conn == self->priv->current_conn) return; /* clear the lists of contacts */ gtk_list_store_clear (self->priv->blocked_contacts); gtk_list_store_clear (self->priv->completion_contacts); if (self->priv->current_conn != NULL) { g_signal_handlers_disconnect_by_func (self->priv->current_conn, blocked_contacts_changed_cb, self); g_clear_object (&self->priv->current_conn); } if (conn == NULL) return; DEBUG ("Account changed: %s", get_pretty_conn_name (conn)); self->priv->current_conn = g_object_ref (conn); tp_g_signal_connect_object (conn, "blocked-contacts-changed", G_CALLBACK (blocked_contacts_changed_cb), self, 0); blocked = tp_connection_get_blocked_contacts (conn); DEBUG ("%u contacts blocked on %s", blocked != NULL ? blocked->len : 0, get_pretty_conn_name (conn)); contact_blocking_dialog_add_blocked (self, blocked); DEBUG ("Loading contacts"); members = tp_connection_dup_contact_list (conn); for (i = 0; i < members->len; i++) { TpContact *contact = g_ptr_array_index (members, i); gchar *tmpstr; tmpstr = g_strdup_printf ("%s (%s)", tp_contact_get_alias (contact), tp_contact_get_identifier (contact)); gtk_list_store_insert_with_values (self->priv->completion_contacts, NULL, -1, COL_COMPLETION_IDENTIFIER, tp_contact_get_identifier (contact), COL_COMPLETION_TEXT, tmpstr, -1); g_free (tmpstr); } g_ptr_array_unref (members); } 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 gboolean contact_selector_dialog_match_func (GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data) { GtkTreeModel *model; gchar *str, *lower; gboolean v = FALSE; model = gtk_entry_completion_get_model (completion); if (model == NULL || iter == NULL) return FALSE; gtk_tree_model_get (model, iter, COL_COMPLETION_TEXT, &str, -1); lower = g_utf8_strdown (str, -1); if (strstr (lower, key)) { DEBUG ("Key %s is matching name **%s**", key, str); v = TRUE; goto out; } g_free (str); g_free (lower); gtk_tree_model_get (model, iter, COL_COMPLETION_IDENTIFIER, &str, -1); lower = g_utf8_strdown (str, -1); if (strstr (lower, key)) { DEBUG ("Key %s is matching ID **%s**", key, str); v = TRUE; goto out; } out: g_free (str); g_free (lower); return v; } static gboolean contact_selector_dialog_match_selected_cb (GtkEntryCompletion *widget, GtkTreeModel *model, GtkTreeIter *iter, EmpathyContactBlockingDialog *self) { gchar *id; if (iter == NULL || model == NULL) return FALSE; gtk_tree_model_get (model, iter, COL_COMPLETION_IDENTIFIER, &id, -1); gtk_entry_set_text (GTK_ENTRY (self->priv->add_contact_entry), id); DEBUG ("Got selected match **%s**", id); g_free (id); return TRUE; } static void contact_blocking_dialog_dispose (GObject *self) { EmpathyContactBlockingDialogPrivate *priv = GET_PRIVATE (self); g_clear_object (&priv->current_conn); 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, *blocked_contacts_sw, *remove_toolbar; GtkEntryCompletion *completion; TpAccountManager *am; GtkStyleContext *context; TpSimpleClientFactory *factory; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, EMPATHY_TYPE_CONTACT_BLOCKING_DIALOG, EmpathyContactBlockingDialogPrivate); 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 = tpaw_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-sw", &blocked_contacts_sw, "blocked-contacts-view", &blocked_contacts_view, "remove-button", &self->priv->remove_button, "remove-toolbar", &remove_toolbar, NULL); tpaw_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); /* join the remove toolbar to the treeview */ context = gtk_widget_get_style_context (blocked_contacts_sw); gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM); context = gtk_widget_get_style_context (remove_toolbar); gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP); /* 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 (N_COMPLETION_COLUMNS, G_TYPE_STRING, /* id */ G_TYPE_STRING, /* text */ TP_TYPE_CONTACT); /* contact */ 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_COMPLETION_TEXT); gtk_entry_completion_set_match_func (completion, contact_selector_dialog_match_func, NULL, NULL); g_signal_connect (completion, "match-selected", G_CALLBACK (contact_selector_dialog_match_selected_cb), self); 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 (); factory = tp_proxy_get_factory (am); tp_simple_client_factory_add_connection_features_varargs (factory, TP_CONNECTION_FEATURE_CONTACT_BLOCKING, NULL); 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; }