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