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