]> git.0d.be Git - empathy.git/blob - src/empathy-accounts-dialog.c
accounts-dialog: move the 'no protocol' message to a different page
[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  *          Danielle Madeley <danielle.madeley@collabora.co.uk>
25  */
26
27 #include <config.h>
28
29 #include <string.h>
30 #include <stdlib.h>
31
32 #include <gtk/gtk.h>
33 #include <glib/gi18n-lib.h>
34 #include <dbus/dbus-glib.h>
35 #include <gio/gdesktopappinfo.h>
36
37 #include <telepathy-glib/account-manager.h>
38 #include <telepathy-glib/defs.h>
39 #include <telepathy-glib/util.h>
40
41 #include <libempathy/empathy-utils.h>
42 #include <libempathy/empathy-connection-managers.h>
43 #include <libempathy/empathy-connectivity.h>
44 #include <libempathy/empathy-tp-contact-factory.h>
45
46 #include <libempathy-gtk/empathy-ui-utils.h>
47 #include <libempathy-gtk/empathy-protocol-chooser.h>
48 #include <libempathy-gtk/empathy-account-widget.h>
49 #include <libempathy-gtk/empathy-account-widget-irc.h>
50 #include <libempathy-gtk/empathy-account-widget-sip.h>
51 #include <libempathy-gtk/empathy-cell-renderer-activatable.h>
52 #include <libempathy-gtk/empathy-contact-widget.h>
53 #include <libempathy-gtk/empathy-images.h>
54 #include <libempathy-gtk/empathy-new-account-dialog.h>
55
56 #include "empathy-accounts-dialog.h"
57 #include "empathy-import-dialog.h"
58 #include "empathy-import-utils.h"
59
60 #define DEBUG_FLAG EMPATHY_DEBUG_ACCOUNT
61 #include <libempathy/empathy-debug.h>
62
63 /* Flashing delay for icons (milliseconds). */
64 #define FLASH_TIMEOUT 500
65
66 /* The primary text of the dialog shown to the user when he is about to lose
67  * unsaved changes */
68 #define PENDING_CHANGES_QUESTION_PRIMARY_TEXT \
69   _("There are unsaved modifications to your %s account.")
70 /* The primary text of the dialog shown to the user when he is about to lose
71  * an unsaved new account */
72 #define UNSAVED_NEW_ACCOUNT_QUESTION_PRIMARY_TEXT \
73   _("Your new account has not been saved yet.")
74
75 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyAccountsDialog)
76 G_DEFINE_TYPE (EmpathyAccountsDialog, empathy_accounts_dialog, GTK_TYPE_DIALOG);
77
78 enum
79 {
80   NOTEBOOK_PAGE_ACCOUNT = 0,
81   NOTEBOOK_PAGE_LOADING,
82   NOTEBOOK_PAGE_NO_PROTOCOL
83 };
84
85 typedef struct {
86   GtkWidget *alignment_settings;
87   GtkWidget *alignment_infobar;
88
89   GtkWidget *vbox_details;
90   GtkWidget *infobar;
91   GtkWidget *label_status;
92   GtkWidget *image_status;
93   GtkWidget *throbber;
94   GtkWidget *enabled_switch;
95
96   GtkWidget *treeview;
97
98   GtkWidget *button_add;
99   GtkWidget *button_remove;
100   GtkWidget *button_import;
101
102   GtkWidget *image_type;
103   GtkWidget *label_name;
104   GtkWidget *label_type;
105   GtkWidget *dialog_content;
106
107   GtkWidget *notebook_account;
108   GtkWidget *spinner;
109   gboolean loading;
110
111   /* We have to keep a weak reference on the actual EmpathyAccountWidget, not
112    * just its GtkWidget. It is the only reliable source we can query to know if
113    * there are any unsaved changes to the currently selected account. We can't
114    * look at the account settings because it does not contain everything that
115    * can be changed using the EmpathyAccountWidget. For instance, it does not
116    * contain the state of the "Enabled" checkbox.
117    *
118    * Even if we create it ourself, we just get a weak ref and not a strong one
119    * as EmpathyAccountWidget unrefs itself when the GtkWidget is destroyed.
120    * That's kinda ugly; cf bgo #640417.
121    *
122    * */
123   EmpathyAccountWidget *setting_widget_object;
124
125   gboolean  connecting_show;
126   guint connecting_id;
127
128   gulong  settings_ready_id;
129   EmpathyAccountSettings *settings_ready;
130
131   TpAccountManager *account_manager;
132   EmpathyConnectionManagers *cms;
133   EmpathyConnectivity *connectivity;
134
135   GtkWindow *parent_window;
136   TpAccount *initial_selection;
137
138   /* Those are needed when changing the selected row. When a user selects
139    * another account and there are unsaved changes on the currently selected
140    * one, a confirmation message box is presented to him. Since his answer
141    * is retrieved asynchronously, we keep some information as member of the
142    * EmpathyAccountsDialog object. */
143   gboolean force_change_row;
144   GtkTreeRowReference *destination_row;
145 } EmpathyAccountsDialogPriv;
146
147 enum {
148   COL_NAME,
149   COL_STATUS,
150   COL_ACCOUNT,
151   COL_ACCOUNT_SETTINGS,
152   COL_COUNT
153 };
154
155 enum {
156   PROP_PARENT = 1
157 };
158
159 static EmpathyAccountSettings * accounts_dialog_model_get_selected_settings (
160     EmpathyAccountsDialog *dialog);
161
162 static void accounts_dialog_model_select_first (EmpathyAccountsDialog *dialog);
163
164 static void accounts_dialog_update_settings (EmpathyAccountsDialog *dialog,
165     EmpathyAccountSettings *settings);
166
167 static void accounts_dialog_model_set_selected (EmpathyAccountsDialog *dialog,
168     TpAccount *account);
169
170 static void accounts_dialog_connection_changed_cb (TpAccount *account,
171     guint old_status,
172     guint current,
173     guint reason,
174     gchar *dbus_error_name,
175     GHashTable *details,
176     EmpathyAccountsDialog *dialog);
177
178 static void accounts_dialog_presence_changed_cb (TpAccount *account,
179     guint presence,
180     gchar *status,
181     gchar *status_message,
182     EmpathyAccountsDialog *dialog);
183
184 static void accounts_dialog_model_selection_changed (
185     GtkTreeSelection *selection,
186     EmpathyAccountsDialog *dialog);
187
188 static gboolean accounts_dialog_has_pending_change (
189     EmpathyAccountsDialog *dialog, TpAccount **account);
190
191 static void
192 accounts_dialog_update_name_label (EmpathyAccountsDialog *dialog,
193     const gchar *display_name)
194 {
195   gchar *text;
196   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
197
198   text = g_markup_printf_escaped ("<b>%s</b>", display_name);
199   gtk_label_set_markup (GTK_LABEL (priv->label_name), text);
200
201   g_free (text);
202 }
203
204 static void
205 accounts_dialog_status_infobar_set_message (EmpathyAccountsDialog *dialog,
206     const gchar *message)
207 {
208   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
209   gchar *message_markup;
210
211   message_markup = g_markup_printf_escaped ("<i>%s</i>", message);
212   gtk_label_set_markup (GTK_LABEL (priv->label_status), message_markup);
213
214   gtk_widget_set_tooltip_text (priv->label_status, message);
215
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 gboolean
501 accounts_dialog_has_valid_accounts (EmpathyAccountsDialog *dialog)
502 {
503   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
504   GtkTreeModel *model;
505   GtkTreeIter iter;
506   gboolean creating;
507
508   g_object_get (priv->setting_widget_object,
509       "creating-account", &creating, NULL);
510
511   if (!creating)
512     return TRUE;
513
514   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
515
516   if (gtk_tree_model_get_iter_first (model, &iter))
517     return gtk_tree_model_iter_next (model, &iter);
518
519   return FALSE;
520 }
521
522 static void
523 account_dialog_create_edit_params_dialog (EmpathyAccountsDialog *dialog)
524 {
525   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
526   EmpathyAccountSettings *settings;
527   GtkWidget *subdialog, *content, *content_area, *align;
528
529   settings = accounts_dialog_model_get_selected_settings (dialog);
530
531   subdialog = gtk_dialog_new_with_buttons (_("Edit Connection Parameters"),
532       GTK_WINDOW (dialog),
533       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
534       NULL, NULL);
535
536   priv->setting_widget_object =
537     empathy_account_widget_new_for_protocol (settings, FALSE);
538
539   g_object_add_weak_pointer (G_OBJECT (priv->setting_widget_object),
540       (gpointer *) &priv->setting_widget_object);
541
542   if (accounts_dialog_has_valid_accounts (dialog))
543     empathy_account_widget_set_other_accounts_exist (
544         priv->setting_widget_object, TRUE);
545
546   content = empathy_account_widget_get_widget (priv->setting_widget_object);
547
548   g_signal_connect (priv->setting_widget_object, "cancelled",
549           G_CALLBACK (empathy_account_dialog_widget_cancelled_cb), dialog);
550
551   g_signal_connect_swapped (priv->setting_widget_object, "close",
552       G_CALLBACK (gtk_widget_destroy), subdialog);
553
554   content_area = gtk_dialog_get_content_area (GTK_DIALOG (subdialog));
555
556   align = gtk_alignment_new (0.5, 0.5, 1, 1);
557   gtk_alignment_set_padding (GTK_ALIGNMENT (align), 6, 0, 6, 6);
558
559   gtk_container_add (GTK_CONTAINER (align), content);
560   gtk_box_pack_start (GTK_BOX (content_area), align, TRUE, TRUE, 0);
561
562   gtk_widget_show (content);
563   gtk_widget_show (align);
564   gtk_widget_show (subdialog);
565 }
566
567 static void
568 start_external_app (GAppInfo *app_info)
569 {
570   GError *error = NULL;
571   GdkAppLaunchContext *context = NULL;
572   GdkDisplay *display;
573
574   display = gdk_display_get_default ();
575   context = gdk_display_get_app_launch_context (display);
576
577   if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
578         &error))
579     {
580       g_critical ("Failed to bisho: %s", error->message);
581       g_clear_error (&error);
582     }
583
584   tp_clear_object (&context);
585 }
586
587 static void
588 use_external_storage_provider (EmpathyAccountsDialog *self,
589     TpAccount *account)
590 {
591   const gchar *provider;
592
593   provider = tp_account_get_storage_provider (account);
594   if (!tp_strdiff (provider, "com.meego.libsocialweb"))
595     {
596       GDesktopAppInfo *desktop_info;
597       gchar *cmd;
598       GAppInfo *app_info;
599       GError *error = NULL;
600
601       desktop_info = g_desktop_app_info_new ("gnome-control-center.desktop");
602       if (desktop_info == NULL)
603         {
604           g_critical ("Could not locate 'gnome-control-center.desktop'");
605           return;
606         }
607
608       /* glib doesn't have API to start a desktop file with args... (#637875) */
609       cmd = g_strdup_printf ("%s bisho.desktop", g_app_info_get_commandline (
610             (GAppInfo *) desktop_info));
611
612       app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
613
614       if (app_info == NULL)
615         {
616           DEBUG ("Failed to create app info: %s", error->message);
617           g_error_free (error);
618         }
619       else
620         {
621           start_external_app (app_info);
622           g_object_unref (app_info);
623         }
624
625       g_object_unref (desktop_info);
626       g_free (cmd);
627       return;
628     }
629   else if (!tp_strdiff (provider, "org.gnome.OnlineAccounts"))
630     {
631       GDesktopAppInfo *desktop_info;
632
633       desktop_info = g_desktop_app_info_new (
634           "gnome-online-accounts-panel.desktop");
635       if (desktop_info == NULL)
636         {
637           g_critical ("Could not locate 'gnome-online-accounts-panel.desktop'");
638         }
639       else
640         {
641           start_external_app (G_APP_INFO (desktop_info));
642           g_object_unref (desktop_info);
643         }
644
645       return;
646     }
647   else
648     {
649       DEBUG ("Don't know how to handle %s", provider);
650       return;
651     }
652 }
653
654 static void
655 account_dialow_show_edit_params_dialog (EmpathyAccountsDialog *dialog,
656     GtkButton *button)
657 {
658   EmpathyAccountSettings *settings;
659   TpAccount *account;
660   TpStorageRestrictionFlags storage_restrictions;
661
662   settings = accounts_dialog_model_get_selected_settings (dialog);
663
664   account = empathy_account_settings_get_account (settings);
665   g_return_if_fail (account != NULL);
666
667   storage_restrictions = tp_account_get_storage_restrictions (account);
668
669   /* Empathy can only edit accounts without the Cannot_Set_Parameters flag */
670   if (storage_restrictions & TP_STORAGE_RESTRICTION_FLAG_CANNOT_SET_PARAMETERS)
671     {
672       DEBUG ("Account is provided by an external storage provider");
673
674       use_external_storage_provider (dialog, account);
675     }
676   else
677     {
678       account_dialog_create_edit_params_dialog (dialog);
679     }
680 }
681
682 static void
683 account_dialog_show_contact_details_failed (EmpathyAccountsDialog *dialog,
684     gboolean error)
685 {
686   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
687   GtkWidget *infobar, *label;
688
689   infobar = gtk_info_bar_new ();
690
691   if (error)
692     {
693       gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar), GTK_MESSAGE_ERROR);
694       label = gtk_label_new (_("Failed to retrieve your personal information "
695                                "from the server."));
696     }
697   else
698     {
699       gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar), GTK_MESSAGE_INFO);
700       label = gtk_label_new (_("Go online to edit your personal information."));
701     }
702
703   gtk_container_add (
704       GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (infobar))),
705       label);
706   gtk_box_pack_start (GTK_BOX (priv->dialog_content), infobar, FALSE, FALSE, 0);
707   gtk_widget_show_all (infobar);
708 }
709
710 static void
711 account_dialog_got_self_contact (TpConnection *conn,
712     EmpathyContact *contact,
713     const GError *in_error,
714     gpointer user_data,
715     GObject *dialog)
716 {
717   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
718   GtkWidget *editor, *alig;
719
720   if (in_error != NULL)
721     {
722       DEBUG ("Failed to get self-contact: %s", in_error->message);
723       account_dialog_show_contact_details_failed (
724           EMPATHY_ACCOUNTS_DIALOG (dialog), TRUE);
725       return;
726     }
727
728   alig = gtk_alignment_new (0.5, 0, 1, 1);
729
730   /* create the contact info editor for this account */
731   editor = empathy_contact_widget_new (contact,
732       EMPATHY_CONTACT_WIDGET_EDIT_ALIAS |
733       EMPATHY_CONTACT_WIDGET_EDIT_AVATAR |
734       EMPATHY_CONTACT_WIDGET_NO_STATUS |
735       EMPATHY_CONTACT_WIDGET_EDIT_DETAILS);
736
737   gtk_box_pack_start (GTK_BOX (priv->dialog_content), alig, TRUE, TRUE, 0);
738   gtk_container_add (GTK_CONTAINER (alig), editor);
739   gtk_widget_show (alig);
740   gtk_widget_show (editor);
741 }
742
743 static void
744 account_dialog_create_dialog_content (EmpathyAccountsDialog *dialog,
745     EmpathyAccountSettings *settings)
746 {
747   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
748   const gchar *icon_name;
749   TpAccount *account;
750   TpConnection *conn = NULL;
751   GtkWidget *bbox, *button;
752
753   account = empathy_account_settings_get_account (settings);
754
755   // if (priv->setting_widget_object != NULL)
756   //   g_object_remove_weak_pointer (G_OBJECT (priv->setting_widget_object),
757   //       (gpointer *) &priv->setting_widget_object);
758
759   priv->dialog_content = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
760   gtk_container_add (GTK_CONTAINER (priv->alignment_settings),
761       priv->dialog_content);
762   gtk_widget_show (priv->dialog_content);
763
764   /* request the self contact */
765   if (account != NULL)
766     conn = tp_account_get_connection (account);
767
768   if (conn != NULL)
769     {
770       empathy_tp_contact_factory_get_from_handle (conn,
771           tp_connection_get_self_handle (conn),
772           account_dialog_got_self_contact,
773           NULL, NULL, G_OBJECT (dialog));
774     }
775   else
776     {
777       account_dialog_show_contact_details_failed (dialog, FALSE);
778     }
779
780   bbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
781   gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
782   gtk_box_pack_end (GTK_BOX (priv->dialog_content), bbox, FALSE, TRUE, 0);
783   gtk_widget_show (bbox);
784
785   button = gtk_button_new_with_mnemonic (_("_Edit Connection Parameters..."));
786   gtk_box_pack_start (GTK_BOX (bbox), button, FALSE, TRUE, 0);
787   gtk_widget_show (button);
788   g_signal_connect_swapped (button, "clicked",
789       G_CALLBACK (account_dialow_show_edit_params_dialog), dialog);
790
791   icon_name = empathy_account_settings_get_icon_name (settings);
792
793   if (!gtk_icon_theme_has_icon (gtk_icon_theme_get_default (),
794           icon_name))
795     /* show the default icon; keep this in sync with the default
796      * one in empathy-accounts-dialog.ui.
797      */
798     icon_name = GTK_STOCK_CUT;
799
800   gtk_image_set_from_icon_name (GTK_IMAGE (priv->image_type),
801       icon_name, GTK_ICON_SIZE_DIALOG);
802   gtk_widget_set_tooltip_text (priv->image_type,
803       empathy_protocol_name_to_display_name
804       (empathy_account_settings_get_protocol (settings)));
805   gtk_widget_show (priv->image_type);
806
807   accounts_dialog_update_name_label (dialog,
808       empathy_account_settings_get_display_name (settings));
809
810   accounts_dialog_update_status_infobar (dialog, account);
811 }
812
813 static void
814 account_dialog_settings_ready_cb (EmpathyAccountSettings *settings,
815     GParamSpec *spec,
816     EmpathyAccountsDialog *dialog)
817 {
818   if (empathy_account_settings_is_ready (settings))
819     account_dialog_create_dialog_content (dialog, settings);
820 }
821
822 static void
823 accounts_dialog_model_select_first (EmpathyAccountsDialog *dialog)
824 {
825   GtkTreeView      *view;
826   GtkTreeModel     *model;
827   GtkTreeSelection *selection;
828   GtkTreeIter       iter;
829   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
830
831   /* select first */
832   view = GTK_TREE_VIEW (priv->treeview);
833   model = gtk_tree_view_get_model (view);
834
835   if (gtk_tree_model_get_iter_first (model, &iter))
836     {
837       selection = gtk_tree_view_get_selection (view);
838       gtk_tree_selection_select_iter (selection, &iter);
839     }
840   else
841     {
842       accounts_dialog_update_settings (dialog, NULL);
843     }
844 }
845
846 static gboolean
847 accounts_dialog_has_pending_change (EmpathyAccountsDialog *dialog,
848     TpAccount **account)
849 {
850   GtkTreeIter iter;
851   GtkTreeModel *model;
852   GtkTreeSelection *selection;
853   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
854
855   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
856
857   if (gtk_tree_selection_get_selected (selection, &model, &iter))
858     gtk_tree_model_get (model, &iter, COL_ACCOUNT, account, -1);
859
860   return priv->setting_widget_object != NULL
861       && empathy_account_widget_contains_pending_changes (
862           priv->setting_widget_object);
863 }
864
865 static void
866 accounts_dialog_show_question_dialog (EmpathyAccountsDialog *dialog,
867     const gchar *primary_text,
868     const gchar *secondary_text,
869     GCallback response_callback,
870     gpointer user_data,
871     const gchar *first_button_text,
872     ...)
873 {
874   va_list button_args;
875   GtkWidget *message_dialog;
876   const gchar *button_text;
877
878   message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
879       GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
880       GTK_MESSAGE_QUESTION,
881       GTK_BUTTONS_NONE,
882       "%s", primary_text);
883
884   gtk_message_dialog_format_secondary_text (
885       GTK_MESSAGE_DIALOG (message_dialog), "%s", secondary_text);
886
887   va_start (button_args, first_button_text);
888   for (button_text = first_button_text;
889        button_text;
890        button_text = va_arg (button_args, const gchar *))
891     {
892       gint response_id;
893       response_id = va_arg (button_args, gint);
894
895       gtk_dialog_add_button (GTK_DIALOG (message_dialog), button_text,
896           response_id);
897     }
898   va_end (button_args);
899
900   g_signal_connect (message_dialog, "response", response_callback, user_data);
901
902   gtk_widget_show (message_dialog);
903 }
904
905 static gchar *
906 get_dialog_primary_text (TpAccount *account)
907 {
908   if (account != NULL)
909     {
910       /* Existing account */
911       return g_strdup_printf (PENDING_CHANGES_QUESTION_PRIMARY_TEXT,
912           tp_account_get_display_name (account));
913     }
914   else
915     {
916       /* Newly created account */
917       return g_strdup (UNSAVED_NEW_ACCOUNT_QUESTION_PRIMARY_TEXT);
918     }
919 }
920
921 static void
922 accounts_dialog_button_add_clicked_cb (GtkWidget *button,
923     EmpathyAccountsDialog *self)
924 {
925   GtkWidget *dialog;
926   gint response;
927
928   dialog = empathy_new_account_dialog_new (GTK_WINDOW (self));
929   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
930
931   response = gtk_dialog_run (GTK_DIALOG (dialog));
932
933   if (response == GTK_RESPONSE_OK)
934     {
935       EmpathyAccountSettings *settings;
936       TpAccount *account;
937
938       settings = empathy_new_account_dialog_get_settings (
939           EMPATHY_NEW_ACCOUNT_DIALOG (dialog));
940
941       /* The newly created account has already been added by
942        * accounts_dialog_account_validity_changed_cb so we just
943        * have to select it. */
944       account = empathy_account_settings_get_account (settings);
945       accounts_dialog_model_set_selected (self, account);
946     }
947
948   gtk_widget_destroy (dialog);
949 }
950
951 static void
952 accounts_dialog_update_settings (EmpathyAccountsDialog *dialog,
953     EmpathyAccountSettings *settings)
954 {
955   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
956
957   if (priv->settings_ready != NULL)
958     {
959       g_signal_handler_disconnect (priv->settings_ready,
960           priv->settings_ready_id);
961       priv->settings_ready = NULL;
962       priv->settings_ready_id = 0;
963     }
964
965   if (!settings)
966     {
967       GtkTreeView  *view;
968       GtkTreeModel *model;
969       GtkTreeSelection *selection;
970
971       view = GTK_TREE_VIEW (priv->treeview);
972       model = gtk_tree_view_get_model (view);
973       selection = gtk_tree_view_get_selection (view);
974
975       if (gtk_tree_model_iter_n_children (model, NULL) > 0)
976         {
977           /* We have configured accounts, select the first one if there
978            * is no other account selected already. */
979           if (!gtk_tree_selection_get_selected (selection, NULL, NULL))
980             accounts_dialog_model_select_first (dialog);
981
982           return;
983         }
984
985       /* No account and no profile, warn the user */
986       gtk_widget_hide (priv->vbox_details);
987       gtk_widget_set_sensitive (priv->button_add, FALSE);
988
989       gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook_account),
990           NOTEBOOK_PAGE_NO_PROTOCOL);
991       return;
992     }
993
994   /* We have an account selected, destroy old settings and create a new
995    * one for the account selected */
996   gtk_widget_show (priv->vbox_details);
997
998   if (priv->dialog_content)
999     {
1000       gtk_widget_destroy (priv->dialog_content);
1001       priv->dialog_content = NULL;
1002     }
1003
1004   if (empathy_account_settings_is_ready (settings))
1005     {
1006       account_dialog_create_dialog_content (dialog, settings);
1007     }
1008   else
1009     {
1010       priv->settings_ready = settings;
1011       priv->settings_ready_id =
1012         g_signal_connect (settings, "notify::ready",
1013             G_CALLBACK (account_dialog_settings_ready_cb), dialog);
1014     }
1015
1016 }
1017
1018 static void
1019 accounts_dialog_name_editing_started_cb (GtkCellRenderer *renderer,
1020     GtkCellEditable *editable,
1021     gchar *path,
1022     EmpathyAccountsDialog *dialog)
1023 {
1024   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1025
1026   if (priv->connecting_id)
1027     g_source_remove (priv->connecting_id);
1028
1029   DEBUG ("Editing account name started; stopping flashing");
1030 }
1031
1032 static const gchar *
1033 get_status_icon_for_account (EmpathyAccountsDialog *self,
1034     TpAccount *account)
1035 {
1036   EmpathyAccountsDialogPriv *priv = GET_PRIV (self);
1037   TpConnectionStatus status;
1038   TpConnectionStatusReason reason;
1039   TpConnectionPresenceType presence;
1040
1041   if (account == NULL)
1042     return empathy_icon_name_for_presence (TP_CONNECTION_PRESENCE_TYPE_OFFLINE);
1043
1044   if (!tp_account_is_enabled (account))
1045     return empathy_icon_name_for_presence (TP_CONNECTION_PRESENCE_TYPE_OFFLINE);
1046
1047   status = tp_account_get_connection_status (account, &reason);
1048
1049   if (status == TP_CONNECTION_STATUS_DISCONNECTED)
1050     {
1051       if (reason != TP_CONNECTION_STATUS_REASON_REQUESTED)
1052         /* An error occured */
1053         return GTK_STOCK_DIALOG_ERROR;
1054
1055       presence = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
1056     }
1057   else if (status == TP_CONNECTION_STATUS_CONNECTING)
1058     {
1059       /* Account is connecting. Display a blinking account alternating between
1060        * the offline icon and the requested presence. */
1061       if (priv->connecting_show)
1062         presence = tp_account_get_requested_presence (account, NULL, NULL);
1063       else
1064         presence = TP_CONNECTION_PRESENCE_TYPE_OFFLINE;
1065     }
1066   else
1067     {
1068       /* status == TP_CONNECTION_STATUS_CONNECTED */
1069       presence = tp_account_get_current_presence (account, NULL, NULL);
1070
1071       /* If presence is Unset (CM doesn't implement SimplePresence),
1072        * display the 'available' icon.
1073        * We also check Offline because of this MC5 bug: fd.o #26060 */
1074       if (presence == TP_CONNECTION_PRESENCE_TYPE_OFFLINE ||
1075           presence == TP_CONNECTION_PRESENCE_TYPE_UNSET)
1076         presence = TP_CONNECTION_PRESENCE_TYPE_AVAILABLE;
1077     }
1078
1079   return empathy_icon_name_for_presence (presence);
1080 }
1081
1082 static void
1083 accounts_dialog_model_status_pixbuf_data_func (GtkTreeViewColumn *tree_column,
1084     GtkCellRenderer *cell,
1085     GtkTreeModel *model,
1086     GtkTreeIter *iter,
1087     EmpathyAccountsDialog *dialog)
1088 {
1089   TpAccount *account;
1090
1091   gtk_tree_model_get (model, iter, COL_ACCOUNT, &account, -1);
1092
1093   g_object_set (cell,
1094       "icon-name", get_status_icon_for_account (dialog, account),
1095       NULL);
1096
1097   if (account != NULL)
1098     g_object_unref (account);
1099 }
1100
1101 static void
1102 accounts_dialog_model_protocol_pixbuf_data_func (GtkTreeViewColumn *tree_column,
1103     GtkCellRenderer *cell,
1104     GtkTreeModel *model,
1105     GtkTreeIter *iter,
1106     EmpathyAccountsDialog *dialog)
1107 {
1108   EmpathyAccountSettings  *settings;
1109   gchar              *icon_name;
1110   GdkPixbuf          *pixbuf;
1111   TpConnectionStatus  status;
1112
1113   gtk_tree_model_get (model, iter,
1114       COL_STATUS, &status,
1115       COL_ACCOUNT_SETTINGS, &settings,
1116       -1);
1117
1118   icon_name = empathy_account_settings_get_icon_name (settings);
1119   pixbuf = empathy_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON);
1120
1121   g_object_set (cell,
1122       "visible", TRUE,
1123       "pixbuf", pixbuf,
1124       NULL);
1125
1126   g_object_unref (settings);
1127
1128   if (pixbuf)
1129     g_object_unref (pixbuf);
1130 }
1131
1132 static gboolean
1133 accounts_dialog_row_changed_foreach (GtkTreeModel *model,
1134     GtkTreePath *path,
1135     GtkTreeIter *iter,
1136     gpointer user_data)
1137 {
1138   TpAccount *account;
1139
1140   gtk_tree_model_get (model, iter, COL_ACCOUNT, &account, -1);
1141
1142   if (account == NULL)
1143     return FALSE;
1144
1145   if (tp_account_get_connection_status (account, NULL) ==
1146       TP_CONNECTION_STATUS_CONNECTING)
1147     {
1148       /* Only update the row where we have a connecting account as that's the
1149        * ones having a blinking icon. */
1150       gtk_tree_model_row_changed (model, path, iter);
1151     }
1152
1153   g_object_unref (account);
1154   return FALSE;
1155 }
1156
1157 static gboolean
1158 accounts_dialog_flash_connecting_cb (EmpathyAccountsDialog *dialog)
1159 {
1160   GtkTreeView  *view;
1161   GtkTreeModel *model;
1162   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1163
1164   priv->connecting_show = !priv->connecting_show;
1165
1166   view = GTK_TREE_VIEW (priv->treeview);
1167   model = gtk_tree_view_get_model (view);
1168
1169   gtk_tree_model_foreach (model, accounts_dialog_row_changed_foreach, NULL);
1170
1171   return TRUE;
1172 }
1173
1174 static void
1175 accounts_dialog_name_edited_cb (GtkCellRendererText *renderer,
1176     gchar *path,
1177     gchar *new_text,
1178     EmpathyAccountsDialog *dialog)
1179 {
1180   EmpathyAccountSettings    *settings;
1181   GtkTreeModel *model;
1182   GtkTreePath  *treepath;
1183   GtkTreeIter   iter;
1184   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1185   gboolean connecting;
1186
1187   empathy_account_manager_get_accounts_connected (&connecting);
1188
1189   if (connecting)
1190     {
1191       priv->connecting_id = g_timeout_add (FLASH_TIMEOUT,
1192           (GSourceFunc) accounts_dialog_flash_connecting_cb,
1193           dialog);
1194     }
1195
1196   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1197   treepath = gtk_tree_path_new_from_string (path);
1198   gtk_tree_model_get_iter (model, &iter, treepath);
1199   gtk_tree_model_get (model, &iter,
1200       COL_ACCOUNT_SETTINGS, &settings,
1201       -1);
1202   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1203       COL_NAME, new_text,
1204       -1);
1205   gtk_tree_path_free (treepath);
1206
1207   empathy_account_settings_set_display_name_async (settings, new_text,
1208       NULL, NULL);
1209   g_object_set (settings, "display-name-overridden", TRUE, NULL);
1210   g_object_unref (settings);
1211 }
1212
1213 static void
1214 accounts_dialog_delete_account_response_cb (GtkDialog *message_dialog,
1215   gint response_id,
1216   gpointer user_data)
1217 {
1218   TpAccount *account;
1219   GtkTreeModel *model;
1220   GtkTreeIter iter;
1221   GtkTreeSelection *selection;
1222   EmpathyAccountsDialog *account_dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
1223   EmpathyAccountsDialogPriv *priv = GET_PRIV (account_dialog);
1224
1225   if (response_id == GTK_RESPONSE_YES)
1226     {
1227       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1228
1229       if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1230         return;
1231
1232       gtk_tree_model_get (model, &iter, COL_ACCOUNT, &account, -1);
1233
1234       if (account != NULL)
1235         {
1236           tp_account_remove_async (account, NULL, NULL);
1237           g_object_unref (account);
1238           account = NULL;
1239         }
1240
1241       /* No need to call accounts_dialog_model_selection_changed while
1242        * removing as we are going to call accounts_dialog_model_select_first
1243        * right after which will update the selection. */
1244       g_signal_handlers_block_by_func (selection,
1245           accounts_dialog_model_selection_changed, account_dialog);
1246
1247       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
1248
1249       g_signal_handlers_unblock_by_func (selection,
1250           accounts_dialog_model_selection_changed, account_dialog);
1251
1252       accounts_dialog_model_select_first (account_dialog);
1253     }
1254
1255   gtk_widget_destroy (GTK_WIDGET (message_dialog));
1256 }
1257
1258 static void
1259 accounts_dialog_remove_account_iter (EmpathyAccountsDialog *dialog,
1260     GtkTreeIter *iter)
1261 {
1262   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1263   TpAccount *account;
1264   GtkTreeModel *model;
1265   gchar *question_dialog_primary_text;
1266
1267   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1268
1269   gtk_tree_model_get (model, iter, COL_ACCOUNT, &account, -1);
1270
1271   if (account == NULL || !tp_account_is_valid (account))
1272     {
1273       if (account != NULL)
1274         g_object_unref (account);
1275       gtk_list_store_remove (GTK_LIST_STORE (model), iter);
1276       accounts_dialog_model_select_first (dialog);
1277       return;
1278     }
1279
1280   question_dialog_primary_text = g_strdup_printf (
1281       _("Do you want to remove %s from your computer?"),
1282       tp_account_get_display_name (account));
1283
1284   accounts_dialog_show_question_dialog (dialog, question_dialog_primary_text,
1285       _("This will not remove your account on the server."),
1286       G_CALLBACK (accounts_dialog_delete_account_response_cb),
1287       dialog,
1288       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1289       GTK_STOCK_REMOVE, GTK_RESPONSE_YES, NULL);
1290
1291   g_free (question_dialog_primary_text);
1292   g_object_unref (account);
1293 }
1294
1295 static void
1296 accounts_dialog_button_remove_clicked_cb (GtkWidget *button,
1297     EmpathyAccountsDialog *dialog)
1298 {
1299   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1300   GtkTreeView  *view;
1301   GtkTreeSelection *selection;
1302   GtkTreeIter iter;
1303
1304   view = GTK_TREE_VIEW (priv->treeview);
1305   selection = gtk_tree_view_get_selection (view);
1306   if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
1307       return;
1308
1309   accounts_dialog_remove_account_iter (dialog, &iter);
1310 }
1311
1312 #ifdef HAVE_MEEGO
1313 static void
1314 accounts_dialog_view_delete_activated_cb (EmpathyCellRendererActivatable *cell,
1315     const gchar *path_string,
1316     EmpathyAccountsDialog *dialog)
1317 {
1318   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1319   GtkTreeModel *model;
1320   GtkTreeIter iter;
1321
1322   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1323   if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
1324     return;
1325
1326   accounts_dialog_remove_account_iter (dialog, &iter);
1327 }
1328 #endif /* HAVE_MEEGO */
1329
1330 static void
1331 accounts_dialog_model_add_columns (EmpathyAccountsDialog *dialog)
1332 {
1333   GtkTreeView       *view;
1334   GtkTreeViewColumn *column;
1335   GtkCellRenderer   *cell;
1336   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1337
1338   view = GTK_TREE_VIEW (priv->treeview);
1339   gtk_tree_view_set_headers_visible (view, FALSE);
1340
1341   /* Account column */
1342   column = gtk_tree_view_column_new ();
1343   gtk_tree_view_column_set_expand (column, TRUE);
1344   gtk_tree_view_append_column (view, column);
1345
1346   /* Status icon renderer */
1347   cell = gtk_cell_renderer_pixbuf_new ();
1348   gtk_tree_view_column_pack_start (column, cell, FALSE);
1349   gtk_tree_view_column_set_cell_data_func (column, cell,
1350       (GtkTreeCellDataFunc)
1351       accounts_dialog_model_status_pixbuf_data_func,
1352       dialog,
1353       NULL);
1354
1355   /* Protocol icon renderer */
1356   cell = gtk_cell_renderer_pixbuf_new ();
1357   gtk_tree_view_column_pack_start (column, cell, FALSE);
1358   gtk_tree_view_column_set_cell_data_func (column, cell,
1359       (GtkTreeCellDataFunc)
1360       accounts_dialog_model_protocol_pixbuf_data_func,
1361       dialog,
1362       NULL);
1363
1364   /* Name renderer */
1365   cell = gtk_cell_renderer_text_new ();
1366   g_object_set (cell,
1367       "ellipsize", PANGO_ELLIPSIZE_END,
1368       "width-chars", 25,
1369       "editable", TRUE,
1370       NULL);
1371   gtk_tree_view_column_pack_start (column, cell, TRUE);
1372   gtk_tree_view_column_add_attribute (column, cell, "text", COL_NAME);
1373   g_signal_connect (cell, "edited",
1374       G_CALLBACK (accounts_dialog_name_edited_cb),
1375       dialog);
1376   g_signal_connect (cell, "editing-started",
1377       G_CALLBACK (accounts_dialog_name_editing_started_cb),
1378       dialog);
1379   g_object_set (cell, "ypad", 4, NULL);
1380
1381 #ifdef HAVE_MEEGO
1382   /* Delete column */
1383   cell = empathy_cell_renderer_activatable_new ();
1384   gtk_tree_view_column_pack_start (column, cell, FALSE);
1385   g_object_set (cell,
1386         "icon-name", GTK_STOCK_DELETE,
1387         "show-on-select", TRUE,
1388         NULL);
1389
1390   g_signal_connect (cell, "path-activated",
1391       G_CALLBACK (accounts_dialog_view_delete_activated_cb),
1392       dialog);
1393 #endif /* HAVE_MEEGO */
1394 }
1395
1396 static EmpathyAccountSettings *
1397 accounts_dialog_model_get_selected_settings (EmpathyAccountsDialog *dialog)
1398 {
1399   GtkTreeView      *view;
1400   GtkTreeModel     *model;
1401   GtkTreeSelection *selection;
1402   GtkTreeIter       iter;
1403   EmpathyAccountSettings   *settings;
1404   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1405
1406   view = GTK_TREE_VIEW (priv->treeview);
1407   selection = gtk_tree_view_get_selection (view);
1408
1409   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1410     return NULL;
1411
1412   gtk_tree_model_get (model, &iter,
1413       COL_ACCOUNT_SETTINGS, &settings, -1);
1414
1415   return settings;
1416 }
1417
1418 static void
1419 accounts_dialog_model_selection_changed (GtkTreeSelection *selection,
1420     EmpathyAccountsDialog *dialog)
1421 {
1422   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1423   EmpathyAccountSettings *settings;
1424   GtkTreeModel *model;
1425   GtkTreeIter   iter;
1426   gboolean      is_selection;
1427   gboolean creating = FALSE;
1428
1429   is_selection = gtk_tree_selection_get_selected (selection, &model, &iter);
1430
1431   settings = accounts_dialog_model_get_selected_settings (dialog);
1432   accounts_dialog_update_settings (dialog, settings);
1433
1434   if (settings != NULL)
1435     g_object_unref (settings);
1436
1437   if (priv->setting_widget_object != NULL)
1438     {
1439       g_object_get (priv->setting_widget_object,
1440           "creating-account", &creating, NULL);
1441     }
1442
1443   /* Update remove button sensitivity */
1444   gtk_widget_set_sensitive (priv->button_remove, is_selection && !creating &&
1445       !priv->loading);
1446 }
1447
1448 static void
1449 accounts_dialog_selection_change_response_cb (GtkDialog *message_dialog,
1450   gint response_id,
1451   gpointer *user_data)
1452 {
1453   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
1454   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1455
1456   gtk_widget_destroy (GTK_WIDGET (message_dialog));
1457
1458     if (response_id == GTK_RESPONSE_YES && priv->destination_row != NULL)
1459       {
1460         /* The user wants to lose unsaved changes to the currently selected
1461          * account and select another account. We discard the changes and
1462          * select the other account. */
1463         GtkTreePath *path;
1464         GtkTreeSelection *selection;
1465
1466         priv->force_change_row = TRUE;
1467         empathy_account_widget_discard_pending_changes (
1468             priv->setting_widget_object);
1469
1470         path = gtk_tree_row_reference_get_path (priv->destination_row);
1471         selection = gtk_tree_view_get_selection (
1472             GTK_TREE_VIEW (priv->treeview));
1473
1474         if (path != NULL)
1475           {
1476             /* This will trigger a call to
1477              * accounts_dialog_account_selection_change() */
1478             gtk_tree_selection_select_path (selection, path);
1479             gtk_tree_path_free (path);
1480           }
1481
1482         gtk_tree_row_reference_free (priv->destination_row);
1483       }
1484     else
1485       {
1486         priv->force_change_row = FALSE;
1487       }
1488 }
1489
1490 static gboolean
1491 accounts_dialog_account_selection_change (GtkTreeSelection *selection,
1492     GtkTreeModel *model,
1493     GtkTreePath *path,
1494     gboolean path_currently_selected,
1495     gpointer data)
1496 {
1497   TpAccount *account = NULL;
1498   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (data);
1499   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1500   gboolean ret;
1501
1502   if (priv->force_change_row)
1503     {
1504       /* We came back here because the user wants to discard changes to his
1505        * modified account. The changes have already been discarded so we
1506        * just change the selected row. */
1507       priv->force_change_row = FALSE;
1508       return TRUE;
1509     }
1510
1511   if (accounts_dialog_has_pending_change (dialog, &account))
1512     {
1513       /* The currently selected account has some unsaved changes. We ask
1514        * the user if he really wants to lose his changes and select another
1515        * account */
1516       gchar *question_dialog_primary_text = get_dialog_primary_text (account);
1517       priv->destination_row = gtk_tree_row_reference_new (model, path);
1518
1519       accounts_dialog_show_question_dialog (dialog,
1520           question_dialog_primary_text,
1521           _("You are about to select another account, which will discard\n"
1522               "your changes. Are you sure you want to proceed?"),
1523           G_CALLBACK (accounts_dialog_selection_change_response_cb),
1524           dialog,
1525           GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1526           GTK_STOCK_DISCARD, GTK_RESPONSE_YES, NULL);
1527
1528       g_free (question_dialog_primary_text);
1529       ret = FALSE;
1530     }
1531   else
1532     {
1533       ret = TRUE;
1534     }
1535
1536   if (account != NULL)
1537     g_object_unref (account);
1538
1539   return ret;
1540 }
1541
1542 static void
1543 accounts_dialog_model_setup (EmpathyAccountsDialog *dialog)
1544 {
1545   GtkListStore     *store;
1546   GtkTreeSelection *selection;
1547   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1548
1549   store = gtk_list_store_new (COL_COUNT,
1550       G_TYPE_STRING,         /* name */
1551       G_TYPE_UINT,           /* status */
1552       TP_TYPE_ACCOUNT,   /* account */
1553       EMPATHY_TYPE_ACCOUNT_SETTINGS); /* settings */
1554
1555   gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview),
1556       GTK_TREE_MODEL (store));
1557
1558   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1559   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1560   gtk_tree_selection_set_select_function (selection,
1561       accounts_dialog_account_selection_change, dialog, NULL);
1562
1563   g_signal_connect (selection, "changed",
1564       G_CALLBACK (accounts_dialog_model_selection_changed),
1565       dialog);
1566
1567   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
1568       COL_NAME, GTK_SORT_ASCENDING);
1569
1570   accounts_dialog_model_add_columns (dialog);
1571
1572   g_object_unref (store);
1573 }
1574
1575 static gboolean
1576 accounts_dialog_get_account_iter (EmpathyAccountsDialog *dialog,
1577     TpAccount *account,
1578     GtkTreeIter *iter)
1579 {
1580   GtkTreeView      *view;
1581   GtkTreeModel     *model;
1582   gboolean          ok;
1583   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1584
1585   /* Update the status in the model */
1586   view = GTK_TREE_VIEW (priv->treeview);
1587   model = gtk_tree_view_get_model (view);
1588
1589   for (ok = gtk_tree_model_get_iter_first (model, iter);
1590        ok;
1591        ok = gtk_tree_model_iter_next (model, iter))
1592     {
1593       TpAccount *this_account;
1594       gboolean   equal;
1595
1596       gtk_tree_model_get (model, iter,
1597           COL_ACCOUNT, &this_account,
1598           -1);
1599
1600       equal = (this_account == account);
1601       g_object_unref (this_account);
1602
1603       if (equal)
1604         return TRUE;
1605     }
1606
1607   return FALSE;
1608 }
1609
1610 static void
1611 select_and_scroll_to_iter (EmpathyAccountsDialog *dialog,
1612     GtkTreeIter *iter)
1613 {
1614   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1615   GtkTreeSelection *selection;
1616   GtkTreePath *path;
1617   GtkTreeModel *model;
1618
1619   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1620
1621   gtk_tree_selection_select_iter (selection, iter);
1622
1623   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1624   path = gtk_tree_model_get_path (model, iter);
1625
1626   gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->treeview), path, NULL,
1627       TRUE, 0, 0.5);
1628
1629   gtk_tree_path_free (path);
1630 }
1631
1632 static void
1633 accounts_dialog_model_set_selected (EmpathyAccountsDialog *dialog,
1634     TpAccount *account)
1635 {
1636   GtkTreeIter       iter;
1637
1638   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1639     select_and_scroll_to_iter (dialog, &iter);
1640 }
1641
1642 static void
1643 accounts_dialog_treeview_enabled_cb (GtkMenuItem *item,
1644     TpAccount *account)
1645 {
1646   gboolean enabled;
1647
1648   enabled = tp_account_is_enabled (account);
1649   tp_account_set_enabled_async (account, !enabled, NULL, NULL);
1650 }
1651
1652 static gboolean
1653 accounts_dialog_treeview_button_press_event_cb (GtkTreeView *view,
1654     GdkEventButton *event,
1655     EmpathyAccountsDialog *dialog)
1656 {
1657   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1658   TpAccount *account = NULL;
1659   GtkTreeModel *model = NULL;
1660   GtkTreePath *path = NULL;
1661   GtkTreeIter iter;
1662   GtkWidget *menu;
1663   GtkWidget *item_enable, *item_disable;
1664   GtkWidget *image_enable, *image_disable;
1665
1666   /* ignore multiple clicks */
1667   if (event->type != GDK_BUTTON_PRESS)
1668     return TRUE;
1669
1670   if (event->button != 3)
1671     goto finally;
1672
1673   /* Selection is not yet set, so we have to get account from event position */
1674   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1675   if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (priv->treeview),
1676       event->x, event->y, &path, NULL, NULL, NULL))
1677     goto finally;
1678
1679   if (!gtk_tree_model_get_iter (model, &iter, path))
1680     goto finally;
1681
1682   gtk_tree_model_get (model, &iter, COL_ACCOUNT, &account, -1);
1683
1684   /* Create the menu */
1685   menu = empathy_context_menu_new (GTK_WIDGET (view));
1686
1687   /* Get images for menu items */
1688   image_enable = gtk_image_new_from_icon_name (empathy_icon_name_for_presence (
1689         tp_account_manager_get_most_available_presence (
1690           priv->account_manager, NULL, NULL)),
1691       GTK_ICON_SIZE_MENU);
1692   image_disable = gtk_image_new_from_icon_name (
1693       empathy_icon_name_for_presence (TP_CONNECTION_PRESENCE_TYPE_OFFLINE),
1694       GTK_ICON_SIZE_MENU);
1695
1696   /* Menu items: to enabled/disable the account */
1697   item_enable = gtk_image_menu_item_new_with_mnemonic (_("_Enable"));
1698   item_disable = gtk_image_menu_item_new_with_mnemonic (_("_Disable"));
1699   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item_enable),
1700       image_enable);
1701   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item_disable),
1702       image_disable);
1703
1704   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_enable);
1705   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_disable);
1706
1707   if (tp_account_is_enabled (account))
1708     {
1709       tp_g_signal_connect_object (item_disable, "activate",
1710           G_CALLBACK (accounts_dialog_treeview_enabled_cb), account, 0);
1711       gtk_widget_set_sensitive (item_enable, FALSE);
1712     }
1713   else
1714     {
1715       tp_g_signal_connect_object (item_enable, "activate",
1716           G_CALLBACK (accounts_dialog_treeview_enabled_cb), account, 0);
1717       gtk_widget_set_sensitive (item_disable, FALSE);
1718     }
1719
1720   gtk_widget_show (item_enable);
1721   gtk_widget_show (item_disable);
1722
1723   /* FIXME: Add here presence items, to be able to set per-account presence */
1724
1725   /* Popup menu */
1726   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1727       event->button, event->time);
1728
1729 finally:
1730   tp_clear_object (&account);
1731   gtk_tree_path_free (path);
1732
1733   return FALSE;
1734 }
1735
1736 static void
1737 accounts_dialog_connection_changed_cb (TpAccount *account,
1738     guint old_status,
1739     guint current,
1740     guint reason,
1741     gchar *dbus_error_name,
1742     GHashTable *details,
1743     EmpathyAccountsDialog *dialog)
1744 {
1745   GtkTreeModel *model;
1746   GtkTreeIter   iter;
1747   gboolean      found;
1748   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1749
1750   /* Update the status-infobar in the details view */
1751   accounts_dialog_update_status_infobar (dialog, account);
1752
1753   /* Update the status in the model */
1754   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1755
1756   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1757     {
1758       GtkTreePath *path;
1759
1760       gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1761           COL_STATUS, current,
1762           -1);
1763
1764       path = gtk_tree_model_get_path (model, &iter);
1765       gtk_tree_model_row_changed (model, path, &iter);
1766       gtk_tree_path_free (path);
1767     }
1768
1769   empathy_account_manager_get_accounts_connected (&found);
1770
1771   if (!found && priv->connecting_id)
1772     {
1773       g_source_remove (priv->connecting_id);
1774       priv->connecting_id = 0;
1775     }
1776
1777   if (found && !priv->connecting_id)
1778     priv->connecting_id = g_timeout_add (FLASH_TIMEOUT,
1779         (GSourceFunc) accounts_dialog_flash_connecting_cb,
1780         dialog);
1781 }
1782
1783 static void
1784 update_account_in_treeview (EmpathyAccountsDialog *self,
1785     TpAccount *account)
1786 {
1787   EmpathyAccountsDialogPriv *priv = GET_PRIV (self);
1788   GtkTreeIter iter;
1789   GtkTreeModel *model;
1790
1791   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1792   if (accounts_dialog_get_account_iter (self, account, &iter))
1793     {
1794       GtkTreePath *path;
1795
1796       path = gtk_tree_model_get_path (model, &iter);
1797       gtk_tree_model_row_changed (model, path, &iter);
1798       gtk_tree_path_free (path);
1799     }
1800 }
1801
1802 static void
1803 accounts_dialog_presence_changed_cb (TpAccount *account,
1804     guint presence,
1805     gchar *status,
1806     gchar *status_message,
1807     EmpathyAccountsDialog *dialog)
1808 {
1809   /* Update the status-infobar in the details view */
1810   accounts_dialog_update_status_infobar (dialog, account);
1811
1812   update_account_in_treeview (dialog, account);
1813 }
1814
1815 static void
1816 accounts_dialog_account_display_name_changed_cb (TpAccount *account,
1817   GParamSpec *pspec,
1818   gpointer user_data)
1819 {
1820   const gchar *display_name;
1821   GtkTreeIter iter;
1822   GtkTreeModel *model;
1823   EmpathyAccountSettings *settings;
1824   TpAccount *selected_account;
1825   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (user_data);
1826   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1827
1828   display_name = tp_account_get_display_name (account);
1829   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1830   settings = accounts_dialog_model_get_selected_settings (dialog);
1831   if (settings == NULL)
1832     return;
1833
1834   selected_account = empathy_account_settings_get_account (settings);
1835
1836   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1837     {
1838       gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1839           COL_NAME, display_name,
1840           -1);
1841     }
1842
1843   if (selected_account == account)
1844     accounts_dialog_update_name_label (dialog, display_name);
1845
1846   g_object_unref (settings);
1847 }
1848
1849 static void
1850 accounts_dialog_add_account (EmpathyAccountsDialog *dialog,
1851     TpAccount *account)
1852 {
1853   EmpathyAccountSettings *settings;
1854   GtkTreeModel       *model;
1855   GtkTreeIter         iter;
1856   TpConnectionStatus  status;
1857   const gchar        *name;
1858   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1859   gboolean selected = FALSE;
1860
1861   model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview));
1862   status = tp_account_get_connection_status (account, NULL);
1863   name = tp_account_get_display_name (account);
1864
1865   settings = empathy_account_settings_new_for_account (account);
1866
1867   if (!accounts_dialog_get_account_iter (dialog, account, &iter))
1868     {
1869       gtk_list_store_append (GTK_LIST_STORE (model), &iter);
1870     }
1871   else
1872     {
1873       GtkTreeSelection *selection;
1874
1875       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
1876       selected = gtk_tree_selection_iter_is_selected (selection, &iter);
1877     }
1878
1879   gtk_list_store_set (GTK_LIST_STORE (model), &iter,
1880       COL_NAME, name,
1881       COL_STATUS, status,
1882       COL_ACCOUNT, account,
1883       COL_ACCOUNT_SETTINGS, settings,
1884       -1);
1885
1886   if (selected)
1887     {
1888       /* We just modified the selected account. Its display name may have been
1889        * changed and so it's place in the treeview. Scroll to it so it stays
1890        * visible. */
1891       select_and_scroll_to_iter (dialog, &iter);
1892     }
1893
1894   accounts_dialog_connection_changed_cb (account,
1895       0,
1896       status,
1897       TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED,
1898       NULL,
1899       NULL,
1900       dialog);
1901
1902   tp_g_signal_connect_object (account, "notify::display-name",
1903       G_CALLBACK (accounts_dialog_account_display_name_changed_cb),
1904       dialog, 0);
1905
1906   tp_g_signal_connect_object (account, "status-changed",
1907       G_CALLBACK (accounts_dialog_connection_changed_cb), dialog, 0);
1908   tp_g_signal_connect_object (account, "presence-changed",
1909       G_CALLBACK (accounts_dialog_presence_changed_cb), dialog, 0);
1910
1911   g_object_unref (settings);
1912 }
1913
1914 static void
1915 accounts_dialog_account_validity_changed_cb (TpAccountManager *manager,
1916     TpAccount *account,
1917     gboolean valid,
1918     EmpathyAccountsDialog *dialog)
1919 {
1920   accounts_dialog_add_account (dialog, account);
1921 }
1922
1923 static void
1924 accounts_dialog_accounts_model_row_inserted_cb (GtkTreeModel *model,
1925     GtkTreePath *path,
1926     GtkTreeIter *iter,
1927     EmpathyAccountsDialog *dialog)
1928 {
1929   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1930
1931   if (priv->setting_widget_object != NULL &&
1932       accounts_dialog_has_valid_accounts (dialog))
1933     {
1934       empathy_account_widget_set_other_accounts_exist (
1935           priv->setting_widget_object, TRUE);
1936     }
1937 }
1938
1939 static void
1940 accounts_dialog_accounts_model_row_deleted_cb (GtkTreeModel *model,
1941     GtkTreePath *path,
1942     EmpathyAccountsDialog *dialog)
1943 {
1944   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1945
1946   if (priv->setting_widget_object != NULL &&
1947       !accounts_dialog_has_valid_accounts (dialog))
1948     {
1949       empathy_account_widget_set_other_accounts_exist (
1950           priv->setting_widget_object, FALSE);
1951     }
1952 }
1953
1954 static void
1955 accounts_dialog_account_removed_cb (TpAccountManager *manager,
1956     TpAccount *account,
1957     EmpathyAccountsDialog *dialog)
1958 {
1959   GtkTreeIter iter;
1960   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
1961
1962   if (accounts_dialog_get_account_iter (dialog, account, &iter))
1963     {
1964       gtk_list_store_remove (GTK_LIST_STORE (
1965           gtk_tree_view_get_model (GTK_TREE_VIEW (priv->treeview))), &iter);
1966     }
1967 }
1968
1969 static void
1970 enable_or_disable_account (EmpathyAccountsDialog *dialog,
1971     TpAccount *account,
1972     gboolean enabled)
1973 {
1974   /* Update the status-infobar in the details view */
1975   accounts_dialog_update_status_infobar (dialog, account);
1976
1977   DEBUG ("Account %s is now %s",
1978       tp_account_get_display_name (account),
1979       enabled ? "enabled" : "disabled");
1980 }
1981
1982 static void
1983 accounts_dialog_account_disabled_cb (TpAccountManager *manager,
1984     TpAccount *account,
1985     EmpathyAccountsDialog *dialog)
1986 {
1987   enable_or_disable_account (dialog, account, FALSE);
1988   update_account_in_treeview (dialog, account);
1989 }
1990
1991 static void
1992 accounts_dialog_account_enabled_cb (TpAccountManager *manager,
1993     TpAccount *account,
1994     EmpathyAccountsDialog *dialog)
1995 {
1996   enable_or_disable_account (dialog, account, TRUE);
1997 }
1998
1999 static GtkWidget *
2000 display_import_dialog (EmpathyAccountsDialog *dialog)
2001 {
2002   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
2003   GtkWidget *import_dialog;
2004
2005   import_dialog = empathy_import_dialog_new (GTK_WINDOW (dialog),
2006       FALSE, priv->cms);
2007   gtk_widget_show (import_dialog);
2008
2009   return import_dialog;
2010 }
2011
2012 static void
2013 accounts_dialog_button_import_clicked_cb (GtkWidget *button,
2014     EmpathyAccountsDialog *dialog)
2015 {
2016   display_import_dialog (dialog);
2017 }
2018
2019 static void
2020 accounts_dialog_close_response_cb (GtkDialog *message_dialog,
2021   gint response_id,
2022   gpointer user_data)
2023 {
2024   GtkWidget *account_dialog = GTK_WIDGET (user_data);
2025
2026   gtk_widget_destroy (GTK_WIDGET (message_dialog));
2027
2028   if (response_id == GTK_RESPONSE_YES)
2029     gtk_widget_destroy (account_dialog);
2030 }
2031
2032 static gboolean
2033 accounts_dialog_delete_event_cb (GtkWidget *widget,
2034     GdkEvent *event,
2035     EmpathyAccountsDialog *dialog)
2036 {
2037   /* we maunally handle responses to delete events */
2038   return TRUE;
2039 }
2040
2041 static void
2042 accounts_dialog_set_selected_account (EmpathyAccountsDialog *dialog,
2043     TpAccount *account)
2044 {
2045   GtkTreeIter       iter;
2046
2047   if (accounts_dialog_get_account_iter (dialog, account, &iter))
2048     select_and_scroll_to_iter (dialog, &iter);
2049 }
2050
2051 static void
2052 finished_loading (EmpathyAccountsDialog *self)
2053 {
2054   EmpathyAccountsDialogPriv *priv = GET_PRIV (self);
2055   GtkTreeSelection *selection;
2056   gboolean has_selected;
2057
2058   priv->loading = FALSE;
2059
2060   gtk_widget_set_sensitive (priv->button_add, TRUE);
2061   gtk_widget_set_sensitive (priv->button_import, TRUE);
2062   gtk_widget_set_sensitive (priv->treeview, TRUE);
2063
2064   /* Sensitive the remove button if there is an account selected */
2065   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview));
2066   has_selected = gtk_tree_selection_get_selected (selection, NULL, NULL);
2067   gtk_widget_set_sensitive (priv->button_remove, has_selected);
2068
2069   gtk_spinner_stop (GTK_SPINNER (priv->spinner));
2070   gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook_account),
2071       NOTEBOOK_PAGE_ACCOUNT);
2072 }
2073
2074 static void
2075 accounts_dialog_cms_prepare_cb (GObject *source,
2076     GAsyncResult *result,
2077     gpointer user_data)
2078 {
2079   EmpathyConnectionManagers *cms = EMPATHY_CONNECTION_MANAGERS (source);
2080   EmpathyAccountsDialog *dialog = user_data;
2081   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
2082
2083   if (!empathy_connection_managers_prepare_finish (cms, result, NULL))
2084     goto out;
2085
2086   /* No need to update the settings if we are already preparing one */
2087   if (priv->settings_ready == NULL)
2088     accounts_dialog_update_settings (dialog, NULL);
2089
2090   if (priv->initial_selection != NULL)
2091     {
2092       accounts_dialog_set_selected_account (dialog, priv->initial_selection);
2093       g_object_unref (priv->initial_selection);
2094       priv->initial_selection = NULL;
2095     }
2096
2097 out:
2098   finished_loading (dialog);
2099 }
2100
2101 static void
2102 accounts_dialog_accounts_setup (EmpathyAccountsDialog *dialog)
2103 {
2104   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
2105   GList *accounts, *l;
2106
2107   g_signal_connect (priv->account_manager, "account-validity-changed",
2108       G_CALLBACK (accounts_dialog_account_validity_changed_cb),
2109       dialog);
2110   g_signal_connect (priv->account_manager, "account-removed",
2111       G_CALLBACK (accounts_dialog_account_removed_cb),
2112       dialog);
2113   g_signal_connect (priv->account_manager, "account-enabled",
2114       G_CALLBACK (accounts_dialog_account_enabled_cb),
2115       dialog);
2116   g_signal_connect (priv->account_manager, "account-disabled",
2117       G_CALLBACK (accounts_dialog_account_disabled_cb),
2118       dialog);
2119
2120   /* Add existing accounts */
2121   accounts = tp_account_manager_get_valid_accounts (priv->account_manager);
2122   for (l = accounts; l; l = l->next)
2123     {
2124       accounts_dialog_add_account (dialog, l->data);
2125     }
2126   g_list_free (accounts);
2127
2128   priv->cms = empathy_connection_managers_dup_singleton ();
2129
2130   empathy_connection_managers_prepare_async (priv->cms,
2131       accounts_dialog_cms_prepare_cb, dialog);
2132
2133   accounts_dialog_model_select_first (dialog);
2134 }
2135
2136 static void
2137 accounts_dialog_manager_ready_cb (GObject *source_object,
2138     GAsyncResult *result,
2139     gpointer user_data)
2140 {
2141   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
2142   GError *error = NULL;
2143
2144   if (!tp_proxy_prepare_finish (manager, result, &error))
2145     {
2146       DEBUG ("Failed to prepare account manager: %s", error->message);
2147       g_error_free (error);
2148       return;
2149     }
2150
2151   accounts_dialog_accounts_setup (user_data);
2152 }
2153
2154 static void
2155 dialog_response_cb (GtkWidget *widget,
2156     gint response_id,
2157     gpointer user_data)
2158 {
2159   EmpathyAccountsDialog *dialog = EMPATHY_ACCOUNTS_DIALOG (widget);
2160
2161   if (response_id == GTK_RESPONSE_HELP)
2162     {
2163       empathy_url_show (widget, "ghelp:empathy?accounts-window");
2164     }
2165   else if (response_id == GTK_RESPONSE_CLOSE ||
2166       response_id == GTK_RESPONSE_DELETE_EVENT)
2167     {
2168       TpAccount *account = NULL;
2169
2170       if (accounts_dialog_has_pending_change (dialog, &account))
2171         {
2172           gchar *question_dialog_primary_text = get_dialog_primary_text (
2173               account);
2174
2175           accounts_dialog_show_question_dialog (dialog,
2176               question_dialog_primary_text,
2177               _("You are about to close the window, which will discard\n"
2178                   "your changes. Are you sure you want to proceed?"),
2179               G_CALLBACK (accounts_dialog_close_response_cb),
2180               widget,
2181               GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
2182               GTK_STOCK_DISCARD, GTK_RESPONSE_YES, NULL);
2183
2184           g_free (question_dialog_primary_text);
2185         }
2186       else
2187         {
2188           gtk_widget_destroy (widget);
2189         }
2190
2191       if (account != NULL)
2192         g_object_unref (account);
2193     }
2194 }
2195
2196 static void
2197 accounts_dialog_build_ui (EmpathyAccountsDialog *dialog)
2198 {
2199   GtkWidget *top_hbox;
2200   GtkBuilder                   *gui;
2201   gchar                        *filename;
2202   EmpathyAccountsDialogPriv    *priv = GET_PRIV (dialog);
2203   GtkWidget *content_area, *action_area;
2204   GtkWidget *grid, *hbox;
2205   GtkWidget *alig;
2206   GtkWidget *sw, *toolbar;
2207   GtkStyleContext *context;
2208
2209   filename = empathy_file_lookup ("empathy-accounts-dialog.ui", "src");
2210
2211   gui = empathy_builder_get_file (filename,
2212       "accounts_dialog_hbox", &top_hbox,
2213       "vbox_details", &priv->vbox_details,
2214       "alignment_settings", &priv->alignment_settings,
2215       "alignment_infobar", &priv->alignment_infobar,
2216       "treeview", &priv->treeview,
2217       "button_add", &priv->button_add,
2218       "button_remove", &priv->button_remove,
2219       "button_import", &priv->button_import,
2220       "notebook_account", &priv->notebook_account,
2221       "alignment_loading", &alig,
2222       "accounts_sw", &sw,
2223       "add_remove_toolbar", &toolbar,
2224       NULL);
2225   g_free (filename);
2226
2227   empathy_builder_connect (gui, dialog,
2228       "button_add", "clicked", accounts_dialog_button_add_clicked_cb,
2229       "button_remove", "clicked", accounts_dialog_button_remove_clicked_cb,
2230       "button_import", "clicked", accounts_dialog_button_import_clicked_cb,
2231       "treeview", "button-press-event",
2232          accounts_dialog_treeview_button_press_event_cb,
2233       NULL);
2234
2235   content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
2236
2237   gtk_box_pack_start (GTK_BOX (content_area), top_hbox, TRUE, TRUE, 0);
2238
2239   g_object_unref (gui);
2240
2241   action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
2242
2243 #ifdef HAVE_MEEGO
2244   gtk_widget_hide (action_area);
2245   gtk_widget_hide (priv->button_remove);
2246 #endif /* HAVE_MEEGO */
2247
2248   /* Display loading page */
2249   priv->loading = TRUE;
2250
2251   priv->spinner = gtk_spinner_new ();
2252
2253   gtk_spinner_start (GTK_SPINNER (priv->spinner));
2254   gtk_widget_show (priv->spinner);
2255
2256   gtk_container_add (GTK_CONTAINER (alig), priv->spinner);
2257
2258   gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook_account),
2259       NOTEBOOK_PAGE_LOADING);
2260
2261   /* Remove button is insensitive until we have a selected account */
2262   gtk_widget_set_sensitive (priv->button_remove, FALSE);
2263
2264   /* Add and Import buttons and treeview are insensitive while the dialog
2265    * is loading */
2266   gtk_widget_set_sensitive (priv->button_add, FALSE);
2267   gtk_widget_set_sensitive (priv->button_import, FALSE);
2268   gtk_widget_set_sensitive (priv->treeview, FALSE);
2269
2270   if (priv->parent_window)
2271     gtk_window_set_transient_for (GTK_WINDOW (dialog),
2272         priv->parent_window);
2273
2274   priv->infobar = gtk_info_bar_new ();
2275   gtk_container_add (GTK_CONTAINER (priv->alignment_infobar),
2276       priv->infobar);
2277   gtk_widget_show (priv->infobar);
2278
2279   grid = gtk_grid_new ();
2280   gtk_container_add (
2281       GTK_CONTAINER (gtk_info_bar_get_content_area (
2282           GTK_INFO_BAR (priv->infobar))),
2283       grid);
2284
2285   priv->image_type = gtk_image_new_from_stock (GTK_STOCK_CUT,
2286       GTK_ICON_SIZE_DIALOG);
2287   gtk_misc_set_alignment (GTK_MISC (priv->image_type), 0.0, 0.5);
2288   gtk_grid_attach (GTK_GRID (grid), priv->image_type, 0, 0, 1, 2);
2289
2290   /* first row */
2291   priv->label_name = gtk_label_new (NULL);
2292   gtk_grid_attach (GTK_GRID (grid), priv->label_name, 1, 0, 1, 1);
2293
2294   /* second row */
2295   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
2296   gtk_widget_set_hexpand (hbox, TRUE);
2297   gtk_widget_set_halign (hbox, GTK_ALIGN_CENTER);
2298   gtk_grid_attach (GTK_GRID (grid), hbox, 1, 1, 1, 1);
2299
2300   /* set up spinner */
2301   priv->throbber = gtk_spinner_new ();
2302
2303   priv->image_status = gtk_image_new_from_icon_name (
2304             empathy_icon_name_for_presence (
2305             TP_CONNECTION_PRESENCE_TYPE_OFFLINE), GTK_ICON_SIZE_SMALL_TOOLBAR);
2306
2307   priv->label_status = gtk_label_new (NULL);
2308   gtk_label_set_ellipsize (GTK_LABEL (priv->label_status), PANGO_ELLIPSIZE_END);
2309
2310   gtk_box_pack_start (GTK_BOX (hbox), priv->throbber, FALSE, FALSE, 0);
2311   gtk_box_pack_start (GTK_BOX (hbox), priv->image_status, FALSE, FALSE, 0);
2312   gtk_box_pack_start (GTK_BOX (hbox), priv->label_status, FALSE, FALSE, 0);
2313
2314   /* enabled switch */
2315   priv->enabled_switch = gtk_switch_new ();
2316   gtk_widget_set_valign (priv->enabled_switch, GTK_ALIGN_CENTER);
2317   g_signal_connect (priv->enabled_switch, "notify::active",
2318       G_CALLBACK (accounts_dialog_enable_switch_active_cb), dialog);
2319   gtk_grid_attach (GTK_GRID (grid), priv->enabled_switch, 2, 0, 1, 2);
2320
2321   gtk_widget_show_all (grid);
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_proxy_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   GString *args;
2521
2522   g_return_if_fail (!selected_account || TP_IS_ACCOUNT (selected_account));
2523
2524   args = g_string_new (NULL);
2525
2526   if (selected_account != NULL)
2527     g_string_append_printf (args, " --select-account=%s",
2528         tp_account_get_path_suffix (selected_account));
2529
2530   if (if_needed)
2531     g_string_append_printf (args, " --if-needed");
2532
2533   if (hidden)
2534     g_string_append_printf (args, " --hidden");
2535
2536   DEBUG ("Launching empathy-accounts (if_needed: %d, hidden: %d, account: %s)",
2537     if_needed, hidden,
2538     selected_account == NULL ? "<none selected>" :
2539       tp_proxy_get_object_path (TP_PROXY (selected_account)));
2540
2541   empathy_launch_program (BIN_DIR, "empathy-accounts", args->str);
2542
2543   g_string_free (args, TRUE);
2544 }
2545
2546 gboolean
2547 empathy_accounts_dialog_is_creating (EmpathyAccountsDialog *dialog)
2548 {
2549   EmpathyAccountsDialogPriv *priv = GET_PRIV (dialog);
2550   gboolean result = FALSE;
2551
2552   if (priv->setting_widget_object == NULL)
2553     goto out;
2554
2555   g_object_get (priv->setting_widget_object,
2556       "creating-account", &result, NULL);
2557
2558 out:
2559   return result;
2560 }