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