[gnome-clocks/wip/vala] Vala rewrite (in progress)



commit 25ceb06c91abf1561810702858039d42c2431c09
Author: Paolo Borelli <pborelli gnome org>
Date:   Sat Feb 2 14:46:00 2013 +0100

    Vala rewrite (in progress)
    
    Python has served us well for prototyping the app, but we are now
    switching to Vala to overcome some problems (in particular lack of
    proper libcanberra bindings) and to take advantage of some other things
    (libgd, egglistbox, gtk resources) not readily available in python.

 .gitmodules                    |    3 +
 Makefile.am                    |   80 +++++++++---
 autogen.sh                     |   29 +++--
 configure.ac                   |   33 ++++-
 gnome-clocks                   |   33 -----
 libgd                          |    1 +
 src/alarms.vala                |  156 +++++++++++++++++++++
 src/application.vala           |  102 ++++++++++++++
 src/clock.vala                 |   27 ++++
 src/config.vapi                |    8 +
 src/gnome-clocks.gresource.xml |    6 +
 src/gnome-desktop-3.0.vapi     |   16 ++
 src/main.vala                  |   30 ++++
 src/menu.ui                    |   24 ++++
 src/stopwatch.vala             |  295 ++++++++++++++++++++++++++++++++++++++++
 src/timer.vala                 |   35 +++++
 src/utils.vala                 |  117 ++++++++++++++++
 src/widgets.vala               |  273 +++++++++++++++++++++++++++++++++++++
 src/window.vala                |  131 ++++++++++++++++++
 src/world.vala                 |   67 +++++++++
 20 files changed, 1399 insertions(+), 67 deletions(-)
---
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..bfd964e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "libgd"]
+	path = libgd
+	url = git://git.gnome.org/libgd
diff --git a/Makefile.am b/Makefile.am
index 8f85924..0722a50 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,6 +1,6 @@
-ACLOCAL_AMFLAGS = -I m4
+ACLOCAL_AMFLAGS = -I m4 -I libgd ${ACLOCAL_FLAGS}
 
-SUBDIRS = po
+SUBDIRS = libgd po
 
 # desktop file
 # (we can't use INTLTOOL_DESKTOP_RULE here due to lp#605826)
@@ -82,31 +82,76 @@ css_DATA = \
 	data/css/button-border-stop-active.svg \
 	data/css/button-border-stop.svg
 
-# main script
-bin_SCRIPTS = gnome-clocks
-
-# python module (nobase means dirname is preserved in site-packages
-packagesdir = $(pythondir)
-nobase_dist_packages_PYTHON = \
-	$(wildcard $(srcdir)/gnomeclocks/*.py)
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/libgd \
+	-DGNOME_DESKTOP_USE_UNSTABLE_API \
+	-DGETTEXT_PACKAGE=\""$(GETTEXT_PACKAGE)"\" \
+	-DGNOMELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale"\" \
+	-DDATADIR=\""$(datadir)"\"
+
+AM_VALAFLAGS = \
+	--vapidir=libgd \
+	--pkg gio-2.0 \
+	--pkg gtk+-3.0 \
+	--pkg libcanberra \
+	--pkg libnotify \
+	--pkg gd-1.0 \
+	--pkg clutter-gtk-1.0
+
+bin_PROGRAMS = gnome-clocks
+
+BUILT_SOURCES = \
+	src/resources.c
+
+src/resources.c: $(top_srcdir)/src/gnome-clocks.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) --generate-dependencies --sourcedir=$(top_srcdir)/src $(top_srcdir)/src/gnome-clocks.gresource.xml)
+	$(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(top_srcdir)/src --generate-source $<
+
+VALA_SOURCES = \
+	src/application.vala \
+	src/window.vala \
+	src/clock.vala \
+	src/world.vala \
+	src/alarms.vala \
+	src/stopwatch.vala \
+	src/timer.vala \
+	src/utils.vala \
+	src/widgets.vala \
+	src/main.vala
+
+gnome_clocks_SOURCES = \
+	$(BUILT_SOURCES) \
+	$(VALA_SOURCES) \
+	src/gnome-desktop-3.0.vapi \
+	src/config.vapi
+
+AM_CFLAGS = \
+	$(CLOCKS_CFLAGS) \
+	-Wall \
+	-Wno-unused-but-set-variable \
+	-Wno-unused-variable
+
+gnome_clocks_LDFLAGS = -export-dynamic -rpath $(libdir)
+gnome_clocks_LDADD = \
+	$(top_builddir)/libgd/libgd.la \
+	$(CLOCKS_LIBS) \
+	-lm
 
 EXTRA_DIST = \
 	$(icon_files) \
 	$(hcicon_files) \
 	$(images_DATA) \
 	$(css_DATA) \
-	$(bin_SCRIPTS) \
-	data/org.gnome.clocks.gschema.xml.in
+	data/org.gnome.clocks.gschema.xml.in \
+	src/gnome-clocks.gresource.xml \
+	src/menu.ui
 
 CLEANFILES = \
-	$(applications_DATA) \
-	$(wildcard $(srcdir)/gnomeclocks/*.pyc)
+	$(applications_DATA)
 
 DISTCLEANFILES = \
-	gnomeclocks/defs.py \
 	$(gsettings_SCHEMAS)
 
-MAINTAINERCLEANFILES = 		\
+MAINTAINERCLEANFILES = \
 	ABOUT-NLS \
 	aclocal.m4 \
 	config.guess \
@@ -122,7 +167,8 @@ MAINTAINERCLEANFILES = 		\
 	po/insert-header.sin \
 	po/quot.sed \
 	po/remove-potcdate.sin \
-	py-compile \
-	$(gsettings_SCHEMAS:.xml=.valid)
+	$(gsettings_SCHEMAS:.xml=.valid) \
+	$(VALA_SOURCES:.vala=.c) \
+	*.stamp
 
 -include $(top_srcdir)/git.mk
diff --git a/autogen.sh b/autogen.sh
index a69b69b..f8c27f7 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -1,19 +1,26 @@
-#!/bin/bash
+#!/bin/sh
 # Run this to generate all the initial makefiles, etc.
 
-srcdir=`dirname $0`
-test -z "$srcdir" && srcdir=.
+test -n "$srcdir" || srcdir=`dirname "$0"`
+test -n "$srcdir" || srcdir=.
 
-PKG_NAME="gnome-clocks"
+OLDDIR=`pwd`
+cd $srcdir
 
-test -f $srcdir/configure.ac || {
-    echo "**Error**: Directory "\`$srcdir\'" does not look like the top-level $PKG_NAME directory"
+AUTORECONF=`which autoreconf`
+if test -z $AUTORECONF; then
+    echo "*** No autoreconf found, please install it ***"
     exit 1
-}
+fi
 
-which gnome-autogen.sh || {
-    echo "You need to install gnome-common from GNOME Git (or from your OS vendor's package manager)."
+INTLTOOLIZE=`which intltoolize`
+if test -z $INTLTOOLIZE; then
+    echo "*** No intltoolize found, please install the intltool package ***"
     exit 1
-}
+fi
 
-. gnome-autogen.sh "$@"
+autopoint --force || exit $?
+AUTOPOINT='intltoolize --automake --copy' autoreconf --force --install --verbose
+
+cd $OLDDIR
+test -n "$NOCONFIGURE" || "$srcdir/configure" "$@"
diff --git a/configure.ac b/configure.ac
index 9c49c96..3601114 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,12 +6,12 @@ AC_INIT([gnome-clocks],
         [gnome-clocks])
 
 AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_HEADERS(config.h)
+AC_CONFIG_SRCDIR(src/main.vala)
 
 AM_INIT_AUTOMAKE([1.11 tar-ustar dist-xz no-dist-gzip foreign])
 m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
 
-AM_PATH_PYTHON([3.2])
-
 IT_PROG_INTLTOOL(0.40)
 AM_GNU_GETTEXT([external])
 AM_GNU_GETTEXT_VERSION([0.17])
@@ -19,18 +19,38 @@ GETTEXT_PACKAGE=AC_PACKAGE_NAME
 AC_SUBST([GETTEXT_PACKAGE])
 AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE",[The name of the gettext domain])
 
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+AM_PROG_VALAC([0.17.4])
+
+AC_PATH_PROG(GLIB_COMPILE_RESOURCES, glib-compile-resources)
+
 GLIB_GSETTINGS
 
+LT_INIT([disable-static])
+
 PKG_PROG_PKG_CONFIG([0.22])
 
 PKG_CHECK_MODULES(CLOCKS, [
-    pycairo
-    pygobject-3.0 >= 3.4.2
+    gio-2.0 >= 2.30.0
+    gtk+-3.0 >= 3.6.0
+    libcanberra >= 0.30
+    gnome-desktop-3.0 >= 3.6.0
+    libnotify >= 0.7.0
+    clutter-1.0 >= 1.12.0
+    clutter-gtk-1.0 >= 1.4.0
+])
+
+LIBGD_INIT([
+    static
+    main-toolbar
+    vapi
 ])
 
 AC_CONFIG_FILES([
     Makefile
-    gnomeclocks/defs.py
+    libgd/Makefile
     po/Makefile.in
 ])
 
@@ -40,7 +60,8 @@ echo "
     gnome-clocks ${VERSION}
 
     prefix: ${prefix}
-    Python interpreter: ${PYTHON}
+    Vala compiler: ${VALAC}
+    C compiler: ${CC}
 
     Now type 'make' to build ${PACKAGE}
 "
diff --git a/libgd b/libgd
new file mode 160000
index 0000000..ee7333b
--- /dev/null
+++ b/libgd
@@ -0,0 +1 @@
+Subproject commit ee7333b87b57d9c5f0ff614c11e43bd37d6a30ce
diff --git a/src/alarms.vala b/src/alarms.vala
new file mode 100644
index 0000000..0b7bb0c
--- /dev/null
+++ b/src/alarms.vala
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+
+    private class AlarmItem : Object {
+        static const TimeSpan RING_DURATION = 5 * TimeSpan.MINUTE;
+        static const TimeSpan SNOOZE_DURATION = 9 * TimeSpan.MINUTE;
+
+        private enum State {
+            READY,
+            RINGING,
+            SNOOZING
+        }
+
+        private State state;
+        private string name { get; set; }
+        private int hour { get; set; }
+        private int minute { get; set; }
+        private int[] days { get; set; }
+        private string time_label;
+        private string repeat_label;
+        private Bell bell;
+        private DateTime alarm_time;
+        private DateTime snooze_time;
+        private DateTime ring_start_time;
+
+        public AlarmItem (string name, bool active, int hour, int minute, int days[]) {
+
+            reset ();
+
+            time_label = "";
+            repeat_label = "";
+
+            bell = new Bell ("alarm-clock-elapsed", _("Alarm"), name);
+        }
+
+        public bool active {
+            get {
+                return active;
+            }
+
+            set {
+                if (value != active) {
+                    if (value) {
+                        reset ();
+                    } else if (state == State.RINGING) {
+                        bell.stop ();
+                    }
+                }
+                active = value;
+            }
+        }
+
+        public bool equal (AlarmItem a) {
+            return a.name == name && a.hour == hour && a.minute == minute && compare_days ();
+        }
+
+        private bool compare_days () {
+            return true;
+        }
+
+        private void reset () {
+            update_alarm_time ();
+            update_snooze_time (alarm_time);
+            state = State.READY;
+        }
+
+        private void update_alarm_time () {
+        }
+
+        private void update_snooze_time(DateTime start_time) {
+            // self.snooze_time = start_time + 9 * TimeSpan.MINUTES;
+        }
+
+        private void ring () {
+            bell.ring ();
+            state = State.RINGING;
+        }
+
+        private void snooze () {
+            bell.stop ();
+            state = State.SNOOZING;
+        }
+
+        private void stop () {
+            bell.stop ();
+            update_snooze_time (alarm_time);
+            state = State.READY;
+        }
+
+        // Updates the state and ringing times of the AlarmItem and
+        // rings or stops the alarm as required, depending on the
+        // current time. Returns true if the state changed, false
+        // otherwise.
+        public bool tick () {
+            if (!active) {
+                return false;
+            }
+
+            State last_state = state;
+/*
+            DateTime datetime = WallClock.get_default ().datetime;
+            if (state != State.RINGING) {
+                if (datetime >= alarm_time) {
+                    ring_start_time = alarm_time;
+                    update_snooze_time (alarm_time);
+                    update_alarm_time ();
+                    ring ();
+                } else if (datetime >= snooze_time) {
+                    ring_start_time = snooze_time;
+                    update_snooze_time (snooze_time);
+                    ring ();
+                }
+            } else if (datetime >= ring_start_time + RING_DURATION) {
+                stop ();
+            }
+*/
+            return state != last_state;
+        }
+
+        public GLib.Variant serialize () {
+            return new GLib.Variant.string ("");
+        }
+    }
+
+    public class Alarms : Gtk.Notebook, Clock {
+        public string label { get; protected set; }
+
+        public Alarms (Embed embed) {
+            Object (label: _("Alarms"), show_tabs: false);
+
+            var todo = new Gtk.Label ("port me!");
+            todo.show ();
+            append_page (todo);
+
+            show_all ();
+        }
+    }
+
+}
diff --git a/src/application.vala b/src/application.vala
new file mode 100644
index 0000000..9686a02
--- /dev/null
+++ b/src/application.vala
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+
+    public class Application : Gtk.Application {
+        static bool print_version;
+        const OptionEntry[] option_entries = {
+            { "version", 'v', 0, OptionArg.NONE, ref print_version, N_("Print version information and exit"), null },
+            { null }
+        };
+
+        const GLib.ActionEntry[] action_entries = {
+            { "quit", on_quit_activate }
+        };
+
+        private Window window;
+
+        public Application () {
+            Object (application_id: "org.gnome.clocks");
+
+            add_action_entries (action_entries, this);
+        }
+
+        protected override void activate () {
+            if (window == null) {
+                window = new Window (this);
+            }
+            window.present ();
+        }
+
+        protected override void startup () {
+            base.startup ();
+
+            // FIXME: move the css in gnome-theme-extras
+            try {
+                var css_provider = new Gtk.CssProvider ();
+                css_provider.load_from_path (Utils.get_css_file("gnome-clocks.css"));
+                Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default(),
+                                                          css_provider,
+                                                          Gtk.STYLE_PROVIDER_PRIORITY_USER);
+            } catch (GLib.Error error) {
+                warning (error.message);
+            }
+
+            var builder = new Gtk.Builder ();
+            try {
+                builder.add_from_resource ("/org/gnome/clocks/ui/menu.ui");
+            } catch (GLib.Error error) {
+                warning (error.message);
+            }
+
+            var app_menu = builder.get_object ("appmenu") as MenuModel;
+            set_app_menu (app_menu);
+        }
+
+        protected override bool local_command_line ([CCode (array_length = false, array_null_terminated = true)] ref unowned string[] arguments, out int exit_status) {
+            var ctx = new OptionContext ("");
+
+            ctx.add_main_entries (option_entries, Config.GETTEXT_PACKAGE);
+            ctx.add_group (Gtk.get_option_group (true));
+
+            // Workaround for bug #642885
+            unowned string[] argv = arguments;
+
+            try {
+                ctx.parse (ref argv);
+            } catch (Error e) {
+                exit_status = 1;
+                return true;
+            }
+
+            if (print_version) {
+                print ("%s %s\n", Environment.get_application_name (), Config.VERSION);
+                exit_status = 0;
+                return true;
+            }
+
+            return base.local_command_line (ref arguments, out exit_status);
+        }
+
+        void on_quit_activate () {
+            quit ();
+        }
+    }
+
+}
diff --git a/src/clock.vala b/src/clock.vala
new file mode 100644
index 0000000..1a8f5e6
--- /dev/null
+++ b/src/clock.vala
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+
+    public interface Clock : GLib.Object {
+        public abstract string label { get; protected set; }
+
+        public virtual void update_toolbar (Toolbar toolbar) {
+        }
+    }
+}
diff --git a/src/config.vapi b/src/config.vapi
new file mode 100644
index 0000000..b286a75
--- /dev/null
+++ b/src/config.vapi
@@ -0,0 +1,8 @@
+[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")]
+namespace Config {
+    public const string VERSION;
+    public const string GETTEXT_PACKAGE;
+    public const string GNOMELOCALEDIR;
+    public const string DATADIR;
+}
+
diff --git a/src/gnome-clocks.gresource.xml b/src/gnome-clocks.gresource.xml
new file mode 100644
index 0000000..83ef1ad
--- /dev/null
+++ b/src/gnome-clocks.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/clocks/ui">
+    <file preprocess="xml-stripblanks">menu.ui</file>
+  </gresource>
+</gresources>
diff --git a/src/gnome-desktop-3.0.vapi b/src/gnome-desktop-3.0.vapi
new file mode 100644
index 0000000..0a8aa5c
--- /dev/null
+++ b/src/gnome-desktop-3.0.vapi
@@ -0,0 +1,16 @@
+/* gnome-desktop-3.0.vapi generated by vapigen, do not modify. */
+
+[CCode (cprefix = "Gnome", gir_namespace = "GnomeDesktop", gir_version = "3.0", lower_case_cprefix = "gnome_")]
+namespace Gnome {
+	[CCode (cheader_filename = "libgnome-desktop/gnome-wall-clock.h", type_id = "gnome_wall_clock_get_type ()")]
+	public class WallClock : GLib.Object {
+		[CCode (has_construct_function = false)]
+		protected WallClock ();
+		[CCode (cname = "gnome_wall_clock_get_clock")]
+		public unowned string get_clock ();
+		[NoAccessorMethod]
+		public string clock { owned get; }
+		[NoAccessorMethod]
+		public bool time_only { get; set; }
+	}
+}
diff --git a/src/main.vala b/src/main.vala
new file mode 100644
index 0000000..59ec017
--- /dev/null
+++ b/src/main.vala
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+int main (string[] args) {
+    Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.GNOMELOCALEDIR);
+    Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
+    Intl.textdomain (Config.GETTEXT_PACKAGE);
+
+    if (GtkClutter.init (ref args) < 0) {
+        error ("Failed to initialize GtkClutter");
+    }
+
+    var app = new Clocks.Application ();
+    return app.run (args);
+}
diff --git a/src/menu.ui b/src/menu.ui
new file mode 100644
index 0000000..105f87b
--- /dev/null
+++ b/src/menu.ui
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <menu id="appmenu">
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">_New</attribute>
+        <attribute name="action">win.new</attribute>
+        <attribute name="accel">&lt;Primary&gt;n</attribute>
+      </item>
+      <item>
+        <attribute name="label" translatable="yes">_About</attribute>
+        <attribute name="action">win.about</attribute>
+      </item>
+    </section>
+    <section>
+      <item>
+        <attribute name="label" translatable="yes">_Quit</attribute>
+        <attribute name="action">app.quit</attribute>
+        <attribute name="accel">&lt;Primary&gt;q</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/src/stopwatch.vala b/src/stopwatch.vala
new file mode 100644
index 0000000..62ad1be
--- /dev/null
+++ b/src/stopwatch.vala
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+
+    public class Stopwatch : Gtk.Box, Clock {
+        private enum State {
+            RESET,
+            RUNNING,
+            STOPPED
+        }
+
+        private enum LapsColumn {
+            LAP,
+            SPLIT,
+            TOTAL,
+            COLUMNS
+        }
+
+        private State state;
+        private GLib.Timer timer;
+        private uint timeout_id;
+        private int current_lap;
+        private double last_lap_time;
+        private Gtk.Label time_label;
+        private Gtk.Button left_button;
+        private Gtk.Label left_label;
+        private Gtk.Button right_button;
+        private Gtk.Label right_label;
+        private Gtk.ListStore laps_model;
+        private Gtk.TreeView laps_view;
+
+        public string label { get; protected set; }
+
+        public Stopwatch (Embed embed) {
+            Object (label: _("Stopwatch"));
+
+            state = State.RESET;
+            timer = new GLib.Timer ();
+            timeout_id = 0;
+            current_lap = 0;
+            last_lap_time = 0;
+
+            set_orientation (Gtk.Orientation.VERTICAL);
+            map.connect (on_stopwatch_mapped);
+            unmap.connect (on_stopwatch_unmapped);
+
+            var grid = new Gtk.Grid ();
+            grid.set_margin_top (36);
+            grid.set_margin_bottom (60);
+            grid.set_halign (Gtk.Align.CENTER);
+            grid.set_row_spacing (24);
+            grid.set_column_spacing (24);
+            grid.set_column_homogeneous (true);
+            pack_start (grid, false, false, 0);
+
+            time_label = new Gtk.Label ("");
+            grid.attach (time_label, 0, 0, 2, 1);
+            update_time_label ();
+
+            left_button = new Gtk.Button ();
+            left_button.get_style_context ().add_class ("clocks-button");
+            left_button.get_style_context ().add_class ("clocks-go");
+            left_button.set_size_request (200, -1);
+            left_label = new Gtk.Label (_("Start"));
+            left_button.add (left_label);
+            grid.attach (left_button, 0, 1, 1, 1);
+
+            right_button = new Gtk.Button ();
+            right_button.get_style_context ().add_class ("clocks-button");
+            right_button.set_size_request (200, -1);
+            right_button.set_sensitive (false);
+            right_label = new Gtk.Label (_("Reset"));
+            right_button.add (right_label);
+            grid.attach (right_button, 1, 1, 1, 1);
+
+            left_button.clicked.connect (on_left_button_clicked);
+            right_button.clicked.connect (on_right_button_clicked);
+
+            laps_model = new Gtk.ListStore (LapsColumn.COLUMNS, typeof (string), typeof (string), typeof (string));
+
+            var cell = new Gtk.CellRendererText ();
+            cell.set_property ("xalign", 1.0);
+            var n_column = new Gtk.TreeViewColumn.with_attributes (_("Lap"), cell, "markup", LapsColumn.LAP, null);
+            n_column.set_expand (false);
+            n_column.set_property ("alignment", 1.0);
+
+            cell = new Gtk.CellRendererText ();
+            cell.set_property ("xalign", 1.0);
+            var split_column = new Gtk.TreeViewColumn.with_attributes (_("Split"), cell, "markup", LapsColumn.SPLIT, null);
+            split_column.set_expand (true);
+            split_column.set_property ("alignment", 1.0);
+
+            cell = new Gtk.CellRendererText ();
+            cell.set_property("xalign", 1.0);
+            var tot_column = new Gtk.TreeViewColumn.with_attributes (_("Total"), cell, "markup", LapsColumn.TOTAL, null);
+            tot_column.set_expand (true);
+            tot_column.set_property ("alignment", 1.0);
+
+            laps_view = new Gtk.TreeView.with_model (laps_model);
+            laps_view.get_style_context ().add_class ("clocks-laps");
+            laps_view.append_column (n_column);
+            laps_view.append_column (split_column);
+            laps_view.append_column (tot_column);
+
+            var scroll = new Gtk.ScrolledWindow (null, null);
+            scroll.get_style_context ().add_class ("clocks-laps-scroll");
+            scroll.set_shadow_type (Gtk.ShadowType.IN);
+            scroll.set_vexpand (true);
+            scroll.add (laps_view);
+            pack_start (scroll, true, true, 0);
+
+            show_all ();
+        }
+
+        private void on_stopwatch_unmapped (Gtk.Widget widget) {
+            if (state == State.RUNNING) {
+                remove_timeout ();
+            }
+        }
+
+        private void on_stopwatch_mapped (Gtk.Widget widget) {
+            if (state == State.RUNNING) {
+                update_time_label ();
+                add_timeout ();
+            }
+        }
+
+        private void on_left_button_clicked (Gtk.Button button) {
+            switch (state) {
+            case State.RESET:
+            case State.STOPPED:
+                start ();
+                break;
+            case State.RUNNING:
+                stop ();
+                break;
+            default:
+                assert_not_reached ();
+            }
+        }
+
+        private void on_right_button_clicked (Gtk.Button button) {
+            switch (state) {
+            case State.STOPPED:
+                reset ();
+                break;
+            case State.RUNNING:
+                lap ();
+                break;
+            default:
+                assert_not_reached ();
+            }
+        }
+
+        private void start () {
+            if (state == State.RESET) {
+                timer.start ();
+            } else {
+                timer.continue ();
+            }
+            state = State.RUNNING;
+            add_timeout ();
+            left_label.set_text (_("Stop"));
+            left_button.get_style_context ().add_class ("clocks-stop");
+            right_button.set_sensitive (true);
+            right_label.set_text (_("Lap"));
+        }
+
+        private void stop () {
+            timer.stop ();
+            state = State.STOPPED;
+            remove_timeout ();
+            left_label.set_text (_("Continue"));
+            left_button.get_style_context ().remove_class ("clocks-stop");
+            left_button.get_style_context ().add_class ("clocks-go");
+            right_button.set_sensitive (true);
+            right_label.set_text (_("Reset"));
+        }
+
+        private void reset () {
+            timer.reset ();
+            state = State.RESET;
+            remove_timeout ();
+            update_time_label ();
+            left_label.set_text (_("Start"));
+            left_button.get_style_context ().add_class ("clocks-go");
+            right_button.set_sensitive (false);
+            current_lap = 0;
+            laps_model.clear ();
+        }
+
+        private void lap () {
+            current_lap += 1;
+            var e = timer.elapsed ();
+            var split = e - last_lap_time;
+            last_lap_time = e;
+
+            int h;
+            int m;
+            double s;
+            time_to_hms (e, out h, out m, out s);
+
+            int split_h;
+            int split_m;
+            double split_s;
+            time_to_hms (split, out split_h, out split_m, out split_s);
+
+            var n_label = "<span color='dimgray'> %d </span>".printf (current_lap);
+
+            string split_label;
+            if (split_h > 0) {
+                split_label = "<span size ='larger'>%i:%02i:%05.2f</span>".printf (split_h, split_m, split_s);
+            } else {
+                split_label = "<span size ='larger'>%02i:%05.2f</span>".printf (split_m, split_s);
+            }
+
+            string tot_label;
+            if (h > 0) {
+                tot_label = "<span size ='larger'>%i:%02i:%05.2f</span>".printf (h, m, s);
+            } else {
+                tot_label = "<span size ='larger'>%02i:%05.2f</span>".printf (m, s);
+            }
+
+            Gtk.TreeIter i;
+            laps_model.append (out i);
+            laps_model.set (i,
+                            LapsColumn.LAP, n_label,
+                            LapsColumn.SPLIT, split_label,
+                            LapsColumn.TOTAL, tot_label,
+                           -1);
+            var p = laps_model.get_path (i);
+            laps_view.scroll_to_cell (p, null, false, 0, 0);
+        }
+
+        private void add_timeout () {
+            if (timeout_id == 0) {
+                timeout_id = Timeout.add (100, update_time_label);
+            }
+        }
+
+        private void remove_timeout () {
+            if (timeout_id != 0) {
+                Source.remove (timeout_id);
+                timeout_id = 0;
+            }
+        }
+
+        private bool update_time_label () {
+            int h = 0;
+            int m = 0;
+            double s = 0;
+            if (state != State.RESET) {
+                time_to_hms (timer.elapsed (), out h, out m, out s);
+            }
+
+            if (h > 0) {
+                time_label.set_markup ("<span font_desc=\"64.0\">%i:%02i:%04.1f</span>".printf (h, m, s));
+            } else {
+                time_label.set_markup ("<span font_desc=\"64.0\">%02i:%04.1f</span>".printf (m, s));
+            }
+
+            return true;
+        }
+
+        private void time_to_hms (double t, out int h, out int m, out double s) {
+            s = t;
+            h = (int) s / 3600;
+            s = t % 3600;
+            m = (int) s / 60;
+            s = s % 60;
+        }
+
+        public void update_toolbar (Toolbar toolbar) {
+            toolbar.clear ();
+            toolbar.mode = Toolbar.Mode.NORMAL;
+        }
+    }
+
+}
diff --git a/src/timer.vala b/src/timer.vala
new file mode 100644
index 0000000..9398fd3
--- /dev/null
+++ b/src/timer.vala
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+
+    public class Timer : Gtk.Notebook, Clock {
+        public string label { get; protected set; }
+
+        public Timer (Embed embed) {
+            Object (label: _("Timer"), show_tabs: false);
+
+            var todo = new Gtk.Label ("port me!");
+            todo.show ();
+            append_page (todo);
+
+            show_all ();
+        }
+    }
+
+}
diff --git a/src/utils.vala b/src/utils.vala
new file mode 100644
index 0000000..17e7ec1
--- /dev/null
+++ b/src/utils.vala
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+
+    namespace Utils {
+        public string get_css_file (string file_name) {
+            return Path.build_filename (Config.DATADIR, "gnome-clocks", "css", file_name);
+        }
+    }
+
+    // TODO: For now we are wrapping Gnome's clock, but we should probably
+    // implement our own class, maybe using gnome-datetime-source
+    // Especially if we want to try to use CLOCK_REALTIME_ALARM
+    // see https://bugzilla.gnome.org/show_bug.cgi?id=686115
+    public class WallClock : Object {
+        private static WallClock instance;
+
+        public signal void tick ();
+
+        private Gnome.WallClock wc;
+
+        private WallClock () {
+            wc = GLib.Object.new (typeof (Gnome.WallClock)) as Gnome.WallClock;
+            wc.notify["clock"].connect (() => {
+                update ();
+                tick ();
+            });
+
+            update ();
+        }
+
+        public WallClock get_default () {
+            if (instance == null) {
+                instance = new WallClock ();
+            }
+            return instance;
+        }
+
+        // provide various types/objects of the same time, to be used directly
+        // in AlarmItem and ClockItem, so they don't need to call these
+        // functions themselves all the time (they only care about minutes).
+        private void update () {
+//            self.time = time.time()
+//            self.localtime = time.localtime(self.time)
+//            self.datetime = datetime.datetime.fromtimestamp(self.time)
+        }
+    }
+
+    public class Bell : Object {
+        private GLib.Settings settings;
+        private Canberra.Context? canberra;
+        private string soundtheme;
+        private string sound;
+        private Notify.Notification? notification;
+
+        public Bell (string soundid, string title, string msg) {
+            settings = new GLib.Settings("org.gnome.desktop.sound");
+
+            if (Canberra.Context.create (out canberra) < 0) {
+                warning ("Sound will not be available");
+                canberra = null;
+            }
+
+            soundtheme = settings.get_string ("theme-name");
+            sound = soundid;
+
+            notification = null;
+            if (Notify.is_initted() || Notify.init ("GNOME Clocks")) {
+                notification = new Notify.Notification (title, msg, "gnome-clocks");
+                notification.set_hint_string ("desktop-entry", "gnome-clocks");
+            } else {
+                warning ("Could not initialize notification");
+            }
+        }
+
+        public void ring () {
+            if (canberra != null) {
+                canberra.play (1,
+                               Canberra.PROP_EVENT_ID, sound,
+                               Canberra.PROP_CANBERRA_XDG_THEME_NAME, soundtheme,
+                               Canberra.PROP_MEDIA_ROLE, "alarm",
+                               null);
+            }
+
+            if (notification != null) {
+                try {
+                    notification.show ();
+                } catch (GLib.Error error) {
+                    warning (error.message);
+                }
+            }
+        }
+
+        public void stop () {
+            if (canberra != null) {
+                canberra.cancel (1);
+            }
+        }
+    }
+
+}
diff --git a/src/widgets.vala b/src/widgets.vala
new file mode 100644
index 0000000..a8f8551
--- /dev/null
+++ b/src/widgets.vala
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+
+    public class Toolbar : Gd.MainToolbar {
+        public enum Mode {
+            NORMAL,
+            SELECTION,
+            STANDALONE
+        }
+
+        private int n_clocks;
+        private List<Gtk.Widget> buttons;
+
+        public Mode mode { get; set; }
+
+        public signal void clock_changed (int clock);
+
+        public Toolbar () {
+            Object (vexpand: false);
+            n_clocks = 0;
+            set_show_modes (true);
+        }
+
+        public int add_clock (Clock clock) {
+            int n = n_clocks;
+            n_clocks++;
+            var button = add_mode (clock.label) as Gtk.ToggleButton;
+            button.toggled.connect(() => {
+                if (button.active) {
+                    clock_changed (n);
+                }
+            });
+
+            return n;
+        }
+
+        // we wrap add_button so that we can keep track of which
+        // buttons to remove in clear() without removing the radio buttons
+        public Gtk.Widget add_button (string? icon_name, string? label, bool pack_start) {
+            var button = base.add_button (icon_name, label, pack_start);
+            buttons.prepend (button);
+            return button;
+        }
+
+        public void clear () {
+            foreach (Gtk.Widget button in buttons) {
+                button.destroy ();
+            }
+        }
+    }
+
+    public class IconView : Gtk.IconView {
+        public enum Mode {
+            NORMAL,
+            SELECTION
+        }
+
+        public Mode mode { get; set; }
+
+        public IconView () {
+            Object (mode: Mode.NORMAL);
+        }
+    }
+
+    private class FloatingToolbar {
+        private const int DEFAULT_WIDTH = 300;
+
+        private GtkClutter.Actor actor;
+        private Gtk.Box button_box;
+        private Clutter.Transition? transition;
+
+        public FloatingToolbar (Clutter.Actor parent_actor) {
+            var widget = new Gtk.Toolbar ();
+            widget.set_show_arrow (false);
+            widget.set_icon_size (Gtk.IconSize.LARGE_TOOLBAR);
+            widget.get_style_context().add_class ("osd");
+            widget.set_size_request (FloatingToolbar.DEFAULT_WIDTH, -1);
+
+            actor = new GtkClutter.Actor.with_contents (widget);
+            actor.set_opacity (0);
+            Gdk.RGBA transparent = { 0, 0, 0, 0};
+            actor.get_widget().override_background_color (0, transparent);
+
+            var constraint = new Clutter.AlignConstraint (parent_actor, Clutter.AlignAxis.X_AXIS, 0.50f);
+            actor.add_constraint (constraint);
+
+            constraint = new Clutter.AlignConstraint (parent_actor, Clutter.AlignAxis.Y_AXIS, 0.95f);
+            actor.add_constraint (constraint);
+
+            button_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
+            var item = new Gtk.ToolItem ();
+            item.set_expand (true);
+            item.add (button_box);
+            widget.insert (item, -1);
+
+            widget.show_all ();
+            actor.hide ();
+
+            transition = null;
+        }
+
+        public Clutter.Actor get_actor () {
+            return actor;
+        }
+
+        public void add_widget (Gtk.Widget w) {
+            w.show ();
+            button_box.pack_start (w, true, true, 0);
+        }
+
+        public void clear () {
+            foreach (Gtk.Widget w in button_box.get_children ()) {
+                button_box.remove (w);
+            }
+        }
+
+        public void fade_in () {
+            actor.show ();
+            actor.save_easing_state ();
+            actor.set_easing_duration (300);
+            actor.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD);
+            actor.set_opacity (255);
+            actor.restore_easing_state ();
+        }
+
+        public void fade_out () {
+            actor.save_easing_state ();
+            actor.set_easing_duration (300);
+            actor.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD);
+            actor.set_opacity (0);
+            actor.restore_easing_state ();
+            if (transition != null) {
+                transition = actor.get_transition ("opacity");
+                transition.completed.connect(() => {
+                    if (actor.opacity == 0) {
+                        actor.hide ();
+                    }
+                    transition = null;
+                });
+            }
+        }
+    }
+
+    // Set can-focus to false and override key-press andvkey-release so
+    // that we skip all Clutter key event handling and we let the
+    // contained gtk widget do their thing
+    // See https://bugzilla.gnome.org/show_bug.cgi?id=684988
+    public class KeyboardEmbed : GtkClutter.Embed {
+        public KeyboardEmbed () {
+            set_can_focus (false);
+        }
+
+        public override bool key_press_event (Gdk.EventKey event) {
+            return false;
+        }
+
+        public override bool key_release_event (Gdk.EventKey event) {
+            return false;
+        }
+    }
+
+    public class Embed : KeyboardEmbed {
+        private Gtk.Widget gtk_widget;
+        private Clutter.BinLayout overlay_layout;
+        private Clutter.Actor actor;
+        private Clutter.BoxLayout contents_layout;
+        private Clutter.Actor contents_actor;
+        private Clutter.BinLayout view_layout;
+        private Clutter.Actor view_actor;
+        private Clutter.Actor widget_actor;
+        private Clutter.Actor background;
+        private FloatingToolbar floating_toolbar;
+
+        public Embed (Gtk.Widget widget) {
+            gtk_widget = widget;
+
+            set_use_layout_size (true);
+
+            var stage = get_stage ();
+
+            var constraint = new Clutter.BindConstraint (stage, Clutter.BindCoordinate.SIZE, 0);
+            overlay_layout = new Clutter.BinLayout (Clutter.BinAlignment.START,
+                                                    Clutter.BinAlignment.START);
+            actor = new Clutter.Box (overlay_layout);
+            actor.add_constraint (constraint);
+            stage.add_actor (actor);
+
+            contents_layout = new Clutter.BoxLayout ();
+            contents_layout.set_vertical (true);
+            contents_actor = new Clutter.Box (contents_layout);
+            overlay_layout.add (contents_actor,
+                                Clutter.BinAlignment.FILL,
+                                Clutter.BinAlignment.FILL);
+
+            view_layout = new Clutter.BinLayout (Clutter.BinAlignment.START,
+                                                 Clutter.BinAlignment.START);
+            view_actor = new Clutter.Box (view_layout);
+            contents_layout.set_expand (view_actor, true);
+            contents_layout.set_fill (view_actor, true, true);
+            contents_actor.add_actor (view_actor);
+
+            widget_actor = new GtkClutter.Actor.with_contents (gtk_widget);
+            view_layout.add (widget_actor,
+                             Clutter.BinAlignment.FILL,
+                             Clutter.BinAlignment.FILL);
+
+            floating_toolbar = new FloatingToolbar (contents_actor);
+            overlay_layout.add (floating_toolbar.get_actor(),
+                                Clutter.BinAlignment.FIXED,
+                                Clutter.BinAlignment.FIXED);
+
+            // also pack a white background to use for flash transition
+            // between window modes
+            background = new Clutter.Actor ();
+            background.set_background_color (Clutter.Color.from_string ("white"));
+            view_layout.add (background,
+                             Clutter.BinAlignment.FILL,
+                             Clutter.BinAlignment.FILL);
+            view_actor.set_child_below_sibling (background, null);
+        }
+
+/*
+    private void flash_finished (Clutter.Actor actor, string name, bool is_finished) {
+        view_actor.set_child_below_sibling (background, null);
+        background.disconnect_by_func (flash_finished);
+    }
+
+    private void flash(action):
+        background.save_easing_state()
+        background.set_easing_duration(0)
+        viewActor.set_child_above_sibling(self._background, None)
+        self._background.set_opacity(255)
+        self._background.restore_easing_state()
+
+        action()
+
+        self._background.save_easing_state()
+        self._background.set_easing_duration(200)
+        self._background.set_easing_mode(Clutter.AnimationMode.LINEAR)
+        self._background.set_opacity(0)
+        self._background.restore_easing_state()
+        self._background.connect('transition-stopped::opacity', self._spotlight_finished)
+*/
+
+        public void show_floatingbar (Gtk.Widget widget) {
+            floating_toolbar.clear ();
+            floating_toolbar.add_widget (widget);
+            floating_toolbar.fade_in ();
+        }
+
+        public void hide_floatingbar () {
+            floating_toolbar.fade_out ();
+        }
+    }
+
+}
diff --git a/src/window.vala b/src/window.vala
new file mode 100644
index 0000000..b9da9ae
--- /dev/null
+++ b/src/window.vala
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+
+    public class Window : Gtk.ApplicationWindow {
+        // Default size is enough for two rows of 4 clocks
+        private const int DEFAULT_WIDTH = 788;
+        private const int DEFAULT_HEIGHT = 540;
+
+        private const GLib.ActionEntry[] action_entries = {
+            { "new", on_new_activate },
+            { "about", on_about_activate }
+        };
+
+        private Toolbar toolbar;
+        private Gtk.Notebook notebook;
+        private Embed embed;
+        private World world;
+        private Alarms alarms;
+        private Stopwatch stopwatch;
+        private Timer timer;
+
+        public Window (Application app) {
+            Object (application: app, title: _("Clocks"));
+
+            set_hide_titlebar_when_maximized (true);
+            add_action_entries (action_entries, this);
+
+            set_size_request (DEFAULT_WIDTH, DEFAULT_HEIGHT);
+
+            notebook = new Gtk.Notebook ();
+            notebook.set_show_tabs (false);
+            notebook.set_show_border (true);
+            notebook.get_style_context ().add_class ("clocks-content-view");
+            notebook.get_style_context ().add_class ("view");
+            notebook.get_style_context ().add_class ("content-view");
+
+            embed = new Embed (notebook);
+
+            world = new World (embed);
+            alarms = new Alarms (embed);
+            stopwatch = new Stopwatch (embed);
+            timer = new Timer (embed);
+
+            toolbar = new Toolbar ();
+            toolbar.add_clock (world);
+            toolbar.add_clock (alarms);
+            toolbar.add_clock (stopwatch);
+            toolbar.add_clock (timer);
+
+            notebook.append_page (world, null);
+            notebook.append_page (alarms, null);
+            notebook.append_page (stopwatch, null);
+            notebook.append_page (timer, null);
+
+            toolbar.clock_changed.connect ((c) => {
+                notebook.set_current_page (c);
+            });
+            notebook.switch_page.connect ((w, i) => {
+                Clock clock = w as Clock;
+                clock.update_toolbar (toolbar);
+            });
+
+            notebook.set_current_page (0);
+            world.update_toolbar (toolbar);
+
+            var vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
+            vbox.pack_start (toolbar, false, false, 0);
+            vbox.pack_end (embed, true, true, 0);
+            add (vbox);
+
+            show_all ();
+        }
+
+        void on_new_activate () {
+        }
+
+        void on_alarm_ringing () {
+        }
+
+        void on_about_activate () {
+            const string copyright = "Copyright \xc2\xa9 2011 Collabora Ltd.\n" +
+                                     "Copyright \xc2\xa9 2012-2013 Collabora Ltd., Seif Lotfy, Emily Gonyer\n" +
+                                     "Eslam Mostafa, Paolo Borelli, Volker Sobek\n";
+
+            const string authors[] = {
+                "Alex Anthony",
+                "Paolo Borelli",
+                "Allan Day",
+                "Piotr DrÄg",
+                "Emily Gonyer",
+                "MaÃl Lavault",
+                "Seif Lotfy",
+                "William Jon McCann",
+                "Eslam Mostafa",
+                "Bastien Nocera",
+                "Volker Sobek",
+                "Jakub Steiner",
+                null
+            };
+
+            Gtk.show_about_dialog (this,
+                                   "program-name", _("Gnome Clocks"),
+                                   "logo-icon-name", "gnome-clocks",
+                                   "version", Config.VERSION,
+                                   "comments", _("Utilities to help you with the time."),
+                                   "copyright", copyright,
+                                   "authors", authors,
+                                   "license-type", Gtk.License.GPL_2_0,
+                                   "wrap-license", false,
+                                   "translator-credits", _("translator-credits"),
+                                   null);
+        }
+    }
+}
diff --git a/src/world.vala b/src/world.vala
new file mode 100644
index 0000000..f3a0ae6
--- /dev/null
+++ b/src/world.vala
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013  Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+namespace Clocks {
+
+    public class World : Gtk.Notebook, Clock {
+        private enum Page {
+            OVERVIEW,
+            STANDALONE
+        }
+
+        public string label { get; protected set; }
+
+        private IconView iconview;
+
+        public World (Embed embed) {
+            Object (label: _("World"), show_tabs: false);
+
+            var todo = new Gtk.Label ("port me!");
+            todo.show ();
+            append_page (todo);
+
+            iconview = new IconView ();
+
+            show_all ();
+        }
+
+        public void update_toolbar (Toolbar toolbar) {
+            toolbar.clear ();
+            switch (get_current_page ()) {
+            case Page.OVERVIEW:
+                if (iconview.mode == IconView.Mode.SELECTION) {
+                    toolbar.mode = Toolbar.Mode.SELECTION;
+                    toolbar.add_button (null, _("Done"), false);
+                } else {
+                    toolbar.mode = Toolbar.Mode.NORMAL;
+                    toolbar.add_button (null, _("New"), true);
+                    toolbar.add_button ("object-select-symbolic", null, false);
+                }
+                break;
+            case Page.STANDALONE:
+                toolbar.mode = Toolbar.Mode.STANDALONE;
+                    toolbar.add_button ("go-previous-symbolic", null, true);
+//                toolbar.set_title (GLib.markup_escape_text (standalone.clock.name));
+                break;
+            default:
+                assert_not_reached ();
+            }
+        }
+    }
+
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]