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