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