]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-account-chooser.c
Merge branch 'subscription-msg-630707'
[empathy.git] / libempathy-gtk / empathy-account-chooser.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
4  * Copyright (C) 2007-2008 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: Martyn Russell <martyn@imendio.com>
22  *          Xavier Claessens <xclaesse@gmail.com>
23  */
24
25 #include "config.h"
26
27 #include <string.h>
28
29 #include <glib/gi18n-lib.h>
30 #include <gtk/gtk.h>
31
32 #include <telepathy-glib/account-manager.h>
33 #include <telepathy-glib/util.h>
34
35 #include <libempathy/empathy-utils.h>
36
37 #include "empathy-ui-utils.h"
38 #include "empathy-account-chooser.h"
39
40 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
41 #include <libempathy/empathy-debug.h>
42
43 /**
44  * SECTION:empathy-account-chooser
45  * @title:EmpathyAccountChooser
46  * @short_description: A widget used to choose from a list of accounts
47  * @include: libempathy-gtk/empathy-account-chooser.h
48  *
49  * #EmpathyAccountChooser is a widget which extends #GtkComboBox to provide
50  * a chooser of available accounts.
51  */
52
53 /**
54  * EmpathyAccountChooser:
55  * @parent: parent object
56  *
57  * Widget which extends #GtkComboBox to provide a chooser of available accounts.
58  */
59
60 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAccountChooser)
61 typedef struct {
62         TpAccountManager               *manager;
63         gboolean                        set_active_item;
64         gboolean                        account_manually_set;
65         gboolean                        has_all_option;
66         EmpathyAccountChooserFilterFunc filter;
67         gpointer                        filter_data;
68         gboolean                        ready;
69 } EmpathyAccountChooserPriv;
70
71 typedef struct {
72         EmpathyAccountChooser *chooser;
73         TpAccount             *account;
74         gboolean               set;
75 } SetAccountData;
76
77 /* Distinguishes between store entries which are actually accounts, and special
78  * items like the "All" entry and the separator below it, so they can be sorted
79  * correctly. Higher-numbered entries will sort earlier.
80  */
81 typedef enum {
82         ROW_ACCOUNT = 0,
83         ROW_SEPARATOR,
84         ROW_ALL
85 } RowType;
86
87 enum {
88         COL_ACCOUNT_IMAGE,
89         COL_ACCOUNT_TEXT,
90         COL_ACCOUNT_ENABLED, /* Usually tied to connected state */
91         COL_ACCOUNT_ROW_TYPE,
92         COL_ACCOUNT_POINTER,
93         COL_ACCOUNT_COUNT
94 };
95
96 static void     account_chooser_finalize               (GObject                  *object);
97 static void     account_chooser_get_property           (GObject                  *object,
98                                                         guint                     param_id,
99                                                         GValue                   *value,
100                                                         GParamSpec               *pspec);
101 static void     account_chooser_set_property           (GObject                  *object,
102                                                         guint                     param_id,
103                                                         const GValue             *value,
104                                                         GParamSpec               *pspec);
105 static void     account_chooser_setup                  (EmpathyAccountChooser    *chooser);
106 static void     account_chooser_account_validity_changed_cb (TpAccountManager    *manager,
107                                                         TpAccount                *account,
108                                                         gboolean                  valid,
109                                                         EmpathyAccountChooser    *chooser);
110 static void     account_chooser_account_add_foreach    (TpAccount                *account,
111                                                         EmpathyAccountChooser    *chooser);
112 static void     account_chooser_account_removed_cb     (TpAccountManager         *manager,
113                                                         TpAccount                *account,
114                                                         EmpathyAccountChooser    *chooser);
115 static void     account_chooser_account_remove_foreach (TpAccount                *account,
116                                                         EmpathyAccountChooser    *chooser);
117 static void     account_chooser_update_iter            (EmpathyAccountChooser    *chooser,
118                                                         GtkTreeIter              *iter);
119 static void     account_chooser_status_changed_cb      (TpAccount  *account,
120                                                         guint       old_status,
121                                                         guint       new_status,
122                                                         guint       reason,
123                                                         gchar      *dbus_error_name,
124                                                         GHashTable *details,
125                                                         gpointer    user_data);
126 static gboolean account_chooser_separator_func         (GtkTreeModel             *model,
127                                                         GtkTreeIter              *iter,
128                                                         EmpathyAccountChooser    *chooser);
129 static gboolean account_chooser_set_account_foreach    (GtkTreeModel             *model,
130                                                         GtkTreePath              *path,
131                                                         GtkTreeIter              *iter,
132                                                         SetAccountData           *data);
133
134 enum {
135         PROP_0,
136         PROP_HAS_ALL_OPTION,
137 };
138
139 enum {
140         READY,
141         LAST_SIGNAL
142 };
143
144 static guint signals[LAST_SIGNAL] = { 0 };
145
146 G_DEFINE_TYPE (EmpathyAccountChooser, empathy_account_chooser, GTK_TYPE_COMBO_BOX);
147
148 static void
149 empathy_account_chooser_class_init (EmpathyAccountChooserClass *klass)
150 {
151         GObjectClass *object_class = G_OBJECT_CLASS (klass);
152
153         object_class->finalize = account_chooser_finalize;
154         object_class->get_property = account_chooser_get_property;
155         object_class->set_property = account_chooser_set_property;
156
157         /**
158          * EmpathyAccountChooser:has-all-option:
159          *
160          * Have an additional option in the list to mean all accounts.
161          */
162         g_object_class_install_property (object_class,
163                                          PROP_HAS_ALL_OPTION,
164                                          g_param_spec_boolean ("has-all-option",
165                                                                "Has All Option",
166                                                                "Have a separate option in the list to mean ALL accounts",
167                                                                FALSE,
168                                                                G_PARAM_READWRITE));
169
170         signals[READY] =
171                 g_signal_new ("ready",
172                               G_OBJECT_CLASS_TYPE (object_class),
173                               G_SIGNAL_RUN_LAST,
174                               0,
175                               NULL, NULL,
176                               g_cclosure_marshal_VOID__VOID,
177                               G_TYPE_NONE,
178                               0);
179
180         g_type_class_add_private (object_class, sizeof (EmpathyAccountChooserPriv));
181 }
182
183 static void
184 empathy_account_chooser_init (EmpathyAccountChooser *chooser)
185 {
186         EmpathyAccountChooserPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (chooser,
187                 EMPATHY_TYPE_ACCOUNT_CHOOSER, EmpathyAccountChooserPriv);
188
189         chooser->priv = priv;
190         priv->set_active_item = FALSE;
191         priv->account_manually_set = FALSE;
192         priv->filter = NULL;
193         priv->filter_data = NULL;
194
195         priv->manager = tp_account_manager_dup ();
196
197         g_signal_connect (priv->manager, "account-validity-changed",
198                           G_CALLBACK (account_chooser_account_validity_changed_cb),
199                           chooser);
200         g_signal_connect (priv->manager, "account-removed",
201                           G_CALLBACK (account_chooser_account_removed_cb),
202                           chooser);
203
204         account_chooser_setup (EMPATHY_ACCOUNT_CHOOSER (chooser));
205 }
206
207 static void
208 account_chooser_finalize (GObject *object)
209 {
210         EmpathyAccountChooserPriv *priv = GET_PRIV (object);
211
212         g_signal_handlers_disconnect_by_func (priv->manager,
213                                               account_chooser_account_validity_changed_cb,
214                                               object);
215         g_signal_handlers_disconnect_by_func (priv->manager,
216                                               account_chooser_account_removed_cb,
217                                               object);
218         g_object_unref (priv->manager);
219
220         G_OBJECT_CLASS (empathy_account_chooser_parent_class)->finalize (object);
221 }
222
223 static void
224 account_chooser_get_property (GObject    *object,
225                               guint       param_id,
226                               GValue     *value,
227                               GParamSpec *pspec)
228 {
229         EmpathyAccountChooserPriv *priv;
230
231         priv = GET_PRIV (object);
232
233         switch (param_id) {
234         case PROP_HAS_ALL_OPTION:
235                 g_value_set_boolean (value, priv->has_all_option);
236                 break;
237         default:
238                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
239                 break;
240         };
241 }
242
243 static void
244 account_chooser_set_property (GObject      *object,
245                               guint         param_id,
246                               const GValue *value,
247                               GParamSpec   *pspec)
248 {
249         EmpathyAccountChooserPriv *priv;
250
251         priv = GET_PRIV (object);
252
253         switch (param_id) {
254         case PROP_HAS_ALL_OPTION:
255                 empathy_account_chooser_set_has_all_option (EMPATHY_ACCOUNT_CHOOSER (object),
256                                                            g_value_get_boolean (value));
257                 break;
258         default:
259                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
260                 break;
261         };
262 }
263
264 /**
265  * empathy_account_chooser_new:
266  *
267  * Creates a new #EmpathyAccountChooser.
268  *
269  * Return value: A new #EmpathyAccountChooser
270  */
271 GtkWidget *
272 empathy_account_chooser_new (void)
273 {
274         GtkWidget                *chooser;
275
276         chooser = g_object_new (EMPATHY_TYPE_ACCOUNT_CHOOSER, NULL);
277
278         return chooser;
279 }
280
281 /**
282  * empathy_account_chooser_dup_account:
283  * @chooser: an #EmpathyAccountChooser
284  *
285  * Returns the account which is currently selected in the chooser or %NULL
286  * if there is no account selected. The #TpAccount returned should be
287  * unrefed with g_object_unref() when finished with.
288  *
289  * Return value: a new ref to the #TpAccount currently selected, or %NULL.
290  */
291 TpAccount *
292 empathy_account_chooser_dup_account (EmpathyAccountChooser *chooser)
293 {
294         EmpathyAccountChooserPriv *priv;
295         TpAccount                 *account;
296         GtkTreeModel             *model;
297         GtkTreeIter               iter;
298
299         g_return_val_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser), NULL);
300
301         priv = GET_PRIV (chooser);
302
303         model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
304         if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (chooser), &iter)) {
305                 return NULL;
306         }
307
308         gtk_tree_model_get (model, &iter, COL_ACCOUNT_POINTER, &account, -1);
309
310         return account;
311 }
312
313 /**
314  * empathy_account_chooser_get_connection:
315  * @chooser: an #EmpathyAccountChooser
316  *
317  * Returns a borrowed reference to the #TpConnection associated with the
318  * account currently selected. The caller must reference the returned object with
319  * g_object_ref() if it will be kept
320  *
321  * Return value: a borrowed reference to the #TpConnection associated with the
322  * account curently selected.
323  */
324 TpConnection *
325 empathy_account_chooser_get_connection (EmpathyAccountChooser *chooser)
326 {
327         EmpathyAccountChooserPriv *priv;
328         TpAccount                 *account;
329         TpConnection              *connection;
330
331         g_return_val_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser), NULL);
332
333         priv = GET_PRIV (chooser);
334
335         account = empathy_account_chooser_dup_account (chooser);
336
337         /* if the returned account is NULL, then the account manager probably
338          * hasn't been prepared yet. It should be safe to return NULL here
339          * though. */
340         if (account == NULL) {
341                 return NULL;
342         }
343
344         connection = tp_account_get_connection (account);
345         g_object_unref (account);
346
347         return connection;
348 }
349
350 /**
351  * empathy_account_chooser_set_account:
352  * @chooser: an #EmpathyAccountChooser
353  * @account: a #TpAccount
354  *
355  * Sets the currently selected account to @account, if it exists in the list.
356  *
357  * Return value: whether the chooser was set to @account.
358  */
359 gboolean
360 empathy_account_chooser_set_account (EmpathyAccountChooser *chooser,
361                                      TpAccount             *account)
362 {
363         EmpathyAccountChooserPriv *priv;
364         GtkComboBox    *combobox;
365         GtkTreeModel   *model;
366         GtkTreeIter     iter;
367         SetAccountData  data;
368
369         g_return_val_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser), FALSE);
370
371         priv = GET_PRIV (chooser);
372
373         combobox = GTK_COMBO_BOX (chooser);
374         model = gtk_combo_box_get_model (combobox);
375         gtk_combo_box_get_active_iter (combobox, &iter);
376
377         data.chooser = chooser;
378         data.account = account;
379
380         gtk_tree_model_foreach (model,
381                                 (GtkTreeModelForeachFunc) account_chooser_set_account_foreach,
382                                 &data);
383
384         priv->account_manually_set = data.set;
385
386         return data.set;
387 }
388
389 /**
390  * empathy_account_chooser_get_has_all_option:
391  * @chooser: an #EmpathyAccountChooser
392  *
393  * Returns whether @chooser has the #EmpathyAccountChooser:has-all-option property
394  * set to true.
395  *
396  * Return value: whether @chooser has the #EmpathyAccountChooser:has-all-option property
397  * enabled.
398  */
399 gboolean
400 empathy_account_chooser_get_has_all_option (EmpathyAccountChooser *chooser)
401 {
402         EmpathyAccountChooserPriv *priv;
403
404         g_return_val_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser), FALSE);
405
406         priv = GET_PRIV (chooser);
407
408         return priv->has_all_option;
409 }
410
411 /**
412  * empathy_account_chooser_set_has_all_option:
413  * @chooser: an #EmpathyAccountChooser
414  * @has_all_option: a new value for the #EmpathyAccountChooser:has-all-option property
415  *
416  * Sets the #EmpathyAccountChooser:has-all-option property.
417  */
418 void
419 empathy_account_chooser_set_has_all_option (EmpathyAccountChooser *chooser,
420                                            gboolean              has_all_option)
421 {
422         EmpathyAccountChooserPriv *priv;
423         GtkComboBox              *combobox;
424         GtkListStore             *store;
425         GtkTreeModel             *model;
426         GtkTreeIter               iter;
427
428         g_return_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser));
429
430         priv = GET_PRIV (chooser);
431
432         if (priv->has_all_option == has_all_option) {
433                 return;
434         }
435
436         combobox = GTK_COMBO_BOX (chooser);
437         model = gtk_combo_box_get_model (combobox);
438         store = GTK_LIST_STORE (model);
439
440         priv->has_all_option = has_all_option;
441
442         /*
443          * The first 2 options are the ALL and separator
444          */
445
446         if (has_all_option) {
447                 gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser),
448                                                       (GtkTreeViewRowSeparatorFunc)
449                                                       account_chooser_separator_func,
450                                                       chooser,
451                                                       NULL);
452
453                 gtk_list_store_prepend (store, &iter);
454                 gtk_list_store_set (store, &iter,
455                                     COL_ACCOUNT_TEXT, NULL,
456                                     COL_ACCOUNT_ENABLED, TRUE,
457                                     COL_ACCOUNT_POINTER, NULL,
458                                     COL_ACCOUNT_ROW_TYPE, ROW_SEPARATOR,
459                                     -1);
460
461                 gtk_list_store_prepend (store, &iter);
462                 gtk_list_store_set (store, &iter,
463                                     COL_ACCOUNT_TEXT, _("All"),
464                                     COL_ACCOUNT_ENABLED, TRUE,
465                                     COL_ACCOUNT_POINTER, NULL,
466                                     COL_ACCOUNT_ROW_TYPE, ROW_ALL,
467                                     -1);
468         } else {
469                 if (gtk_tree_model_get_iter_first (model, &iter)) {
470                         if (gtk_list_store_remove (GTK_LIST_STORE (model), &iter)) {
471                                 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
472                         }
473                 }
474
475                 gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (chooser),
476                                                       (GtkTreeViewRowSeparatorFunc)
477                                                       NULL,
478                                                       NULL,
479                                                       NULL);
480         }
481
482         g_object_notify (G_OBJECT (chooser), "has-all-option");
483 }
484
485 static void
486 account_manager_prepared_cb (GObject *source_object,
487                              GAsyncResult *result,
488                              gpointer user_data)
489 {
490         GList *accounts, *l;
491         TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
492         EmpathyAccountChooser *chooser = user_data;
493         EmpathyAccountChooserPriv *priv = GET_PRIV (chooser);
494         GError *error = NULL;
495
496         if (!tp_account_manager_prepare_finish (manager, result, &error)) {
497                 DEBUG ("Failed to prepare account manager: %s", error->message);
498                 g_error_free (error);
499                 return;
500         }
501
502         accounts = tp_account_manager_get_valid_accounts (manager);
503
504         for (l = accounts; l != NULL; l = l->next) {
505                 TpAccount *account = l->data;
506
507                 account_chooser_account_add_foreach (account, chooser);
508
509                 tp_g_signal_connect_object (account, "status-changed",
510                                              G_CALLBACK (account_chooser_status_changed_cb),
511                                              chooser, 0);
512         }
513
514         g_list_free (accounts);
515
516         priv->ready = TRUE;
517         g_signal_emit (chooser, signals[READY], 0);
518 }
519
520 static gint
521 account_cmp (GtkTreeModel *model,
522              GtkTreeIter *a,
523              GtkTreeIter *b,
524              gpointer user_data)
525 {
526         RowType a_type, b_type;
527         gboolean a_enabled, b_enabled;
528         gchar *a_text, *b_text;
529         gint result;
530
531         gtk_tree_model_get (model, a,
532                 COL_ACCOUNT_ENABLED, &a_enabled,
533                 COL_ACCOUNT_ROW_TYPE, &a_type,
534                 -1);
535         gtk_tree_model_get (model, b,
536                 COL_ACCOUNT_ENABLED, &b_enabled,
537                 COL_ACCOUNT_ROW_TYPE, &b_type,
538                 -1);
539
540         /* This assumes that we have at most one of each special row type. */
541         if (a_type != b_type) {
542                 /* Display higher-numbered special row types first. */
543                 return (b_type - a_type);
544         }
545
546         /* Enabled accounts are displayed first */
547         if (a_enabled != b_enabled)
548                 return a_enabled ? -1: 1;
549
550         gtk_tree_model_get (model, a, COL_ACCOUNT_TEXT, &a_text, -1);
551         gtk_tree_model_get (model, b, COL_ACCOUNT_TEXT, &b_text, -1);
552
553         if (a_text == b_text)
554                 result = 0;
555         else if (a_text == NULL)
556                 result = 1;
557         else if (b_text == NULL)
558                 result = -1;
559         else
560                 result = g_ascii_strcasecmp (a_text, b_text);
561
562         g_free (a_text);
563         g_free (b_text);
564
565         return result;
566 }
567
568 static void
569 account_chooser_setup (EmpathyAccountChooser *chooser)
570 {
571         EmpathyAccountChooserPriv *priv;
572         GtkListStore             *store;
573         GtkCellRenderer          *renderer;
574         GtkComboBox              *combobox;
575
576         priv = GET_PRIV (chooser);
577
578         /* Set up combo box with new store */
579         combobox = GTK_COMBO_BOX (chooser);
580
581         gtk_cell_layout_clear (GTK_CELL_LAYOUT (combobox));
582
583         store = gtk_list_store_new (COL_ACCOUNT_COUNT,
584                                     G_TYPE_STRING,    /* Image */
585                                     G_TYPE_STRING,    /* Name */
586                                     G_TYPE_BOOLEAN,   /* Enabled */
587                                     G_TYPE_UINT,      /* Row type */
588                                     TP_TYPE_ACCOUNT);
589
590         gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
591                 account_cmp, chooser, NULL);
592         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
593                 GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
594
595         gtk_combo_box_set_model (combobox, GTK_TREE_MODEL (store));
596
597         renderer = gtk_cell_renderer_pixbuf_new ();
598         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), renderer, FALSE);
599         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), renderer,
600                                         "icon-name", COL_ACCOUNT_IMAGE,
601                                         "sensitive", COL_ACCOUNT_ENABLED,
602                                         NULL);
603         g_object_set (renderer, "stock-size", GTK_ICON_SIZE_BUTTON, NULL);
604
605         renderer = gtk_cell_renderer_text_new ();
606         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), renderer, TRUE);
607         gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), renderer,
608                                         "text", COL_ACCOUNT_TEXT,
609                                         "sensitive", COL_ACCOUNT_ENABLED,
610                                         NULL);
611
612         /* Populate accounts */
613         tp_account_manager_prepare_async (priv->manager, NULL,
614                                           account_manager_prepared_cb, chooser);
615
616         g_object_unref (store);
617 }
618
619 static void
620 account_chooser_account_validity_changed_cb (TpAccountManager      *manager,
621                                              TpAccount             *account,
622                                              gboolean               valid,
623                                              EmpathyAccountChooser *chooser)
624 {
625         if (valid) {
626                 account_chooser_account_add_foreach (account, chooser);
627         } else {
628                 account_chooser_account_remove_foreach (account, chooser);
629         }
630 }
631
632 static void
633 account_chooser_account_add_foreach (TpAccount             *account,
634                                      EmpathyAccountChooser *chooser)
635 {
636         GtkListStore *store;
637         GtkComboBox  *combobox;
638         GtkTreeIter   iter;
639         gint          position;
640
641         combobox = GTK_COMBO_BOX (chooser);
642         store = GTK_LIST_STORE (gtk_combo_box_get_model (combobox));
643
644         position = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL);
645         gtk_list_store_insert_with_values (store, &iter, position,
646                                            COL_ACCOUNT_POINTER, account,
647                                            -1);
648         account_chooser_update_iter (chooser, &iter);
649 }
650
651 static void
652 account_chooser_account_removed_cb (TpAccountManager      *manager,
653                                     TpAccount             *account,
654                                     EmpathyAccountChooser *chooser)
655 {
656         account_chooser_account_remove_foreach (account, chooser);
657 }
658
659 typedef struct {
660         TpAccount   *account;
661         GtkTreeIter *iter;
662         gboolean     found;
663 } FindAccountData;
664
665 static gboolean
666 account_chooser_find_account_foreach (GtkTreeModel *model,
667                                       GtkTreePath  *path,
668                                       GtkTreeIter  *iter,
669                                       gpointer      user_data)
670 {
671         FindAccountData *data = user_data;
672         TpAccount  *account;
673
674         gtk_tree_model_get (model, iter, COL_ACCOUNT_POINTER, &account, -1);
675
676         if (account == data->account) {
677                 data->found = TRUE;
678                 *(data->iter) = *iter;
679                 g_object_unref (account);
680
681                 return TRUE;
682         }
683
684         g_object_unref (account);
685
686         return FALSE;
687 }
688
689 static gboolean
690 account_chooser_find_account (EmpathyAccountChooser *chooser,
691                               TpAccount             *account,
692                               GtkTreeIter           *iter)
693 {
694         GtkListStore    *store;
695         GtkComboBox     *combobox;
696         FindAccountData  data;
697
698         combobox = GTK_COMBO_BOX (chooser);
699         store = GTK_LIST_STORE (gtk_combo_box_get_model (combobox));
700
701         data.account = account;
702         data.iter = iter;
703         gtk_tree_model_foreach (GTK_TREE_MODEL (store),
704                                 account_chooser_find_account_foreach,
705                                 &data);
706
707         return data.found;
708 }
709
710 static void
711 account_chooser_account_remove_foreach (TpAccount             *account,
712                                         EmpathyAccountChooser *chooser)
713 {
714         GtkListStore *store;
715         GtkComboBox  *combobox;
716         GtkTreeIter   iter;
717
718         combobox = GTK_COMBO_BOX (chooser);
719         store = GTK_LIST_STORE (gtk_combo_box_get_model (combobox));
720
721         if (account_chooser_find_account (chooser, account, &iter)) {
722                 gtk_list_store_remove (store, &iter);
723         }
724 }
725
726 static void
727 account_chooser_update_iter (EmpathyAccountChooser *chooser,
728                              GtkTreeIter           *iter)
729 {
730         EmpathyAccountChooserPriv *priv;
731         GtkListStore              *store;
732         GtkComboBox               *combobox;
733         TpAccount                 *account;
734         const gchar               *icon_name;
735         gboolean                   is_enabled = TRUE;
736
737         priv = GET_PRIV (chooser);
738
739         combobox = GTK_COMBO_BOX (chooser);
740         store = GTK_LIST_STORE (gtk_combo_box_get_model (combobox));
741
742         gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
743                             COL_ACCOUNT_POINTER, &account,
744                             -1);
745
746         /* Skip rows without account associated */
747         if (account == NULL)
748                 return;
749
750         icon_name = tp_account_get_icon_name (account);
751         if (priv->filter) {
752                 is_enabled = priv->filter (account, priv->filter_data);
753         }
754
755         gtk_list_store_set (store, iter,
756                             COL_ACCOUNT_IMAGE, icon_name,
757                             COL_ACCOUNT_TEXT, tp_account_get_display_name (account),
758                             COL_ACCOUNT_ENABLED, is_enabled,
759                             -1);
760
761         /* set first connected account as active account */
762         if (priv->account_manually_set == FALSE &&
763             priv->set_active_item == FALSE && is_enabled) {
764                 priv->set_active_item = TRUE;
765                 gtk_combo_box_set_active_iter (combobox, iter);
766         }
767
768         g_object_unref (account);
769 }
770
771 static void
772 account_chooser_status_changed_cb (TpAccount  *account,
773                                    guint       old_status,
774                                    guint       new_status,
775                                    guint       reason,
776                                    gchar      *dbus_error_name,
777                                    GHashTable *details,
778                                    gpointer    user_data)
779 {
780         EmpathyAccountChooser *chooser = user_data;
781         GtkTreeIter iter;
782
783         if (account_chooser_find_account (chooser, account, &iter)) {
784                 account_chooser_update_iter (chooser, &iter);
785         }
786 }
787
788 static gboolean
789 account_chooser_separator_func (GtkTreeModel         *model,
790                                 GtkTreeIter          *iter,
791                                 EmpathyAccountChooser *chooser)
792 {
793         RowType row_type;
794
795         gtk_tree_model_get (model, iter, COL_ACCOUNT_ROW_TYPE, &row_type, -1);
796         return (row_type == ROW_SEPARATOR);
797 }
798
799 static gboolean
800 account_chooser_set_account_foreach (GtkTreeModel   *model,
801                                      GtkTreePath    *path,
802                                      GtkTreeIter    *iter,
803                                      SetAccountData *data)
804 {
805         TpAccount *account;
806         gboolean   equal;
807
808         gtk_tree_model_get (model, iter, COL_ACCOUNT_POINTER, &account, -1);
809
810         equal = (data->account == account);
811
812         if (account) {
813                 g_object_unref (account);
814         }
815
816         if (equal) {
817                 GtkComboBox *combobox;
818
819                 combobox = GTK_COMBO_BOX (data->chooser);
820                 gtk_combo_box_set_active_iter (combobox, iter);
821
822                 data->set = TRUE;
823         }
824
825         return equal;
826 }
827
828 static gboolean
829 account_chooser_filter_foreach (GtkTreeModel *model,
830                                 GtkTreePath  *path,
831                                 GtkTreeIter  *iter,
832                                 gpointer      chooser)
833 {
834         account_chooser_update_iter (chooser, iter);
835         return FALSE;
836 }
837
838 /**
839  * empathy_account_chooser_set_filter:
840  * @chooser: an #EmpathyAccountChooser
841  * @filter: a filter
842  * @user_data: data to pass to @filter, or %NULL
843  *
844  * Sets a filter on the @chooser so only accounts that are %TRUE in the eyes
845  * of the filter are visible in the @chooser.
846  */
847 void
848 empathy_account_chooser_set_filter (EmpathyAccountChooser           *chooser,
849                                     EmpathyAccountChooserFilterFunc  filter,
850                                     gpointer                         user_data)
851 {
852         EmpathyAccountChooserPriv *priv;
853         GtkTreeModel *model;
854
855         g_return_if_fail (EMPATHY_IS_ACCOUNT_CHOOSER (chooser));
856
857         priv = GET_PRIV (chooser);
858
859         priv->filter = filter;
860         priv->filter_data = user_data;
861
862         /* Refilter existing data */
863         priv->set_active_item = FALSE;
864         model = gtk_combo_box_get_model (GTK_COMBO_BOX (chooser));
865         gtk_tree_model_foreach (model, account_chooser_filter_foreach, chooser);
866 }
867
868 /**
869  * EmpathyAccountChooserFilterFunc:
870  * @account: a #TpAccount
871  * @user_data: user data, or %NULL
872  *
873  * A function which decides whether the account indicated by @account
874  * is visible.
875  *
876  * Return value: whether the account indicated by @account is visible.
877  */
878
879 /**
880  * empathy_account_chooser_filter_is_connected:
881  * @account: a #TpAccount
882  * @user_data: user data or %NULL
883  *
884  * A useful #EmpathyAccountChooserFilterFunc that one could pass into
885  * empathy_account_chooser_set_filter() and only show connected accounts.
886  *
887  * Return value: Whether @account is connected
888  */
889 gboolean
890 empathy_account_chooser_filter_is_connected (TpAccount *account,
891                                              gpointer   user_data)
892 {
893         return (tp_account_get_connection_status (account, NULL)
894             == TP_CONNECTION_STATUS_CONNECTED);
895 }
896
897 gboolean
898 empathy_account_chooser_is_ready (EmpathyAccountChooser *self)
899 {
900         EmpathyAccountChooserPriv *priv = GET_PRIV (self);
901
902         return priv->ready;
903 }
904
905 TpAccount *
906 empathy_account_chooser_get_account (EmpathyAccountChooser *chooser)
907 {
908         TpAccount *account;
909
910         account = empathy_account_chooser_dup_account (chooser);
911         if (account == NULL)
912                 return NULL;
913
914         g_object_unref (account);
915
916         return account;
917 }