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