]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/empathy-account-chooser.c
Merge branch 'crash-659118'
[empathy.git] / libempathy-gtk / empathy-account-chooser.c
index 7624c2c13686dcfaafe4be5f36245de24dddc156..ad4f778465bdfd2e9a18cb227985941bed119ac4 100644 (file)
@@ -30,6 +30,7 @@
 #include <gtk/gtk.h>
 
 #include <telepathy-glib/account-manager.h>
+#include <telepathy-glib/util.h>
 
 #include <libempathy/empathy-utils.h>
 
@@ -64,6 +65,7 @@ typedef struct {
        gboolean                        has_all_option;
        EmpathyAccountChooserFilterFunc filter;
        gpointer                        filter_data;
+       gboolean                        ready;
 } EmpathyAccountChooserPriv;
 
 typedef struct {
@@ -72,14 +74,56 @@ typedef struct {
        gboolean               set;
 } SetAccountData;
 
+typedef struct {
+       EmpathyAccountChooser *chooser;
+       TpAccount             *account;
+       GtkTreeIter           *iter;
+} FilterResultCallbackData;
+
+static FilterResultCallbackData *
+filter_result_callback_data_new (EmpathyAccountChooser *chooser,
+                                TpAccount             *account,
+                                GtkTreeIter           *iter)
+{
+       FilterResultCallbackData *data;
+
+       data = g_slice_new0 (FilterResultCallbackData);
+       data->chooser = g_object_ref (chooser);
+       data->account = g_object_ref (account);
+       data->iter = gtk_tree_iter_copy (iter);
+
+       return data;
+}
+
+static void
+filter_result_callback_data_free (FilterResultCallbackData *data)
+{
+       g_object_unref (data->chooser);
+       g_object_unref (data->account);
+       gtk_tree_iter_free (data->iter);
+       g_slice_free (FilterResultCallbackData, data);
+}
+
+/* Distinguishes between store entries which are actually accounts, and special
+ * items like the "All" entry and the separator below it, so they can be sorted
+ * correctly. Higher-numbered entries will sort earlier.
+ */
+typedef enum {
+       ROW_ACCOUNT = 0,
+       ROW_SEPARATOR,
+       ROW_ALL
+} RowType;
+
 enum {
        COL_ACCOUNT_IMAGE,
        COL_ACCOUNT_TEXT,
        COL_ACCOUNT_ENABLED, /* Usually tied to connected state */
+       COL_ACCOUNT_ROW_TYPE,
        COL_ACCOUNT_POINTER,
        COL_ACCOUNT_COUNT
 };
 
+static void     account_chooser_constructed            (GObject                  *object);
 static void     account_chooser_finalize               (GObject                  *object);
 static void     account_chooser_get_property           (GObject                  *object,
                                                        guint                     param_id,
@@ -123,6 +167,13 @@ enum {
        PROP_HAS_ALL_OPTION,
 };
 
+enum {
+       READY,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
 G_DEFINE_TYPE (EmpathyAccountChooser, empathy_account_chooser, GTK_TYPE_COMBO_BOX);
 
 static void
@@ -130,6 +181,7 @@ empathy_account_chooser_class_init (EmpathyAccountChooserClass *klass)
 {
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+       object_class->constructed = account_chooser_constructed;
        object_class->finalize = account_chooser_finalize;
        object_class->get_property = account_chooser_get_property;
        object_class->set_property = account_chooser_set_property;
@@ -147,6 +199,16 @@ empathy_account_chooser_class_init (EmpathyAccountChooserClass *klass)
                                                               FALSE,
                                                               G_PARAM_READWRITE));
 
+       signals[READY] =
+               g_signal_new ("ready",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             0,
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__VOID,
+                             G_TYPE_NONE,
+                             0);
+
        g_type_class_add_private (object_class, sizeof (EmpathyAccountChooserPriv));
 }
 
@@ -170,8 +232,14 @@ empathy_account_chooser_init (EmpathyAccountChooser *chooser)
        g_signal_connect (priv->manager, "account-removed",
                          G_CALLBACK (account_chooser_account_removed_cb),
                          chooser);
+}
 
-       account_chooser_setup (EMPATHY_ACCOUNT_CHOOSER (chooser));
+static void
+account_chooser_constructed (GObject *object)
+{
+       EmpathyAccountChooser *self = (EmpathyAccountChooser *) object;
+
+       account_chooser_setup (self);
 }
 
 static void
@@ -216,10 +284,6 @@ account_chooser_set_property (GObject      *object,
                              const GValue *value,
                              GParamSpec   *pspec)
 {
-       EmpathyAccountChooserPriv *priv;
-
-       priv = GET_PRIV (object);
-
        switch (param_id) {
        case PROP_HAS_ALL_OPTION:
                empathy_account_chooser_set_has_all_option (EMPATHY_ACCOUNT_CHOOSER (object),
@@ -248,6 +312,30 @@ empathy_account_chooser_new (void)
        return chooser;
 }
 
+gboolean
+empathy_account_chooser_has_all_selected (EmpathyAccountChooser *chooser)
+{
+       EmpathyAccountChooserPriv *priv;
+       GtkTreeModel              *model;
+       GtkTreeIter                iter;
+       RowType                    type;
+
+       g_return_val_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser), FALSE);
+
+       priv = GET_PRIV (chooser);
+
+       g_return_val_if_fail (priv->has_all_option == TRUE, FALSE);
+
+       model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
+       if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser), &iter)) {
+               return FALSE;
+       }
+
+       gtk_tree_model_get (model, &iter, COL_ACCOUNT_ROW_TYPE, &type, -1);
+
+       return type == ROW_ALL;
+}
+
 /**
  * empathy_account_chooser_dup_account:
  * @chooser: an #EmpathyAccountChooser
@@ -261,15 +349,12 @@ empathy_account_chooser_new (void)
 TpAccount *
 empathy_account_chooser_dup_account (EmpathyAccountChooser *chooser)
 {
-       EmpathyAccountChooserPriv *priv;
        TpAccount                 *account;
        GtkTreeModel             *model;
        GtkTreeIter               iter;
 
        g_return_val_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser), NULL);
 
-       priv = GET_PRIV (chooser);
-
        model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
        if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser), &iter)) {
                return NULL;
@@ -294,14 +379,11 @@ empathy_account_chooser_dup_account (EmpathyAccountChooser *chooser)
 TpConnection *
 empathy_account_chooser_get_connection (EmpathyAccountChooser *chooser)
 {
-       EmpathyAccountChooserPriv *priv;
        TpAccount                 *account;
        TpConnection              *connection;
 
        g_return_val_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser), NULL);
 
-       priv = GET_PRIV (chooser);
-
        account = empathy_account_chooser_dup_account (chooser);
 
        /* if the returned account is NULL, then the account manager probably
@@ -346,6 +428,7 @@ empathy_account_chooser_set_account (EmpathyAccountChooser *chooser,
 
        data.chooser = chooser;
        data.account = account;
+       data.set = FALSE;
 
        gtk_tree_model_foreach (model,
                                (GtkTreeModelForeachFunc) account_chooser_set_account_foreach,
@@ -356,6 +439,30 @@ empathy_account_chooser_set_account (EmpathyAccountChooser *chooser,
        return data.set;
 }
 
+void
+empathy_account_chooser_set_all (EmpathyAccountChooser *chooser)
+{
+       EmpathyAccountChooserPriv *priv;
+       GtkComboBox    *combobox;
+       GtkTreeModel   *model;
+       GtkTreeIter     iter;
+
+       g_return_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser));
+
+       priv = GET_PRIV (chooser);
+
+       g_return_if_fail (priv->has_all_option);
+
+       combobox = GTK_COMBO_BOX (chooser);
+       model = gtk_combo_box_get_model (combobox);
+
+       if (gtk_tree_model_get_iter_first (model, &iter)) {
+               /* 'All accounts' is the first row */
+               gtk_combo_box_set_active_iter (combobox, &iter);
+               priv->account_manually_set = TRUE;
+       }
+}
+
 /**
  * empathy_account_chooser_get_has_all_option:
  * @chooser: an #EmpathyAccountChooser
@@ -425,13 +532,15 @@ empathy_account_chooser_set_has_all_option (EmpathyAccountChooser *chooser,
                                    COL_ACCOUNT_TEXT, NULL,
                                    COL_ACCOUNT_ENABLED, TRUE,
                                    COL_ACCOUNT_POINTER, NULL,
+                                   COL_ACCOUNT_ROW_TYPE, ROW_SEPARATOR,
                                    -1);
 
                gtk_list_store_prepend (store, &iter);
                gtk_list_store_set (store, &iter,
-                                   COL_ACCOUNT_TEXT, _("All"),
+                                   COL_ACCOUNT_TEXT, _("All accounts"),
                                    COL_ACCOUNT_ENABLED, TRUE,
                                    COL_ACCOUNT_POINTER, NULL,
+                                   COL_ACCOUNT_ROW_TYPE, ROW_ALL,
                                    -1);
        } else {
                if (gtk_tree_model_get_iter_first (model, &iter)) {
@@ -458,9 +567,10 @@ account_manager_prepared_cb (GObject *source_object,
        GList *accounts, *l;
        TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
        EmpathyAccountChooser *chooser = user_data;
+       EmpathyAccountChooserPriv *priv = GET_PRIV (chooser);
        GError *error = NULL;
 
-       if (!tp_account_manager_prepare_finish (manager, result, &error)) {
+       if (!tp_proxy_prepare_finish (manager, result, &error)) {
                DEBUG ("Failed to prepare account manager: %s", error->message);
                g_error_free (error);
                return;
@@ -473,12 +583,63 @@ account_manager_prepared_cb (GObject *source_object,
 
                account_chooser_account_add_foreach (account, chooser);
 
-               empathy_signal_connect_weak (account, "status-changed",
+               tp_g_signal_connect_object (account, "status-changed",
                                             G_CALLBACK (account_chooser_status_changed_cb),
-                                            G_OBJECT (chooser));
+                                            chooser, 0);
        }
 
        g_list_free (accounts);
+
+       priv->ready = TRUE;
+       g_signal_emit (chooser, signals[READY], 0);
+}
+
+static gint
+account_cmp (GtkTreeModel *model,
+            GtkTreeIter *a,
+            GtkTreeIter *b,
+            gpointer user_data)
+{
+       RowType a_type, b_type;
+       gboolean a_enabled, b_enabled;
+       gchar *a_text, *b_text;
+       gint result;
+
+       gtk_tree_model_get (model, a,
+               COL_ACCOUNT_ENABLED, &a_enabled,
+               COL_ACCOUNT_ROW_TYPE, &a_type,
+               -1);
+       gtk_tree_model_get (model, b,
+               COL_ACCOUNT_ENABLED, &b_enabled,
+               COL_ACCOUNT_ROW_TYPE, &b_type,
+               -1);
+
+       /* This assumes that we have at most one of each special row type. */
+       if (a_type != b_type) {
+               /* Display higher-numbered special row types first. */
+               return (b_type - a_type);
+       }
+
+       /* Enabled accounts are displayed first */
+       if (a_enabled != b_enabled)
+               return a_enabled ? -1: 1;
+
+       gtk_tree_model_get (model, a, COL_ACCOUNT_TEXT, &a_text, -1);
+       gtk_tree_model_get (model, b, COL_ACCOUNT_TEXT, &b_text, -1);
+
+       if (a_text == b_text)
+               result = 0;
+       else if (a_text == NULL)
+               result = 1;
+       else if (b_text == NULL)
+               result = -1;
+       else
+               result = g_ascii_strcasecmp (a_text, b_text);
+
+       g_free (a_text);
+       g_free (b_text);
+
+       return result;
 }
 
 static void
@@ -497,20 +658,25 @@ account_chooser_setup (EmpathyAccountChooser *chooser)
        gtk_cell_layout_clear (GTK_CELL_LAYOUT (combobox));
 
        store = gtk_list_store_new (COL_ACCOUNT_COUNT,
-                                   G_TYPE_STRING,    /* Image */
+                                   GDK_TYPE_PIXBUF,    /* Image */
                                    G_TYPE_STRING,    /* Name */
                                    G_TYPE_BOOLEAN,   /* Enabled */
+                                   G_TYPE_UINT,      /* Row type */
                                    TP_TYPE_ACCOUNT);
 
+       gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
+               account_cmp, chooser, NULL);
+       gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+               GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
+
        gtk_combo_box_set_model (combobox, GTK_TREE_MODEL (store));
 
        renderer = gtk_cell_renderer_pixbuf_new ();
        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), renderer, FALSE);
        gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), renderer,
-                                       "icon-name", COL_ACCOUNT_IMAGE,
+                                       "pixbuf", COL_ACCOUNT_IMAGE,
                                        "sensitive", COL_ACCOUNT_ENABLED,
                                        NULL);
-       g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
 
        renderer = gtk_cell_renderer_text_new ();
        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), renderer, TRUE);
@@ -520,7 +686,7 @@ account_chooser_setup (EmpathyAccountChooser *chooser)
                                        NULL);
 
        /* Populate accounts */
-       tp_account_manager_prepare_async (priv->manager, NULL,
+       tp_proxy_prepare_async (priv->manager, NULL,
                                          account_manager_prepared_cb, chooser);
 
        g_object_unref (store);
@@ -580,8 +746,15 @@ account_chooser_find_account_foreach (GtkTreeModel *model,
 {
        FindAccountData *data = user_data;
        TpAccount  *account;
+       RowType type;
 
-       gtk_tree_model_get (model, iter, COL_ACCOUNT_POINTER, &account, -1);
+       gtk_tree_model_get (model, iter,
+               COL_ACCOUNT_POINTER, &account,
+               COL_ACCOUNT_ROW_TYPE, &type,
+                -1);
+
+       if (type != ROW_ACCOUNT)
+               return FALSE;
 
        if (account == data->account) {
                data->found = TRUE;
@@ -634,36 +807,39 @@ account_chooser_account_remove_foreach (TpAccount             *account,
 }
 
 static void
-account_chooser_update_iter (EmpathyAccountChooser *chooser,
-                            GtkTreeIter           *iter)
+account_chooser_filter_ready_cb (gboolean is_enabled,
+                                gpointer data)
 {
+       FilterResultCallbackData  *fr_data = data;
+       EmpathyAccountChooser     *chooser;
        EmpathyAccountChooserPriv *priv;
+       TpAccount                 *account;
+       GtkTreeIter               *iter;
        GtkListStore              *store;
        GtkComboBox               *combobox;
-       TpAccount                 *account;
        const gchar               *icon_name;
-       gboolean                   is_enabled = TRUE;
+       GdkPixbuf                 *pixbuf;
 
+       chooser = fr_data->chooser;
        priv = GET_PRIV (chooser);
-
+       account = fr_data->account;
+       iter = fr_data->iter;
        combobox = GTK_COMBO_BOX (chooser);
        store = GTK_LIST_STORE (gtk_combo_box_get_model (combobox));
 
-       gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
-                           COL_ACCOUNT_POINTER, &account,
-                           -1);
-
        icon_name = tp_account_get_icon_name (account);
-       if (priv->filter) {
-               is_enabled = priv->filter (account, priv->filter_data);
-       }
+       pixbuf = empathy_pixbuf_from_icon_name (icon_name,
+               GTK_ICON_SIZE_BUTTON);
 
        gtk_list_store_set (store, iter,
-                           COL_ACCOUNT_IMAGE, icon_name,
+                           COL_ACCOUNT_IMAGE, pixbuf,
                            COL_ACCOUNT_TEXT, tp_account_get_display_name (account),
                            COL_ACCOUNT_ENABLED, is_enabled,
                            -1);
 
+       if (pixbuf != NULL)
+               g_object_unref (pixbuf);
+
        /* set first connected account as active account */
        if (priv->account_manually_set == FALSE &&
            priv->set_active_item == FALSE && is_enabled) {
@@ -671,6 +847,40 @@ account_chooser_update_iter (EmpathyAccountChooser *chooser,
                gtk_combo_box_set_active_iter (combobox, iter);
        }
 
+       filter_result_callback_data_free (fr_data);
+}
+
+static void
+account_chooser_update_iter (EmpathyAccountChooser *chooser,
+                            GtkTreeIter           *iter)
+{
+       EmpathyAccountChooserPriv *priv;
+       GtkListStore              *store;
+       GtkComboBox               *combobox;
+       TpAccount                 *account;
+       FilterResultCallbackData  *data;
+
+       priv = GET_PRIV (chooser);
+
+       combobox = GTK_COMBO_BOX (chooser);
+       store = GTK_LIST_STORE (gtk_combo_box_get_model (combobox));
+
+       gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+                           COL_ACCOUNT_POINTER, &account,
+                           -1);
+
+       /* Skip rows without account associated */
+       if (account == NULL)
+               return;
+
+       data = filter_result_callback_data_new (chooser, account, iter);
+
+       if (priv->filter)
+               priv->filter (account, account_chooser_filter_ready_cb,
+                             (gpointer) data, priv->filter_data);
+       else
+               account_chooser_filter_ready_cb (TRUE, (gpointer) data);
+
        g_object_unref (account);
 }
 
@@ -696,21 +906,10 @@ account_chooser_separator_func (GtkTreeModel         *model,
                                GtkTreeIter          *iter,
                                EmpathyAccountChooser *chooser)
 {
-       EmpathyAccountChooserPriv *priv;
-       gchar                    *text;
-       gboolean                  is_separator;
+       RowType row_type;
 
-       priv = GET_PRIV (chooser);
-
-       if (!priv->has_all_option) {
-               return FALSE;
-       }
-
-       gtk_tree_model_get (model, iter, COL_ACCOUNT_TEXT, &text, -1);
-       is_separator = text == NULL;
-       g_free (text);
-
-       return is_separator;
+       gtk_tree_model_get (model, iter, COL_ACCOUNT_ROW_TYPE, &row_type, -1);
+       return (row_type == ROW_SEPARATOR);
 }
 
 static gboolean
@@ -724,13 +923,7 @@ account_chooser_set_account_foreach (GtkTreeModel   *model,
 
        gtk_tree_model_get (model, iter, COL_ACCOUNT_POINTER, &account, -1);
 
-       /* Special case so we can make it possible to select the All option */
-       if ((data->account == NULL) != (account == NULL)) {
-               equal = FALSE;
-       }
-       else {
-               equal = (data->account == account);
-       }
+       equal = (data->account == account);
 
        if (account) {
                g_object_unref (account);
@@ -802,18 +995,92 @@ empathy_account_chooser_set_filter (EmpathyAccountChooser           *chooser,
 /**
  * empathy_account_chooser_filter_is_connected:
  * @account: a #TpAccount
+ * @callback: an #EmpathyAccountChooserFilterResultCallback accepting the result
+ * @callback_data: data passed to the @callback
  * @user_data: user data or %NULL
  *
  * A useful #EmpathyAccountChooserFilterFunc that one could pass into
  * empathy_account_chooser_set_filter() and only show connected accounts.
  *
- * Return value: Whether @account is connected
+ * Returns (via the callback) TRUE is @account is connected
+ */
+void
+empathy_account_chooser_filter_is_connected (
+       TpAccount                                 *account,
+       EmpathyAccountChooserFilterResultCallback  callback,
+       gpointer                                   callback_data,
+       gpointer                                   user_data)
+{
+       gboolean is_connected =
+               tp_account_get_connection_status (account, NULL)
+               == TP_CONNECTION_STATUS_CONNECTED;
+       callback (is_connected, callback_data);
+}
+
+/**
+ * empathy_account_chooser_filter_supports_multichat:
+ * @account: a #TpAccount
+ * @callback: an #EmpathyAccountChooserFilterResultCallback accepting the result
+ * @callback_data: data passed to the @callback
+ * @user_data: user data or %NULL
+ *
+ * An #EmpathyAccountChooserFilterFunc that returns accounts that both
+ * support multiuser text chat and are connected.
+ *
+ * Returns (via the callback) TRUE if @account both supports muc and is connected
  */
+void
+empathy_account_chooser_filter_supports_chatrooms (
+       TpAccount                                 *account,
+       EmpathyAccountChooserFilterResultCallback  callback,
+       gpointer                                   callback_data,
+       gpointer                                   user_data)
+{
+       TpConnection       *connection;
+       gboolean           supported = FALSE;
+       TpCapabilities     *caps;
+
+       /* check if CM supports multiuser text chat */
+       connection = tp_account_get_connection (account);
+       if (connection == NULL)
+               goto out;
+
+       caps = tp_connection_get_capabilities (connection);
+       if (caps == NULL)
+               goto out;
+
+       supported = tp_capabilities_supports_text_chatrooms (caps);
+
+out:
+       callback (supported, callback_data);
+}
+
 gboolean
-empathy_account_chooser_filter_is_connected (TpAccount *account,
-                                            gpointer   user_data)
+empathy_account_chooser_is_ready (EmpathyAccountChooser *self)
+{
+       EmpathyAccountChooserPriv *priv = GET_PRIV (self);
+
+       return priv->ready;
+}
+
+TpAccount *
+empathy_account_chooser_get_account (EmpathyAccountChooser *chooser)
 {
-       return (tp_account_get_connection_status (account, NULL)
-           == TP_CONNECTION_STATUS_CONNECTED);
+       TpAccount *account;
+
+       account = empathy_account_chooser_dup_account (chooser);
+       if (account == NULL)
+               return NULL;
+
+       g_object_unref (account);
+
+       return account;
 }
 
+TpAccountManager *
+empathy_account_chooser_get_account_manager (EmpathyAccountChooser *self)
+{
+       EmpathyAccountChooserPriv *priv = GET_PRIV (self);
+
+       return priv->manager;
+}