]> git.0d.be Git - empathy.git/blob - src/empathy-accounts-dialog.c
Remove calls to gtk_dialog_set_has_separator() if building against GTK+ 3
[empathy.git] / src / empathy-accounts-dialog.c
1 /*
2  * Copyright (C) 2005-2007 Imendio AB
3  * Copyright (C) 2007-2009 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  *          Cosimo Cecchi <cosimo.cecchi@collabora.co.uk>
23  *          Jonathan Tellier <jonathan.tellier@gmail.com>
24  */
25
26 #include <config.h>
27
28 #include <string.h>
29 #include <stdlib.h>
30
31 #include <gtk/gtk.h>
32 #include <glib/gi18n-lib.h>
33 #include <dbus/dbus-glib.h>
34
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/defs.h>
37 #include <telepathy-glib/util.h>
38
39 #include <libempathy/empathy-utils.h>
40 #include <libempathy/empathy-connection-managers.h>
41 #include <libempathy/empathy-connectivity.h>
42 #include <libempathy/empathy-gsettings.h>
43
44 #include <libempathy-gtk/empathy-ui-utils.h>
45 #include <libempathy-gtk/empathy-protocol-chooser.h>
46 #include <libempathy-gtk/empathy-account-widget.h>
47 #include <libempathy-gtk/empathy-account-widget-irc.h>
48 #include <libempathy-gtk/empathy-account-widget-sip.h>
49 #include <libempathy-gtk/empathy-cell-renderer-activatable.h>
50 #include <libempathy-gtk/empathy-images.h>
51
52 #include "empathy-accounts-dialog.h"
53 #include "empathy-import-dialog.h"
54 #include "empathy-import-utils.h"
55
56 #define DEBUG_FLAG EMPATHY_DEBUG_ACCOUNT
57 #include <libempathy/empathy-debug.h>
58
59 /* Flashing delay for icons (milliseconds). */
60 #define FLASH_TIMEOUT 500
61
62 /* The primary text of the dialog shown to the user when he is about to lose
63  * unsaved changes */
64 #define PENDING_CHANGES_QUESTION_PRIMARY_TEXT \
65   _("There are unsaved modifications to your %s account.")
66 /* The primary text of the dialog shown to the user when he is about to lose
67  * an unsaved new account */
68 #define UNSAVED_NEW_ACCOUNT_QUESTION_PRIMARY_TEXT \
69   _("Your new account has not been saved yet.")
70
71 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAccountsDialog)
72 G_DEFINE_TYPE (EmpathyAccountsDialog, empathy_accounts_dialog, GTK_TYPE_DIALOG);
73
74 enum
75 {
76   NOTEBOOK_PAGE_ACCOUNT = 0,
77   NOTEBOOK_PAGE_LOADING
78 };
79
80 typedef struct {
81   GtkWidget *alignment_settings;
82   GtkWidget *alignment_infobar;
83
84   GtkWidget *vbox_details;
85   GtkWidget *infobar;
86   GtkWidget *label_status;
87   GtkWidget *image_status;
88   GtkWidget *throbber;
89   GtkWidget *frame_no_protocol;
90
91   GtkWidget *treeview;
92
93   GtkWidget *button_add;
94   GtkWidget *button_remove;
95   GtkWidget *button_import;
96
97   GtkWidget *combobox_protocol;
98   GtkWidget *hbox_protocol;
99
100   GtkWidget *image_type;
101   GtkWidget *label_name;
102   GtkWidget *label_type;
103   GtkWidget *settings_widget;
104
105   GtkWidget *notebook_account;
106   GtkWidget *spinner;
107   gboolean loading;
108
109   /* We have to keep a reference on the actual EmpathyAccountWidget, not just
110    * his GtkWidget. It is the only reliable source we can query to know if
111    * there are any unsaved changes to the currently selected account. We can't
112    * look at the account settings because it does not contain everything that
113    * can be changed using the EmpathyAccountWidget. For instance, it does not
114    * contain the state of the "Enabled" checkbox. */
115   EmpathyAccountWidget *setting_widget_object;
116
117   gboolean  connecting_show;
118   guint connecting_id;
119
120   gulong  settings_ready_id;
121   EmpathyAccountSettings *settings_ready;
122
123   TpAccountManager *account_manager;
124   EmpathyConnectionManagers *cms;
125   EmpathyConnectivity *connectivity;
126
127   GtkWindow *parent_window;
128   TpAccount *initial_selection;
129
130   /* Those are needed when changing the selected row. When a user selects
131    * another account and there are unsaved changes on the currently selected
132    * one, a confirmation message box is presented to him. Since his answer
133    * is retrieved asynchronously, we keep some information as member of the
134    * EmpathyAccountsDialog object. */
135   gboolean force_change_row;
136   GtkTreeRowReference *destination_row;
137 } EmpathyAccountsDialogPriv;
138
139 enum {
140   COL_NAME,
141   COL_STATUS,
142   COL_ACCOUNT,
143   COL_ACCOUNT_SETTINGS,
144   COL_COUNT
145 };
146
147 enum {
148   PROP_PARENT = 1
149 };
150
151 static EmpathyAccountSettings * accounts_dialog_model_get_selected_settings (
152     EmpathyAccountsDialog *dialog);
153
154 static gboolean accounts_dialog_get_settings_iter (
155     EmpathyAccountsDialog *dialog,
156     EmpathyAccountSettings *settings,
157     GtkTreeIter *iter);
158
159 static void accounts_dialog_model_select_first (EmpathyAccountsDialog *dialog);
160
161 static void accounts_dialog_update_settings (EmpathyAccountsDialog *dialog,
162     EmpathyAccountSettings *settings);
163
164 static void accounts_dialog_add (EmpathyAccountsDialog *dialog,
165     EmpathyAccountSettings *settings);
166
167 static void accounts_dialog_model_set_selected (EmpathyAccountsDialog *dialog,
168     EmpathyAccountSettings *settings);
169
170 static void accounts_dialog_connection_changed_cb (TpAccount *account,
171     guint old_status,
172     guint current,
173     guint reason,
174     gchar *dbus_error_name,
175     GHashTable *details,
176     EmpathyAccountsDialog *dialog);
177
178 static void accounts_dialog_presence_changed_cb (TpAccount *account,
179     guint presence,
180     gchar *status,
181     gchar *status_message,
182     EmpathyAccountsDialog *dialog);
183
184 static void accounts_dialog_model_selection_changed (
185     GtkTreeSelection *selection,
186     EmpathyAccountsDialog *dialog);
187
188 static void
189 accounts_dialog_update_name_label (EmpathyAccountsDialog *dialog,
190     const gchar *display_name)
191 {
192   gchar *text;
193   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
194
195   text = g_markup_printf_escaped ("<b>%s</b>", display_name);
196   gtk_label_set_markup (GTK_LABEL (priv->label_name), text);
197
198   g_free (text);
199 }
200
201 static void
202 accounts_dialog_status_infobar_set_message (EmpathyAccountsDialog *dialog,
203     const gchar *message)
204 {
205   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
206   gchar *message_markup;
207
208   message_markup = g_markup_printf_escaped ("<i>%s</i>", message);
209   gtk_label_set_markup (GTK_LABEL (priv->label_status), message_markup);
210   g_free (message_markup);
211 }
212
213 static void
214 accounts_dialog_update_status_infobar (EmpathyAccountsDialog *dialog,
215     TpAccount *account)
216 {
217   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
218   gchar                     *status_message = NULL;
219   guint                     status;
220   guint                     reason;
221   guint                     presence;
222   GtkTreeView               *view;
223   GtkTreeModel              *model;
224   GtkTreeSelection          *selection;
225   GtkTreeIter               iter;
226   TpAccount                 *selected_account;
227   gboolean                  account_enabled;
228   gboolean                  creating_account;
229
230   view = GTK_TREE_VIEW (priv->treeview);
231   selection = gtk_tree_view_get_selection (view);
232
233   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
234     return;
235
236   gtk_tree_model_get (model, &iter, COL_ACCOUNT, &selected_account, -1);
237   if (selected_account != NULL)
238     g_object_unref (selected_account);
239
240   /* do not update the infobar when the account is not selected */
241   if (account != selected_account)
242     return;
243
244   if (account != NULL)
245     {
246       status = tp_account_get_connection_status (account, &reason);
247       presence = tp_account_get_current_presence (account, NULL, &status_message);
248       account_enabled = tp_account_is_enabled (account);
249       creating_account = FALSE;
250
251       if (status == TP_CONNECTION_STATUS_CONNECTED &&
252           (presence == TP_CONNECTION_PRESENCE_TYPE_OFFLINE ||
253            presence == TP_CONNECTION_PRESENCE_TYPE_UNSET))
254         /* If presence is Unset (CM doesn't implement SimplePresence) but we
255          * are connected, consider ourself as Available.
256          * We also check Offline because of this MC5 bug: fd.o #26060 */
257         presence = TP_CONNECTION_PRESENCE_TYPE_AVAILABLE;
258
259       /* set presence to offline if account is disabled
260        * (else no icon is shown in infobar)*/
261       if (!account_enabled)
262         presence = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
263     }
264   else
265     {
266       status = TP_CONNECTION_STATUS_DISCONNECTED;
267       presence = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
268       account_enabled = FALSE;
269       creating_account = TRUE;
270     }
271
272   gtk_image_set_from_icon_name (GTK_IMAGE (priv->image_status),
273       empathy_icon_name_for_presence (presence), GTK_ICON_SIZE_SMALL_TOOLBAR);
274
275   if (account_enabled)
276     {
277       switch (status)
278         {
279           case TP_CONNECTION_STATUS_CONNECTING:
280             accounts_dialog_status_infobar_set_message (dialog,
281                 _("Connecting…"));
282             gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
283                 GTK_MESSAGE_INFO);
284
285             gtk_spinner_start (GTK_SPINNER (priv->throbber));
286             gtk_widget_show (priv->throbber);
287             gtk_widget_hide (priv->image_status);
288             break;
289           case TP_CONNECTION_STATUS_CONNECTED:
290             if (g_strcmp0 (status_message, "") == 0)
291               {
292                 gchar *message;
293
294                 message = g_strdup_printf ("%s",
295                     empathy_presence_get_default_message (presence));
296
297                 accounts_dialog_status_infobar_set_message (dialog, message);
298                 g_free (message);
299               }
300             else
301               {
302                 gchar *message;
303
304                 message = g_strdup_printf ("%s — %s",
305                     empathy_presence_get_default_message (presence),
306                     status_message);
307
308                 accounts_dialog_status_infobar_set_message (dialog, message);
309                 g_free (message);
310               }
311             gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
312                 GTK_MESSAGE_INFO);
313
314             gtk_widget_show (priv->image_status);
315             gtk_widget_hide (priv->throbber);
316             break;
317           case TP_CONNECTION_STATUS_DISCONNECTED:
318             if (reason == TP_CONNECTION_STATUS_REASON_REQUESTED)
319               {
320                 gchar *message;
321
322                 message = g_strdup_printf (_("Offline — %s"),
323                     empathy_account_get_error_message (account, NULL));
324                 gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
325                     GTK_MESSAGE_WARNING);
326
327                 accounts_dialog_status_infobar_set_message (dialog, message);
328                 g_free (message);
329               }
330             else
331               {
332                 gchar *message;
333
334                 message = g_strdup_printf (_("Disconnected — %s"),
335                     empathy_account_get_error_message (account, NULL));
336                 gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
337                     GTK_MESSAGE_ERROR);
338
339                 accounts_dialog_status_infobar_set_message (dialog, message);
340                 g_free (message);
341               }
342
343             if (!empathy_connectivity_is_online (priv->connectivity))
344                accounts_dialog_status_infobar_set_message (dialog,
345                     _("Offline — No Network Connection"));
346
347             gtk_spinner_stop (GTK_SPINNER (priv->throbber));
348             gtk_widget_show (priv->image_status);
349             gtk_widget_hide (priv->throbber);
350             break;
351           default:
352             accounts_dialog_status_infobar_set_message (dialog, _("Unknown Status"));
353             gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
354                 GTK_MESSAGE_WARNING);
355
356             gtk_spinner_stop (GTK_SPINNER (priv->throbber));
357             gtk_widget_hide (priv->image_status);
358             gtk_widget_hide (priv->throbber);
359         }
360     }
361   else
362     {
363       accounts_dialog_status_infobar_set_message (dialog,
364           _("Offline — Account Disabled"));
365
366       gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
367           GTK_MESSAGE_WARNING);
368       gtk_spinner_stop (GTK_SPINNER (priv->throbber));
369       gtk_widget_show (priv->image_status);
370       gtk_widget_hide (priv->throbber);
371     }
372
373   gtk_widget_show (priv->label_status);
374
375   if (!creating_account)
376     gtk_widget_show (priv->infobar);
377   else
378     gtk_widget_hide (priv->infobar);
379
380   g_free (status_message);
381 }
382
383 void
384 empathy_account_dialog_cancel (EmpathyAccountsDialog *dialog)
385 {
386   GtkTreeView *view;
387   GtkTreeModel *model;
388   GtkTreeSelection *selection;
389   GtkTreeIter iter;
390   EmpathyAccountSettings *settings;
391   TpAccount *account;
392   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
393
394   view = GTK_TREE_VIEW (priv->treeview);
395   selection = gtk_tree_view_get_selection (view);
396
397   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
398     return;
399
400   gtk_tree_model_get (model, &iter,
401       COL_ACCOUNT_SETTINGS, &settings,
402       COL_ACCOUNT, &account, -1);
403
404   empathy_account_widget_discard_pending_changes (priv->setting_widget_object);
405
406   if (account == NULL)
407     {
408       /* We were creating an account. We remove the selected row */
409       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
410     }
411   else
412     {
413       /* We were modifying an account. We discard the changes by reloading the
414        * settings and the UI. */
415       accounts_dialog_update_settings (dialog, settings);
416       g_object_unref (account);
417     }
418
419   gtk_widget_set_sensitive (priv->treeview, TRUE);
420   gtk_widget_set_sensitive (priv->button_add, TRUE);
421   gtk_widget_set_sensitive (priv->button_remove, TRUE);
422   gtk_widget_set_sensitive (priv->button_import, TRUE);
423
424   if (settings != NULL)
425     g_object_unref (settings);
426 }
427
428 static void
429 empathy_account_dialog_widget_cancelled_cb (
430     EmpathyAccountWidget *widget_object,
431     EmpathyAccountsDialog *dialog)
432 {
433   empathy_account_dialog_cancel (dialog);
434 }
435
436 static void
437 empathy_account_dialog_account_created_cb (EmpathyAccountWidget *widget_object,
438     TpAccount *account,
439     EmpathyAccountsDialog *dialog)
440 {
441   EmpathyAccountSettings *settings =
442       accounts_dialog_model_get_selected_settings (dialog);
443   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
444
445   accounts_dialog_update_settings (dialog, settings);
446   accounts_dialog_update_status_infobar (dialog,
447       empathy_account_settings_get_account (settings));
448
449   gtk_widget_set_sensitive (priv->treeview, TRUE);
450   gtk_widget_set_sensitive (priv->button_add, TRUE);
451   gtk_widget_set_sensitive (priv->button_remove, TRUE);
452   gtk_widget_set_sensitive (priv->button_import, TRUE);
453
454   if (settings)
455     g_object_unref (settings);
456 }
457
458 static gboolean
459 accounts_dialog_has_valid_accounts (EmpathyAccountsDialog *dialog)
460 {
461   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
462   GtkTreeModel *model;
463   GtkTreeIter iter;
464   gboolean creating;
465
466   g_object_get (priv->setting_widget_object,
467       "creating-account", &creating, NULL);
468
469   if (!creating)
470     return TRUE;
471
472   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
473
474   if (gtk_tree_model_get_iter_first (model, &iter))
475     return gtk_tree_model_iter_next (model, &iter);
476
477   return FALSE;
478 }
479
480 static void
481 account_dialog_create_settings_widget (EmpathyAccountsDialog *dialog,
482     EmpathyAccountSettings *settings)
483 {
484   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
485   const gchar               *icon_name;
486   TpAccount                 *account;
487
488   priv->setting_widget_object =
489       empathy_account_widget_new_for_protocol (settings, FALSE);
490
491   if (accounts_dialog_has_valid_accounts (dialog))
492     empathy_account_widget_set_other_accounts_exist (
493         priv->setting_widget_object, TRUE);
494
495   priv->settings_widget =
496       empathy_account_widget_get_widget (priv->setting_widget_object);
497
498   g_signal_connect (priv->setting_widget_object, "account-created",
499         G_CALLBACK (empathy_account_dialog_account_created_cb), dialog);
500   g_signal_connect (priv->setting_widget_object, "cancelled",
501           G_CALLBACK (empathy_account_dialog_widget_cancelled_cb), dialog);
502
503   gtk_container_add (GTK_CONTAINER (priv->alignment_settings),
504       priv->settings_widget);
505   gtk_widget_show (priv->settings_widget);
506
507   icon_name = empathy_account_settings_get_icon_name (settings);
508
509   if (!gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
510           icon_name))
511     /* show the default icon; keep this in sync with the default
512      * one in empathy-accounts-dialog.ui.
513      */
514     icon_name = GTK_STOCK_CUT;
515
516   gtk_image_set_from_icon_name (GTK_IMAGE (priv->image_type),
517       icon_name, GTK_ICON_SIZE_DIALOG);
518   gtk_widget_set_tooltip_text (priv->image_type,
519       empathy_protocol_name_to_display_name
520       (empathy_account_settings_get_protocol (settings)));
521   gtk_widget_show (priv->image_type);
522
523   accounts_dialog_update_name_label (dialog,
524       empathy_account_settings_get_display_name (settings));
525
526   account = empathy_account_settings_get_account (settings);
527   accounts_dialog_update_status_infobar (dialog, account);
528 }
529
530 static void
531 account_dialog_settings_ready_cb (EmpathyAccountSettings *settings,
532     GParamSpec *spec,
533     EmpathyAccountsDialog *dialog)
534 {
535   if (empathy_account_settings_is_ready (settings))
536     account_dialog_create_settings_widget (dialog, settings);
537 }
538
539 static void
540 accounts_dialog_model_select_first (EmpathyAccountsDialog *dialog)
541 {
542   GtkTreeView      *view;
543   GtkTreeModel     *model;
544   GtkTreeSelection *selection;
545   GtkTreeIter       iter;
546   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
547
548   /* select first */
549   view = GTK_TREE_VIEW (priv->treeview);
550   model = gtk_tree_view_get_model (view);
551
552   if (gtk_tree_model_get_iter_first (model, &iter))
553     {
554       selection = gtk_tree_view_get_selection (view);
555       gtk_tree_selection_select_iter (selection, &iter);
556     }
557   else
558     {
559       accounts_dialog_update_settings (dialog, NULL);
560     }
561 }
562
563 static gboolean
564 accounts_dialog_has_pending_change (EmpathyAccountsDialog *dialog,
565     TpAccount **account)
566 {
567   GtkTreeIter iter;
568   GtkTreeModel *model;
569   GtkTreeSelection *selection;
570   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
571
572   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
573
574   if (gtk_tree_selection_get_selected (selection, &model, &iter))
575     gtk_tree_model_get (model, &iter, COL_ACCOUNT, account, -1);
576
577   return priv->setting_widget_object != NULL
578       && empathy_account_widget_contains_pending_changes (
579           priv->setting_widget_object);
580 }
581
582 static void
583 accounts_dialog_setup_ui_to_add_account (EmpathyAccountsDialog *dialog)
584 {
585   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
586   EmpathyAccountSettings *settings;
587   gchar *str;
588   const gchar *name, *display_name;
589   TpConnectionManager *cm;
590   TpConnectionManagerProtocol *proto;
591   gboolean is_gtalk, is_facebook;
592
593   cm = empathy_protocol_chooser_dup_selected (
594       EMPATHY_PROTOCOL_CHOOSER (priv->combobox_protocol), &proto, &is_gtalk,
595       &is_facebook);
596   if (cm == NULL)
597     return;
598
599   if (is_gtalk)
600     name = "gtalk";
601   else if (is_facebook)
602     name ="facebook";
603   else
604     name = proto->name;
605
606   display_name = empathy_protocol_name_to_display_name (name);
607   if (display_name == NULL)
608     display_name = proto->name;
609
610   /* Create account */
611   /* To translator: %s is the name of the protocol, such as "Google Talk" or
612    * "Yahoo!"
613    */
614   str = g_strdup_printf (_("New %s account"), display_name);
615   settings = empathy_account_settings_new (cm->name, proto->name, str);
616
617   g_free (str);
618
619   if (is_gtalk)
620     {
621       empathy_account_settings_set_icon_name_async (settings, "im-google-talk",
622           NULL, NULL);
623       /* We should not have to set the server but that may cause issue with
624        * buggy router. */
625       empathy_account_settings_set_string (settings, "server",
626           "talk.google.com");
627     }
628   else if (is_facebook)
629     {
630       empathy_account_settings_set_icon_name_async (settings, "im-facebook",
631           NULL, NULL);
632
633       empathy_account_settings_set_string (settings, "server",
634           "chat.facebook.com");
635     }
636
637   accounts_dialog_add (dialog, settings);
638   accounts_dialog_model_set_selected (dialog, settings);
639
640   gtk_widget_show_all (priv->hbox_protocol);
641
642   g_object_unref (settings);
643   g_object_unref (cm);
644 }
645
646 static void
647 accounts_dialog_protocol_changed_cb (GtkWidget *widget,
648     EmpathyAccountsDialog *dialog)
649 {
650   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
651   GtkTreeSelection *selection;
652   GtkTreeModel *model;
653   GtkTreeIter iter;
654   gboolean creating;
655   EmpathyAccountSettings *settings;
656   gchar *account = NULL, *password = NULL;
657
658   /* The "changed" signal is fired during the initiation of the
659    * EmpathyProtocolChooser while populating the widget. Such signals should
660    * be ignored so we check if we are actually creating a new account. */
661   if (priv->setting_widget_object == NULL)
662     return;
663
664   g_object_get (priv->setting_widget_object,
665       "creating-account", &creating, NULL);
666   if (!creating)
667     return;
668
669   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
670
671   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
672     return;
673
674   /* Save "account" and "password" parameters */
675   g_object_get (priv->setting_widget_object, "settings", &settings, NULL);
676
677   if (settings != NULL)
678     {
679       account = g_strdup (empathy_account_settings_get_string (settings,
680             "account"));
681       password = g_strdup (empathy_account_settings_get_string (settings,
682             "password"));
683       g_object_unref (settings);
684     }
685
686   /* We are creating a new widget to replace the current one, don't ask
687    * confirmation to the user. */
688   priv->force_change_row = TRUE;
689
690   /* We'll update the selection after we create the new account widgets;
691    * updating it right now causes problems for the # of accounts = zero case */
692   g_signal_handlers_block_by_func (selection,
693       accounts_dialog_model_selection_changed, dialog);
694
695   gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
696
697   g_signal_handlers_unblock_by_func (selection,
698       accounts_dialog_model_selection_changed, dialog);
699
700   accounts_dialog_setup_ui_to_add_account (dialog);
701
702   /* Restore "account" and "password" parameters in the new widget */
703   if (account != NULL)
704     {
705       empathy_account_widget_set_account_param (priv->setting_widget_object,
706           account);
707       g_free (account);
708     }
709
710   if (password != NULL)
711     {
712       empathy_account_widget_set_password_param (priv->setting_widget_object,
713           password);
714       g_free (password);
715     }
716 }
717
718 static void
719 accounts_dialog_show_question_dialog (EmpathyAccountsDialog *dialog,
720     const gchar *primary_text,
721     const gchar *secondary_text,
722     GCallback response_callback,
723     gpointer user_data,
724     const gchar *first_button_text,
725     ...)
726 {
727   va_list button_args;
728   GtkWidget *message_dialog;
729   const gchar *button_text;
730
731   message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
732       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
733       GTK_MESSAGE_QUESTION,
734       GTK_BUTTONS_NONE,
735       "%s", primary_text);
736
737   gtk_message_dialog_format_secondary_text (
738       GTK_MESSAGE_DIALOG (message_dialog), "%s", secondary_text);
739
740   va_start (button_args, first_button_text);
741   for (button_text = first_button_text;
742        button_text;
743        button_text = va_arg (button_args, const gchar *))
744     {
745       gint response_id;
746       response_id = va_arg (button_args, gint);
747
748       gtk_dialog_add_button (GTK_DIALOG (message_dialog), button_text,
749           response_id);
750     }
751   va_end (button_args);
752
753   g_signal_connect (message_dialog, "response", response_callback, user_data);
754
755   gtk_widget_show (message_dialog);
756 }
757
758 static void
759 accounts_dialog_add_pending_changes_response_cb (GtkDialog *message_dialog,
760   gint response_id,
761   gpointer *user_data)
762 {
763   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
764   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
765
766   gtk_widget_destroy (GTK_WIDGET (message_dialog));
767
768   if (response_id == GTK_RESPONSE_YES)
769     {
770       empathy_account_widget_discard_pending_changes (
771           priv->setting_widget_object);
772       accounts_dialog_setup_ui_to_add_account (dialog);
773     }
774 }
775
776 static gchar *
777 get_dialog_primary_text (TpAccount *account)
778 {
779   if (account != NULL)
780     {
781       /* Existing account */
782       return g_strdup_printf (PENDING_CHANGES_QUESTION_PRIMARY_TEXT,
783           tp_account_get_display_name (account));
784     }
785   else
786     {
787       /* Newly created account */
788       return g_strdup (UNSAVED_NEW_ACCOUNT_QUESTION_PRIMARY_TEXT);
789     }
790 }
791
792 static void
793 accounts_dialog_button_add_clicked_cb (GtkWidget *button,
794     EmpathyAccountsDialog *dialog)
795 {
796   TpAccount *account = NULL;
797   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
798
799   if (accounts_dialog_has_pending_change (dialog, &account))
800     {
801       gchar *question_dialog_primary_text = get_dialog_primary_text (account);
802
803       accounts_dialog_show_question_dialog (dialog,
804           question_dialog_primary_text,
805           _("You are about to create a new account, which will discard\n"
806               "your changes. Are you sure you want to proceed?"),
807           G_CALLBACK (accounts_dialog_add_pending_changes_response_cb),
808           dialog,
809           GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
810           GTK_STOCK_DISCARD, GTK_RESPONSE_YES, NULL);
811
812       g_free (question_dialog_primary_text);
813     }
814   else
815     {
816       accounts_dialog_setup_ui_to_add_account (dialog);
817       gtk_widget_set_sensitive (priv->treeview, FALSE);
818       gtk_widget_set_sensitive (priv->button_add, FALSE);
819       gtk_widget_set_sensitive (priv->button_remove, FALSE);
820       gtk_widget_set_sensitive (priv->button_import, FALSE);
821     }
822
823   if (account != NULL)
824     g_object_unref (account);
825 }
826
827 static void
828 accounts_dialog_update_settings (EmpathyAccountsDialog *dialog,
829     EmpathyAccountSettings *settings)
830 {
831   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
832
833   if (priv->settings_ready != NULL)
834     {
835       g_signal_handler_disconnect (priv->settings_ready,
836           priv->settings_ready_id);
837       priv->settings_ready = NULL;
838       priv->settings_ready_id = 0;
839     }
840
841   if (!settings)
842     {
843       GtkTreeView  *view;
844       GtkTreeModel *model;
845       GtkTreeSelection *selection;
846
847       view = GTK_TREE_VIEW (priv->treeview);
848       model = gtk_tree_view_get_model (view);
849       selection = gtk_tree_view_get_selection (view);
850
851       if (gtk_tree_model_iter_n_children (model, NULL) > 0)
852         {
853           /* We have configured accounts, select the first one if there
854            * is no other account selected already. */
855           if (!gtk_tree_selection_get_selected (selection, NULL, NULL))
856             accounts_dialog_model_select_first (dialog);
857
858           return;
859         }
860       if (empathy_connection_managers_get_cms_num (priv->cms) > 0)
861         {
862           /* We have no account configured but we have some
863            * profiles installed. The user obviously wants to add
864            * an account. Click on the Add button for him. */
865           accounts_dialog_button_add_clicked_cb (priv->button_add,
866               dialog);
867           return;
868         }
869
870       /* No account and no profile, warn the user */
871       gtk_widget_hide (priv->vbox_details);
872       gtk_widget_show (priv->frame_no_protocol);
873       gtk_widget_set_sensitive (priv->button_add, FALSE);
874       return;
875     }
876
877   /* We have an account selected, destroy old settings and create a new
878    * one for the account selected */
879   gtk_widget_hide (priv->frame_no_protocol);
880   gtk_widget_show (priv->vbox_details);
881   gtk_widget_hide (priv->hbox_protocol);
882
883   if (priv->settings_widget)
884     {
885       gtk_widget_destroy (priv->settings_widget);
886       priv->settings_widget = NULL;
887     }
888
889   if (empathy_account_settings_is_ready (settings))
890     {
891       account_dialog_create_settings_widget (dialog, settings);
892     }
893   else
894     {
895       priv->settings_ready = settings;
896       priv->settings_ready_id =
897         g_signal_connect (settings, "notify::ready",
898             G_CALLBACK (account_dialog_settings_ready_cb), dialog);
899     }
900
901 }
902
903 static void
904 accounts_dialog_name_editing_started_cb (GtkCellRenderer *renderer,
905     GtkCellEditable *editable,
906     gchar *path,
907     EmpathyAccountsDialog *dialog)
908 {
909   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
910
911   if (priv->connecting_id)
912     g_source_remove (priv->connecting_id);
913
914   DEBUG ("Editing account name started; stopping flashing");
915 }
916
917 static const gchar *
918 get_status_icon_for_account (EmpathyAccountsDialog *self,
919     TpAccount *account)
920 {
921   EmpathyAccountsDialogPriv *priv = GET_PRIV (self);
922   TpConnectionStatus status;
923   TpConnectionStatusReason reason;
924   TpConnectionPresenceType presence;
925
926   if (account == NULL)
927     return empathy_icon_name_for_presence (TP_CONNECTION_PRESENCE_TYPE_OFFLINE);
928
929   if (!tp_account_is_enabled (account))
930     return empathy_icon_name_for_presence (TP_CONNECTION_PRESENCE_TYPE_OFFLINE);
931
932   status = tp_account_get_connection_status (account, &reason);
933
934   if (status == TP_CONNECTION_STATUS_DISCONNECTED)
935     {
936       if (reason != TP_CONNECTION_STATUS_REASON_REQUESTED)
937         /* An error occured */
938         return GTK_STOCK_DIALOG_ERROR;
939
940       presence = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
941     }
942   else if (status == TP_CONNECTION_STATUS_CONNECTING)
943     {
944       /* Account is connecting. Display a blinking account alternating between
945        * the offline icon and the requested presence. */
946       if (priv->connecting_show)
947         presence = tp_account_get_requested_presence (account, NULL, NULL);
948       else
949         presence = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
950     }
951   else
952     {
953       /* status == TP_CONNECTION_STATUS_CONNECTED */
954       presence = tp_account_get_current_presence (account, NULL, NULL);
955
956       /* If presence is Unset (CM doesn't implement SimplePresence),
957        * display the 'available' icon.
958        * We also check Offline because of this MC5 bug: fd.o #26060 */
959       if (presence == TP_CONNECTION_PRESENCE_TYPE_OFFLINE ||
960           presence == TP_CONNECTION_PRESENCE_TYPE_UNSET)
961         presence = TP_CONNECTION_PRESENCE_TYPE_AVAILABLE;
962     }
963
964   return empathy_icon_name_for_presence (presence);
965 }
966
967 static void
968 accounts_dialog_model_status_pixbuf_data_func (GtkTreeViewColumn *tree_column,
969     GtkCellRenderer *cell,
970     GtkTreeModel *model,
971     GtkTreeIter *iter,
972     EmpathyAccountsDialog *dialog)
973 {
974   TpAccount *account;
975
976   gtk_tree_model_get (model, iter, COL_ACCOUNT, &account, -1);
977
978   g_object_set (cell,
979       "icon-name", get_status_icon_for_account (dialog, account),
980       NULL);
981
982   if (account != NULL)
983     g_object_unref (account);
984 }
985
986 static void
987 accounts_dialog_model_protocol_pixbuf_data_func (GtkTreeViewColumn *tree_column,
988     GtkCellRenderer *cell,
989     GtkTreeModel *model,
990     GtkTreeIter *iter,
991     EmpathyAccountsDialog *dialog)
992 {
993   EmpathyAccountSettings  *settings;
994   gchar              *icon_name;
995   GdkPixbuf          *pixbuf;
996   TpConnectionStatus  status;
997
998   gtk_tree_model_get (model, iter,
999       COL_STATUS, &status,
1000       COL_ACCOUNT_SETTINGS, &settings,
1001       -1);
1002
1003   icon_name = empathy_account_settings_get_icon_name (settings);
1004   pixbuf = empathy_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
1005
1006   g_object_set (cell,
1007       "visible", TRUE,
1008       "pixbuf", pixbuf,
1009       NULL);
1010
1011   g_object_unref (settings);
1012
1013   if (pixbuf)
1014     g_object_unref (pixbuf);
1015 }
1016
1017 static gboolean
1018 accounts_dialog_row_changed_foreach (GtkTreeModel *model,
1019     GtkTreePath *path,
1020     GtkTreeIter *iter,
1021     gpointer user_data)
1022 {
1023   gtk_tree_model_row_changed (model, path, iter);
1024
1025   return FALSE;
1026 }
1027
1028 static gboolean
1029 accounts_dialog_flash_connecting_cb (EmpathyAccountsDialog *dialog)
1030 {
1031   GtkTreeView  *view;
1032   GtkTreeModel *model;
1033   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1034
1035   priv->connecting_show = !priv->connecting_show;
1036
1037   view = GTK_TREE_VIEW (priv->treeview);
1038   model = gtk_tree_view_get_model (view);
1039
1040   gtk_tree_model_foreach (model, accounts_dialog_row_changed_foreach, NULL);
1041
1042   return TRUE;
1043 }
1044
1045 static void
1046 accounts_dialog_name_edited_cb (GtkCellRendererText *renderer,
1047     gchar *path,
1048     gchar *new_text,
1049     EmpathyAccountsDialog *dialog)
1050 {
1051   EmpathyAccountSettings    *settings;
1052   GtkTreeModel *model;
1053   GtkTreePath  *treepath;
1054   GtkTreeIter   iter;
1055   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1056   gboolean connecting;
1057
1058   empathy_account_manager_get_accounts_connected (&connecting);
1059
1060   if (connecting)
1061     {
1062       priv->connecting_id = g_timeout_add (FLASH_TIMEOUT,
1063           (GSourceFunc) accounts_dialog_flash_connecting_cb,
1064           dialog);
1065     }
1066
1067   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1068   treepath = gtk_tree_path_new_from_string (path);
1069   gtk_tree_model_get_iter (model, &iter, treepath);
1070   gtk_tree_model_get (model, &iter,
1071       COL_ACCOUNT_SETTINGS, &settings,
1072       -1);
1073   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1074       COL_NAME, new_text,
1075       -1);
1076   gtk_tree_path_free (treepath);
1077
1078   empathy_account_settings_set_display_name_async (settings, new_text,
1079       NULL, NULL);
1080   g_object_set (settings, "display-name-overridden", TRUE, NULL);
1081   g_object_unref (settings);
1082 }
1083
1084 static void
1085 accounts_dialog_delete_account_response_cb (GtkDialog *message_dialog,
1086   gint response_id,
1087   gpointer user_data)
1088 {
1089   TpAccount *account;
1090   GtkTreeModel *model;
1091   GtkTreeIter iter;
1092   GtkTreeSelection *selection;
1093   EmpathyAccountsDialog *account_dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
1094   EmpathyAccountsDialogPriv *priv = GET_PRIV (account_dialog);
1095
1096   if (response_id == GTK_RESPONSE_YES)
1097     {
1098       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1099
1100       if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1101         return;
1102
1103       gtk_tree_model_get (model, &iter, COL_ACCOUNT, &account, -1);
1104
1105       if (account != NULL)
1106         {
1107           tp_account_remove_async (account, NULL, NULL);
1108           g_object_unref (account);
1109           account = NULL;
1110         }
1111
1112       /* No need to call accounts_dialog_model_selection_changed while
1113        * removing as we are going to call accounts_dialog_model_select_first
1114        * right after which will update the selection. */
1115       g_signal_handlers_block_by_func (selection,
1116           accounts_dialog_model_selection_changed, account_dialog);
1117
1118       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
1119
1120       g_signal_handlers_unblock_by_func (selection,
1121           accounts_dialog_model_selection_changed, account_dialog);
1122
1123       accounts_dialog_model_select_first (account_dialog);
1124     }
1125
1126   gtk_widget_destroy (GTK_WIDGET (message_dialog));
1127 }
1128
1129 static void
1130 accounts_dialog_remove_account_iter (EmpathyAccountsDialog *dialog,
1131     GtkTreeIter *iter)
1132 {
1133   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1134   TpAccount *account;
1135   GtkTreeModel *model;
1136   gchar *question_dialog_primary_text;
1137
1138   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1139
1140   gtk_tree_model_get (model, iter, COL_ACCOUNT, &account, -1);
1141
1142   if (account == NULL || !tp_account_is_valid (account))
1143     {
1144       if (account != NULL)
1145         g_object_unref (account);
1146       gtk_list_store_remove (GTK_LIST_STORE (model), iter);
1147       accounts_dialog_model_select_first (dialog);
1148       return;
1149     }
1150
1151   question_dialog_primary_text = g_strdup_printf (
1152       _("Do you want to remove %s from your computer?"),
1153       tp_account_get_display_name (account));
1154
1155   accounts_dialog_show_question_dialog (dialog, question_dialog_primary_text,
1156       _("This will not remove your account on the server."),
1157       G_CALLBACK (accounts_dialog_delete_account_response_cb),
1158       dialog,
1159       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1160       GTK_STOCK_REMOVE, GTK_RESPONSE_YES, NULL);
1161
1162   g_free (question_dialog_primary_text);
1163   g_object_unref (account);
1164 }
1165
1166 static void
1167 accounts_dialog_button_remove_clicked_cb (GtkWidget *button,
1168     EmpathyAccountsDialog *dialog)
1169 {
1170   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1171   GtkTreeView  *view;
1172   GtkTreeModel *model;
1173   GtkTreeSelection *selection;
1174   GtkTreeIter iter;
1175
1176   view = GTK_TREE_VIEW (priv->treeview);
1177   model = gtk_tree_view_get_model (view);
1178   selection = gtk_tree_view_get_selection (view);
1179   if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
1180       return;
1181
1182   accounts_dialog_remove_account_iter (dialog, &iter);
1183 }
1184
1185 #ifdef HAVE_MEEGO
1186 static void
1187 accounts_dialog_view_delete_activated_cb (EmpathyCellRendererActivatable *cell,
1188     const gchar *path_string,
1189     EmpathyAccountsDialog *dialog)
1190 {
1191   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1192   GtkTreeModel *model;
1193   GtkTreeIter iter;
1194
1195   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1196   if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1197     return;
1198
1199   accounts_dialog_remove_account_iter (dialog, &iter);
1200 }
1201 #endif /* HAVE_MEEGO */
1202
1203 static void
1204 accounts_dialog_model_add_columns (EmpathyAccountsDialog *dialog)
1205 {
1206   GtkTreeView       *view;
1207   GtkTreeViewColumn *column;
1208   GtkCellRenderer   *cell;
1209   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1210
1211   view = GTK_TREE_VIEW (priv->treeview);
1212   gtk_tree_view_set_headers_visible (view, FALSE);
1213
1214   /* Account column */
1215   column = gtk_tree_view_column_new ();
1216   gtk_tree_view_column_set_expand (column, TRUE);
1217   gtk_tree_view_append_column (view, column);
1218
1219   /* Status icon renderer */
1220   cell = gtk_cell_renderer_pixbuf_new ();
1221   gtk_tree_view_column_pack_start (column, cell, FALSE);
1222   gtk_tree_view_column_set_cell_data_func (column, cell,
1223       (GtkTreeCellDataFunc)
1224       accounts_dialog_model_status_pixbuf_data_func,
1225       dialog,
1226       NULL);
1227
1228   /* Protocol icon renderer */
1229   cell = gtk_cell_renderer_pixbuf_new ();
1230   gtk_tree_view_column_pack_start (column, cell, FALSE);
1231   gtk_tree_view_column_set_cell_data_func (column, cell,
1232       (GtkTreeCellDataFunc)
1233       accounts_dialog_model_protocol_pixbuf_data_func,
1234       dialog,
1235       NULL);
1236
1237   /* Name renderer */
1238   cell = gtk_cell_renderer_text_new ();
1239   g_object_set (cell,
1240       "ellipsize", PANGO_ELLIPSIZE_END,
1241       "width-chars", 25,
1242       "editable", TRUE,
1243       NULL);
1244   gtk_tree_view_column_pack_start (column, cell, TRUE);
1245   gtk_tree_view_column_add_attribute (column, cell, "text", COL_NAME);
1246   g_signal_connect (cell, "edited",
1247       G_CALLBACK (accounts_dialog_name_edited_cb),
1248       dialog);
1249   g_signal_connect (cell, "editing-started",
1250       G_CALLBACK (accounts_dialog_name_editing_started_cb),
1251       dialog);
1252   g_object_set (cell, "ypad", 4, NULL);
1253
1254 #ifdef HAVE_MEEGO
1255   /* Delete column */
1256   cell = empathy_cell_renderer_activatable_new ();
1257   gtk_tree_view_column_pack_start (column, cell, FALSE);
1258   g_object_set (cell,
1259         "icon-name", GTK_STOCK_DELETE,
1260         "show-on-select", TRUE,
1261         NULL);
1262
1263   g_signal_connect (cell, "path-activated",
1264       G_CALLBACK (accounts_dialog_view_delete_activated_cb),
1265       dialog);
1266 #endif /* HAVE_MEEGO */
1267 }
1268
1269 static EmpathyAccountSettings *
1270 accounts_dialog_model_get_selected_settings (EmpathyAccountsDialog *dialog)
1271 {
1272   GtkTreeView      *view;
1273   GtkTreeModel     *model;
1274   GtkTreeSelection *selection;
1275   GtkTreeIter       iter;
1276   EmpathyAccountSettings   *settings;
1277   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1278
1279   view = GTK_TREE_VIEW (priv->treeview);
1280   selection = gtk_tree_view_get_selection (view);
1281
1282   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1283     return NULL;
1284
1285   gtk_tree_model_get (model, &iter,
1286       COL_ACCOUNT_SETTINGS, &settings, -1);
1287
1288   return settings;
1289 }
1290
1291 static void
1292 accounts_dialog_model_selection_changed (GtkTreeSelection *selection,
1293     EmpathyAccountsDialog *dialog)
1294 {
1295   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1296   EmpathyAccountSettings *settings;
1297   GtkTreeModel *model;
1298   GtkTreeIter   iter;
1299   gboolean      is_selection;
1300   gboolean creating = FALSE;
1301
1302   is_selection = gtk_tree_selection_get_selected (selection, &model, &iter);
1303
1304   settings = accounts_dialog_model_get_selected_settings (dialog);
1305   accounts_dialog_update_settings (dialog, settings);
1306
1307   if (settings != NULL)
1308     g_object_unref (settings);
1309
1310   if (priv->setting_widget_object != NULL)
1311     {
1312       g_object_get (priv->setting_widget_object,
1313           "creating-account", &creating, NULL);
1314     }
1315
1316   /* Update remove button sensitivity */
1317   gtk_widget_set_sensitive (priv->button_remove, is_selection && !creating &&
1318       !priv->loading);
1319 }
1320
1321 static void
1322 accounts_dialog_selection_change_response_cb (GtkDialog *message_dialog,
1323   gint response_id,
1324   gpointer *user_data)
1325 {
1326   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
1327   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1328
1329   gtk_widget_destroy (GTK_WIDGET (message_dialog));
1330
1331     if (response_id == GTK_RESPONSE_YES && priv->destination_row != NULL)
1332       {
1333         /* The user wants to lose unsaved changes to the currently selected
1334          * account and select another account. We discard the changes and
1335          * select the other account. */
1336         GtkTreePath *path;
1337         GtkTreeSelection *selection;
1338
1339         priv->force_change_row = TRUE;
1340         empathy_account_widget_discard_pending_changes (
1341             priv->setting_widget_object);
1342
1343         path = gtk_tree_row_reference_get_path (priv->destination_row);
1344         selection = gtk_tree_view_get_selection (
1345             GTK_TREE_VIEW (priv->treeview));
1346
1347         if (path != NULL)
1348           {
1349             /* This will trigger a call to
1350              * accounts_dialog_account_selection_change() */
1351             gtk_tree_selection_select_path (selection, path);
1352             gtk_tree_path_free (path);
1353           }
1354
1355         gtk_tree_row_reference_free (priv->destination_row);
1356       }
1357     else
1358       {
1359         priv->force_change_row = FALSE;
1360       }
1361 }
1362
1363 static gboolean
1364 accounts_dialog_account_selection_change (GtkTreeSelection *selection,
1365     GtkTreeModel *model,
1366     GtkTreePath *path,
1367     gboolean path_currently_selected,
1368     gpointer data)
1369 {
1370   TpAccount *account = NULL;
1371   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (data);
1372   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1373   gboolean ret;
1374
1375   if (priv->force_change_row)
1376     {
1377       /* We came back here because the user wants to discard changes to his
1378        * modified account. The changes have already been discarded so we
1379        * just change the selected row. */
1380       priv->force_change_row = FALSE;
1381       return TRUE;
1382     }
1383
1384   if (accounts_dialog_has_pending_change (dialog, &account))
1385     {
1386       /* The currently selected account has some unsaved changes. We ask
1387        * the user if he really wants to lose his changes and select another
1388        * account */
1389       gchar *question_dialog_primary_text = get_dialog_primary_text (account);
1390       priv->destination_row = gtk_tree_row_reference_new (model, path);
1391
1392       accounts_dialog_show_question_dialog (dialog,
1393           question_dialog_primary_text,
1394           _("You are about to select another account, which will discard\n"
1395               "your changes. Are you sure you want to proceed?"),
1396           G_CALLBACK (accounts_dialog_selection_change_response_cb),
1397           dialog,
1398           GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1399           GTK_STOCK_DISCARD, GTK_RESPONSE_YES, NULL);
1400
1401       g_free (question_dialog_primary_text);
1402       ret = FALSE;
1403     }
1404   else
1405     {
1406       ret = TRUE;
1407     }
1408
1409   if (account != NULL)
1410     g_object_unref (account);
1411
1412   return ret;
1413 }
1414
1415 static void
1416 accounts_dialog_model_setup (EmpathyAccountsDialog *dialog)
1417 {
1418   GtkListStore     *store;
1419   GtkTreeSelection *selection;
1420   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1421
1422   store = gtk_list_store_new (COL_COUNT,
1423       G_TYPE_STRING,         /* name */
1424       G_TYPE_UINT,           /* status */
1425       TP_TYPE_ACCOUNT,   /* account */
1426       EMPATHY_TYPE_ACCOUNT_SETTINGS); /* settings */
1427
1428   gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
1429       GTK_TREE_MODEL (store));
1430
1431   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1432   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1433   gtk_tree_selection_set_select_function (selection,
1434       accounts_dialog_account_selection_change, dialog, NULL);
1435
1436   g_signal_connect (selection, "changed",
1437       G_CALLBACK (accounts_dialog_model_selection_changed),
1438       dialog);
1439
1440   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
1441       COL_NAME, GTK_SORT_ASCENDING);
1442
1443   accounts_dialog_model_add_columns (dialog);
1444
1445   g_object_unref (store);
1446 }
1447
1448 static gboolean
1449 accounts_dialog_get_settings_iter (EmpathyAccountsDialog *dialog,
1450     EmpathyAccountSettings *settings,
1451     GtkTreeIter *iter)
1452 {
1453   GtkTreeView      *view;
1454   GtkTreeSelection *selection;
1455   GtkTreeModel     *model;
1456   gboolean          ok;
1457   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1458
1459   /* Update the status in the model */
1460   view = GTK_TREE_VIEW (priv->treeview);
1461   selection = gtk_tree_view_get_selection (view);
1462   model = gtk_tree_view_get_model (view);
1463
1464   for (ok = gtk_tree_model_get_iter_first (model, iter);
1465        ok;
1466        ok = gtk_tree_model_iter_next (model, iter))
1467     {
1468       EmpathyAccountSettings *this_settings;
1469       gboolean   equal;
1470
1471       gtk_tree_model_get (model, iter,
1472           COL_ACCOUNT_SETTINGS, &this_settings,
1473           -1);
1474
1475       equal = (this_settings == settings);
1476       g_object_unref (this_settings);
1477
1478       if (equal)
1479         return TRUE;
1480     }
1481
1482   return FALSE;
1483 }
1484
1485 static gboolean
1486 accounts_dialog_get_account_iter (EmpathyAccountsDialog *dialog,
1487     TpAccount *account,
1488     GtkTreeIter *iter)
1489 {
1490   GtkTreeView      *view;
1491   GtkTreeSelection *selection;
1492   GtkTreeModel     *model;
1493   gboolean          ok;
1494   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1495
1496   /* Update the status in the model */
1497   view = GTK_TREE_VIEW (priv->treeview);
1498   selection = gtk_tree_view_get_selection (view);
1499   model = gtk_tree_view_get_model (view);
1500
1501   for (ok = gtk_tree_model_get_iter_first (model, iter);
1502        ok;
1503        ok = gtk_tree_model_iter_next (model, iter))
1504     {
1505       EmpathyAccountSettings *settings;
1506       gboolean   equal;
1507
1508       gtk_tree_model_get (model, iter,
1509           COL_ACCOUNT_SETTINGS, &settings,
1510           -1);
1511
1512       equal = empathy_account_settings_has_account (settings, account);
1513       g_object_unref (settings);
1514
1515       if (equal)
1516         return TRUE;
1517     }
1518
1519   return FALSE;
1520 }
1521
1522 static void
1523 accounts_dialog_model_set_selected (EmpathyAccountsDialog *dialog,
1524     EmpathyAccountSettings *settings)
1525 {
1526   GtkTreeSelection *selection;
1527   GtkTreeIter       iter;
1528   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1529
1530   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1531   if (accounts_dialog_get_settings_iter (dialog, settings, &iter))
1532     gtk_tree_selection_select_iter (selection, &iter);
1533 }
1534 static void
1535 accounts_dialog_add (EmpathyAccountsDialog *dialog,
1536     EmpathyAccountSettings *settings)
1537 {
1538   GtkTreeModel       *model;
1539   GtkTreeIter         iter;
1540   const gchar        *name;
1541   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1542
1543   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1544   name = empathy_account_settings_get_display_name (settings);
1545
1546   gtk_list_store_append (GTK_LIST_STORE (model), &iter);
1547
1548   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1549       COL_NAME, name,
1550       COL_STATUS, TP_CONNECTION_STATUS_DISCONNECTED,
1551       COL_ACCOUNT_SETTINGS, settings,
1552       -1);
1553 }
1554
1555 static void
1556 accounts_dialog_connection_changed_cb (TpAccount *account,
1557     guint old_status,
1558     guint current,
1559     guint reason,
1560     gchar *dbus_error_name,
1561     GHashTable *details,
1562     EmpathyAccountsDialog *dialog)
1563 {
1564   GtkTreeModel *model;
1565   GtkTreeIter   iter;
1566   gboolean      found;
1567   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1568
1569   /* Update the status-infobar in the details view */
1570   accounts_dialog_update_status_infobar (dialog, account);
1571
1572   /* Update the status in the model */
1573   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1574
1575   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1576     {
1577       GtkTreePath *path;
1578
1579       gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1580           COL_STATUS, current,
1581           -1);
1582
1583       path = gtk_tree_model_get_path (model, &iter);
1584       gtk_tree_model_row_changed (model, path, &iter);
1585       gtk_tree_path_free (path);
1586     }
1587
1588   empathy_account_manager_get_accounts_connected (&found);
1589
1590   if (!found && priv->connecting_id)
1591     {
1592       g_source_remove (priv->connecting_id);
1593       priv->connecting_id = 0;
1594     }
1595
1596   if (found && !priv->connecting_id)
1597     priv->connecting_id = g_timeout_add (FLASH_TIMEOUT,
1598         (GSourceFunc) accounts_dialog_flash_connecting_cb,
1599         dialog);
1600 }
1601
1602 static void
1603 update_account_in_treeview (EmpathyAccountsDialog *self,
1604     TpAccount *account)
1605 {
1606   EmpathyAccountsDialogPriv *priv = GET_PRIV (self);
1607   GtkTreeIter iter;
1608   GtkTreeModel *model;
1609
1610   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1611   if (accounts_dialog_get_account_iter (self, account, &iter))
1612     {
1613       GtkTreePath *path;
1614
1615       path = gtk_tree_model_get_path (model, &iter);
1616       gtk_tree_model_row_changed (model, path, &iter);
1617       gtk_tree_path_free (path);
1618     }
1619 }
1620
1621 static void
1622 accounts_dialog_presence_changed_cb (TpAccount *account,
1623     guint presence,
1624     gchar *status,
1625     gchar *status_message,
1626     EmpathyAccountsDialog *dialog)
1627 {
1628   /* Update the status-infobar in the details view */
1629   accounts_dialog_update_status_infobar (dialog, account);
1630
1631   update_account_in_treeview (dialog, account);
1632 }
1633
1634 static void
1635 accounts_dialog_account_display_name_changed_cb (TpAccount *account,
1636   GParamSpec *pspec,
1637   gpointer user_data)
1638 {
1639   const gchar *display_name;
1640   GtkTreeIter iter;
1641   GtkTreeModel *model;
1642   EmpathyAccountSettings *settings;
1643   TpAccount *selected_account;
1644   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
1645   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1646
1647   display_name = tp_account_get_display_name (account);
1648   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1649   settings = accounts_dialog_model_get_selected_settings (dialog);
1650   if (settings == NULL)
1651     return;
1652
1653   selected_account = empathy_account_settings_get_account (settings);
1654
1655   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1656     {
1657       gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1658           COL_NAME, display_name,
1659           -1);
1660     }
1661
1662   if (selected_account == account)
1663     accounts_dialog_update_name_label (dialog, display_name);
1664
1665   g_object_unref (settings);
1666 }
1667
1668 static void
1669 accounts_dialog_add_account (EmpathyAccountsDialog *dialog,
1670     TpAccount *account)
1671 {
1672   EmpathyAccountSettings *settings;
1673   GtkTreeModel       *model;
1674   GtkTreeIter         iter;
1675   TpConnectionStatus  status;
1676   const gchar        *name;
1677   gboolean            enabled;
1678   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1679
1680   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1681   status = tp_account_get_connection_status (account, NULL);
1682   name = tp_account_get_display_name (account);
1683   enabled = tp_account_is_enabled (account);
1684
1685   settings = empathy_account_settings_new_for_account (account);
1686
1687   if (!accounts_dialog_get_account_iter (dialog, account, &iter))
1688     gtk_list_store_append (GTK_LIST_STORE (model), &iter);
1689
1690   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1691       COL_NAME, name,
1692       COL_STATUS, status,
1693       COL_ACCOUNT, account,
1694       COL_ACCOUNT_SETTINGS, settings,
1695       -1);
1696
1697   accounts_dialog_connection_changed_cb (account,
1698       0,
1699       status,
1700       TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED,
1701       NULL,
1702       NULL,
1703       dialog);
1704
1705   tp_g_signal_connect_object (account, "notify::display-name",
1706       G_CALLBACK (accounts_dialog_account_display_name_changed_cb),
1707       dialog, 0);
1708
1709   tp_g_signal_connect_object (account, "status-changed",
1710       G_CALLBACK (accounts_dialog_connection_changed_cb), dialog, 0);
1711   tp_g_signal_connect_object (account, "presence-changed",
1712       G_CALLBACK (accounts_dialog_presence_changed_cb), dialog, 0);
1713
1714   g_object_unref (settings);
1715 }
1716
1717 static void
1718 account_prepare_cb (GObject *source_object,
1719     GAsyncResult *result,
1720     gpointer user_data)
1721 {
1722   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
1723   TpAccount *account = TP_ACCOUNT (source_object);
1724   GError *error = NULL;
1725
1726   if (!tp_account_prepare_finish (account, result, &error))
1727     {
1728       DEBUG ("Failed to prepare account: %s", error->message);
1729       g_error_free (error);
1730       return;
1731     }
1732
1733   accounts_dialog_add_account (dialog, account);
1734 }
1735
1736 static void
1737 accounts_dialog_account_validity_changed_cb (TpAccountManager *manager,
1738     TpAccount *account,
1739     gboolean valid,
1740     EmpathyAccountsDialog *dialog)
1741 {
1742   tp_account_prepare_async (account, NULL, account_prepare_cb, dialog);
1743 }
1744
1745 static void
1746 accounts_dialog_accounts_model_row_inserted_cb (GtkTreeModel *model,
1747     GtkTreePath *path,
1748     GtkTreeIter *iter,
1749     EmpathyAccountsDialog *dialog)
1750 {
1751   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1752
1753   if (priv->setting_widget_object != NULL &&
1754       accounts_dialog_has_valid_accounts (dialog))
1755     {
1756       empathy_account_widget_set_other_accounts_exist (
1757           priv->setting_widget_object, TRUE);
1758     }
1759 }
1760
1761 static void
1762 accounts_dialog_accounts_model_row_deleted_cb (GtkTreeModel *model,
1763     GtkTreePath *path,
1764     EmpathyAccountsDialog *dialog)
1765 {
1766   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1767
1768   if (priv->setting_widget_object != NULL &&
1769       !accounts_dialog_has_valid_accounts (dialog))
1770     {
1771       empathy_account_widget_set_other_accounts_exist (
1772           priv->setting_widget_object, FALSE);
1773     }
1774 }
1775
1776 static void
1777 accounts_dialog_account_removed_cb (TpAccountManager *manager,
1778     TpAccount *account,
1779     EmpathyAccountsDialog *dialog)
1780 {
1781   GtkTreeIter iter;
1782   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1783
1784   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1785     {
1786       gtk_list_store_remove (GTK_LIST_STORE (
1787           gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview))), &iter);
1788     }
1789 }
1790
1791 static void
1792 enable_or_disable_account (EmpathyAccountsDialog *dialog,
1793     TpAccount *account,
1794     gboolean enabled)
1795 {
1796   GtkTreeModel *model;
1797   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1798
1799   /* Update the status in the model */
1800   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1801
1802   /* Update the status-infobar in the details view */
1803   accounts_dialog_update_status_infobar (dialog, account);
1804
1805   DEBUG ("Account %s is now %s",
1806       tp_account_get_display_name (account),
1807       enabled ? "enabled" : "disabled");
1808 }
1809
1810 static void
1811 accounts_dialog_account_disabled_cb (TpAccountManager *manager,
1812     TpAccount *account,
1813     EmpathyAccountsDialog *dialog)
1814 {
1815   enable_or_disable_account (dialog, account, FALSE);
1816   update_account_in_treeview (dialog, account);
1817 }
1818
1819 static void
1820 accounts_dialog_account_enabled_cb (TpAccountManager *manager,
1821     TpAccount *account,
1822     EmpathyAccountsDialog *dialog)
1823 {
1824   enable_or_disable_account (dialog, account, TRUE);
1825 }
1826
1827 static void
1828 accounts_dialog_button_import_clicked_cb (GtkWidget *button,
1829     EmpathyAccountsDialog *dialog)
1830 {
1831   GtkWidget *import_dialog;
1832
1833   import_dialog = empathy_import_dialog_new (GTK_WINDOW (dialog),
1834       FALSE);
1835   gtk_widget_show (import_dialog);
1836 }
1837
1838 static void
1839 accounts_dialog_close_response_cb (GtkDialog *message_dialog,
1840   gint response_id,
1841   gpointer user_data)
1842 {
1843   GtkWidget *account_dialog = GTK_WIDGET (user_data);
1844
1845   gtk_widget_destroy (GTK_WIDGET (message_dialog));
1846
1847   if (response_id == GTK_RESPONSE_YES)
1848     gtk_widget_destroy (account_dialog);
1849 }
1850
1851 static gboolean
1852 accounts_dialog_delete_event_cb (GtkWidget *widget,
1853     GdkEvent *event,
1854     EmpathyAccountsDialog *dialog)
1855 {
1856   /* we maunally handle responses to delete events */
1857   return TRUE;
1858 }
1859
1860 static void
1861 accounts_dialog_set_selected_account (EmpathyAccountsDialog *dialog,
1862     TpAccount *account)
1863 {
1864   GtkTreeSelection *selection;
1865   GtkTreeIter       iter;
1866   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1867
1868   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1869   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1870     gtk_tree_selection_select_iter (selection, &iter);
1871 }
1872
1873 static void
1874 finished_loading (EmpathyAccountsDialog *self)
1875 {
1876   EmpathyAccountsDialogPriv *priv = GET_PRIV (self);
1877   GtkTreeSelection *selection;
1878   gboolean has_selected;
1879
1880   priv->loading = FALSE;
1881
1882   gtk_widget_set_sensitive (priv->button_add, TRUE);
1883   gtk_widget_set_sensitive (priv->button_import, TRUE);
1884   gtk_widget_set_sensitive (priv->treeview, TRUE);
1885
1886   /* Sensitive the remove button if there is an account selected */
1887   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1888   has_selected = gtk_tree_selection_get_selected (selection, NULL, NULL);
1889   gtk_widget_set_sensitive (priv->button_remove, has_selected);
1890
1891   gtk_spinner_stop (GTK_SPINNER (priv->spinner));
1892   gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook_account),
1893       NOTEBOOK_PAGE_ACCOUNT);
1894 }
1895
1896 static void
1897 accounts_dialog_cms_prepare_cb (GObject *source,
1898     GAsyncResult *result,
1899     gpointer user_data)
1900 {
1901   EmpathyConnectionManagers *cms = EMPATHY_CONNECTION_MANAGERS (source);
1902   EmpathyAccountsDialog *dialog = user_data;
1903   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1904
1905   if (!empathy_connection_managers_prepare_finish (cms, result, NULL))
1906     goto out;
1907
1908   accounts_dialog_update_settings (dialog, NULL);
1909
1910   if (priv->initial_selection != NULL)
1911     {
1912       accounts_dialog_set_selected_account (dialog, priv->initial_selection);
1913       g_object_unref (priv->initial_selection);
1914       priv->initial_selection = NULL;
1915     }
1916
1917 out:
1918   finished_loading (dialog);
1919 }
1920
1921 static void
1922 accounts_dialog_accounts_setup (EmpathyAccountsDialog *dialog)
1923 {
1924   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1925   GList *accounts, *l;
1926
1927   g_signal_connect (priv->account_manager, "account-validity-changed",
1928       G_CALLBACK (accounts_dialog_account_validity_changed_cb),
1929       dialog);
1930   g_signal_connect (priv->account_manager, "account-removed",
1931       G_CALLBACK (accounts_dialog_account_removed_cb),
1932       dialog);
1933   g_signal_connect (priv->account_manager, "account-enabled",
1934       G_CALLBACK (accounts_dialog_account_enabled_cb),
1935       dialog);
1936   g_signal_connect (priv->account_manager, "account-disabled",
1937       G_CALLBACK (accounts_dialog_account_disabled_cb),
1938       dialog);
1939
1940   /* Add existing accounts */
1941   accounts = tp_account_manager_get_valid_accounts (priv->account_manager);
1942   for (l = accounts; l; l = l->next)
1943     {
1944       accounts_dialog_add_account (dialog, l->data);
1945     }
1946   g_list_free (accounts);
1947
1948   priv->cms = empathy_connection_managers_dup_singleton ();
1949
1950   empathy_connection_managers_prepare_async (priv->cms,
1951       accounts_dialog_cms_prepare_cb, dialog);
1952
1953   accounts_dialog_model_select_first (dialog);
1954 }
1955
1956 static void
1957 accounts_dialog_manager_ready_cb (GObject *source_object,
1958     GAsyncResult *result,
1959     gpointer user_data)
1960 {
1961   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
1962   GError *error = NULL;
1963
1964   if (!tp_account_manager_prepare_finish (manager, result, &error))
1965     {
1966       DEBUG ("Failed to prepare account manager: %s", error->message);
1967       g_error_free (error);
1968       return;
1969     }
1970
1971   accounts_dialog_accounts_setup (user_data);
1972 }
1973
1974 static void
1975 dialog_response_cb (GtkWidget *widget,
1976     gint response_id,
1977     gpointer user_data)
1978 {
1979   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (widget);
1980
1981   if (response_id == GTK_RESPONSE_HELP)
1982     {
1983       empathy_url_show (widget, "ghelp:empathy?accounts-window");
1984     }
1985   else if (response_id == GTK_RESPONSE_CLOSE ||
1986       response_id == GTK_RESPONSE_DELETE_EVENT)
1987     {
1988       TpAccount *account = NULL;
1989
1990       if (accounts_dialog_has_pending_change (dialog, &account))
1991         {
1992           gchar *question_dialog_primary_text = get_dialog_primary_text (
1993               account);
1994
1995           accounts_dialog_show_question_dialog (dialog,
1996               question_dialog_primary_text,
1997               _("You are about to close the window, which will discard\n"
1998                   "your changes. Are you sure you want to proceed?"),
1999               G_CALLBACK (accounts_dialog_close_response_cb),
2000               widget,
2001               GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2002               GTK_STOCK_DISCARD, GTK_RESPONSE_YES, NULL);
2003
2004           g_free (question_dialog_primary_text);
2005         }
2006       else
2007         {
2008           gtk_widget_destroy (widget);
2009         }
2010
2011       if (account != NULL)
2012         g_object_unref (account);
2013     }
2014 }
2015
2016 static void
2017 accounts_dialog_build_ui (EmpathyAccountsDialog *dialog)
2018 {
2019   GtkWidget *top_hbox;
2020   GtkBuilder                   *gui;
2021   gchar                        *filename;
2022   EmpathyAccountsDialogPriv    *priv = GET_PRIV (dialog);
2023   GtkWidget                    *content_area;
2024   GtkWidget *action_area, *vbox, *hbox, *align;
2025   GtkWidget *alig;
2026
2027   filename = empathy_file_lookup ("empathy-accounts-dialog.ui", "src");
2028
2029   gui = empathy_builder_get_file (filename,
2030       "accounts_dialog_hbox", &top_hbox,
2031       "vbox_details", &priv->vbox_details,
2032       "frame_no_protocol", &priv->frame_no_protocol,
2033       "alignment_settings", &priv->alignment_settings,
2034       "alignment_infobar", &priv->alignment_infobar,
2035       "treeview", &priv->treeview,
2036       "button_add", &priv->button_add,
2037       "button_remove", &priv->button_remove,
2038       "button_import", &priv->button_import,
2039       "hbox_protocol", &priv->hbox_protocol,
2040       "notebook_account", &priv->notebook_account,
2041       "alignment_loading", &alig,
2042       NULL);
2043   g_free (filename);
2044
2045   gtk_widget_set_no_show_all (priv->frame_no_protocol, TRUE);
2046
2047   empathy_builder_connect (gui, dialog,
2048       "button_add", "clicked", accounts_dialog_button_add_clicked_cb,
2049       "button_remove", "clicked", accounts_dialog_button_remove_clicked_cb,
2050       "button_import", "clicked", accounts_dialog_button_import_clicked_cb,
2051       NULL);
2052
2053   content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
2054
2055   gtk_container_add (GTK_CONTAINER (content_area), top_hbox);
2056
2057   g_object_unref (gui);
2058
2059   action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
2060
2061 #ifdef HAVE_MEEGO
2062   gtk_widget_hide (action_area);
2063   gtk_widget_hide (priv->button_remove);
2064 #endif /* HAVE_MEEGO */
2065
2066   /* Display loading page */
2067   priv->loading = TRUE;
2068
2069   priv->spinner = gtk_spinner_new ();
2070
2071   gtk_spinner_start (GTK_SPINNER (priv->spinner));
2072   gtk_widget_show (priv->spinner);
2073
2074   gtk_container_add (GTK_CONTAINER (alig), priv->spinner);
2075
2076   gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook_account),
2077       NOTEBOOK_PAGE_LOADING);
2078
2079   /* Remove button is insensitive until we have a selected account */
2080   gtk_widget_set_sensitive (priv->button_remove, FALSE);
2081
2082   /* Add and Import buttons and treeview are insensitive while the dialog
2083    * is loading */
2084   gtk_widget_set_sensitive (priv->button_add, FALSE);
2085   gtk_widget_set_sensitive (priv->button_import, FALSE);
2086   gtk_widget_set_sensitive (priv->treeview, FALSE);
2087
2088   priv->combobox_protocol = empathy_protocol_chooser_new ();
2089   gtk_box_pack_start (GTK_BOX (priv->hbox_protocol), priv->combobox_protocol,
2090       TRUE, TRUE, 0);
2091   g_signal_connect (priv->combobox_protocol, "changed",
2092       G_CALLBACK (accounts_dialog_protocol_changed_cb),
2093       dialog);
2094
2095   if (priv->parent_window)
2096     gtk_window_set_transient_for (GTK_WINDOW (dialog),
2097         priv->parent_window);
2098
2099   priv->infobar = gtk_info_bar_new ();
2100   gtk_container_add (GTK_CONTAINER (priv->alignment_infobar),
2101       priv->infobar);
2102   gtk_widget_show (priv->infobar);
2103
2104   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (priv->infobar));
2105
2106   priv->image_type = gtk_image_new_from_stock (GTK_STOCK_CUT,
2107       GTK_ICON_SIZE_DIALOG);
2108   gtk_misc_set_alignment (GTK_MISC (priv->image_type), 0.0, 0.5);
2109   gtk_box_pack_start (GTK_BOX (content_area), priv->image_type, FALSE, FALSE, 0);
2110   gtk_widget_show (priv->image_type);
2111
2112   vbox = gtk_vbox_new (FALSE, 6);
2113   gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0);
2114   gtk_widget_show (vbox);
2115
2116   /* first row */
2117   align = gtk_alignment_new (0.5, 0.0, 0.0, 0.0);
2118   gtk_widget_show (align);
2119
2120   priv->label_name = gtk_label_new (NULL);
2121   gtk_container_add (GTK_CONTAINER (align), priv->label_name);
2122   gtk_widget_show (priv->label_name);
2123
2124   gtk_box_pack_start (GTK_BOX (vbox), align, TRUE, TRUE, 0);
2125
2126   /* second row */
2127   align = gtk_alignment_new (0.5, 0.0, 0.0, 0.0);
2128   gtk_widget_show (align);
2129   hbox = gtk_hbox_new (FALSE, 6);
2130   gtk_widget_show (hbox);
2131   gtk_container_add (GTK_CONTAINER (align), hbox);
2132
2133   gtk_box_pack_start (GTK_BOX (vbox), align, TRUE, TRUE, 0);
2134
2135   /* set up spinner */
2136   priv->throbber = gtk_spinner_new ();
2137
2138   priv->image_status = gtk_image_new_from_icon_name (
2139             empathy_icon_name_for_presence (
2140             TP_CONNECTION_PRESENCE_TYPE_OFFLINE), GTK_ICON_SIZE_SMALL_TOOLBAR);
2141
2142   priv->label_status = gtk_label_new (NULL);
2143   gtk_label_set_line_wrap (GTK_LABEL (priv->label_status), TRUE);
2144   gtk_widget_show (priv->label_status);
2145
2146   gtk_box_pack_start (GTK_BOX (hbox), priv->throbber, FALSE, FALSE, 0);
2147   gtk_box_pack_start (GTK_BOX (hbox), priv->image_status, FALSE, FALSE, 3);
2148   gtk_box_pack_start (GTK_BOX (hbox), priv->label_status, TRUE, TRUE, 0);
2149
2150   /* Tweak the dialog */
2151   gtk_window_set_title (GTK_WINDOW (dialog), _("Messaging and VoIP Accounts"));
2152   gtk_window_set_role (GTK_WINDOW (dialog), "accounts");
2153
2154   gtk_window_set_default_size (GTK_WINDOW (dialog), 640, 450);
2155
2156   gtk_window_set_type_hint (GTK_WINDOW (dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
2157
2158   /* FIXME: Remove this once we unconditionally depend on GTK+ 3 */
2159 #ifndef HAVE_GTK3
2160   gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
2161 #endif
2162
2163   /* add dialog buttons */
2164   gtk_button_box_set_layout (GTK_BUTTON_BOX (action_area), GTK_BUTTONBOX_END);
2165
2166   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
2167       GTK_STOCK_HELP, GTK_RESPONSE_HELP,
2168       GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
2169       NULL);
2170
2171   g_signal_connect (dialog, "response",
2172       G_CALLBACK (dialog_response_cb), dialog);
2173
2174   g_signal_connect (dialog, "delete-event",
2175       G_CALLBACK (accounts_dialog_delete_event_cb), dialog);
2176 }
2177
2178 static void
2179 do_dispose (GObject *obj)
2180 {
2181   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (obj);
2182   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
2183
2184   if (priv->connecting_id != 0)
2185     {
2186       g_source_remove (priv->connecting_id);
2187       priv->connecting_id = 0;
2188     }
2189
2190   if (priv->account_manager != NULL)
2191     {
2192       g_object_unref (priv->account_manager);
2193       priv->account_manager = NULL;
2194     }
2195
2196   if (priv->cms != NULL)
2197     {
2198       g_object_unref (priv->cms);
2199       priv->cms = NULL;
2200     }
2201
2202   if (priv->connectivity)
2203     {
2204       g_object_unref (priv->connectivity);
2205       priv->connectivity = NULL;
2206     }
2207
2208   if (priv->initial_selection != NULL)
2209     {
2210       g_object_unref (priv->initial_selection);
2211       priv->initial_selection = NULL;
2212     }
2213
2214   G_OBJECT_CLASS (empathy_accounts_dialog_parent_class)->dispose (obj);
2215 }
2216
2217 static void
2218 do_get_property (GObject *object,
2219     guint property_id,
2220     GValue *value,
2221     GParamSpec *pspec)
2222 {
2223   EmpathyAccountsDialogPriv *priv = GET_PRIV (object);
2224
2225   switch (property_id)
2226     {
2227     case PROP_PARENT:
2228       g_value_set_object (value, priv->parent_window);
2229       break;
2230     default:
2231       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2232     }
2233 }
2234
2235 static void
2236 do_set_property (GObject *object,
2237     guint property_id,
2238     const GValue *value,
2239     GParamSpec *pspec)
2240 {
2241   EmpathyAccountsDialogPriv *priv = GET_PRIV (object);
2242
2243   switch (property_id)
2244     {
2245     case PROP_PARENT:
2246       priv->parent_window = g_value_get_object (value);
2247       break;
2248     default:
2249       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
2250     }
2251 }
2252
2253 static void
2254 do_constructed (GObject *object)
2255 {
2256   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (object);
2257   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
2258   GtkTreeModel *model;
2259   GSettings *gsettings = g_settings_new (EMPATHY_PREFS_SCHEMA);
2260
2261   accounts_dialog_build_ui (dialog);
2262   accounts_dialog_model_setup (dialog);
2263
2264   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
2265   g_signal_connect (model, "row-inserted",
2266       (GCallback) accounts_dialog_accounts_model_row_inserted_cb, dialog);
2267   g_signal_connect (model, "row-deleted",
2268       (GCallback) accounts_dialog_accounts_model_row_deleted_cb, dialog);
2269
2270   /* Set up signalling */
2271   priv->account_manager = tp_account_manager_dup ();
2272
2273   tp_account_manager_prepare_async (priv->account_manager, NULL,
2274       accounts_dialog_manager_ready_cb, dialog);
2275
2276   if (empathy_import_accounts_to_import ())
2277     {
2278       gtk_widget_show (priv->button_import);
2279
2280       if (!g_settings_get_boolean (gsettings, EMPATHY_PREFS_IMPORT_ASKED))
2281         {
2282           GtkWidget *import_dialog;
2283
2284           g_settings_set_boolean (gsettings, EMPATHY_PREFS_IMPORT_ASKED, TRUE);
2285           import_dialog = empathy_import_dialog_new (GTK_WINDOW (dialog),
2286               FALSE);
2287           gtk_widget_show (import_dialog);
2288         }
2289     }
2290
2291   priv->connectivity = empathy_connectivity_dup_singleton ();
2292
2293   g_object_unref (gsettings);
2294 }
2295
2296 static void
2297 empathy_accounts_dialog_class_init (EmpathyAccountsDialogClass *klass)
2298 {
2299   GObjectClass *oclass = G_OBJECT_CLASS (klass);
2300   GParamSpec *param_spec;
2301
2302   oclass->dispose = do_dispose;
2303   oclass->constructed = do_constructed;
2304   oclass->set_property = do_set_property;
2305   oclass->get_property = do_get_property;
2306
2307   param_spec = g_param_spec_object ("parent",
2308       "parent", "The parent window",
2309       GTK_TYPE_WINDOW,
2310       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
2311   g_object_class_install_property (oclass, PROP_PARENT, param_spec);
2312
2313   g_type_class_add_private (klass, sizeof (EmpathyAccountsDialogPriv));
2314 }
2315
2316 static void
2317 empathy_accounts_dialog_init (EmpathyAccountsDialog *dialog)
2318 {
2319   EmpathyAccountsDialogPriv *priv;
2320
2321   priv = G_TYPE_INSTANCE_GET_PRIVATE ((dialog),
2322       EMPATHY_TYPE_ACCOUNTS_DIALOG,
2323       EmpathyAccountsDialogPriv);
2324   dialog->priv = priv;
2325 }
2326
2327 /* public methods */
2328
2329 GtkWidget *
2330 empathy_accounts_dialog_show (GtkWindow *parent,
2331     TpAccount *selected_account)
2332 {
2333   EmpathyAccountsDialog *dialog;
2334   EmpathyAccountsDialogPriv *priv;
2335
2336   dialog = g_object_new (EMPATHY_TYPE_ACCOUNTS_DIALOG,
2337       "parent", parent, NULL);
2338
2339   priv = GET_PRIV (dialog);
2340
2341   if (selected_account)
2342     {
2343       if (priv->cms != NULL && empathy_connection_managers_is_ready (priv->cms))
2344         accounts_dialog_set_selected_account (dialog, selected_account);
2345       else
2346         /* save the selection to set it later when the cms
2347          * becomes ready.
2348          */
2349         priv->initial_selection = g_object_ref (selected_account);
2350     }
2351
2352   gtk_window_present (GTK_WINDOW (dialog));
2353
2354   return GTK_WIDGET (dialog);
2355 }
2356
2357 void
2358 empathy_accounts_dialog_show_application (GdkScreen *screen,
2359     TpAccount *selected_account,
2360     gboolean if_needed,
2361     gboolean hidden)
2362 {
2363   GError *error = NULL;
2364   const gchar *argv[4] = { NULL, };
2365   gint i = 0;
2366   gchar *account_option = NULL;
2367   gchar *path;
2368
2369   g_return_if_fail (GDK_IS_SCREEN (screen));
2370   g_return_if_fail (!selected_account || TP_IS_ACCOUNT (selected_account));
2371
2372   /* Try to run from source directory if possible */
2373   path = g_build_filename (g_getenv ("EMPATHY_SRCDIR"), "src",
2374       "empathy-accounts", NULL);
2375
2376   if (!g_file_test (path, G_FILE_TEST_EXISTS))
2377     {
2378       g_free (path);
2379       path = g_build_filename (BIN_DIR, "empathy-accounts", NULL);
2380     }
2381
2382   argv[i++] = path;
2383
2384   if (selected_account != NULL)
2385     {
2386       const gchar *account_path;
2387
2388       account_path = tp_proxy_get_object_path (TP_PROXY (selected_account));
2389       account_option = g_strdup_printf ("--select-account=%s",
2390           &account_path[strlen (TP_ACCOUNT_OBJECT_PATH_BASE)]);
2391
2392       argv[i++] = account_option;
2393     }
2394
2395   if (if_needed)
2396     argv[i++] = "--if-needed";
2397
2398   if (hidden)
2399     argv[i++] = "--hidden";
2400
2401   DEBUG ("Launching empathy-accounts (if_needed: %d, hidden: %d, account: %s)",
2402     if_needed, hidden,
2403     selected_account == NULL ? "<none selected>" :
2404       tp_proxy_get_object_path (TP_PROXY (selected_account)));
2405
2406   gdk_spawn_on_screen (screen, NULL, (gchar**) argv, NULL, G_SPAWN_SEARCH_PATH,
2407       NULL, NULL, NULL, &error);
2408   if (error != NULL)
2409     {
2410       g_warning ("Failed to open accounts dialog: %s", error->message);
2411       g_error_free (error);
2412     }
2413
2414   g_free (account_option);
2415   g_free (path);
2416 }
2417
2418 gboolean
2419 empathy_accounts_dialog_is_creating (EmpathyAccountsDialog *dialog)
2420 {
2421   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
2422   gboolean result = FALSE;
2423
2424   if (priv->setting_widget_object == NULL)
2425     goto out;
2426
2427   g_object_get (priv->setting_widget_object,
2428       "creating-account", &result, NULL);
2429
2430 out:
2431   return result;
2432 }