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