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