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