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