summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Kukkonen <jku@linux.intel.com>2011-11-17 14:24:23 +0200
committerPatrick Ohly <patrick.ohly@intel.com>2012-03-27 15:30:20 +0200
commitf1454948b854840b3420e196b752db2fb0394b76 (patch)
treeb877ea1dabae117ffc471817397647de72020152
parenta8ee160ce57ed09ab79518311cca0f71de27f902 (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.ac43
-rw-r--r--src/gtk-ui/README13
-rw-r--r--src/gtk3-ui/README8
-rw-r--r--src/gtk3-ui/gtk-ui.am97
-rw-r--r--src/gtk3-ui/main.c179
-rw-r--r--src/gtk3-ui/mux-frame.c372
-rw-r--r--src/gtk3-ui/mux-frame.h64
-rw-r--r--src/gtk3-ui/sync-config-widget.c2195
-rw-r--r--src/gtk3-ui/sync-config-widget.h128
-rw-r--r--src/gtk3-ui/sync-generic.pngbin0 -> 1669 bytes
-rw-r--r--src/gtk3-ui/sync-gtk.desktop.in10
-rw-r--r--src/gtk3-ui/sync-spinner.gifbin0 -> 2150 bytes
-rw-r--r--src/gtk3-ui/sync-ui-config.c160
-rw-r--r--src/gtk3-ui/sync-ui-config.h95
-rw-r--r--src/gtk3-ui/sync-ui.c3599
-rw-r--r--src/gtk3-ui/sync-ui.h55
-rw-r--r--src/gtk3-ui/sync-ui.rc49
-rw-r--r--src/gtk3-ui/sync.desktop.in10
-rw-r--r--src/gtk3-ui/sync.pngbin0 -> 3095 bytes
-rw-r--r--src/gtk3-ui/ui.xml1509
-rw-r--r--src/src.am7
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
new file mode 100644
index 00000000..75e6ed7c
--- /dev/null
+++ b/src/gtk3-ui/sync-generic.png
Binary files differ
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
new file mode 100644
index 00000000..f4bc16de
--- /dev/null
+++ b/src/gtk3-ui/sync-spinner.gif
Binary files differ
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
new file mode 100644
index 00000000..41edd0da
--- /dev/null
+++ b/src/gtk3-ui/sync.png
Binary files differ
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">&lt;big&gt;Slow sync&lt;/big&gt;</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">&lt;big&gt;Start from scratch&lt;/big&gt;</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 &quot;start from scratch&quot; buttons in emergency view">&lt;b&gt;or&lt;/b&gt;</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">&lt;big&gt;Restore from backup&lt;/big&gt;</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 &quot;Restore backup&quot; 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">&lt;big&gt;Network sync&lt;/big&gt;</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">&lt;big&gt;Direct sync&lt;/big&gt;</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">&lt;b&gt;Actions&lt;/b&gt;</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>
diff --git a/src/src.am b/src/src.am
index 72c15b8a..30cdac08 100644
--- a/src/src.am
+++ b/src/src.am
@@ -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)