]> git.0d.be Git - empathy.git/blob - src/empathy-accounts-dialog.c
set infobar message to no network connection
[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.h>
33 #include <dbus/dbus-glib.h>
34
35 #include <telepathy-glib/account-manager.h>
36 #include <telepathy-glib/util.h>
37
38 #include <libempathy/empathy-utils.h>
39 #include <libempathy/empathy-connection-managers.h>
40 #include <libempathy/empathy-connectivity.h>
41 #include <libempathy-gtk/empathy-ui-utils.h>
42
43 #include <libempathy-gtk/empathy-protocol-chooser.h>
44 #include <libempathy-gtk/empathy-account-widget.h>
45 #include <libempathy-gtk/empathy-account-widget-irc.h>
46 #include <libempathy-gtk/empathy-account-widget-sip.h>
47 #include <libempathy-gtk/empathy-cell-renderer-activatable.h>
48 #include <libempathy-gtk/empathy-conf.h>
49 #include <libempathy-gtk/empathy-images.h>
50
51 #include "empathy-accounts-dialog.h"
52 #include "empathy-import-dialog.h"
53 #include "empathy-import-utils.h"
54 #include "ephy-spinner.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 modification regarding 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, G_TYPE_OBJECT);
73
74 static EmpathyAccountsDialog *dialog_singleton = NULL;
75
76 typedef struct {
77   GtkWidget *window;
78
79   GtkWidget *alignment_settings;
80   GtkWidget *alignment_infobar;
81
82   GtkWidget *vbox_details;
83   GtkWidget *infobar;
84   GtkWidget *label_status;
85   GtkWidget *image_status;
86   GtkWidget *throbber;
87   GtkWidget *frame_no_protocol;
88
89   GtkWidget *treeview;
90
91   GtkWidget *button_add;
92   GtkWidget *button_import;
93
94   GtkWidget *frame_new_account;
95   GtkWidget *combobox_protocol;
96   GtkWidget *hbox_type;
97   GtkWidget *button_create;
98   GtkWidget *button_back;
99   GtkWidget *radiobutton_reuse;
100   GtkWidget *radiobutton_register;
101
102   GtkWidget *image_type;
103   GtkWidget *label_name;
104   GtkWidget *label_type;
105   GtkWidget *settings_widget;
106
107   /* We have to keep a reference on the actual EmpathyAccountWidget, not just
108    * his GtkWidget. It is the only reliable source we can query to know if
109    * there are any unsaved changes to the currently selected account. We can't
110    * look at the account settings because it does not contain everything that
111    * can be changed using the EmpathyAccountWidget. For instance, it does not
112    * contain the state of the "Enabled" checkbox. */
113   EmpathyAccountWidget *setting_widget_object;
114
115   gboolean  connecting_show;
116   guint connecting_id;
117
118   gulong  settings_ready_id;
119   EmpathyAccountSettings *settings_ready;
120
121   TpAccountManager *account_manager;
122   EmpathyConnectionManagers *cms;
123
124   GtkWindow *parent_window;
125   TpAccount *initial_selection;
126
127   /* Those are needed when changing the selected row. When a user selects
128    * another account and there are unsaved changes on the currently selected
129    * one, a confirmation message box is presented to him. Since his answer
130    * is retrieved asynchronously, we keep some information as member of the
131    * EmpathyAccountsDialog object. */
132   gboolean force_change_row;
133   GtkTreeRowReference *destination_row;
134
135
136 } EmpathyAccountsDialogPriv;
137
138 enum {
139   COL_NAME,
140   COL_STATUS,
141   COL_ACCOUNT_POINTER,
142   COL_ACCOUNT_SETTINGS_POINTER,
143   COL_COUNT
144 };
145
146 enum {
147   PROP_PARENT = 1
148 };
149
150 static EmpathyAccountSettings * accounts_dialog_model_get_selected_settings (
151     EmpathyAccountsDialog *dialog);
152
153 static gboolean accounts_dialog_get_settings_iter (
154     EmpathyAccountsDialog *dialog,
155     EmpathyAccountSettings *settings,
156     GtkTreeIter *iter);
157
158 static void accounts_dialog_model_select_first (EmpathyAccountsDialog *dialog);
159
160 static void accounts_dialog_update_settings (EmpathyAccountsDialog *dialog,
161     EmpathyAccountSettings *settings);
162
163 static void
164 accounts_dialog_update_name_label (EmpathyAccountsDialog *dialog,
165     const gchar *display_name)
166 {
167   gchar *text;
168   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
169
170   text = g_markup_printf_escaped ("<big><b>%s</b></big>", display_name);
171   gtk_label_set_markup (GTK_LABEL (priv->label_name), text);
172
173   g_free (text);
174 }
175
176 static void
177 accounts_dialog_update_status_infobar (EmpathyAccountsDialog *dialog,
178     TpAccount *account)
179 {
180   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
181   const gchar               *message;
182   guint                     status;
183   guint                     reason;
184   guint                     presence;
185   EmpathyConnectivity       *connectivity;
186
187   status = tp_account_get_connection_status (account, &reason);
188   presence = tp_account_get_current_presence (account, NULL, NULL);
189
190   connectivity = empathy_connectivity_dup_singleton ();
191
192   gtk_image_set_from_icon_name (GTK_IMAGE (priv->image_status),
193       empathy_icon_name_for_presence (presence), GTK_ICON_SIZE_SMALL_TOOLBAR);
194
195   if (tp_account_is_enabled (account))
196     {
197       switch (status)
198         {
199           case TP_CONNECTION_STATUS_CONNECTING:
200             message = _("Connecting...");
201             gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
202                 GTK_MESSAGE_INFO);
203
204             ephy_spinner_start (EPHY_SPINNER (priv->throbber));
205             gtk_widget_show (priv->throbber);
206             gtk_widget_hide (priv->image_status);
207             break;
208           case TP_CONNECTION_STATUS_CONNECTED:
209             message = g_strdup_printf (_("Connected with status %s"),
210                 empathy_presence_get_default_message (presence));
211
212             gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
213                 GTK_MESSAGE_INFO);
214
215             gtk_widget_show (priv->image_status);
216             gtk_widget_hide (priv->throbber);
217             break;
218           case TP_CONNECTION_STATUS_DISCONNECTED:
219             message = g_strdup_printf (_("Offline - %s"),
220                 empathy_status_reason_get_default_message (reason));
221
222             if (reason == TP_CONNECTION_STATUS_REASON_REQUESTED)
223               {
224                 gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
225                     GTK_MESSAGE_WARNING);
226               }
227             else
228               {
229                 gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
230                     GTK_MESSAGE_ERROR);
231               }
232
233             if (!empathy_connectivity_is_online (connectivity))
234                message = _("Offline - No Network Connection");
235
236             ephy_spinner_stop (EPHY_SPINNER (priv->throbber));
237             gtk_widget_show (priv->image_status);
238             gtk_widget_hide (priv->throbber);
239             break;
240           default:
241             message = _("Unknown Status");
242             gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
243                 GTK_MESSAGE_WARNING);
244
245             ephy_spinner_stop (EPHY_SPINNER (priv->throbber));
246             gtk_widget_hide (priv->image_status);
247             gtk_widget_hide (priv->throbber);
248         }
249     }
250   else
251     {
252       message = _("Offline - Account disabled");
253       gtk_info_bar_set_message_type (GTK_INFO_BAR (priv->infobar),
254           GTK_MESSAGE_WARNING);
255       ephy_spinner_stop (EPHY_SPINNER (priv->throbber));
256       gtk_widget_show (priv->image_status);
257       gtk_widget_hide (priv->throbber);
258     }
259
260   gtk_label_set_text (GTK_LABEL (priv->label_status), message);
261   gtk_widget_show (priv->label_status);
262   gtk_widget_show (priv->infobar);
263
264   g_object_unref (connectivity);
265 }
266
267 static void
268 empathy_account_dialog_widget_cancelled_cb (
269     EmpathyAccountWidget *widget_object,
270     EmpathyAccountsDialog *dialog)
271 {
272   GtkTreeView *view;
273   GtkTreeModel *model;
274   GtkTreeSelection *selection;
275   GtkTreeIter iter;
276   EmpathyAccountSettings *settings;
277   TpAccount *account;
278   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
279
280   view = GTK_TREE_VIEW (priv->treeview);
281   selection = gtk_tree_view_get_selection (view);
282
283   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
284     return;
285
286   gtk_tree_model_get (model, &iter,
287       COL_ACCOUNT_SETTINGS_POINTER, &settings,
288       COL_ACCOUNT_POINTER, &account, -1);
289
290   empathy_account_widget_discard_pending_changes (priv->setting_widget_object);
291
292   if (account == NULL)
293     {
294       /* We were creating an account. We remove the selected row */
295       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
296     }
297   else
298     {
299       /* We were modifying an account. We discard the changes by reloading the
300        * settings and the UI. */
301       accounts_dialog_update_settings (dialog, settings);
302       g_object_unref (account);
303     }
304
305   if (settings != NULL)
306     g_object_unref (settings);
307 }
308
309 static void
310 empathy_account_dialog_account_created_cb (EmpathyAccountWidget *widget_object,
311     EmpathyAccountsDialog *dialog)
312 {
313   gchar *display_name;
314   EmpathyAccountSettings *settings =
315       accounts_dialog_model_get_selected_settings (dialog);
316
317   display_name = empathy_account_widget_get_default_display_name (
318       widget_object);
319
320   empathy_account_settings_set_display_name_async (settings,
321       display_name, NULL, NULL);
322
323   g_free (display_name);
324
325   accounts_dialog_update_settings (dialog, settings);
326
327   if (settings)
328     g_object_unref (settings);
329 }
330
331 static void
332 account_dialog_create_settings_widget (EmpathyAccountsDialog *dialog,
333     EmpathyAccountSettings *settings)
334 {
335   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
336   gchar                     *icon_name;
337   TpAccount                 *account;
338
339   priv->setting_widget_object =
340       empathy_account_widget_new_for_protocol (settings, FALSE);
341
342   priv->settings_widget =
343       empathy_account_widget_get_widget (priv->setting_widget_object);
344
345   priv->settings_widget =
346       empathy_account_widget_get_widget (priv->setting_widget_object);
347   g_signal_connect (priv->setting_widget_object, "account-created",
348         G_CALLBACK (empathy_account_dialog_account_created_cb), dialog);
349   g_signal_connect (priv->setting_widget_object, "cancelled",
350           G_CALLBACK (empathy_account_dialog_widget_cancelled_cb), dialog);
351
352   gtk_container_add (GTK_CONTAINER (priv->alignment_settings),
353       priv->settings_widget);
354   gtk_widget_show (priv->settings_widget);
355
356   icon_name = empathy_account_settings_get_icon_name (settings);
357
358   if (!gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
359           icon_name))
360     /* show the default icon; keep this in sync with the default
361      * one in empathy-accounts-dialog.ui.
362      */
363     icon_name = GTK_STOCK_CUT;
364
365   gtk_image_set_from_icon_name (GTK_IMAGE (priv->image_type),
366       icon_name, GTK_ICON_SIZE_DIALOG);
367   gtk_widget_set_tooltip_text (priv->image_type,
368       empathy_protocol_name_to_display_name
369       (empathy_account_settings_get_protocol (settings)));
370   gtk_widget_show (priv->image_type);
371
372   accounts_dialog_update_name_label (dialog,
373       empathy_account_settings_get_display_name (settings));
374
375   account = empathy_account_settings_get_account (settings);
376   accounts_dialog_update_status_infobar (dialog, account);
377 }
378
379 static void
380 account_dialog_settings_ready_cb (EmpathyAccountSettings *settings,
381     GParamSpec *spec,
382     EmpathyAccountsDialog *dialog)
383 {
384   if (empathy_account_settings_is_ready (settings))
385     account_dialog_create_settings_widget (dialog, settings);
386 }
387
388 static void
389 accounts_dialog_model_select_first (EmpathyAccountsDialog *dialog)
390 {
391   GtkTreeView      *view;
392   GtkTreeModel     *model;
393   GtkTreeSelection *selection;
394   GtkTreeIter       iter;
395   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
396
397   /* select first */
398   view = GTK_TREE_VIEW (priv->treeview);
399   model = gtk_tree_view_get_model (view);
400
401   if (gtk_tree_model_get_iter_first (model, &iter))
402     {
403       selection = gtk_tree_view_get_selection (view);
404       gtk_tree_selection_select_iter (selection, &iter);
405     }
406   else
407     {
408       accounts_dialog_update_settings (dialog, NULL);
409     }
410 }
411
412 static gboolean
413 accounts_dialog_has_pending_change (EmpathyAccountsDialog *dialog,
414     TpAccount **account)
415 {
416   GtkTreeIter iter;
417   GtkTreeModel *model;
418   GtkTreeSelection *selection;
419   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
420
421   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
422
423   if (gtk_tree_selection_get_selected (selection, &model, &iter))
424     gtk_tree_model_get (model, &iter, COL_ACCOUNT_POINTER, account, -1);
425
426   return priv->setting_widget_object != NULL
427       && empathy_account_widget_contains_pending_changes (
428           priv->setting_widget_object);
429 }
430
431 static void
432 accounts_dialog_protocol_changed_cb (GtkWidget *widget,
433     EmpathyAccountsDialog *dialog)
434 {
435   TpConnectionManager *cm;
436   TpConnectionManagerProtocol *proto;
437   gboolean is_gtalk;
438   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
439
440   cm = empathy_protocol_chooser_dup_selected (
441       EMPATHY_PROTOCOL_CHOOSER (priv->combobox_protocol), &proto, &is_gtalk);
442
443   if (cm == NULL)
444     return;
445
446   if (proto == NULL)
447     {
448       g_object_unref (cm);
449       return;
450     }
451
452 #ifndef HAVE_MOBLIN
453   if (tp_connection_manager_protocol_can_register (proto) && !is_gtalk)
454     {
455       gtk_widget_show (priv->radiobutton_register);
456       gtk_widget_show (priv->radiobutton_reuse);
457     }
458   else
459     {
460       gtk_widget_hide (priv->radiobutton_register);
461       gtk_widget_hide (priv->radiobutton_reuse);
462     }
463 #else
464   gtk_widget_hide (priv->radiobutton_register);
465   gtk_widget_hide (priv->radiobutton_reuse);
466 #endif
467
468   g_object_unref (cm);
469 }
470
471 static void
472 accounts_dialog_setup_ui_to_add_account (EmpathyAccountsDialog *dialog)
473 {
474   GtkTreeView *view;
475   GtkTreeModel *model;
476   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
477
478   view = GTK_TREE_VIEW (priv->treeview);
479   model = gtk_tree_view_get_model (view);
480
481   gtk_widget_set_sensitive (priv->button_add, FALSE);
482   gtk_widget_hide (priv->vbox_details);
483   gtk_widget_hide (priv->frame_no_protocol);
484   gtk_widget_show (priv->frame_new_account);
485
486   /* If we have no account, no need of a back button */
487   if (gtk_tree_model_iter_n_children (model, NULL) > 0)
488     gtk_widget_show (priv->button_back);
489   else
490     gtk_widget_hide (priv->button_back);
491
492   accounts_dialog_protocol_changed_cb (priv->radiobutton_register, dialog);
493   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->radiobutton_reuse),
494       TRUE);
495   gtk_combo_box_set_active (GTK_COMBO_BOX (priv->combobox_protocol), 0);
496   gtk_widget_grab_focus (priv->combobox_protocol);
497 }
498
499 static void
500 accounts_dialog_show_question_dialog (EmpathyAccountsDialog *dialog,
501     const gchar *primary_text,
502     const gchar *secondary_text,
503     GCallback response_callback,
504     gpointer user_data,
505     const gchar *first_button_text,
506     ...)
507 {
508   va_list button_args;
509   GtkWidget *message_dialog;
510   const gchar *button_text;
511   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
512
513   message_dialog = gtk_message_dialog_new (GTK_WINDOW (priv->window),
514       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
515       GTK_MESSAGE_QUESTION,
516       GTK_BUTTONS_NONE,
517       "%s", primary_text);
518
519   gtk_message_dialog_format_secondary_text (
520       GTK_MESSAGE_DIALOG (message_dialog), "%s", secondary_text);
521
522   va_start (button_args, first_button_text);
523   for (button_text = first_button_text;
524        button_text;
525        button_text = va_arg (button_args, const gchar *))
526     {
527       gint response_id;
528       response_id = va_arg (button_args, gint);
529
530       gtk_dialog_add_button (GTK_DIALOG (message_dialog), button_text,
531           response_id);
532     }
533   va_end (button_args);
534
535   g_signal_connect (message_dialog, "response", response_callback, user_data);
536
537   gtk_widget_show (message_dialog);
538 }
539
540 static void
541 accounts_dialog_add_pending_changes_response_cb (GtkDialog *message_dialog,
542   gint response_id,
543   gpointer *user_data)
544 {
545   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
546   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
547
548   gtk_widget_destroy (GTK_WIDGET (message_dialog));
549
550   if (response_id == GTK_RESPONSE_YES)
551     {
552       empathy_account_widget_discard_pending_changes (
553           priv->setting_widget_object);
554       accounts_dialog_setup_ui_to_add_account (dialog);
555     }
556 }
557
558 static gchar *
559 get_dialog_primary_text (TpAccount *account)
560 {
561   if (account != NULL)
562     {
563       /* Existing account */
564       return g_strdup_printf (PENDING_CHANGES_QUESTION_PRIMARY_TEXT,
565           tp_account_get_display_name (account));
566     }
567   else
568     {
569       /* Newly created account */
570       return g_strdup (UNSAVED_NEW_ACCOUNT_QUESTION_PRIMARY_TEXT);
571     }
572 }
573
574 static void
575 accounts_dialog_button_add_clicked_cb (GtkWidget *button,
576     EmpathyAccountsDialog *dialog)
577 {
578   TpAccount *account = NULL;
579
580   if (accounts_dialog_has_pending_change (dialog, &account))
581     {
582       gchar *question_dialog_primary_text = get_dialog_primary_text (account);
583
584       accounts_dialog_show_question_dialog (dialog,
585           question_dialog_primary_text,
586           _("You are about to create a new account, which will discard\n"
587               "your changes. Are you sure you want to proceed?"),
588           G_CALLBACK (accounts_dialog_add_pending_changes_response_cb),
589           dialog,
590           GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
591           GTK_STOCK_DISCARD, GTK_RESPONSE_YES, NULL);
592
593       g_free (question_dialog_primary_text);
594     }
595   else
596     {
597       accounts_dialog_setup_ui_to_add_account (dialog);
598     }
599 }
600
601 static void
602 accounts_dialog_update_settings (EmpathyAccountsDialog *dialog,
603     EmpathyAccountSettings *settings)
604 {
605   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
606
607   if (priv->settings_ready != NULL)
608     {
609       g_signal_handler_disconnect (priv->settings_ready,
610           priv->settings_ready_id);
611       priv->settings_ready = NULL;
612       priv->settings_ready_id = 0;
613     }
614
615   if (!settings)
616     {
617       GtkTreeView  *view;
618       GtkTreeModel *model;
619       GtkTreeSelection *selection;
620
621       view = GTK_TREE_VIEW (priv->treeview);
622       model = gtk_tree_view_get_model (view);
623       selection = gtk_tree_view_get_selection (view);
624
625       if (gtk_tree_model_iter_n_children (model, NULL) > 0)
626         {
627           /* We have configured accounts, select the first one if there
628            * is no other account selected already. */
629           if (!gtk_tree_selection_get_selected (selection, NULL, NULL))
630             accounts_dialog_model_select_first (dialog);
631
632           return;
633         }
634       if (empathy_connection_managers_get_cms_num (priv->cms) > 0)
635         {
636           /* We have no account configured but we have some
637            * profiles installed. The user obviously wants to add
638            * an account. Click on the Add button for him. */
639           accounts_dialog_button_add_clicked_cb (priv->button_add,
640               dialog);
641           return;
642         }
643
644       /* No account and no profile, warn the user */
645       gtk_widget_hide (priv->vbox_details);
646       gtk_widget_hide (priv->frame_new_account);
647       gtk_widget_show (priv->frame_no_protocol);
648       gtk_widget_set_sensitive (priv->button_add, FALSE);
649       return;
650     }
651
652   /* We have an account selected, destroy old settings and create a new
653    * one for the account selected */
654   gtk_widget_hide (priv->frame_new_account);
655   gtk_widget_hide (priv->frame_no_protocol);
656   gtk_widget_show (priv->vbox_details);
657   gtk_widget_set_sensitive (priv->button_add, TRUE);
658
659   if (priv->settings_widget)
660     {
661       gtk_widget_destroy (priv->settings_widget);
662       priv->settings_widget = NULL;
663     }
664
665   if (empathy_account_settings_is_ready (settings))
666     {
667       account_dialog_create_settings_widget (dialog, settings);
668     }
669   else
670     {
671       priv->settings_ready = settings;
672       priv->settings_ready_id =
673         g_signal_connect (settings, "notify::ready",
674             G_CALLBACK (account_dialog_settings_ready_cb), dialog);
675     }
676
677 }
678
679 static void
680 accounts_dialog_name_editing_started_cb (GtkCellRenderer *renderer,
681     GtkCellEditable *editable,
682     gchar *path,
683     EmpathyAccountsDialog *dialog)
684 {
685   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
686
687   if (priv->connecting_id)
688     g_source_remove (priv->connecting_id);
689
690   DEBUG ("Editing account name started; stopping flashing");
691 }
692
693 static void
694 accounts_dialog_model_pixbuf_data_func (GtkTreeViewColumn *tree_column,
695     GtkCellRenderer *cell,
696     GtkTreeModel *model,
697     GtkTreeIter *iter,
698     EmpathyAccountsDialog *dialog)
699 {
700   EmpathyAccountSettings  *settings;
701   gchar              *icon_name;
702   GdkPixbuf          *pixbuf;
703   TpConnectionStatus  status;
704   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
705
706   gtk_tree_model_get (model, iter,
707       COL_STATUS, &status,
708       COL_ACCOUNT_SETTINGS_POINTER, &settings,
709       -1);
710
711   icon_name = empathy_account_settings_get_icon_name (settings);
712   pixbuf = empathy_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
713
714   if (pixbuf)
715     {
716       if (status == TP_CONNECTION_STATUS_DISCONNECTED ||
717           (status == TP_CONNECTION_STATUS_CONNECTING &&
718               !priv->connecting_show))
719         {
720           GdkPixbuf *modded_pixbuf;
721
722           modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
723               TRUE,
724               8,
725               gdk_pixbuf_get_width (pixbuf),
726               gdk_pixbuf_get_height (pixbuf));
727
728           gdk_pixbuf_saturate_and_pixelate (pixbuf,
729               modded_pixbuf,
730               1.0,
731               TRUE);
732           g_object_unref (pixbuf);
733           pixbuf = modded_pixbuf;
734         }
735     }
736
737   g_object_set (cell,
738       "visible", TRUE,
739       "pixbuf", pixbuf,
740       NULL);
741
742   g_object_unref (settings);
743
744   if (pixbuf)
745     g_object_unref (pixbuf);
746 }
747
748 static gboolean
749 accounts_dialog_row_changed_foreach (GtkTreeModel *model,
750     GtkTreePath *path,
751     GtkTreeIter *iter,
752     gpointer user_data)
753 {
754   gtk_tree_model_row_changed (model, path, iter);
755
756   return FALSE;
757 }
758
759 static gboolean
760 accounts_dialog_flash_connecting_cb (EmpathyAccountsDialog *dialog)
761 {
762   GtkTreeView  *view;
763   GtkTreeModel *model;
764   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
765
766   priv->connecting_show = !priv->connecting_show;
767
768   view = GTK_TREE_VIEW (priv->treeview);
769   model = gtk_tree_view_get_model (view);
770
771   gtk_tree_model_foreach (model, accounts_dialog_row_changed_foreach, NULL);
772
773   return TRUE;
774 }
775
776 static void
777 accounts_dialog_name_edited_cb (GtkCellRendererText *renderer,
778     gchar *path,
779     gchar *new_text,
780     EmpathyAccountsDialog *dialog)
781 {
782   EmpathyAccountSettings    *settings;
783   GtkTreeModel *model;
784   GtkTreePath  *treepath;
785   GtkTreeIter   iter;
786   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
787   gboolean connecting;
788
789   empathy_account_manager_get_accounts_connected (&connecting);
790
791   if (connecting)
792     {
793       priv->connecting_id = g_timeout_add (FLASH_TIMEOUT,
794           (GSourceFunc) accounts_dialog_flash_connecting_cb,
795           dialog);
796     }
797
798   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
799   treepath = gtk_tree_path_new_from_string (path);
800   gtk_tree_model_get_iter (model, &iter, treepath);
801   gtk_tree_model_get (model, &iter,
802       COL_ACCOUNT_SETTINGS_POINTER, &settings,
803       -1);
804   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
805       COL_NAME, new_text,
806       -1);
807   gtk_tree_path_free (treepath);
808
809   empathy_account_settings_set_display_name_async (settings, new_text,
810       NULL, NULL);
811   g_object_set (settings, "display-name-overridden", TRUE, NULL);
812   g_object_unref (settings);
813 }
814
815 static void
816 accounts_dialog_delete_account_response_cb (GtkDialog *message_dialog,
817   gint response_id,
818   gpointer user_data)
819 {
820   TpAccount *account;
821   GtkTreeModel *model;
822   GtkTreeIter iter;
823   GtkTreeSelection *selection;
824   EmpathyAccountsDialog *account_dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
825   EmpathyAccountsDialogPriv *priv = GET_PRIV (account_dialog);
826
827   if (response_id == GTK_RESPONSE_YES)
828     {
829       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
830
831       if (!gtk_tree_selection_get_selected (selection, &model, &iter))
832         return;
833
834       gtk_tree_model_get (model, &iter, COL_ACCOUNT_POINTER, &account, -1);
835
836       if (account != NULL)
837         {
838           tp_account_remove_async (account, NULL, NULL);
839           g_object_unref (account);
840           account = NULL;
841         }
842
843       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
844       accounts_dialog_model_select_first (account_dialog);
845     }
846
847   gtk_widget_destroy (GTK_WIDGET (message_dialog));
848 }
849
850 static void
851 accounts_dialog_view_delete_activated_cb (EmpathyCellRendererActivatable *cell,
852     const gchar *path_string,
853     EmpathyAccountsDialog *dialog)
854 {
855   TpAccount *account;
856   GtkTreeModel *model;
857   GtkTreeIter iter;
858   gchar *question_dialog_primary_text;
859   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
860
861   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
862
863   if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
864     return;
865
866   gtk_tree_model_get (model, &iter, COL_ACCOUNT_POINTER, &account, -1);
867
868   if (account == NULL || !tp_account_is_valid (account))
869     {
870       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
871       accounts_dialog_model_select_first (dialog);
872       return;
873     }
874
875   question_dialog_primary_text = g_strdup_printf (
876       _("Do you want to remove %s from your computer?"),
877       tp_account_get_display_name (account));
878
879   accounts_dialog_show_question_dialog (dialog, question_dialog_primary_text,
880       _("This will not remove your account on the server."),
881       G_CALLBACK (accounts_dialog_delete_account_response_cb),
882       dialog,
883       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
884       GTK_STOCK_REMOVE, GTK_RESPONSE_YES, NULL);
885
886   g_free (question_dialog_primary_text);
887
888   if (account != NULL)
889     {
890       g_object_unref (account);
891       account = NULL;
892     }
893 }
894
895 static void
896 accounts_dialog_model_add_columns (EmpathyAccountsDialog *dialog)
897 {
898   GtkTreeView       *view;
899   GtkTreeViewColumn *column;
900   GtkCellRenderer   *cell;
901   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
902
903   view = GTK_TREE_VIEW (priv->treeview);
904   gtk_tree_view_set_headers_visible (view, FALSE);
905
906   /* Account column */
907   column = gtk_tree_view_column_new ();
908   gtk_tree_view_column_set_expand (column, TRUE);
909   gtk_tree_view_append_column (view, column);
910
911   /* Icon renderer */
912   cell = gtk_cell_renderer_pixbuf_new ();
913   gtk_tree_view_column_pack_start (column, cell, FALSE);
914   gtk_tree_view_column_set_cell_data_func (column, cell,
915       (GtkTreeCellDataFunc)
916       accounts_dialog_model_pixbuf_data_func,
917       dialog,
918       NULL);
919
920   /* Name renderer */
921   cell = gtk_cell_renderer_text_new ();
922   g_object_set (cell,
923       "ellipsize", PANGO_ELLIPSIZE_END,
924       "width-chars", 25,
925       "editable", TRUE,
926       NULL);
927   gtk_tree_view_column_pack_start (column, cell, TRUE);
928   gtk_tree_view_column_add_attribute (column, cell, "text", COL_NAME);
929   g_signal_connect (cell, "edited",
930       G_CALLBACK (accounts_dialog_name_edited_cb),
931       dialog);
932   g_signal_connect (cell, "editing-started",
933       G_CALLBACK (accounts_dialog_name_editing_started_cb),
934       dialog);
935   g_object_set (cell, "ypad", 4, NULL);
936
937   /* Delete column */
938   cell = empathy_cell_renderer_activatable_new ();
939   gtk_tree_view_column_pack_start (column, cell, FALSE);
940   g_object_set (cell,
941         "icon-name", GTK_STOCK_DELETE,
942         "show-on-select", TRUE,
943         NULL);
944
945   g_signal_connect (cell, "path-activated",
946       G_CALLBACK (accounts_dialog_view_delete_activated_cb),
947       dialog);
948 }
949
950 static EmpathyAccountSettings *
951 accounts_dialog_model_get_selected_settings (EmpathyAccountsDialog *dialog)
952 {
953   GtkTreeView      *view;
954   GtkTreeModel     *model;
955   GtkTreeSelection *selection;
956   GtkTreeIter       iter;
957   EmpathyAccountSettings   *settings;
958   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
959
960   view = GTK_TREE_VIEW (priv->treeview);
961   selection = gtk_tree_view_get_selection (view);
962
963   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
964     return NULL;
965
966   gtk_tree_model_get (model, &iter,
967       COL_ACCOUNT_SETTINGS_POINTER, &settings, -1);
968
969   return settings;
970 }
971
972 static void
973 accounts_dialog_model_selection_changed (GtkTreeSelection *selection,
974     EmpathyAccountsDialog *dialog)
975 {
976   EmpathyAccountSettings *settings;
977   GtkTreeModel *model;
978   GtkTreeIter   iter;
979   gboolean      is_selection;
980
981   is_selection = gtk_tree_selection_get_selected (selection, &model, &iter);
982
983   settings = accounts_dialog_model_get_selected_settings (dialog);
984   accounts_dialog_update_settings (dialog, settings);
985
986   if (settings != NULL)
987     g_object_unref (settings);
988 }
989
990 static void
991 accounts_dialog_selection_change_response_cb (GtkDialog *message_dialog,
992   gint response_id,
993   gpointer *user_data)
994 {
995   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
996   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
997
998   gtk_widget_destroy (GTK_WIDGET (message_dialog));
999
1000     if (response_id == GTK_RESPONSE_YES && priv->destination_row != NULL)
1001       {
1002         /* The user wants to lose unsaved changes to the currently selected
1003          * account and select another account. We discard the changes and
1004          * select the other account. */
1005         GtkTreePath *path;
1006         GtkTreeSelection *selection;
1007
1008         priv->force_change_row = TRUE;
1009         empathy_account_widget_discard_pending_changes (
1010             priv->setting_widget_object);
1011
1012         path = gtk_tree_row_reference_get_path (priv->destination_row);
1013         selection = gtk_tree_view_get_selection (
1014             GTK_TREE_VIEW (priv->treeview));
1015
1016         if (path != NULL)
1017           {
1018             /* This will trigger a call to
1019              * accounts_dialog_account_selection_change() */
1020             gtk_tree_selection_select_path (selection, path);
1021             gtk_tree_path_free (path);
1022           }
1023
1024         gtk_tree_row_reference_free (priv->destination_row);
1025       }
1026     else
1027       {
1028         priv->force_change_row = FALSE;
1029       }
1030 }
1031
1032 static gboolean
1033 accounts_dialog_account_selection_change (GtkTreeSelection *selection,
1034     GtkTreeModel *model,
1035     GtkTreePath *path,
1036     gboolean path_currently_selected,
1037     gpointer data)
1038 {
1039   TpAccount *account = NULL;
1040   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (data);
1041   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1042
1043   if (priv->force_change_row)
1044     {
1045       /* We came back here because the user wants to discard changes to his
1046        * modified account. The changes have already been discarded so we
1047        * just change the selected row. */
1048       priv->force_change_row = FALSE;
1049       return TRUE;
1050     }
1051
1052   if (accounts_dialog_has_pending_change (dialog, &account))
1053     {
1054       /* The currently selected account has some unsaved changes. We ask
1055        * the user if he really wants to lose his changes and select another
1056        * account */
1057       gchar *question_dialog_primary_text = get_dialog_primary_text (account);
1058       priv->destination_row = gtk_tree_row_reference_new (model, path);
1059
1060       accounts_dialog_show_question_dialog (dialog,
1061           question_dialog_primary_text,
1062           _("You are about to select another account, which will discard\n"
1063               "your changes. Are you sure you want to proceed?"),
1064           G_CALLBACK (accounts_dialog_selection_change_response_cb),
1065           dialog,
1066           GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1067           GTK_STOCK_DISCARD, GTK_RESPONSE_YES, NULL);
1068
1069       g_free (question_dialog_primary_text);
1070     }
1071   else
1072     {
1073       return TRUE;
1074     }
1075
1076   return FALSE;
1077 }
1078
1079 static void
1080 accounts_dialog_model_setup (EmpathyAccountsDialog *dialog)
1081 {
1082   GtkListStore     *store;
1083   GtkTreeSelection *selection;
1084   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1085
1086   store = gtk_list_store_new (COL_COUNT,
1087       G_TYPE_STRING,         /* name */
1088       G_TYPE_UINT,           /* status */
1089       TP_TYPE_ACCOUNT,   /* account */
1090       EMPATHY_TYPE_ACCOUNT_SETTINGS); /* settings */
1091
1092   gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
1093       GTK_TREE_MODEL (store));
1094
1095   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1096   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1097   gtk_tree_selection_set_select_function (selection,
1098       accounts_dialog_account_selection_change, dialog, NULL);
1099
1100   g_signal_connect (selection, "changed",
1101       G_CALLBACK (accounts_dialog_model_selection_changed),
1102       dialog);
1103
1104   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
1105       COL_NAME, GTK_SORT_ASCENDING);
1106
1107   accounts_dialog_model_add_columns (dialog);
1108
1109   g_object_unref (store);
1110 }
1111
1112 static gboolean
1113 accounts_dialog_get_settings_iter (EmpathyAccountsDialog *dialog,
1114     EmpathyAccountSettings *settings,
1115     GtkTreeIter *iter)
1116 {
1117   GtkTreeView      *view;
1118   GtkTreeSelection *selection;
1119   GtkTreeModel     *model;
1120   gboolean          ok;
1121   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1122
1123   /* Update the status in the model */
1124   view = GTK_TREE_VIEW (priv->treeview);
1125   selection = gtk_tree_view_get_selection (view);
1126   model = gtk_tree_view_get_model (view);
1127
1128   for (ok = gtk_tree_model_get_iter_first (model, iter);
1129        ok;
1130        ok = gtk_tree_model_iter_next (model, iter))
1131     {
1132       EmpathyAccountSettings *this_settings;
1133       gboolean   equal;
1134
1135       gtk_tree_model_get (model, iter,
1136           COL_ACCOUNT_SETTINGS_POINTER, &this_settings,
1137           -1);
1138
1139       equal = (this_settings == settings);
1140       g_object_unref (this_settings);
1141
1142       if (equal)
1143         return TRUE;
1144     }
1145
1146   return FALSE;
1147 }
1148
1149 static gboolean
1150 accounts_dialog_get_account_iter (EmpathyAccountsDialog *dialog,
1151     TpAccount *account,
1152     GtkTreeIter *iter)
1153 {
1154   GtkTreeView      *view;
1155   GtkTreeSelection *selection;
1156   GtkTreeModel     *model;
1157   gboolean          ok;
1158   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1159
1160   /* Update the status in the model */
1161   view = GTK_TREE_VIEW (priv->treeview);
1162   selection = gtk_tree_view_get_selection (view);
1163   model = gtk_tree_view_get_model (view);
1164
1165   for (ok = gtk_tree_model_get_iter_first (model, iter);
1166        ok;
1167        ok = gtk_tree_model_iter_next (model, iter))
1168     {
1169       EmpathyAccountSettings *settings;
1170       gboolean   equal;
1171
1172       gtk_tree_model_get (model, iter,
1173           COL_ACCOUNT_SETTINGS_POINTER, &settings,
1174           -1);
1175
1176       equal = empathy_account_settings_has_account (settings, account);
1177       g_object_unref (settings);
1178
1179       if (equal)
1180         return TRUE;
1181     }
1182
1183   return FALSE;
1184 }
1185
1186 static void
1187 accounts_dialog_model_set_selected (EmpathyAccountsDialog *dialog,
1188     EmpathyAccountSettings *settings)
1189 {
1190   GtkTreeSelection *selection;
1191   GtkTreeIter       iter;
1192   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1193
1194   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1195   if (accounts_dialog_get_settings_iter (dialog, settings, &iter))
1196     gtk_tree_selection_select_iter (selection, &iter);
1197 }
1198 static void
1199 accounts_dialog_add (EmpathyAccountsDialog *dialog,
1200     EmpathyAccountSettings *settings)
1201 {
1202   GtkTreeModel       *model;
1203   GtkTreeIter         iter;
1204   const gchar        *name;
1205   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1206
1207   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1208   name = empathy_account_settings_get_display_name (settings);
1209
1210   gtk_list_store_append (GTK_LIST_STORE (model), &iter);
1211
1212   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1213       COL_NAME, name,
1214       COL_STATUS, TP_CONNECTION_STATUS_DISCONNECTED,
1215       COL_ACCOUNT_SETTINGS_POINTER, settings,
1216       -1);
1217 }
1218
1219 static void
1220 accounts_dialog_connection_changed_cb (TpAccount *account,
1221     guint old_status,
1222     guint current,
1223     guint reason,
1224     gchar *dbus_error_name,
1225     GHashTable *details,
1226     EmpathyAccountsDialog *dialog)
1227 {
1228   GtkTreeModel *model;
1229   GtkTreeIter   iter;
1230   gboolean      found;
1231   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1232
1233   /* Update the status-infobar in the details view*/
1234   accounts_dialog_update_status_infobar (dialog, account);
1235
1236   /* Update the status in the model */
1237   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1238
1239   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1240     {
1241       GtkTreePath *path;
1242
1243       gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1244           COL_STATUS, current,
1245           -1);
1246
1247       path = gtk_tree_model_get_path (model, &iter);
1248       gtk_tree_model_row_changed (model, path, &iter);
1249       gtk_tree_path_free (path);
1250     }
1251
1252   empathy_account_manager_get_accounts_connected (&found);
1253
1254   if (!found && priv->connecting_id)
1255     {
1256       g_source_remove (priv->connecting_id);
1257       priv->connecting_id = 0;
1258     }
1259
1260   if (found && !priv->connecting_id)
1261     priv->connecting_id = g_timeout_add (FLASH_TIMEOUT,
1262         (GSourceFunc) accounts_dialog_flash_connecting_cb,
1263         dialog);
1264 }
1265
1266 static void
1267 accounts_dialog_presence_changed_cb (TpAccount *account,
1268     guint presence,
1269     gchar *status,
1270     gchar *status_message,
1271     EmpathyAccountsDialog *dialog)
1272 {
1273   /* Update the status-infobar in the details view*/
1274   accounts_dialog_update_status_infobar (dialog, account);
1275 }
1276
1277 static void
1278 accounts_dialog_account_display_name_changed_cb (TpAccount *account,
1279   GParamSpec *pspec,
1280   gpointer user_data)
1281 {
1282   const gchar *display_name;
1283   GtkTreeIter iter;
1284   GtkTreeModel *model;
1285   EmpathyAccountSettings *settings;
1286   TpAccount *selected_account;
1287   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
1288   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1289
1290   display_name = tp_account_get_display_name (account);
1291   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1292   settings = accounts_dialog_model_get_selected_settings (dialog);
1293   if (settings == NULL)
1294     return;
1295
1296   selected_account = empathy_account_settings_get_account (settings);
1297
1298   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1299     {
1300       gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1301           COL_NAME, display_name,
1302           -1);
1303     }
1304
1305   if (selected_account == account)
1306     accounts_dialog_update_name_label (dialog, display_name);
1307
1308   g_object_unref (settings);
1309 }
1310
1311 static void
1312 accounts_dialog_add_account (EmpathyAccountsDialog *dialog,
1313     TpAccount *account)
1314 {
1315   EmpathyAccountSettings *settings;
1316   GtkTreeModel       *model;
1317   GtkTreeIter         iter;
1318   TpConnectionStatus  status;
1319   const gchar        *name;
1320   gboolean            enabled;
1321   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1322
1323   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1324   status = tp_account_get_connection_status (account, NULL);
1325   name = tp_account_get_display_name (account);
1326   enabled = tp_account_is_enabled (account);
1327
1328   settings = empathy_account_settings_new_for_account (account);
1329
1330   if (!accounts_dialog_get_account_iter (dialog, account, &iter))
1331     gtk_list_store_append (GTK_LIST_STORE (model), &iter);
1332
1333   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1334       COL_NAME, name,
1335       COL_STATUS, status,
1336       COL_ACCOUNT_POINTER, account,
1337       COL_ACCOUNT_SETTINGS_POINTER, settings,
1338       -1);
1339
1340   accounts_dialog_connection_changed_cb (account,
1341       0,
1342       status,
1343       TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED,
1344       NULL,
1345       NULL,
1346       dialog);
1347
1348   empathy_signal_connect_weak (account, "notify::display-name",
1349       G_CALLBACK (accounts_dialog_account_display_name_changed_cb),
1350       G_OBJECT (dialog));
1351
1352   g_object_unref (settings);
1353 }
1354
1355 static void
1356 account_prepare_cb (GObject *source_object,
1357     GAsyncResult *result,
1358     gpointer user_data)
1359 {
1360   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
1361   TpAccount *account = TP_ACCOUNT (source_object);
1362   GError *error = NULL;
1363
1364   if (!tp_account_prepare_finish (account, result, &error))
1365     {
1366       DEBUG ("Failed to prepare account: %s", error->message);
1367       g_error_free (error);
1368       return;
1369     }
1370
1371   accounts_dialog_add_account (dialog, account);
1372 }
1373
1374 static void
1375 accounts_dialog_account_validity_changed_cb (TpAccountManager *manager,
1376     TpAccount *account,
1377     gboolean valid,
1378     EmpathyAccountsDialog *dialog)
1379 {
1380   tp_account_prepare_async (account, NULL, account_prepare_cb, dialog);
1381 }
1382
1383 static void
1384 accounts_dialog_account_removed_cb (TpAccountManager *manager,
1385     TpAccount *account,
1386     EmpathyAccountsDialog *dialog)
1387 {
1388   GtkTreeIter iter;
1389   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1390
1391   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1392     {
1393       gtk_list_store_remove (GTK_LIST_STORE (
1394             gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview))), &iter);
1395     }
1396 }
1397
1398 static void
1399 enable_or_disable_account (EmpathyAccountsDialog *dialog,
1400     TpAccount *account,
1401     gboolean enabled)
1402 {
1403   GtkTreeModel *model;
1404   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1405
1406   /* Update the status in the model */
1407   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1408
1409   /* Update the status-infobar in the details view when disabling*/
1410   if (!enabled)
1411     accounts_dialog_update_status_infobar (dialog, account);
1412
1413   DEBUG ("Account %s is now %s",
1414       tp_account_get_display_name (account),
1415       enabled ? "enabled" : "disabled");
1416 }
1417
1418 static void
1419 accounts_dialog_account_disabled_cb (TpAccountManager *manager,
1420     TpAccount *account,
1421     EmpathyAccountsDialog *dialog)
1422 {
1423   enable_or_disable_account (dialog, account, FALSE);
1424 }
1425
1426 static void
1427 accounts_dialog_account_enabled_cb (TpAccountManager *manager,
1428     TpAccount *account,
1429     EmpathyAccountsDialog *dialog)
1430 {
1431   enable_or_disable_account (dialog, account, TRUE);
1432 }
1433
1434 static void
1435 accounts_dialog_button_create_clicked_cb (GtkWidget *button,
1436     EmpathyAccountsDialog *dialog)
1437 {
1438   EmpathyAccountSettings *settings;
1439   gchar *str;
1440   const gchar *display_name;
1441   TpConnectionManager *cm;
1442   TpConnectionManagerProtocol *proto;
1443   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1444   gboolean is_gtalk;
1445
1446   cm = empathy_protocol_chooser_dup_selected (
1447       EMPATHY_PROTOCOL_CHOOSER (priv->combobox_protocol), &proto, &is_gtalk);
1448
1449   display_name = empathy_protocol_name_to_display_name (
1450       is_gtalk ? "gtalk" : proto->name);
1451
1452   if (display_name == NULL)
1453     display_name = proto->name;
1454
1455   /* Create account */
1456   /* To translator: %s is the name of the protocol, such as "Google Talk" or
1457    * "Yahoo!"
1458    */
1459   str = g_strdup_printf (_("New %s account"), display_name);
1460   settings = empathy_account_settings_new (cm->name, proto->name, str);
1461
1462   g_free (str);
1463
1464 #ifndef HAVE_MOBLIN
1465   if (tp_connection_manager_protocol_can_register (proto))
1466     {
1467       gboolean active;
1468
1469       active = gtk_toggle_button_get_active
1470         (GTK_TOGGLE_BUTTON (priv->radiobutton_register));
1471       if (active)
1472         empathy_account_settings_set_boolean (settings, "register", TRUE);
1473     }
1474 #endif
1475
1476   if (is_gtalk)
1477     empathy_account_settings_set_icon_name_async (settings, "im-google-talk",
1478         NULL, NULL);
1479
1480   accounts_dialog_add (dialog, settings);
1481   accounts_dialog_model_set_selected (dialog, settings);
1482
1483   g_object_unref (settings);
1484   g_object_unref (cm);
1485 }
1486
1487 static void
1488 accounts_dialog_button_back_clicked_cb (GtkWidget *button,
1489     EmpathyAccountsDialog *dialog)
1490 {
1491   EmpathyAccountSettings *settings;
1492
1493   settings = accounts_dialog_model_get_selected_settings (dialog);
1494   accounts_dialog_update_settings (dialog, settings);
1495
1496   if (settings)
1497     g_object_unref (settings);
1498 }
1499
1500 static void
1501 accounts_dialog_button_help_clicked_cb (GtkWidget *button,
1502     EmpathyAccountsDialog *dialog)
1503 {
1504   empathy_url_show (button, "ghelp:empathy?accounts-window");
1505 }
1506
1507 static void
1508 accounts_dialog_button_import_clicked_cb (GtkWidget *button,
1509     EmpathyAccountsDialog *dialog)
1510 {
1511   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1512   GtkWidget *import_dialog;
1513
1514   import_dialog = empathy_import_dialog_new (GTK_WINDOW (priv->window),
1515       FALSE);
1516   gtk_widget_show (import_dialog);
1517 }
1518
1519 static void
1520 accounts_dialog_close_response_cb (GtkDialog *message_dialog,
1521   gint response_id,
1522   gpointer user_data)
1523 {
1524   GtkWidget *account_dialog = GTK_WIDGET (user_data);
1525
1526   gtk_widget_destroy (GTK_WIDGET (message_dialog));
1527
1528   if (response_id == GTK_RESPONSE_YES)
1529     gtk_widget_destroy (account_dialog);
1530 }
1531
1532 static void
1533 accounts_dialog_response_cb (GtkWidget *widget,
1534     gint response,
1535     EmpathyAccountsDialog *dialog)
1536 {
1537   TpAccount *account = NULL;
1538
1539   if (accounts_dialog_has_pending_change (dialog, &account))
1540     {
1541       gchar *question_dialog_primary_text = get_dialog_primary_text (account);
1542
1543       accounts_dialog_show_question_dialog (dialog,
1544           question_dialog_primary_text,
1545           _("You are about to close the window, which will discard\n"
1546               "your changes. Are you sure you want to proceed?"),
1547           G_CALLBACK (accounts_dialog_close_response_cb),
1548           widget,
1549           GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1550           GTK_STOCK_DISCARD, GTK_RESPONSE_YES, NULL);
1551
1552       g_free (question_dialog_primary_text);
1553     }
1554   else if (response == GTK_RESPONSE_CLOSE ||
1555            response == GTK_RESPONSE_DELETE_EVENT)
1556     gtk_widget_destroy (widget);
1557 }
1558
1559 static gboolean
1560 accounts_dialog_delete_event_cb (GtkWidget *widget,
1561     GdkEvent *event,
1562     EmpathyAccountsDialog *dialog)
1563 {
1564   /* we maunally handle responses to delete events */
1565   return TRUE;
1566 }
1567
1568 static void
1569 accounts_dialog_destroy_cb (GtkObject *obj,
1570     EmpathyAccountsDialog *dialog)
1571 {
1572   DEBUG ("%p", obj);
1573
1574   g_object_unref (dialog);
1575 }
1576
1577 static void
1578 accounts_dialog_set_selected_account (EmpathyAccountsDialog *dialog,
1579     TpAccount *account)
1580 {
1581   GtkTreeSelection *selection;
1582   GtkTreeIter       iter;
1583   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1584
1585   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1586   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1587     gtk_tree_selection_select_iter (selection, &iter);
1588 }
1589
1590 static void
1591 accounts_dialog_cms_ready_cb (EmpathyConnectionManagers *cms,
1592     GParamSpec *pspec,
1593     EmpathyAccountsDialog *dialog)
1594 {
1595   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1596
1597   if (empathy_connection_managers_is_ready (cms))
1598     {
1599       accounts_dialog_update_settings (dialog, NULL);
1600
1601       if (priv->initial_selection != NULL)
1602         {
1603           accounts_dialog_set_selected_account
1604               (dialog, priv->initial_selection);
1605           g_object_unref (priv->initial_selection);
1606           priv->initial_selection = NULL;
1607         }
1608     }
1609 }
1610
1611 static void
1612 accounts_dialog_accounts_setup (EmpathyAccountsDialog *dialog)
1613 {
1614   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1615   GList *accounts, *l;
1616
1617   g_signal_connect (priv->account_manager, "account-validity-changed",
1618       G_CALLBACK (accounts_dialog_account_validity_changed_cb),
1619       dialog);
1620   g_signal_connect (priv->account_manager, "account-removed",
1621       G_CALLBACK (accounts_dialog_account_removed_cb),
1622       dialog);
1623   g_signal_connect (priv->account_manager, "account-enabled",
1624       G_CALLBACK (accounts_dialog_account_enabled_cb),
1625       dialog);
1626   g_signal_connect (priv->account_manager, "account-disabled",
1627       G_CALLBACK (accounts_dialog_account_disabled_cb),
1628       dialog);
1629
1630   /* Add existing accounts */
1631   accounts = tp_account_manager_get_valid_accounts (priv->account_manager);
1632   for (l = accounts; l; l = l->next)
1633     {
1634       accounts_dialog_add_account (dialog, l->data);
1635
1636       empathy_signal_connect_weak (l->data, "status-changed",
1637           G_CALLBACK (accounts_dialog_connection_changed_cb), G_OBJECT (dialog));
1638       empathy_signal_connect_weak (l->data, "presence-changed",
1639           G_CALLBACK (accounts_dialog_presence_changed_cb), G_OBJECT (dialog));
1640     }
1641   g_list_free (accounts);
1642
1643   priv->cms = empathy_connection_managers_dup_singleton ();
1644   if (!empathy_connection_managers_is_ready (priv->cms))
1645     g_signal_connect (priv->cms, "notify::ready",
1646         G_CALLBACK (accounts_dialog_cms_ready_cb), dialog);
1647
1648   accounts_dialog_model_select_first (dialog);
1649 }
1650
1651 static void
1652 accounts_dialog_manager_ready_cb (GObject *source_object,
1653     GAsyncResult *result,
1654     gpointer user_data)
1655 {
1656   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
1657   GError *error = NULL;
1658
1659   if (!tp_account_manager_prepare_finish (manager, result, &error))
1660     {
1661       DEBUG ("Failed to prepare account manager: %s", error->message);
1662       g_error_free (error);
1663       return;
1664     }
1665
1666   accounts_dialog_accounts_setup (user_data);
1667 }
1668
1669 static void
1670 accounts_dialog_build_ui (EmpathyAccountsDialog *dialog)
1671 {
1672   GtkBuilder                   *gui;
1673   gchar                        *filename;
1674   EmpathyAccountsDialogPriv    *priv = GET_PRIV (dialog);
1675   GtkWidget                    *content_area;
1676 #ifdef HAVE_MOBLIN
1677   GtkWidget                    *action_area;
1678 #endif
1679
1680   filename = empathy_file_lookup ("empathy-accounts-dialog.ui", "src");
1681
1682   gui = empathy_builder_get_file (filename,
1683       "accounts_dialog", &priv->window,
1684       "vbox_details", &priv->vbox_details,
1685       "frame_no_protocol", &priv->frame_no_protocol,
1686       "alignment_settings", &priv->alignment_settings,
1687       "alignment_infobar", &priv->alignment_infobar,
1688       "treeview", &priv->treeview,
1689       "frame_new_account", &priv->frame_new_account,
1690       "hbox_type", &priv->hbox_type,
1691       "button_create", &priv->button_create,
1692       "button_back", &priv->button_back,
1693       "radiobutton_reuse", &priv->radiobutton_reuse,
1694       "radiobutton_register", &priv->radiobutton_register,
1695       "image_type", &priv->image_type,
1696       "label_name", &priv->label_name,
1697       "button_add", &priv->button_add,
1698       "button_import", &priv->button_import,
1699       NULL);
1700   g_free (filename);
1701
1702   empathy_builder_connect (gui, dialog,
1703       "accounts_dialog", "response", accounts_dialog_response_cb,
1704       "accounts_dialog", "destroy", accounts_dialog_destroy_cb,
1705       "accounts_dialog", "delete-event", accounts_dialog_delete_event_cb,
1706       "button_create", "clicked", accounts_dialog_button_create_clicked_cb,
1707       "button_back", "clicked", accounts_dialog_button_back_clicked_cb,
1708       "button_add", "clicked", accounts_dialog_button_add_clicked_cb,
1709       "button_help", "clicked", accounts_dialog_button_help_clicked_cb,
1710       "button_import", "clicked", accounts_dialog_button_import_clicked_cb,
1711       NULL);
1712
1713   g_object_unref (gui);
1714
1715 #ifdef HAVE_MOBLIN
1716   action_area = gtk_dialog_get_action_area (GTK_DIALOG (priv->window));
1717   gtk_widget_hide (action_area);
1718
1719   /* Translators: this is used only when built on a moblin platform */
1720   gtk_button_set_label (GTK_BUTTON (priv->button_create), _("_Next"));
1721   gtk_button_set_use_underline (GTK_BUTTON (priv->button_create), TRUE);
1722 #endif
1723
1724   priv->combobox_protocol = empathy_protocol_chooser_new ();
1725   gtk_box_pack_start (GTK_BOX (priv->hbox_type),
1726       priv->combobox_protocol,
1727       TRUE, TRUE, 0);
1728   gtk_widget_show (priv->combobox_protocol);
1729   g_signal_connect (priv->combobox_protocol, "changed",
1730       G_CALLBACK (accounts_dialog_protocol_changed_cb),
1731       dialog);
1732
1733   if (priv->parent_window)
1734     gtk_window_set_transient_for (GTK_WINDOW (priv->window),
1735         priv->parent_window);
1736
1737   /* set up spinner */
1738   priv->throbber = ephy_spinner_new ();
1739   ephy_spinner_set_size (EPHY_SPINNER (priv->throbber), GTK_ICON_SIZE_SMALL_TOOLBAR);
1740
1741   priv->infobar = gtk_info_bar_new ();
1742   gtk_container_add (GTK_CONTAINER (priv->alignment_infobar),
1743       priv->infobar);
1744   gtk_widget_show (priv->infobar);
1745
1746   priv->image_status = gtk_image_new_from_icon_name (
1747             empathy_icon_name_for_presence (
1748             TP_CONNECTION_PRESENCE_TYPE_OFFLINE), GTK_ICON_SIZE_SMALL_TOOLBAR);
1749
1750   priv->label_status = gtk_label_new (NULL);
1751
1752   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (priv->infobar));
1753   gtk_box_pack_start (GTK_BOX (content_area), priv->throbber,
1754       FALSE, FALSE, 0);
1755   gtk_box_pack_start (GTK_BOX (content_area), priv->image_status,
1756       FALSE, FALSE, 0);
1757   gtk_container_add (GTK_CONTAINER (content_area), priv->label_status);
1758 }
1759
1760 static void
1761 do_dispose (GObject *obj)
1762 {
1763   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (obj);
1764   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1765
1766   /* Disconnect signals */
1767   g_signal_handlers_disconnect_by_func (priv->account_manager,
1768       accounts_dialog_account_validity_changed_cb,
1769       dialog);
1770   g_signal_handlers_disconnect_by_func (priv->account_manager,
1771       accounts_dialog_account_removed_cb,
1772       dialog);
1773   g_signal_handlers_disconnect_by_func (priv->account_manager,
1774       accounts_dialog_account_enabled_cb,
1775       dialog);
1776   g_signal_handlers_disconnect_by_func (priv->account_manager,
1777       accounts_dialog_account_disabled_cb,
1778       dialog);
1779   g_signal_handlers_disconnect_by_func (priv->account_manager,
1780       accounts_dialog_manager_ready_cb,
1781       dialog);
1782
1783   if (priv->connecting_id)
1784     g_source_remove (priv->connecting_id);
1785
1786   if (priv->account_manager != NULL)
1787     {
1788       g_object_unref (priv->account_manager);
1789       priv->account_manager = NULL;
1790     }
1791
1792   if (priv->cms != NULL)
1793     {
1794       g_object_unref (priv->cms);
1795       priv->cms = NULL;
1796     }
1797
1798   if (priv->initial_selection != NULL)
1799     g_object_unref (priv->initial_selection);
1800   priv->initial_selection = NULL;
1801
1802   G_OBJECT_CLASS (empathy_accounts_dialog_parent_class)->dispose (obj);
1803 }
1804
1805 static GObject *
1806 do_constructor (GType type,
1807     guint n_props,
1808     GObjectConstructParam *props)
1809 {
1810   GObject *retval;
1811
1812   if (dialog_singleton)
1813     {
1814       retval = G_OBJECT (dialog_singleton);
1815       g_object_ref (retval);
1816     }
1817   else
1818     {
1819       retval =
1820         G_OBJECT_CLASS (empathy_accounts_dialog_parent_class)->constructor
1821             (type, n_props, props);
1822
1823       dialog_singleton = EMPATHY_ACCOUNTS_DIALOG (retval);
1824       g_object_add_weak_pointer (retval, (gpointer) &dialog_singleton);
1825       /* We add an extra reference that we'll release when the dialog is
1826        * destroyed (accounts_dialog_destroy_cb) */
1827       g_object_ref (retval);
1828     }
1829
1830   return retval;
1831 }
1832
1833 static void
1834 do_get_property (GObject *object,
1835     guint property_id,
1836     GValue *value,
1837     GParamSpec *pspec)
1838 {
1839   EmpathyAccountsDialogPriv *priv = GET_PRIV (object);
1840
1841   switch (property_id)
1842     {
1843     case PROP_PARENT:
1844       g_value_set_object (value, priv->parent_window);
1845       break;
1846     default:
1847       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1848     }
1849 }
1850
1851 static void
1852 do_set_property (GObject *object,
1853     guint property_id,
1854     const GValue *value,
1855     GParamSpec *pspec)
1856 {
1857   EmpathyAccountsDialogPriv *priv = GET_PRIV (object);
1858
1859   switch (property_id)
1860     {
1861     case PROP_PARENT:
1862       priv->parent_window = g_value_get_object (value);
1863       break;
1864     default:
1865       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1866     }
1867 }
1868
1869 static void
1870 do_constructed (GObject *object)
1871 {
1872   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (object);
1873   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1874   gboolean import_asked;
1875
1876   accounts_dialog_build_ui (dialog);
1877   accounts_dialog_model_setup (dialog);
1878
1879   /* Set up signalling */
1880   priv->account_manager = tp_account_manager_dup ();
1881
1882   tp_account_manager_prepare_async (priv->account_manager, NULL,
1883       accounts_dialog_manager_ready_cb, dialog);
1884
1885   empathy_conf_get_bool (empathy_conf_get (),
1886       EMPATHY_PREFS_IMPORT_ASKED, &import_asked);
1887
1888   if (empathy_import_accounts_to_import ())
1889     {
1890       gtk_widget_show (priv->button_import);
1891
1892       if (!import_asked)
1893         {
1894           GtkWidget *import_dialog;
1895
1896           empathy_conf_set_bool (empathy_conf_get (),
1897               EMPATHY_PREFS_IMPORT_ASKED, TRUE);
1898           import_dialog = empathy_import_dialog_new (GTK_WINDOW (priv->window),
1899               FALSE);
1900           gtk_widget_show (import_dialog);
1901         }
1902     }
1903 }
1904
1905 static void
1906 empathy_accounts_dialog_class_init (EmpathyAccountsDialogClass *klass)
1907 {
1908   GObjectClass *oclass = G_OBJECT_CLASS (klass);
1909   GParamSpec *param_spec;
1910
1911   oclass->constructor = do_constructor;
1912   oclass->dispose = do_dispose;
1913   oclass->constructed = do_constructed;
1914   oclass->set_property = do_set_property;
1915   oclass->get_property = do_get_property;
1916
1917   param_spec = g_param_spec_object ("parent",
1918       "parent", "The parent window",
1919       GTK_TYPE_WINDOW,
1920       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
1921   g_object_class_install_property (oclass, PROP_PARENT, param_spec);
1922
1923   g_type_class_add_private (klass, sizeof (EmpathyAccountsDialogPriv));
1924 }
1925
1926 static void
1927 empathy_accounts_dialog_init (EmpathyAccountsDialog *dialog)
1928 {
1929   EmpathyAccountsDialogPriv *priv;
1930
1931   priv = G_TYPE_INSTANCE_GET_PRIVATE ((dialog),
1932       EMPATHY_TYPE_ACCOUNTS_DIALOG,
1933       EmpathyAccountsDialogPriv);
1934   dialog->priv = priv;
1935 }
1936
1937 /* public methods */
1938
1939 GtkWidget *
1940 empathy_accounts_dialog_show (GtkWindow *parent,
1941     TpAccount *selected_account)
1942 {
1943   EmpathyAccountsDialog *dialog;
1944   EmpathyAccountsDialogPriv *priv;
1945
1946   dialog = g_object_new (EMPATHY_TYPE_ACCOUNTS_DIALOG,
1947       "parent", parent, NULL);
1948
1949   priv = GET_PRIV (dialog);
1950
1951   if (selected_account)
1952     {
1953       if (priv->cms != NULL && empathy_connection_managers_is_ready (priv->cms))
1954         accounts_dialog_set_selected_account (dialog, selected_account);
1955       else
1956         /* save the selection to set it later when the cms
1957          * becomes ready.
1958          */
1959         priv->initial_selection = g_object_ref (selected_account);
1960     }
1961
1962   gtk_window_present (GTK_WINDOW (priv->window));
1963   /* EmpathyAccountsDialog kepts a ref on itself until the dialog is
1964    * destroyed so we can release the ref returned by the constructor now. */
1965   g_object_unref (dialog);
1966
1967   return priv->window;
1968 }