diff options
author | Jussi Kukkonen <jku@linux.intel.com> | 2011-11-17 14:24:23 +0200 |
---|---|---|
committer | Patrick Ohly <patrick.ohly@intel.com> | 2012-03-27 15:30:20 +0200 |
commit | f1454948b854840b3420e196b752db2fb0394b76 (patch) | |
tree | b877ea1dabae117ffc471817397647de72020152 | |
parent | a8ee160ce57ed09ab79518311cca0f71de27f902 (diff) |
gtk-ui: fork gtk3-version of the ui
We want to support both GTK+-2.0 and GTK+-3.0 for the time being
(even if the former is just maintenance). This is not possible in
the same codebase without large amounts of ifdefs so we fork the UI.
-rw-r--r-- | configure.ac | 43 | ||||
-rw-r--r-- | src/gtk-ui/README | 13 | ||||
-rw-r--r-- | src/gtk3-ui/README | 8 | ||||
-rw-r--r-- | src/gtk3-ui/gtk-ui.am | 97 | ||||
-rw-r--r-- | src/gtk3-ui/main.c | 179 | ||||
-rw-r--r-- | src/gtk3-ui/mux-frame.c | 372 | ||||
-rw-r--r-- | src/gtk3-ui/mux-frame.h | 64 | ||||
-rw-r--r-- | src/gtk3-ui/sync-config-widget.c | 2195 | ||||
-rw-r--r-- | src/gtk3-ui/sync-config-widget.h | 128 | ||||
-rw-r--r-- | src/gtk3-ui/sync-generic.png | bin | 0 -> 1669 bytes | |||
-rw-r--r-- | src/gtk3-ui/sync-gtk.desktop.in | 10 | ||||
-rw-r--r-- | src/gtk3-ui/sync-spinner.gif | bin | 0 -> 2150 bytes | |||
-rw-r--r-- | src/gtk3-ui/sync-ui-config.c | 160 | ||||
-rw-r--r-- | src/gtk3-ui/sync-ui-config.h | 95 | ||||
-rw-r--r-- | src/gtk3-ui/sync-ui.c | 3599 | ||||
-rw-r--r-- | src/gtk3-ui/sync-ui.h | 55 | ||||
-rw-r--r-- | src/gtk3-ui/sync-ui.rc | 49 | ||||
-rw-r--r-- | src/gtk3-ui/sync.desktop.in | 10 | ||||
-rw-r--r-- | src/gtk3-ui/sync.png | bin | 0 -> 3095 bytes | |||
-rw-r--r-- | src/gtk3-ui/ui.xml | 1509 | ||||
-rw-r--r-- | src/src.am | 7 |
21 files changed, 8581 insertions, 12 deletions
diff --git a/configure.ac b/configure.ac index 83aea255..65fa1e9d 100644 --- a/configure.ac +++ b/configure.ac @@ -397,6 +397,26 @@ fi # for dbus interface file mangling AC_PATH_PROG(XSLT, xsltproc) + +# Changes in GTK3 mean that supporting both GTK3 and GTK2 in the same codebase +# is difficult. We want to support GTK2 for the time being so the code is forked. +AC_ARG_ENABLE(gtk, + AS_HELP_STRING([--enable-gtk=major version], + [Selects the gtk+ version ("2" or "3") to use for the UI. + If this option is used, --enable-gui should be used as well. + "3" is the default option.]), + [ if test "$enableval" = "3" ; then + gtk_version=gtk+-3.0 + elif test "$enableval" = "2" ; then + gtk_version=gtk+-2.0 + else + AC_MSG_ERROR([Unknown gtk version: '$enableval']) + fi + ], + [ gtk_version=gtk+-3.0 ]) + +AM_CONDITIONAL([COND_GTK2], [test "$gtk_version" = "gtk+-2.0"]) + AC_ARG_ENABLE(gui, AS_HELP_STRING([--enable-gui[=gui type]], [enables building the GTK+ UI that uses the SyncEvolution DBus API. @@ -544,19 +564,30 @@ fi # decide which sync-ui(s) we are building: # sync-ui (in either GTK or Moblin mode) or both (in separate binaries) + +if test $gtk_version = "gtk+-3.0"; then + gtk_dir=src/gtk3-ui +else + gtk_dir=src/gtk-ui +fi +echo ${gtk_dir} + case $enable_gui in - all) GUI_PROGRAMS='src/gtk-ui/sync-ui-gtk${EXEEXT} src/gtk-ui/sync-ui-moblin${EXEEXT}'; GUI_DESKTOP_FILES="src/gtk-ui/sync-gtk.desktop src/gtk-ui/sync-moblin.desktop";; - gtk|moblin) GUI_PROGRAMS='src/gtk-ui/sync-ui${EXEEXT}'; GUI_DESKTOP_FILES="src/gtk-ui/sync.desktop";; + all) GUI_PROGRAMS=${gtk_dir}'/sync-ui-gtk${EXEEXT} '${gtk_dir}'/sync-ui-moblin${EXEEXT}'; GUI_DESKTOP_FILES="${gtk_dir}/sync-gtk.desktop ${gtk_dir}/sync-moblin.desktop";; + gtk|moblin) GUI_PROGRAMS=${gtk_dir}'/sync-ui${EXEEXT}'; GUI_DESKTOP_FILES="${gtk_dir}/sync.desktop";; no) GUI_PROGRAMS=; GUI_DESKTOP_FILES=;; *) AC_MSG_ERROR([Unknown enable_gui type: '$enable_gui']) esac +echo ${GUI_PROGRAMS} +echo ${GUI_DESKTOP_FILES} if test $enable_gui != "no"; then PKG_CHECK_MODULES(DBUS_GLIB, dbus-glib-1 glib-2.0) AC_PATH_PROG(DBUS_BINDING_TOOL, dbus-binding-tool) AC_PATH_PROG(GLIB_GENMARSHAL, glib-genmarshal) - gui_modules="glib-2.0 dbus-glib-1 >= 0.60 gtk+-2.0 libglade-2.0 gio-2.0" + gui_modules="$gtk_version glib-2.0 dbus-glib-1 >= 0.60 libglade-2.0 gio-2.0" + if test $enable_gui = "moblin"; then AC_DEFINE(USE_MOBLIN_UX, 1, [Use Moblin UI widgets]) fi @@ -1018,7 +1049,11 @@ echo "DBus service: $enable_dbus_service" echo "Notifications: $enable_notify" echo "GIO GDBus: $with_gio_gdbus" echo "GNOME keyring: $enable_gnome_keyring" -echo "UI (DBus client): $enable_gui" +if test "$enable_gui" = "no"; then + echo "UI (DBus client): no" +else + echo "UI (DBus client): $enable_gui (using $gtk_version)" +fi echo "Bluetooth transport: $have_bluetooth" echo "GNOME Bluetooth panel plugin: $enable_gnome_bluetooth_panel" echo "SHA-256: $have_sha" diff --git a/src/gtk-ui/README b/src/gtk-ui/README index c333eab9..3727b0a1 100644 --- a/src/gtk-ui/README +++ b/src/gtk-ui/README @@ -4,13 +4,10 @@ SyncEvolution Graphical User Interface for Moblin Syncevolution D-Bus service must be either running or installed properly (so DBus autostart works). the client itself must be installed - so gtkbuilder xml file and rc style file get found. + so glade ui file and rc style file get found. + * Support - * Bugs - - - There is an initial flicker when windows are shown. - This is probably a MuxBin initialization problem. - - Sync progress notification and logs are not implemented fully - - Restoring a backup is not implemented - - Not possible to toggle Moblin / normal GTK+ style in configure + This is the GTK+-2.0 version of the client. It is supported but sohuld be + considered to be in maintenance mode: Please consider the version in + src/gtk3-ui. diff --git a/src/gtk3-ui/README b/src/gtk3-ui/README new file mode 100644 index 00000000..e8d061b4 --- /dev/null +++ b/src/gtk3-ui/README @@ -0,0 +1,8 @@ +SyncEvolution Graphical User Interface for Moblin + + * Running + + Syncevolution D-Bus service must be either running or installed + properly (so DBus autostart works). the client itself must be installed + so glade ui file and rc style file get found. + diff --git a/src/gtk3-ui/gtk-ui.am b/src/gtk3-ui/gtk-ui.am new file mode 100644 index 00000000..27dbe255 --- /dev/null +++ b/src/gtk3-ui/gtk-ui.am @@ -0,0 +1,97 @@ +dist_noinst_DATA += \ + src/gtk3-ui/README + +src_gtk3_ui_applicationsdir = $(datadir)/applications +src_gtk3_ui_applications_in_files = \ + src/gtk3-ui/sync.desktop.in \ + src/gtk3-ui/sync-gtk.desktop.in +src_gtk3_ui_applications_generated = $(src_gtk3_ui_applications_in_files:.desktop.in=.desktop) +src_gtk3_ui_applications_DATA = @GUI_DESKTOP_FILES@ + +# if this will pose a problem then see the link below, probably the solution +# here will need to be used. +# http://mail.gnome.org/archives/commits-list/2010-October/msg05148.html +@INTLTOOL_DESKTOP_RULE@ + +# When installing both the plain GTK and the Moblin-themed version, +# the Moblin version uses the normal "Sync - Up to date" name/comment +# and the GTK version uses "Sync (GTK)" as name with the same +# comment. This is a somewhat arbitrary choice, with the rationale +# being that a Moblin user is less likely to care about the +# distinction while a GTK user might understand what "(GTK)" means. +src/gtk3-ui/sync-moblin.desktop: src/gtk3-ui/sync.desktop + $(AM_V_GEN)cp $< $@ + +src_gtk3_ui_gladedir = $(datadir)/syncevolution/ +src_gtk3_ui_glade_DATA = src/gtk3-ui/ui.xml + +src_gtk3_ui_icondir = $(datadir)/icons/hicolor/48x48/apps +dist_src_gtk3_ui_icon_DATA = src/gtk3-ui/sync.png + +src_gtk3_ui_themercfiles = \ + src/gtk3-ui/sync-generic.png \ + src/gtk3-ui/sync-spinner.gif \ + src/gtk3-ui/sync-ui.rc + +src_gtk3_ui_themercdir = $(datadir)/syncevolution/ +dist_src_gtk3_ui_themerc_DATA = $(src_gtk3_ui_themercfiles) + +src_gtk3_ui_desktopdir = $(datadir)/applications + +dist_noinst_DATA += \ + $(src_gtk3_ui_applications_in_files) + +# sync-ui: default GUI, could be plain GTK or Moblin UX +# sync-ui-gtk: GTK GUI +# sync-ui-moblin: Moblin UX +# +# The later two are built when --enable-gui=all was used. +EXTRA_PROGRAMS += \ + src/gtk3-ui/sync-ui \ + src/gtk3-ui/sync-ui-gtk \ + src/gtk3-ui/sync-ui-moblin +bin_PROGRAMS += @GUI_PROGRAMS@ + +src_gtk3_ui_sync_ui_SOURCES = \ + src/gtk3-ui/main.c \ + src/gtk3-ui/sync-ui.c \ + src/gtk3-ui/sync-ui.h \ + src/gtk3-ui/sync-ui-config.c \ + src/gtk3-ui/sync-ui-config.h \ + src/gtk3-ui/mux-frame.c \ + src/gtk3-ui/mux-frame.h \ + src/gtk3-ui/sync-config-widget.c \ + src/gtk3-ui/sync-config-widget.h + +src_gtk3_ui_sync_ui_LDADD = \ + $(GUI_LIBS) \ + $(DBUS_GLIB_LIBS) \ + $(top_builddir)/src/dbus/glib/libsyncevo-dbus.la +src_gtk3_ui_sync_ui_CFLAGS = \ + $(GUI_CFLAGS) \ + $(DBUS_GLIB_CFLAGS) \ + -DGLADEDIR=\""$(src_gtk3_ui_gladedir)"\" \ + -DTHEMEDIR=\""$(src_gtk3_ui_themercdir)"\" \ + -DLIBEXECDIR=\"@libexecdir@\" \ + -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\" \ + $(SYNCEVO_WFLAGS) +src_gtk3_ui_sync_ui_CPPFLAGS = \ + -I$(top_builddir) \ + -I$(top_srcdir) \ + -I$(top_builddir)/src/dbus/glib \ + -I$(top_srcdir)/src/dbus/glib \ + $(SYNTHESIS_CFLAGS) + +src_gtk3_ui_sync_ui_gtk_SOURCES = $(src_gtk3_ui_sync_ui_SOURCES) +src_gtk3_ui_sync_ui_gtk_LDADD = $(src_gtk3_ui_sync_ui_LDADD) +src_gtk3_ui_sync_ui_gtk_CFLAGS = $(src_gtk3_ui_sync_ui_CFLAGS) +src_gtk3_ui_sync_ui_gtk_CPPFLAGS = $(src_gtk3_ui_sync_ui_CPPFLAGS) + +src_gtk3_ui_sync_ui_moblin_SOURCES = $(src_gtk3_ui_sync_ui_SOURCES) +src_gtk3_ui_sync_ui_moblin_LDADD = $(src_gtk3_ui_sync_ui_LDADD) +src_gtk3_ui_sync_ui_moblin_CFLAGS = $(src_gtk3_ui_sync_ui_CFLAGS) +src_gtk3_ui_sync_ui_moblin_CPPFLAGS = $(src_gtk3_ui_sync_ui_CPPFLAGS) -DUSE_MOBLIN_UX + +CLEANFILES += \ + src/gtk3-ui/sync-moblin.desktop \ + $(src_gtk3_ui_applications_generated) diff --git a/src/gtk3-ui/main.c b/src/gtk3-ui/main.c new file mode 100644 index 00000000..a6a27a43 --- /dev/null +++ b/src/gtk3-ui/main.c @@ -0,0 +1,179 @@ +/* + * 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 + */ + +#include <gtk/gtk.h> +#include <glib/gi18n.h> + +#include "config.h" +#include "sync-ui.h" + +static char *settings_id = NULL; + +static GOptionEntry entries[] = +{ + { "show-settings", 0, 0, G_OPTION_ARG_STRING, &settings_id, "Open sync settings for given sync url or configuration name", "url or config name" }, + { NULL } +}; + + +static void +set_app_name_and_icon () +{ + /* TRANSLATORS: this is the application name that may be used by e.g. + the windowmanager */ + g_set_application_name (_("Sync")); + gtk_window_set_default_icon_name ("sync"); +} + +static void +init (int argc, char *argv[]) +{ + GError *error = NULL; + GOptionContext *context; + + gtk_init (&argc, &argv); + bindtextdomain (GETTEXT_PACKAGE, SYNCEVOLUTION_LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + context = g_option_context_new ("- synchronise PIM data with Syncevolution"); + g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE); + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + if (!g_option_context_parse (context, &argc, &argv, &error)) { + g_warning ("option parsing failed: %s\n", error->message); + } +} + + +#ifdef ENABLE_UNIQUE +#include <unique/unique.h> + +enum +{ + COMMAND_0, + + COMMAND_SHOW_CONFIGURATION + /* no sync-ui specific commands */ +}; + +static UniqueResponse +message_received_cb (UniqueApp *app, + gint command, + UniqueMessageData *message, + guint time_, + app_data *data) +{ + char *arg; + GtkWindow *main_win; + + main_win = sync_ui_get_main_window (data); + switch (command) { + case UNIQUE_ACTIVATE: + if (GTK_IS_WINDOW (main_win)) { + /* move the main window to the screen that sent us the command */ + gtk_window_set_screen (GTK_WINDOW (main_win), + unique_message_data_get_screen (message)); + gtk_window_present (GTK_WINDOW (main_win)); + } + break; + case COMMAND_SHOW_CONFIGURATION: + arg = unique_message_data_get_text (message); + if (GTK_IS_WINDOW (main_win) && arg) { + /* move the main window to the screen that sent us the command */ + gtk_window_set_screen (GTK_WINDOW (main_win), + unique_message_data_get_screen (message)); + sync_ui_show_settings (data, arg); + } + break; + default: + break; + } + + return UNIQUE_RESPONSE_OK; +} + +int +main (int argc, char *argv[]) +{ + UniqueApp *app; + + init (argc, argv); + + app = unique_app_new_with_commands ("org.Moblin.Sync", NULL, + "show-configuration", COMMAND_SHOW_CONFIGURATION, + NULL); + + if (unique_app_is_running (app)) { + UniqueMessageData *message = NULL; + UniqueCommand command = UNIQUE_ACTIVATE; + + if (settings_id) { + command = COMMAND_SHOW_CONFIGURATION; + message = unique_message_data_new (); + unique_message_data_set_text (message, settings_id, -1); + } + unique_app_send_message (app, command, message); + unique_message_data_free (message); + } else { + app_data *data; + + set_app_name_and_icon (); + + data = sync_ui_create (); + if (data) { + /* UniqueApp watches the main window so it can terminate + * the startup notification sequence for us */ + unique_app_watch_window (app, sync_ui_get_main_window (data)); + + /* handle notifications from new app launches */ + g_signal_connect (app, "message-received", + G_CALLBACK (message_received_cb), data); + if (settings_id) { + sync_ui_show_settings (data, settings_id); + } + gtk_main (); + } + } + + g_object_unref (app); + return 0; +} + +#else + +int +main (int argc, char *argv[]) +{ + app_data *data; + + init (argc, argv); + + set_app_name_and_icon (); + data = sync_ui_create (); + + if (settings_id) { + sync_ui_show_settings (data, settings_id); + } + + gtk_main (); + return 0; +} + +#endif /* ENABLE_UNIQUE */ + diff --git a/src/gtk3-ui/mux-frame.c b/src/gtk3-ui/mux-frame.c new file mode 100644 index 00000000..a209144a --- /dev/null +++ b/src/gtk3-ui/mux-frame.c @@ -0,0 +1,372 @@ +/* + * 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 + */ + +#include "mux-frame.h" +#include <math.h> + +static GdkColor mux_frame_default_border_color = { 0, 0xdddd, 0xe2e2, 0xe5e5 }; +static GdkColor mux_frame_default_bullet_color = { 0, 0xaaaa, 0xaaaa, 0xaaaa }; +static gfloat mux_frame_bullet_size_factor = 1.3; +#define MUX_FRAME_BULLET_PADDING 10 + +static void mux_frame_buildable_init (GtkBuildableIface *iface); +static void mux_frame_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *type); + +G_DEFINE_TYPE_WITH_CODE (MuxFrame, mux_frame, GTK_TYPE_FRAME, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, mux_frame_buildable_init)) + +static void +mux_frame_dispose (GObject *object) +{ + G_OBJECT_CLASS (mux_frame_parent_class)->dispose (object); +} + +static void +mux_frame_finalize (GObject *object) +{ + G_OBJECT_CLASS (mux_frame_parent_class)->finalize (object); +} + +static void +label_changed_cb (MuxFrame *frame) +{ + char *font = NULL; + GtkFrame *gtk_frame = GTK_FRAME (frame); + GtkWidget *label = gtk_frame_get_label_widget (GTK_FRAME (frame)); + + if (!label) + return; + + /* ensure font is correct */ + gtk_widget_style_get (GTK_WIDGET (frame), + "title-font", &font, + NULL); + if (font) { + PangoFontDescription *desc; + desc = pango_font_description_from_string (font); + gtk_widget_modify_font (label, desc); + pango_font_description_free (desc); + g_free (font); + } + + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 1.0); +} + +static void +mux_frame_update_style (MuxFrame *frame) +{ + GdkColor *border_color, *bullet_color; + char *font = NULL; + + gtk_widget_style_get (GTK_WIDGET (frame), + "border-color", &border_color, + "bullet-color", &bullet_color, + NULL); + + if (border_color) { + frame->border_color = *border_color; + gdk_color_free (border_color); + } else { + frame->border_color = mux_frame_default_border_color; + } + if (bullet_color) { + frame->bullet_color = *bullet_color; + gdk_color_free (bullet_color); + } else { + frame->bullet_color = mux_frame_default_bullet_color; + } + + label_changed_cb (frame); +} + +static void +rounded_rectangle (cairo_t * cr, + double x, double y, double w, double h, + guint radius) +{ + if (radius > w / 2) + radius = w / 2; + if (radius > h / 2) + radius = h / 2; + + cairo_move_to (cr, x + radius, y); + cairo_arc (cr, x + w - radius, y + radius, radius, M_PI * 1.5, M_PI * 2); + cairo_arc (cr, x + w - radius, y + h - radius, radius, 0, M_PI * 0.5); + cairo_arc (cr, x + radius, y + h - radius, radius, M_PI * 0.5, M_PI); + cairo_arc (cr, x + radius, y + radius, radius, M_PI, M_PI * 1.5); +} + +static void +mux_frame_paint (GtkWidget *widget, GdkRectangle *area) +{ + MuxFrame *frame = MUX_FRAME (widget); + cairo_t *cairo; + GtkStyle *style; + guint width; + + g_return_if_fail (widget != NULL); + g_return_if_fail (MUX_IS_FRAME (widget)); + g_return_if_fail (area != NULL); + + style = gtk_widget_get_style (widget); + cairo = gdk_cairo_create (widget->window); + width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + /* draw border */ + if (width != 0) { + gdk_cairo_set_source_color (cairo, &frame->border_color); + + rounded_rectangle (cairo, + widget->allocation.x, + widget->allocation.y, + widget->allocation.width, + widget->allocation.height, + width); + cairo_clip (cairo); + + gdk_cairo_rectangle (cairo, area); + cairo_clip (cairo); + + cairo_paint (cairo); + } + + /* draw background */ + gdk_cairo_set_source_color (cairo, &style->bg[GTK_WIDGET_STATE(widget)]); + rounded_rectangle (cairo, + widget->allocation.x + width, + widget->allocation.y + width, + widget->allocation.width - 2 * width, + widget->allocation.height- 2 * width, + width); + cairo_clip (cairo); + + gdk_cairo_rectangle (cairo, area); + cairo_clip (cairo); + + cairo_paint (cairo); + + /* draw bullet before title */ + if (gtk_frame_get_label_widget (GTK_FRAME (frame))) { + gdk_cairo_set_source_color (cairo, &frame->bullet_color); + + rounded_rectangle (cairo, + frame->bullet_allocation.x, + frame->bullet_allocation.y, + frame->bullet_allocation.height, + frame->bullet_allocation.height, + 4); + cairo_clip (cairo); + + gdk_cairo_rectangle (cairo, area); + cairo_clip (cairo); + + cairo_paint (cairo); + + } + cairo_destroy (cairo); +} + +static gboolean +mux_frame_expose(GtkWidget *widget, + GdkEventExpose *event) +{ + GtkWidgetClass *grand_parent; + if (gtk_widget_is_drawable (widget)) { + mux_frame_paint (widget, &event->area); + + grand_parent = GTK_WIDGET_CLASS (g_type_class_peek_parent (mux_frame_parent_class)); + grand_parent->expose_event (widget, event); + } + return FALSE; +} + +static void +mux_frame_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + GtkWidget *label = gtk_frame_get_label_widget (GTK_FRAME (widget)); + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + GtkRequisition child_req; + GtkRequisition title_req; + + child_req.width = child_req.height = 0; + if (child) + gtk_widget_size_request (child, &child_req); + + title_req.width = title_req.height = 0; + if (label) { + gtk_widget_size_request (label, &title_req); + /* add room for bullet */ + title_req.height = title_req.height * mux_frame_bullet_size_factor + + 2 * MUX_FRAME_BULLET_PADDING; + title_req.width += title_req.height * mux_frame_bullet_size_factor + + 2 * MUX_FRAME_BULLET_PADDING; + } + + requisition->width = MAX (child_req.width, title_req.width) + + 2 * (GTK_CONTAINER (widget)->border_width + + GTK_WIDGET (widget)->style->xthickness); + requisition->height = title_req.height + child_req.height + + 2 * (GTK_CONTAINER (widget)->border_width + + GTK_WIDGET (widget)->style->ythickness); +} + + + +static void +mux_frame_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkBin *bin = GTK_BIN (widget); + MuxFrame *mux_frame = MUX_FRAME (widget); + GtkFrame *frame = GTK_FRAME (widget); + GtkAllocation child_allocation; + int xmargin, ymargin, title_height; + + widget->allocation = *allocation; + xmargin = GTK_CONTAINER (widget)->border_width + + widget->style->xthickness; + ymargin = GTK_CONTAINER (widget)->border_width + + widget->style->ythickness; + + title_height = 0; + if (frame->label_widget) { + GtkAllocation title_allocation; + GtkRequisition title_req; + gtk_widget_get_child_requisition (frame->label_widget, &title_req); + + /* the bullet is bigger than the text */ + title_height = title_req.height * mux_frame_bullet_size_factor + + 2 * MUX_FRAME_BULLET_PADDING; + + /* x allocation starts after bullet */ + title_allocation.x = allocation->x + xmargin + title_height; + title_allocation.y = allocation->y + ymargin + MUX_FRAME_BULLET_PADDING; + title_allocation.width = MIN (title_req.width, + allocation->width - 2 * xmargin - title_height); + title_allocation.height = title_height - 2 * MUX_FRAME_BULLET_PADDING; + gtk_widget_size_allocate (frame->label_widget, &title_allocation); + + mux_frame->bullet_allocation.x = allocation->x + xmargin + MUX_FRAME_BULLET_PADDING; + mux_frame->bullet_allocation.y = allocation->y + ymargin + MUX_FRAME_BULLET_PADDING; + mux_frame->bullet_allocation.width = title_allocation.height; + mux_frame->bullet_allocation.height = title_allocation.height; + } + + child_allocation.x = allocation->x + xmargin; + child_allocation.y = allocation->y + ymargin + title_height; + child_allocation.width = allocation->width - 2 * xmargin; + child_allocation.height = allocation->height - 2 * ymargin - title_height; + + if (GTK_WIDGET_MAPPED (widget) && + (child_allocation.x != frame->child_allocation.x || + child_allocation.y != frame->child_allocation.y || + child_allocation.width != frame->child_allocation.width || + child_allocation.height != frame->child_allocation.height)) { + gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE); + } + + if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) { + gtk_widget_size_allocate (bin->child, &child_allocation); + } + + frame->child_allocation = child_allocation; +} + +static void mux_frame_style_set (GtkWidget *widget, + GtkStyle *previous) +{ + MuxFrame *frame = MUX_FRAME (widget); + + mux_frame_update_style (frame); + + GTK_WIDGET_CLASS (mux_frame_parent_class)->style_set (widget, previous); +} + +static void +mux_frame_class_init (MuxFrameClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GParamSpec *pspec; + + object_class->dispose = mux_frame_dispose; + object_class->finalize = mux_frame_finalize; + + widget_class->expose_event = mux_frame_expose; + widget_class->size_request = mux_frame_size_request; + widget_class->size_allocate = mux_frame_size_allocate; + widget_class->style_set = mux_frame_style_set; + + pspec = g_param_spec_boxed ("border-color", + "Border color", + "Color of the outside border", + GDK_TYPE_COLOR, + G_PARAM_READABLE); + gtk_widget_class_install_style_property(widget_class, pspec); + pspec = g_param_spec_boxed ("bullet-color", + "Bullet color", + "Color of the rounded rectangle before a title", + GDK_TYPE_COLOR, + G_PARAM_READABLE); + gtk_widget_class_install_style_property(widget_class, pspec); + pspec = g_param_spec_string ("title-font", + "Title font", + "Pango font description string for title text", + "12", + G_PARAM_READWRITE); + gtk_widget_class_install_style_property(widget_class, pspec); +} + +static void +mux_frame_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *type) +{ + if (!type) + gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child)); + else + GTK_BUILDER_WARN_INVALID_CHILD_TYPE (MUX_FRAME (buildable), type); +} + +static void +mux_frame_buildable_init (GtkBuildableIface *iface) +{ + iface->add_child = mux_frame_buildable_add_child; +} + +static void +mux_frame_init (MuxFrame *self) +{ + g_signal_connect (self, "notify::label-widget", + G_CALLBACK (label_changed_cb), NULL); +} + +GtkWidget* +mux_frame_new (void) +{ + return g_object_new (MUX_TYPE_FRAME, + "border-width", 4, + NULL); +} + diff --git a/src/gtk3-ui/mux-frame.h b/src/gtk3-ui/mux-frame.h new file mode 100644 index 00000000..75accda6 --- /dev/null +++ b/src/gtk3-ui/mux-frame.h @@ -0,0 +1,64 @@ +/* + * 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 + */ + +#ifndef _MUX_FRAME +#define _MUX_FRAME + +#include <glib-object.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define MUX_TYPE_FRAME mux_frame_get_type() + +#define MUX_FRAME(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), MUX_TYPE_FRAME, MuxFrame)) + +#define MUX_FRAME_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), MUX_TYPE_FRAME, MuxFrameClass)) + +#define MUX_IS_FRAME(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MUX_TYPE_FRAME)) + +#define MUX_IS_FRAME_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), MUX_TYPE_FRAME)) + +#define MUX_FRAME_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), MUX_TYPE_FRAME, MuxFrameClass)) + +typedef struct { + GtkFrame parent; + + GtkAllocation bullet_allocation; + + GdkColor bullet_color; + GdkColor border_color; +} MuxFrame; + +typedef struct { + GtkFrameClass parent_class; +} MuxFrameClass; + +GType mux_frame_get_type (void); + +GtkWidget* mux_frame_new (void); + +G_END_DECLS + +#endif diff --git a/src/gtk3-ui/sync-config-widget.c b/src/gtk3-ui/sync-config-widget.c new file mode 100644 index 00000000..76702eb0 --- /dev/null +++ b/src/gtk3-ui/sync-config-widget.c @@ -0,0 +1,2195 @@ +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <glib/gi18n.h> +#include <dbus/dbus-glib.h> + +#ifdef USE_MOBLIN_UX +#ifdef MX_GTK_0_99_1 +#include <mx-gtk/mx-gtk.h> +#else +#include <mx/mx-gtk.h> +#endif +#endif + +#include "sync-ui.h" +#include "sync-config-widget.h" + +#define INDICATOR_SIZE 16 +#define CHILD_PADDING 3 + +G_DEFINE_TYPE (SyncConfigWidget, sync_config_widget, GTK_TYPE_CONTAINER) + + +typedef struct source_widgets { + char *name; + + GtkWidget *label; + GtkWidget *entry; + GtkWidget *check; + + GtkWidget *source_toggle_label; + + guint count; +} source_widgets; + +enum +{ + PROP_0, + + PROP_SERVER, + PROP_NAME, + PROP_CONFIG, + PROP_CURRENT, + PROP_HAS_TEMPLATE, + PROP_CONFIGURED, + PROP_CURRENT_SERVICE_NAME, + PROP_EXPANDED, +}; + +enum { + SIGNAL_CHANGED, + LAST_SIGNAL +}; +static guint32 signals[LAST_SIGNAL] = {0, }; + +typedef struct save_config_data { + SyncConfigWidget *widget; + gboolean delete; + gboolean temporary; + source_widgets *widgets; + char *basename; +} save_config_data; + +static void start_session_for_config_write_cb (SyncevoServer *server, char *path, GError *error, save_config_data *data); +static void sync_config_widget_update_label (SyncConfigWidget *self); +static void sync_config_widget_set_name (SyncConfigWidget *self, const char *name); + +static void +remove_child (GtkWidget *widget, GtkContainer *container) +{ + gtk_container_remove (container, widget); +} + +const char* +get_service_description (const char *service) +{ + if (!service) + return NULL; + + /* TRANSLATORS: Descriptions for specific services, shown in service + * configuration form */ + if (strcmp (service, "ScheduleWorld") == 0) { + return _("ScheduleWorld enables you to keep your contacts, events, " + "tasks, and notes in sync."); + }else if (strcmp (service, "Google") == 0) { + return _("Google Sync can back up and synchronize your contacts " + "with your Gmail contacts."); + }else if (strcmp (service, "Funambol") == 0) { + /* TRANSLATORS: Please include the word "demo" (or the equivalent in + your language): Funambol is going to be a 90 day demo service + in the future */ + return _("Back up your contacts and calendar. Sync with a single " + "click, anytime, anywhere (DEMO)."); + }else if (strcmp (service, "Mobical") == 0) { + return _("Mobical Backup and Restore service allows you to securely " + "back up your personal mobile data for free."); + }else if (strcmp (service, "ZYB") == 0) { + return _("ZYB is a simple way for people to store and share mobile " + "information online."); + }else if (strcmp (service, "Memotoo") == 0) { + return _("Memotoo lets you access your personal data from any " + "computer connected to the Internet."); + } + + return NULL; +} + +static void +update_source_uri (char *name, + GHashTable *source_configuration, + SyncConfigWidget *self) +{ + const char *uri; + source_widgets *widgets; + + widgets = (source_widgets*)g_hash_table_lookup (self->sources, name); + if (!widgets) { + return; + } + + uri = gtk_entry_get_text (GTK_ENTRY (widgets->entry)); + g_hash_table_insert (source_configuration, g_strdup ("uri"), g_strdup (uri)); +} + +static source_widgets * +source_widgets_ref (source_widgets *widgets) +{ + if (widgets) { + widgets->count++; + } + return widgets; +} + +static void +source_widgets_unref (source_widgets *widgets) +{ + if (widgets) { + widgets->count--; + if (widgets->count == 0) + g_slice_free (source_widgets, widgets); + } +} + +static void +check_source_cb (SyncevoSession *session, + GError *error, + source_widgets *widgets) +{ + gboolean show = TRUE; + + if (error) { + if(error->code == DBUS_GERROR_REMOTE_EXCEPTION && + dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_SOURCE_UNUSABLE)) { + show = FALSE; + } else { + g_warning ("CheckSource failed: %s", error->message); + /* non-fatal, ignore in UI */ + } + g_error_free (error); + } + + if (widgets->count > 1) { + if (show) { + /* NOTE: with the new two sources per row layout not showing things + * may look weird in some cases... the layout should really only be + * done at this point */ + gtk_widget_show (widgets->source_toggle_label); + gtk_widget_show (widgets->label); + gtk_widget_show (widgets->entry); + gtk_widget_show (widgets->check); + } else { + /* next save should disable this source */ + toggle_set_active (widgets->check, FALSE); + } + } + source_widgets_unref (widgets); + g_object_unref (session); +} + +static void +set_config_cb (SyncevoSession *session, + GError *error, + save_config_data *data) +{ + if (error) { + g_warning ("Error in Session.SetConfig: %s", error->message); + g_error_free (error); + g_object_unref (session); + show_error_dialog (GTK_WIDGET (data->widget), + _("Sorry, failed to save the configuration")); + return; + } + + if (data->temporary) { + syncevo_session_check_source (session, + data->widgets->name, + (SyncevoSessionGenericCb)check_source_cb, + data->widgets); + } else { + data->widget->configured = TRUE; + g_signal_emit (data->widget, signals[SIGNAL_CHANGED], 0); + g_object_unref (session); + } + +} + +static void +get_config_for_overwrite_prevention_cb (SyncevoSession *session, + SyncevoConfig *config, + GError *error, + save_config_data *data) +{ + static int index = 0; + char *name; + + if (error) { + index = 0; + if (error->code == DBUS_GERROR_REMOTE_EXCEPTION && + dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_NO_SUCH_CONFIG)) { + /* Config does not exist (as expected), we can now save */ + syncevo_session_set_config (session, + data->temporary, + data->temporary, + data->widget->config, + (SyncevoSessionGenericCb)set_config_cb, + data); + return; + } + g_warning ("Unexpected error in Session.GetConfig: %s", error->message); + g_error_free (error); + g_object_unref (session); + return; + } + + /* Config exists when we are trying to create a new config... + * Need to start a new session with another name */ + g_object_unref (session); + name = g_strdup_printf ("%s__%d", data->basename, ++index); + sync_config_widget_set_name (data->widget, name); + g_free (name); + + syncevo_server_start_no_sync_session (data->widget->server, + data->widget->config_name, + (SyncevoServerStartSessionCb)start_session_for_config_write_cb, + data); +} + +static void +save_config (save_config_data *data, + SyncevoSession *session) +{ + SyncConfigWidget *w = data->widget; + + if (data->delete) { + syncevo_config_free (w->config); + w->config = g_hash_table_new (g_str_hash, g_str_equal); + } + + /* if this is a client peer (a device) and not configured, we + * need to test that we aren't overwriting existing + * configs */ + /* TODO: This might be a good thing to do for any configurations.*/ + if (peer_is_client (w->config) && + !w->configured && !data->temporary) { + + syncevo_session_get_config (session, + FALSE, + (SyncevoSessionGetConfigCb)get_config_for_overwrite_prevention_cb, + data); + } else { + syncevo_session_set_config (session, + data->temporary, + data->temporary, + data->widget->config, + (SyncevoSessionGenericCb)set_config_cb, + data); + } +} + +static void +status_changed_for_config_write_cb (SyncevoSession *session, + SyncevoSessionStatus status, + guint error_code, + SyncevoSourceStatuses *source_statuses, + save_config_data *data) +{ + if (status == SYNCEVO_STATUS_IDLE) { + save_config (data, session); + } +} + +static void +get_status_for_config_write_cb (SyncevoSession *session, + SyncevoSessionStatus status, + guint error_code, + SyncevoSourceStatuses *source_statuses, + GError *error, + save_config_data *data) +{ + if (error) { + g_warning ("Error in Session.GetStatus: %s", error->message); + g_error_free (error); + g_object_unref (session); + /* TODO show in UI: save failed in service list */ + return; + } + + syncevo_source_statuses_free (source_statuses); + + if (status == SYNCEVO_STATUS_IDLE) { + save_config (data, session); + } +} + + +static void +start_session_for_config_write_cb (SyncevoServer *server, + char *path, + GError *error, + save_config_data *data) +{ + SyncevoSession *session; + + if (error) { + g_warning ("Error in Server.StartSession: %s", error->message); + g_error_free (error); + /* TODO show in UI: save failed in service list */ + return; + } + + session = syncevo_session_new (path); + + /* we want to know about status changes to our session */ + g_signal_connect (session, "status-changed", + G_CALLBACK (status_changed_for_config_write_cb), data); + syncevo_session_get_status (session, + (SyncevoSessionGetStatusCb)get_status_for_config_write_cb, + data); +} + +static void +stop_clicked_cb (GtkButton *btn, SyncConfigWidget *self) +{ + save_config_data *data; + + if (!self->config) { + return; + } + + syncevo_config_set_value (self->config, NULL, "defaultPeer", ""); + sync_config_widget_set_current (self, FALSE); + + data = g_slice_new (save_config_data); + data->widget = self; + data->delete = FALSE; + data->temporary = FALSE; + syncevo_server_start_no_sync_session (self->server, + self->config_name, + (SyncevoServerStartSessionCb)start_session_for_config_write_cb, + data); +} + +static void +use_clicked_cb (GtkButton *btn, SyncConfigWidget *self) +{ + save_config_data *data; + const char *username, *password, *sync_url, *pretty_name; + char *real_url, *device; + gboolean send, receive; + SyncevoSyncMode mode; + + if (!self->config) { + return; + } + + if (!self->config_name || strlen (self->config_name) == 0) { + g_free (self->config_name); + self->config_name = g_strdup (gtk_entry_get_text (GTK_ENTRY (self->entry))); + } + + if (self->mode_changed) { + GHashTableIter iter; + source_widgets *widgets; + char *name; + gboolean client = peer_is_client (self->config); + + send = toggle_get_active (self->send_check); + receive = toggle_get_active (self->receive_check); + + if (send && receive) { + mode = SYNCEVO_SYNC_TWO_WAY; + } else if (send) { + mode = client ? + SYNCEVO_SYNC_ONE_WAY_FROM_SERVER : + SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT; + } else if (receive) { + mode = client ? + SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT : + SYNCEVO_SYNC_ONE_WAY_FROM_SERVER; + } else { + mode = SYNCEVO_SYNC_NONE; + } + + g_hash_table_iter_init (&iter, self->sources); + while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)&widgets)) { + const char *mode_str; + gboolean active; + + active = toggle_get_active (widgets->check) && + GTK_WIDGET_SENSITIVE (widgets->check); + if (active) { + mode_str = syncevo_sync_mode_to_string (mode); + } else { + mode_str = "none"; + } + syncevo_config_set_value (self->config, name, "sync", mode_str); + } + } + + username = gtk_entry_get_text (GTK_ENTRY (self->username_entry)); + syncevo_config_set_value (self->config, NULL, "username", username); + + sync_url = gtk_entry_get_text (GTK_ENTRY (self->baseurl_entry)); + /* make a wild guess if no scheme in url */ + if (strstr (sync_url, "://") == NULL) { + real_url = g_strdup_printf ("http://%s", sync_url); + } else { + real_url = g_strdup (sync_url); + } + syncevo_config_set_value (self->config, NULL, "syncURL", real_url); + + password = gtk_entry_get_text (GTK_ENTRY (self->password_entry)); + syncevo_config_set_value (self->config, NULL, "password", password); + + syncevo_config_get_value (self->config, NULL, "deviceName", &device); + if (!device || strlen (device) == 0) { + if (!self->config_name || strlen (self->config_name) == 0 || + !sync_url || strlen (sync_url) == 0) { + show_error_dialog (GTK_WIDGET (self), + _("Service must have a name and server URL")); + return; + } + } + + syncevo_config_foreach_source (self->config, + (ConfigFunc)update_source_uri, + self); + + pretty_name = gtk_entry_get_text (GTK_ENTRY (self->entry)); + syncevo_config_set_value (self->config, NULL, "PeerName", pretty_name); + syncevo_config_get_value (self->config, NULL, "PeerName", &self->pretty_name); + syncevo_config_set_value (self->config, NULL, "defaultPeer", self->config_name); + sync_config_widget_set_current (self, TRUE); + + data = g_slice_new (save_config_data); + data->widget = self; + data->delete = FALSE; + data->temporary = FALSE; + data->basename = g_strdup (self->config_name); + syncevo_server_start_no_sync_session (self->server, + self->config_name, + (SyncevoServerStartSessionCb)start_session_for_config_write_cb, + data); + + g_free (real_url); +} + +static void +reset_delete_clicked_cb (GtkButton *btn, SyncConfigWidget *self) +{ + char *msg, *yes, *no; + save_config_data *data; + + if (!self->config) { + return; + } + + if (self->has_template) { + /*TRANSLATORS: warning dialog text for resetting pre-defined + services */ + msg = g_strdup_printf + (_("Do you want to reset the settings for %s? " + "This will not remove any synced information on either end."), + self->pretty_name); + /*TRANSLATORS: buttons in reset-service warning dialog */ + yes = _("Yes, reset"); + no = _("No, keep settings"); + } else { + /*TRANSLATORS: warning dialog text for deleting user-defined + services */ + msg = g_strdup_printf + (_("Do you want to delete the settings for %s? " + "This will not remove any synced information on either " + "end but it will remove these settings."), + self->pretty_name); + /*TRANSLATORS: buttons in delete-service warning dialog */ + yes = _("Yes, delete"); + no = _("No, keep settings"); + } + + /*TRANSLATORS: decline button in "Reset/delete service" warning dialogs */ + if (!show_confirmation (GTK_WIDGET (self), msg, yes, no)) { + g_free (msg); + return; + } + g_free (msg); + + if (self->current) { + sync_config_widget_set_current (self, FALSE); + } + + data = g_slice_new (save_config_data); + data->widget = self; + data->delete = TRUE; + data->temporary = FALSE; + + syncevo_server_start_no_sync_session (self->server, + self->config_name, + (SyncevoServerStartSessionCb)start_session_for_config_write_cb, + data); +} + +static void update_buttons (SyncConfigWidget *self) +{ + if (self->has_template) { + /* TRANSLATORS: button labels in service configuration form */ + gtk_button_set_label (GTK_BUTTON (self->reset_delete_button), + _("Reset settings")); + } else { + gtk_button_set_label (GTK_BUTTON (self->reset_delete_button), + _("Delete settings")); + } + if (self->configured) { + gtk_widget_show (GTK_WIDGET (self->reset_delete_button)); + } else { + gtk_widget_hide (GTK_WIDGET (self->reset_delete_button)); + } + + if (self->current || !self->current_service_name) { + gtk_button_set_label (GTK_BUTTON (self->use_button), + _("Save and use")); + } else { + gtk_button_set_label (GTK_BUTTON (self->use_button), + _("Save and replace\ncurrent service")); + } + + + + if (self->current && self->config) { + if (peer_is_client (self->config)) { + gtk_button_set_label (GTK_BUTTON (self->stop_button), + _("Stop using device")); + } else { + gtk_button_set_label (GTK_BUTTON (self->stop_button), + _("Stop using service")); + } + gtk_widget_show (self->stop_button); + } else { + gtk_widget_hide (self->stop_button); + } +} + +static void +mode_widget_notify_active_cb (GtkWidget *widget, + GParamSpec *pspec, + SyncConfigWidget *self) +{ + self->mode_changed = TRUE; +} + +static void +source_entry_notify_text_cb (GObject *gobject, + GParamSpec *pspec, + source_widgets *widgets) +{ + gboolean new_editable, old_editable; + const char *text; + + text = gtk_entry_get_text (GTK_ENTRY (widgets->entry)); + new_editable = (strlen (text) > 0); + old_editable = GTK_WIDGET_SENSITIVE (widgets->check); + if (new_editable != old_editable) { + gtk_widget_set_sensitive (widgets->check, new_editable); + toggle_set_active (widgets->check, new_editable); + } +} + +static GtkWidget* +add_toggle_widget (SyncConfigWidget *self, + const char *title, + gboolean active, + guint row, guint col) +{ + GtkWidget *toggle; + int padding; + + padding = (col == 1) ? 0 : 32; + +#ifdef USE_MOBLIN_UX + GtkWidget *label; + + col = col * 2; + label = gtk_label_new (title); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_widget_set_size_request (label, 260, -1); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (self->mode_table), label, + col, col + 1, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0); + toggle = mx_gtk_light_switch_new (); + g_signal_connect_swapped (toggle, "hide", + G_CALLBACK (gtk_widget_hide), label); + g_signal_connect_swapped (toggle, "show", + G_CALLBACK (gtk_widget_show), label); + toggle_set_active (toggle, active); + g_signal_connect (toggle, "switch-flipped", + G_CALLBACK (mode_widget_notify_active_cb), self); +#else + toggle = gtk_check_button_new_with_label (title); + gtk_widget_set_size_request (toggle, 260, -1); + toggle_set_active (toggle, active); + g_signal_connect (toggle, "notify::active", + G_CALLBACK (mode_widget_notify_active_cb), self); +#endif + + gtk_table_attach (GTK_TABLE (self->mode_table), toggle, + col + 1, col + 2, row, row + 1, + GTK_FILL, GTK_FILL, padding, 0); + + return toggle; +} + + +/* check if config includes a virtual source that covers the given + * source */ +static gboolean +virtual_source_exists (SyncevoConfig *config, const char *name) +{ + GHashTableIter iter; + const char *source_string; + GHashTable *source_config; + + g_hash_table_iter_init (&iter, config); + while (g_hash_table_iter_next (&iter, + (gpointer)&source_string, + (gpointer)&source_config)) { + char **strs; + + if (g_str_has_prefix (source_string, "source/")) { + const char *uri, *type; + type = g_hash_table_lookup (source_config, "backend"); + uri = g_hash_table_lookup (source_config, "uri"); + + if (!uri || !type || !g_str_has_prefix (type, "virtual:")) { + /* this source is not defined, or not virtual */ + continue; + } + + strs = g_strsplit (source_string + 7, "+", 0); + if (g_strv_length (strs) > 1) { + int i; + + for (i = 0; strs[i]; i++) { + if (g_strcmp0 (name, strs[i]) == 0) { + g_strfreev (strs); + return TRUE; + } + } + } + g_strfreev (strs); + } + } + + return FALSE; +} + +static void +init_source (char *name, + GHashTable *source_configuration, + SyncConfigWidget *self) +{ + char *str, *pretty_name; + const char *uri, *type; + guint rows; + guint row; + static guint col = 0; + source_widgets *widgets; + SyncevoSyncMode mode; + save_config_data *data; + + type = g_hash_table_lookup (source_configuration, "backend"); + uri = g_hash_table_lookup (source_configuration, "uri"); + if (!type || strlen (type) == 0) { + return; + } + + if (g_str_has_prefix (type, "virtual:") && !uri) { + /* undefined virtual source */ + return; + } + + if (virtual_source_exists (self->config, name)) { + return; + } + + g_object_get (self->mode_table, + "n-rows", &rows, + NULL); + + if (!self->no_source_toggles && col == 0) { + col = 1; + row = rows - 1; + } else { + col = 0; + row = rows; + } + self->no_source_toggles = FALSE; + + widgets = g_slice_new0 (source_widgets); + widgets->name = name; + widgets->count = 1; + g_hash_table_insert (self->sources, name, widgets); + + widgets->source_toggle_label = self->source_toggle_label; + + pretty_name = get_pretty_source_name (name); + mode = syncevo_sync_mode_from_string + (g_hash_table_lookup (source_configuration, "sync")); + + widgets->check = add_toggle_widget (self, + pretty_name, + (mode > SYNCEVO_SYNC_NONE), + row, col); + + /* TRANSLATORS: label for an entry in service configuration form. + * Placeholder is a source name. + * Example: "Appointments URI" */ + str = g_strdup_printf (_("%s URI"), pretty_name); + widgets->label = gtk_label_new (str); + g_free (str); + g_free (pretty_name); + + g_object_get (self->server_settings_table, + "n-rows", &row, + NULL); + + gtk_misc_set_alignment (GTK_MISC (widgets->label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (self->server_settings_table), widgets->label, + 0, 1, row, row + 1, GTK_FILL, GTK_EXPAND, 0, 0); + + widgets->entry = gtk_entry_new (); + gtk_entry_set_max_length (GTK_ENTRY (widgets->entry), 99); + gtk_entry_set_width_chars (GTK_ENTRY (widgets->entry), 80); + if (uri) { + gtk_entry_set_text (GTK_ENTRY (widgets->entry), uri); + } + gtk_table_attach_defaults (GTK_TABLE (self->server_settings_table), + widgets->entry, + 1, 2, row, row + 1); + g_signal_connect (widgets->entry, "notify::text", + G_CALLBACK (source_entry_notify_text_cb), widgets); + + gtk_widget_set_sensitive (widgets->check, + uri && strlen (uri) > 0); + + /* start a session so we save a temporary config so we can do + * CheckSource, and show the source-related widgets if the + * source is available */ + data = g_slice_new (save_config_data); + data->widget = self; + data->delete = FALSE; + data->temporary = TRUE; + data->widgets = source_widgets_ref (widgets); + + syncevo_server_start_no_sync_session (self->server, + self->config_name, + (SyncevoServerStartSessionCb)start_session_for_config_write_cb, + data); +} + +static void +get_common_mode (char *name, + GHashTable *source_configuration, + SyncevoSyncMode *common_mode) +{ + SyncevoSyncMode mode; + char *mode_str, *type; + + type = g_hash_table_lookup (source_configuration, "backend"); + if (!type || strlen (type) == 0) { + return; + } + + mode_str = g_hash_table_lookup (source_configuration, "sync"); + mode = syncevo_sync_mode_from_string (mode_str); + + if (mode == SYNCEVO_SYNC_NONE) { + return; + } + + if (*common_mode == SYNCEVO_SYNC_NONE) { + *common_mode = mode; + } else if (mode != *common_mode) { + *common_mode = SYNCEVO_SYNC_UNKNOWN; + } +} + +void +sync_config_widget_expand_id (SyncConfigWidget *self, + const char *id) +{ + if (id && self->config) { + char *sync_url; + + if (syncevo_config_get_value (self->config, NULL, + "syncURL", &sync_url) && + strncmp (sync_url, id, strlen (id)) == 0) { + + sync_config_widget_set_expanded (self, TRUE); + } else if (self->config_name && + g_strcasecmp (self->config_name, id) == 0) { + + sync_config_widget_set_expanded (self, TRUE); + } + } +} + +static void +sync_config_widget_update_expander (SyncConfigWidget *self) +{ + char *username = ""; + char *password = ""; + char *sync_url = ""; + const char *descr; + char *str; + GtkWidget *label, *align; + SyncevoSyncMode mode = SYNCEVO_SYNC_NONE; + gboolean send, receive; + gboolean client; + + gtk_container_foreach (GTK_CONTAINER (self->server_settings_table), + (GtkCallback)remove_child, + self->server_settings_table); + gtk_table_resize (GTK_TABLE (self->server_settings_table), + 2, 1); + gtk_container_foreach (GTK_CONTAINER (self->mode_table), + (GtkCallback)remove_child, + self->mode_table); + gtk_table_resize (GTK_TABLE (self->mode_table), + 2, 1); + + client = peer_is_client (self->config); + if (client) { + if (!self->device_template_selected) { + gtk_widget_hide (self->settings_box); + gtk_widget_show (self->device_selector_box); + /* temporary solution for device template selection: + * show list of templates only */ + } else { + gtk_widget_show (self->settings_box); + gtk_widget_hide (self->device_selector_box); + gtk_widget_hide (self->userinfo_table); + gtk_widget_hide (self->fake_expander); + } + } else { + gtk_widget_show (self->settings_box); + gtk_widget_hide (self->device_selector_box); + gtk_widget_show (self->userinfo_table); + gtk_widget_show (self->fake_expander); + } + + syncevo_config_foreach_source (self->config, + (ConfigFunc)get_common_mode, + &mode); + switch (mode) { + case SYNCEVO_SYNC_TWO_WAY: + send = receive = TRUE; + break; + case SYNCEVO_SYNC_ONE_WAY_FROM_CLIENT: + if (client) { + send = FALSE; + receive = TRUE; + } else { + send = TRUE; + receive = FALSE; + } + break; + case SYNCEVO_SYNC_ONE_WAY_FROM_SERVER: + if (client) { + send = TRUE; + receive = FALSE; + } else { + send = FALSE; + receive = TRUE; + } + break; + default: + gtk_widget_show (self->complex_config_info_bar); + send = FALSE; + receive = FALSE; + } + self->mode_changed = FALSE; + + + if (self->pretty_name) { + gtk_entry_set_text (GTK_ENTRY (self->entry), self->pretty_name); + } + if (!self->config_name || strlen (self->config_name) == 0) { + gtk_expander_set_expanded (GTK_EXPANDER (self->expander), TRUE); + } + + descr = get_service_description (self->config_name); + if (descr) { + gtk_label_set_text (GTK_LABEL (self->description_label), + get_service_description (self->config_name)); + gtk_widget_show (self->description_label); + } else { + gtk_widget_hide (self->description_label); + } + + update_buttons (self); + + /* TRANSLATORS: toggles in service configuration form, placeholder is service + * or device name */ + str = g_strdup_printf (_("Send changes to %s"), self->pretty_name); + self->send_check = add_toggle_widget (self, str, send, 0, 0); + gtk_widget_show (self->send_check); + g_free (str); + + str = g_strdup_printf (_("Receive changes from %s"), self->pretty_name); + self->receive_check = add_toggle_widget (self, str, receive, 0, 1); + gtk_widget_show (self->receive_check); + g_free (str); + + align = gtk_alignment_new (0.0, 1.0, 0.0, 0.0); + gtk_alignment_set_padding (GTK_ALIGNMENT (align), 10, 0, 0, 0); + gtk_widget_show (align); + gtk_table_attach (GTK_TABLE (self->mode_table), align, + 0, 1, 1, 2, + GTK_FILL, GTK_FILL, 0, 0); + + self->source_toggle_label = gtk_label_new (""); + /* TRANSLATORS: Label for the source toggles in configuration form. + This is a verb, as in "Sync Calendar". */ + gtk_label_set_markup (GTK_LABEL (self->source_toggle_label), + _("<b>Sync</b>")); + gtk_widget_show (self->source_toggle_label); + gtk_container_add (GTK_CONTAINER (align), self->source_toggle_label); + + syncevo_config_get_value (self->config, NULL, "username", &username); + syncevo_config_get_value (self->config, NULL, "password", &password); + syncevo_config_get_value (self->config, NULL, "syncURL", &sync_url); + + if (username) { + gtk_entry_set_text (GTK_ENTRY (self->username_entry), username); + } + if (password) { + gtk_entry_set_text (GTK_ENTRY (self->password_entry), password); + } + + // TRANSLATORS: label of a entry in service configuration + label = gtk_label_new (_("Server address")); + gtk_misc_set_alignment (GTK_MISC (label), 9.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (self->server_settings_table), label, + 0, 1, 0, 1, GTK_FILL, GTK_EXPAND, 0, 0); + + self->baseurl_entry = gtk_entry_new (); + gtk_entry_set_max_length (GTK_ENTRY (self->baseurl_entry), 99); + gtk_entry_set_width_chars (GTK_ENTRY (self->baseurl_entry), 80); + if (sync_url) { + gtk_entry_set_text (GTK_ENTRY (self->baseurl_entry), sync_url); + } + gtk_widget_show (self->baseurl_entry); + + gtk_table_attach_defaults (GTK_TABLE (self->server_settings_table), + self->baseurl_entry, + 1, 2, 0, 1); + + /* update source widgets */ + if (self->sources) { + g_hash_table_destroy (self->sources); + } + self->sources = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify)source_widgets_unref); + self->no_source_toggles = TRUE; + syncevo_config_foreach_source (self->config, + (ConfigFunc)init_source, + self); +} + +/* only adds config to hashtable and combo */ +static void +sync_config_widget_add_config (SyncConfigWidget *self, + const char *name, + SyncevoConfig *config) +{ + GtkListStore *store; + GtkTreeIter iter; + const char *guess_name; + SyncevoConfig *guess_config; + int score = 1; + int guess_score, second_guess_score = -1; + char *str; + + store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (self->combo))); + if (syncevo_config_get_value (config, NULL, "score", &str)) { + score = (int)strtol (str, NULL, 10); + } + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + 0, name, + 1, config, + 2, score, + -1); + + /* make an educated guess if possible */ + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + 0, &guess_name, + 1, &guess_config, + 2, &guess_score, + -1); + + if (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + 2, &second_guess_score, + -1); + } + + if (guess_score > 1 && guess_score > second_guess_score) { + gtk_combo_box_set_active (GTK_COMBO_BOX (self->combo), 0); + /* TRANSLATORS: explanation before a device template combobox. + * Placeholder is a device name like 'Nokia N85' or 'Syncevolution + * Client' */ + str = g_strdup_printf (_("This device looks like it might be a '%s'. " + "If this is not correct, please take a look at " + "the list of supported devices and pick yours " + "if it is listed"), guess_name); + } else { + gtk_combo_box_set_active (GTK_COMBO_BOX (self->combo), -1); + str = g_strdup (_("We don't know what this device is exactly. " + "Please take a look at the list of " + "supported devices and pick yours if it " + "is listed")); + } + gtk_label_set_text (GTK_LABEL (self->device_text), str); + g_free (str); +} + +static void +sync_config_widget_update_pretty_name (SyncConfigWidget *self) +{ + self->pretty_name = NULL; + + if (self->config) { + syncevo_config_get_value (self->config, NULL, + "PeerName", &self->pretty_name); + if (!self->pretty_name) { + syncevo_config_get_value (self->config, NULL, + "deviceName", &self->pretty_name); + } + } + + if (!self->pretty_name) { + self->pretty_name = self->config_name; + } +} + +static void +sync_config_widget_set_config (SyncConfigWidget *self, + SyncevoConfig *config) +{ + self->config = config; + sync_config_widget_update_pretty_name (self); +} + + +static void +setup_service_clicked (GtkButton *btn, SyncConfigWidget *self) +{ + sync_config_widget_set_expanded (self, TRUE); +} + +static void +sync_config_widget_set_name (SyncConfigWidget *self, + const char *name) +{ + g_free (self->config_name); + self->config_name = g_strdup (name); + sync_config_widget_update_pretty_name (self); +} + + +static void +device_selection_btn_clicked_cb (GtkButton *btn, SyncConfigWidget *self) +{ + GtkTreeIter iter; + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self->combo), &iter)) { + const char *name; + SyncevoConfig *config; + GtkTreeModel *model; + + self->device_template_selected = TRUE; + + model = gtk_combo_box_get_model(GTK_COMBO_BOX (self->combo)); + gtk_tree_model_get (model, &iter, 0, &name, -1 ); + gtk_tree_model_get (model, &iter, 1, &config, -1 ); + + sync_config_widget_set_config (self, config); + + sync_config_widget_update_expander (self); + } +} + +static void +server_settings_notify_expand_cb (GtkExpander *expander, + GParamSpec *pspec, + SyncConfigWidget *self) +{ + /* NOTE: expander can be the fake or real one... */ + if (gtk_expander_get_expanded (GTK_EXPANDER (self->fake_expander))) { + g_signal_handlers_disconnect_by_func (self->fake_expander, + server_settings_notify_expand_cb, + self); + + gtk_widget_hide (self->fake_expander); + gtk_expander_set_expanded (GTK_EXPANDER (self->fake_expander), FALSE); + gtk_expander_set_expanded (GTK_EXPANDER (self->expander), TRUE); + gtk_widget_show (self->expander); + + g_signal_connect (self->expander, "notify::expanded", + G_CALLBACK (server_settings_notify_expand_cb), self); + } else { + g_signal_handlers_disconnect_by_func (self->expander, + server_settings_notify_expand_cb, + self); + + gtk_widget_hide (self->expander); + gtk_widget_show (self->fake_expander); + + g_signal_connect (self->fake_expander, "notify::expanded", + G_CALLBACK (server_settings_notify_expand_cb), self); + } +} + +static GdkPixbuf* +load_icon (const char *uri, guint icon_size) +{ + GError *error = NULL; + GdkPixbuf *pixbuf; + 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 NULL; + } + + return pixbuf; +} + +static void +sync_config_widget_update_label (SyncConfigWidget *self) +{ + if (self->config && self->pretty_name) { + char *url, *sync_url; + char *str; + + syncevo_config_get_value (self->config, NULL, "WebURL", &url); + syncevo_config_get_value (self->config, NULL, "syncURL", &sync_url); + + if (self->current) { + str = g_strdup_printf ("<b>%s</b>", self->pretty_name); + } else { + str = g_strdup_printf ("%s", self->pretty_name); + } + if (g_str_has_prefix (sync_url, "obex-bt://")) { + char *tmp = g_strdup_printf (_("%s - Bluetooth device"), str); + g_free (str); + str = tmp; + } else if (!self->has_template) { + /* TRANSLATORS: service title for services that are not based on a + * template in service list, the placeholder is the name of the service */ + char *tmp = g_strdup_printf (_("%s - manually setup"), str); + g_free (str); + str = tmp; + } else if (url && strlen (url) > 0) { + char *tmp = g_strdup_printf ("%s -",str); + g_free (str); + str = tmp; + } + + gtk_label_set_markup (GTK_LABEL (self->label), str); + g_free (str); + } +} + +void +sync_config_widget_set_current_service_name (SyncConfigWidget *self, + const char *name) +{ + g_free (self->current_service_name); + self->current_service_name = g_strdup (name); + + update_buttons (self); +} + +void +sync_config_widget_set_current (SyncConfigWidget *self, + gboolean current) +{ + if (self->current != current) { + self->current = current; + sync_config_widget_update_label (self); + } +} + +static void +set_session (SyncConfigWidget *self, const char *path) +{ + g_free (self->running_session); + self->running_session = g_strdup (path); + + gtk_widget_set_sensitive (GTK_WIDGET (self->reset_delete_button), + !self->running_session); + gtk_widget_set_sensitive (GTK_WIDGET (self->use_button), + !self->running_session); + + /* TODO: maybe add a explanation text somewhere: + * "Configuration changes are not possible while a sync is in progress" */ +} + +static void +session_changed_cb (SyncevoServer *server, + char *path, + gboolean started, + SyncConfigWidget *self) +{ + if (started) { + set_session (self, path); + } else if (g_strcmp0 (self->running_session, path) == 0 ) { + set_session (self, NULL); + } +} + +static void +get_sessions_cb (SyncevoServer *server, + SyncevoSessions *sessions, + GError *error, + SyncConfigWidget *self) +{ + if (error) { + g_warning ("Server.GetSessions failed: %s", error->message); + g_error_free (error); + /* non-fatal, ignore in UI */ + + g_object_unref (self); + return; + } + + set_session (self, syncevo_sessions_index (sessions, 0)); + syncevo_sessions_free (sessions); + g_object_unref (self); +} + +void +sync_config_widget_set_server (SyncConfigWidget *self, + SyncevoServer *server) +{ + if (self->server) { + g_signal_handlers_disconnect_by_func (self->server, + session_changed_cb, + self); + g_object_unref (self->server); + self->server = NULL; + } + if (!server && !self->server) { + return; + } + + self->server = g_object_ref (server); + + /* monitor sessions so we can set editing buttons insensitive */ + g_signal_connect (self->server, "session-changed", + G_CALLBACK (session_changed_cb), self); + + /* reference is released in callback */ + g_object_ref (self); + syncevo_server_get_sessions (self->server, + (SyncevoServerGetSessionsCb)get_sessions_cb, + self); +} + +static void +sync_config_widget_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (object); + + switch (property_id) { + case PROP_SERVER: + sync_config_widget_set_server (self, g_value_get_pointer (value)); + break; + case PROP_NAME: + sync_config_widget_set_name (self, g_value_get_string (value)); + break; + case PROP_CONFIG: + sync_config_widget_set_config (self, g_value_get_pointer (value)); + break; + case PROP_CURRENT: + sync_config_widget_set_current (self, g_value_get_boolean (value)); + break; + case PROP_HAS_TEMPLATE: + sync_config_widget_set_has_template (self, g_value_get_boolean (value)); + break; + case PROP_CONFIGURED: + sync_config_widget_set_configured (self, g_value_get_boolean (value)); + break; + case PROP_CURRENT_SERVICE_NAME: + sync_config_widget_set_current_service_name (self, g_value_get_string (value)); + break; + case PROP_EXPANDED: + sync_config_widget_set_expanded (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +sync_config_widget_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (object); + + switch (property_id) { + case PROP_SERVER: + g_value_set_pointer (value, self->server); + case PROP_NAME: + g_value_set_string (value, self->config_name); + case PROP_CONFIG: + g_value_set_pointer (value, self->config); + case PROP_CURRENT: + g_value_set_boolean (value, self->current); + case PROP_HAS_TEMPLATE: + g_value_set_boolean (value, self->has_template); + case PROP_CONFIGURED: + g_value_set_boolean (value, self->configured); + case PROP_CURRENT_SERVICE_NAME: + g_value_set_string (value, self->current_service_name); + case PROP_EXPANDED: + g_value_set_boolean (value, self->expanded); + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +sync_config_widget_dispose (GObject *object) +{ + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (object); + + sync_config_widget_set_server (self, NULL); + if (self->config) { + syncevo_config_free (self->config); + } + self->config = NULL; + + g_free (self->config_name); + self->config_name = NULL; + g_free (self->current_service_name); + self->current_service_name = NULL; + g_free (self->running_session); + self->running_session = NULL; + if (self->sources) { + g_hash_table_destroy (self->sources); + self->sources = NULL; + } + + + + G_OBJECT_CLASS (sync_config_widget_parent_class)->dispose (object); +} + +static void +init_default_config (SyncConfigWidget *self) +{ + sync_config_widget_set_name (self, ""); + self->has_template = FALSE; + + syncevo_config_set_value (self->config, NULL, "username", ""); + syncevo_config_set_value (self->config, NULL, "password", ""); + syncevo_config_set_value (self->config, NULL, "syncURL", ""); + syncevo_config_set_value (self->config, NULL, "WebURL", ""); + syncevo_config_set_value (self->config, "memo", "uri", ""); + syncevo_config_set_value (self->config, "todo", "uri", ""); + syncevo_config_set_value (self->config, "addressbook", "uri", ""); + syncevo_config_set_value (self->config, "calendar", "uri", ""); + +} + +static gboolean +label_button_expose_cb (GtkWidget *widget, + GdkEventExpose *event, + SyncConfigWidget *self) +{ + GtkExpanderStyle style; + gint indicator_x, indicator_y; + + indicator_x = widget->style->xthickness + INDICATOR_SIZE / 2; + indicator_y = widget->style->ythickness + + widget->allocation.height / 2; + + if (self->expanded) { + style = GTK_EXPANDER_EXPANDED; + } else { + style = GTK_EXPANDER_COLLAPSED; + } + + gtk_paint_expander (widget->style, + widget->window, + widget->state, + NULL, + GTK_WIDGET (self), + NULL, + indicator_x, + indicator_y, + style); + + return FALSE; +} + +static gboolean +sync_config_widget_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + GdkRectangle rect; + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); + + rect.x = widget->allocation.x; + rect.y = widget->allocation.y; + rect.height = widget->allocation.height; + rect.width = widget->allocation.width; + + gtk_paint_box (widget->style, + widget->window, + widget->state, + GTK_SHADOW_OUT, + &rect, + widget, + NULL, + rect.x, + rect.y, + rect.width, + rect.height); + + gtk_container_propagate_expose (GTK_CONTAINER (self), + self->label_box, event); + gtk_container_propagate_expose (GTK_CONTAINER (self), + self->expando_box, event); + + return FALSE; +} + + +static void +sync_config_widget_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkRequisition req; + GtkAllocation alloc; + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); + + GTK_WIDGET_CLASS (sync_config_widget_parent_class)->size_allocate (widget, + allocation); + + gtk_widget_size_request (self->label_box, &req); + + alloc.x = allocation->x + widget->style->xthickness; + alloc.y = allocation->y + widget->style->ythickness; + alloc.width = allocation->width - 2 * widget->style->xthickness; + alloc.height = req.height; + + gtk_widget_size_allocate (self->label_box, &alloc); + + + if (self->expanded) { + gtk_widget_size_request (self->expando_box, &req); + + alloc.x = allocation->x + 2 * widget->style->xthickness; + alloc.y = allocation->y + widget->style->ythickness + + alloc.height + CHILD_PADDING; + alloc.width = allocation->width - 4 * widget->style->xthickness; + alloc.height = req.height; + + gtk_widget_size_allocate (self->expando_box, &alloc); + } +} + +static void +sync_config_widget_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); + GtkRequisition req; + + requisition->width = widget->style->xthickness * 2; + requisition->height = widget->style->ythickness * 2; + + gtk_widget_size_request (self->label_box, &req); + + requisition->width += req.width; + requisition->height = MAX (req.height, INDICATOR_SIZE) + + widget->style->ythickness * 2; + + if (self->expanded) { + + gtk_widget_size_request (self->expando_box, &req); + requisition->width = MAX (requisition->width, + req.width + widget->style->xthickness * 4); + requisition->height += req.height + 2 * widget->style->ythickness; + } +} + +static GObject * +sync_config_widget_constructor (GType gtype, + guint n_properties, + GObjectConstructParam *properties) +{ + SyncConfigWidget *self; + GObjectClass *parent_class; + char *url, *icon; + GdkPixbuf *buf; + + parent_class = G_OBJECT_CLASS (sync_config_widget_parent_class); + self = SYNC_CONFIG_WIDGET (parent_class->constructor (gtype, + n_properties, + properties)); + + if (!self->config || !self->server) { + g_warning ("No SyncevoServer or Syncevoconfig set for SyncConfigWidget"); + return G_OBJECT (self); + } + + if (g_strcmp0 (self->config_name, "default") == 0) { + + init_default_config (self); + gtk_widget_show (self->entry); + gtk_widget_hide (self->label); + } else { + gtk_widget_hide (self->entry); + gtk_widget_show (self->label); + } + + syncevo_config_get_value (self->config, NULL, "WebURL", &url); + syncevo_config_get_value (self->config, NULL, "IconURI", &icon); + + buf = load_icon (icon, SYNC_UI_LIST_ICON_SIZE); + gtk_image_set_from_pixbuf (GTK_IMAGE (self->image), buf); + g_object_unref (buf); + + if (url && strlen (url) > 0) { + gtk_link_button_set_uri (GTK_LINK_BUTTON (self->link), url); + gtk_widget_show (self->link); + } else { + gtk_widget_hide (self->link); + } + + sync_config_widget_update_label (self); + sync_config_widget_update_expander (self); + + /* hack to get focus in the right place on "Setup new service" */ + if (gtk_widget_get_visible (self->entry)) { + gtk_widget_grab_focus (self->entry); + } + + return G_OBJECT (self); +} + +static void +sync_config_widget_map (GtkWidget *widget) +{ + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); + + if (self->label_box && gtk_widget_get_visible (self->expando_box)) { + gtk_widget_map (self->label_box); + } + if (self->expando_box && gtk_widget_get_visible (self->expando_box)) { + gtk_widget_map (self->expando_box); + } + GTK_WIDGET_CLASS (sync_config_widget_parent_class)->map (widget); +} + +static void +sync_config_widget_unmap (GtkWidget *widget) +{ + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (widget); + + GTK_WIDGET_CLASS (sync_config_widget_parent_class)->unmap (widget); + + if (self->label_box) { + gtk_widget_unmap (self->label_box); + } + if (self->expando_box) { + gtk_widget_unmap (self->expando_box); + } +} + +static void +sync_config_widget_add (GtkContainer *container, + GtkWidget *widget) +{ + g_warning ("Can't add widgets in to SyncConfigWidget!"); +} + +static void +sync_config_widget_remove (GtkContainer *container, + GtkWidget *widget) +{ + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (container); + + + if (self->label_box == widget) { + gtk_widget_unparent (widget); + self->label_box = NULL; + } + if (self->expando_box == widget) { + gtk_widget_unparent (widget); + self->expando_box = NULL; + } +} + +static void +sync_config_widget_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + SyncConfigWidget *self = SYNC_CONFIG_WIDGET (container); + + if (self->label_box) { + (* callback) (self->label_box, callback_data); + } + if (self->expando_box) { + (* callback) (self->expando_box, callback_data); + } +} + + +static void +sync_config_widget_class_init (SyncConfigWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *w_class = GTK_WIDGET_CLASS (klass); + GtkContainerClass *c_class = GTK_CONTAINER_CLASS (klass); + GParamSpec *pspec; + + object_class->set_property = sync_config_widget_set_property; + object_class->get_property = sync_config_widget_get_property; + object_class->dispose = sync_config_widget_dispose; + object_class->constructor = sync_config_widget_constructor; + + w_class->expose_event = sync_config_widget_expose_event; + w_class->size_request = sync_config_widget_size_request; + w_class->size_allocate = sync_config_widget_size_allocate; + w_class->map = sync_config_widget_map; + w_class->unmap = sync_config_widget_unmap; + + c_class->add = sync_config_widget_add; + c_class->remove = sync_config_widget_remove; + c_class->forall = sync_config_widget_forall; + + pspec = g_param_spec_pointer ("server", + "SyncevoServer", + "The SyncevoServer to use in Syncevolution DBus calls", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_SERVER, pspec); + + pspec = g_param_spec_string ("name", + "Configuration name", + "The name of the Syncevolution service configuration", + NULL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_NAME, pspec); + + pspec = g_param_spec_pointer ("config", + "SyncevoConfig", + "The SyncevoConfig struct this widget represents. Takes ownership.", + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_CONFIG, pspec); + + pspec = g_param_spec_boolean ("current", + "Current", + "Whether the service is currently used", + FALSE, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_CURRENT, pspec); + + pspec = g_param_spec_boolean ("has-template", + "has template", + "Whether the service has a matching template", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_HAS_TEMPLATE, pspec); + + pspec = g_param_spec_boolean ("configured", + "Configured", + "Whether the service has a configuration already", + FALSE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_CONFIGURED, pspec); + + pspec = g_param_spec_string ("current-service-name", + "Current service name", + "The name of the currently used service or NULL", + NULL, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_CURRENT_SERVICE_NAME, pspec); + + pspec = g_param_spec_boolean ("expanded", + "Expanded", + "Whether the expander is open or closed", + FALSE, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_EXPANDED, pspec); + + signals[SIGNAL_CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE, + G_STRUCT_OFFSET (SyncConfigWidgetClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, + 0); + +} + +static void +label_enter_notify_cb (GtkWidget *widget, + GdkEventCrossing *event, + SyncConfigWidget *self) +{ + if (!self->expanded) { + gtk_widget_show (self->button); + } + gtk_widget_set_state (self->label_box, GTK_STATE_PRELIGHT); +} + +static void +label_leave_notify_cb (GtkWidget *widget, + GdkEventCrossing *event, + SyncConfigWidget *self) +{ + if (event->detail != GDK_NOTIFY_INFERIOR) { + gtk_widget_hide (self->button); + gtk_widget_set_state (self->label_box, GTK_STATE_NORMAL); + } +} + +static void +device_combo_changed (GtkComboBox *combo, + SyncConfigWidget *self) +{ + int active; + + active = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); + gtk_widget_set_sensitive (self->device_select_btn, active > -1); +} + +static void +label_button_release_cb (GtkWidget *widget, + GdkEventButton *event, + SyncConfigWidget *self) + +{ + if (event->button == 1) { + sync_config_widget_set_expanded (self, + !sync_config_widget_get_expanded (self)); + } +} + +static gint +compare_list_items (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + SyncConfigWidget *self) +{ + int score_a, score_b; + + gtk_tree_model_get(model, a, 2, &score_a, -1); + gtk_tree_model_get(model, b, 2, &score_b, -1); + + return score_a - score_b; +} + +static void +sync_config_widget_init (SyncConfigWidget *self) +{ + GtkWidget *tmp_box, *hbox, *cont, *vbox, *label; + GtkListStore *store; + GtkCellRenderer *renderer; + + gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); + + self->label_box = gtk_event_box_new (); + gtk_widget_set_app_paintable (self->label_box, TRUE); + gtk_widget_show (self->label_box); + gtk_widget_set_parent (self->label_box, GTK_WIDGET (self)); + gtk_widget_set_size_request (self->label_box, -1, SYNC_UI_LIST_ICON_SIZE + 6); + g_signal_connect (self->label_box, "enter-notify-event", + G_CALLBACK (label_enter_notify_cb), self); + g_signal_connect (self->label_box, "leave-notify-event", + G_CALLBACK (label_leave_notify_cb), self); + g_signal_connect (self->label_box, "button-release-event", + G_CALLBACK (label_button_release_cb), self); + g_signal_connect (self->label_box, "expose-event", + G_CALLBACK (label_button_expose_cb), self); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox); + gtk_container_add (GTK_CONTAINER (self->label_box), hbox); + + self->image = gtk_image_new (); + /* leave room for drawing the expander indicator in expose handler */ + gtk_widget_set_size_request (self->image, + SYNC_UI_LIST_ICON_SIZE + INDICATOR_SIZE, + SYNC_UI_LIST_ICON_SIZE); + gtk_misc_set_alignment (GTK_MISC (self->image), 1.0, 0.5); + gtk_widget_show (self->image); + gtk_box_pack_start (GTK_BOX (hbox), self->image, FALSE, FALSE, 8); + + tmp_box = gtk_hbox_new (FALSE, 0); + gtk_widget_show (tmp_box); + gtk_box_pack_start (GTK_BOX (hbox), tmp_box, FALSE, FALSE, 8); + + self->label = gtk_label_new (""); + gtk_label_set_max_width_chars (GTK_LABEL (self->label), 60); + gtk_label_set_ellipsize (GTK_LABEL (self->label), PANGO_ELLIPSIZE_END); + gtk_misc_set_alignment (GTK_MISC (self->label), 0.0, 0.5); + gtk_widget_show (self->label); + gtk_box_pack_start (GTK_BOX (tmp_box), self->label, FALSE, FALSE, 0); + + self->entry = gtk_entry_new (); + gtk_widget_set_no_show_all (self->entry, TRUE); + gtk_box_pack_start (GTK_BOX (tmp_box), self->entry, FALSE, FALSE, 4); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox); + gtk_box_pack_start (GTK_BOX (tmp_box), vbox, FALSE, FALSE, 0); + + /* TRANSLATORS: link button in service configuration form */ + self->link = gtk_link_button_new_with_label ("", _("Launch website")); + gtk_widget_set_no_show_all (self->link, TRUE); + gtk_box_pack_start (GTK_BOX (vbox), self->link, TRUE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox); + gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 32); + + /* TRANSLATORS: button in service configuration form */ + self->button = gtk_button_new_with_label (_("Set up now")); + gtk_widget_set_size_request (self->button, SYNC_UI_LIST_BTN_WIDTH, -1); + g_signal_connect (self->button, "clicked", + G_CALLBACK (setup_service_clicked), self); + gtk_box_pack_start (GTK_BOX (vbox), self->button, TRUE, FALSE, 0); + + /* label_box built, now build expando_box */ + + self->expando_box = gtk_hbox_new (FALSE, 0); + gtk_widget_set_no_show_all (self->expando_box, TRUE); + gtk_widget_set_parent (self->expando_box, GTK_WIDGET (self)); + + self->device_selector_box = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (self->expando_box), self->device_selector_box, + TRUE, TRUE, 16); + + hbox = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (self->device_selector_box), hbox, + FALSE, TRUE, 8); + self->device_text = gtk_label_new (_("We don't know what this device is exactly. " + "Please take a look at the list of " + "supported devices and pick yours if it " + "is listed")); + gtk_widget_show (self->device_text); + gtk_label_set_line_wrap (GTK_LABEL (self->device_text), TRUE); + gtk_widget_set_size_request (self->device_text, 600, -1); + gtk_box_pack_start (GTK_BOX (hbox), self->device_text, + FALSE, TRUE, 0); + + + hbox = gtk_hbox_new (FALSE, 16); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (self->device_selector_box), hbox, + FALSE, TRUE, 16); + + store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_INT); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + 2, GTK_SORT_DESCENDING); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store), 2, + (GtkTreeIterCompareFunc)compare_list_items, + NULL, NULL); + + self->combo = gtk_combo_box_new_with_model (GTK_TREE_MODEL (store)); + g_object_unref (G_OBJECT (store)); + gtk_widget_set_size_request (self->combo, 200, -1); + gtk_widget_show (self->combo); + gtk_box_pack_start (GTK_BOX (hbox), self->combo, FALSE, TRUE, 0); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(self->combo), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT(self->combo), renderer, + "text", 0, NULL); + + g_signal_connect (self->combo, "changed", + G_CALLBACK (device_combo_changed), self); + + + self->device_select_btn = gtk_button_new_with_label (_("Use these settings")); + gtk_widget_set_sensitive (self->device_select_btn, FALSE); + gtk_widget_show (self->device_select_btn); + gtk_box_pack_start (GTK_BOX (hbox), self->device_select_btn, + FALSE, TRUE, 0); + g_signal_connect (self->device_select_btn, "clicked", + G_CALLBACK (device_selection_btn_clicked_cb), self); + + /* settings_box has normal expander contents */ + self->settings_box = gtk_vbox_new (FALSE, 0); + gtk_widget_show (self->settings_box); + gtk_box_pack_start (GTK_BOX (self->expando_box), self->settings_box, + TRUE, TRUE, 16); + + vbox = gtk_vbox_new (FALSE, 8); + gtk_widget_show (vbox); + gtk_box_pack_start (GTK_BOX (self->settings_box), vbox, TRUE, TRUE, 0); + + tmp_box = gtk_vbox_new (FALSE, 0); + gtk_widget_show (tmp_box); + gtk_box_pack_start (GTK_BOX (vbox), tmp_box, FALSE, FALSE, 8); + + self->description_label = gtk_label_new (""); + gtk_misc_set_alignment (GTK_MISC (self->description_label), 0.0, 0.5); + gtk_widget_set_size_request (self->description_label, 700, -1); + gtk_label_set_line_wrap (GTK_LABEL (self->description_label), TRUE); + gtk_box_pack_start (GTK_BOX (tmp_box), self->description_label, FALSE, FALSE, 0); + + tmp_box = gtk_hbox_new (FALSE, 0); + gtk_widget_show (tmp_box); + gtk_box_pack_start (GTK_BOX (vbox), tmp_box, FALSE, FALSE, 0); + + self->userinfo_table = gtk_table_new (4, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (self->userinfo_table), 2); + gtk_table_set_col_spacings (GTK_TABLE (self->userinfo_table), 5); + gtk_widget_show (self->userinfo_table); + gtk_box_pack_start (GTK_BOX (tmp_box), self->userinfo_table, FALSE, FALSE, 0); + + /* TRANSLATORS: labels of entries in service configuration form */ + label = gtk_label_new (_("Username")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), label, + 0, 1, + 0, 1); + + self->username_entry = gtk_entry_new (); + gtk_widget_show (self->username_entry); + gtk_entry_set_width_chars (GTK_ENTRY (self->username_entry), 40); + gtk_entry_set_max_length (GTK_ENTRY (self->username_entry), 99); + gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), self->username_entry, + 1, 2, + 0, 1); + + label = gtk_label_new (_("Password")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), label, + 0, 1, + 1, 2); + + self->password_entry = gtk_entry_new (); + gtk_widget_show (self->password_entry); + gtk_entry_set_width_chars (GTK_ENTRY (self->password_entry), 40); + gtk_entry_set_visibility (GTK_ENTRY (self->password_entry), FALSE); + gtk_entry_set_max_length (GTK_ENTRY (self->password_entry), 99); + gtk_table_attach_defaults (GTK_TABLE (self->userinfo_table), self->password_entry, + 1, 2, + 1, 2); + + self->complex_config_info_bar = gtk_info_bar_new (); + gtk_info_bar_set_message_type (GTK_INFO_BAR (self->complex_config_info_bar), + GTK_MESSAGE_WARNING); + gtk_box_pack_start (GTK_BOX (vbox), self->complex_config_info_bar, + FALSE, FALSE, 0); + /* TRANSLATORS: warning in service configuration form for people + who have modified the configuration via other means. */ + label = gtk_label_new (_("Current configuration is more complex " + "than what can be shown here. Changes to sync " + "mode or synced data types will overwrite that " + "configuration.")); + gtk_widget_set_size_request (label, 600, -1); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_widget_show (label); + cont = gtk_info_bar_get_content_area ( + GTK_INFO_BAR (self->complex_config_info_bar)); + gtk_container_add (GTK_CONTAINER (cont), label); + + self->mode_table = gtk_table_new (4, 1, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (self->mode_table), 2); + gtk_table_set_col_spacings (GTK_TABLE (self->mode_table), 5); + gtk_widget_show (self->mode_table); + gtk_box_pack_start (GTK_BOX (vbox), self->mode_table, FALSE, FALSE, 0); + + /* TRANSLATORS: this is the epander label for server settings + in service configuration form */ + self->expander = gtk_expander_new (_("Hide server settings")); + gtk_box_pack_start (GTK_BOX (vbox), self->expander, FALSE, FALSE, 8); + + tmp_box = gtk_hbox_new (FALSE, 0); + gtk_widget_show (tmp_box); + gtk_container_add (GTK_CONTAINER (self->expander), tmp_box); + + self->server_settings_table = gtk_table_new (1, 1, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (self->server_settings_table), 2); + gtk_table_set_col_spacings (GTK_TABLE (self->server_settings_table), 5); + gtk_widget_show (self->server_settings_table); + gtk_box_pack_start (GTK_BOX (tmp_box), self->server_settings_table, FALSE, FALSE, 0); + + + tmp_box = gtk_hbox_new (FALSE, 0); + gtk_widget_show (tmp_box); + gtk_box_pack_start (GTK_BOX (vbox), tmp_box, FALSE, FALSE, 8); + + /* TRANSLATORS: this is the epander label for server settings + in service configuration form */ + self->fake_expander = gtk_expander_new (_("Show server settings")); + gtk_widget_show (self->fake_expander); + gtk_box_pack_start (GTK_BOX (tmp_box), self->fake_expander, FALSE, FALSE, 0); + g_signal_connect (self->fake_expander, "notify::expanded", + G_CALLBACK (server_settings_notify_expand_cb), self); + + self->use_button = gtk_button_new (); + gtk_widget_show (self->use_button); + gtk_box_pack_end (GTK_BOX (tmp_box), self->use_button, FALSE, FALSE, 8); + g_signal_connect (self->use_button, "clicked", + G_CALLBACK (use_clicked_cb), self); + + self->stop_button = gtk_button_new (); + gtk_box_pack_end (GTK_BOX (tmp_box), self->stop_button, FALSE, FALSE, 8); + g_signal_connect (self->stop_button, "clicked", + G_CALLBACK (stop_clicked_cb), self); + + self->reset_delete_button = gtk_button_new (); + gtk_widget_show (self->reset_delete_button); + gtk_box_pack_end (GTK_BOX (tmp_box), self->reset_delete_button, FALSE, FALSE, 8); + g_signal_connect (self->reset_delete_button, "clicked", + G_CALLBACK (reset_delete_clicked_cb), self); +} + + +GtkWidget* +sync_config_widget_new (SyncevoServer *server, + const char *name, + SyncevoConfig *config, + gboolean current, + const char *current_service_name, + gboolean configured, + gboolean has_template) +{ + return g_object_new (SYNC_TYPE_CONFIG_WIDGET, + "server", server, + "name", name, + "config", config, + "current", current, + "current-service-name", current_service_name, + "configured", configured, + "has-template", has_template, + NULL); +} + +void +sync_config_widget_set_expanded (SyncConfigWidget *self, gboolean expanded) +{ + g_return_if_fail (SYNC_IS_CONFIG_WIDGET (self)); + + if (self->expanded != expanded) { + + self->expanded = expanded; + if (self->expanded) { + gtk_widget_hide (self->button); + gtk_widget_show (self->expando_box); + if (gtk_widget_get_visible (self->entry)) { + gtk_widget_grab_focus (self->entry); + } else { + gtk_widget_grab_focus (self->username_entry); + } + } else { + gtk_widget_show (self->button); + gtk_widget_hide (self->expando_box); + } + g_object_notify (G_OBJECT (self), "expanded"); + + } +} + +gboolean +sync_config_widget_get_expanded (SyncConfigWidget *self) +{ + return self->expanded; +} + +void +sync_config_widget_set_has_template (SyncConfigWidget *self, gboolean has_template) +{ + if (self->has_template != has_template) { + self->has_template = has_template; + update_buttons (self); + sync_config_widget_update_label (self); + } +} + +gboolean +sync_config_widget_get_has_template (SyncConfigWidget *self) +{ + return self->has_template; +} + +void +sync_config_widget_set_configured (SyncConfigWidget *self, gboolean configured) +{ + if (self->configured != configured) { + self->configured = configured; + self->device_template_selected = configured; + update_buttons (self); + } +} + +gboolean +sync_config_widget_get_configured (SyncConfigWidget *self) +{ + return self->configured; +} + + +gboolean +sync_config_widget_get_current (SyncConfigWidget *widget) +{ + return widget->current; +} + +const char* +sync_config_widget_get_name (SyncConfigWidget *widget) +{ + return widget->config_name; +} + +void +sync_config_widget_add_alternative_config (SyncConfigWidget *self, + const char *template_name, + SyncevoConfig *config, + gboolean configured) +{ + sync_config_widget_add_config (self, template_name, config); + if (configured) { + sync_config_widget_set_config (self, config); + sync_config_widget_set_configured (self, TRUE); + } + + + sync_config_widget_update_expander (self); + +} diff --git a/src/gtk3-ui/sync-config-widget.h b/src/gtk3-ui/sync-config-widget.h new file mode 100644 index 00000000..ea07b0c1 --- /dev/null +++ b/src/gtk3-ui/sync-config-widget.h @@ -0,0 +1,128 @@ +#ifndef _SYNC_CONFIG_WIDGET +#define _SYNC_CONFIG_WIDGET + +#include <glib-object.h> +#include <gtk/gtk.h> + +#include "syncevo-server.h" + +G_BEGIN_DECLS + +#define SYNC_TYPE_CONFIG_WIDGET sync_config_widget_get_type() + +#define SYNC_CONFIG_WIDGET(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), SYNC_TYPE_CONFIG_WIDGET, SyncConfigWidget)) + +#define SYNC_CONFIG_WIDGET_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), SYNC_TYPE_CONFIG_WIDGET, SyncConfigWidgetClass)) + +#define SYNC_IS_CONFIG_WIDGET(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SYNC_TYPE_CONFIG_WIDGET)) + +#define SYNC_IS_CONFIG_WIDGET_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), SYNC_TYPE_CONFIG_WIDGET)) + +#define SYNC_CONFIG_WIDGET_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), SYNC_TYPE_CONFIG_WIDGET, SyncConfigWidgetClass)) + + +typedef struct { + GtkContainer parent; + GtkWidget *expando_box; + GtkWidget *label_box; + + GtkWidget *device_selector_box; + GtkWidget *device_text; + GtkWidget *combo; + GtkWidget *device_select_btn; + + GtkWidget *settings_box; + + gboolean current; /* is this currently used config */ + char *current_service_name; /* name of the current service */ + gboolean configured; /* actual service configuration exists on server */ + gboolean device_template_selected; + gboolean has_template; /* this service configuration has a matching template */ + gboolean expanded; + + SyncevoServer *server; + SyncevoConfig *config; + GHashTable *configs; /* possible configs. config above is one of these */ + + char *config_name; + char *pretty_name; + + char *running_session; + + char *expand_id; + + /* label */ + GtkWidget *image; + GtkWidget *label; + GtkWidget *entry; + GtkWidget *link; + GtkWidget *button; + + /* content */ + GtkWidget *description_label; + GtkWidget *userinfo_table; + GtkWidget *name_label; + GtkWidget *name_entry; + GtkWidget *complex_config_info_bar; + GtkWidget *mode_table; + GtkWidget *send_check; + GtkWidget *receive_check; + GtkWidget *username_entry; + GtkWidget *password_entry; + GtkWidget *source_toggle_label; + GtkWidget *baseurl_entry; + GtkWidget *expander; + GtkWidget *fake_expander; + GtkWidget *server_settings_table; + GtkWidget *reset_delete_button; + GtkWidget *stop_button; + GtkWidget *use_button; + + GHashTable *sources; /* key is source name, value is source_widgets */ + + gboolean mode_changed; + + gboolean no_source_toggles; +} SyncConfigWidget; + +typedef struct { + GtkContainerClass parent_class; + + void (*changed) (SyncConfigWidget *widget); +} SyncConfigWidgetClass; + +GType sync_config_widget_get_type (void); + +GtkWidget *sync_config_widget_new (SyncevoServer *server, + const char *name, + SyncevoConfig *config, + gboolean current, + const char *current_service_name, + gboolean configured, + gboolean has_template); + +void sync_config_widget_set_expanded (SyncConfigWidget *widget, gboolean expanded); +gboolean sync_config_widget_get_expanded (SyncConfigWidget *widget); + +gboolean sync_config_widget_get_current (SyncConfigWidget *widget); +void sync_config_widget_set_current (SyncConfigWidget *self, gboolean current); + +void sync_config_widget_set_has_template (SyncConfigWidget *self, gboolean has_template); +gboolean sync_config_widget_get_has_template (SyncConfigWidget *self); + +void sync_config_widget_set_configured (SyncConfigWidget *self, gboolean configured); +gboolean sync_config_widget_get_configured (SyncConfigWidget *self); + +const char *sync_config_widget_get_name (SyncConfigWidget *widget); + +void sync_config_widget_expand_id (SyncConfigWidget *self, const char *id); +void sync_config_widget_add_alternative_config (SyncConfigWidget *self, const char *name, SyncevoConfig *config, gboolean configured); +G_END_DECLS + + +#endif diff --git a/src/gtk3-ui/sync-generic.png b/src/gtk3-ui/sync-generic.png Binary files differnew file mode 100644 index 00000000..75e6ed7c --- /dev/null +++ b/src/gtk3-ui/sync-generic.png diff --git a/src/gtk3-ui/sync-gtk.desktop.in b/src/gtk3-ui/sync-gtk.desktop.in new file mode 100644 index 00000000..2de82f0b --- /dev/null +++ b/src/gtk3-ui/sync-gtk.desktop.in @@ -0,0 +1,10 @@ +[Desktop Entry] +_Name=SyncEvolution (GTK) +_Comment=Synchronize PIM data +Version=1.0 +Type=Application +Exec=sync-ui +Icon=sync +Categories=Office;PDA;GTK; +Terminal=false +StartupNotify=true diff --git a/src/gtk3-ui/sync-spinner.gif b/src/gtk3-ui/sync-spinner.gif Binary files differnew file mode 100644 index 00000000..f4bc16de --- /dev/null +++ b/src/gtk3-ui/sync-spinner.gif diff --git a/src/gtk3-ui/sync-ui-config.c b/src/gtk3-ui/sync-ui-config.c new file mode 100644 index 00000000..1f890941 --- /dev/null +++ b/src/gtk3-ui/sync-ui-config.c @@ -0,0 +1,160 @@ +/* + * 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 + */ + +#include <glib/gi18n.h> + +#include <string.h> +#include "sync-ui-config.h" +#include "sync-ui.h" + +void +server_config_free (server_config *server) +{ + if (!server) + return; + + g_free (server->name); + syncevo_config_free (server->config); + + g_slice_free (server_config, server); +} + +void +server_config_update_from_entry (server_config *server, GtkEntry *entry) +{ + char **str; + const char *new_str; + + /* all entries have a pointer to the correct string in server_config */ + str = g_object_get_data (G_OBJECT (entry), "value"); + g_assert (str); + new_str = gtk_entry_get_text (entry); + + if ((*str == NULL && strlen (new_str) != 0) || + (*str != NULL && strcmp (*str, new_str) != 0)) { + + server->changed = TRUE; + + g_free (*str); + *str = g_strdup (new_str); + } +} + +static void +add_source_config (char *name, + GHashTable *syncevo_source_config, + GHashTable *source_configs) +{ + source_config *new_conf; + + new_conf = g_slice_new0 (source_config); + new_conf->name = name; + new_conf->supported_locally = TRUE; + new_conf->stats_set = FALSE; + new_conf->config = syncevo_source_config; + + g_hash_table_insert (source_configs, name, new_conf); +} + +void +server_config_init (server_config *server, SyncevoConfig *config) +{ + server->config = config; + + /* build source_configs */ + server->source_configs = g_hash_table_new (g_str_hash, g_str_equal); + syncevo_config_foreach_source (config, + (ConfigFunc)add_source_config, + server->source_configs); + if (!syncevo_config_get_value (config, NULL, "PeerName", &server->pretty_name)) { + server->pretty_name = server->name; + } +} + +gboolean +source_config_is_usable (source_config *source) +{ + const char *source_uri; + + source_uri = g_hash_table_lookup (source->config, "uri"); + + if (!source_config_is_enabled (source) || + !source_uri || + strlen (source_uri) == 0 || + !source->supported_locally) { + return FALSE; + } + return TRUE; +} + +gboolean +source_config_is_enabled (source_config *source) +{ + char *mode; + + mode = g_hash_table_lookup (source->config, "sync"); + if (mode && + (strcmp (mode, "none") == 0 || + strcmp (mode, "disabled") == 0)) { + return FALSE; + } + return TRUE; +} + +server_data* +server_data_new (const char *name, gpointer *data) +{ + server_data *serv_data; + + serv_data = g_slice_new0 (server_data); + serv_data->data = data; + serv_data->config = g_slice_new0 (server_config); + serv_data->config->name = g_strdup (name); + + return serv_data; +} + +void +server_data_free (server_data *data, gboolean free_config) +{ + if (!data) + return; + + if (free_config && data->config) { + server_config_free (data->config); + } + 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); +} + +gboolean +peer_is_client (SyncevoConfig *config) +{ + char *is_client; + + g_return_val_if_fail (config, FALSE); + + syncevo_config_get_value (config, NULL, "PeerIsClient", &is_client); + return is_client && g_strcmp0 ("1", is_client) == 0; +} diff --git a/src/gtk3-ui/sync-ui-config.h b/src/gtk3-ui/sync-ui-config.h new file mode 100644 index 00000000..97f8b10c --- /dev/null +++ b/src/gtk3-ui/sync-ui-config.h @@ -0,0 +1,95 @@ +/* + * 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 + */ + +#ifndef SYNC_UI_CONFIG_H +#define SYNC_UI_CONFIG_H + +#include <gtk/gtk.h> +#include "syncevo-session.h" +#include "syncevo-server.h" + +typedef struct source_config { + char *name; + gboolean supported_locally; + + SyncevoSourcePhase phase; + + gboolean stats_set; + long status; + long local_changes; + long remote_changes; + long local_rejections; + long remote_rejections; + + GtkWidget *info_bar; /* info/error bar, after ui has been constructed */ + GtkWidget *label; /* source report label, after ui has been constructed */ + GtkWidget *box; /* source box, after ui has been constructed */ + + GHashTable *config; /* link to a "sub-hashtable" inside server_config->config */ +} source_config; + +typedef struct server_config { + char *name; + char *pretty_name; + char *password; + /* any field in config has changed */ + gboolean changed; + + /* a authentication detail (base_url/username/password) has changed */ + gboolean auth_changed; + + gboolean password_changed; + + GHashTable *source_configs; /* source_config's*/ + + SyncevoConfig *config; +} server_config; + +gboolean source_config_is_usable (source_config *source); +gboolean source_config_is_enabled (source_config *source); +void source_config_free (source_config *source); + +void server_config_init (server_config *server, SyncevoConfig *config); +void server_config_free (server_config *server); + +void server_config_update_from_entry (server_config *server, GtkEntry *entry); +GPtrArray* server_config_get_option_array (server_config *server); +void server_config_disable_unsupported_sources (server_config *server); + +void server_config_ensure_default_sources_exist (server_config *server); + +/* 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 { + server_config *config; + GPtrArray *options_override; + gpointer *data; +} server_data; +server_data* server_data_new (const char *name, gpointer *data); +void server_data_free (server_data *data, gboolean free_config); + +/** + * utility function: TRUE if the config belongs to a client (PeerIsClient) + */ +gboolean peer_is_client (SyncevoConfig *config); + +#endif diff --git a/src/gtk3-ui/sync-ui.c b/src/gtk3-ui/sync-ui.c new file mode 100644 index 00000000..6579096a --- /dev/null +++ b/src/gtk3-ui/sync-ui.c @@ -0,0 +1,3599 @@ +/* + * 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 + */ + + +#include <stdlib.h> +#include <math.h> +#include <string.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-bindings.h> + +#include "syncevo-server.h" +#include "syncevo-session.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 "config.h" +#include "sync-ui-config.h" +#include "sync-ui.h" +#include "sync-config-widget.h" + +#ifdef USE_MOBLIN_UX +#include "mux-frame.h" + +#ifdef MX_GTK_0_99_1 +#include <mx-gtk/mx-gtk.h> +#else +#include <mx/mx-gtk.h> +#endif + +#endif + +static gboolean support_canceling = FALSE; +#define REPORTS_PER_CALL 10 + +#define SYNC_UI_ICON_SIZE 48 + +#define STRING_VARIANT_HASHTABLE (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE)) + +enum { + PAGE_MAIN, + PAGE_SETTINGS, + PAGE_EMERGENCY, +}; + +typedef enum bluetooth_type { + SYNC_BLUETOOTH_NONE, + SYNC_BLUETOOTH_GNOME, + SYNC_BLUETOOTH_MOBLIN +} bluetooth_type; + +typedef enum app_state { + SYNC_UI_STATE_CURRENT_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; + +typedef enum ui_operation { + OP_SYNC, /* use sync mode from config */ + OP_SYNC_SLOW, + OP_SYNC_REFRESH_FROM_CLIENT, + OP_SYNC_REFRESH_FROM_SERVER, + OP_SAVE, + OP_RESTORE, +} ui_operation; + +typedef struct operation_data { + app_data *data; + ui_operation operation; + gboolean started; + const char *dir; /* for OP_RESTORE */ +} operation_data; + +struct _app_data { + GtkWidget *sync_win; + + GtkWidget *services_win; /* will be NULL when USE_MOBLIN_UX is set*/ + GtkWidget *emergency_win; /* will be NULL when USE_MOBLIN_UX is set*/ +#ifdef USE_MOBLIN_UX + GtkWidget *notebook; /* only in use with USE_MOBLIN_UX */ + GtkWidget *back_btn; /* only in use with USE_MOBLIN_UX */ +#endif + GtkWidget *settings_btn; /* only in use with USE_MOBLIN_UX */ + + guint settings_id; + + GtkWidget *service_box; + GtkWidget *info_bar; + GtkWidget *no_connection_box; + GtkWidget *main_frame; + GtkWidget *log_frame; + GtkWidget *server_icon_box; + + GtkWidget *offline_label; + GtkWidget *progress; + GtkWidget *sync_status_label; + GtkWidget *spinner_image; + GtkWidget *sync_btn; + GtkWidget *change_service_btn; + GtkWidget *emergency_btn; + + GtkWidget *server_label; + GtkWidget *autosync_box; + GtkWidget *autosync_toggle; + GtkWidget *last_synced_label; + GtkWidget *sources_box; + + GtkWidget *new_service_btn; + GtkWidget *new_device_btn; + GtkWidget *services_box; + GtkWidget *devices_box; + GtkWidget *scrolled_window; + GtkWidget *expanded_config; + GtkWidget *settings_close_btn; + + GtkWidget *emergency_label; + GtkWidget *emergency_expander; + GtkWidget *emergency_source_table; + GtkWidget *refresh_from_server_btn_label; + GtkWidget *refresh_from_client_btn_label; + GtkWidget *emergency_backup_table; + GtkWidget *emergency_close_btn; + + GtkWidget *password_dialog_entry; + char *password_dialog_id; + + gboolean forced_emergency; + GHashTable *emergency_sources; + guint backup_count; + + gboolean online; + + gboolean syncing; + gboolean synced_this_session; + int last_sync; + guint last_sync_src_id; + + ui_operation current_operation; + server_config *current_service; + app_state current_state; + guint service_list_updates_left; + gboolean open_current; /* should the service list open the current + service when it populates next time*/ + char *config_id_to_open; + + SyncevoServer *server; + + SyncevoSession *running_session; /* session that is currently active */ + + bluetooth_type bluetooth_wizard; +}; + +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_main_view (app_data *data); +static void update_emergency_view (app_data *data); +static void update_emergency_expander (app_data *data); +static void show_emergency_view (app_data *data); +static void show_services_list (app_data *data, const char *config_id_to_open); +static void update_services_list (app_data *data); +static void update_service_ui (app_data *data); +static void setup_new_service_clicked (GtkButton *btn, app_data *data); +static gboolean source_config_update_widget (source_config *source); +static void get_presence_cb (SyncevoServer *server, char *status, char **transport, + GError *error, app_data *data); +static void get_reports_cb (SyncevoServer *server, SyncevoReports *reports, + GError *error, app_data *data); +static void start_session_cb (SyncevoServer *server, char *path, + GError *error, operation_data *op_data); +static void get_config_for_main_win_cb (SyncevoServer *server, SyncevoConfig *config, + GError *error, app_data *data); + + +void +toggle_set_active (GtkWidget *toggle, gboolean active) +{ +#ifdef USE_MOBLIN_UX + /* MxGtkLightSwitch does not have "active" property yet */ + mx_gtk_light_switch_set_active (MX_GTK_LIGHT_SWITCH (toggle), active); +#else + g_object_set (toggle, "active", active, NULL); +#endif +} + +gboolean +toggle_get_active (GtkWidget *toggle) +{ +#ifdef USE_MOBLIN_UX + /* MxGtkLightSwitch does not have "active" property yet */ + return mx_gtk_light_switch_get_active (MX_GTK_LIGHT_SWITCH (toggle)); +#else + gboolean active; + g_object_get (toggle, "active", &active, NULL); + return active; +#endif +} + +void +show_error_dialog (GtkWidget *widget, const char* message) +{ + GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (widget)); + + GtkWidget *w; + w = gtk_message_dialog_new (window, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", + message); + gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); +} + + +static void +remove_child (GtkWidget *widget, GtkContainer *container) +{ + gtk_container_remove (container, widget); +} + +static void +change_service_clicked_cb (GtkButton *btn, app_data *data) +{ + /* data->open_current = TRUE; */ + show_services_list (data, NULL); +} + +static void +emergency_clicked_cb (GtkButton *btn, app_data *data) +{ + show_emergency_view (data); +} + + +char* +get_pretty_source_name (const char *source_name) +{ + /* TRANSLATORS: There have been name changes to keep things in line with + * the rest of the moblin UI. Please make sure the name you use matches + * the ones in e.g. the panels. */ + if (strcmp (source_name, "addressbook") == 0) { + return g_strdup (_("Contacts")); + } else if (strcmp (source_name, "calendar") == 0) { + return g_strdup (_("Appointments")); + } else if (strcmp (source_name, "todo") == 0) { + return g_strdup (_("Tasks")); + } else if (strcmp (source_name, "memo") == 0) { + return g_strdup (_("Notes")); + } else if (strcmp (source_name, "calendar+todo") == 0) { + /* TRANSLATORS: This is a "combination source" for syncing with devices + * that combine appointments and tasks. the name should match the ones + * used for calendar and todo above */ + return g_strdup (_("Appointments & Tasks")); + } else { + char *tmp; + tmp = g_strdup (source_name); + tmp[0] = g_ascii_toupper (tmp[0]); + return tmp; + } +} + +char* +get_pretty_source_name_markup (const char *source_name) +{ + char *plain, *markup; + + plain = get_pretty_source_name (source_name); + markup = g_markup_escape_text (plain, -1); + g_free (plain); + return markup; +} + +static void +reload_config (app_data *data, const char *server) +{ + server_config_free (data->current_service); + data->forced_emergency = FALSE; + g_hash_table_remove_all (data->emergency_sources); + + if (!server || strlen (server) == 0) { + data->current_service = NULL; + update_service_ui (data); + set_app_state (data, SYNC_UI_STATE_SERVER_OK); + } else { + data->synced_this_session = FALSE; + data->current_service = g_slice_new0 (server_config); + data->current_service->name = g_strdup (server); + set_app_state (data, SYNC_UI_STATE_GETTING_SERVER); + + syncevo_server_get_config (data->server, + data->current_service->name, + FALSE, + (SyncevoServerGetConfigCb)get_config_for_main_win_cb, + data); + + } +} + + +static void +abort_sync_cb (SyncevoSession *session, + GError *error, + app_data *data) +{ + if (error) { + /* TODO show in UI: failed to abort sync (while syncing) */ + g_error_free (error); + } + + /* status change handler takes care of updating UI */ +} + +static void +sync_cb (SyncevoSession *session, + GError *error, + app_data *data) +{ + if (error) { + /* TODO show in UI: sync failed (failed to even start) */ + g_error_free (error); + g_object_unref (session); + return; + } + + set_sync_progress (data, 0.0, _("Starting sync")); + /* stop updates of "last synced" */ + if (data->last_sync_src_id > 0) + g_source_remove (data->last_sync_src_id); + set_app_state (data, SYNC_UI_STATE_SYNCING); +} + +gboolean +show_confirmation (GtkWidget *widget, const char *message, + const char *yes, const char *no) +{ + GtkWidget *w; + int ret; + + w = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (widget)), + GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + "%s", + message); + gtk_dialog_add_buttons (GTK_DIALOG (w), + no, GTK_RESPONSE_NO, + yes, GTK_RESPONSE_YES, + NULL); + ret = gtk_dialog_run (GTK_DIALOG (w)); + gtk_widget_destroy (w); + + return (ret == GTK_RESPONSE_YES); +} + +static void +slow_sync (app_data *data) +{ + operation_data *op_data; + char *message; + + /* TRANSLATORS: slow sync confirmation dialog message. Placeholder + * is service/device name */ + message = g_strdup_printf (_("Do you want to slow sync with %s?"), + data->current_service->pretty_name); + /* TRANSLATORS: slow sync confirmation dialog buttons */ + if (!show_confirmation (data->sync_win, message, + _("Yes, do slow sync"), _("No, cancel sync"))) { + g_free (message); + return; + } + g_free (message); + + op_data = g_slice_new (operation_data); + op_data->data = data; + op_data->operation = OP_SYNC_SLOW; + op_data->started = FALSE; + syncevo_server_start_session (data->server, + data->current_service->name, + (SyncevoServerStartSessionCb)start_session_cb, + op_data); + + show_main_view (data); +} + +static void +slow_sync_clicked_cb (GtkButton *btn, app_data *data) +{ + slow_sync (data); +} + + +static void +refresh_from_server_clicked_cb (GtkButton *btn, app_data *data) +{ + operation_data *op_data; + char *message; + + /* TRANSLATORS: confirmation dialog for "refresh from peer". Placeholder + * is service/device name */ + 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->pretty_name); + /* TRANSLATORS: "refresh from peer" confirmation dialog buttons */ + if (!show_confirmation (data->sync_win, message, + _("Yes, delete and replace"), _("No"))) { + g_free (message); + return; + } + g_free (message); + + op_data = g_slice_new (operation_data); + op_data->data = data; + op_data->operation = peer_is_client (data->current_service->config) ? + OP_SYNC_REFRESH_FROM_CLIENT : + OP_SYNC_REFRESH_FROM_SERVER; + op_data->started = FALSE; + syncevo_server_start_session (data->server, + data->current_service->name, + (SyncevoServerStartSessionCb)start_session_cb, + op_data); + + show_main_view (data); +} + +static void +refresh_from_client_clicked_cb (GtkButton *btn, app_data *data) +{ + operation_data *op_data; + char *message; + + /* TRANSLATORS: confirmation dialog for "refresh from local side". Placeholder + * is service/device name */ + 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->pretty_name); + /* TRANSLATORS: "refresh from local side" confirmation dialog buttons */ + if (!show_confirmation (data->sync_win, message, + _("Yes, delete and replace"), _("No"))) { + g_free (message); + return; + } + g_free (message); + + op_data = g_slice_new (operation_data); + op_data->data = data; + op_data->operation = peer_is_client (data->current_service->config) ? + OP_SYNC_REFRESH_FROM_SERVER : + OP_SYNC_REFRESH_FROM_CLIENT; + op_data->started = FALSE; + syncevo_server_start_session (data->server, + data->current_service->name, + (SyncevoServerStartSessionCb)start_session_cb, + op_data); + + show_main_view (data); +} + +static void +start_sync (app_data *data) +{ + operation_data *op_data; + + if (data->syncing) { + syncevo_session_abort (data->running_session, + (SyncevoSessionGenericCb)abort_sync_cb, + data); + set_sync_progress (data, -1.0, _("Trying to cancel sync")); + } else { + + op_data = g_slice_new (operation_data); + op_data->data = data; + op_data->operation = OP_SYNC; + op_data->started = FALSE; + syncevo_server_start_session (data->server, + data->current_service->name, + (SyncevoServerStartSessionCb)start_session_cb, + op_data); + } +} + + +static void +sync_clicked_cb (GtkButton *btn, app_data *data) +{ + g_return_if_fail (data->current_service); + + start_sync (data); +} + +#define DAY 60 * 60 * 24 +#define HALF_DAY 60 * 60 * 12 +#define HOUR 60 * 60 +#define HALF_HOUR 60 * 30 +#define MINUTE 60 +#define HALF_MINUTE 30 + +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->current_service) { + msg = g_strdup (_("No service or device selected")); + delay = -1; + } else if (data->last_sync <= 0) { + msg = g_strdup (data->current_service->pretty_name); /* we don't know */ + delay = -1; + } else if (diff < HALF_MINUTE) { + /* TRANSLATORS: This is the title on main view. Placeholder is + * the service name. Example: "Google - synced just now" */ + msg = g_strdup_printf (_("%s - synced just now"), + data->current_service->pretty_name); + delay = 10; + } else if (diff < MINUTE + HALF_MINUTE) { + msg = g_strdup_printf (_("%s - synced a minute ago"), + data->current_service->pretty_name); + delay = MINUTE; + } else if (diff < HOUR) { + msg = g_strdup_printf (_("%s - synced %ld minutes ago"), + data->current_service->pretty_name, + (diff + HALF_MINUTE) / MINUTE); + delay = MINUTE; + } else if (diff < HOUR + HALF_HOUR) { + msg = g_strdup_printf (_("%s - synced an hour ago"), + data->current_service->pretty_name); + delay = HOUR; + } else if (diff < DAY) { + msg = g_strdup_printf (_("%s - synced %ld hours ago"), + data->current_service->pretty_name, + (diff + HALF_HOUR) / (HOUR)); + delay = HOUR; + } else if (diff < DAY + HALF_DAY) { + msg = g_strdup_printf (_("%s - synced a day ago"), + data->current_service->pretty_name); + delay = HOUR; + } else { + msg = g_strdup_printf (_("%s - synced %ld days ago"), + data->current_service->pretty_name, + (diff + HALF_DAY) / (DAY)); + delay = HOUR; + } + + gtk_label_set_text (GTK_LABEL (data->server_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_info_bar (GtkWidget *widget, + GtkMessageType type, + SyncErrorResponse response_id, + const char *message) +{ + GtkWidget *container, *label; + GtkInfoBar *bar = GTK_INFO_BAR (widget); + + if (!message) { + gtk_widget_hide (widget); + return; + } + + container = gtk_info_bar_get_action_area (bar); + gtk_container_foreach (GTK_CONTAINER (container), + (GtkCallback)remove_child, + container); + switch (response_id) { + case SYNC_ERROR_RESPONSE_SYNC: + /* TRANSLATORS: Action button in info bar in main view. Shown with e.g. + * "You've just restored a backup. The changes have not been " + * "synced with %s yet" */ + gtk_info_bar_add_button (bar, _("Sync now"), response_id); + break; + case SYNC_ERROR_RESPONSE_EMERGENCY: + /* TRANSLATORS: Action button in info bar in main view. Shown with e.g. + * "A normal sync is not possible at this time..." message. + * "Other options" will open Emergency view */ + gtk_info_bar_add_button (bar, _("Slow sync"), SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC); + gtk_info_bar_add_button (bar, _("Other options..."), response_id); + break; + case SYNC_ERROR_RESPONSE_SETTINGS_SELECT: + /* TRANSLATORS: Action button in info bar in main view. Shown e.g. + * when no service is selected. Will open configuration view */ + gtk_info_bar_add_button (bar, _("Select sync service"), response_id); + break; + case SYNC_ERROR_RESPONSE_SETTINGS_OPEN: + /* TRANSLATORS: Action button in info bar in main view. Shown e.g. + * login to service fails. Will open configuration view for this service */ + gtk_info_bar_add_button (bar, _("Edit service settings"), response_id); + break; + case SYNC_ERROR_RESPONSE_NONE: + break; + default: + g_warn_if_reached (); + } + + gtk_info_bar_set_message_type (bar, type); + container = gtk_info_bar_get_content_area (bar); + gtk_container_foreach (GTK_CONTAINER (container), + (GtkCallback)remove_child, + container); + + label = gtk_label_new (message); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_widget_set_size_request (label, 450, -1); + gtk_box_pack_start (GTK_BOX (container), label, FALSE, FALSE, 8); + gtk_widget_show (label); + gtk_widget_show (widget); +} + +static void +set_app_state (app_data *data, app_state state) +{ + if (state != SYNC_UI_STATE_CURRENT_STATE) + data->current_state = state; + + switch (data->current_state) { + case SYNC_UI_STATE_GETTING_SERVER: + gtk_widget_hide (data->service_box); + gtk_widget_hide (data->info_bar); + gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); + refresh_last_synced_label (data); + + gtk_widget_set_sensitive (data->main_frame, TRUE); + gtk_widget_set_sensitive (data->sync_btn, FALSE); + gtk_widget_set_sensitive (data->change_service_btn, FALSE); + gtk_widget_set_sensitive (data->emergency_btn, FALSE); + + if (data->settings_btn) + gtk_widget_set_sensitive (data->settings_btn, FALSE); + + break; + case SYNC_UI_STATE_SERVER_FAILURE: + gtk_widget_hide (data->service_box); + gtk_widget_hide (data->autosync_box); + gtk_widget_hide (data->progress); + refresh_last_synced_label (data); + + /* info bar content should be set earlier */ + gtk_widget_show (data->info_bar); + + 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->emergency_btn, FALSE); + gtk_widget_set_sensitive (data->change_service_btn, FALSE); + + if (data->settings_btn) + gtk_widget_set_sensitive (data->settings_btn, FALSE); + + break; + case SYNC_UI_STATE_SERVER_OK: + if (data->online) { + gtk_widget_hide (data->no_connection_box); + } else { + gtk_widget_show (data->no_connection_box); + } + + /* TRANSLATORS: These are for the button in main view, right side. + Keep line length below ~20 characters, use two lines if needed */ + gtk_button_set_label (GTK_BUTTON (data->sync_btn), + _("Sync now")); + + if (!data->current_service) { + gtk_widget_hide (data->service_box); + gtk_widget_hide (data->autosync_box); + gtk_widget_hide (data->progress); + set_info_bar (data->info_bar, + GTK_MESSAGE_INFO, SYNC_ERROR_RESPONSE_SETTINGS_SELECT, + _("You haven't selected a sync service or device yet. " + "Sync services let you synchronize your data " + "between your netbook and a web service. You can " + "also sync directly with some devices.")); + refresh_last_synced_label (data); + + gtk_label_set_text (GTK_LABEL (data->sync_status_label), ""); + + gtk_widget_set_sensitive (data->sync_btn, FALSE); + gtk_widget_set_sensitive (data->emergency_btn, FALSE); + gtk_window_set_focus (GTK_WINDOW (data->sync_win), + data->change_service_btn); + } else { + gtk_widget_hide (data->info_bar); + gtk_widget_show (data->service_box); + gtk_widget_show (data->autosync_box); + gtk_widget_set_sensitive (data->sync_btn, data->online); + gtk_widget_set_sensitive (data->emergency_btn, TRUE); + if (data->synced_this_session && data->current_operation != OP_RESTORE) { + gtk_button_set_label (GTK_BUTTON (data->sync_btn), + _("Sync again")); + } else { + gtk_widget_hide (data->progress); + } + gtk_window_set_focus (GTK_WINDOW (data->sync_win), data->sync_btn); + } + + gtk_widget_set_sensitive (data->main_frame, TRUE); + gtk_widget_set_sensitive (data->change_service_btn, TRUE); + + if (data->settings_btn) + gtk_widget_set_sensitive (data->settings_btn, TRUE); + + data->syncing = FALSE; + break; + + case SYNC_UI_STATE_SYNCING: + /* we have a active session, and a session is running + (the running session may or may not be ours) */ + gtk_widget_show (data->progress); + if (data->current_operation == OP_RESTORE) { + gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Restoring")); + } else { + gtk_label_set_text (GTK_LABEL (data->sync_status_label), _("Syncing")); + } + gtk_widget_set_sensitive (data->main_frame, FALSE); + gtk_widget_set_sensitive (data->change_service_btn, FALSE); + gtk_widget_set_sensitive (data->emergency_btn, FALSE); + + if (data->settings_btn) + gtk_widget_set_sensitive (data->settings_btn, FALSE); + + gtk_widget_set_sensitive (data->sync_btn, + support_canceling && data->current_operation != OP_RESTORE); + if (support_canceling && support_canceling && data->current_operation != OP_RESTORE) { + /* TRANSLATORS: This is for the button in main view, right side. + Keep line length below ~20 characters, use two lines if needed */ + gtk_button_set_label (GTK_BUTTON (data->sync_btn), _("Cancel sync")); + } + + data->syncing = TRUE; + break; + default: + g_assert_not_reached (); + } +} + +#ifdef USE_MOBLIN_UX + +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); + + gtk_box_pack_start (GTK_BOX (parent), frame, TRUE, TRUE, 0); + gtk_widget_show (frame); + return frame; +} + +static void +set_page (app_data *data, int page) +{ + int current = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); + + if (page != current) { + gtk_notebook_set_current_page (GTK_NOTEBOOK (data->notebook), + page); + if (page != PAGE_MAIN) { + gtk_widget_show (data->back_btn); + } else { + gtk_widget_hide (data->back_btn); + } + + /* make sure the toggle is correct */ + g_signal_handler_block (data->settings_btn, data->settings_id); + if (page == PAGE_SETTINGS) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->settings_btn), + TRUE); + } else if (current == PAGE_SETTINGS) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (data->settings_btn), + FALSE); + } + g_signal_handler_unblock (data->settings_btn, data->settings_id); + } + + gtk_window_present (GTK_WINDOW (data->sync_win)); +} + + +static void +settings_toggled (GtkToggleButton *button, app_data *data) +{ + int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); + + if (page == PAGE_SETTINGS) { + show_main_view (data); + } else { + show_services_list (data, NULL); + } +} + +static gboolean +key_press_cb (GtkWidget *widget, + GdkEventKey *event, + app_data *data) +{ + int page = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook)); + + if (event->keyval == GDK_KEY_Escape && page != PAGE_MAIN) { + show_main_view (data); + } + + return FALSE; +} + +/* For some reason metacity sometimes won't maximize but will if asked + * another time. For the record, I'm not proud of writing this */ +static gboolean +try_maximize (GtkWindow *win) +{ + static int count = 0; + + count++; + gtk_window_maximize (win); + + return (count < 10); +} + + +static void +setup_windows (app_data *data, + GtkWidget *main, + GtkWidget *settings, + GtkWidget *emergency) +{ + GtkWidget *tmp, *toolbar, *close_btn; + GtkToolItem *item; + + g_assert (GTK_IS_WINDOW (main)); + g_assert (GTK_IS_WINDOW (settings)); + g_assert (GTK_IS_WINDOW (emergency)); + + data->sync_win = main; + data->services_win = NULL; + data->emergency_win = NULL; + + /* populate the notebook with window contents */ + data->notebook = gtk_notebook_new (); + gtk_widget_show (data->notebook); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (data->notebook), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (data->notebook), FALSE); + + gtk_window_maximize (GTK_WINDOW (data->sync_win)); + g_timeout_add (10, (GSourceFunc)try_maximize, data->sync_win); + gtk_window_set_decorated (GTK_WINDOW (data->sync_win), FALSE); + gtk_widget_set_name (data->sync_win, "meego_win"); + g_signal_connect (data->sync_win, "key-press-event", + G_CALLBACK (key_press_cb), data); + + tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (data->sync_win))); + gtk_container_remove (GTK_CONTAINER (data->sync_win), tmp); + gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); + g_object_unref (tmp); + + tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (settings))); + gtk_container_remove (GTK_CONTAINER (settings), tmp); + gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); + g_object_unref (tmp); + + tmp = g_object_ref (gtk_bin_get_child (GTK_BIN (emergency))); + gtk_container_remove (GTK_CONTAINER (emergency), tmp); + gtk_notebook_append_page (GTK_NOTEBOOK (data->notebook), tmp, NULL); + g_object_unref (tmp); + + tmp = gtk_vbox_new (FALSE, 0); + gtk_widget_show (tmp); + gtk_container_add (GTK_CONTAINER (data->sync_win), tmp); + + gtk_box_pack_end (GTK_BOX (tmp), data->notebook, + TRUE, TRUE, 0); + + /* create the window toolbar */ + toolbar = gtk_toolbar_new (); + gtk_widget_set_name (toolbar, "moblin-toolbar"); + gtk_box_pack_start (GTK_BOX (tmp), toolbar, + FALSE, FALSE, 0); + + data->back_btn = gtk_button_new_with_label (_("Back to sync")); + gtk_widget_set_name (data->back_btn, "moblin-toolbar-button"); + gtk_widget_set_can_focus (data->back_btn, FALSE); + gtk_widget_set_no_show_all (data->back_btn, TRUE); + g_signal_connect_swapped (data->back_btn, "clicked", + G_CALLBACK (show_main_view), data); + item = gtk_tool_item_new (); + gtk_container_add (GTK_CONTAINER (item), data->back_btn); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 0); + + item = gtk_tool_item_new (); + gtk_tool_item_set_expand (item, TRUE); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, 1); + + data->settings_btn = gtk_toggle_button_new (); + gtk_widget_set_can_focus (data->settings_btn, FALSE); + gtk_widget_set_name (data->settings_btn, "moblin-settings-button"); + data->settings_id = g_signal_connect (data->settings_btn, "toggled", + G_CALLBACK (settings_toggled), data); + + gtk_container_add (GTK_CONTAINER (data->settings_btn), + gtk_image_new_from_icon_name ("preferences-other", + GTK_ICON_SIZE_LARGE_TOOLBAR)); + item = gtk_tool_item_new (); + gtk_container_add (GTK_CONTAINER (item), data->settings_btn); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); + + close_btn = gtk_button_new (); + gtk_widget_set_can_focus (close_btn, FALSE); + gtk_widget_set_name (close_btn, "moblin-close-button"); + g_signal_connect (close_btn, "clicked", + G_CALLBACK (gtk_main_quit), NULL); + gtk_container_add (GTK_CONTAINER (close_btn), + gtk_image_new_from_icon_name ("window-close", + GTK_ICON_SIZE_LARGE_TOOLBAR)); + item = gtk_tool_item_new (); + gtk_container_add (GTK_CONTAINER (item), close_btn); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); + + gtk_widget_show_all (toolbar); + + /* no need for close buttons */ + gtk_widget_hide (data->settings_close_btn); + gtk_widget_hide (data->emergency_close_btn); +} + +static void +show_emergency_view (app_data *data) +{ + update_emergency_view (data); + set_page (data, PAGE_EMERGENCY); +} + +static void +show_services_list (app_data *data, const char *config_id_to_open) +{ + g_free (data->config_id_to_open); + data->config_id_to_open = g_strdup (config_id_to_open); + set_page (data, PAGE_SETTINGS); + update_services_list (data); +} + +static void +show_main_view (app_data *data) +{ + set_page (data, PAGE_MAIN); +} + +#else + +/* return the placeholders themselves when not using Moblin UX */ +static GtkWidget* +switch_dummy_to_mux_frame (GtkWidget *dummy) { + return dummy; +} +static void +setup_windows (app_data *data, + GtkWidget *main, + GtkWidget *settings, + GtkWidget *emergency) +{ + data->sync_win = main; + data->services_win = settings; + data->emergency_win = emergency; + gtk_window_set_transient_for (GTK_WINDOW (data->services_win), + GTK_WINDOW (data->sync_win)); + gtk_window_set_transient_for (GTK_WINDOW (data->emergency_win), + GTK_WINDOW (data->sync_win)); + g_signal_connect (data->services_win, "delete-event", + G_CALLBACK (gtk_widget_hide_on_delete), NULL); + g_signal_connect (data->emergency_win, "delete-event", + G_CALLBACK (gtk_widget_hide_on_delete), NULL); +} + +static void +show_emergency_view (app_data *data) +{ + update_emergency_view (data); + gtk_widget_hide (data->services_win); + gtk_window_present (GTK_WINDOW (data->emergency_win)); +} + +static void +show_services_list (app_data *data, const char *config_id_to_open) +{ + g_free (data->config_id_to_open); + data->config_id_to_open = g_strdup (config_id_to_open); + + gtk_widget_hide (data->emergency_win); + gtk_window_present (GTK_WINDOW (data->services_win)); + update_services_list (data); +} + +static void +show_main_view (app_data *data) +{ + gtk_widget_hide (data->services_win); + gtk_widget_hide (data->emergency_win); + gtk_window_present (GTK_WINDOW (data->sync_win)); +} + +#endif + +/* This is a hacky way to achieve autoscrolling when the expanders open/close */ +static void +services_box_allocate_cb (GtkWidget *widget, + GtkAllocation *allocation, + app_data *data) +{ + if (GTK_IS_WIDGET (data->expanded_config)) { + int y; + GtkAdjustment *adj; + GtkAllocation alloc; + + gtk_widget_translate_coordinates (data->expanded_config, + data->services_box, + 0, 0, NULL, &y); + gtk_widget_get_allocation (data->expanded_config, &alloc); + + adj = gtk_scrolled_window_get_vadjustment + (GTK_SCROLLED_WINDOW (data->scrolled_window)); + gtk_adjustment_clamp_page (adj, y, y + alloc.height); + + data->expanded_config = NULL; + } +} + +static void +info_bar_response_cb (GtkInfoBar *info_bar, + SyncErrorResponse response_id, + app_data *data) +{ + switch (response_id) { + case SYNC_ERROR_RESPONSE_SYNC: + start_sync (data); + break; + case SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC: + slow_sync (data); + break; + case SYNC_ERROR_RESPONSE_EMERGENCY: + show_emergency_view (data); + break; + case SYNC_ERROR_RESPONSE_SETTINGS_OPEN: + data->open_current = TRUE; + show_services_list (data, NULL); + break; + case SYNC_ERROR_RESPONSE_SETTINGS_SELECT: + show_services_list (data, NULL); + break; + default: + g_warn_if_reached (); + } +} + + +static void +new_device_clicked_cb (GtkButton *btn, app_data *data) +{ + DBusGProxy *proxy; + DBusGConnection *bus; + char *argv[2] = {"bluetooth-wizard", NULL}; + GError *error = NULL; + + switch (data->bluetooth_wizard) { + case SYNC_BLUETOOTH_MOBLIN: + + bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); + if (bus) { + proxy = dbus_g_proxy_new_for_name (bus, + "org.moblin.UX.Shell.Toolbar", + "/org/moblin/UX/Shell/Toolbar", + "org.moblin.UX.Shell.Toolbar"); + dbus_g_proxy_call_no_reply (proxy, "ShowPanel", + G_TYPE_STRING, "bluetooth-panel", + G_TYPE_INVALID, + G_TYPE_INVALID); + g_object_unref (proxy); + } + break; + + case SYNC_BLUETOOTH_GNOME: + if (!gdk_spawn_on_screen (gtk_window_get_screen (GTK_WINDOW (data->sync_win)), + NULL, + argv, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, + NULL, + NULL, + &error)) { + g_warning ("Failed to spawn bluetooth-wizard: %s", error->message); + g_error_free (error); + return; + } + break; + default: + ; + } +} + +static void +name_has_owner_cb (DBusGProxy *proxy, gboolean has_owner, + GError *error, app_data *data) +{ + if (has_owner) { + gtk_widget_show (data->new_device_btn); + data->bluetooth_wizard = SYNC_BLUETOOTH_MOBLIN; + } + g_object_unref (proxy); +} + +static void +init_bluetooth_ui (app_data *data) +{ + char *bt_wizard; + DBusGConnection *bus; + DBusGProxy *proxy; + + data->bluetooth_wizard = SYNC_BLUETOOTH_NONE; + + /* look for gnome bluetooth wizard first */ + bt_wizard = g_find_program_in_path ("bluetooth-wizard"); + if (bt_wizard) { + gtk_widget_show (data->new_device_btn); + data->bluetooth_wizard = SYNC_BLUETOOTH_GNOME; + g_free (bt_wizard); + } else { + /* try Moblin shell next (bluetooth panel integrates bt wizard) */ + bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); + proxy = dbus_g_proxy_new_for_name (bus, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); + if (proxy) { + org_freedesktop_DBus_name_has_owner_async (proxy, + "org.moblin.UX.Shell.Toolbar", + (org_freedesktop_DBus_name_has_owner_reply)name_has_owner_cb, + data); + } + } +} + + +static void +autosync_toggle_cb (GtkWidget *widget, gpointer x, app_data *data) +{ + if (data->current_service && data->current_service->config) { + gboolean new_active, old_active = FALSE; + char *autosync = NULL; + + new_active = toggle_get_active (widget); + syncevo_config_get_value (data->current_service->config, NULL, + "autoSync", &autosync); + old_active = (g_strcmp0 (autosync, "1") == 0); + + if (old_active != new_active) { + char *new_val; + operation_data *op_data; + + new_val = new_active ? "1": "0"; + syncevo_config_set_value (data->current_service->config, NULL, + "autoSync", new_val); + + op_data = g_slice_new (operation_data); + op_data->data = data; + op_data->operation = OP_SAVE; + op_data->started = FALSE; + syncevo_server_start_no_sync_session (data->server, + data->current_service->name, + (SyncevoServerStartSessionCb)start_session_cb, + op_data); + } + } +} + +static void +build_autosync_ui (app_data *data) +{ + char *txt; + + /* TRANSLATORS: label for checkbutton/toggle in main view. + * Please stick to similar length strings or break the line with + * "\n" if absolutely needed */ + txt = _("Automatic sync"); + +#ifdef USE_MOBLIN_UX + GtkWidget *lbl; + + lbl = gtk_label_new (txt); + gtk_widget_show (lbl); + gtk_box_pack_end (GTK_BOX (data->autosync_box), lbl, FALSE, FALSE, 0); + + data->autosync_toggle = mx_gtk_light_switch_new (); + gtk_widget_show (data->autosync_toggle); + gtk_box_pack_end (GTK_BOX (data->autosync_box), data->autosync_toggle, + FALSE, FALSE, 0); + g_signal_connect (data->autosync_toggle, "switch-flipped", + G_CALLBACK (autosync_toggle_cb), data); +#else + GtkWidget *align; + + align = gtk_alignment_new (0.5, 1.0, 1.0, 0.0); + gtk_widget_show (align); + gtk_box_pack_start (GTK_BOX (data->autosync_box), align, TRUE, TRUE, 0); + + data->autosync_toggle = gtk_check_button_new_with_label (txt); + gtk_container_add (GTK_CONTAINER (align), data->autosync_toggle); + g_signal_connect (data->autosync_toggle, "notify::active", + G_CALLBACK (autosync_toggle_cb), data); +#endif + gtk_widget_show (data->autosync_toggle); +} + +static void +glade_name_workaround (GtkBuilder *builder, const char *name) +{ + GtkWidget *w; + + w = GTK_WIDGET (gtk_builder_get_object (builder, name)); + if (w) { + gtk_widget_set_name (w, name); + } +} + +static gboolean +init_ui (app_data *data) +{ + GtkBuilder *builder; + GError *error = NULL; + GtkWidget *frame, * service_error_box, *btn; + GtkAdjustment *adj; + + 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: %s\n", + GLADEDIR "ui.xml", + error->message); + g_error_free (error); + g_object_unref (builder); + return FALSE; + } + + data->service_box = GTK_WIDGET (gtk_builder_get_object (builder, "service_box")); + service_error_box = GTK_WIDGET (gtk_builder_get_object (builder, "service_error_box")); + data->info_bar = gtk_info_bar_new (); + gtk_widget_set_no_show_all (data->info_bar, TRUE); + g_signal_connect (data->info_bar, "response", + G_CALLBACK (info_bar_response_cb), data); + gtk_box_pack_start (GTK_BOX (service_error_box), data->info_bar, + TRUE, TRUE, 16); + + data->no_connection_box = GTK_WIDGET (gtk_builder_get_object (builder, "no_connection_box")); + data->server_icon_box = GTK_WIDGET (gtk_builder_get_object (builder, "server_icon_box")); + + data->offline_label = GTK_WIDGET (gtk_builder_get_object (builder, "offline_label")); + data->progress = GTK_WIDGET (gtk_builder_get_object (builder, "progressbar")); + data->change_service_btn = GTK_WIDGET (gtk_builder_get_object (builder, "change_service_btn")); + data->emergency_btn = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_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->spinner_image = GTK_WIDGET (gtk_builder_get_object (builder, "spinner_image")); + gtk_image_set_from_file (GTK_IMAGE (data->spinner_image), THEMEDIR "sync-spinner.gif"); + gtk_widget_set_no_show_all (data->spinner_image, TRUE); + gtk_widget_hide (data->spinner_image); + + data->autosync_box = GTK_WIDGET (gtk_builder_get_object (builder, "autosync_box")); + build_autosync_ui (data); + + 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); + + /* service list view */ + data->scrolled_window = GTK_WIDGET (gtk_builder_get_object (builder, "scrolledwindow")); + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (data->scrolled_window)); + data->services_box = GTK_WIDGET (gtk_builder_get_object (builder, "services_box")); + gtk_container_set_focus_vadjustment (GTK_CONTAINER (data->services_box), adj); + g_signal_connect(data->services_box, "size-allocate", + G_CALLBACK (services_box_allocate_cb), data); + + data->devices_box = GTK_WIDGET (gtk_builder_get_object (builder, "devices_box")); + data->settings_close_btn = GTK_WIDGET (gtk_builder_get_object (builder, "settings_close_btn")); + + /* emergency view */ + btn = GTK_WIDGET (gtk_builder_get_object (builder, "slow_sync_btn")); + g_signal_connect (btn, "clicked", + G_CALLBACK (slow_sync_clicked_cb), data); + data->refresh_from_server_btn_label = GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_server_btn_label")); + g_signal_connect (GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_server_btn")), "clicked", + G_CALLBACK (refresh_from_server_clicked_cb), data); + data->refresh_from_client_btn_label = GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_client_btn_label")); + g_signal_connect (GTK_WIDGET (gtk_builder_get_object (builder, "refresh_from_client_btn")), "clicked", + G_CALLBACK (refresh_from_client_clicked_cb), data); + + data->emergency_label = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_label")); + data->emergency_expander = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_expander")); + data->emergency_source_table = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_source_table")); + data->emergency_backup_table = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_backup_table")); + data->emergency_close_btn = GTK_WIDGET (gtk_builder_get_object (builder, "emergency_close_btn")); + + /* No (documented) way to add own widgets to gtkbuilder it seems... + swap the all dummy widgets with Muxwidgets */ + setup_windows (data, + GTK_WIDGET (gtk_builder_get_object (builder, "sync_win")), + GTK_WIDGET (gtk_builder_get_object (builder, "services_win")), + GTK_WIDGET (gtk_builder_get_object (builder, "emergency_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"))); + frame = switch_dummy_to_mux_frame (GTK_WIDGET (gtk_builder_get_object (builder, "emergency_frame"))); + + g_signal_connect (data->sync_win, "destroy", + G_CALLBACK (gtk_main_quit), NULL); + g_signal_connect (data->change_service_btn, "clicked", + G_CALLBACK (change_service_clicked_cb), data); + g_signal_connect (data->emergency_btn, "clicked", + G_CALLBACK (emergency_clicked_cb), data); + g_signal_connect (data->sync_btn, "clicked", + G_CALLBACK (sync_clicked_cb), data); + g_signal_connect_swapped (data->emergency_close_btn, "clicked", + G_CALLBACK (show_main_view), data); + g_signal_connect_swapped (data->settings_close_btn, "clicked", + G_CALLBACK (show_main_view), data); + g_signal_connect (data->emergency_btn, "clicked", + G_CALLBACK (emergency_clicked_cb), data); + + data->new_device_btn = GTK_WIDGET (gtk_builder_get_object (builder, "new_device_btn")); + g_signal_connect (data->new_device_btn, "clicked", + G_CALLBACK (new_device_clicked_cb), data); + + /* workarounds for glade not working with gtkbuilder >= 2.20: + * widgets do not get names. */ + glade_name_workaround (builder, "meego_win"); + glade_name_workaround (builder, "sync_data_and_type_box"); + glade_name_workaround (builder, "log_frame"); + glade_name_workaround (builder, "backup_frame"); + glade_name_workaround (builder, "services_frame"); + glade_name_workaround (builder, "sync_service_label"); + glade_name_workaround (builder, "sync_status_label"); + glade_name_workaround (builder, "no_server_label"); + glade_name_workaround (builder, "sync_failure_label"); + glade_name_workaround (builder, "sync_btn"); + + init_bluetooth_ui (data); + + g_object_unref (builder); + + return 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); + gtk_widget_set_size_request (image, icon_size, icon_size); + g_object_unref (pixbuf); + gtk_container_foreach (GTK_CONTAINER(icon_box), + (GtkCallback)remove_child, + icon_box); + gtk_box_pack_start (icon_box, image, FALSE, FALSE, 0); + gtk_widget_show (image); +} + +static void +emergency_toggle_notify_active_cb (GtkWidget *widget, + gpointer p, + app_data *data) +{ + gboolean active; + char *source; + + active = toggle_get_active (widget); + source = g_object_get_data (G_OBJECT (widget), "source"); + + g_return_if_fail (source); + + if (active) { + g_hash_table_insert (data->emergency_sources, g_strdup (source), ""); + } else { + g_hash_table_remove (data->emergency_sources, source); + } + update_emergency_expander (data); +} + +static GtkWidget* +add_emergency_toggle_widget (app_data *data, + const char *title, + gboolean active, + guint row, guint col) +{ + GtkWidget *toggle; + +#ifdef USE_MOBLIN_UX + GtkWidget *label; + col = col * 2; + label = gtk_label_new (title); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (data->emergency_source_table), label, + col, col + 1, row, row + 1, + GTK_FILL, GTK_FILL, 16, 0); + toggle = mx_gtk_light_switch_new (); + toggle_set_active (toggle, active); + g_signal_connect (toggle, "switch-flipped", + G_CALLBACK (emergency_toggle_notify_active_cb), data); +#else + toggle = gtk_check_button_new_with_label (title); + toggle_set_active (toggle, active); + g_signal_connect (toggle, "notify::active", + G_CALLBACK (emergency_toggle_notify_active_cb), data); +#endif + gtk_widget_show (toggle); + gtk_table_attach (GTK_TABLE (data->emergency_source_table), toggle, + col + 1, col + 2, row, row + 1, + GTK_FILL, GTK_FILL, 0, 0); + return toggle; +} + +static void +update_emergency_expander (app_data *data) +{ + char *text, *sources = NULL; + GHashTableIter iter; + char *name; + + g_hash_table_iter_init (&iter, data->emergency_sources); + while (g_hash_table_iter_next (&iter, (gpointer)&name, NULL)) { + char *pretty, *tmp; + pretty = get_pretty_source_name (name); + if (sources) { + tmp = g_strdup_printf ("%s, %s", sources, pretty); + g_free (sources); + g_free (pretty); + sources = tmp; + } else { + sources = pretty; + } + } + if (sources) { + /* This is the expander label in emergency view. It summarizes the + * currently selected data sources. First placeholder is service/device + * name, second a comma separeted list of sources. + * E.g. "Affected data: Google Contacts, Appointments" */ + text = g_strdup_printf (_("Affected data: %s %s"), + data->current_service->pretty_name, + sources); + g_free (sources); + } else { + text = g_strdup_printf (_("Affected data: none")); + } + + gtk_expander_set_label (GTK_EXPANDER (data->emergency_expander), text); + g_free (text); +} + +static void +add_emergency_source (const char *name, GHashTable *source, app_data *data) +{ + source_config *conf; + GtkWidget *toggle; + guint rows, cols; + guint row; + static guint col; + gboolean active = TRUE; + char *pretty_name; + + conf = g_hash_table_lookup (data->current_service->source_configs, + name); + g_object_get (data->emergency_source_table, + "n-rows", &rows, + "n-columns", &cols, + NULL); + if (cols != 1 && col == 0){ + col = 1; + row = rows - 1; + } else { + col = 0; + row = rows; + } + + active = (g_hash_table_lookup (data->emergency_sources, name) != NULL); + + pretty_name = get_pretty_source_name (name); + toggle = add_emergency_toggle_widget (data, pretty_name, active, row, col); + gtk_widget_set_sensitive (toggle, source_config_is_usable (conf)); + g_object_set_data_full (G_OBJECT (toggle), "source", g_strdup (name), g_free); + g_free (pretty_name); +} + +static void +update_backup_visibilities (app_data *data) +{ + char *key; + GHashTableIter iter; + GList *l, *widgets; + + widgets = gtk_container_get_children ( + GTK_CONTAINER (data->emergency_backup_table)); + gtk_widget_show_all (data->emergency_backup_table); + + /* hide backup widgets that do not contain selected sources */ + g_hash_table_iter_init (&iter, data->emergency_sources); + while (g_hash_table_iter_next (&iter, (gpointer)&key, NULL)) { + for (l = widgets; l; l = l->next) { + if (!g_object_get_data (G_OBJECT (l->data), key)) { + gtk_widget_hide (GTK_WIDGET (l->data)); + } + } + } + + g_list_free (widgets); +} + +static void +restore_clicked_cb (GtkButton *btn, app_data *data) +{ + const char *dir, *time_str; + operation_data *op_data; + char *message; + + dir = g_object_get_data (G_OBJECT (btn), "dir"); + time_str = g_object_get_data (G_OBJECT (btn), "time"); + g_return_if_fail (dir && time_str); + + /* TRANSLATORS: confirmation for restoring a backup. placeholder is the + * backup time string defined below */ + message = g_strdup_printf (_("Do you want to restore the backup from %s? " + "All changes you have made since then will be lost."), + time_str); + if (!show_confirmation (data->sync_win, message, _("Yes, restore"), _("No"))) { + g_free (message); + return; + } + g_free (message); + + op_data = g_slice_new (operation_data); + op_data->data = data; + op_data->operation = OP_RESTORE; + op_data->dir = dir; + op_data->started = FALSE; + syncevo_server_start_session (data->server, + data->current_service->name, + (SyncevoServerStartSessionCb)start_session_cb, + op_data); + + show_main_view (data); +} + +static void +add_backup (app_data *data, const char *peername, const char *dir, + long endtime, GList *sources) +{ + GtkWidget *timelabel, *label, *blabel, *button, *box;; + guint rows; + char *text; + char time_str[60]; + struct tm *tim; + + tim = localtime (&endtime); + /* TRANSLATORS: date/time for strftime(), used in emergency view backup + * label. Any time format that shows date and time is good. */ + strftime (time_str, sizeof (time_str), _("%x %X"), tim); + + g_object_get (data->emergency_backup_table, + "n-rows", &rows, + NULL); + + box = gtk_vbox_new (TRUE, 6); + gtk_table_attach (GTK_TABLE (data->emergency_backup_table), box, + 0, 1, rows, rows + 1, + GTK_EXPAND|GTK_FILL, GTK_FILL, 16, 0); + + timelabel = gtk_label_new (time_str); + gtk_misc_set_alignment (GTK_MISC (timelabel), 0.0, 0.5); + gtk_label_set_line_wrap (GTK_LABEL (timelabel), TRUE); + gtk_widget_set_size_request (timelabel, 600, -1); + gtk_box_pack_start (GTK_BOX (box), timelabel, TRUE, TRUE, 0); + + /* TRANSLATORS: label for a backup in emergency view. Placeholder is + * service or device name */ + text = g_strdup_printf (_("Backed up before syncing with %s"), peername); + label = gtk_label_new (text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_widget_set_size_request (label, 600, -1); + gtk_box_pack_start (GTK_BOX (box), label, TRUE, TRUE, 0); + g_free (text); + + button = gtk_button_new (); + gtk_table_attach (GTK_TABLE (data->emergency_backup_table), button, + 1, 2, rows, rows + 1, + GTK_FILL, GTK_SHRINK, 32, 0); + g_object_set_data_full (G_OBJECT (button), "dir", g_strdup(dir), g_free); + g_object_set_data_full (G_OBJECT (button), "time", g_strdup(time_str), g_free); + g_signal_connect (button, "clicked", + G_CALLBACK (restore_clicked_cb), data); + + blabel = gtk_label_new (_("Restore")); + gtk_misc_set_padding (GTK_MISC (blabel), 32, 0); + gtk_container_add (GTK_CONTAINER (button), blabel); + + for (; sources; sources = sources->next) { + g_object_set_data (G_OBJECT (box), (char *)sources->data, ""); + g_object_set_data (G_OBJECT (button), (char *)sources->data, ""); + } +} + +static void +get_reports_for_backups_cb (SyncevoServer *server, + SyncevoReports *reports, + GError *error, + app_data *data) +{ + guint len, i; + + if (error) { + g_warning ("Error in Session.GetReports: %s", error->message); + g_error_free (error); + /* non-fatal, unknown error */ + return; + } + + len = syncevo_reports_get_length (reports); + for (i = 0; i < len; i++) { + GHashTable *report = syncevo_reports_index (reports, i); + GHashTableIter iter; + char *key, *val; + long status = -1; + long endtime = -1; + char *peername = NULL; + char *dir = NULL; + GList *backup_sources = NULL; + + g_hash_table_iter_init (&iter, report); + while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&val)) { + char **strs; + + strs = g_strsplit (key, "-", 6); + if (!strs) { + continue; + } + + if (g_strcmp0 (strs[0], "source") == 0 && + g_strcmp0 (strs[2], "backup") == 0 && + g_strcmp0 (strs[3], "before") == 0) { + backup_sources = g_list_prepend (backup_sources, + g_strdup (strs[1])); + } else if (g_strcmp0 (strs[0], "end") == 0) { + endtime = strtol (val, NULL, 10); + } else if (g_strcmp0 (strs[0], "status") == 0) { + status = strtol (val, NULL, 10); + } else if (g_strcmp0 (strs[0], "peer") == 0) { + peername = val; + } else if (g_strcmp0 (strs[0], "dir") == 0) { + dir = val; + } + g_strfreev (strs); + } + + if (peername && dir && endtime > 0) { + add_backup (data, peername, dir, endtime, backup_sources); + } + g_list_foreach (backup_sources, (GFunc)g_free, NULL); + g_list_free (backup_sources); + } + + data->backup_count += len; + if (len == REPORTS_PER_CALL) { + syncevo_server_get_reports (data->server, + "", + data->backup_count, REPORTS_PER_CALL, + (SyncevoServerGetReportsCb)get_reports_for_backups_cb, + data); + } + + update_backup_visibilities (data); +} + +static const char* +get_syncevo_context (const char *config_name) +{ + char *context; + + context = g_strrstr (config_name, "@"); + if (!context) { + context = ""; + } + return context; +} + +static void +update_emergency_view (app_data *data) +{ + char *text; + + if (!data->current_service) { + g_warning ("no service defined in Emergency view"); + return; + } + + if (data->forced_emergency) { + text = g_strdup_printf ( + /* TRANSLATORS: this is an explanation in Emergency view. + * Placeholder is a service/device name */ + _("A normal sync with %s is not possible at this time. " + "You can do a slow two-way sync or start from scratch. You " + "can also restore a backup, but a slow sync or starting from " + "scratch will still be required before normal sync is " + "possible."), + data->current_service->pretty_name); + } else { + /* TRANSLATORS: this is an explanation in Emergency view. + * Placeholder is a service/device name */ + text = g_strdup_printf ( + _("If something has gone horribly wrong, you can try a " + "slow sync, start from scratch or restore from backup.")); + } + gtk_label_set_text (GTK_LABEL (data->emergency_label), text); + g_free (text); + + /* TRANSLATORS: These are a buttons in Emergency view. Placeholder is a + * service/device name. Please don't use too long lines, but feel free to + * use several lines. */ + text = g_strdup_printf (_("Delete all your local\n" + "data and replace with\n" + "data from %s"), + data->current_service->pretty_name); + gtk_label_set_text (GTK_LABEL (data->refresh_from_server_btn_label), text); + g_free (text); + text = g_strdup_printf (_("Delete all data on\n" + "%s and replace\n" + "with your local data"), + data->current_service->pretty_name); + gtk_label_set_text (GTK_LABEL (data->refresh_from_client_btn_label), text); + g_free (text); + + gtk_container_foreach (GTK_CONTAINER (data->emergency_source_table), + (GtkCallback)remove_child, + data->emergency_source_table); + gtk_table_resize (GTK_TABLE (data->emergency_source_table), 1, 1); + + /* using this instead of current_service->source_configs + * to get the same order as the configuration has... */ + syncevo_config_foreach_source (data->current_service->config, + (ConfigFunc)add_emergency_source, + data); + update_emergency_expander (data); + + data->backup_count = 0; + gtk_container_foreach (GTK_CONTAINER (data->emergency_backup_table), + (GtkCallback)remove_child, + data->emergency_backup_table); + gtk_table_resize (GTK_TABLE (data->emergency_backup_table), 1, 1); + syncevo_server_get_reports (data->server, + get_syncevo_context (data->current_service->name), + 0, REPORTS_PER_CALL, + (SyncevoServerGetReportsCb)get_reports_for_backups_cb, + data); + +} + +static void +update_service_source_ui (const char *name, source_config *conf, app_data *data) +{ + GtkWidget *lbl, *box; + char *pretty_name, *title; + + if (!source_config_is_usable (conf)) { + return; + } + + conf->box = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (data->sources_box), conf->box, + FALSE, FALSE, 8); + + pretty_name = get_pretty_source_name_markup (name); + title = g_strdup_printf ("<b>%s</b>", pretty_name); + lbl = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (lbl), title); + g_free (pretty_name); + g_free (title); + gtk_misc_set_alignment (GTK_MISC (lbl), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (conf->box), lbl, TRUE, TRUE, 0); + + box = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (conf->box), box, FALSE, FALSE, 0); + + conf->info_bar = gtk_info_bar_new (); + gtk_box_pack_start (GTK_BOX (box), conf->info_bar, TRUE, TRUE, 16); + gtk_widget_set_no_show_all (conf->info_bar, TRUE); + g_signal_connect (conf->info_bar, "response", + G_CALLBACK (info_bar_response_cb), data); + + conf->label = gtk_label_new (NULL); + gtk_misc_set_alignment (GTK_MISC (conf->label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (conf->box), conf->label, TRUE, TRUE, 0); + + source_config_update_widget (conf); + + gtk_widget_show_all (conf->box); +} + +static void +check_source_cb (SyncevoSession *session, + GError *error, + source_config *source) +{ + if (error) { + if(error->code == DBUS_GERROR_REMOTE_EXCEPTION && + dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_SOURCE_UNUSABLE)) { + /* source is not supported locally */ + if (source) { + source->supported_locally = FALSE; + if (source->box) { + /* widget is already on screen, hide it */ + gtk_widget_hide (source->box); + } + } + } else { + g_warning ("CheckSource failed: %s", error->message); + /* non-fatal, unknown error */ + } + + g_error_free (error); + return; + } +} + +static void +update_service_ui (app_data *data) +{ + char *icon_uri = NULL; + char *autosync = NULL; + + gtk_container_foreach (GTK_CONTAINER (data->sources_box), + (GtkCallback)remove_child, + data->sources_box); + + if (data->current_service && data->current_service->config) { + syncevo_config_get_value (data->current_service->config, + NULL, "IconURI", &icon_uri); + syncevo_config_get_value (data->current_service->config, + NULL, "autoSync", &autosync); + + g_hash_table_foreach (data->current_service->source_configs, + (GHFunc)update_service_source_ui, + data); + } + load_icon (icon_uri, + GTK_BOX (data->server_icon_box), + SYNC_UI_ICON_SIZE); + + toggle_set_active (data->autosync_toggle, + g_strcmp0 (autosync, "1") == 0); + + refresh_last_synced_label (data); + + gtk_widget_show_all (data->sources_box); +} + +static void +unexpand_config_widget (GtkWidget *w, GtkWidget *exception) +{ + if (SYNC_IS_CONFIG_WIDGET (w) && exception && exception != w) { + sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (w), FALSE); + } +} + +static void +config_widget_expanded_cb (GtkWidget *widget, GParamSpec *pspec, app_data *data) +{ + if (sync_config_widget_get_expanded (SYNC_CONFIG_WIDGET (widget))) { + data->expanded_config = widget; + gtk_container_foreach (GTK_CONTAINER (data->services_box), + (GtkCallback)unexpand_config_widget, + widget); + } +} + +static void +config_widget_changed_cb (GtkWidget *widget, app_data *data) +{ + if (sync_config_widget_get_current (SYNC_CONFIG_WIDGET (widget))) { + const char *name = NULL; + name = sync_config_widget_get_name (SYNC_CONFIG_WIDGET (widget)); + reload_config (data, name); + show_main_view (data); + } else { + reload_config (data, NULL); + update_services_list (data); + } +} + +static SyncConfigWidget* +add_configuration_to_box (GtkBox *box, + SyncevoConfig *config, + const char *name, + gboolean has_template, + gboolean has_configuration, + app_data *data) +{ + GtkWidget *item = NULL; + gboolean current = FALSE; + const char *current_name = NULL; + + if (data->current_service) { + current_name = data->current_service->pretty_name; + if (data->current_service->name && name && + g_strcasecmp (name, data->current_service->name) == 0) { + current = TRUE; + } + } + + item = sync_config_widget_new (data->server, name, + config, + current, current_name, + has_configuration, has_template); + g_signal_connect (item, "changed", + G_CALLBACK (config_widget_changed_cb), data); + g_signal_connect (item, "notify::expanded", + G_CALLBACK (config_widget_expanded_cb), data); + gtk_widget_show (item); + gtk_box_pack_start (box, item, FALSE, FALSE, 0); + + if (current) { + sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (item), + data->open_current); + } + if (g_strcmp0 (name, "default") == 0) { + sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (item), + TRUE); + } + + if (data->config_id_to_open) { + sync_config_widget_expand_id (SYNC_CONFIG_WIDGET (item), + data->config_id_to_open); + } + + return SYNC_CONFIG_WIDGET (item); + +} + +static void +find_new_service_config (SyncConfigWidget *w, GtkWidget **found) +{ + if (SYNC_IS_CONFIG_WIDGET (w)) { + if (!sync_config_widget_get_configured (w) && + !sync_config_widget_get_has_template (w)) { + *found = GTK_WIDGET (w); + } + } +} + +typedef struct config_data { + app_data *data; + char *name; + gboolean has_configuration; + gboolean has_template; + GHashTable *device_templates; + +} config_data; + +#define LEGAL_CONFIG_NAME_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVXYZ1234567890-_" + +static void +get_config_for_config_widget_cb (SyncevoServer *server, + SyncevoConfig *config, + GError *error, + config_data *c_data) +{ + char *ready, *is_peer, *url, *type; + + c_data->data->service_list_updates_left--; + + if (error) { + /* show in UI? */ + g_warning ("Server.GetConfig() failed: %s", error->message); + g_error_free (error); + return; + } + + syncevo_config_get_value (config, NULL, "ConsumerReady", &ready); + syncevo_config_get_value (config, NULL, "PeerIsClient", &is_peer); + syncevo_config_get_value (config, NULL, "syncURL", &url); + syncevo_config_get_value (config, NULL, "peerType", &type); + + + if (g_strcmp0 ("1", ready) != 0 || + (type && g_strcmp0 ("WebDAV", type) == 0) || + (url && g_str_has_prefix (url, "local://@"))) { + + /* Ignore existing configs and templates unless they are + explicitly marked as "ConsumerReady. + Also ignore webdav (and the local syncs used for webdav) + for now */ + } else if (is_peer && g_strcmp0 ("1", is_peer) == 0) { + if (url) { + SyncConfigWidget *w; + char *fp, *tmp, *template_name, *device_name = NULL; + char **fpv = NULL; + + syncevo_config_get_value (config, NULL, "deviceName", &tmp); + if (!tmp) { + device_name = g_strdup (c_data->name); + } else { + device_name = g_strcanon (g_strdup (tmp), LEGAL_CONFIG_NAME_CHARS, '-'); + } + + + syncevo_config_get_value (config, NULL, "templateName", &template_name); + if (!template_name) { + syncevo_config_get_value (config, NULL, "fingerPrint", &fp); + if (fp) { + fpv = g_strsplit_set (fp, ",;", 2); + if (g_strv_length (fpv) > 0) { + template_name = fpv[0]; + } + } + } + + /* keep a list of added devices */ + w = g_hash_table_lookup (c_data->device_templates, url); + if (!w) { + w = add_configuration_to_box (GTK_BOX (c_data->data->devices_box), + config, + device_name, + c_data->has_template, + c_data->has_configuration, + c_data->data); + g_hash_table_insert (c_data->device_templates, url, w); + sync_config_widget_add_alternative_config (w, template_name, config, + c_data->has_configuration); + } else { + /* TODO: might want to add a new widget, if user has created more + * configs for same device: this really requires us to look at + * all configs / templates, then decide what to sho w*/ + + /* there is a widget for this device already, add this info there*/ + sync_config_widget_add_alternative_config (w, template_name, config, + c_data->has_configuration); + } + g_free (device_name); + g_strfreev (fpv); + } + } else { + add_configuration_to_box (GTK_BOX (c_data->data->services_box), + config, + c_data->name, + c_data->has_template, + c_data->has_configuration, + c_data->data); + } + + g_free (c_data->name); + g_hash_table_unref (c_data->device_templates); + g_slice_free (config_data, c_data); +} + +static void +get_config_for_config_widget (app_data *data, + const char *config, + gboolean has_template, + gboolean has_configuration, + GHashTable *device_templates) + +{ + config_data *c_data; + + data->service_list_updates_left++; + + c_data = g_slice_new0 (config_data); + c_data->data = data; + c_data->name = g_strdup (config); + c_data->has_template = has_template; + c_data->has_configuration = has_configuration; + if (device_templates) { + c_data->device_templates = g_hash_table_ref (device_templates); + } + + syncevo_server_get_config (data->server, + config, + !has_configuration, + (SyncevoServerGetConfigCb)get_config_for_config_widget_cb, + c_data); +} + +static void +setup_new_service_clicked (GtkButton *btn, app_data *data) +{ + GtkWidget *widget = NULL; + + gtk_container_foreach (GTK_CONTAINER (data->services_box), + (GtkCallback)unexpand_config_widget, + NULL); + + /* if a new service config has already been added, use that. + * Otherwise add one. */ + gtk_container_foreach (GTK_CONTAINER (data->services_box), + (GtkCallback)find_new_service_config, + &widget); + if (!widget) { + get_config_for_config_widget (data, "default", TRUE, FALSE, NULL); + } else { + sync_config_widget_set_expanded (SYNC_CONFIG_WIDGET (widget), TRUE); + } +} + +typedef struct templates_data { + app_data *data; + char **templates; +} templates_data; + +static void +get_configs_cb (SyncevoServer *server, + char **configs, + GError *error, + templates_data *templ_data) +{ + char **config_iter, **template_iter, **templates; + app_data *data; + GHashTable *device_templates; + + templ_data->data->service_list_updates_left = 0; + + templates = templ_data->templates; + data = templ_data->data; + g_slice_free (templates_data, templ_data); + + if (error) { + show_main_view (data); + + g_warning ("Server.GetConfigs() failed: %s", error->message); + g_strfreev (templates); + g_error_free (error); + return; + } + + device_templates = g_hash_table_new (g_str_hash, g_str_equal); + + for (template_iter = templates; *template_iter; template_iter++){ + gboolean found_config = FALSE; + + for (config_iter = configs; *config_iter; config_iter++) { + if (*template_iter && *config_iter && + g_ascii_strncasecmp (*template_iter, + *config_iter, + strlen (*config_iter)) == 0) { + /* have template and config */ + get_config_for_config_widget (data, *config_iter, + TRUE, TRUE, device_templates); + found_config = TRUE; + break; + } + } + if (!found_config) { + /* have template, no config */ + get_config_for_config_widget (data, *template_iter, + TRUE, FALSE, device_templates); + } + } + + for (config_iter = configs; *config_iter; config_iter++) { + gboolean found_template = FALSE; + + for (template_iter = templates; *template_iter; template_iter++) { + if (*template_iter && + *config_iter && + g_ascii_strncasecmp (*template_iter, + *config_iter, + strlen (*config_iter)) == 0) { + + found_template = TRUE; + break; + } + } + if (!found_template) { + /* have config, no template */ + get_config_for_config_widget (data, *config_iter, + FALSE, TRUE, device_templates); + } + } + + /* config initialization might ref/unref as well... */ + g_hash_table_unref (device_templates); + g_strfreev (configs); + g_strfreev (templates); +} + +static void +get_template_configs_cb (SyncevoServer *server, + char **templates, + GError *error, + app_data *data) +{ + templates_data *templ_data; + + if (error) { + data->service_list_updates_left = 0; + show_main_view (data); + + show_error_dialog (data->sync_win, + _("Failed to get list of supported services from SyncEvolution")); + g_warning ("Server.GetConfigs() failed: %s", error->message); + g_error_free (error); + return; + } + + templ_data = g_slice_new (templates_data); + templ_data->data = data; + templ_data->templates = templates; + + syncevo_server_get_configs (data->server, + FALSE, + (SyncevoServerGetConfigsCb)get_configs_cb, + templ_data); +} + +static void +update_services_list (app_data *data) +{ + if (data->service_list_updates_left > 0) { + return; + } + + gtk_container_foreach (GTK_CONTAINER (data->services_box), + (GtkCallback)remove_child, + data->services_box); + gtk_container_foreach (GTK_CONTAINER (data->devices_box), + (GtkCallback)remove_child, + data->devices_box); + + /* set temp number before we know the real one */ + data->service_list_updates_left = 1; + syncevo_server_get_configs (data->server, + TRUE, + (SyncevoServerGetConfigsCb)get_template_configs_cb, + data); +} + +static void +get_config_for_main_win_cb (SyncevoServer *server, + SyncevoConfig *config, + GError *error, + app_data *data) +{ + if (error) { + if (error->code == DBUS_GERROR_REMOTE_EXCEPTION && + dbus_g_error_has_name (error, SYNCEVO_DBUS_ERROR_NO_SUCH_CONFIG)) { + /* another syncevolution client probably removed the config */ + reload_config (data, NULL); + } else { + g_warning ("Error in Server.GetConfig: %s", error->message); + /* TRANSLATORS: message in main view */ + set_info_bar (data->info_bar, GTK_MESSAGE_ERROR, + SYNC_ERROR_RESPONSE_NONE, + _("There was a problem communicating with the " + "sync process. Please try again later.")); + set_app_state (data, SYNC_UI_STATE_SERVER_FAILURE); + } + g_error_free (error); + + return; + } + + if (config) { + GHashTableIter iter; + char *name; + source_config *source; + + server_config_init (data->current_service, config); + set_app_state (data, SYNC_UI_STATE_SERVER_OK); + + /* get "locally supported" status for all sources */ + g_hash_table_iter_init (&iter, data->current_service->source_configs); + while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)&source)) { + + syncevo_server_check_source (data->server, + data->current_service->name, + name, + (SyncevoServerGenericCb)check_source_cb, + source); + } + + syncevo_server_get_presence (server, + data->current_service->name, + (SyncevoServerGetPresenceCb)get_presence_cb, + data); + + syncevo_server_get_reports (server, + data->current_service->name, + 0, 1, + (SyncevoServerGetReportsCb)get_reports_cb, + data); + + update_service_ui (data); + + } +} + +static void +set_running_session_status (app_data *data, + SyncevoSessionStatus status, + int error_code) +{ + if (status & SYNCEVO_STATUS_QUEUEING) { + g_warning ("Running session is queued, this shouldn't happen..."); + } else if (status & SYNCEVO_STATUS_IDLE) { + set_app_state (data, SYNC_UI_STATE_SERVER_OK); + } else if (status & SYNCEVO_STATUS_DONE) { + char *err; + err = get_error_string_for_code (error_code, NULL); + if (err) { + if (data->current_operation == OP_RESTORE) { + gtk_label_set_text (GTK_LABEL (data->sync_status_label), + _("Restore failed")); + } else { + gtk_label_set_text (GTK_LABEL (data->sync_status_label), + _("Sync failed")); + } + g_free (err); + } else { + if (data->current_operation == OP_RESTORE) { + gtk_label_set_text (GTK_LABEL (data->sync_status_label), + _("Restore complete")); + } else { + gtk_label_set_text (GTK_LABEL (data->sync_status_label), + _("Sync complete")); + } + } + set_app_state (data, SYNC_UI_STATE_SERVER_OK); + set_sync_progress (data, 1.0, ""); + } else if (status & SYNCEVO_STATUS_RUNNING || + status & SYNCEVO_STATUS_SUSPENDING || + status & SYNCEVO_STATUS_ABORTING) { + set_app_state (data, SYNC_UI_STATE_SYNCING); + } + + if (status & SYNCEVO_STATUS_WAITING) { + gtk_widget_show (data->spinner_image); + } else { + gtk_widget_hide (data->spinner_image); + } +} + +static void +running_session_status_changed_cb (SyncevoSession *session, + SyncevoSessionStatus status, + guint error_code, + SyncevoSourceStatuses *source_statuses, + app_data *data) +{ + set_running_session_status (data, status, error_code); +} + +static void +get_running_session_status_cb (SyncevoSession *session, + SyncevoSessionStatus status, + guint error_code, + SyncevoSourceStatuses *source_statuses, + GError *error, + app_data *data) +{ + if (error) { + g_warning ("Error in Session.GetStatus: %s", error->message); + g_error_free (error); + /* non-fatal, unknown error */ + return; + } + + set_running_session_status (data, status, error_code); +} + +typedef struct source_progress_data { + app_data *data; + SyncevoSourcePhase phase; + const char *source; +} source_progress_data; + +static void +find_updated_source_progress (const char *name, + SyncevoSourcePhase phase, + source_progress_data *prog_data) +{ + GHashTable *configs = prog_data->data->current_service->source_configs; + source_config *config; + config = g_hash_table_lookup (configs, name); + if (config) { + if (phase != config->phase) { + config->phase = phase; + prog_data->phase = config->phase; + prog_data->source = name; + } + } +} + +static void +running_session_progress_changed_cb (SyncevoSession *session, + int progress, + SyncevoSourceProgresses *source_progresses, + app_data *data) +{ + source_progress_data *prog_data = g_slice_new0 (source_progress_data); + prog_data->data = data; + prog_data->phase = SYNCEVO_PHASE_NONE; + prog_data->source = NULL; + + syncevo_source_progresses_foreach (source_progresses, + (SourceProgressFunc)find_updated_source_progress, + prog_data); + if (!prog_data->source) { + set_sync_progress (data, ((float)progress) / 100, NULL); + } else { + char *name; + char *msg = NULL; + + name = get_pretty_source_name (prog_data->source); + switch (prog_data->phase) { + case SYNCEVO_PHASE_PREPARING: + msg = g_strdup_printf (_("Preparing '%s'"), name); + break; + case SYNCEVO_PHASE_RECEIVING: + msg = g_strdup_printf (_("Receiving '%s'"), name); + break; + case SYNCEVO_PHASE_SENDING: + msg = g_strdup_printf (_("Sending '%s'"), name); + break; + default: + ; + } + + if (msg) { + set_sync_progress (data, ((float)progress) / 100, msg); + } + g_free (msg); + g_free (name); + + } + + g_slice_free (source_progress_data, prog_data); +} + +typedef struct source_stats { + long status; + long mode; + + long local_changes; + long remote_changes; + long local_rejections; + long remote_rejections; +} source_stats; + +static void +free_source_stats (source_stats *stats) +{ + g_slice_free (source_stats, stats); +} + +static gboolean +handle_source_report_item (char **strs, const char *value, GHashTable *sources) +{ + source_stats *stats; + char **tmp; + char *name; + + if (g_strv_length (strs) < 3) { + return FALSE; + } + + /* replace '__' with '_' and '_+' with '-' */ + tmp = g_strsplit (strs[1], "__", 0); + name = g_strjoinv ("_", tmp); + g_strfreev (tmp); + tmp = g_strsplit (name, "_+", 0); + g_free (name); + name = g_strjoinv ("-", tmp); + g_strfreev (tmp); + + stats = g_hash_table_lookup (sources, name); + if (!stats) { + stats = g_slice_new0 (source_stats); + g_hash_table_insert (sources, g_strdup (name), stats); + } + g_free (name); + + if (strcmp (strs[2], "stat") == 0) { + if (g_strv_length (strs) != 6) { + return FALSE; + } + + if (strcmp (strs[3], "remote") == 0) { + if (strcmp (strs[4], "added") == 0 || + strcmp (strs[4], "updated") == 0 || + strcmp (strs[4], "removed") == 0) { + stats->remote_changes += strtol (value, NULL, 10); + } else if (strcmp (strs[5], "reject") == 0) { + stats->remote_rejections += strtol (value, NULL, 10); + } + + } else if (strcmp (strs[3], "local") == 0) { + if (strcmp (strs[4], "added") == 0 || + strcmp (strs[4], "updated") == 0 || + strcmp (strs[4], "removed") == 0) { + stats->local_changes += strtol (value, NULL, 10); + } else if (strcmp (strs[5], "reject") == 0) { + stats->local_rejections += strtol (value, NULL, 10); + } + } else { + return FALSE; + } + } else if (strcmp (strs[2], "mode") == 0) { + stats->mode = strtol (value, NULL, 10); + } else if (strcmp (strs[2], "status") == 0) { + stats->status = strtol (value, NULL, 10); + } else if (strcmp (strs[2], "resume") == 0) { + } else if (strcmp (strs[2], "first") == 0) { + } else if (strcmp (strs[2], "backup") == 0) { + if (g_strv_length (strs) != 4) { + return FALSE; + } + if (strcmp (strs[3], "before") == 0) { + } else if (strcmp (strs[3], "after") == 0) { + } else { + return FALSE; + } + } else { + return FALSE; + } + + return TRUE; +} + +static char* +get_report_summary (source_config *source) +{ + char *rejects = NULL; + char *changes = NULL; + char *msg = NULL; + + if (!source->stats_set) { + return g_strdup (""); + } + + if (source->local_rejections + source->remote_rejections == 0) { + rejects = NULL; + } else if (source->local_rejections == 0) { + rejects = g_strdup_printf (ngettext ("There was one remote rejection.", + "There were %ld remote rejections.", + source->remote_rejections), + source->remote_rejections); + } else if (source->remote_rejections == 0) { + rejects = g_strdup_printf (ngettext ("There was one local rejection.", + "There were %ld local rejections.", + source->local_rejections), + source->local_rejections); + } else { + rejects = g_strdup_printf (_ ("There were %ld local rejections and %ld remote rejections."), + source->local_rejections, source->remote_rejections); + } + + if (source->local_changes + source->remote_changes == 0) { + changes = g_strdup_printf (_("Last time: No changes.")); + } else if (source->local_changes == 0) { + changes = g_strdup_printf (ngettext ("Last time: Sent one change.", + "Last time: Sent %ld changes.", + source->remote_changes), + source->remote_changes); + } else if (source->remote_changes == 0) { + // This is about changes made to the local data. Not all of these + // changes were requested by the remote server, so "applied" + // is a better word than "received" (bug #5185). + changes = g_strdup_printf (ngettext ("Last time: Applied one change.", + "Last time: Applied %ld changes.", + source->local_changes), + source->local_changes); + } else { + changes = g_strdup_printf (_("Last time: Applied %ld changes and sent %ld changes."), + source->local_changes, source->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; +} + +/* return TRUE if no errors are shown */ +static gboolean +source_config_update_widget (source_config *source) +{ + char *msg; + gboolean show_error; + SyncErrorResponse response; + + if (!source->label) { + return TRUE; + } + + msg = get_error_string_for_code (source->status, &response); + if (msg) { + show_error = TRUE; + set_info_bar (source->info_bar, GTK_MESSAGE_ERROR, response, msg); + } else { + show_error = FALSE; + gtk_widget_hide (source->info_bar); + msg = get_report_summary (source); + gtk_label_set_text (GTK_LABEL (source->label), msg); + } + g_free (msg); + + return !show_error; +} + + +static void +get_reports_cb (SyncevoServer *server, + SyncevoReports *reports, + GError *error, + app_data *data) +{ + long status = -1; + long common_status = -1; + source_stats *stats; + GHashTable *sources; /* key is source name, value is a source_stats */ + GHashTableIter iter; + const char *key, *val; + source_config *source_conf; + char *error_msg; + SyncErrorResponse response; + gboolean have_source_errors; + GHashTable *report = NULL; + guint len; + + if (error) { + g_warning ("Error in Session.GetReports: %s", error->message); + g_error_free (error); + /* non-fatal, unknown error */ + return; + } + + sources = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify)free_source_stats); + + len = syncevo_reports_get_length (reports); + + if (len > 0) { + report = syncevo_reports_index (reports, 0); + val = g_hash_table_lookup (report, "dir"); + if (!val || strlen (val) == 0) { + /* dummy report for first time sync info*/ + if (len > 1) { + report = syncevo_reports_index (reports, 1); + } else { + report = NULL; + } + } + } + + if (report) { + g_hash_table_iter_init (&iter, report); + while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&val)) { + char **strs; + strs = g_strsplit (key, "-", 6); + if (!strs) { + continue; + } + + if (strcmp (strs[0], "source") == 0) { + if (!handle_source_report_item (strs, val, sources)) { + g_warning ("Unidentified sync report item: %s=%s", + key, val); + } + } else if (strcmp (strs[0], "start") == 0) { + /* not used */ + } else if (strcmp (strs[0], "end") == 0) { + data->last_sync = strtol (val, NULL, 10); + } else if (strcmp (strs[0], "status") == 0) { + status = strtol (val, NULL, 10); + } else if (strcmp (strs[0], "peer") == 0) { + /* not used */ + } else if (strcmp (strs[0], "error") == 0) { + /* not used */ + } else if (strcmp (strs[0], "dir") == 0) { + /* not used */ + } else { + g_warning ("Unidentified sync report item: %s=%s", + key, val); + } + + g_strfreev (strs); + + } + } else { + common_status = 0; + } + + /* sources now has all statistics we want */ + + /* ficure out if all sources have same status or if there's a slow sync */ + data->forced_emergency = FALSE; + g_hash_table_remove_all (data->emergency_sources); + + g_hash_table_iter_init (&iter, sources); + while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&stats)) { + if (stats->status == 22001) { + /* ignore abort because of another source slow syncing */ + } else if (stats->status == 22000) { + common_status = stats->status; + data->forced_emergency = TRUE; + g_hash_table_insert (data->emergency_sources, + g_strdup (key), ""); + } else if (common_status == -1) { + common_status = stats->status; + } else if (common_status != stats->status) { + common_status = 0; + } + } + + if (status != 200) { + /* don't want to show a sync time for failed syncs */ + data->last_sync = -1; + } + + if (!data->forced_emergency) { + /* if user initiates a emergency sync wihtout forced_emergency, + enable all sources by default*/ + g_hash_table_iter_init (&iter, data->current_service->source_configs); + while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&source_conf)) { + if (source_config_is_usable (source_conf)) { + g_hash_table_insert (data->emergency_sources, g_strdup (key), ""); + } + } + } + + /* get common error message */ + error_msg = get_error_string_for_code (common_status, &response); + have_source_errors = FALSE; + + g_hash_table_iter_init (&iter, sources); + while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&stats)) { + /* store the statistics in source config */ + source_conf = g_hash_table_lookup (data->current_service->source_configs, + key); + if (source_conf) { + source_conf->stats_set = TRUE; + source_conf->local_changes = stats->local_changes; + source_conf->remote_changes = stats->remote_changes; + source_conf->local_rejections = stats->local_rejections; + source_conf->remote_rejections = stats->remote_rejections; + if (error_msg) { + /* there is a service-wide error, no need to show here */ + source_conf->status = 0; + } else { + source_conf->status = stats->status; + } + /* if ui has been constructed already, update it */ + if (!source_config_update_widget (source_conf)) { + have_source_errors = TRUE; + } + } + } + + if (!error_msg && !have_source_errors) { + /* no common source errors or individual source errors: + it's still possible that there are sync errors */ + error_msg = get_error_string_for_code (status, &response); + } + + /* update service UI */ + refresh_last_synced_label (data); + if (error_msg) { + GtkMessageType type = GTK_MESSAGE_ERROR; + + if (response == SYNC_ERROR_RESPONSE_EMERGENCY) { + type = GTK_MESSAGE_QUESTION; + } + + if (!data->synced_this_session) { + /* TRANSLATORS: the placeholder is a error message (hopefully) + * explaining the problem */ + char *msg = g_strdup_printf (_("There was a problem with last sync:\n%s"), + error_msg); + g_free (error_msg); + error_msg = msg; + } + set_info_bar (data->info_bar, type, response, error_msg); + g_free (error_msg); + } else if (data->current_operation == OP_RESTORE) { + /* special case for just after restoring */ + error_msg = g_strdup_printf + (_("You've just restored a backup. The changes have not been " + "synced with %s yet"), data->current_service->pretty_name); + set_info_bar (data->info_bar, + GTK_MESSAGE_INFO, + SYNC_ERROR_RESPONSE_SYNC, + error_msg); + } + + g_hash_table_destroy (sources); +} + +static void +set_config_cb (SyncevoSession *session, + GError *error, + app_data *data) +{ + if (error) { + g_warning ("Error in Session.SetConfig: %s", error->message); + g_error_free (error); + /* TODO show in UI: save failed */ + } + g_object_unref (session); +} + +static void +restore_cb (SyncevoSession *session, + GError *error, + app_data *data) +{ + if (error) { + g_warning ("Error in Session.Restore: %s", error->message); + g_error_free (error); + + return; + } +} + +static void +restore_backup (app_data *data, SyncevoSession *session, const char *dir) +{ + char **sources; + GHashTableIter iter; + int i = 0; + char *source; + + sources = g_malloc0 (sizeof (char*) * + (g_hash_table_size (data->emergency_sources) + 1)); + + g_hash_table_iter_init (&iter, data->emergency_sources); + while (g_hash_table_iter_next (&iter, (gpointer)&source, NULL)) { + sources[i++] = g_strdup (source); + } + sources[i] = NULL; + + syncevo_session_restore (session, dir, TRUE, (const char**)sources, + (SyncevoSessionGenericCb)restore_cb, + data); + + g_strfreev (sources); +} + +static void +save_config (app_data *data, SyncevoSession *session) +{ + syncevo_session_set_config (session, + TRUE, + FALSE, + data->current_service->config, + (SyncevoSessionGenericCb)set_config_cb, + data); +} + +static void +do_sync (operation_data *op_data, SyncevoSession *session) +{ + GHashTable *source_modes; + GHashTableIter iter; + source_config *source; + SyncevoSyncMode mode = SYNCEVO_SYNC_NONE; + + app_data *data = op_data->data; + + source_modes = syncevo_source_modes_new (); + + if (op_data->operation != OP_SYNC) { + /* in an emergency sync, set non-emergency sources to not sync*/ + g_hash_table_iter_init (&iter, data->current_service->source_configs); + while (g_hash_table_iter_next (&iter, NULL, (gpointer)&source)) { + if (g_hash_table_lookup (data->emergency_sources, source->name) == NULL) { + syncevo_source_modes_add (source_modes, + source->name, + SYNCEVO_SYNC_NONE); + } + } + } + + /* override all non-supported with "none". */ + g_hash_table_iter_init (&iter, data->current_service->source_configs); + while (g_hash_table_iter_next (&iter, NULL, (gpointer)&source)) { + if (!source->supported_locally) { + syncevo_source_modes_add (source_modes, + source->name, + SYNCEVO_SYNC_NONE); + } + } + + /* use given mode or use default for normal syncs */ + switch (op_data->operation) { + case OP_SYNC: + mode = SYNCEVO_SYNC_DEFAULT; + break; + case OP_SYNC_SLOW: + mode = SYNCEVO_SYNC_SLOW; + break; + case OP_SYNC_REFRESH_FROM_CLIENT: + mode = SYNCEVO_SYNC_REFRESH_FROM_CLIENT; + break; + case OP_SYNC_REFRESH_FROM_SERVER: + mode = SYNCEVO_SYNC_REFRESH_FROM_SERVER; + break; + default: + g_warn_if_reached(); + } + syncevo_session_sync (session, + mode, + source_modes, + (SyncevoSessionGenericCb)sync_cb, + data); + syncevo_source_modes_free (source_modes); +} + +static void +set_config_for_sync_cb (SyncevoSession *session, + GError *error, + operation_data *op_data) +{ + if (error) { + g_warning ("Error in Session.SetConfig: %s", error->message); + g_error_free (error); + /* TODO show in UI: sync failed (failed to even start) */ + return; + } + + do_sync (op_data, session); +} + +static void +run_operation (operation_data *op_data, SyncevoSession *session) +{ + SyncevoConfig *config; + + /* when we first get idle, start the operation */ + if (op_data->started) { + return; + } + op_data->started = TRUE; + op_data->data->current_operation = op_data->operation; + + /* time for business */ + switch (op_data->operation) { + case OP_SYNC: + case OP_SYNC_SLOW: + case OP_SYNC_REFRESH_FROM_CLIENT: + case OP_SYNC_REFRESH_FROM_SERVER: + /* Make sure we don't get change diffs printed out, then sync */ + config = g_hash_table_new (g_str_hash, g_str_equal); + syncevo_config_set_value (config, + NULL, "printChanges", "0"); + syncevo_session_set_config (session, + TRUE, + TRUE, + config, + (SyncevoSessionGenericCb)set_config_for_sync_cb, + op_data); + syncevo_config_free (config); + + break; + case OP_SAVE: + save_config (op_data->data, session); + break; + case OP_RESTORE: + restore_backup (op_data->data, session, op_data->dir); + break; + default: + g_warn_if_reached (); + } +} +/* Our sync session status */ +static void +status_changed_cb (SyncevoSession *session, + SyncevoSessionStatus status, + guint error_code, + SyncevoSourceStatuses *source_statuses, + operation_data *op_data) +{ + + switch (status) { + case SYNCEVO_STATUS_IDLE: + run_operation (op_data, session); + break; + case SYNCEVO_STATUS_DONE: + op_data->data->synced_this_session = TRUE; + + /* no need for sync session anymore */ + g_object_unref (session); + + /* refresh stats -- the service may no longer be the one syncing, + * and we might have only saved config but what the heck... */ + syncevo_server_get_reports (op_data->data->server, + op_data->data->current_service->name, + 0, 1, + (SyncevoServerGetReportsCb)get_reports_cb, + op_data->data); + + g_slice_free (operation_data, op_data); + default: + ; + } +} + +/* Our sync (or config-save) session status */ +static void +get_status_cb (SyncevoSession *session, + SyncevoSessionStatus status, + guint error_code, + SyncevoSourceStatuses *source_statuses, + GError *error, + operation_data *op_data) +{ + if (error) { + g_warning ("Error in Session.GetStatus: %s", error->message); + g_error_free (error); + g_object_unref (session); + + switch (op_data->operation) { + case OP_SYNC: + /* TODO show in UI: sync failed (failed to even start) */ + break; + case OP_SAVE: + /* TODO show in UI: save failed */ + break; + default: + g_warn_if_reached (); + } + g_slice_free (operation_data, op_data); + return; + } + + if (status == SYNCEVO_STATUS_IDLE) { + run_operation (op_data, session); + } +} + +static void +start_session_cb (SyncevoServer *server, + char *path, + GError *error, + operation_data *op_data) +{ + SyncevoSession *session; + app_data *data = op_data->data; + + if (error) { + g_warning ("Error in Server.StartSession: %s", error->message); + g_error_free (error); + g_free (path); + + switch (op_data->operation) { + case OP_SYNC: + /* TODO show in UI: sync failed (failed to even start) */ + break; + case OP_SAVE: + /* TODO show in UI: save failed */ + break; + default: + g_warn_if_reached (); + } + g_slice_free (operation_data, op_data); + return; + } + + session = syncevo_session_new (path); + + if (data->running_session && + strcmp (path, syncevo_session_get_path (data->running_session)) != 0) { + /* This is a really unfortunate event: + Someone got a session and we did not have time to set UI insensitive... */ + gtk_label_set_markup (GTK_LABEL (data->server_label), + _("Waiting for current operation to finish...")); + gtk_widget_show_all (data->sources_box); + } + + /* we want to know about status changes to our session */ + g_signal_connect (session, "status-changed", + G_CALLBACK (status_changed_cb), op_data); + syncevo_session_get_status (session, + (SyncevoSessionGetStatusCb)get_status_cb, + op_data); + + g_free (path); +} + +/* TODO: this function should accept source/peer name as param */ +char* +get_error_string_for_code (int error_code, SyncErrorResponse *response) +{ + if (response) { + *response = SYNC_ERROR_RESPONSE_NONE; + } + + switch (error_code) { + case -1: /* no errorcode */ + case 0: + case 200: + case LOCERR_USERABORT: + case LOCERR_USERSUSPEND: + return NULL; + case 22000: + if (response) { + *response = SYNC_ERROR_RESPONSE_EMERGENCY; + } + /* TRANSLATORS: next strings are error messages. */ + return g_strdup (_("A normal sync is not possible at this time. The server " + "suggests a slow sync, but this might not always be " + "what you want if both ends already have data.")); + case 22002: + return g_strdup (_("The sync process died unexpectedly.")); + case 22003: + if (response) { + *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; + } + return g_strdup (_("Password request was not answered. You can save the " + "password in the settings to prevent the request.")); + case 506: + /* TODO use the service device name here, this is a remote problem */ + return g_strdup (_("There was a problem processing sync request. " + "Trying again may help.")); + case DB_Unauthorized: + if (response) { + *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; + } + return g_strdup(_("Failed to login. Could there be a problem with " + "your username or password?")); + case DB_Forbidden: + return g_strdup(_("Forbidden")); + case DB_NotFound: + if (response) { + *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; + } + /* TRANSLATORS: data source means e.g. calendar or addressbook */ + return g_strdup(_("A data source could not be found. Could there be a " + "problem with the settings?")); + case DB_Fatal: + case DB_Error: + return g_strdup(_("Remote database error")); + case LOCAL_STATUS_CODE + DB_Fatal: + /* This can happen when EDS is borked, restart it may help... */ + return g_strdup(_("There is a problem with the local database. " + "Syncing again or rebooting may help.")); + case DB_Full: + return g_strdup(_("No space on disk")); + case LOCERR_PROCESSMSG: + 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_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: + case LOCERR_TRANSPFAIL: + case LOCERR_TIMEOUT: + if (response) { + *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; + } + return g_strdup(_("We were unable to connect to the server. The problem " + "could be temporary or there could be something wrong " + "with the settings.")); + case LOCERR_BADURL: + if (response) { + *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; + } + return g_strdup(_("The server URL is bad")); + case LOCERR_SRVNOTFOUND: + if (response) { + *response = SYNC_ERROR_RESPONSE_SETTINGS_OPEN; + } + return g_strdup(_("The server was not found")); + default: + return g_strdup_printf (_("Error %d"), error_code); + } +} + +static void +server_shutdown_cb (SyncevoServer *server, + app_data *data) +{ + if (data->syncing) { + gtk_label_set_text (GTK_LABEL (data->sync_status_label), + _("Sync failed")); + set_sync_progress (data, 1.0 , ""); + set_app_state (data, SYNC_UI_STATE_SERVER_OK); + } + + /* re-init server here */ +} + + +static void +set_running_session (app_data *data, const char *path) +{ + if (data->running_session) { + g_object_unref (data->running_session); + } + + if (!path) { + data->running_session = NULL; + return; + } + + data->running_session = syncevo_session_new (path); + + g_signal_connect (data->running_session, "progress-changed", + G_CALLBACK (running_session_progress_changed_cb), data); + g_signal_connect (data->running_session, "status-changed", + G_CALLBACK (running_session_status_changed_cb), data); + syncevo_session_get_status (data->running_session, + (SyncevoSessionGetStatusCb)get_running_session_status_cb, + data); +} + +static void +set_online_status (app_data *data, gboolean online) +{ + if (online != data->online) { + data->online = online; + + if (data->current_state == SYNC_UI_STATE_SERVER_OK) { + if (data->online) { + gtk_widget_hide (data->no_connection_box); + } else { + gtk_widget_show (data->no_connection_box); + } + } + gtk_widget_set_sensitive (data->sync_btn, data->online); + } +} + +static void +get_presence_cb (SyncevoServer *server, + char *status, + char **transports, + GError *error, + app_data *data) +{ + if (error) { + g_warning ("Server.GetSessions failed: %s", error->message); + g_error_free (error); + /* non-fatal, ignore in UI */ + return; + } + + if (data->current_service && status) { + set_online_status (data, strcmp (status, "") == 0); + } + g_free (status); + g_strfreev (transports); +} + +static void +password_dialog_response_cb (GtkWidget *dialog, int response, app_data *data) +{ + const char *password; + GHashTable *return_dict; + + return_dict = g_hash_table_new (g_str_hash, g_str_equal); + + if (response == GTK_RESPONSE_OK) { + password = gtk_entry_get_text (GTK_ENTRY (data->password_dialog_entry)); + g_hash_table_insert (return_dict, "password", (gpointer)password); + } + + syncevo_server_info_response (data->server, data->password_dialog_id, + "response", return_dict, NULL, NULL); + + g_hash_table_destroy (return_dict); + + g_free (data->password_dialog_id); + data->password_dialog_id = NULL; + gtk_widget_destroy (dialog); +} + +static void +info_request_cb (SyncevoServer *syncevo, + char *id, + char *session_path, + char *state, + char *handler_path, + char *type, + GHashTable *parameters, + app_data *data) +{ + GHashTable *t; + GtkWidget *dialog, *content, *label, *align; + char *msg; + + if (g_strcmp0 (state, "request") != 0 || + g_strcmp0 (type, "password") != 0) { + /* not handling other stuff */ + return; + } + + if (!data->running_session || + g_strcmp0 (session_path, + syncevo_session_get_path (data->running_session)) != 0) { + /* not our problem */ + return; + } + + t = g_hash_table_new (g_str_hash, g_str_equal); + syncevo_server_info_response (syncevo, id, "working", t, NULL, NULL); + g_hash_table_destroy (t); + + data->password_dialog_id = g_strdup (id); + + /* TRANSLATORS: password request dialog contents: title, cancel button + * and ok button */ + dialog = gtk_dialog_new_with_buttons (_("Password is required for sync"), + GTK_WINDOW (data->sync_win), + GTK_DIALOG_DESTROY_WITH_PARENT, + _("Cancel sync"), GTK_RESPONSE_CANCEL, + _("Sync with password"), GTK_RESPONSE_OK, + NULL); + content = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); + gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 16, 16); + gtk_widget_show (align); + gtk_box_pack_start (GTK_BOX (content), align, FALSE, FALSE, 6); + + /* TRANSLATORS: password request dialog message, placeholder is service name */ + msg = g_strdup_printf (_("Please enter password for syncing with %s:"), + data->current_service->pretty_name); + label = gtk_label_new (msg); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_widget_set_size_request (label, 500, -1); + + gtk_widget_show (label); + gtk_container_add (GTK_CONTAINER (align), label); + g_free (msg); + + align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); + gtk_alignment_set_padding (GTK_ALIGNMENT (align), 0, 0, 16, 16); + gtk_widget_show (align); + gtk_box_pack_start (GTK_BOX (content), align, FALSE, FALSE, 6); + + data->password_dialog_entry = gtk_entry_new (); + gtk_entry_set_max_length (GTK_ENTRY (data->password_dialog_entry), 99); + gtk_entry_set_width_chars (GTK_ENTRY (data->password_dialog_entry), 30); + gtk_entry_set_visibility (GTK_ENTRY (data->password_dialog_entry), FALSE); + + gtk_widget_show (data->password_dialog_entry); + gtk_container_add (GTK_CONTAINER (align), data->password_dialog_entry); + + g_signal_connect (dialog, "response", + G_CALLBACK (password_dialog_response_cb), data); + + gtk_window_present (GTK_WINDOW (dialog)); + gtk_widget_grab_focus (data->password_dialog_entry); +} + +static void +server_presence_changed_cb (SyncevoServer *server, + char *config_name, + char *status, + char *transport, + app_data *data) +{ + if (data->current_service && + config_name && status && + g_strcasecmp (data->current_service->name, config_name) == 0) { + + set_online_status (data, strcmp (status, "") == 0); + } +} + +static void +server_templates_changed_cb (SyncevoServer *server, + app_data *data) +{ + if (gtk_widget_get_visible (data->services_box)) { + update_services_list (data); + } +} + +static void +server_session_changed_cb (SyncevoServer *server, + char *path, + gboolean started, + app_data *data) +{ + if (started) { + set_running_session (data, path); + } else if (data->running_session && + strcmp (syncevo_session_get_path (data->running_session), path) == 0 ) { + set_running_session (data, NULL); + } +} + +static void +get_sessions_cb (SyncevoServer *server, + SyncevoSessions *sessions, + GError *error, + app_data *data) +{ + const char *path; + + if (error) { + g_warning ("Server.GetSessions failed: %s", error->message); + g_error_free (error); + + /* TODO show in UI: failed first syncevo call (unexpected, fatal?) */ + return; + } + + /* assume first one is active */ + path = syncevo_sessions_index (sessions, 0); + set_running_session (data, path); + + syncevo_sessions_free (sessions); +} + +static void +get_config_for_default_peer_cb (SyncevoServer *syncevo, + SyncevoConfig *config, + GError *error, + app_data *data) +{ + char *name; + + if (error) { + g_warning ("Server.GetConfig failed: %s", error->message); + g_error_free (error); + /* TODO show in UI: failed first syncevo call (unexpected, fatal?) */ + return; + } + + syncevo_config_get_value (config, NULL, "defaultPeer", &name); + reload_config (data, name); + + syncevo_config_free (config); +} + +app_data* +sync_ui_create () +{ + app_data *data; + + data = g_slice_new0 (app_data); + data->online = TRUE; + data->current_state = SYNC_UI_STATE_GETTING_SERVER; + data->forced_emergency = FALSE; + data->emergency_sources = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + if (!init_ui (data)) { + return NULL; + } + + data->server = syncevo_server_get_default(); + g_signal_connect (data->server, "shutdown", + G_CALLBACK (server_shutdown_cb), data); + g_signal_connect (data->server, "session-changed", + G_CALLBACK (server_session_changed_cb), data); + g_signal_connect (data->server, "presence-changed", + G_CALLBACK (server_presence_changed_cb), data); + g_signal_connect (data->server, "templates-changed", + G_CALLBACK (server_templates_changed_cb), data); + g_signal_connect (data->server, "info-request", + G_CALLBACK (info_request_cb), data); + + syncevo_server_get_config (data->server, + "", + FALSE, + (SyncevoServerGetConfigCb)get_config_for_default_peer_cb, + data); + + syncevo_server_get_sessions (data->server, + (SyncevoServerGetSessionsCb)get_sessions_cb, + data); + + gtk_window_present (GTK_WINDOW (data->sync_win)); + + return data; +} + +void sync_ui_show_settings (app_data *data, const char *id) +{ + show_services_list (data, id); +} + +GtkWindow* +sync_ui_get_main_window (app_data *data) +{ + return GTK_WINDOW(data->sync_win); +} diff --git a/src/gtk3-ui/sync-ui.h b/src/gtk3-ui/sync-ui.h new file mode 100644 index 00000000..1ffc38c1 --- /dev/null +++ b/src/gtk3-ui/sync-ui.h @@ -0,0 +1,55 @@ +/* + * 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 + */ + +#ifndef SYNC_UI_H +#define SYNC_UI_H + +#include <gtk/gtk.h> +#include "config.h" +#include "sync-ui-config.h" +#include "sync-ui.h" + +#define SYNC_UI_LIST_ICON_SIZE 32 +#define SYNC_UI_LIST_BTN_WIDTH 150 + +typedef struct _app_data app_data; + +typedef enum SyncErrorResponse { + SYNC_ERROR_RESPONSE_NONE, + SYNC_ERROR_RESPONSE_SYNC, + SYNC_ERROR_RESPONSE_SETTINGS_SELECT, + SYNC_ERROR_RESPONSE_SETTINGS_OPEN, + SYNC_ERROR_RESPONSE_EMERGENCY, + SYNC_ERROR_RESPONSE_EMERGENCY_SLOW_SYNC, +} SyncErrorResponse; + + +char* get_pretty_source_name (const char *source_name); +char* get_error_string_for_code (int error_code, SyncErrorResponse *response); +void show_error_dialog (GtkWidget *widget, const char* message); +gboolean show_confirmation (GtkWidget *widget, const char *message, const char *yes, const char *no); + +app_data *sync_ui_create (); +GtkWindow *sync_ui_get_main_window (app_data *data); +void sync_ui_show_settings (app_data *data, const char *id); + +void toggle_set_active (GtkWidget *toggle, gboolean active); +gboolean toggle_get_active (GtkWidget *toggle); + +#endif diff --git a/src/gtk3-ui/sync-ui.rc b/src/gtk3-ui/sync-ui.rc new file mode 100644 index 00000000..389c7049 --- /dev/null +++ b/src/gtk3-ui/sync-ui.rc @@ -0,0 +1,49 @@ +# generic rules for MuxWidgets + +style "mux-frame" { + bg[NORMAL] = "#ffffff" + bg[INSENSITIVE] = "#ffffff" + MuxFrame::border-color = "#dee2e5" + MuxFrame::bullet-color = "#aaaaaa" + MuxFrame::title-font = "Bold 12" +} +class "MuxFrame" style "mux-frame" + +# sync-ui specific rules + +style "meego-win" { + bg[NORMAL] = "#4a535a" +} +widget "meego_win" style "meego-win" + +style "data-box" { + bg[NORMAL] = "#ececec" + bg[INSENSITIVE] = "#ececec" +} +widget "*.sync_data_and_type_box" style "data-box" + +style "insensitive-frame" { + bg[INSENSITIVE] = "#d2d6d9" +} +widget "*.log_frame*" style "insensitive-frame" +widget "*.backup_frame*" style "insensitive-frame" +widget "*.services_frame*" style "insensitive-frame" + +style "service-title" { + font_name = "Bold 14" +} +widget "*.sync_service_label" style "service-title" +widget "*.sync_status_label" style "service-title" +widget "*.no_server_label" style "service-title" +widget "*.sync_failure_label" style "service-title" + +style "big-button" { + GtkButton::inner-border = { 10,10,10,10 } + font_name = "Bold 11" +} +widget "*.sync_btn*" style "big-button" + +style "bread-crumb-button" { + GtkButton::inner-border = { 5,5,0,0 } +} +widget "*.mux_window_bread_crumbs.*GtkButton*" style "bread-crumb-button" diff --git a/src/gtk3-ui/sync.desktop.in b/src/gtk3-ui/sync.desktop.in new file mode 100644 index 00000000..377e0f7c --- /dev/null +++ b/src/gtk3-ui/sync.desktop.in @@ -0,0 +1,10 @@ +[Desktop Entry] +_Name=Sync +_Comment=Up to date +Version=1.0 +Type=Application +Exec=sync-ui +Icon=sync +Categories=Network;GTK; +Terminal=false +StartupNotify=true diff --git a/src/gtk3-ui/sync.png b/src/gtk3-ui/sync.png Binary files differnew file mode 100644 index 00000000..41edd0da --- /dev/null +++ b/src/gtk3-ui/sync.png diff --git a/src/gtk3-ui/ui.xml b/src/gtk3-ui/ui.xml new file mode 100644 index 00000000..d08c905e --- /dev/null +++ b/src/gtk3-ui/ui.xml @@ -0,0 +1,1509 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <!-- interface-requires gtk+ 2.10 --> + <object class="GtkWindow" id="emergency_win"> + <property name="width_request">1024</property> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Sync Emergency</property> + <property name="modal">True</property> + <property name="default_width">800</property> + <property name="default_height">550</property> + <property name="destroy_with_parent">True</property> + <property name="transient_for">sync_win</property> + <child> + <object class="GtkVBox" id="vbox14"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">4</property> + <child> + <object class="GtkFrame" id="emergency_frame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkScrolledWindow" id="emergency_scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">never</property> + <child> + <object class="GtkViewport" id="emergency_viewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="resize_mode">queue</property> + <child> + <object class="GtkHBox" id="hbox16"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkVBox" id="vbox20"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">8</property> + <child> + <object class="GtkVBox" id="vbox39"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">8</property> + <child> + <object class="GtkLabel" id="emergency_label"> + <property name="width_request">800</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label">If something has gone horribly wrong you can try a slow sync, start from scratch or restore from backup.</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkExpander" id="emergency_expander"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <object class="GtkAlignment" id="alignment19"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="yscale">0</property> + <property name="left_padding">20</property> + <child> + <object class="GtkTable" id="emergency_source_table"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="n_rows">2</property> + <child> + <object class="GtkCheckButton" id="checkbutton2"> + <property name="label" translatable="yes">Calendar</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="draw_indicator">True</property> + </object> + </child> + <child> + <object class="GtkCheckButton" id="checkbutton3"> + <property name="label" translatable="yes">Tasks</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_action_appearance">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + </packing> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="emergency_expander_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label">Affected data:</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">8</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="slow_sync_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">5</property> + <child> + <object class="GtkLabel" id="slow_sync_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" comments="a title in emergency view"><big>Slow sync</big></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkAlignment" id="alignment4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="xscale">0</property> + <property name="left_padding">40</property> + <property name="right_padding">40</property> + <child> + <object class="GtkHBox" id="hbox19"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkButton" id="slow_sync_btn"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <child> + <object class="GtkLabel" id="slow_sync_btn_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xpad">16</property> + <property name="label" translatable="yes">Slow sync</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="slow_sync_explanation_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xpad">8</property> + <property name="label" translatable="yes">A slow sync compares items from both sides and tries to merge them. +This may fail in some cases, leading to duplicates or lost information.</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">8</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">5</property> + <child> + <object class="GtkLabel" id="label14"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" comments="a title in emergency view"><big>Start from scratch</big></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkAlignment" id="alignment3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="yalign">0</property> + <property name="xscale">0</property> + <property name="left_padding">40</property> + <property name="right_padding">40</property> + <child> + <object class="GtkHBox" id="hbox13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkButton" id="refresh_from_server_btn"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <child> + <object class="GtkLabel" id="refresh_from_server_btn_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xpad">16</property> + <property name="label" translatable="yes">Delete all your local +information and replace +with data from Zyb</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xpad">15</property> + <property name="label" translatable="yes" comments="text between the two "start from scratch" buttons in emergency view"><b>or</b></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="refresh_from_client_btn"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <child> + <object class="GtkLabel" id="refresh_from_client_btn_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xpad">16</property> + <property name="label" translatable="yes">Delete all data on Zyb +and replace with your +local information</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">8</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox21"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">5</property> + <child> + <object class="GtkLabel" id="label9"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" comments="a title in emergency view"><big>Restore from backup</big></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkAlignment" id="alignment2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="resize_mode">queue</property> + <property name="left_padding">40</property> + <property name="right_padding">40</property> + <child> + <object class="GtkVBox" id="vbox22"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkLabel" id="label12"> + <property name="width_request">800</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" comments="explanation of "Restore backup" function">Backups are made before every time we Sync. Choose a backup to restore. Any changes you have made since then will be lost.</property> + <property name="wrap">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <object class="GtkViewport" id="backup_viewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="resize_mode">queue</property> + <child> + <object class="GtkTable" id="emergency_backup_table"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="n_rows">10</property> + <property name="n_columns">2</property> + <property name="row_spacing">16</property> + <child> + <object class="GtkLabel" id="label59"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label">Backup before syncing Dec 5th 2009 16:35</property> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label69"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label">Backup before syncing Dec 5th 2009 13:35</property> + </object> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label79"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label">Backup before syncing Dec 5th 2009 11:35</property> + </object> + <packing> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label89"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label">Backup before syncing Dec 3rd</property> + </object> + <packing> + <property name="top_attach">5</property> + <property name="bottom_attach">6</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label109"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label">Backup before syncing Dec 1st</property> + </object> + <packing> + <property name="top_attach">6</property> + <property name="bottom_attach">7</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label29"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label">Backup before syncing yesterday</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label30"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="ypad">5</property> + <property name="label">Backup before syncing two hours ago</property> + </object> + <packing> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkButton" id="button1"> + <property name="label">Restore</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + <property name="x_padding">16</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">8</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">40</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label11"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="emergency_close_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkButton" id="emergency_close_btn"> + <property name="label" translatable="yes" comments="close button for settings window">Close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">12</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkWindow" id="services_win"> + <property name="width_request">1024</property> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Settings</property> + <property name="modal">True</property> + <property name="default_width">800</property> + <property name="default_height">550</property> + <property name="destroy_with_parent">True</property> + <property name="transient_for">sync_win</property> + <child> + <object class="GtkVBox" id="vbox6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">4</property> + <child> + <object class="GtkFrame" id="services_list_frame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkScrolledWindow" id="settings_scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">never</property> + <child> + <object class="GtkViewport" id="settings_viewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="resize_mode">queue</property> + <child> + <object class="GtkHBox" id="hbox4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkAlignment" id="alignment_1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="bottom_padding">12</property> + <property name="left_padding">36</property> + <property name="right_padding">12</property> + <child> + <object class="GtkVBox" id="vbox7"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkVBox" id="vbox13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">5</property> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><big>Network sync</big></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox11"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">To sync you'll need a network connection and an account with a sync service. +We support the following services: </property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow"> + <property name="height_request">300</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkViewport" id="viewport1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="resize_mode">queue</property> + <child> + <object class="GtkVBox" id="services_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox14"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">If you don't see your service above but know that your sync provider uses SyncML +you can setup a service manually.</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="new_service_btn"> + <property name="label" translatable="yes">Add new service</property> + <property name="width_request">150</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">8</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">8</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="direct_sync_box"> + <property name="height_request">400</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">5</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><big>Direct sync</big></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Use Bluetooth to Sync your data from one device to another.</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="device_scrolledwindow"> + <property name="height_request">300</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkViewport" id="viewport2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="resize_mode">queue</property> + <child> + <object class="GtkVBox" id="devices_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="add_bt_device_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkLabel" id="label_4"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">You will need to add Bluetooth devices before they can be synced.</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="new_device_btn"> + <property name="label" translatable="yes">Add new device</property> + <property name="width_request">150</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">8</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">6</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="settings_close_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkButton" id="settings_close_btn"> + <property name="label" translatable="yes" comments="close button for settings window">Close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">12</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkWindow" id="sync_win"> + <property name="width_request">1024</property> + <property name="can_focus">False</property> + <property name="title" translatable="yes">Sync</property> + <property name="default_width">800</property> + <property name="default_height">550</property> + <child> + <object class="GtkVBox" id="window_child"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="hbox2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">4</property> + <property name="spacing">4</property> + <child> + <object class="GtkVBox" id="vbox2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkFrame" id="main_frame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkVBox" id="main_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="hbox3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="server_icon_box"> + <property name="width_request">48</property> + <property name="height_request">48</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkImage" id="sync_service_image"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="stock">gtk-missing-image</property> + <property name="icon-size">6</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">10</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="sync_service_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="yalign">0.20000000298023224</property> + <property name="use_markup">True</property> + <property name="ellipsize">end</property> + <property name="max_width_chars">42</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="autosync_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">6</property> + <property name="spacing">4</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">8</property> + <property name="pack_type">end</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">16</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox5"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkEventBox" id="sync_data_and_type_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkVBox" id="vbox15"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="hbox15"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkVBox" id="vbox16"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="service_error_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="service_box"> + <property name="can_focus">False</property> + <child> + <object class="GtkFrame" id="frame2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="top_padding">5</property> + <property name="left_padding">12</property> + <child> + <object class="GtkVBox" id="sources_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + </child> + </object> + </child> + <child type="label_item"> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">55</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">6</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label13"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox3"> + <property name="width_request">250</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">5</property> + <child> + <object class="GtkVBox" id="vbox9"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkFrame" id="log_frame"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label_xalign">0</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkVBox" id="vbox18"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkVBox" id="vbox10"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="hbox7"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkLabel" id="sync_status_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="padding">6</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="spinner_box"> + <property name="height_request">24</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkImage" id="spinner_image"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="stock">gtk-missing-image</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">10</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox20"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkVBox" id="info_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkHBox" id="no_connection_box"> + <property name="can_focus">False</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox17"> + <property name="height_request">25</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkProgressBar" id="progressbar"> + <property name="can_focus">False</property> + <property name="ellipsize">end</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">5</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="hbox6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkVBox" id="vbox8"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">10</property> + <child> + <object class="GtkLabel" id="action_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label" translatable="yes" comments="title for the buttons on the right side of main view"><b>Actions</b></property> + <property name="use_markup">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sync_btn"> + <property name="label">Sync now</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="change_service_btn"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + <child> + <object class="GtkLabel" id="label15"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes" comments="Button in main view, right side. Keep to below 20 chars per line, feel free to use two lines">Change or edit +sync service</property> + <property name="justify">center</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="emergency_btn"> + <property name="label" translatable="yes" comments="button in main view, right side. Keep length to 20 characters or so, use two lines if needed">Fix a sync +emergency</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_action_appearance">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="padding">30</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="padding">20</property> + <property name="position">3</property> + </packing> + </child> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label18"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + </object> +</interface> @@ -27,10 +27,17 @@ endif include $(top_srcdir)/src/dbus/dbus.am src_cppflags += -I$(top_srcdir)/src/dbus + if COND_GUI +if COND_GTK2 include $(top_srcdir)/src/gtk-ui/gtk-ui.am src_cppflags += -I$(top_srcdir)/src/gtk-ui +else +include $(top_srcdir)/src/gtk3-ui/gtk-ui.am +src_cppflags += -I$(top_srcdir)/src/gtk3-ui endif +endif + src_cppflags += -I$(top_srcdir)/test -I$(top_srcdir) $(BACKEND_CPPFLAGS) |