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