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