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