syncevolution/src/gtk-ui/sync-ui.c

1897 lines
68 KiB
C

/*
* Copyright (C) 2009 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
/*
* TODO
*
* * redesign main window? (talk with nick/patrick). Issues:
- sync types (other than two-way) should maybe be available somewhere
else than main window?
- showing usable statistic
- sync errors can flood the ui... need to dicuss with nick.
Possible solution: show only a few errors, but have a linkbutton to open a
separate error window
* * get history data from syncevolution
* * backup/restore ?
* * GTK styling missing:
* - current implementation of MuxFrame results in a slight flicker on window open
* * notes on dbus API:
* - a more cleaner solution would be to have StartSync return a
* dbus path that could be used to connect to signals related to that specific
* sync
*/
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <gconf/gconf-client.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include "syncevo-dbus.h"
/* for return value definitions */
/* TODO: would be nice to have a non-synthesis-dependent API but for now it's like this... */
#include <synthesis/syerror.h>
#include <synthesis/engine_defs.h>
#include "sync-ui-config.h"
#ifdef USE_MOBLIN_UX
#include "mux-frame.h"
#include "mux-window.h"
#endif
#define SYNC_UI_GCONF_DIR "/apps/sync-ui"
#define SYNC_UI_SERVER_KEY SYNC_UI_GCONF_DIR"/server"
#define SYNC_UI_ICON_SIZE 48
#define SYNC_UI_LIST_ICON_SIZE 32
#define SYNC_UI_LIST_BTN_WIDTH 150
typedef struct source_progress {
char* name;
/* progress */
int prepare_current;
int prepare_total;
int send_current;
int send_total;
int receive_current;
int receive_total;
/* statistics */
int added_local;
int modified_local;
int deleted_local;
int rejected_local;
int added_remote;
int modified_remote;
int deleted_remote;
int rejected_remote;
int bytes_uploaded;
int bytes_downloaded;
} source_progress;
typedef enum app_state {
SYNC_UI_STATE_GETTING_SERVER,
SYNC_UI_STATE_NO_SERVER,
SYNC_UI_STATE_SERVER_OK,
SYNC_UI_STATE_SERVER_FAILURE,
SYNC_UI_STATE_SYNCING,
} app_state;
/* absolute progress amounts 0-100 */
const float sync_progress_clicked = 0.02;
const float sync_progress_session_start = 0.04;
const float sync_progress_sync_start = 0.06; /* prepare/send/receive cycles start here */
const float sync_progress_sync_end = 0.96;
/* how are prepare/send/receive weighed -- sum should be 100 */
const float sync_weight_prepare = 0.50;
const float sync_weight_send = 0.25;
const float sync_weight_receive = 0.25;
/* non-abolute progress amounts for prepare/send/receive (for all sources) */
#define SYNC_PROGRESS_PREPARE ((sync_progress_sync_end - sync_progress_sync_start) * sync_weight_prepare)
#define SYNC_PROGRESS_SEND ((sync_progress_sync_end - sync_progress_sync_start) * sync_weight_send)
#define SYNC_PROGRESS_RECEIVE ((sync_progress_sync_end - sync_progress_sync_start) * sync_weight_receive)
typedef struct app_data {
GtkWidget *sync_win;
GtkWidget *services_win;
GtkWidget *service_settings_win;
GtkWidget *server_box;
GtkWidget *server_failure_box;
GtkWidget *no_server_box;
GtkWidget *error_box;
GtkWidget *error_img;
GtkWidget *main_frame;
GtkWidget *log_frame;
GtkWidget *server_icon_box;
GtkWidget *progress;
GtkWidget *sync_status_label;
GtkWidget *sync_btn;
GtkWidget *edit_service_btn;
GtkWidget *change_service_btn;
GtkWidget *server_label;
GtkWidget *last_synced_label;
GtkWidget *sources_box;
GtkWidget *new_service_btn;
GtkWidget *services_table;
GtkWidget *manual_services_table;
GtkWidget *manual_services_scrolled;
GtkWidget *back_btn;
GtkWidget *service_settings_frame;
GtkWidget *service_link;
GtkWidget *service_name_label;
GtkWidget *service_name_entry;
GtkWidget *username_entry;
GtkWidget *password_entry;
GtkWidget *server_settings_expander;
GtkWidget *server_settings_table;
GtkWidget *reset_server_btn;
GtkWidget *stop_using_service_btn;
GtkWidget *delete_service_btn;
gboolean syncing;
gboolean synced_this_session;
int last_sync;
guint last_sync_src_id;
GList *source_progresses;
GHashTable *source_report_labels;
SyncMode mode;
SyncevoService *service;
server_config *current_service;
} app_data;
static void set_sync_progress (app_data *data, float progress, char *status);
static void set_app_state (app_data *data, app_state state);
static void show_services_window (app_data *data);
static void show_settings_window (app_data *data, server_config *config);
static void ensure_default_sources_exist(server_config *server);
static void add_server_option (SyncevoOption *option, server_config *server);
static void setup_new_service_clicked (GtkButton *btn, app_data *data);
static void
remove_child (GtkWidget *widget, GtkContainer *container)
{
gtk_container_remove (container, widget);
}
static void
change_service_clicked_cb (GtkButton *btn, app_data *data)
{
show_services_window (data);
}
static void
edit_services_clicked_cb (GtkButton *btn, app_data *data)
{
data->current_service->changed = FALSE;
gtk_window_set_transient_for (GTK_WINDOW (data->service_settings_win),
GTK_WINDOW (data->sync_win));
show_settings_window (data, data->current_service);
}
static void
update_server_config (GtkWidget *widget, server_config *config)
{
if (GTK_IS_ENTRY (widget))
server_config_update_from_entry (config, GTK_ENTRY (widget));
}
static void
show_error_dialog (GtkWindow *parent, const char* message)
{
GtkWidget *w;
w = gtk_message_dialog_new (parent,
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
message);
gtk_dialog_run (GTK_DIALOG (w));
gtk_widget_destroy (w);
}
static void
clear_error_info (app_data *data)
{
gtk_container_foreach (GTK_CONTAINER(data->error_box),
(GtkCallback)remove_child,
data->error_box);
gtk_widget_hide (data->error_img);
}
static char*
get_pretty_source_name (const char *source_name)
{
if (strcmp (source_name, "addressbook") == 0) {
return g_strdup (_("Addressbook"));
} else if (strcmp (source_name, "calendar") == 0) {
return g_strdup (_("Calendar"));
} else if (strcmp (source_name, "todo") == 0) {
return g_strdup (_("Todo"));
} else if (strcmp (source_name, "memo") == 0) {
return g_strdup (_("Memo"));
} else {
char *tmp;
tmp = g_strdup (source_name);
tmp[0] = g_ascii_toupper (tmp[0]);
return tmp;
}
}
static void
add_error_info (app_data *data, const char *message, const char *external_reason)
{
GtkWidget *lbl;
GList *l, *children;
/* synthesis may emit same error several times, work around that: */
children = gtk_container_get_children (GTK_CONTAINER (data->error_box));
for (l = children; l; l = l->next) {
GtkLabel *old_lbl = GTK_LABEL (l->data);
if (strcmp (message, gtk_label_get_text (old_lbl)) == 0) {
g_list_free (children);
return;
}
}
g_list_free (children);
gtk_widget_show (data->error_img);
lbl = gtk_label_new (message);
gtk_label_set_line_wrap (GTK_LABEL (lbl), TRUE);
/* FIXME ugly hard coding*/
gtk_widget_set_size_request (lbl, 160, -1);
gtk_widget_show (lbl);
gtk_misc_set_alignment (GTK_MISC (lbl), 0.0, 0.5);
gtk_box_pack_start (GTK_BOX (data->error_box), lbl, FALSE, FALSE, 0);
if (external_reason) {
g_warning ("%s: %s", message, external_reason);
} else {
g_warning ("%s", message);
}
}
static void
save_gconf_settings (app_data *data, char *service_name)
{
GConfClient* client;
GError *err = NULL;
client = gconf_client_get_default ();
if (!gconf_client_set_string (client, SYNC_UI_SERVER_KEY,
service_name ? service_name : "",
&err)) {
show_error_dialog (GTK_WINDOW (data->sync_win),
_("Failed to save current service in GConf configuration system"));
g_warning ("Failed to save current service in GConf configuration system: %s", err->message);
g_error_free (err);
}
}
static void
set_server_config_cb (SyncevoService *service, GError *error, app_data *data)
{
if (error) {
show_error_dialog (GTK_WINDOW (data->sync_win),
_("Failed to save service configuration to SyncEvolution"));
g_warning ("Failed to save service configuration to SyncEvolution: %s",
error->message);
g_error_free (error);
return;
}
save_gconf_settings (data, data->current_service->name);
}
/* temporary data structure for syncevo_service_get_template_config_async and
* syncevo_service_get_server_config_async. server is the server that
* the method was called for. options_override are options that should
* be overridden on the config we get. */
typedef struct server_data {
char *server_name;
GPtrArray *options_override;
app_data *data;
} server_data;
static void
get_server_config_for_template_cb (SyncevoService *service, GPtrArray *options, GError *error, server_data *data)
{
server_config *config;
if (error) {
show_error_dialog (GTK_WINDOW (data->data->sync_win),
_("Failed to get service configuration from SyncEvolution"));
g_warning ("Failed to get service configuration from SyncEvolution: %s",
error->message);
g_error_free (error);
} else {
config = g_slice_new0 (server_config);
config->name = g_strdup (data->server_name);
g_ptr_array_foreach (options, (GFunc)add_server_option, config);
if (data->options_override)
g_ptr_array_foreach (data->options_override, (GFunc)add_server_option, config);
ensure_default_sources_exist (config);
config->changed = TRUE;
show_settings_window (data->data, config);
}
g_free (data->server_name);
if (data->options_override) {
g_ptr_array_foreach (data->options_override, (GFunc)syncevo_option_free, NULL);
g_ptr_array_free (data->options_override, TRUE);
}
g_slice_free (server_data ,data);
g_ptr_array_foreach (options, (GFunc)syncevo_option_free, NULL);
g_ptr_array_free (options, TRUE);
}
static void
remove_server_config_cb (SyncevoService *service,
GError *error,
server_data *data)
{
g_assert (data);
if (error) {
show_error_dialog (GTK_WINDOW (data->data->sync_win),
_("Failed to remove service configuration from SyncEvolution"));
g_warning ("Failed to remove service configuration from SyncEvolution: %s",
error->message);
g_error_free (error);
} else {
/* update list if visible */
if (GTK_WIDGET_VISIBLE (data->data->services_win))
show_services_window (data->data);
if (data->data->current_service && data->data->current_service->name &&
strcmp (data->data->current_service->name, data->server_name) == 0)
save_gconf_settings (data->data, NULL);
}
g_free (data->server_name);
g_slice_free (server_data ,data);
}
static void
stop_using_service_clicked_cb (GtkButton *btn, app_data *data)
{
server_config *server;
server = g_object_get_data (G_OBJECT (data->service_settings_win), "server");
g_assert (server);
gtk_widget_hide (GTK_WIDGET (data->service_settings_win));
gtk_widget_hide (GTK_WIDGET (data->services_win));
save_gconf_settings (data, NULL);
}
static void
delete_service_clicked_cb (GtkButton *btn, app_data *data)
{
server_config *server;
server_data* serv_data;
server = g_object_get_data (G_OBJECT (data->service_settings_win), "server");
g_assert (server);
gtk_widget_hide (GTK_WIDGET (data->service_settings_win));
serv_data = g_slice_new0 (server_data);
serv_data->data = data;
serv_data->server_name = g_strdup (server->name);
syncevo_service_remove_server_config_async (data->service,
server->name,
(SyncevoRemoveServerConfigCb)remove_server_config_cb,
serv_data);
}
static void
reset_service_clicked_cb (GtkButton *btn, app_data *data)
{
server_config *server;
server_data* serv_data;
SyncevoOption *option;
server = g_object_get_data (G_OBJECT (data->service_settings_win), "server");
g_assert (server);
serv_data = g_slice_new0 (server_data);
serv_data->data = data;
serv_data->server_name = g_strdup (server->name);
serv_data->options_override = g_ptr_array_new ();
option = syncevo_option_new (NULL, g_strdup ("username"), g_strdup (server->username));
g_ptr_array_add (serv_data->options_override, option);
option = syncevo_option_new (NULL, g_strdup ("password"), g_strdup (server->password));
g_ptr_array_add (serv_data->options_override, option);
syncevo_service_get_template_config_async (data->service,
server->name,
(SyncevoGetTemplateConfigCb)get_server_config_for_template_cb,
serv_data);
}
static void
service_save_clicked_cb (GtkButton *btn, app_data *data)
{
GPtrArray *options;
server_config *server;
server = g_object_get_data (G_OBJECT (data->service_settings_win), "server");
g_assert (server);
server_config_update_from_entry (server, GTK_ENTRY (data->service_name_entry));
server_config_update_from_entry (server, GTK_ENTRY (data->username_entry));
server_config_update_from_entry (server, GTK_ENTRY (data->password_entry));
gtk_container_foreach (GTK_CONTAINER (data->server_settings_table),
(GtkCallback)update_server_config, server);
if (!server->name || strlen (server->name) == 0) {
show_error_dialog (GTK_WINDOW (data->service_settings_win),
_("Service must have a name"));
return;
}
gtk_widget_hide (GTK_WIDGET (data->service_settings_win));
gtk_widget_hide (GTK_WIDGET (data->services_win));
if (data->current_service && data->current_service != server) {
server_config_free (data->current_service);
}
data->current_service = server;
if (!server->changed) {
/* no need to save first, set the gconf key right away */
save_gconf_settings (data, data->current_service->name);
} else {
/* save the server, let callback change current server gconf key */
options = server_config_get_option_array (server);
syncevo_service_set_server_config_async (data->service,
server->name,
options,
(SyncevoSetServerConfigCb)set_server_config_cb,
data);
g_ptr_array_foreach (options, (GFunc)syncevo_option_free, NULL);
g_ptr_array_free (options, TRUE);
}
}
static void
sync_clicked_cb (GtkButton *btn, app_data *data)
{
GPtrArray *sources;
GList *list;
GError *error = NULL;
if (data->syncing) {
syncevo_service_abort_sync (data->service, data->current_service->name, &error);
if (error) {
/* FIXME: this can happen if the server has shutdown and the sync is no longer
in progress*/
add_error_info (data, _("Failed to cancel sync"), error->message);
g_error_free (error);
return;
} else {
set_sync_progress (data, -1.0, _("Canceling sync"));
}
} else {
char *message = NULL;
/* confirmation dialog for destructive sync options */
switch (data->mode) {
case SYNC_REFRESH_FROM_SERVER:
message = g_strdup_printf (_("Do you want to delete all local data and replace it with "
"data from %s? This is not usually advised."),
data->current_service->name);
break;
case SYNC_REFRESH_FROM_CLIENT:
message = g_strdup_printf (_("Do you want to delete all data in %s and replace it with "
"your local data? This is not usually advised."),
data->current_service->name);
break;
default:
;
}
if (message) {
GtkWidget *w;
int ret;
w = gtk_message_dialog_new (GTK_WINDOW (data->sync_win),
GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_NONE,
message);
gtk_dialog_add_buttons (GTK_DIALOG (w),
_("No, cancel sync"), GTK_RESPONSE_NO,
_("Yes, delete and replace"), GTK_RESPONSE_YES,
NULL);
ret = gtk_dialog_run (GTK_DIALOG (w));
gtk_widget_destroy (w);
g_free (message);
if (ret != GTK_RESPONSE_YES) {
return;
}
}
/* empty source progress list */
list = data->source_progresses;
for (list = data->source_progresses; list; list = list->next) {
g_free (((source_progress*)list->data)->name);
g_slice_free (source_progress, list->data);
}
g_list_free (data->source_progresses);
data->source_progresses = NULL;
sources = server_config_get_source_array (data->current_service, data->mode);
if (sources->len == 0) {
g_ptr_array_free (sources, TRUE);
add_error_info (data, _("No sources are enabled, not syncing"), NULL);
return;
}
syncevo_service_start_sync (data->service,
data->current_service->name,
sources,
&error);
if (error) {
add_error_info (data, _("Failed to start SyncEvolution sync"), error->message);
g_error_free (error);
} else {
/* stop updates of "last synced" */
if (data->last_sync_src_id > 0)
g_source_remove (data->last_sync_src_id);
set_sync_progress (data, sync_progress_clicked, _("Starting sync"));
set_app_state (data, SYNC_UI_STATE_SYNCING);
}
}
}
static gboolean
refresh_last_synced_label (app_data *data)
{
GTimeVal val;
glong diff;
char *msg;
int delay;
g_get_current_time (&val);
diff = val.tv_sec - data->last_sync;
if (data->last_sync <= 0) {
msg = g_strdup (""); /* we don't know */
delay = -1;
} else if (diff < 30) {
msg = g_strdup (_("Last synced just seconds ago"));
delay = 30;
} else if (diff < 90) {
msg = g_strdup (_("Last synced a minute ago"));
delay = 60;
} else if (diff < 60 * 60) {
msg = g_strdup_printf (_("Last synced %ld minutes ago"), (diff + 30) / 60);
delay = 60;
} else if (diff < 60 * 90) {
msg = g_strdup (_("Last synced an hour ago"));
delay = 60 * 60;
} else if (diff < 60 * 60 * 24) {
msg = g_strdup_printf (_("Last synced %ld hours ago"), (diff + 60 * 30) / (60 * 60));
delay = 60 * 60;
} else if (diff < 60 * 60 * 24 - (60 * 30)) {
msg = g_strdup (_("Last synced a day ago"));
delay = 60 * 60 * 24;
} else {
msg = g_strdup_printf (_("Last synced %ld days ago"), (diff + 24 * 60 * 30) / (60 * 60 * 24));
delay = 60 * 60 * 24;
}
gtk_label_set_text (GTK_LABEL (data->last_synced_label), msg);
g_free (msg);
if (data->last_sync_src_id > 0)
g_source_remove (data->last_sync_src_id);
if (delay > 0)
data->last_sync_src_id = g_timeout_add_seconds (delay, (GSourceFunc)refresh_last_synced_label, data);
return FALSE;
}
static void
set_sync_progress (app_data *data, float progress, char *status)
{
if (progress >= 0)
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (data->progress), progress);
if (status)
gtk_progress_bar_set_text (GTK_PROGRESS_BAR (data->progress), status);
}
static void
set_app_state (app_data *data, app_state state)
{
static int old_state = -1;
if (old_state == state)
return;
old_state = state;
switch (state) {
case SYNC_UI_STATE_GETTING_SERVER:
clear_error_info (data);
gtk_widget_show (data->server_box);
gtk_widget_hide (data->server_failure_box);
gtk_widget_hide (data->no_server_box);
gtk_label_set_text (GTK_LABEL (data->sync_status_label), "");
gtk_widget_set_sensitive (data->main_frame, TRUE);
gtk_widget_set_sensitive (data->sync_btn, FALSE);
gtk_widget_set_sensitive (data->change_service_btn, TRUE);
break;
case SYNC_UI_STATE_NO_SERVER:
clear_error_info (data);
gtk_widget_hide (data->server_box);
gtk_widget_hide (data->server_failure_box);
gtk_widget_show (data->no_server_box);
gtk_label_set_text (GTK_LABEL (data->sync_status_label), "");
gtk_widget_set_sensitive (data->main_frame, TRUE);
gtk_widget_set_sensitive (data->sync_btn, FALSE);
gtk_widget_set_sensitive (data->change_service_btn, TRUE);
gtk_window_set_focus (GTK_WINDOW (data->sync_win), data->change_service_btn);
break;
case SYNC_UI_STATE_SERVER_FAILURE:
clear_error_info (data);
gtk_widget_hide (data->server_box);
gtk_widget_hide (data->no_server_box);
gtk_widget_show (data->server_failure_box);
gtk_label_set_text (GTK_LABEL (data->sync_status_label), "");
gtk_widget_set_sensitive (data->main_frame, FALSE);
gtk_widget_set_sensitive (data->sync_btn, FALSE);
gtk_widget_set_sensitive (data->change_service_btn, FALSE);
break;
case SYNC_UI_STATE_SERVER_OK:
gtk_widget_show (data->server_box);
gtk_widget_hide (data->server_failure_box);
gtk_widget_hide (data->no_server_box);
gtk_widget_set_sensitive (data->main_frame, TRUE);
gtk_widget_set_sensitive (data->sync_btn, TRUE);
gtk_widget_set_sensitive (data->change_service_btn, TRUE);
if (data->synced_this_session)
gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Sync again"));
else
gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Sync now"));
gtk_window_set_focus (GTK_WINDOW (data->sync_win), data->sync_btn);
data->syncing = FALSE;
break;
case SYNC_UI_STATE_SYNCING:
clear_error_info (data);
gtk_widget_show (data->progress);
gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Syncing"));
gtk_widget_set_sensitive (data->main_frame, FALSE);
gtk_widget_set_sensitive (data->sync_btn, TRUE);
gtk_widget_set_sensitive (data->change_service_btn, TRUE);
gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Cancel sync"));
data->syncing = TRUE;
break;
default:
g_assert_not_reached ();
}
}
static void
sync_type_toggled_cb (GObject *radio, app_data *data)
{
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (radio))) {
data->mode = GPOINTER_TO_INT (g_object_get_data (radio, "mode"));
}
}
#ifdef USE_MOBLIN_UX
/* truly stupid, but glade doesn't allow custom containers.
Now glade file has dummy containers that will be replaced here.
The dummy should be a gtkbin and it's parent should be a box with just one child */
static GtkWidget*
switch_dummy_to_mux_frame (GtkWidget *dummy)
{
GtkWidget *frame, *parent;
const char *title;
g_assert (GTK_IS_BIN (dummy));
frame = mux_frame_new ();
gtk_widget_set_name (frame, gtk_widget_get_name (dummy));
title = gtk_frame_get_label (GTK_FRAME(dummy));
if (title && strlen (title) > 0)
gtk_frame_set_label (GTK_FRAME (frame), title);
parent = gtk_widget_get_parent (dummy);
g_assert (GTK_IS_BOX (parent));
gtk_widget_reparent (gtk_bin_get_child (GTK_BIN (dummy)), frame);
gtk_container_remove (GTK_CONTAINER (parent), dummy);
/* make sure there are no other children in box */
g_assert (gtk_container_get_children (GTK_CONTAINER (parent)) == NULL);
gtk_box_pack_start (GTK_BOX (parent), frame, TRUE, TRUE, 0);
gtk_widget_show (frame);
return frame;
}
/* truly stupid, but glade doesn't allow custom containers.
Now glade file has dummy containers that will be replaced here.
The dummy should be a gtkwindow */
static GtkWidget*
switch_dummy_to_mux_window (GtkWidget *dummy)
{
GtkWidget *window;
const char *title;
g_assert (GTK_IS_WINDOW (dummy));
window = mux_window_new ();
gtk_widget_set_name (window, gtk_widget_get_name (dummy));
title = gtk_window_get_title (GTK_WINDOW (dummy));
if (title && strlen (title) > 0)
gtk_window_set_title (GTK_WINDOW (window), title);
mux_window_set_decorations (MUX_WINDOW (window), MUX_DECOR_CLOSE);
gtk_widget_reparent (gtk_bin_get_child (GTK_BIN (dummy)), window);
return window;
}
#else
/* return the placeholders themselves when not using Moblin UX */
static GtkWidget*
switch_dummy_to_mux_frame (GtkWidget *dummy) {
return dummy;
}
static GtkWidget*
switch_dummy_to_mux_window (GtkWidget *dummy)
{
return dummy;
}
#endif
static void
show_link_button_url (GtkLinkButton *link)
{
const char *url;
GError *error = NULL;
url = gtk_link_button_get_uri (GTK_LINK_BUTTON (link));
if (!g_app_info_launch_default_for_uri (url, NULL, &error)) {
g_warning ("Failed to show url '%s': %s", url, error->message);
g_error_free (error);
}
}
/* keypress handler for the transient windows (service list & service settings) */
static gboolean
key_press_cb (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data)
{
if (event->keyval == GDK_Escape) {
gtk_widget_hide (widget);
return TRUE;
}
return FALSE;
}
static gboolean
init_ui (app_data *data)
{
GtkBuilder *builder;
GError *error = NULL;
GObject *radio;
GtkWidget *frame, *service_save_btn;
gtk_rc_parse (THEMEDIR "sync-ui.rc");
builder = gtk_builder_new ();
gtk_builder_add_from_file (builder, GLADEDIR "ui.xml", &error);
if (error) {
g_printerr ("Failed to load user interface from %s\n", GLADEDIR "ui.xml");
g_error_free (error);
g_object_unref (builder);
return FALSE;
}
data->server_box = GTK_WIDGET (gtk_builder_get_object (builder, "server_box"));
data->no_server_box = GTK_WIDGET (gtk_builder_get_object (builder, "no_server_box"));
data->server_failure_box = GTK_WIDGET (gtk_builder_get_object (builder, "server_failure_box"));
data->error_img = GTK_WIDGET (gtk_builder_get_object (builder, "error_img"));
data->error_box = GTK_WIDGET (gtk_builder_get_object (builder, "error_box"));
data->server_icon_box = GTK_WIDGET (gtk_builder_get_object (builder, "server_icon_box"));
data->progress = GTK_WIDGET (gtk_builder_get_object (builder, "progressbar"));
data->edit_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "edit_service_btn"));
data->change_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "change_service_btn"));
data->sync_btn = GTK_WIDGET (gtk_builder_get_object (builder, "sync_btn"));
data->sync_status_label = GTK_WIDGET (gtk_builder_get_object (builder, "sync_status_label"));
data->server_label = GTK_WIDGET (gtk_builder_get_object (builder, "sync_service_label"));
data->last_synced_label = GTK_WIDGET (gtk_builder_get_object (builder, "last_synced_label"));
data->sources_box = GTK_WIDGET (gtk_builder_get_object (builder, "sources_box"));
data->new_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "new_service_btn"));
gtk_widget_set_size_request (data->new_service_btn,
SYNC_UI_LIST_BTN_WIDTH, SYNC_UI_LIST_ICON_SIZE);
g_signal_connect (data->new_service_btn, "clicked",
G_CALLBACK (setup_new_service_clicked), data);
data->services_table = GTK_WIDGET (gtk_builder_get_object (builder, "services_table"));
data->manual_services_table = GTK_WIDGET (gtk_builder_get_object (builder, "manual_services_table"));
data->manual_services_scrolled = GTK_WIDGET (gtk_builder_get_object (builder, "manual_services_scrolled"));
data->back_btn = GTK_WIDGET (gtk_builder_get_object (builder, "back_btn"));
data->service_link = GTK_WIDGET (gtk_builder_get_object (builder, "service_link"));
data->service_name_label = GTK_WIDGET (gtk_builder_get_object (builder, "service_name_label"));
data->service_name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "service_name_entry"));
data->server_settings_expander = GTK_WIDGET (gtk_builder_get_object (builder, "server_settings_expander"));
data->username_entry = GTK_WIDGET (gtk_builder_get_object (builder, "username_entry"));
data->password_entry = GTK_WIDGET (gtk_builder_get_object (builder, "password_entry"));
data->server_settings_table = GTK_WIDGET (gtk_builder_get_object (builder, "server_settings_table"));
data->reset_server_btn = GTK_WIDGET (gtk_builder_get_object (builder, "reset_server_btn"));
data->delete_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "delete_service_btn"));
data->stop_using_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "stop_using_service_btn"));
service_save_btn = GTK_WIDGET (gtk_builder_get_object (builder, "service_save_btn"));
radio = gtk_builder_get_object (builder, "two_way_radio");
g_object_set_data (radio, "mode", GINT_TO_POINTER (SYNC_TWO_WAY));
g_signal_connect (radio, "toggled",
G_CALLBACK (sync_type_toggled_cb), data);
radio = gtk_builder_get_object (builder, "one_way_from_remote_radio");
g_object_set_data (radio, "mode", GINT_TO_POINTER (SYNC_REFRESH_FROM_SERVER));
g_signal_connect (radio, "toggled",
G_CALLBACK (sync_type_toggled_cb), data);
radio = gtk_builder_get_object (builder, "one_way_from_local_radio");
g_object_set_data (radio, "mode", GINT_TO_POINTER (SYNC_REFRESH_FROM_CLIENT));
g_signal_connect (radio, "toggled",
G_CALLBACK (sync_type_toggled_cb), data);
/* No (documented) way to add own widgets to gtkbuilder it seems...
swap the all dummy widgets with Muxwidgets */
data->sync_win = switch_dummy_to_mux_window (GTK_WIDGET (gtk_builder_get_object (builder, "sync_win")));
data->services_win = switch_dummy_to_mux_window (GTK_WIDGET (gtk_builder_get_object (builder, "services_win")));
gtk_window_set_transient_for (GTK_WINDOW (data->services_win),
GTK_WINDOW (data->sync_win));
data->service_settings_win = switch_dummy_to_mux_window (GTK_WIDGET (gtk_builder_get_object (builder, "service_settings_win")));
data->main_frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "main_frame")));
data->log_frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "log_frame")));
frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "services_list_frame")));
data->service_settings_frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "service_settings_frame")));
g_signal_connect (data->sync_win, "destroy",
G_CALLBACK (gtk_main_quit), NULL);
g_signal_connect (data->services_win, "delete-event",
G_CALLBACK (gtk_widget_hide_on_delete), NULL);
g_signal_connect (data->services_win, "key-press-event",
G_CALLBACK (key_press_cb), NULL);
g_signal_connect (data->service_settings_win, "delete-event",
G_CALLBACK (gtk_widget_hide_on_delete), NULL);
g_signal_connect (data->service_settings_win, "key-press-event",
G_CALLBACK (key_press_cb), NULL);
g_signal_connect_swapped (data->back_btn, "clicked",
G_CALLBACK (gtk_widget_hide), data->services_win);
g_signal_connect (data->service_link, "clicked",
G_CALLBACK (show_link_button_url), NULL);
g_signal_connect (data->delete_service_btn, "clicked",
G_CALLBACK (delete_service_clicked_cb), data);
g_signal_connect (data->stop_using_service_btn, "clicked",
G_CALLBACK (stop_using_service_clicked_cb), data);
g_signal_connect (data->reset_server_btn, "clicked",
G_CALLBACK (reset_service_clicked_cb), data);
g_signal_connect (service_save_btn, "clicked",
G_CALLBACK (service_save_clicked_cb), data);
g_signal_connect (data->change_service_btn, "clicked",
G_CALLBACK (change_service_clicked_cb),
data);
g_signal_connect (data->edit_service_btn, "clicked",
G_CALLBACK (edit_services_clicked_cb), data);
g_signal_connect (data->sync_btn, "clicked",
G_CALLBACK (sync_clicked_cb), data);
g_object_unref (builder);
return TRUE;
}
static void
add_server_option (SyncevoOption *option, server_config *server)
{
server_config_update_from_option (server, option);
}
static void
source_check_toggled_cb (GtkCheckButton *check, app_data *data)
{
GPtrArray *options;
gboolean *enabled;
enabled = g_object_get_data (G_OBJECT (check), "enabled");
*enabled = !*enabled;
options = server_config_get_option_array (data->current_service);
syncevo_service_set_server_config_async (data->service,
data->current_service->name,
options,
(SyncevoSetServerConfigCb)set_server_config_cb,
data);
g_ptr_array_foreach (options, (GFunc)syncevo_option_free, NULL);
g_ptr_array_free (options, TRUE);
}
static void
load_icon (const char *uri, GtkBox *icon_box, guint icon_size)
{
GError *error = NULL;
GdkPixbuf *pixbuf;
GtkWidget *image;
const char *filename;
if (uri && strlen (uri) > 0) {
if (g_str_has_prefix (uri, "file://")) {
filename = uri+7;
} else {
g_warning ("only file:// icon uri is supported: %s", uri);
filename = THEMEDIR "sync-generic.png";
}
} else {
filename = THEMEDIR "sync-generic.png";
}
pixbuf = gdk_pixbuf_new_from_file_at_scale (filename,
icon_size, icon_size,
TRUE, &error);
if (!pixbuf) {
g_warning ("Failed to load service icon: %s", error->message);
g_error_free (error);
return;
}
image = gtk_image_new_from_pixbuf (pixbuf);
g_object_unref (pixbuf);
gtk_container_foreach (GTK_CONTAINER (icon_box),
(GtkCallback)remove_child,
icon_box);
gtk_box_pack_start_defaults (icon_box, image);
gtk_widget_show (image);
}
static void
update_service_ui (app_data *data)
{
GList *l;
g_assert (data->current_service);
gtk_container_foreach (GTK_CONTAINER (data->sources_box),
(GtkCallback)remove_child,
data->sources_box);
g_hash_table_remove_all (data->source_report_labels);
if (data->current_service->name)
gtk_label_set_markup (GTK_LABEL (data->server_label), data->current_service->name);
load_icon (data->current_service->icon_uri,
GTK_BOX (data->server_icon_box),
SYNC_UI_ICON_SIZE);
for (l = data->current_service->source_configs; l; l = l->next) {
source_config *source = (source_config*)l->data;
GtkWidget *check, *box, *lbl;
char *name;
name = get_pretty_source_name (source->name);
box = gtk_vbox_new (FALSE, 0);
if (source->uri && strlen (source->uri) > 0) {
check = gtk_check_button_new_with_label (name);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), source->enabled);
gtk_widget_set_sensitive (check, TRUE);
} else {
char *text;
/* TRANSLATORS: placeholder is a source name, shown with checkboxes in main window */
text = g_strdup_printf (_("%s (not supported by this service)"), name);
check = gtk_check_button_new_with_label (text);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), FALSE);
gtk_widget_set_sensitive (check, FALSE);
}
g_free (name);
g_object_set_data (G_OBJECT (check), "enabled", &source->enabled);
g_signal_connect (check, "toggled",
G_CALLBACK (source_check_toggled_cb), data);
gtk_box_pack_start_defaults (GTK_BOX (box), check);
lbl = gtk_label_new (NULL);
gtk_misc_set_alignment (GTK_MISC (lbl), 0.0, 0.5);
gtk_misc_set_padding (GTK_MISC (lbl), 23, 0);
gtk_box_pack_start_defaults (GTK_BOX (box), lbl);
/* this is a bit hacky... maybe the link to the label should be in source_config ? */
g_hash_table_insert (data->source_report_labels,
source->name,
lbl);
gtk_box_pack_start_defaults (GTK_BOX (data->sources_box), box);
}
gtk_widget_show_all (data->sources_box);
}
static char*
get_report_summary (int local_changes, int remote_changes, int local_rejects, int remote_rejects)
{
char *rejects, *changes, *msg;
if (local_rejects + remote_rejects == 0) {
rejects = NULL;
} else if (local_rejects == 0) {
rejects = g_strdup_printf (ngettext ("There was one remote rejection.",
"There were %d remote rejections.",
remote_rejects),
remote_rejects);
} else if (remote_rejects == 0) {
rejects = g_strdup_printf (ngettext ("There was one local rejection.",
"There were %d local rejections.",
local_rejects),
local_rejects);
} else {
rejects = g_strdup_printf (_ ("There were %d local rejections and %d remote rejections."),
local_rejects, remote_rejects);
}
if (local_changes + remote_changes == 0) {
changes = g_strdup_printf (_("Last time: No changes."));
} else if (local_changes == 0) {
changes = g_strdup_printf (ngettext ("Last time: Sent one change.",
"Last time: Sent %d changes.",
remote_changes),
remote_changes);
} else if (remote_changes == 0) {
changes = g_strdup_printf (ngettext ("Last time: Received one change.",
"Last time: Received %d changes.",
local_changes),
local_changes);
} else {
changes = g_strdup_printf (_("Last time: Received %d changes and sent %d changes."),
local_changes, remote_changes);
}
if (rejects)
msg = g_strdup_printf ("%s\n%s", changes, rejects);
else
msg = g_strdup (changes);
g_free (rejects);
g_free (changes);
return msg;
}
static void
update_sync_report_data (SyncevoReport *report, app_data *data)
{
const char *name;
GtkLabel *lbl;
name = syncevo_report_get_name (report);
lbl = GTK_LABEL (g_hash_table_lookup (data->source_report_labels, name));
if (lbl) {
char *msg;
int local_changes, local_adds, local_updates, local_removes, local_rejects;
int remote_changes, remote_adds, remote_updates, remote_removes, remote_rejects;
syncevo_report_get_local (report, &local_adds, &local_updates, &local_removes, &local_rejects);
syncevo_report_get_remote (report, &remote_adds, &remote_updates, &remote_removes, &remote_rejects);
local_changes = local_adds + local_updates + local_removes;
remote_changes = remote_adds + remote_updates + remote_removes;
msg = get_report_summary (local_changes, remote_changes, local_rejects, remote_rejects);
gtk_label_set_text (lbl, msg);
g_free (msg);
}
}
static void
get_sync_reports_cb (SyncevoService *service, GPtrArray *reports, GError *error, app_data *data)
{
if (error) {
g_warning ("Failed to get sync reports from SyncEvolution: %s", error->message);
g_error_free (error);
return;
}
if (reports->len < 1) {
data->last_sync = -1;
} else {
GPtrArray *source_reports;
SyncevoReportArray *session_report = (SyncevoReportArray*)g_ptr_array_index (reports, 0);
syncevo_report_array_get (session_report, &data->last_sync, &source_reports);
g_ptr_array_foreach (source_reports, (GFunc)update_sync_report_data, data);
}
refresh_last_synced_label (data);
g_ptr_array_foreach (reports, (GFunc)syncevo_report_array_free, NULL);
g_ptr_array_free (reports, TRUE);
}
static void
get_server_config_cb (SyncevoService *service, GPtrArray *options, GError *error, app_data *data)
{
if (error) {
/* don't warn if server has disappeared -- probably just command line use */
if (error->code != SYNCEVO_DBUS_ERROR_NO_SUCH_SERVER) {
add_error_info (data,
_("Failed to get server configuration from SyncEvolution"),
error->message);
set_app_state (data, SYNC_UI_STATE_NO_SERVER);
} else {
set_app_state (data, SYNC_UI_STATE_SERVER_FAILURE);
}
g_error_free (error);
return;
}
g_ptr_array_foreach (options, (GFunc)add_server_option, data->current_service);
ensure_default_sources_exist (data->current_service);
update_service_ui (data);
set_app_state (data, SYNC_UI_STATE_SERVER_OK);
/* get last sync report (for last sync time) */
syncevo_service_get_sync_reports_async (service, data->current_service->name, 1,
(SyncevoGetSyncReportsCb)get_sync_reports_cb,
data);
g_ptr_array_foreach (options, (GFunc)syncevo_option_free, NULL);
g_ptr_array_free (options, TRUE);
}
static void
show_settings_window (app_data *data, server_config *config)
{
GList *l;
GtkWidget *label, *entry;
int i = 0;
gtk_container_foreach (GTK_CONTAINER (data->server_settings_table),
(GtkCallback)remove_child,
data->server_settings_table);
gtk_table_resize (GTK_TABLE (data->server_settings_table),
2, g_list_length (config->source_configs) + 1);
gtk_entry_set_text (GTK_ENTRY (data->service_name_entry),
config->name ? config->name : "");
g_object_set_data (G_OBJECT (data->service_name_entry), "value", &config->name);
if (config->name) {
gtk_frame_set_label (GTK_FRAME (data->service_settings_frame), config->name);
gtk_widget_hide (data->service_name_label);
gtk_widget_hide (data->service_name_entry);
} else {
gtk_frame_set_label (GTK_FRAME (data->service_settings_frame), _("New service"));
gtk_widget_show (data->service_name_label);
gtk_widget_show (data->service_name_entry);
}
if (config->web_url) {
gtk_link_button_set_uri (GTK_LINK_BUTTON (data->service_link),
config->web_url);
gtk_widget_show (data->service_link);
} else {
gtk_widget_hide (data->service_link);
}
gtk_expander_set_expanded (GTK_EXPANDER (data->server_settings_expander),
!config->from_template);
if (config->from_template) {
gtk_widget_show (GTK_WIDGET (data->reset_server_btn));
gtk_widget_hide (GTK_WIDGET (data->delete_service_btn));
} else {
gtk_widget_hide (GTK_WIDGET (data->reset_server_btn));
if (config->name) {
gtk_widget_show (GTK_WIDGET (data->delete_service_btn));
} else {
gtk_widget_hide (GTK_WIDGET (data->delete_service_btn));
}
}
if (data->current_service && data->current_service->name && config->name &&
strcmp (data->current_service->name, config->name) == 0)
gtk_widget_show (data->stop_using_service_btn);
else
gtk_widget_hide (data->stop_using_service_btn);
gtk_entry_set_text (GTK_ENTRY (data->username_entry),
config->username ? config->username : "");
g_object_set_data (G_OBJECT (data->username_entry), "value", &config->username);
gtk_entry_set_text (GTK_ENTRY (data->password_entry),
config->password ? config->password : "");
g_object_set_data (G_OBJECT (data->password_entry), "value", &config->password);
label = gtk_label_new (_("Server URL"));
gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
gtk_table_attach (GTK_TABLE (data->server_settings_table), label,
0, 1, i, i + 1, GTK_FILL, GTK_EXPAND, 0, 0);
entry = gtk_entry_new_with_max_length (100);
gtk_entry_set_text (GTK_ENTRY (entry),
config->base_url ? config->base_url : "");
g_object_set_data (G_OBJECT (entry), "value", &config->base_url);
gtk_table_attach_defaults (GTK_TABLE (data->server_settings_table), entry,
1, 2, i, i + 1);
for (l = config->source_configs; l; l = l->next) {
source_config *source = (source_config*)l->data;
char *str;
char *name;
i++;
name = get_pretty_source_name (source->name);
/* TRANSLATORS: placeholder is a source name in settings window */
str = g_strdup_printf (_("%s URI"), name);
label = gtk_label_new (str);
g_free (str);
g_free (name);
gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
gtk_table_attach (GTK_TABLE (data->server_settings_table), label,
0, 1, i, i + 1, GTK_FILL, GTK_EXPAND, 0, 0);
entry = gtk_entry_new_with_max_length (100);
gtk_entry_set_text (GTK_ENTRY (entry),
source->uri ? source->uri : "");
g_object_set_data (G_OBJECT (entry), "value", &source->uri);
g_object_set_data (G_OBJECT (entry), "enabled", &source->enabled);
gtk_table_attach_defaults (GTK_TABLE (data->server_settings_table), entry,
1, 2, i, i + 1);
}
gtk_widget_show_all (data->server_settings_table);
g_object_set_data (G_OBJECT (data->service_settings_win), "server", config);
gtk_window_present (GTK_WINDOW (data->service_settings_win));
}
static void
ensure_default_sources_exist(server_config *server)
{
server_config_get_source_config (server, "addressbook");
server_config_get_source_config (server, "calendar");
server_config_get_source_config (server, "memo");
server_config_get_source_config (server, "todo");
}
static void
setup_service_clicked (GtkButton *btn, app_data *data)
{
SyncevoServer *server;
server_data *serv_data;
const char *name;
server = g_object_get_data (G_OBJECT (btn), "server");
syncevo_server_get (server, &name, NULL, NULL);
serv_data = g_slice_new0 (server_data);
serv_data->data = data;
serv_data->server_name = g_strdup (name);
gtk_window_set_transient_for (GTK_WINDOW (data->service_settings_win),
GTK_WINDOW (data->services_win));
syncevo_service_get_server_config_async (data->service,
(char*)serv_data->server_name,
(SyncevoGetServerConfigCb)get_server_config_for_template_cb,
serv_data);
}
static void
setup_new_service_clicked (GtkButton *btn, app_data *data)
{
server_data *serv_data;
SyncevoOption *option;
serv_data = g_slice_new0 (server_data);
serv_data->data = data;
/* syncevolution defaults are not empty, override ... */
serv_data->options_override = g_ptr_array_new ();
option = syncevo_option_new (NULL, "syncURL", NULL);
g_ptr_array_add (serv_data->options_override, option);
option = syncevo_option_new (NULL, "webURL", NULL);
g_ptr_array_add (serv_data->options_override, option);
option = syncevo_option_new (NULL, "fromTemplate", "no");
g_ptr_array_add (serv_data->options_override, option);
option = syncevo_option_new ("memo", "uri", NULL);
g_ptr_array_add (serv_data->options_override, option);
option = syncevo_option_new ("todo", "uri", NULL);
g_ptr_array_add (serv_data->options_override, option);
option = syncevo_option_new ("addressbook", "uri", NULL);
g_ptr_array_add (serv_data->options_override, option);
option = syncevo_option_new ("calendar", "uri", NULL);
g_ptr_array_add (serv_data->options_override, option);
gtk_window_set_transient_for (GTK_WINDOW (data->service_settings_win),
GTK_WINDOW (data->services_win));
syncevo_service_get_server_config_async (data->service,
"default",
(SyncevoGetServerConfigCb)get_server_config_for_template_cb,
serv_data);
}
enum ServerCols {
COL_ICON = 0,
COL_NAME,
COL_LINK,
COL_BUTTON,
NR_SERVER_COLS
};
static void
add_server_to_table (GtkTable *table, int row, SyncevoServer *server, app_data *data)
{
GtkWidget *label, *box, *link, *btn;
const char *name, *url, *icon;
syncevo_server_get (server, &name, &url, &icon);
box = gtk_hbox_new (FALSE, 0);
gtk_widget_set_size_request (box, SYNC_UI_LIST_ICON_SIZE, SYNC_UI_LIST_ICON_SIZE);
gtk_table_attach (table, box, COL_ICON, COL_ICON + 1, row, row+1,
GTK_SHRINK|GTK_FILL, GTK_EXPAND|GTK_FILL, 5, 0);
load_icon (icon, GTK_BOX (box), SYNC_UI_LIST_ICON_SIZE);
label = gtk_label_new (name);
if (data->current_service && data->current_service->name &&
strcmp (name, data->current_service->name) == 0) {
char *str = g_strdup_printf ("<b>%s</b>", name);
gtk_label_set_markup (GTK_LABEL (label), str);
g_free (str);
}
gtk_widget_set_size_request (label, SYNC_UI_LIST_BTN_WIDTH, -1);
gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
gtk_table_attach (table, label, COL_NAME, COL_NAME + 1, row, row+1,
GTK_SHRINK|GTK_FILL, GTK_EXPAND|GTK_FILL, 5, 0);
box = gtk_hbox_new (FALSE, 0);
gtk_table_attach (table, box, COL_LINK, COL_LINK + 1, row, row+1,
GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 5, 0);
if (url && strlen (url) > 0) {
link = gtk_link_button_new_with_label (url, _("Launch website"));
g_signal_connect (link, "clicked",
G_CALLBACK (show_link_button_url), NULL);
gtk_box_pack_start (GTK_BOX (box), link, FALSE, FALSE, 0);
}
btn = gtk_button_new_with_label (_("Setup and use"));
gtk_widget_set_size_request (btn, SYNC_UI_LIST_BTN_WIDTH, -1);
g_signal_connect (btn, "clicked",
G_CALLBACK (setup_service_clicked), data);
gtk_table_attach (table, btn, COL_BUTTON, COL_BUTTON + 1, row, row+1,
GTK_SHRINK|GTK_FILL, GTK_EXPAND|GTK_FILL, 5, 0);
g_object_set_data_full (G_OBJECT (btn), "server", server,
(GDestroyNotify)syncevo_server_free);
}
typedef struct templates_data {
app_data *data;
GPtrArray *templates;
}templates_data;
static gboolean
server_array_contains (GPtrArray *array, SyncevoServer *server)
{
int i;
const char *name;
syncevo_server_get (server, &name, NULL, NULL);
for (i = 0; i < array->len; i++) {
const char *n;
SyncevoServer *s = (SyncevoServer*)g_ptr_array_index (array, i);
syncevo_server_get (s, &n, NULL, NULL);
if (g_ascii_strcasecmp (name, n) == 0)
return TRUE;
}
return FALSE;
}
static void
get_servers_cb (SyncevoService *service,
GPtrArray *servers,
GError *error,
templates_data *templ_data)
{
int i, k = 0;
app_data *data = templ_data->data;
if (error) {
gtk_widget_hide (data->services_win);
show_error_dialog (GTK_WINDOW (data->sync_win),
_("Failed to get list of manually setup services from SyncEvolution"));
g_warning ("Failed to get list of manually setup services from SyncEvolution: %s",
error->message);
g_error_free (error);
return;
}
gtk_table_resize (GTK_TABLE (data->manual_services_table), 2, 4);
for (i = 0; i < servers->len; i++) {
SyncevoServer *server = (SyncevoServer*)g_ptr_array_index (servers, i);
/* make sure server is not added as template already */
if (!server_array_contains (templ_data->templates, server)) {
add_server_to_table (GTK_TABLE (data->manual_services_table), k++,
server, data);
}
}
if (k > 0) {
gtk_widget_show_all (data->manual_services_scrolled);
} else {
gtk_widget_hide (data->manual_services_scrolled);
}
/* the SyncevoServers in arrays are freed when the table gets freed */
g_ptr_array_free (templ_data->templates, TRUE);
g_ptr_array_free (servers, TRUE);
g_slice_free (templates_data, templ_data);
}
static void
get_templates_cb (SyncevoService *service,
GPtrArray *templates,
GError *error,
app_data *data)
{
int i;
templates_data *temps_data;
if (error) {
show_error_dialog (GTK_WINDOW (data->sync_win),
_("Failed to get list of supported services from SyncEvolution"));
g_warning ("Failed to get list of supported services from SyncEvolution: %s",
error->message);
g_error_free (error);
gtk_widget_hide (data->services_win);
return;
}
gtk_table_resize (GTK_TABLE (data->services_table),
templates->len, 4);
for (i = 0; i < templates->len; i++) {
add_server_to_table (GTK_TABLE (data->services_table), i,
(SyncevoServer*)g_ptr_array_index (templates, i),
data);
}
gtk_widget_show_all (data->services_table);
temps_data = g_slice_new0 (templates_data);
temps_data->data = data;
temps_data->templates = templates;
syncevo_service_get_servers_async (data->service,
(SyncevoGetServerConfigCb)get_servers_cb,
temps_data);
}
static void show_services_window (app_data *data)
{
gtk_container_foreach (GTK_CONTAINER (data->services_table),
(GtkCallback)remove_child,
data->services_table);
gtk_container_foreach (GTK_CONTAINER (data->manual_services_table),
(GtkCallback)remove_child,
data->manual_services_table);
syncevo_service_get_templates_async (data->service,
(SyncevoGetServerConfigCb)get_templates_cb,
data);
gtk_window_present (GTK_WINDOW (data->services_win));
}
static void
gconf_change_cb (GConfClient *client, guint id, GConfEntry *entry, app_data *data)
{
char *server = NULL;
GError *error = NULL;
server = gconf_client_get_string (client, SYNC_UI_SERVER_KEY, &error);
if (error) {
g_warning ("Could not read current server name from gconf: %s", error->message);
g_error_free (error);
error = NULL;
}
/*TODO: avoid the rest if server did not actually change */
server_config_free (data->current_service);
if (!server || strlen (server) == 0) {
data->current_service = NULL;
set_app_state (data, SYNC_UI_STATE_NO_SERVER);
} else {
data->synced_this_session = FALSE;
data->current_service = g_slice_new0 (server_config);
data->current_service->name = server;
set_app_state (data, SYNC_UI_STATE_GETTING_SERVER);
syncevo_service_get_server_config_async (data->service,
server,
(SyncevoGetServerConfigCb)get_server_config_cb,
data);
}
}
static void
init_configuration (app_data *data)
{
GConfClient* client;
client = gconf_client_get_default ();
gconf_client_add_dir (client, SYNC_UI_GCONF_DIR, GCONF_CLIENT_PRELOAD_RECURSIVE, NULL);
gconf_client_notify_add (client, SYNC_UI_GCONF_DIR, (GConfClientNotifyFunc)gconf_change_cb,
data, NULL, NULL);
/* fake gconf change to init values */
gconf_change_cb (client, 0, NULL, data);
}
static void
calc_and_update_progress (app_data *data, char *msg)
{
float progress;
GList *list;
int count = 0;
progress = 0.0;
for (list = data->source_progresses; list; list = list->next) {
source_progress *p = (source_progress*)list->data;
if (p->prepare_total > 0)
progress += SYNC_PROGRESS_PREPARE * p->prepare_current / p->prepare_total;
if (p->send_total > 0)
progress += SYNC_PROGRESS_SEND * p->send_current / p->send_total;
if (p->receive_total > 0)
progress += SYNC_PROGRESS_RECEIVE * p->receive_current / p->receive_total;
count++;
}
set_sync_progress (data, sync_progress_sync_start + (progress / count), msg);
}
static void
refresh_statistics (app_data *data)
{
GList *list;
for (list = data->source_progresses; list; list = list->next) {
source_progress *p = (source_progress*)list->data;
GtkLabel *lbl;
lbl = GTK_LABEL (g_hash_table_lookup (data->source_report_labels, p->name));
if (lbl) {
char *msg;
msg = get_report_summary (p->added_local + p->modified_local + p->deleted_local,
p->added_remote + p->modified_remote + p->deleted_remote,
p->rejected_local,
p->rejected_remote);
gtk_label_set_text (lbl, msg);
g_free (msg);
}
}
}
static source_progress*
find_source_progress (GList *source_progresses, char *name)
{
GList *list;
for (list = source_progresses; list; list = list->next) {
if (strcmp (((source_progress*)list->data)->name, name) == 0) {
return (source_progress*)list->data;
}
}
return NULL;
}
static char*
get_error_string_for_code (int error_code)
{
switch (error_code) {
case 0:
case LOCERR_USERABORT:
case LOCERR_USERSUSPEND:
return NULL;
case DB_Unauthorized:
return g_strdup(_("Not authorized"));
case DB_Forbidden:
return g_strdup(_("Forbidden"));
case DB_NotFound:
return g_strdup(_("Not found"));
case DB_Fatal:
return g_strdup(_("Fatal database error"));
case DB_Error:
return g_strdup(_("Database error"));
case DB_Full:
return g_strdup(_("No space left"));
case LOCERR_PROCESSMSG:
/* TODO identify problem item somehow ? */
return g_strdup(_("Failed to process SyncML"));
case LOCERR_AUTHFAIL:
return g_strdup(_("Server authorization failed"));
case LOCERR_CFGPARSE:
return g_strdup(_("Failed to parse configuration file"));
case LOCERR_CFGREAD:
return g_strdup(_("Failed to read configuration file"));
case LOCERR_NOCFG:
return g_strdup(_("No configuration found"));
case LOCERR_NOCFGFILE:
return g_strdup(_("No configuration file found"));
case LOCERR_BADCONTENT:
return g_strdup(_("Server sent bad content"));
case LOCERR_TIMEOUT:
return g_strdup(_("Connection timed out"));
case LOCERR_CERT_EXPIRED:
return g_strdup(_("Connection certificate has expired"));
case LOCERR_CERT_INVALID:
return g_strdup(_("Connection certificate is invalid"));
case LOCERR_CONN:
case LOCERR_NOCONN:
return g_strdup(_("Connection failed"));
case LOCERR_BADURL:
return g_strdup(_("URL is bad"));
case LOCERR_SRVNOTFOUND:
return g_strdup(_("Server not found"));
default:
return g_strdup_printf (_("Error %d"), error_code);
}
}
static void
sync_progress_cb (SyncevoService *service,
char *server,
char *source,
int type,
int extra1, int extra2, int extra3,
app_data *data)
{
static source_progress *source_prog;
char *msg = NULL;
char *error = NULL;
char *name = NULL;
GTimeVal val;
/* just in case UI was just started and there is another sync in progress */
set_app_state (data, SYNC_UI_STATE_SYNCING);
switch(type) {
case -1:
/* syncevolution finished sync */
error = get_error_string_for_code (extra1);
if (error)
add_error_info (data, error, NULL);
switch (extra1) {
case 0:
g_get_current_time (&val);
data->last_sync = val.tv_sec;
refresh_last_synced_label (data);
data->synced_this_session = TRUE;
gtk_label_set_text (GTK_LABEL (data->sync_status_label),
_("Sync complete"));
break;
case LOCERR_USERABORT:
case LOCERR_USERSUSPEND:
gtk_label_set_text (GTK_LABEL (data->sync_status_label),
_("Sync canceled"));
default:
gtk_label_set_text (GTK_LABEL (data->sync_status_label),
_("Sync Failed"));
}
/* get sync report */
refresh_statistics (data);
set_sync_progress (data, 1.0 , "");
set_app_state (data, SYNC_UI_STATE_SERVER_OK);
break;
case PEV_SESSIONSTART:
/* double check we're in correct state*/
set_app_state (data, SYNC_UI_STATE_SYNCING);
set_sync_progress (data, sync_progress_session_start, NULL);
break;
case PEV_SESSIONEND:
/* NOTE extra1 can be error here */
set_sync_progress (data, sync_progress_sync_end, _("Ending sync"));
break;
case PEV_ALERTED:
source_prog = g_slice_new0 (source_progress);
source_prog->name = g_strdup (source);
data->source_progresses = g_list_append (data->source_progresses, source_prog);
break;
case PEV_SENDSTART:
case PEV_SENDEND:
case PEV_RECVSTART:
case PEV_RECVEND:
/* these would be useful but they have no source so I can't tell how far we are... */
break;
case PEV_PREPARING:
/* find the right source (try last used one first) */
if (strcmp (source_prog->name, source) != 0) {
source_prog = find_source_progress (data->source_progresses, source);
if (!source_prog) {
g_warning ("Prepare: No alert received for source '%s'", source);
return;
}
}
source_prog->prepare_current = CLAMP (extra1, 0, extra2);
source_prog->prepare_total = extra2;
name = get_pretty_source_name (source);
/* TRANSLATORS: placeholder is a source name (e.g. 'Calendar') in a progress text */
msg = g_strdup_printf (_("Preparing '%s'"), name);
calc_and_update_progress(data, msg);
break;
case PEV_ITEMSENT:
/* find the right source (try last used one first) */
if (strcmp (source_prog->name, source) != 0) {
source_prog = find_source_progress (data->source_progresses, source);
if (!source_prog) {
g_warning ("Sent: No alert received for source '%s'", source);
return;
}
}
source_prog->send_current = CLAMP (extra1, 0, extra2);
source_prog->send_total = extra2;
name = get_pretty_source_name (source);
/* TRANSLATORS: placeholder is a source name in a progress text */
msg = g_strdup_printf (_("Sending '%s'"), name);
calc_and_update_progress (data, msg);
break;
case PEV_ITEMRECEIVED:
/* find the right source (try last used one first) */
if (strcmp (source_prog->name, source) != 0) {
source_prog = find_source_progress (data->source_progresses, source);
if (!source_prog) {
g_warning ("Received: No alert received for source '%s'", source);
return;
}
}
source_prog->receive_current = CLAMP (extra1, 0, extra2);
source_prog->receive_total = extra2;
name = get_pretty_source_name (source);
/* TRANSLATORS: placeholder is a source name in a progress text */
msg = g_strdup_printf (_("Receiving '%s'"), name);
calc_and_update_progress (data, msg);
break;
case PEV_SYNCEND:
error = get_error_string_for_code (extra1);
if (error) {
name = get_pretty_source_name (source);
msg = g_strdup_printf ("%s: %s", name, error);
add_error_info (data, msg, NULL);
}
break;
case PEV_DSSTATS_L:
source_prog = find_source_progress (data->source_progresses, source);
if (!source_prog)
return;
source_prog->added_local = extra1;
source_prog->modified_local = extra2;
source_prog->deleted_local = extra3;
break;
case PEV_DSSTATS_R:
source_prog = find_source_progress (data->source_progresses, source);
if (!source_prog)
return;
source_prog->added_remote = extra1;
source_prog->modified_remote = extra2;
source_prog->deleted_remote = extra3;
break;
case PEV_DSSTATS_E:
source_prog = find_source_progress (data->source_progresses, source);
if (!source_prog)
return;
source_prog->rejected_local = extra1;
source_prog->rejected_remote = extra2;
break;
case PEV_DSSTATS_D:
source_prog = find_source_progress (data->source_progresses, source);
if (!source_prog)
return;
source_prog->bytes_uploaded = extra1;
source_prog->bytes_downloaded = extra2;
break;
default:
;
}
g_free (msg);
g_free (error);
g_free (name);
}
int
main (int argc, char *argv[]) {
app_data *data;
gtk_init (&argc, &argv);
bindtextdomain (GETTEXT_PACKAGE, SYNCEVOLUTION_LOCALEDIR);
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
textdomain (GETTEXT_PACKAGE);
data = g_slice_new0 (app_data);
data->source_report_labels = g_hash_table_new (g_str_hash, g_str_equal);
if (!init_ui (data)) {
return (1);
}
data->service = syncevo_service_get_default();
g_signal_connect (data->service, "progress",
G_CALLBACK (sync_progress_cb), data);
init_configuration (data);
gtk_window_present (GTK_WINDOW (data->sync_win));
gtk_main();
return 0;
}