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