[glabels/vala] Initial import of vala port work.



commit cfb682d6f2b802a776e9c607176b90b2580a086d
Author: Jim Evins <evins snaught com>
Date:   Sun Mar 4 00:31:54 2012 -0500

    Initial import of vala port work.

 .gitignore                                         |   11 +
 Makefile.am                                        |   59 +-
 autogen.sh                                         |   15 +-
 configure.ac                                       |  373 +---
 configure.ac.old                                   |  378 ++++
 data/Makefile.am                                   |    9 +-
 data/icons/22x22/Makefile.am                       |    9 +-
 .../22x22/actions/glabels-align-text-bottom.png    |  Bin 429 -> 0 bytes
 .../22x22/actions/glabels-align-text-middle.png    |  Bin 418 -> 0 bytes
 .../icons/22x22/actions/glabels-align-text-top.png |  Bin 396 -> 0 bytes
 data/icons/24x24/Makefile.am                       |    3 -
 .../24x24/actions/glabels-align-text-bottom.png    |  Bin 481 -> 0 bytes
 .../24x24/actions/glabels-align-text-middle.png    |  Bin 639 -> 0 bytes
 .../icons/24x24/actions/glabels-align-text-top.png |  Bin 398 -> 0 bytes
 data/icons/Makefile.am                             |    2 +-
 data/schemas/org.gnome.glabels-3.gschema.xml.in.in |    8 +-
 data/ui/Makefile.am                                |   10 +-
 data/ui/new-label-dialog.ui                        |  486 -----
 data/ui/new_label_dialog.ui                        |  135 ++
 data/ui/object-editor.ui                           |  424 +----
 data/ui/property-bar.ui                            |  103 +-
 glabels/Makefile.am                                |   87 +
 glabels/TMP_gdk_key.vala                           |   19 +
 glabels/color.vala                                 |  136 ++
 glabels/color_button.vala                          |  181 ++
 glabels/color_history.vala                         |  127 ++
 glabels/color_menu.vala                            |  253 +++
 glabels/color_menu_item.vala                       |   66 +
 glabels/color_node.vala                            |   93 +
 glabels/color_swatch.vala                          |  107 +
 glabels/enum_util.vala                             |  106 +
 glabels/file.vala                                  |  483 +++++
 glabels/file_util.vala                             |   58 +
 glabels/font_button.vala                           |  154 ++
 glabels/font_families.vala                         |  107 +
 glabels/font_history.vala                          |  101 +
 glabels/font_menu.vala                             |  138 ++
 glabels/font_menu_item.vala                        |   85 +
 glabels/font_sample.vala                           |  140 ++
 glabels/glabels.vala                               |   95 +
 glabels/handle.vala                                |  243 +++
 glabels/help.vala                                  |   99 +
 glabels/label.vala                                 | 1470 ++++++++++++++
 glabels/label_object.vala                          |  725 +++++++
 glabels/label_object_box.vala                      |  176 ++
 glabels/label_region.vala                          |   35 +
 glabels/label_state.vala                           |   93 +
 glabels/merge.vala                                 |   87 +
 glabels/merge_factory.vala                         |   78 +
 glabels/merge_field.vala                           |   33 +
 glabels/merge_none.vala                            |   72 +
 glabels/merge_record.vala                          |   47 +
 glabels/merge_text.vala                            |  395 ++++
 glabels/mini_preview.vala                          |  417 ++++
 glabels/new_label_dialog.vala                      |  218 ++
 glabels/prefs.vala                                 |  376 ++++
 glabels/print.vala                                 |  217 ++
 glabels/template_history.vala                      |   94 +
 glabels/test.vala                                  |  221 ++
 glabels/ui.vala                                    | 1391 +++++++++++++
 glabels/units_util.vala                            |   96 +
 glabels/view.vala                                  | 1232 ++++++++++++
 glabels/window.vala                                |  279 +++
 glabels/xml_label.vala                             |  504 +++++
 libglabels/Makefile.am                             |  122 +-
 libglabels/{lgl-str.h => category.vala}            |   44 +-
 libglabels/db.vala                                 |  788 ++++++++
 libglabels/lgl-category.c                          |  132 --
 libglabels/lgl-category.h                          |   62 -
 libglabels/lgl-db.c                                | 2123 --------------------
 libglabels/lgl-db.h                                |  197 --
 libglabels/lgl-paper.c                             |  147 --
 libglabels/lgl-paper.h                             |   70 -
 libglabels/lgl-str.c                               |  284 ---
 libglabels/lgl-template.c                          | 1438 -------------
 libglabels/lgl-template.h                          |  424 ----
 libglabels/lgl-units.c                             |  261 ---
 libglabels/lgl-units.h                             |   70 -
 libglabels/lgl-vendor.c                            |  128 --
 libglabels/lgl-vendor.h                            |   63 -
 libglabels/lgl-xml-category.c                      |  185 --
 libglabels/lgl-xml-category.h                      |   51 -
 libglabels/lgl-xml-paper.c                         |  192 --
 libglabels/lgl-xml-template.c                      | 1207 -----------
 libglabels/lgl-xml-template.h                      |   61 -
 libglabels/lgl-xml-vendor.c                        |  183 --
 libglabels/lgl-xml-vendor.h                        |   51 -
 libglabels/lgl-xml.c                               |  524 -----
 libglabels/lgl-xml.h                               |  119 --
 libglabels/libglabels-3.0.pc.in                    |   12 -
 libglabels/libglabels.h                            |   46 -
 libglabels/mini_preview_pixbuf.vala                |  156 ++
 libglabels/paper.vala                              |   51 +
 libglabels/str_util.vala                           |  213 ++
 libglabels/template.vala                           |  252 +++
 libglabels/template_coord.vala                     |   70 +
 libglabels/template_frame.vala                     |  123 ++
 libglabels/template_frame_cd.vala                  |  182 ++
 libglabels/template_frame_ellipse.vala             |  157 ++
 libglabels/template_frame_rect.vala                |  160 ++
 libglabels/template_frame_round.vala               |  128 ++
 libglabels/template_layout.vala                    |   86 +
 .../{libglabels-private.h => template_markup.vala} |   40 +-
 libglabels/template_markup_circle.vala             |   63 +
 libglabels/template_markup_ellipse.vala            |   83 +
 libglabels/template_markup_line.vala               |   66 +
 libglabels/template_markup_margin.vala             |  171 ++
 libglabels/template_markup_rect.vala               |   82 +
 libglabels/units.vala                              |  137 ++
 libglabels/{lgl-xml-paper.h => vendor.vala}        |   45 +-
 libglabels/xml_category.vala                       |  104 +
 libglabels/xml_paper.vala                          |  109 +
 libglabels/xml_template.vala                       |  744 +++++++
 libglabels/xml_util.vala                           |  244 +++
 libglabels/xml_vendor.vala                         |  104 +
 po/POTFILES.in                                     |  267 +---
 po/POTFILES.skip                                   |    3 -
 vapi/Makefile.am                                   |    5 +
 vapi/config.vapi                                   |   18 +
 119 files changed, 16255 insertions(+), 9751 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 897e589..11de99a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ glabels-*.tar.gz
 /aclocal.m4
 /autom4te.cache
 /autoscan.log
+/compile
 /config.guess
 /config.h
 /config.h.in
@@ -42,9 +43,19 @@ glabels-*.tar.gz
 /stamp-h1
 /xmldocs.make
 
+/libglabels/*.c
+/libglabels/*.h
+/libglabels/*.vapi
+/libglabels/*.stamp
 /libglabels/libglabels*.pc
+
 /libglbarcode/libglbarcode*.pc
 
+/glabels/*.c
+/glabels/*.stamp
+/glabels/test
+/glabels/glabels
+
 /src/marshal.[ch]
 /src/stock-pixmaps/stockpixbufs.h
 /src/cursors/cursor_pixdata.h
diff --git a/Makefile.am b/Makefile.am
index 0aec6e1..b23c823 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,50 +1,31 @@
-## Process this file with automake to produce Makefile.in
+NULL = 
+
+#Build in these directories:
 
 SUBDIRS = \
-	po \
 	libglabels \
-	libglbarcode \
-	src \
-	data \
+	glabels \
+	po \
 	templates \
+	data \
 	help \
-	docs
+	$(NULL)
+
+glabelsdocdir = ${prefix}/doc/glabels
+glabelsdoc_DATA = \
+	$(NULL)
 
 EXTRA_DIST = \
-	README \
-	COPYING.README_FIRST \
-	COPYING \
-	COPYING-DOCS \
-	COPYING-LIBS \
-	COPYING-TEMPLATES \
-	AUTHORS \
-	ChangeLog \
-	INSTALL \
-	NEWS \
-	TODO \
+	$(glabelsdoc_DATA) \
 	intltool-extract.in \
 	intltool-merge.in \
-	intltool-update.in \
-	gnome-doc-utils.make \
-	glabels.spec.in \
-	glabels.spec
-
-DISTCLEANFILES = gnome-doc-utils.make
+	intltool-update.in\
+	$(NULL)
 
-DISTCHECK_CONFIGURE_FLAGS = \
-	--disable-scrollkeeper	\
-	--enable-gtk-doc
+DISTCLEANFILES = \
+	intltool-extract \
+	intltool-merge \
+	intltool-update \
+	po/.intltool-merge-cache \
+	$(NULL)
 
-dist-hook:
-	@if test -d "$(srcdir)/.git"; \
-	then \
-		echo Creating ChangeLog && \
-		(GIT_DIR=$(top_srcdir)/.git \
-		  ./missing --run git log -M -C --name-status --date=short --no-color) | \
-		  fmt --split-only > ChangeLog.tmp \
-		&& mv -f ChangeLog.tmp $(top_distdir)/ChangeLog \
-		|| ( rm -f ChangeLog.tmp ; \
-		  echo Failed to generate ChangeLog >&2 ); \
-	else \
-		echo A git clone is required to generate a ChangeLog >&2; \
-	fi
diff --git a/autogen.sh b/autogen.sh
index 307c331..fe21110 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -6,17 +6,4 @@ test -z "$srcdir" && srcdir=.
 
 PKG_NAME="glabels"
 
-(test -f $srcdir/configure.ac \
-  && test -f $srcdir/README \
-  && test -d $srcdir/src) || {
-    echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
-    echo " top-level $PKG_NAME directory"
-    exit 1
-}
-
-which gnome-autogen.sh || {
-    echo "You need to install gnome-common from the GNOME CVS"
-    exit 1
-}
-USE_GNOME2_MACROS=1 USE_COMMON_DOC_BUILD=yes . gnome-autogen.sh
-
+. gnome-autogen.sh
diff --git a/configure.ac b/configure.ac
index f5d92c7..6e39130 100644
--- a/configure.ac
+++ b/configure.ac
@@ -5,7 +5,7 @@ dnl ---------------------------------------------------------------------------
 dnl - GLABELS version
 dnl ---------------------------------------------------------------------------
 m4_define([glabels_major_version], [3])
-m4_define([glabels_minor_version], [0])
+m4_define([glabels_minor_version], [99])
 m4_define([glabels_micro_version], [0])
 
 m4_define([glabels_version],
@@ -14,367 +14,104 @@ m4_define([glabels_version],
 dnl ---------------------------------------------------------------------------
 
 
-AC_PREREQ(2.64)
-AC_INIT([glabels],[glabels_version],
+AC_INIT([glabels], [glabels_version],
 	[http://bugzilla.gnome.org/enter_bug.cgi?product=glabels],
-	[glabels])
-
-AC_CONFIG_SRCDIR(src/glabels.c)
-
-AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
+        [glabels])
 
+AC_CONFIG_SRCDIR([Makefile.am])
+AC_CONFIG_HEADERS(config.h)
+AM_INIT_AUTOMAKE([dist-bzip2])
 AM_MAINTAINER_MODE
 m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
 
-AC_CONFIG_HEADERS(config.h)
-
-IT_PROG_INTLTOOL([0.21])
-
-PKG_PROG_PKG_CONFIG
-
-LT_INIT
-
-GNOME_DOC_INIT
-GTK_DOC_CHECK(1.0)
-
-GLIB_GSETTINGS
-
-AC_SEARCH_LIBS([strerror],[cposix])
 AC_PROG_CC
-AC_PROG_INSTALL
+AM_PROG_CC_C_O
+AC_DISABLE_STATIC
+AC_PROG_LIBTOOL
 
-GNOME_COMPILE_WARNINGS
+GNOME_DOC_INIT
 
-AC_PATH_PROG(GLIB_GENMARSHAL,         glib-genmarshal)
-AC_PATH_PROG(GDK_PIXBUF_CSOURCE,      gdk-pixbuf-csource)
 AC_PATH_PROG(GTK_UPDATE_ICON_CACHE,   gtk-update-icon-cache)
 
-
 dnl ---------------------------------------------------------------------------
 dnl - GLABELS branch
 dnl ---------------------------------------------------------------------------
-GLABELS_BRANCH=glabels-3.0
+GLABELS_BRANCH=glabels-4.0
 AC_SUBST(GLABELS_BRANCH)
 
 dnl ---------------------------------------------------------------------------
 dnl - LIBGLABELS branch
 dnl ---------------------------------------------------------------------------
-LIBGLABELS_BRANCH=libglabels-3.0
+LIBGLABELS_BRANCH=libglabels-4.0
 AC_SUBST(LIBGLABELS_BRANCH)
 
 dnl ---------------------------------------------------------------------------
 dnl - LIBGLBARCODE branch
 dnl ---------------------------------------------------------------------------
-LIBGLBARCODE_BRANCH=libglbarcode-3.0
+LIBGLBARCODE_BRANCH=libglbarcode-4.0
 AC_SUBST(LIBGLBARCODE_BRANCH)
 
-dnl ---------------------------------------------------------------------------
-dnl - LIBGLABELS API versioning
-dnl ---------------------------------------------------------------------------
-dnl From the libtool manual:
-dnl 1. Start with version information of `0:0:0' for each libtool library.
-dnl 2. Update the version information only immediately before a public release.
-dnl    More frequent updates are unnecessary, and only guarantee that the current
-dnl    interface number gets larger faster.
-dnl 3. If the library source code has changed at all since the last update, then increment
-dnl    revision (`c:r:a' becomes `c:r+1:a').
-dnl 4. If any interfaces have been added, removed, or changed since the last update,
-dnl    increment current, and set revision to 0.
-dnl 5. If any interfaces have been added since the last public release, then increment age.
-dnl 6. If any interfaces have been removed since the last public release, then set age
-dnl    to 0.
-LIBGLABELS_C=8
-LIBGLABELS_R=0
-LIBGLABELS_A=0
 
-LIBGLABELS_API_VERSION=${LIBGLABELS_C}:${LIBGLABELS_R}:${LIBGLABELS_A}
-AC_SUBST(LIBGLABELS_API_VERSION)
+AC_PATH_PROG(VALAC, valac, valac)
+AC_SUBST(VALAC)
 
-dnl ---------------------------------------------------------------------------
-dnl - LIBGLBARCODE API versioning
-dnl ---------------------------------------------------------------------------
-dnl From the libtool manual:
-dnl 1. Start with version information of `0:0:0' for each libtool library.
-dnl 2. Update the version information only immediately before a public release.
-dnl    More frequent updates are unnecessary, and only guarantee that the current
-dnl    interface number gets larger faster.
-dnl 3. If the library source code has changed at all since the last update, then increment
-dnl    revision (`c:r:a' becomes `c:r+1:a').
-dnl 4. If any interfaces have been added, removed, or changed since the last update,
-dnl    increment current, and set revision to 0.
-dnl 5. If any interfaces have been added since the last public release, then increment age.
-dnl 6. If any interfaces have been removed since the last public release, then set age
-dnl    to 0.
-LIBGLBARCODE_C=0
-LIBGLBARCODE_R=0
-LIBGLBARCODE_A=0
+AH_TEMPLATE([GETTEXT_PACKAGE], [Package name for gettext])
+GETTEXT_PACKAGE=glabels-4.0
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE")
+AC_SUBST(GETTEXT_PACKAGE)
+AM_GLIB_GNU_GETTEXT
+IT_PROG_INTLTOOL([0.35.0])
 
-LIBGLBARCODE_API_VERSION=${LIBGLBARCODE_C}:${LIBGLBARCODE_R}:${LIBGLBARCODE_A}
-AC_SUBST(LIBGLBARCODE_API_VERSION)
+AC_SUBST(CFLAGS)
+AC_SUBST(CPPFLAGS)
+AC_SUBST(LDFLAGS)
 
-dnl ---------------------------------------------------------------------------
-dnl - Library dependencies
-dnl ---------------------------------------------------------------------------
-dnl Required dependencies
-GLIB_REQUIRED=2.28.2
+GLIB_REQUIRED=2.28.0
 GTK_REQUIRED=3.0.9
 LIBXML_REQUIRED=2.7.8
-LIBRSVG_REQUIRED=2.32.0
-CAIRO_REQUIRED=1.10.0
-PANGO_REQUIRED=1.28.1
-
-dnl Optional dependencies
-LIBEBOOK_REQUIRED=2.30.3
-LIBBARCODE_REQUIRED=0.98
-LIBQRENCODE_REQUIRED=3.1.0
-LIBIEC16022_REQUIRED=0.2.4
-LIBZINT_REQUIRED=2.4.0
+GEE_REQUIRED=0.6.2.1
+CAIRO_REQUIRED=1.10.2
+GDK_PIXBUF_REQUIRED=2.24.0
 
-dnl Make above strings available for packaging files (e.g. rpm spec files)
-AC_SUBST(GLIB_REQUIRED)
-AC_SUBST(GTK_REQUIRED)
-AC_SUBST(LIBXML_REQUIRED)
-AC_SUBST(LIBRSVG_REQUIRED)
-AC_SUBST(CAIRO_REQUIRED)
-AC_SUBST(PANGO_REQUIRED)
-AC_SUBST(LIBEBOOK_REQUIRED)
-AC_SUBST(LIBBARCODE_REQUIRED)
-AC_SUBST(LIBQRENCODE_REQUIRED)
-AC_SUBST(LIBIEC16022_REQUIRED)
-AC_SUBST(LIBZINT_REQUIRED)
-
-
-dnl ---------------------------------------------------------------------------
-dnl - GLABELS prerequisites
-dnl ---------------------------------------------------------------------------
 PKG_CHECK_MODULES(GLABELS, [\
-	glib-2.0 >= $GLIB_REQUIRED \
-	gtk+-3.0 >= $GTK_REQUIRED \
-	libxml-2.0 >= $LIBXML_REQUIRED \
-	librsvg-2.0 >= $LIBRSVG_REQUIRED \
+			   glib-2.0 >= $GLIB_REQUIRED \
+			   gobject-2.0 >= $GLIB_REQUIRED \
+			   gtk+-3.0 >= $GTK_REQUIRED \
+			   libxml-2.0 >= $LIBXML_REQUIRED \
+			   gee-1.0 >= $GEE_REQUIRED \
 ])
 
 AC_SUBST(GLABELS_CFLAGS)
 AC_SUBST(GLABELS_LIBS)
 
-
-dnl ---------------------------------------------------------------------------
-dnl - LIBGLABELS more modest prerequisites
-dnl ---------------------------------------------------------------------------
 PKG_CHECK_MODULES(LIBGLABELS, [\
-	glib-2.0 >= $GLIB_REQUIRED \
-	gobject-2.0 >= $GLIB_REQUIRED \
-	libxml-2.0 >= $LIBXML_REQUIRED \
+			      glib-2.0 >= $GLIB_REQUIRED \
+			      gobject-2.0 >= $GLIB_REQUIRED \
+			      libxml-2.0 >= $LIBXML_REQUIRED \
+			      gee-1.0 >= $GEE_REQUIRED \
+                              cairo >= $CAIRO_REQUIRED \
+                              gdk-pixbuf-2.0 >= $GDK_PIXBUF_REQUIRED \
 ])
 
 AC_SUBST(LIBGLABELS_CFLAGS)
 AC_SUBST(LIBGLABELS_LIBS)
 
 
-dnl ---------------------------------------------------------------------------
-dnl - LIBGLBARCODE more modest prerequisites
-dnl ---------------------------------------------------------------------------
-PKG_CHECK_MODULES(LIBGLBARCODE, [\
-	glib-2.0 >= $GLIB_REQUIRED \
-	cairo >= $CAIRO_REQUIRED \
-	pango >= $PANGO_REQUIRED \
-	pangocairo >= $PANGO_REQUIRED \
-])
-
-AC_SUBST(LIBGLBARCODE_CFLAGS)
-AC_SUBST(LIBGLBARCODE_LIBS)
-
-
-dnl ---------------------------------------------------------------------------
-dnl - Check for optional evolution data server
-dnl ---------------------------------------------------------------------------
-AC_ARG_WITH(libebook,
-	    [AS_HELP_STRING([--without-libebook],[build without Evolution Data Server support])])
-have_libebook=no
-if test "x$with_libebook" != xno; then
-	PKG_CHECK_MODULES(LIBEBOOK, libebook-1.2 >= $LIBEBOOK_REQUIRED,
-			  [have_libebook=yes], [have_libebook=no])
-fi
-
-if test "x$have_libebook" = "xyes"; then
-	AC_DEFINE(HAVE_LIBEBOOK,1,[Define to 1 for EDS support])
-	AC_SUBST(LIBEBOOK_CFLAGS)
-	AC_SUBST(LIBEBOOK_LIBS)
-fi
-
+AC_CONFIG_FILES([Makefile
+	vapi/Makefile
+	libglabels/Makefile
+	glabels/Makefile
+	po/Makefile.in
+        templates/Makefile
+        data/Makefile
+        data/icons/Makefile
+        data/icons/16x16/Makefile
+        data/icons/22x22/Makefile
+        data/icons/24x24/Makefile
+        data/icons/32x32/Makefile
+        data/icons/48x48/Makefile
+        data/pixmaps/Makefile
+        data/ui/Makefile
+        help/Makefile])
 
-dnl ---------------------------------------------------------------------------
-dnl - Check for optional GNU Barcode backend
-dnl ---------------------------------------------------------------------------
-AC_ARG_WITH(libbarcode,
-	    [AS_HELP_STRING([--without-libbarcode],[build without GNU Barcode support])])
-have_libbarcode=no
-if test "x$with_libbarcode" != xno; then
-   AC_CHECK_LIB(barcode, Barcode_Create,
-		[have_libbarcode=yes], [have_libbarcode=no])
-fi
-
-if test "x$have_libbarcode" = "xyes"; then
-	AC_DEFINE(HAVE_LIBBARCODE,1,[Define to 1 for GNU Barcode support])
-	LIBBARCODE_CFLAGS=""
-	LIBBARCODE_LIBS="-lbarcode"
-	AC_SUBST(LIBBARCODE_CFLAGS)
-	AC_SUBST(LIBBARCODE_LIBS)
-else
-	help_libbarcode="(See http://www.gnu.org/software/barcode/barcode.html)"
-fi
-
-dnl ---------------------------------------------------------------------------
-dnl - Check for optional Zint backend
-dnl ---------------------------------------------------------------------------
-AC_ARG_WITH(libzint,
-	    [AS_HELP_STRING([--without-libzint],[build without Zint Barcode support])])
-have_libzint=no
-if test "x$with_libzint" != xno; then
-   AC_CHECK_LIB(zint, ZBarcode_Render,
-		[have_libzint=yes], [have_libzint=no])
-fi
-
-if test "x$have_libzint" = "xyes"; then
-	AC_DEFINE(HAVE_LIBZINT,1,[Define to 1 for Zint Barcode support])
-	LIBZINT_CFLAGS=""
-	LIBZINT_LIBS="-lzint"
-	AC_SUBST(LIBZINT_CFLAGS)
-	AC_SUBST(LIBZINT_LIBS)
-else
-	help_libzint="(See http://www.zint.org.uk)"
-fi
-
-
-dnl ---------------------------------------------------------------------------
-dnl - Check for optional QRencode Barcode backend
-dnl ---------------------------------------------------------------------------
-AC_ARG_WITH(libqrencode,
-	    [AS_HELP_STRING([--without-libqrencode],[build without QR code support])])
-have_libqrencode=no
-if test "x$with_libqrencode" != xno; then
-	PKG_CHECK_MODULES(LIBQRENCODE, libqrencode >= $LIBQRENCODE_REQUIRED,
-			  [have_libqrencode=yes], [have_libqrencode=no])
-fi
-
-if test "x$have_libqrencode" = "xyes"; then
-	AC_DEFINE(HAVE_LIBQRENCODE,1,[Define to 1 for QR code support])
-	AC_SUBST(LIBQRENCODE_CFLAGS)
-	AC_SUBST(LIBQRENCODE_LIBS)
-else
-	help_libqrencode="(See http://megaui.net/fukuchi/works/qrencode/index.en.html)"
-fi
-
-
-dnl ---------------------------------------------------------------------------
-dnl - Check for IEC16022 Barcode backend
-dnl ---------------------------------------------------------------------------
-AC_ARG_WITH(libiec16022,
-	    [AS_HELP_STRING([--without-libiec16022],[build without IEC 16022 support])])
-have_libiec16022=no
-if test "x$with_libiec16022" != xno; then
-	PKG_CHECK_MODULES(LIBIEC16022, libiec16022 >= $LIBIEC16022_REQUIRED,
-			  [have_libiec16022=yes], [have_libiec16022=no])
-fi
-
-if test "x$have_libiec16022" = "xyes"; then
-	AC_DEFINE(HAVE_LIBIEC16022,1,[Define to 1 for IEC 16022 support])
-	AC_SUBST(LIBIEC16022_CFLAGS)
-	AC_SUBST(LIBIEC16022_LIBS)
-else
-	help_libiec16022="(See http://datenfreihafen.org/projects/iec16022.html)"
-fi
-
-
-dnl ---------------------------------------------------------------------------
-dnl - Enable deprecation testing
-dnl ---------------------------------------------------------------------------
-AC_ARG_ENABLE(deprecations,
-              [AS_HELP_STRING([--enable-deprecations],[warn about deprecated usages [default=no]])],,
-              [enable_deprecations=no])
-
-if test "x$enable_deprecations" = "xyes"; then
-   DISABLE_DEPRECATED_CFLAGS="\
--DG_DISABLE_DEPRECATED \
--DGDK_DISABLE_DEPRECATED \
--DGTK_DISABLE_DEPRECATED \
--DGDK_PIXBUF_DISABLE_DEPRECATED \
-"
-   AC_SUBST(DISABLE_DEPRECATED_CFLAGS)
-fi
-
-
-dnl ---------------------------------------------------------------------------
-dnl - i18n support
-dnl ---------------------------------------------------------------------------
-GETTEXT_PACKAGE=${GLABELS_BRANCH}
-AC_SUBST(GETTEXT_PACKAGE)
-AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [Gettext package])
-
-AM_GLIB_GNU_GETTEXT
-
-
-dnl ---------------------------------------------------------------------------
-dnl - Makefiles, etc.
-dnl ---------------------------------------------------------------------------
-AC_CONFIG_FILES([
-Makefile
-libglabels/Makefile
-libglabels/${LIBGLABELS_BRANCH}.pc
-libglbarcode/Makefile
-libglbarcode/${LIBGLBARCODE_BRANCH}.pc
-src/Makefile
-src/cursors/Makefile
-src/pixmaps/Makefile
-data/Makefile
-data/desktop/Makefile
-data/icons/Makefile
-data/icons/16x16/Makefile
-data/icons/22x22/Makefile
-data/icons/24x24/Makefile
-data/icons/32x32/Makefile
-data/icons/48x48/Makefile
-data/man/Makefile
-data/mime/Makefile
-data/pixmaps/Makefile
-data/schemas/Makefile
-data/schemas/org.gnome.glabels-3.gschema.xml.in
-data/ui/Makefile
-templates/Makefile
-po/Makefile.in
-help/Makefile
-docs/Makefile
-docs/libglabels/Makefile
-docs/libglbarcode/Makefile
-glabels.spec
-])
 AC_OUTPUT
-
-
-dnl ---------------------------------------------------------------------------
-dnl - Print configuration information
-dnl ---------------------------------------------------------------------------
-echo "
-
-Configuration:
-
-        Package ................. ${PACKAGE}-${VERSION}
-        Installation prefix ..... ${prefix}
-        Source code location .... ${srcdir}
-        Compiler ................ ${CC} 
-
-
-Optional data merge backends:
-
-        Evolution Data Server ... ${have_libebook}
-
-
-Optional barcode backends:
-
-        GNU Barcode ............. ${have_libbarcode} ${help_libbarcode}
-        QR Code ................. ${have_libqrencode} ${help_libqrencode}
-        IEC 16022 ............... ${have_libiec16022} ${help_libiec16022}
-        Zint .................... ${have_libzint} ${help_libzint}
-
-
-"
diff --git a/configure.ac.old b/configure.ac.old
new file mode 100644
index 0000000..a52cd1a
--- /dev/null
+++ b/configure.ac.old
@@ -0,0 +1,378 @@
+dnl Process this file with autoconf to produce a configure script.
+
+
+dnl ---------------------------------------------------------------------------
+dnl - GLABELS version
+dnl ---------------------------------------------------------------------------
+m4_define([glabels_major_version], [3])
+m4_define([glabels_minor_version], [0])
+m4_define([glabels_micro_version], [0])
+
+m4_define([glabels_version],
+          [glabels_major_version.glabels_minor_version.glabels_micro_version])
+
+dnl ---------------------------------------------------------------------------
+
+
+AC_PREREQ(2.64)
+AC_INIT([glabels],[glabels_version],
+	[http://bugzilla.gnome.org/enter_bug.cgi?product=glabels],
+	[glabels])
+
+AC_CONFIG_SRCDIR(src/glabels.c)
+
+AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
+
+AM_MAINTAINER_MODE
+m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
+
+AC_CONFIG_HEADERS(config.h)
+
+IT_PROG_INTLTOOL([0.21])
+
+PKG_PROG_PKG_CONFIG
+
+LT_INIT
+
+GNOME_DOC_INIT
+GTK_DOC_CHECK(1.0)
+
+GLIB_GSETTINGS
+
+AC_SEARCH_LIBS([strerror],[cposix])
+AC_PROG_CC
+AC_PROG_INSTALL
+
+GNOME_COMPILE_WARNINGS
+
+AC_PATH_PROG(GLIB_GENMARSHAL,         glib-genmarshal)
+AC_PATH_PROG(GDK_PIXBUF_CSOURCE,      gdk-pixbuf-csource)
+AC_PATH_PROG(GTK_UPDATE_ICON_CACHE,   gtk-update-icon-cache)
+
+
+dnl ---------------------------------------------------------------------------
+dnl - GLABELS branch
+dnl ---------------------------------------------------------------------------
+GLABELS_BRANCH=glabels-4.0
+AC_SUBST(GLABELS_BRANCH)
+
+dnl ---------------------------------------------------------------------------
+dnl - LIBGLABELS branch
+dnl ---------------------------------------------------------------------------
+LIBGLABELS_BRANCH=libglabels-4.0
+AC_SUBST(LIBGLABELS_BRANCH)
+
+dnl ---------------------------------------------------------------------------
+dnl - LIBGLBARCODE branch
+dnl ---------------------------------------------------------------------------
+LIBGLBARCODE_BRANCH=libglbarcode-4.0
+AC_SUBST(LIBGLBARCODE_BRANCH)
+
+dnl ---------------------------------------------------------------------------
+dnl - LIBGLABELS API versioning
+dnl ---------------------------------------------------------------------------
+dnl From the libtool manual:
+dnl 1. Start with version information of `0:0:0' for each libtool library.
+dnl 2. Update the version information only immediately before a public release.
+dnl    More frequent updates are unnecessary, and only guarantee that the current
+dnl    interface number gets larger faster.
+dnl 3. If the library source code has changed at all since the last update, then increment
+dnl    revision (`c:r:a' becomes `c:r+1:a').
+dnl 4. If any interfaces have been added, removed, or changed since the last update,
+dnl    increment current, and set revision to 0.
+dnl 5. If any interfaces have been added since the last public release, then increment age.
+dnl 6. If any interfaces have been removed since the last public release, then set age
+dnl    to 0.
+LIBGLABELS_C=8
+LIBGLABELS_R=0
+LIBGLABELS_A=0
+
+LIBGLABELS_API_VERSION=${LIBGLABELS_C}:${LIBGLABELS_R}:${LIBGLABELS_A}
+AC_SUBST(LIBGLABELS_API_VERSION)
+
+dnl ---------------------------------------------------------------------------
+dnl - LIBGLBARCODE API versioning
+dnl ---------------------------------------------------------------------------
+dnl From the libtool manual:
+dnl 1. Start with version information of `0:0:0' for each libtool library.
+dnl 2. Update the version information only immediately before a public release.
+dnl    More frequent updates are unnecessary, and only guarantee that the current
+dnl    interface number gets larger faster.
+dnl 3. If the library source code has changed at all since the last update, then increment
+dnl    revision (`c:r:a' becomes `c:r+1:a').
+dnl 4. If any interfaces have been added, removed, or changed since the last update,
+dnl    increment current, and set revision to 0.
+dnl 5. If any interfaces have been added since the last public release, then increment age.
+dnl 6. If any interfaces have been removed since the last public release, then set age
+dnl    to 0.
+LIBGLBARCODE_C=0
+LIBGLBARCODE_R=0
+LIBGLBARCODE_A=0
+
+LIBGLBARCODE_API_VERSION=${LIBGLBARCODE_C}:${LIBGLBARCODE_R}:${LIBGLBARCODE_A}
+AC_SUBST(LIBGLBARCODE_API_VERSION)
+
+dnl ---------------------------------------------------------------------------
+dnl - Library dependencies
+dnl ---------------------------------------------------------------------------
+dnl Required dependencies
+GLIB_REQUIRED=2.28.2
+GTK_REQUIRED=3.0.9
+LIBXML_REQUIRED=2.7.8
+LIBRSVG_REQUIRED=2.32.0
+CAIRO_REQUIRED=1.10.0
+PANGO_REQUIRED=1.28.1
+
+dnl Optional dependencies
+LIBEBOOK_REQUIRED=2.30.3
+LIBBARCODE_REQUIRED=0.98
+LIBQRENCODE_REQUIRED=3.1.0
+LIBIEC16022_REQUIRED=0.2.4
+LIBZINT_REQUIRED=2.4.0
+
+dnl Make above strings available for packaging files (e.g. rpm spec files)
+AC_SUBST(GLIB_REQUIRED)
+AC_SUBST(GTK_REQUIRED)
+AC_SUBST(LIBXML_REQUIRED)
+AC_SUBST(LIBRSVG_REQUIRED)
+AC_SUBST(CAIRO_REQUIRED)
+AC_SUBST(PANGO_REQUIRED)
+AC_SUBST(LIBEBOOK_REQUIRED)
+AC_SUBST(LIBBARCODE_REQUIRED)
+AC_SUBST(LIBQRENCODE_REQUIRED)
+AC_SUBST(LIBIEC16022_REQUIRED)
+AC_SUBST(LIBZINT_REQUIRED)
+
+
+dnl ---------------------------------------------------------------------------
+dnl - GLABELS prerequisites
+dnl ---------------------------------------------------------------------------
+PKG_CHECK_MODULES(GLABELS, [\
+	glib-2.0 >= $GLIB_REQUIRED \
+	gtk+-3.0 >= $GTK_REQUIRED \
+	libxml-2.0 >= $LIBXML_REQUIRED \
+	librsvg-2.0 >= $LIBRSVG_REQUIRED \
+])
+
+AC_SUBST(GLABELS_CFLAGS)
+AC_SUBST(GLABELS_LIBS)
+
+
+dnl ---------------------------------------------------------------------------
+dnl - LIBGLABELS more modest prerequisites
+dnl ---------------------------------------------------------------------------
+PKG_CHECK_MODULES(LIBGLABELS, [\
+	glib-2.0 >= $GLIB_REQUIRED \
+	libxml-2.0 >= $LIBXML_REQUIRED \
+])
+
+AC_SUBST(LIBGLABELS_CFLAGS)
+AC_SUBST(LIBGLABELS_LIBS)
+
+
+dnl ---------------------------------------------------------------------------
+dnl - LIBGLBARCODE more modest prerequisites
+dnl ---------------------------------------------------------------------------
+PKG_CHECK_MODULES(LIBGLBARCODE, [\
+	glib-2.0 >= $GLIB_REQUIRED \
+	cairo >= $CAIRO_REQUIRED \
+	pango >= $PANGO_REQUIRED \
+])
+
+AC_SUBST(LIBGLBARCODE_CFLAGS)
+AC_SUBST(LIBGLBARCODE_LIBS)
+
+
+dnl ---------------------------------------------------------------------------
+dnl - Check for optional evolution data server
+dnl ---------------------------------------------------------------------------
+AC_ARG_WITH(libebook,
+	    [AS_HELP_STRING([--without-libebook],[build without Evolution Data Server support])])
+have_libebook=no
+if test "x$with_libebook" != xno; then
+	PKG_CHECK_MODULES(LIBEBOOK, libebook-1.2 >= $LIBEBOOK_REQUIRED,
+			  [have_libebook=yes], [have_libebook=no])
+fi
+
+if test "x$have_libebook" = "xyes"; then
+	AC_DEFINE(HAVE_LIBEBOOK,1,[Define to 1 for EDS support])
+	AC_SUBST(LIBEBOOK_CFLAGS)
+	AC_SUBST(LIBEBOOK_LIBS)
+fi
+
+
+dnl ---------------------------------------------------------------------------
+dnl - Check for optional GNU Barcode backend
+dnl ---------------------------------------------------------------------------
+AC_ARG_WITH(libbarcode,
+	    [AS_HELP_STRING([--without-libbarcode],[build without GNU Barcode support])])
+have_libbarcode=no
+if test "x$with_libbarcode" != xno; then
+   AC_CHECK_LIB(barcode, Barcode_Create,
+		[have_libbarcode=yes], [have_libbarcode=no])
+fi
+
+if test "x$have_libbarcode" = "xyes"; then
+	AC_DEFINE(HAVE_LIBBARCODE,1,[Define to 1 for GNU Barcode support])
+	LIBBARCODE_CFLAGS=""
+	LIBBARCODE_LIBS="-lbarcode"
+	AC_SUBST(LIBBARCODE_CFLAGS)
+	AC_SUBST(LIBBARCODE_LIBS)
+else
+	help_libbarcode="(See http://www.gnu.org/software/barcode/barcode.html)"
+fi
+
+dnl ---------------------------------------------------------------------------
+dnl - Check for optional Zint backend
+dnl ---------------------------------------------------------------------------
+AC_ARG_WITH(libzint,
+	    [AS_HELP_STRING([--without-libzint],[build without Zint Barcode support])])
+have_libzint=no
+if test "x$with_libzint" != xno; then
+   AC_CHECK_LIB(zint, ZBarcode_Render,
+		[have_libzint=yes], [have_libzint=no])
+fi
+
+if test "x$have_libzint" = "xyes"; then
+	AC_DEFINE(HAVE_LIBZINT,1,[Define to 1 for Zint Barcode support])
+	LIBZINT_CFLAGS=""
+	LIBZINT_LIBS="-lzint"
+	AC_SUBST(LIBZINT_CFLAGS)
+	AC_SUBST(LIBZINT_LIBS)
+else
+	help_libzint="(See http://www.zint.org.uk)"
+fi
+
+
+dnl ---------------------------------------------------------------------------
+dnl - Check for optional QRencode Barcode backend
+dnl ---------------------------------------------------------------------------
+AC_ARG_WITH(libqrencode,
+	    [AS_HELP_STRING([--without-libqrencode],[build without QR code support])])
+have_libqrencode=no
+if test "x$with_libqrencode" != xno; then
+	PKG_CHECK_MODULES(LIBQRENCODE, libqrencode >= $LIBQRENCODE_REQUIRED,
+			  [have_libqrencode=yes], [have_libqrencode=no])
+fi
+
+if test "x$have_libqrencode" = "xyes"; then
+	AC_DEFINE(HAVE_LIBQRENCODE,1,[Define to 1 for QR code support])
+	AC_SUBST(LIBQRENCODE_CFLAGS)
+	AC_SUBST(LIBQRENCODE_LIBS)
+else
+	help_libqrencode="(See http://megaui.net/fukuchi/works/qrencode/index.en.html)"
+fi
+
+
+dnl ---------------------------------------------------------------------------
+dnl - Check for IEC16022 Barcode backend
+dnl ---------------------------------------------------------------------------
+AC_ARG_WITH(libiec16022,
+	    [AS_HELP_STRING([--without-libiec16022],[build without IEC 16022 support])])
+have_libiec16022=no
+if test "x$with_libiec16022" != xno; then
+	PKG_CHECK_MODULES(LIBIEC16022, libiec16022 >= $LIBIEC16022_REQUIRED,
+			  [have_libiec16022=yes], [have_libiec16022=no])
+fi
+
+if test "x$have_libiec16022" = "xyes"; then
+	AC_DEFINE(HAVE_LIBIEC16022,1,[Define to 1 for IEC 16022 support])
+	AC_SUBST(LIBIEC16022_CFLAGS)
+	AC_SUBST(LIBIEC16022_LIBS)
+else
+	help_libiec16022="(See http://datenfreihafen.org/projects/iec16022.html)"
+fi
+
+
+dnl ---------------------------------------------------------------------------
+dnl - Enable deprecation testing
+dnl ---------------------------------------------------------------------------
+AC_ARG_ENABLE(deprecations,
+              [AS_HELP_STRING([--enable-deprecations],[warn about deprecated usages [default=no]])],,
+              [enable_deprecations=no])
+
+if test "x$enable_deprecations" = "xyes"; then
+   DISABLE_DEPRECATED_CFLAGS="\
+-DG_DISABLE_DEPRECATED \
+-DGDK_DISABLE_DEPRECATED \
+-DGTK_DISABLE_DEPRECATED \
+-DGDK_PIXBUF_DISABLE_DEPRECATED \
+"
+   AC_SUBST(DISABLE_DEPRECATED_CFLAGS)
+fi
+
+
+dnl ---------------------------------------------------------------------------
+dnl - i18n support
+dnl ---------------------------------------------------------------------------
+GETTEXT_PACKAGE=${GLABELS_BRANCH}
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [Gettext package])
+
+AM_GLIB_GNU_GETTEXT
+
+
+dnl ---------------------------------------------------------------------------
+dnl - Makefiles, etc.
+dnl ---------------------------------------------------------------------------
+AC_CONFIG_FILES([
+Makefile
+libglabels/Makefile
+libglabels/${LIBGLABELS_BRANCH}.pc
+libglbarcode/Makefile
+libglbarcode/${LIBGLBARCODE_BRANCH}.pc
+src/Makefile
+src/cursors/Makefile
+src/pixmaps/Makefile
+data/Makefile
+data/desktop/Makefile
+data/icons/Makefile
+data/icons/16x16/Makefile
+data/icons/22x22/Makefile
+data/icons/24x24/Makefile
+data/icons/32x32/Makefile
+data/icons/48x48/Makefile
+data/man/Makefile
+data/mime/Makefile
+data/pixmaps/Makefile
+data/schemas/Makefile
+data/schemas/org.gnome.glabels-3.gschema.xml.in
+data/ui/Makefile
+templates/Makefile
+po/Makefile.in
+help/Makefile
+docs/Makefile
+docs/libglabels/Makefile
+docs/libglbarcode/Makefile
+glabels.spec
+])
+AC_OUTPUT
+
+
+dnl ---------------------------------------------------------------------------
+dnl - Print configuration information
+dnl ---------------------------------------------------------------------------
+echo "
+
+Configuration:
+
+        Package ................. ${PACKAGE}-${VERSION}
+        Installation prefix ..... ${prefix}
+        Source code location .... ${srcdir}
+        Compiler ................ ${CC} 
+
+
+Optional data merge backends:
+
+        Evolution Data Server ... ${have_libebook}
+
+
+Optional barcode backends:
+
+        GNU Barcode ............. ${have_libbarcode} ${help_libbarcode}
+        QR Code ................. ${have_libqrencode} ${help_libqrencode}
+        IEC 16022 ............... ${have_libiec16022} ${help_libiec16022}
+        Zint .................... ${have_libzint} ${help_libzint}
+
+
+"
diff --git a/data/Makefile.am b/data/Makefile.am
index bc73c75..2eb6b7c 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -1,12 +1,13 @@
 ## Process this file with automake to produce Makefile.in
 
 SUBDIRS = \
-	desktop \
 	icons \
-	man \
-	mime \
 	pixmaps \
-	schemas \
 	ui
 
+#	desktop \
+#	man \
+#	mime \
+#	schemas
+
 
diff --git a/data/icons/22x22/Makefile.am b/data/icons/22x22/Makefile.am
index 1587bb6..955d860 100644
--- a/data/icons/22x22/Makefile.am
+++ b/data/icons/22x22/Makefile.am
@@ -1,18 +1,11 @@
 
 size = 22x22
 appiconsdir = $(datadir)/icons/hicolor/$(size)/apps
-actioniconsdir = $(datadir)/$(GLABELS_BRANCH)/icons/hicolor/$(size)/actions
 
 appicons_DATA = \
 	apps/glabels-3.0.png
 
-actionicons_DATA = \
-	actions/glabels-align-text-bottom.png	\
-	actions/glabels-align-text-middle.png	\
-	actions/glabels-align-text-top.png
-
 noinst_DATA =
 
-EXTRA_DIST = $(appicons_DATA)			\
-	     $(actionicons_DATA)		\
+EXTRA_DIST = $(appicons_DATA) \
 	     $(noinst_DATA)
diff --git a/data/icons/24x24/Makefile.am b/data/icons/24x24/Makefile.am
index 4af195e..4dd62a0 100644
--- a/data/icons/24x24/Makefile.am
+++ b/data/icons/24x24/Makefile.am
@@ -7,9 +7,6 @@ appicons_DATA = \
 	apps/glabels-3.0.png
 
 actionicons_DATA = \
-	actions/glabels-align-text-bottom.png	\
-	actions/glabels-align-text-middle.png	\
-	actions/glabels-align-text-top.png	\
 	actions/glabels-arrow.png		\
 	actions/glabels-barcode.png		\
 	actions/glabels-box.png			\
diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am
index 7a9e4f2..1d25fa2 100644
--- a/data/icons/Makefile.am
+++ b/data/icons/Makefile.am
@@ -1,7 +1,7 @@
 
 SUBDIRS = 16x16 22x22 24x24 32x32 48x48
 
-gtk_update_icon_cache = $(GTK_UPDATE_ICON_CACHE) -f -t $(datadir)/icons/hicolor
+gtk_update_icon_cache = $(GTK_UPDATE_ICON_CACHE) -f -t $(datadir)/$(GLABELS_BRANCH)/icons/hicolor
 
 install-data-hook: update-icon-cache
 uninstall-hook: update-icon-cache
diff --git a/data/schemas/org.gnome.glabels-3.gschema.xml.in.in b/data/schemas/org.gnome.glabels-3.gschema.xml.in.in
index 065fe00..c7e7ea9 100644
--- a/data/schemas/org.gnome.glabels-3.gschema.xml.in.in
+++ b/data/schemas/org.gnome.glabels-3.gschema.xml.in.in
@@ -2,10 +2,10 @@
 
 
   <schema id="org.gnome.glabels-3" path="/apps/glabels-3/" gettext-domain="@GETTEXT_PACKAGE@">
-    <child name="ui" schema="org.gnome.glabels-3.ui"/>
-    <child name="locale" schema="org.gnome.glabels-3.locale"/>
-    <child name="objects" schema="org.gnome.glabels-3.objects"/>
-    <child name="history" schema="org.gnome.glabels-3.history"/>
+    <child name="ui" schema="org.gnome.glabels.ui"/>
+    <child name="locale" schema="org.gnome.glabels.locale"/>
+    <child name="objects" schema="org.gnome.glabels.objects"/>
+    <child name="history" schema="org.gnome.glabels.history"/>
   </schema>
 
 
diff --git a/data/ui/Makefile.am b/data/ui/Makefile.am
index 833eb26..70b2f69 100644
--- a/data/ui/Makefile.am
+++ b/data/ui/Makefile.am
@@ -3,13 +3,7 @@
 builderdir = $(datadir)/$(GLABELS_BRANCH)/ui/
 
 builder_DATA = \
-	property-bar.ui				\
-	print-op-dialog-custom-widget.ui	\
-	media-select.ui				\
-	new-label-dialog.ui			\
-	merge-properties-dialog.ui		\
-	template-designer.ui			\
-	prefs-dialog.ui				\
-	object-editor.ui
+	new_label_dialog.ui
+
 
 EXTRA_DIST = $(builder_DATA)
diff --git a/data/ui/new_label_dialog.ui b/data/ui/new_label_dialog.ui
new file mode 100644
index 0000000..014a523
--- /dev/null
+++ b/data/ui/new_label_dialog.ui
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.24"/>
+  <object class="GtkWindow" id="new_label_dialog">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">New Label</property>
+    <property name="icon_name">preferences-desktop</property>
+    <child>
+      <object class="GtkVBox" id="main_vbox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkToolbar" id="toolbar">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkToolItem" id="toolbutton1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="use_action_appearance">False</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="bottom_padding">5</property>
+                    <property name="left_padding">10</property>
+                    <property name="right_padding">5</property>
+                    <child>
+                      <object class="GtkHBox" id="hbox1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <child>
+                          <object class="GtkAlignment" id="entry-alignment">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="xalign">1</property>
+                            <property name="xscale">0</property>
+                            <child>
+                              <object class="GtkEntry" id="search_entry">
+                                <property name="width_request">210</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="invisible_char">â</property>
+                                <property name="secondary_icon_name">edit-find-symbolic</property>
+                                <property name="secondary_icon_activatable">False</property>
+                                <property name="secondary_icon_sensitive">False</property>
+                              </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>
+              <packing>
+                <property name="expand">True</property>
+                <property name="homogeneous">True</property>
+              </packing>
+            </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="info_vbox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">10</property>
+            <child>
+              <object class="GtkHBox" id="hbox2">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">10</property>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <object class="GtkVBox" id="vbox2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="spacing">18</property>
+                    <child>
+                      <object class="GtkScrolledWindow" id="scrolledwindow1">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <object class="GtkIconView" id="icon_view">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="selection_mode">browse</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">1</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">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/data/ui/object-editor.ui b/data/ui/object-editor.ui
index 422bcca..582a19e 100644
--- a/data/ui/object-editor.ui
+++ b/data/ui/object-editor.ui
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="2.20"/>
+  <!-- interface-naming-policy toplevel-contextual -->
   <object class="GtkAdjustment" id="adjustment1">
     <property name="lower">1</property>
     <property name="upper">250</property>
@@ -8,29 +9,6 @@
     <property name="step_increment">1</property>
     <property name="page_increment">10</property>
   </object>
-  <object class="GtkAdjustment" id="adjustment10">
-    <property name="upper">100</property>
-    <property name="step_increment">0.01</property>
-    <property name="page_increment">1</property>
-  </object>
-  <object class="GtkAdjustment" id="adjustment11">
-    <property name="upper">100</property>
-    <property name="step_increment">0.01</property>
-    <property name="page_increment">1</property>
-  </object>
-  <object class="GtkAdjustment" id="adjustment12">
-    <property name="upper">100</property>
-    <property name="value">1</property>
-    <property name="step_increment">1</property>
-    <property name="page_increment">10</property>
-  </object>
-  <object class="GtkAdjustment" id="adjustment13">
-    <property name="lower">1</property>
-    <property name="upper">100</property>
-    <property name="value">1</property>
-    <property name="step_increment">1</property>
-    <property name="page_increment">10</property>
-  </object>
   <object class="GtkAdjustment" id="adjustment2">
     <property name="upper">5</property>
     <property name="value">1</property>
@@ -78,60 +56,70 @@
     <property name="step_increment">0.01</property>
     <property name="page_increment">1</property>
   </object>
+  <object class="GtkAdjustment" id="adjustment10">
+    <property name="upper">100</property>
+    <property name="step_increment">0.01</property>
+    <property name="page_increment">1</property>
+  </object>
+  <object class="GtkAdjustment" id="adjustment11">
+    <property name="upper">100</property>
+    <property name="step_increment">0.01</property>
+    <property name="page_increment">1</property>
+  </object>
+  <object class="GtkAdjustment" id="adjustment12">
+    <property name="upper">100</property>
+    <property name="value">1</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="adjustment13">
+    <property name="lower">1</property>
+    <property name="upper">100</property>
+    <property name="value">1</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkSizeGroup" id="page_sizegroup">
+    <property name="mode">both</property>
+    <widgets>
+      <widget name="shadow_page_vbox"/>
+      <widget name="lsize_page_vbox"/>
+      <widget name="size_page_vbox"/>
+      <widget name="bc_page_vbox"/>
+      <widget name="data_page_vbox"/>
+      <widget name="img_page_vbox"/>
+      <widget name="fill_page_vbox"/>
+      <widget name="line_page_vbox"/>
+      <widget name="text_page_vbox"/>
+      <widget name="edit_page_vbox"/>
+    </widgets>
+  </object>
+  <object class="GtkSizeGroup" id="width_sizegroup">
+    <widgets>
+      <widget name="notebook"/>
+      <widget name="title_hbox"/>
+    </widgets>
+  </object>
   <object class="GtkDialog" id="dialog">
     <property name="visible">True</property>
-    <property name="can_focus">False</property>
     <property name="border_width">6</property>
     <property name="title" translatable="yes">dialog1</property>
     <property name="type_hint">dialog</property>
     <child internal-child="vbox">
-      <object class="GtkBox" id="dialog-vbox1">
+      <object class="GtkVBox" id="dialog-vbox1">
         <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <child internal-child="action_area">
-          <object class="GtkButtonBox" id="dialog-action_area1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="layout_style">end</property>
-            <child>
-              <object class="GtkButton" id="closebutton1">
-                <property name="label">gtk-close</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="can_default">True</property>
-                <property name="receives_default">False</property>
-                <property name="use_action_appearance">False</property>
-                <property name="use_stock">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</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">0</property>
-          </packing>
-        </child>
         <child>
           <object class="GtkVBox" id="editor_vbox">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="border_width">6</property>
             <property name="spacing">12</property>
             <child>
               <object class="GtkHBox" id="title_hbox">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="spacing">12</property>
                 <child>
                   <object class="GtkImage" id="title_image">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                   </object>
                   <packing>
                     <property name="expand">False</property>
@@ -142,7 +130,6 @@
                 <child>
                   <object class="GtkLabel" id="title_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label">&lt;span weight="bold" size="larger"&gt;Xxx object properties&lt;/span&gt;</property>
                     <property name="use_markup">True</property>
                   </object>
@@ -167,13 +154,11 @@
                 <child>
                   <object class="GtkVBox" id="edit_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">6</property>
                     <child>
                       <object class="GtkHBox" id="hbox30">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <child>
                           <object class="GtkScrolledWindow" id="scrolledwindow1">
                             <property name="visible">True</property>
@@ -202,12 +187,10 @@
                     <child>
                       <object class="GtkHBox" id="hbox1">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkVBox" id="edit_insert_field_vbox">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <child>
                               <placeholder/>
                             </child>
@@ -233,7 +216,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="edit_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Text</property>
                   </object>
                   <packing>
@@ -243,18 +225,15 @@
                 <child>
                   <object class="GtkVBox" id="text_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">12</property>
                     <child>
                       <object class="GtkHBox" id="hbox65">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkLabel" id="text_family_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Family:</property>
                           </object>
@@ -267,7 +246,6 @@
                         <child>
                           <object class="GtkHBox" id="text_family_hbox">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <child>
                               <placeholder/>
                             </child>
@@ -288,12 +266,10 @@
                     <child>
                       <object class="GtkHBox" id="hbox66">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkLabel" id="text_size_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Size:</property>
                           </object>
@@ -306,7 +282,6 @@
                         <child>
                           <object class="GtkHBox" id="hbox28">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkSpinButton" id="text_size_spin">
@@ -339,12 +314,10 @@
                     <child>
                       <object class="GtkHBox" id="hbox67">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkLabel" id="text_style_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Style:</property>
                           </object>
@@ -357,18 +330,15 @@
                         <child>
                           <object class="GtkHBox" id="hbox29">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkToggleButton" id="text_bold_toggle">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
-                                <property name="use_action_appearance">False</property>
                                 <child>
                                   <object class="GtkImage" id="image1">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="stock">gtk-bold</property>
                                   </object>
                                 </child>
@@ -384,11 +354,9 @@
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
-                                <property name="use_action_appearance">False</property>
                                 <child>
                                   <object class="GtkImage" id="image2">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="stock">gtk-italic</property>
                                   </object>
                                 </child>
@@ -416,12 +384,10 @@
                     <child>
                       <object class="GtkHBox" id="hbox68">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkLabel" id="text_color_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="yalign">0.14000000059604645</property>
                             <property name="label" translatable="yes">Color:</property>
@@ -435,12 +401,10 @@
                         <child>
                           <object class="GtkVBox" id="vbox3">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">6</property>
                             <child>
                               <object class="GtkHBox" id="hbox42">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkRadioButton" id="text_color_radio">
@@ -448,7 +412,6 @@
                                     <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="use_underline">True</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="draw_indicator">True</property>
@@ -462,7 +425,6 @@
                                 <child>
                                   <object class="GtkHBox" id="text_color_hbox">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">12</property>
                                     <child>
                                       <placeholder/>
@@ -484,7 +446,6 @@
                             <child>
                               <object class="GtkHBox" id="hbox44">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkRadioButton" id="text_color_key_radio">
@@ -492,7 +453,6 @@
                                     <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="use_underline">True</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="xalign">0.54000002145767212</property>
@@ -508,7 +468,6 @@
                                 <child>
                                   <object class="GtkHBox" id="text_color_key_hbox">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">12</property>
                                     <child>
                                       <placeholder/>
@@ -544,12 +503,10 @@
                     <child>
                       <object class="GtkHBox" id="hbox69">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkLabel" id="text_align_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Alignment:</property>
                           </object>
@@ -562,18 +519,15 @@
                         <child>
                           <object class="GtkHBox" id="hbox4">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkToggleButton" id="text_left_toggle">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
-                                <property name="use_action_appearance">False</property>
                                 <child>
                                   <object class="GtkImage" id="image3">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="stock">gtk-justify-left</property>
                                   </object>
                                 </child>
@@ -589,11 +543,9 @@
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
-                                <property name="use_action_appearance">False</property>
                                 <child>
                                   <object class="GtkImage" id="image4">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="stock">gtk-justify-center</property>
                                   </object>
                                 </child>
@@ -609,11 +561,9 @@
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="receives_default">False</property>
-                                <property name="use_action_appearance">False</property>
                                 <child>
                                   <object class="GtkImage" id="image5">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="stock">gtk-justify-right</property>
                                   </object>
                                 </child>
@@ -639,115 +589,12 @@
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkHBox" id="hbox69v">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="spacing">12</property>
-                        <child>
-                          <object class="GtkLabel" id="text_valign_label">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="xalign">0</property>
-                            <property name="label" translatable="yes">Vertical alignment:</property>
-                          </object>
-                          <packing>
-                            <property name="expand">False</property>
-                            <property name="fill">False</property>
-                            <property name="position">0</property>
-                          </packing>
-                        </child>
-                        <child>
-                          <object class="GtkHBox" id="hbox4v">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="spacing">12</property>
-                            <child>
-                              <object class="GtkToggleButton" id="text_top_toggle">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="receives_default">False</property>
-                                <property name="use_action_appearance">False</property>
-                                <child>
-                                  <object class="GtkImage" id="image3v">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="pixel_size">22</property>
-                                    <property name="icon_name">glabels-align-text-top</property>
-                                  </object>
-                                </child>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">False</property>
-                                <property name="position">0</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkToggleButton" id="text_vcenter_toggle">
-                                <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="xalign">0.47999998927116394</property>
-                                <child>
-                                  <object class="GtkImage" id="image4v">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="pixel_size">22</property>
-                                    <property name="icon_name">glabels-align-text-middle</property>
-                                  </object>
-                                </child>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">False</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                            <child>
-                              <object class="GtkToggleButton" id="text_bottom_toggle">
-                                <property name="visible">True</property>
-                                <property name="can_focus">True</property>
-                                <property name="receives_default">False</property>
-                                <property name="use_action_appearance">False</property>
-                                <child>
-                                  <object class="GtkImage" id="image5v">
-                                    <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
-                                    <property name="pixel_size">22</property>
-                                    <property name="icon_name">glabels-align-text-bottom</property>
-                                  </object>
-                                </child>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">False</property>
-                                <property name="position">2</property>
-                              </packing>
-                            </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">False</property>
-                        <property name="fill">False</property>
-                        <property name="position">5</property>
-                      </packing>
-                    </child>
-                    <child>
                       <object class="GtkHBox" id="hbox70">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkLabel" id="text_line_spacing_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Line Spacing:</property>
                           </object>
@@ -760,7 +607,6 @@
                         <child>
                           <object class="GtkHBox" id="hbox31">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkSpinButton" id="text_line_spacing_spin">
@@ -787,7 +633,7 @@
                       <packing>
                         <property name="expand">False</property>
                         <property name="fill">False</property>
-                        <property name="position">6</property>
+                        <property name="position">5</property>
                       </packing>
                     </child>
                     <child>
@@ -796,14 +642,13 @@
                         <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="use_underline">True</property>
                         <property name="draw_indicator">True</property>
                       </object>
                       <packing>
                         <property name="expand">False</property>
                         <property name="fill">False</property>
-                        <property name="position">7</property>
+                        <property name="position">6</property>
                       </packing>
                     </child>
                   </object>
@@ -815,7 +660,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="text_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="xalign">0.46000000834465027</property>
                     <property name="label" translatable="yes">Style</property>
                   </object>
@@ -827,18 +671,15 @@
                 <child>
                   <object class="GtkVBox" id="line_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">12</property>
                     <child>
                       <object class="GtkHBox" id="hbox71">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkLabel" id="line_w_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Width:</property>
                           </object>
@@ -851,7 +692,6 @@
                         <child>
                           <object class="GtkHBox" id="hbox7">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkSpinButton" id="line_width_spin">
@@ -871,7 +711,6 @@
                             <child>
                               <object class="GtkLabel" id="label21">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="label" translatable="yes">points</property>
                               </object>
                               <packing>
@@ -897,12 +736,10 @@
                     <child>
                       <object class="GtkHBox" id="hbox72">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkLabel" id="line_color_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="yalign">0.14000000059604645</property>
                             <property name="label" translatable="yes">Color:</property>
@@ -916,12 +753,11 @@
                         <child>
                           <object class="GtkVBox" id="vbox2">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">6</property>
+			    <property name="vexpand">False</property>
                             <child>
                               <object class="GtkHBox" id="hbox38">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkRadioButton" id="line_color_radio">
@@ -929,7 +765,6 @@
                                     <property name="visible">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="receives_default">False</property>
-                                    <property name="use_action_appearance">False</property>
                                     <property name="use_underline">True</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="draw_indicator">True</property>
@@ -943,7 +778,6 @@
                                 <child>
                                   <object class="GtkHBox" id="line_color_hbox">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">12</property>
                                     <child>
                                       <placeholder/>
@@ -965,7 +799,6 @@
                             <child>
                               <object class="GtkHBox" id="hbox40">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkRadioButton" id="line_key_radio">
@@ -973,7 +806,6 @@
                                     <property name="visible">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="receives_default">False</property>
-                                    <property name="use_action_appearance">False</property>
                                     <property name="use_underline">True</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="draw_indicator">True</property>
@@ -988,7 +820,6 @@
                                 <child>
                                   <object class="GtkHBox" id="line_key_hbox">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">12</property>
                                     <child>
                                       <placeholder/>
@@ -1029,7 +860,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="line_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Line</property>
                   </object>
                   <packing>
@@ -1040,17 +870,15 @@
                 <child>
                   <object class="GtkVBox" id="fill_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <child>
                       <object class="GtkHBox" id="hbox73">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
+			<property name="vexpand">False</property>
                         <child>
                           <object class="GtkLabel" id="fill_color_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="yalign">0.14000000059604645</property>
                             <property name="label" translatable="yes">Color:</property>
@@ -1064,12 +892,10 @@
                         <child>
                           <object class="GtkVBox" id="vbox5">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">6</property>
                             <child>
                               <object class="GtkHBox" id="hbox50">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkRadioButton" id="fill_color_radio">
@@ -1077,7 +903,6 @@
                                     <property name="visible">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="receives_default">False</property>
-                                    <property name="use_action_appearance">False</property>
                                     <property name="use_underline">True</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="active">True</property>
@@ -1092,7 +917,6 @@
                                 <child>
                                   <object class="GtkHBox" id="fill_color_hbox">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">12</property>
                                     <child>
                                       <placeholder/>
@@ -1114,7 +938,6 @@
                             <child>
                               <object class="GtkHBox" id="hbox52">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkRadioButton" id="fill_key_radio">
@@ -1122,7 +945,6 @@
                                     <property name="visible">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="receives_default">False</property>
-                                    <property name="use_action_appearance">False</property>
                                     <property name="use_underline">True</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="draw_indicator">True</property>
@@ -1137,7 +959,6 @@
                                 <child>
                                   <object class="GtkHBox" id="fill_key_hbox">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">12</property>
                                     <child>
                                       <placeholder/>
@@ -1178,7 +999,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="fill_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Fill</property>
                   </object>
                   <packing>
@@ -1189,13 +1009,11 @@
                 <child>
                   <object class="GtkVBox" id="img_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">12</property>
                     <child>
                       <object class="GtkHBox" id="hbox74">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkRadioButton" id="img_file_radio">
@@ -1203,7 +1021,6 @@
                             <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="use_underline">True</property>
                             <property name="focus_on_click">False</property>
                             <property name="draw_indicator">True</property>
@@ -1217,7 +1034,6 @@
                         <child>
                           <object class="GtkFileChooserButton" id="img_file_button">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="show_hidden">True</property>
                           </object>
                           <packing>
@@ -1236,7 +1052,6 @@
                     <child>
                       <object class="GtkHBox" id="hbox75">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkRadioButton" id="img_key_radio">
@@ -1245,7 +1060,6 @@
                             <property name="sensitive">False</property>
                             <property name="can_focus">True</property>
                             <property name="receives_default">False</property>
-                            <property name="use_action_appearance">False</property>
                             <property name="use_underline">True</property>
                             <property name="focus_on_click">False</property>
                             <property name="draw_indicator">True</property>
@@ -1260,7 +1074,6 @@
                         <child>
                           <object class="GtkHBox" id="img_key_hbox">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <child>
                               <placeholder/>
                             </child>
@@ -1286,7 +1099,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="img_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Image</property>
                   </object>
                   <packing>
@@ -1297,13 +1109,11 @@
                 <child>
                   <object class="GtkVBox" id="data_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">12</property>
                     <child>
                       <object class="GtkHBox" id="hbox76">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkRadioButton" id="data_literal_radio">
@@ -1311,7 +1121,6 @@
                             <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="use_underline">True</property>
                             <property name="draw_indicator">True</property>
                           </object>
@@ -1342,7 +1151,6 @@
                     <child>
                       <object class="GtkHBox" id="hbox77">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkRadioButton" id="data_key_radio">
@@ -1350,7 +1158,6 @@
                             <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="use_underline">True</property>
                             <property name="draw_indicator">True</property>
                             <property name="group">data_literal_radio</property>
@@ -1364,7 +1171,6 @@
                         <child>
                           <object class="GtkHBox" id="data_key_hbox">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <child>
                               <placeholder/>
                             </child>
@@ -1385,12 +1191,10 @@
                     <child>
                       <object class="GtkHBox" id="hbox78">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
                         <child>
                           <object class="GtkLabel" id="data_fill_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="label">         </property>
                           </object>
                           <packing>
@@ -1402,7 +1206,6 @@
                         <child>
                           <object class="GtkTable" id="table9">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="n_rows">2</property>
                             <property name="n_columns">2</property>
                             <property name="column_spacing">6</property>
@@ -1410,7 +1213,6 @@
                             <child>
                               <object class="GtkLabel" id="data_format_label">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="xalign">0</property>
                                 <property name="label" translatable="yes">format:</property>
                               </object>
@@ -1422,7 +1224,6 @@
                             <child>
                               <object class="GtkLabel" id="data_ex_label">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="xalign">0</property>
                                 <property name="label">00000000000 00000</property>
                               </object>
@@ -1436,7 +1237,6 @@
                             <child>
                               <object class="GtkLabel" id="data_digits_label">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="xalign">0</property>
                                 <property name="label" translatable="yes">digits:</property>
                               </object>
@@ -1450,7 +1250,6 @@
                             <child>
                               <object class="GtkHBox" id="hbox32">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <child>
                                   <object class="GtkSpinButton" id="data_digits_spin">
                                     <property name="visible">True</property>
@@ -1497,7 +1296,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="data_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Data</property>
                   </object>
                   <packing>
@@ -1508,21 +1306,19 @@
                 <child>
                   <object class="GtkVBox" id="bc_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">12</property>
                     <child>
                       <object class="GtkTable" id="table1">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="n_rows">2</property>
                         <property name="n_columns">2</property>
                         <property name="column_spacing">6</property>
                         <property name="row_spacing">6</property>
+			<property name="vexpand">False</property>
                         <child>
                           <object class="GtkLabel" id="bc_be_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Backend:</property>
                           </object>
@@ -1534,7 +1330,6 @@
                         <child>
                           <object class="GtkLabel" id="bc_style_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Style:</property>
                           </object>
@@ -1548,7 +1343,6 @@
                         <child>
                           <object class="GtkHBox" id="bc_backend_combo_hbox">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <child>
                               <placeholder/>
                             </child>
@@ -1561,7 +1355,6 @@
                         <child>
                           <object class="GtkHBox" id="bc_style_combo_hbox">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <child>
                               <placeholder/>
                             </child>
@@ -1586,7 +1379,6 @@
                         <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="use_underline">True</property>
                         <property name="draw_indicator">True</property>
                       </object>
@@ -1602,7 +1394,6 @@
                         <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="use_underline">True</property>
                         <property name="draw_indicator">True</property>
                       </object>
@@ -1615,12 +1406,11 @@
                     <child>
                       <object class="GtkHBox" id="hbox80">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
+			<property name="vexpand">False</property>
                         <child>
                           <object class="GtkLabel" id="bc_color_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="yalign">0.14000000059604645</property>
                             <property name="label" translatable="yes">Color:</property>
@@ -1634,12 +1424,10 @@
                         <child>
                           <object class="GtkVBox" id="vbox4">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">6</property>
                             <child>
                               <object class="GtkHBox" id="hbox46">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkRadioButton" id="bc_color_radio">
@@ -1647,7 +1435,6 @@
                                     <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="use_underline">True</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="active">True</property>
@@ -1662,7 +1449,6 @@
                                 <child>
                                   <object class="GtkHBox" id="bc_color_hbox">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">12</property>
                                     <child>
                                       <placeholder/>
@@ -1684,7 +1470,6 @@
                             <child>
                               <object class="GtkHBox" id="hbox48">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkRadioButton" id="bc_key_radio">
@@ -1692,7 +1477,6 @@
                                     <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="use_underline">True</property>
                                     <property name="focus_on_click">False</property>
                                     <property name="draw_indicator">True</property>
@@ -1707,7 +1491,6 @@
                                 <child>
                                   <object class="GtkHBox" id="bc_key_hbox">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">12</property>
                                     <child>
                                       <placeholder/>
@@ -1748,7 +1531,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="bc_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Style</property>
                   </object>
                   <packing>
@@ -1759,13 +1541,11 @@
                 <child>
                   <object class="GtkVBox" id="size_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">12</property>
                     <child>
                       <object class="GtkTable" id="table6">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="n_rows">3</property>
                         <property name="n_columns">3</property>
                         <property name="column_spacing">12</property>
@@ -1773,7 +1553,6 @@
                         <child>
                           <object class="GtkLabel" id="size_w_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Width:</property>
                           </object>
@@ -1785,7 +1564,6 @@
                         <child>
                           <object class="GtkLabel" id="size_h_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Height:</property>
                           </object>
@@ -1799,7 +1577,6 @@
                         <child>
                           <object class="GtkHBox" id="hbox11">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkSpinButton" id="size_h_spin">
@@ -1820,7 +1597,6 @@
                             <child>
                               <object class="GtkLabel" id="size_h_units_label">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="label" translatable="yes">inches</property>
                               </object>
                               <packing>
@@ -1842,7 +1618,6 @@
                         <child>
                           <object class="GtkHBox" id="hbox13">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkButton" id="size_reset_image_button">
@@ -1850,7 +1625,6 @@
                                 <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="use_underline">True</property>
                               </object>
                               <packing>
@@ -1871,7 +1645,6 @@
                         <child>
                           <object class="GtkHBox" id="hbox10">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkSpinButton" id="size_w_spin">
@@ -1892,7 +1665,6 @@
                             <child>
                               <object class="GtkLabel" id="size_w_units_label">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="label" translatable="yes">inches</property>
                               </object>
                               <packing>
@@ -1912,7 +1684,6 @@
                         <child>
                           <object class="GtkVBox" id="size_aspect_vbox">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <child>
                               <placeholder/>
                             </child>
@@ -1943,7 +1714,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="size_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Size</property>
                   </object>
                   <packing>
@@ -1954,13 +1724,11 @@
                 <child>
                   <object class="GtkVBox" id="lsize_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">12</property>
                     <child>
                       <object class="GtkTable" id="table7">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="n_rows">2</property>
                         <property name="n_columns">2</property>
                         <property name="column_spacing">12</property>
@@ -1968,7 +1736,6 @@
                         <child>
                           <object class="GtkLabel" id="lsize_r_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Length:</property>
                           </object>
@@ -1980,7 +1747,6 @@
                         <child>
                           <object class="GtkLabel" id="lsize_theta_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Angle:</property>
                           </object>
@@ -1994,7 +1760,6 @@
                         <child>
                           <object class="GtkHBox" id="hbox25">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkSpinButton" id="lsize_r_spin">
@@ -2015,7 +1780,6 @@
                             <child>
                               <object class="GtkLabel" id="lsize_r_units_label">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="label" translatable="yes">inches</property>
                               </object>
                               <packing>
@@ -2034,7 +1798,6 @@
                         <child>
                           <object class="GtkHBox" id="hbox26">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkSpinButton" id="lsize_theta_spin">
@@ -2055,7 +1818,6 @@
                             <child>
                               <object class="GtkLabel" id="label38">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="label" translatable="yes">degrees</property>
                               </object>
                               <packing>
@@ -2089,7 +1851,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="lsize_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Size</property>
                   </object>
                   <packing>
@@ -2100,13 +1861,11 @@
                 <child>
                   <object class="GtkVBox" id="pos_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">12</property>
                     <child>
                       <object class="GtkTable" id="table8">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="n_rows">2</property>
                         <property name="n_columns">3</property>
                         <property name="column_spacing">12</property>
@@ -2115,7 +1874,6 @@
                           <object class="GtkLabel" id="pos_x_label">
                             <property name="width_request">50</property>
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">X:</property>
                           </object>
@@ -2127,7 +1885,6 @@
                         <child>
                           <object class="GtkLabel" id="pos_y_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">Y:</property>
                           </object>
@@ -2141,7 +1898,6 @@
                         <child>
                           <object class="GtkLabel" id="pos_x_units_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">inches</property>
                           </object>
@@ -2155,7 +1911,6 @@
                         <child>
                           <object class="GtkLabel" id="pos_y_units_label">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="xalign">0</property>
                             <property name="label" translatable="yes">inches</property>
                           </object>
@@ -2219,7 +1974,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="pos_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Position</property>
                   </object>
                   <packing>
@@ -2230,7 +1984,6 @@
                 <child>
                   <object class="GtkVBox" id="shadow_page_vbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="border_width">12</property>
                     <property name="spacing">12</property>
                     <child>
@@ -2239,7 +1992,6 @@
                         <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="use_underline">True</property>
                         <property name="draw_indicator">True</property>
                       </object>
@@ -2252,17 +2004,15 @@
                     <child>
                       <object class="GtkVBox" id="shadow_controls_table">
                         <property name="visible">True</property>
-                        <property name="can_focus">False</property>
                         <property name="spacing">12</property>
+			<property name="vexpand">False</property>
                         <child>
                           <object class="GtkHBox" id="hbox63">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkLabel" id="label40">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="xalign">0</property>
                                 <property name="label" translatable="yes">X Offset:</property>
                               </object>
@@ -2275,7 +2025,6 @@
                             <child>
                               <object class="GtkHBox" id="hbox54">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">12</property>
                                 <child>
                                   <object class="GtkSpinButton" id="shadow_x_spin">
@@ -2296,7 +2045,6 @@
                                 <child>
                                   <object class="GtkLabel" id="shadow_x_units_label">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="label" translatable="yes">inches</property>
                                   </object>
                                   <packing>
@@ -2322,12 +2070,10 @@
                         <child>
                           <object class="GtkHBox" id="hbox64">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkLabel" id="label41">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="xalign">0</property>
                                 <property name="label" translatable="yes">Y Offset:</property>
                               </object>
@@ -2340,7 +2086,6 @@
                             <child>
                               <object class="GtkHBox" id="hbox55">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">12</property>
                                 <child>
                                   <object class="GtkSpinButton" id="shadow_y_spin">
@@ -2361,7 +2106,6 @@
                                 <child>
                                   <object class="GtkLabel" id="shadow_y_units_label">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="label" translatable="yes">inches</property>
                                   </object>
                                   <packing>
@@ -2387,12 +2131,10 @@
                         <child>
                           <object class="GtkHBox" id="hbox61">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkLabel" id="label45">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="xalign">0</property>
                                 <property name="yalign">0.14000000059604645</property>
                                 <property name="label" translatable="yes">Color:</property>
@@ -2406,12 +2148,10 @@
                             <child>
                               <object class="GtkVBox" id="vbox7">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">6</property>
                                 <child>
                                   <object class="GtkHBox" id="hbox57">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">6</property>
                                     <child>
                                       <object class="GtkRadioButton" id="shadow_color_radio">
@@ -2419,7 +2159,6 @@
                                         <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="use_underline">True</property>
                                         <property name="focus_on_click">False</property>
                                         <property name="draw_indicator">True</property>
@@ -2433,7 +2172,6 @@
                                     <child>
                                       <object class="GtkHBox" id="shadow_color_hbox">
                                         <property name="visible">True</property>
-                                        <property name="can_focus">False</property>
                                         <property name="spacing">12</property>
                                         <child>
                                           <placeholder/>
@@ -2455,7 +2193,6 @@
                                 <child>
                                   <object class="GtkHBox" id="hbox59">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="spacing">6</property>
                                     <child>
                                       <object class="GtkRadioButton" id="shadow_key_radio">
@@ -2463,7 +2200,6 @@
                                         <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="use_underline">True</property>
                                         <property name="focus_on_click">False</property>
                                         <property name="draw_indicator">True</property>
@@ -2478,7 +2214,6 @@
                                     <child>
                                       <object class="GtkHBox" id="shadow_key_hbox">
                                         <property name="visible">True</property>
-                                        <property name="can_focus">False</property>
                                         <property name="spacing">12</property>
                                         <child>
                                           <placeholder/>
@@ -2514,12 +2249,10 @@
                         <child>
                           <object class="GtkHBox" id="hbox62">
                             <property name="visible">True</property>
-                            <property name="can_focus">False</property>
                             <property name="spacing">12</property>
                             <child>
                               <object class="GtkLabel" id="label46">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="xalign">0</property>
                                 <property name="label" translatable="yes">Opacity:</property>
                               </object>
@@ -2532,7 +2265,6 @@
                             <child>
                               <object class="GtkHBox" id="hbox56">
                                 <property name="visible">True</property>
-                                <property name="can_focus">False</property>
                                 <property name="spacing">12</property>
                                 <child>
                                   <object class="GtkSpinButton" id="shadow_opacity_spin">
@@ -2550,7 +2282,6 @@
                                 <child>
                                   <object class="GtkLabel" id="label47">
                                     <property name="visible">True</property>
-                                    <property name="can_focus">False</property>
                                     <property name="label" translatable="yes">%</property>
                                   </object>
                                   <packing>
@@ -2588,7 +2319,6 @@
                 <child type="tab">
                   <object class="GtkLabel" id="shadow_tab_label">
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
                     <property name="label" translatable="yes">Shadow</property>
                   </object>
                   <packing>
@@ -2610,31 +2340,37 @@
             <property name="position">2</property>
           </packing>
         </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area1">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="closebutton1">
+                <property name="label">gtk-close</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</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">0</property>
+          </packing>
+        </child>
       </object>
     </child>
     <action-widgets>
       <action-widget response="-7">closebutton1</action-widget>
     </action-widgets>
   </object>
-  <object class="GtkSizeGroup" id="page_sizegroup">
-    <property name="mode">both</property>
-    <widgets>
-      <widget name="shadow_page_vbox"/>
-      <widget name="lsize_page_vbox"/>
-      <widget name="size_page_vbox"/>
-      <widget name="bc_page_vbox"/>
-      <widget name="data_page_vbox"/>
-      <widget name="img_page_vbox"/>
-      <widget name="fill_page_vbox"/>
-      <widget name="line_page_vbox"/>
-      <widget name="text_page_vbox"/>
-      <widget name="edit_page_vbox"/>
-    </widgets>
-  </object>
-  <object class="GtkSizeGroup" id="width_sizegroup">
-    <widgets>
-      <widget name="notebook"/>
-      <widget name="title_hbox"/>
-    </widgets>
-  </object>
 </interface>
diff --git a/data/ui/property-bar.ui b/data/ui/property-bar.ui
index 2bb07e6..0d3914a 100644
--- a/data/ui/property-bar.ui
+++ b/data/ui/property-bar.ui
@@ -1,13 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="2.20"/>
-  <object class="GtkAdjustment" id="adjustment1">
-    <property name="lower">1</property>
-    <property name="upper">250</property>
-    <property name="value">1</property>
-    <property name="step_increment">1</property>
-    <property name="page_increment">10</property>
-  </object>
+  <!-- interface-naming-policy toplevel-contextual -->
   <object class="GtkAdjustment" id="adjustment2">
     <property name="lower">0.25</property>
     <property name="upper">4</property>
@@ -15,25 +9,27 @@
     <property name="step_increment">0.25</property>
     <property name="page_increment">1</property>
   </object>
+  <object class="GtkAdjustment" id="adjustment1">
+    <property name="lower">1</property>
+    <property name="upper">250</property>
+    <property name="value">1</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
   <object class="GtkWindow" id="window1">
     <property name="visible">True</property>
-    <property name="can_focus">False</property>
     <property name="title" translatable="yes">window1</property>
     <child>
       <object class="GtkToolbar" id="property_toolbar">
         <property name="visible">True</property>
-        <property name="can_focus">False</property>
         <property name="toolbar_style">icons</property>
         <property name="show_arrow">False</property>
         <child>
           <object class="GtkToolItem" id="toolitem1">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="use_action_appearance">False</property>
             <child>
               <object class="GtkEventBox" id="font_family_eventbox">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="tooltip_text" translatable="yes">Font family</property>
                 <property name="visible_window">False</property>
                 <child>
@@ -49,12 +45,9 @@
         <child>
           <object class="GtkToolItem" id="toolitem2">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="use_action_appearance">False</property>
             <child>
               <object class="GtkAlignment" id="alignment1">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="left_padding">6</property>
                 <child>
                   <object class="GtkSpinButton" id="font_size_spin">
@@ -75,7 +68,6 @@
         <child>
           <object class="GtkSeparatorToolItem" id="separatortoolitem1">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -84,9 +76,7 @@
         <child>
           <object class="GtkToggleToolButton" id="font_bold_toggle">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="tooltip_text" translatable="yes">Bold</property>
-            <property name="use_action_appearance">False</property>
             <property name="use_underline">True</property>
             <property name="stock_id">gtk-bold</property>
           </object>
@@ -98,9 +88,7 @@
         <child>
           <object class="GtkToggleToolButton" id="font_italic_toggle">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="tooltip_text" translatable="yes">Italic</property>
-            <property name="use_action_appearance">False</property>
             <property name="use_underline">True</property>
             <property name="stock_id">gtk-italic</property>
           </object>
@@ -112,7 +100,6 @@
         <child>
           <object class="GtkSeparatorToolItem" id="separatortoolitem2">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -121,9 +108,7 @@
         <child>
           <object class="GtkRadioToolButton" id="text_align_left_radio">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="tooltip_text" translatable="yes">Left align</property>
-            <property name="use_action_appearance">False</property>
             <property name="use_underline">True</property>
             <property name="stock_id">gtk-justify-left</property>
           </object>
@@ -135,9 +120,7 @@
         <child>
           <object class="GtkRadioToolButton" id="text_align_center_radio">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="tooltip_text" translatable="yes">Center align</property>
-            <property name="use_action_appearance">False</property>
             <property name="use_underline">True</property>
             <property name="stock_id">gtk-justify-center</property>
             <property name="group">text_align_left_radio</property>
@@ -150,9 +133,7 @@
         <child>
           <object class="GtkRadioToolButton" id="text_align_right_radio">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
             <property name="tooltip_text" translatable="yes">Right align</property>
-            <property name="use_action_appearance">False</property>
             <property name="use_underline">True</property>
             <property name="stock_id">gtk-justify-right</property>
             <property name="group">text_align_left_radio</property>
@@ -165,60 +146,6 @@
         <child>
           <object class="GtkSeparatorToolItem" id="separatortoolitem3">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkRadioToolButton" id="text_valign_top_radio">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="tooltip_text" translatable="yes">Top vertical align</property>
-            <property name="use_action_appearance">False</property>
-            <property name="use_underline">True</property>
-            <property name="icon_name">glabels-align-text-top</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="homogeneous">True</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkRadioToolButton" id="text_valign_vcenter_radio">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="tooltip_text" translatable="yes">Center vertical align</property>
-            <property name="use_action_appearance">False</property>
-            <property name="use_underline">True</property>
-            <property name="icon_name">glabels-align-text-middle</property>
-            <property name="group">text_valign_top_radio</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="homogeneous">True</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkRadioToolButton" id="text_valign_bottom_radio">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="tooltip_text" translatable="yes">Bottom vertical align</property>
-            <property name="use_action_appearance">False</property>
-            <property name="use_underline">True</property>
-            <property name="icon_name">glabels-align-text-bottom</property>
-            <property name="group">text_valign_top_radio</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="homogeneous">True</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkSeparatorToolItem" id="separatortoolitem4">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -227,12 +154,9 @@
         <child>
           <object class="GtkToolItem" id="toolitem3">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="use_action_appearance">False</property>
             <child>
               <object class="GtkEventBox" id="text_color_eventbox">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="tooltip_text" translatable="yes">Text color</property>
                 <property name="visible_window">False</property>
                 <child>
@@ -248,12 +172,9 @@
         <child>
           <object class="GtkToolItem" id="toolitem4">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="use_action_appearance">False</property>
             <child>
               <object class="GtkEventBox" id="fill_color_eventbox">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="tooltip_text" translatable="yes">Fill color</property>
                 <property name="visible_window">False</property>
                 <child>
@@ -269,12 +190,9 @@
         <child>
           <object class="GtkToolItem" id="toolitem5">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="use_action_appearance">False</property>
             <child>
               <object class="GtkEventBox" id="line_color_eventbox">
                 <property name="visible">True</property>
-                <property name="can_focus">False</property>
                 <property name="tooltip_text" translatable="yes">Line color</property>
                 <property name="visible_window">False</property>
                 <child>
@@ -288,9 +206,8 @@
           </packing>
         </child>
         <child>
-          <object class="GtkSeparatorToolItem" id="separatortoolitem5">
+          <object class="GtkSeparatorToolItem" id="separatortoolitem4">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -299,8 +216,6 @@
         <child>
           <object class="GtkToolItem" id="toolitem6">
             <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="use_action_appearance">False</property>
             <child>
               <object class="GtkSpinButton" id="line_width_spin">
                 <property name="visible">True</property>
diff --git a/glabels/Makefile.am b/glabels/Makefile.am
new file mode 100644
index 0000000..e7ed4cf
--- /dev/null
+++ b/glabels/Makefile.am
@@ -0,0 +1,87 @@
+NULL = 
+
+bin_PROGRAMS = glabels-4
+
+glabels_4_SOURCES = \
+	glabels.vala \
+	TMP_gdk_key.vala \
+	color.vala \
+	color_button.vala \
+	color_history.vala \
+	color_menu.vala \
+	color_menu_item.vala \
+	color_node.vala \
+	color_swatch.vala \
+	enum_util.vala \
+	file.vala \
+	file_util.vala \
+	font_button.vala \
+	font_families.vala \
+	font_history.vala \
+	font_menu.vala \
+	font_menu_item.vala \
+	font_sample.vala \
+	help.vala \
+	handle.vala \
+	label.vala \
+	label_object.vala \
+	label_object_box.vala \
+	label_region.vala \
+	label_state.vala \
+	merge.vala \
+	merge_factory.vala \
+	merge_field.vala \
+	merge_none.vala \
+	merge_record.vala \
+	merge_text.vala \
+	mini_preview.vala \
+	new_label_dialog.vala \
+	prefs.vala \
+	print.vala \
+	template_history.vala \
+	ui.vala \
+	units_util.vala \
+	view.vala \
+	window.vala \
+	xml_label.vala \
+	$(NULL)
+
+
+INCLUDES = \
+	-include config.h \
+	$(GLABELS_CFLAGS) \
+	-I../libglabels \
+	-DLOCALEDIR=\""$(localedir)"\" \
+	-DDATADIR=\""$(datadir)"\" \
+	-DGLABELS_BRANCH=\""$(GLABELS_BRANCH)"\" \
+	$(NULL)
+
+VALAFLAGS = \
+	--vapidir=$(srcdir)/../vapi \
+	--vapidir=$(srcdir)/../libglabels \
+	--pkg config \
+	--pkg posix \
+	--pkg gtk+-3.0 \
+	--pkg libxml-2.0 \
+	--pkg gee-1.0 \
+	--pkg libglabels-4 \
+	$(NULL)
+
+
+glabels_4_LDADD = \
+	-L../libglabels -lglabels-4.0 \
+	$(GLABELS_LIBS) \
+	$(NULL)
+
+
+EXTRA_DIST = \
+	$(NULL)
+
+DISTCLEANFILES = \
+	$(NULL)
+
+
+$(bin_PROGRAMS): libglabels
+
+libglabels:
+	cd ../libglabels; $(MAKE)
diff --git a/glabels/TMP_gdk_key.vala b/glabels/TMP_gdk_key.vala
new file mode 100644
index 0000000..85aa8c1
--- /dev/null
+++ b/glabels/TMP_gdk_key.vala
@@ -0,0 +1,19 @@
+namespace Gdk {
+	namespace Key {
+
+		public const int Left       = 0xff51;
+		public const int Up         = 0xff52;
+		public const int Right      = 0xff53;
+		public const int Down       = 0xff54;
+
+		public const int KP_Left    = 0xff96;
+		public const int KP_Up      = 0xff97;
+		public const int KP_Right   = 0xff98;
+		public const int KP_Down    = 0xff99;
+
+		public const int KP_Delete  = 0xff9f;
+
+		public const int Delete     = 0xffff;
+
+	}
+}
\ No newline at end of file
diff --git a/glabels/color.vala b/glabels/color.vala
new file mode 100644
index 0000000..5b5e0af
--- /dev/null
+++ b/glabels/color.vala
@@ -0,0 +1,136 @@
+/*  color.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public struct Color
+	{
+		public double r { get; set; }
+		public double g { get; set; }
+		public double b { get; set; }
+		public double a { get; set; }
+
+		public Color( double r, double g, double b, double a )
+		{
+			this.r = r;
+			this.g = g;
+			this.b = b;
+			this.a = a;
+		}
+
+		public Color.from_rgba( double r, double g, double b, double a )
+		{
+			this( r, g, b, a );
+		}
+
+		public Color.from_rgb( double r, double g, double b )
+		{
+			this( r, g, b, 1.0 );
+		}
+
+		public Color.from_byte_rgba( uchar r, uchar g, uchar b, uchar a )
+		{
+			this( r/255.0, g/255.0, b/255.0, a/255.0 );
+		}
+
+		public Color.from_byte_rgb( uchar r, uchar g, uchar b )
+		{
+			this( r/255.0, g/255.0, b/255.0, 1.0 );
+		}
+
+		public Color.from_gdk_color( Gdk.Color c )
+		{
+			this( c.red/65535.0, c.green/65535.0, c.blue/65535.0, 1.0 );
+		}
+
+		public Color.from_legacy_color( uint32 c )
+		{
+			this( ((c>>24) & 0xff) / 255.0,
+			      ((c>>16) & 0xff) / 255.0,
+			      ((c>>8)  & 0xff) / 255.0,
+			      ((c)     & 0xff) / 255.0 );
+		}
+
+		public Color.none()
+		{
+			this( 0, 0, 0, 0 );
+		}
+
+		public Color.black()
+		{
+			this( 0, 0, 0, 1 );
+		}
+
+		public Color.white()
+		{
+			this( 1, 1, 1, 1 );
+		}
+
+		public Color.from_color_and_opacity( Color color,
+		                                     double opacity )
+		{
+			this( color.r, color.g, color.b, opacity * color.a );
+		}
+
+		public Gdk.Color to_gdk_color()
+		{
+			Gdk.Color c = Gdk.Color();
+
+			c.red   = (uint16) (r * 65535);
+			c.green = (uint16) (g * 65535);
+			c.blue  = (uint16) (b * 65535);
+
+			return c;
+		}
+
+		public uint32 to_legacy_color()
+		{
+			uint32 c;
+
+			c = (((uint32)(r*255) & 0xff) << 24) |
+			    (((uint32)(g*255) & 0xff) << 16) |
+			    (((uint32)(b*255) & 0xff) << 8)  |
+			    (((uint32)(a*255) & 0xff));
+
+			return c;
+		}
+
+		public void set_opacity( double opacity )
+		{
+			a *= opacity;
+		}
+
+		public bool equal( Color c )
+		{
+			return ( (this.r == c.r) && (this.g == c.g) && (this.b == c.b) && (this.a == c.a) );
+		}
+
+		public bool has_alpha()
+		{
+			return ( a != 0 );
+		}
+
+	}
+	
+}
diff --git a/glabels/color_button.vala b/glabels/color_button.vala
new file mode 100644
index 0000000..3129d9b
--- /dev/null
+++ b/glabels/color_button.vala
@@ -0,0 +1,181 @@
+/*  color_button.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using Gtk;
+
+namespace glabels
+{
+
+	public class ColorButton : Gtk.ToggleButton
+	{
+		private const int SWATCH_W = 24;
+		private const int SWATCH_H = 24;
+
+		public  signal void color_changed( Color color,
+		                                   bool  is_default );
+
+		private bool           is_default_flag;
+		private Color          color;
+
+		private Color          default_color;
+
+		private ColorSwatch    swatch;
+		private ColorMenu      menu;
+
+
+		public ColorButton( string? default_label,
+		                    Color   default_color,
+		                    Color   color )
+		{
+			if ( default_label == null )
+			{
+				default_label = _("Default color");
+			}
+
+			Gtk.HBox hbox = new Gtk.HBox( false, 3 );
+			this.add( hbox );
+
+			swatch = new ColorSwatch( SWATCH_W, SWATCH_H, color );
+			hbox.pack_start( swatch, true, true, 0 );
+
+			Gtk.Arrow arrow = new Gtk.Arrow( Gtk.ArrowType.DOWN, Gtk.ShadowType.IN );
+			hbox.pack_end( arrow, false, false, 0 );
+
+			this.default_color = default_color;
+			this.color = color;
+
+			menu = new ColorMenu( default_label, color );
+			menu.show_all();
+
+			menu.color_changed.connect( on_menu_color_changed );
+			menu.selection_done.connect( on_menu_selection_done );
+
+			this.button_press_event.connect( on_button_press_event );
+		}
+
+		public void set_color( Color color )
+		{
+			is_default_flag = false;
+			this.color = color;
+			swatch.set_color( color );
+		}
+
+		public void set_to_default()
+		{
+			is_default_flag = true;
+			color = default_color;
+			swatch.set_color( color );
+		}
+
+		public Color get_color( out bool is_default )
+		{
+			is_default = is_default_flag;
+			return color;
+		}
+
+		private void menu_position_function( Gtk.Menu menu,
+		                                     out int  x,
+		                                     out int  y,
+		                                     out bool push_in )
+		{
+			Gdk.Screen screen = this.get_screen();
+			int w_screen = screen.get_width();
+			int h_screen = screen.get_height();
+
+			Gdk.Window window = this.get_window();
+			int x_window, y_window;
+			window.get_origin( out x_window, out y_window );
+
+			Gtk.Allocation allocation;
+			this.get_allocation( out allocation );
+			int x_this = allocation.x;
+			int y_this = allocation.y;
+			int h_this = allocation.height;
+
+			int w_menu, h_menu;
+			menu.get_size_request( out w_menu, out h_menu );
+
+			x = x_window + x_this;
+			y = y_window + y_this + h_this;
+
+			if ( (y + h_menu) > h_screen )
+			{
+				y = y_window + y_this - h_menu;
+
+				if ( y < 0 )
+				{
+					y = h_screen - h_menu;
+				}
+			}
+
+			if ( (x + w_menu) > w_screen )
+			{
+				x = w_screen - w_menu;
+			}
+
+			push_in = true;
+		}
+
+		private bool on_button_press_event( Gdk.EventButton event )
+		{
+			switch (event.button)
+			{
+
+			case 1:
+				this.set_active( true );
+				menu.popup( null, null, menu_position_function, event.button, event.time );
+				break;
+
+			default:
+				break;
+
+			}
+
+			return false;
+		}
+
+		private void on_menu_color_changed( Color color, bool is_default )
+		{
+
+			if (is_default)
+			{
+				this.color = default_color;
+			}
+			else
+			{
+				this.color = color;
+			}
+			this.is_default_flag = is_default;
+
+			swatch.set_color( color );
+
+			color_changed( color, is_default );
+		}
+
+		private void on_menu_selection_done()
+		{
+			this.set_active( false );
+		}
+
+	}
+
+}
diff --git a/glabels/color_history.vala b/glabels/color_history.vala
new file mode 100644
index 0000000..097ccb8
--- /dev/null
+++ b/glabels/color_history.vala
@@ -0,0 +1,127 @@
+/*  color_history.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class ColorHistory : Object
+	{
+
+		public signal void changed();
+
+		private static GLib.Settings history;
+		private int                  max_n;
+
+		static construct
+		{
+			history = new GLib.Settings( "org.gnome.glabels-3.history" );
+		}
+
+		public ColorHistory( int n )
+		{
+			max_n = n;
+
+			history.changed["recent-colors"].connect( on_history_changed );
+		}
+
+		public void add_color( Color color )
+		{
+			size_t n;
+			uint[] old_colors = get_color_array( out n );
+			uint[] new_colors = new uint[max_n];
+			size_t i;
+
+			new_colors[0] = color.to_legacy_color();
+
+			for ( i = 0; (i < (max_n-1)) && (i < n); i++ )
+			{
+				new_colors[i+1] = old_colors[i];
+			}
+
+			set_color_array( new_colors, i+1 );
+		}
+
+		public Color get_color( int i )
+		{
+			size_t n;
+			uint[] colors = get_color_array( out n );
+			Color  color;
+
+			if ( (n > 0) && (i < n))
+			{
+				color = Color.from_legacy_color( colors[i] );
+			}
+			else
+			{
+				color = Color.none();
+			}
+
+			return color;
+		}
+
+		private void on_history_changed()
+		{
+			changed();
+		}
+
+		private uint[] get_color_array( out size_t n )
+		{
+			GLib.Variant value;
+			GLib.Variant child_value;
+			size_t       i;
+
+			value = history.get_value( "recent-colors" );
+			n     = value.n_children();
+
+			uint[] array = new uint[n];
+
+			for ( i = 0; i < n; i++ )
+			{
+				child_value = value.get_child_value( i );
+				array[i] = child_value.get_uint32();
+			}
+
+			return array;
+		}
+
+		private void set_color_array( uint[] array, size_t n )
+		{
+			GLib.Variant   value;
+			GLib.Variant[] child_values;
+			size_t         i;
+
+			child_values = new GLib.Variant[n];
+
+			for ( i = 0; i < n; i++ )
+			{
+				child_values[i] = new Variant.uint32( array[i] );
+			}
+
+			value = new Variant.array( GLib.VariantType.UINT32, child_values );
+
+			history.set_value( "recent-colors", value );
+		}
+
+	}
+
+}
diff --git a/glabels/color_menu.vala b/glabels/color_menu.vala
new file mode 100644
index 0000000..d721373
--- /dev/null
+++ b/glabels/color_menu.vala
@@ -0,0 +1,253 @@
+/*  color_menu.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class ColorMenu : Gtk.Menu
+	{
+		public  signal void color_changed( Color color,
+		                                   bool  is_default );
+
+		private Color          default_color;
+		private Color          color;
+
+		private struct ColorTableEntry {
+			uchar  r;
+			uchar  g;
+			uchar  b;
+			string name;
+		}
+
+		private const ColorTableEntry color_table[] = {
+
+			{ 139,   0,   0, N_("Dark Red") },
+			{ 165,  42,  42, N_("Brown") },
+			{ 205, 149,  12, N_("Dark Goldenrod") },
+			{   0, 100,   0, N_("Dark Green") },
+			{   0, 139, 139, N_("Dark Cyan") },
+			{   0,   0, 128, N_("Navy Blue") },
+			{ 148,   0, 211, N_("Dark Violet") },
+
+			{ 255,   0,   0, N_("Red") },
+			{ 255, 165,   0, N_("Orange") },
+			{ 205, 205,   0, N_("Dark Yellow") },
+			{   0, 205,   0, N_("Medium green") },
+			{  64, 224, 208, N_("Turquoise") },
+			{   0,   0, 255, N_("Blue") },
+			{ 160,  32, 240, N_("Purple") },
+
+			{ 250, 128, 114, N_("Salmon") },
+			{ 255, 215,   0, N_("Gold") },
+			{ 255, 255,   0, N_("Yellow") },
+			{   0, 255,   0, N_("Green") },
+			{   0, 255, 255, N_("Cyan") },
+			{ 135, 206, 235, N_("SkyBlue") },
+			{ 238, 130, 238, N_("Violet") },
+
+			{ 255, 192, 203, N_("Pink") },
+			{ 255, 246, 143, N_("Khaki") },
+			{ 255, 255, 224, N_("Light Yellow") },
+			{ 144, 238, 144, N_("Light Green") },
+			{ 224, 255, 255, N_("Light Cyan") },
+			{ 198, 226, 255, N_("Slate Gray") },
+			{ 216, 191, 216, N_("Thistle") },
+
+			{ 255, 255, 255, N_("White") },
+			/* xgettext: no-c-format */
+			{ 230, 230, 230, N_("10% Gray") },
+			/* xgettext: no-c-format */
+			{ 192, 192, 192, N_("25% Gray") },
+			/* xgettext: no-c-format */
+			{ 153, 153, 153, N_("40% Gray") },
+			/* xgettext: no-c-format */
+			{ 128, 128, 128, N_("50% Gray") },
+			/* xgettext: no-c-format */
+			{ 102, 102, 102, N_("60% Gray") },
+			{   0,   0,   0, N_("Black") }
+
+		};
+
+		private const int      PALETTE_COLS = 7;
+		private const int      PALETTE_ROWS = color_table.length/PALETTE_COLS + 1;
+
+		private const int      ROW_DEFAULT = 0;
+		private const int      ROW_SEP_1   = ROW_DEFAULT + 1;
+		private const int      ROW_PALETTE = ROW_SEP_1   + 1;
+		private const int      ROW_SEP_2   = ROW_PALETTE + PALETTE_ROWS;
+		private const int      ROW_HISTORY = ROW_SEP_2   + 1;
+		private const int      ROW_SEP_3   = ROW_HISTORY + 1;
+		private const int      ROW_CUSTOM  = ROW_SEP_3   + 1;
+
+		private	Gtk.MenuItem       default_menu_item;
+
+		private ColorHistory       custom_color_history;
+		private ColorMenuItem      history_menu_item[7];
+
+		private	Gtk.MenuItem       custom_menu_item;
+
+		public ColorMenu( string default_label,
+		                  Color  color )
+		{
+			Gtk.SeparatorMenuItem separator_menu_item;
+
+			this.default_color = this.color = color;
+
+			this.default_menu_item = new Gtk.MenuItem.with_label( default_label );
+			this.attach( this.default_menu_item, 0, PALETTE_COLS, ROW_DEFAULT, ROW_DEFAULT+1 );
+			this.default_menu_item.activate.connect( on_default_menu_item_activate );
+
+			separator_menu_item = new Gtk.SeparatorMenuItem();
+			this.attach( separator_menu_item, 0, PALETTE_COLS, ROW_SEP_1, ROW_SEP_1+1 );
+
+			for ( int i=0; i < color_table.length; i++ )
+			{
+				int i_row = i / PALETTE_COLS;
+				int i_col = i % PALETTE_COLS;
+
+				uchar r = color_table[i].r;
+				uchar g = color_table[i].g;
+				uchar b = color_table[i].b;
+
+				var palette_menu_item = new ColorMenuItem( i,
+				                                           Color.from_byte_rgb(r, g, b),
+				                                           dgettext(null, color_table[i].name) );
+
+				palette_menu_item.activated.connect( on_palette_menu_item_activated );
+
+				this.attach( palette_menu_item, i_col, i_col+1, ROW_PALETTE+i_row, ROW_PALETTE+i_row+1 );
+			}
+
+			separator_menu_item = new Gtk.SeparatorMenuItem();
+			this.attach( separator_menu_item, 0, PALETTE_COLS, ROW_SEP_2, ROW_SEP_2+1 );
+
+			for ( int i=0; i < PALETTE_COLS; i++ )
+			{
+
+				history_menu_item[i] = new ColorMenuItem( i, Color.none(), null );
+				history_menu_item[i].set_sensitive( false );
+
+				history_menu_item[i].activated.connect( on_history_menu_item_activated );
+
+				this.attach( history_menu_item[i], i, i+1, ROW_HISTORY, ROW_HISTORY+1 );
+			}
+
+			separator_menu_item = new Gtk.SeparatorMenuItem();
+			this.attach( separator_menu_item, 0, PALETTE_COLS, ROW_SEP_3, ROW_SEP_3+1 );
+
+			custom_menu_item = new Gtk.MenuItem.with_label( _("Custom color") );
+			custom_menu_item.activate.connect( on_custom_menu_item_activate );
+
+			this.attach( custom_menu_item, 0, PALETTE_COLS, ROW_CUSTOM, ROW_CUSTOM+1 );
+
+			custom_color_history = new ColorHistory( PALETTE_COLS );
+			this.map_event.connect( on_map_event );
+		}
+
+		private void on_default_menu_item_activate()
+		{
+			color = default_color;
+
+			color_changed( color, true );
+		}
+
+		private void on_palette_menu_item_activated( int id )
+		{
+			color = Color.from_byte_rgb( color_table[id].r,
+			                             color_table[id].g,
+			                             color_table[id].b );
+
+			color_changed( color, false );
+		}
+
+		private void on_history_menu_item_activated( int id )
+		{
+
+			color = custom_color_history.get_color( id );
+
+			color_changed( color, false );
+		}
+
+		private void on_custom_menu_item_activate()
+		{
+			Gtk.ColorSelectionDialog dialog     = new Gtk.ColorSelectionDialog( _("Custom Color") );
+			Gtk.ColorSelection       colorsel   = dialog.get_color_selection() as Gtk.ColorSelection;
+			Gdk.Color                gdk_color;
+			int                      response;
+
+			gdk_color = color.to_gdk_color();
+			colorsel.set_current_color( gdk_color );
+
+			response = dialog.run();
+
+			switch (response) {
+			case Gtk.ResponseType.OK:
+				colorsel.get_current_color( out gdk_color );
+
+				color = Color.from_gdk_color( gdk_color );
+				custom_color_history.add_color( color );
+
+				color_changed( color, false );
+
+				dialog.destroy();
+				break;
+
+			default:
+				dialog.destroy();
+				break;
+			}
+			
+		}
+
+		private void load_custom_color_history()
+		{
+			int i;
+
+			for ( i = 0; i < PALETTE_COLS; i++ )
+			{
+				Color color = custom_color_history.get_color( i );
+
+				if (color.a != 0)
+				{
+					string tip = _("Custom color #%d").printf( i+1 );
+
+					history_menu_item[i].set_color( i, color, tip );
+					history_menu_item[i].set_sensitive( true );
+				}
+			}
+
+		}
+
+		private bool on_map_event()
+		{
+			load_custom_color_history();
+			return false;
+		}
+
+	}
+
+}
+
+
+
+
diff --git a/glabels/color_menu_item.vala b/glabels/color_menu_item.vala
new file mode 100644
index 0000000..e216305
--- /dev/null
+++ b/glabels/color_menu_item.vala
@@ -0,0 +1,66 @@
+/*  color_menu_item.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class ColorMenuItem : Gtk.MenuItem
+	{
+		private const int    SIZE = 20;
+
+		public signal void activated( int id );
+
+		private int          id;
+		private ColorSwatch  swatch;
+
+		public ColorMenuItem( int     id,
+		                      Color   color,
+		                      string? tip )
+		{
+			this.id = id;
+			this.swatch = new ColorSwatch( SIZE, SIZE, color );
+
+			this.add( this.swatch );
+
+			this.set_tooltip_text( tip );
+
+			this.activate.connect( on_activate );
+		}
+
+		public void set_color( int    id,
+		                       Color  color,
+		                       string tip )
+		{
+			this.swatch.set_color( color );
+			this.set_tooltip_text( tip );
+		}
+
+		private void on_activate()
+		{
+			activated( id );
+		}
+
+	}
+
+}
+
diff --git a/glabels/color_node.vala b/glabels/color_node.vala
new file mode 100644
index 0000000..f92bb21
--- /dev/null
+++ b/glabels/color_node.vala
@@ -0,0 +1,93 @@
+/*  color_node.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public struct ColorNode
+	{
+		public bool    field_flag { get; set; }
+		public Color   color      { get; set; }
+		public string? key        { get; set; }
+
+
+		public ColorNode( bool field_flag, Color color, string? key )
+		{
+			this.field_flag = field_flag;
+			this.color      = color;
+			this.key        = key;
+		}
+
+
+		public ColorNode.from_color( Color color )
+		{
+			this( false, color, null );
+		}
+
+
+		public bool equal( ColorNode cn )
+		{
+			return ( (this.field_flag == cn.field_flag) && this.color.equal( cn.color ) && (this.key == cn.key) );
+		}
+
+
+		public Color expand( MergeRecord? record )
+		{
+			if ( field_flag )
+			{
+				if ( record == null )
+				{
+					return Color.none();
+				}
+				else
+				{
+					string? text = record.eval_key( key );
+					if ( text != null )
+					{
+						Gdk.Color gdk_color = Gdk.Color();
+						if ( Gdk.Color.parse( text, out gdk_color ) )
+						{
+							Color color = Color.from_gdk_color( gdk_color );
+							return color;
+						}
+						else
+						{
+							return Color.none();
+						}
+					}
+					else
+					{
+						return Color.none();
+					}
+				}
+			}
+			else
+			{
+				return color;
+			}
+		}
+
+
+	}
+
+}
diff --git a/glabels/color_swatch.vala b/glabels/color_swatch.vala
new file mode 100644
index 0000000..ce763e1
--- /dev/null
+++ b/glabels/color_swatch.vala
@@ -0,0 +1,107 @@
+/*  color_swatch.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class ColorSwatch : Gtk.DrawingArea
+	{
+		private Color color;
+
+		public ColorSwatch( int       w,
+		                    int       h,
+		                    Color     color )
+		{
+			this.set_has_window( false );
+
+			this.set_size_request( w, h );
+			this.color = color;
+		}
+
+		public override bool draw( Cairo.Context cr )
+		{
+			Gtk.Style       style;
+			double          w, h;
+			Color           fill_color, line_color;
+
+			cr.set_antialias( Cairo.Antialias.NONE );
+
+			w = get_allocated_width();
+			h = get_allocated_height();
+
+			style = this.get_style();
+			if ( this.is_sensitive() )
+			{
+				fill_color = color;
+				line_color = Color.from_gdk_color( style.fg[Gtk.StateType.NORMAL] );
+			}
+			else
+			{
+				fill_color = Color.none();
+				line_color = Color.from_gdk_color( style.fg[Gtk.StateType.INSENSITIVE] );
+			}
+
+			cr.rectangle( 1, 1, w-2, h-2 );
+
+			cr.set_source_rgba( fill_color.r, fill_color.g, fill_color.b, fill_color.a );
+			cr.fill_preserve();
+
+			cr.set_source_rgb( line_color.r, line_color.g, line_color.b );
+			cr.set_line_width( 1.0 );
+			cr.stroke();
+
+			return false;
+		}
+
+		public override void style_set( Gtk.Style? style )
+		{
+			redraw_canvas();
+		}
+
+		public void set_color( Color color )
+		{
+			if ( !this.color.equal( color ) )
+			{
+				this.color = color;
+				redraw_canvas();
+			}
+		}
+
+		private void redraw_canvas()
+		{
+			var window = get_window ();
+			if (null == window)
+			{
+				return;
+			}
+
+			unowned Cairo.Region region = window.get_clip_region ();
+			// redraw the cairo canvas completely by exposing it
+			window.invalidate_region (region, true);
+			window.process_updates (true);
+		}
+
+
+	}
+
+}
diff --git a/glabels/enum_util.vala b/glabels/enum_util.vala
new file mode 100644
index 0000000..ce2cf52
--- /dev/null
+++ b/glabels/enum_util.vala
@@ -0,0 +1,106 @@
+/*  enum_util.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	namespace EnumUtil
+	{
+
+		/****************************************************************************/
+		/* Utilities to deal with PangoAlignment types.                             */
+		/****************************************************************************/
+
+		public string align_to_string( Pango.Alignment align )
+		{
+			switch (align) {
+			case Pango.Alignment.LEFT:
+				return "left";
+			case Pango.Alignment.CENTER:
+				return "center";
+			case Pango.Alignment.RIGHT:
+				return "right";
+			default:
+				return "?";
+			}
+		}
+
+		public Pango.Alignment string_to_align( string align_string )
+		{
+			switch (align_string)
+			{
+			case "left":
+			case "Left":
+			case "LEFT":
+				return Pango.Alignment.LEFT;
+			case "center":
+			case "Center":
+			case "CENTER":
+				return Pango.Alignment.CENTER;
+			case "right":
+			case "Right":
+			case "RIGHT":
+				return Pango.Alignment.RIGHT;
+			default:
+				return Pango.Alignment.LEFT;
+			}
+		}
+
+
+		/****************************************************************************/
+		/* Utilities to deal with PangoWeight types                                 */
+		/****************************************************************************/
+
+		public string weight_to_string( Pango.Weight weight )
+		{
+			switch (weight) {
+			case Pango.Weight.NORMAL:
+				return "regular";
+			case Pango.Weight.BOLD:
+				return "bold";
+			default:
+				return "?";
+			}
+		}
+
+		public Pango.Weight string_to_weight( string weight_string )
+		{
+			switch (weight_string)
+			{
+			case "regular":
+			case "Regular":
+			case "REGULAR":
+                return Pango.Weight.NORMAL;
+			case "bold":
+			case "Bold":
+			case "BOLD":
+                return Pango.Weight.BOLD;
+			default:
+                return Pango.Weight.NORMAL;
+			}
+		}
+
+
+	}
+
+}
diff --git a/glabels/file.vala b/glabels/file.vala
new file mode 100644
index 0000000..4f5b662
--- /dev/null
+++ b/glabels/file.vala
@@ -0,0 +1,483 @@
+/*  file.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	namespace File
+	{
+		private static string? previous_open_path = null;
+		private static string? previous_save_path = null;
+
+
+		public void new_label( Window window )
+		{
+			NewLabelDialog dialog = new NewLabelDialog( window );
+			dialog.set_title( _("New Label or Card") );
+
+			dialog.show_all();
+			int response = dialog.run();
+
+			if ( response == Gtk.ResponseType.OK )
+			{
+				Label label = new Label();
+				label.template = libglabels.Db.lookup_template_from_name( dialog.template_name );
+
+				if ( window.is_empty() )
+				{
+					window.set_label( label );
+				}
+				else
+				{
+					Window new_window = new Window.from_label( label );
+					new_window.show_all();
+				}
+
+				dialog.hide();
+			}
+		}
+
+
+		public void open( Window window )
+		{
+			OpenDialog chooser = new OpenDialog( window );
+
+			chooser.response.connect( on_open_response );
+
+			chooser.show();
+		}
+
+
+		private void on_open_response( Gtk.Dialog gtk_dialog, int response )
+		{
+			OpenDialog dialog = (OpenDialog)gtk_dialog;
+
+			switch (response)
+			{
+
+			case Gtk.ResponseType.ACCEPT:
+				string raw_filename = dialog.get_filename();
+
+				string? filename = null;
+				try
+				{
+					filename = Filename.to_utf8( raw_filename, -1, null, null );
+				}
+				catch ( ConvertError e )
+				{
+					message( "Utf8 filename conversion: %s", e.message );
+				}
+
+				if ( (raw_filename == null) || (filename == null) ||
+				     FileUtils.test( filename, FileTest.IS_DIR ) )
+				{
+					var md = new Gtk.MessageDialog( dialog,
+					                                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+					                                Gtk.MessageType.WARNING,
+					                                Gtk.ButtonsType.CLOSE,
+					                                _("Empty file name selection") );
+					md.format_secondary_text( _("Please select a file or supply a valid file name") );
+
+					md.run();
+					md.destroy();
+				}
+				else
+				{
+
+					if ( !FileUtils.test( filename, FileTest.IS_REGULAR ) )
+					{
+						var md = new Gtk.MessageDialog( dialog,
+						                                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+						                                Gtk.MessageType.WARNING,
+						                                Gtk.ButtonsType.CLOSE,
+						                                _("File does not exist") );
+						md.format_secondary_text( _("Please select a file or supply a valid file name") );
+
+						md.run();
+						md.destroy();
+					}
+					else
+					{
+						if ( open_real( filename, dialog.parent_window ) )
+						{
+							dialog.destroy();
+						}
+					}
+
+				}
+				break;
+
+			default:
+				dialog.destroy();
+				break;
+
+			}
+		}
+
+
+		private bool open_real( string filename, Window parent )
+		{
+			string abs_filename = FileUtil.make_absolute( filename );
+			Label? label = null;
+			try {
+				label = XmlLabel.open_file( abs_filename );
+			}
+			catch ( XmlLabel.XmlError e )
+			{
+				var md = new Gtk.MessageDialog( parent,
+				                                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+				                                Gtk.MessageType.WARNING,
+				                                Gtk.ButtonsType.CLOSE,
+				                                _("Could not open file \"%s\""), filename );
+				md.format_secondary_text( _("Not a supported file format") );
+
+				md.run();
+				md.destroy();
+
+				return false;
+			}
+
+			if ( parent.is_empty() )
+			{
+				parent.set_label( label );
+			}
+			else
+			{
+				Window new_window = new Window.from_label( label );
+				new_window.show_all();
+			}
+
+			/* TODO: add abs_filename to recents. */
+
+			previous_open_path = Path.get_dirname( abs_filename );
+
+			return true;
+		}
+
+
+		private class OpenDialog : Gtk.FileChooserDialog
+		{
+			public Window parent_window { get; private set; }
+
+			public OpenDialog( Window window )
+			{
+				parent_window = window;
+
+				set_title( _("Open label") );
+				action = Gtk.FileChooserAction.OPEN;
+				add_button( Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL );
+				add_button(	Gtk.Stock.OPEN, Gtk.ResponseType.ACCEPT );
+
+				set_transient_for( parent_window );
+
+				/* Recover state of open dialog */
+				if ( previous_open_path != null )
+				{
+					set_current_folder( previous_open_path );
+				}
+
+				Gtk.FileFilter filter_all = new Gtk.FileFilter();
+				filter_all.add_pattern( "*" );
+				filter_all.set_name( _("All files") );
+				add_filter( filter_all );
+
+				Gtk.FileFilter filter_glabels = new Gtk.FileFilter();
+				filter_glabels.add_pattern( "*.glabels" );
+				filter_glabels.set_name( _("gLabels documents") );
+				add_filter( filter_glabels );
+
+				set_filter( filter_glabels );
+
+			}
+
+		}
+
+
+		public bool save( Label label, Window parent )
+		{
+
+			if ( label.is_untitled() )
+			{
+				return save_as( label, parent );
+			}
+
+			if ( !label.modified )
+			{
+				return true;
+			}
+
+			try
+			{
+				XmlLabel.save_file( label, label.filename );
+			}
+			catch ( XmlLabel.XmlError e )
+			{
+				var md = new Gtk.MessageDialog( parent,
+				                                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+				                                Gtk.MessageType.WARNING,
+				                                Gtk.ButtonsType.CLOSE,
+				                                _("Could not save file \"%s\""), label.filename );
+				md.format_secondary_text( _("Error encountered during save.  The file is still not saved.") );
+
+				md.run();
+				md.destroy();
+
+				return false;
+			}
+
+			/* TODO: add filename to recents. */
+
+			return true;
+		}
+
+
+		public bool save_as( Label label, Window parent )
+		{
+			SaveAsDialog chooser = new SaveAsDialog( label, parent );
+			chooser.response.connect( on_save_as_response );
+
+			chooser.show();
+
+			/* Hold here and process events until we are done with this dialog. */
+			/* This is so we can return a bollean result of our save attempt.   */
+			Gtk.main();
+
+			bool ret = chooser.saved_flag;
+
+			chooser.destroy();
+
+			return ret;
+		}
+
+
+		private void on_save_as_response( Gtk.Dialog gtk_dialog, int response )
+		{
+			SaveAsDialog dialog = (SaveAsDialog)gtk_dialog;
+
+			switch (response)
+			{
+
+			case Gtk.ResponseType.ACCEPT:
+				string raw_filename = dialog.get_filename();
+
+				if ( (raw_filename == null) || FileUtils.test( raw_filename, FileTest.IS_DIR ) )
+				{
+					var md = new Gtk.MessageDialog( dialog,
+					                                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+					                                Gtk.MessageType.WARNING,
+					                                Gtk.ButtonsType.CLOSE,
+					                                _("Empty file name selection") );
+					md.format_secondary_text( _("Please select a file or supply a valid file name") );
+
+					md.run();
+					md.destroy();
+				}
+				else
+				{
+					bool cancel_flag = false;
+
+					string full_filename = FileUtil.add_extension( raw_filename );
+
+					string? filename = null;
+					try
+					{
+						filename = Filename.to_utf8( full_filename, -1, null, null );
+					}
+					catch ( ConvertError e )
+					{
+						message( "Utf8 filename conversion: %s", e.message );
+					}
+
+					if ( FileUtils.test( filename, FileTest.IS_REGULAR ) )
+					{
+						var md = new Gtk.MessageDialog( dialog,
+						                                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+						                                Gtk.MessageType.WARNING,
+						                                Gtk.ButtonsType.YES_NO,
+						                                _("Overwrite file \"%s\"?"), filename );
+						md.format_secondary_text( _("File already exists.") );
+
+						int ret = md.run();
+						if ( ret == Gtk.ResponseType.NO )
+						{
+							cancel_flag = true;
+						}
+
+						md.destroy();
+					}
+
+					if ( !cancel_flag )
+					{
+						try
+						{
+							XmlLabel.save_file( dialog.label, filename );
+						}
+						catch ( XmlLabel.XmlError e )
+						{
+							var md = new Gtk.MessageDialog( dialog,
+							                                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+							                                Gtk.MessageType.WARNING,
+							                                Gtk.ButtonsType.CLOSE,
+							                                _("Could not save file \"%s\""), filename );
+							md.format_secondary_text( _("Error encountered during save.  The file is still not saved.") );
+
+							md.run();
+							md.destroy();
+
+							return;
+						}
+
+						dialog.saved_flag = true;
+
+						/* TODO: add filename to recents. */
+
+						previous_save_path = Path.get_dirname( filename );
+
+						Gtk.main_quit();
+
+					}
+
+				}
+				break;
+
+			default:
+				Gtk.main_quit();
+				break;
+
+			}
+		}
+
+
+		private class SaveAsDialog : Gtk.FileChooserDialog
+		{
+			public Label  label         { get; private set; }
+			public Window parent_window { get; private set; }
+			public bool   saved_flag    { get; set; default = false; }
+
+			public SaveAsDialog( Label label, Window window )
+			{
+				this.label         = label;
+				this.parent_window = window;
+
+				string title = _("Save \"%s\" as").printf( label.get_short_name() );
+
+				set_title( title );
+				action = Gtk.FileChooserAction.SAVE;
+				add_button( Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL );
+				add_button(	Gtk.Stock.SAVE, Gtk.ResponseType.ACCEPT );
+
+				set_transient_for( parent_window );
+
+				/* Recover state of open dialog */
+				if ( previous_save_path != null )
+				{
+					set_current_folder( previous_save_path );
+				}
+
+				Gtk.FileFilter filter_all = new Gtk.FileFilter();
+				filter_all.add_pattern( "*" );
+				filter_all.set_name( _("All files") );
+				add_filter( filter_all );
+
+				Gtk.FileFilter filter_glabels = new Gtk.FileFilter();
+				filter_glabels.add_pattern( "*.glabels" );
+				filter_glabels.set_name( _("gLabels documents") );
+				add_filter( filter_glabels );
+
+				set_filter( filter_glabels );
+
+			}
+
+		}
+
+
+		private void close( Window window )
+		{
+			bool close_flag = true;
+
+			if ( !window.is_empty() )
+			{
+				if ( window.view.label.modified )
+				{
+					var md = new Gtk.MessageDialog( window,
+					                                Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+					                                Gtk.MessageType.WARNING,
+					                                Gtk.ButtonsType.NONE,
+					                                _("Save changes to document \"%s\" before closing?"),
+					                                window.view.label.get_short_name() );
+					md.format_secondary_text( _("Your changes will be lost if you don't save them.") );
+
+					md.add_button( _("Close without saving"), Gtk.ResponseType.NO );
+					md.add_button( Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL );
+					md.add_button( Gtk.Stock.SAVE, Gtk.ResponseType.YES );
+					md.set_default_response( Gtk.ResponseType.YES );
+
+					md.set_resizable( false );
+
+					int ret = md.run();
+
+					md.destroy();
+
+					switch (ret)
+					{
+					case Gtk.ResponseType.YES:
+						close_flag = save( window.view.label, window );
+						break;
+					case Gtk.ResponseType.NO:
+						close_flag = true;
+						break;
+					default:
+						close_flag = false;
+						break;
+					}
+					
+
+				}
+			}
+
+			if ( close_flag )
+			{
+				window.destroy();
+			}
+
+		}
+
+
+		private void exit()
+		{
+			unowned List<Window> p;
+			unowned List<Window> p_next = null;
+
+			for ( p = Window.window_list; p != null; p = p_next )
+			{
+				p_next = p.next; /* Squirrel away next pointer since close may be destructive to list. */
+
+				close( p.data );
+			}
+		}
+
+
+	}
+
+}
+
diff --git a/glabels/file_util.vala b/glabels/file_util.vala
new file mode 100644
index 0000000..03ebf97
--- /dev/null
+++ b/glabels/file_util.vala
@@ -0,0 +1,58 @@
+/*  file_util.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	namespace FileUtil
+	{
+
+		public string make_absolute( string filename )
+		{
+			if ( Path.is_absolute( filename ) )
+			{
+				return filename;
+			}
+			else
+			{
+				string pwd = Environment.get_current_dir();
+				return Path.build_filename( pwd, filename, null );
+			}
+		}
+
+
+		public string add_extension( string filename )
+		{
+			if ( filename.has_suffix( ".glabels" ) )
+			{
+				return filename;
+			}
+			else
+			{
+				return filename.concat( ".glabels", null );
+			}
+		}
+
+	}
+
+}
diff --git a/glabels/font_button.vala b/glabels/font_button.vala
new file mode 100644
index 0000000..6d85ba3
--- /dev/null
+++ b/glabels/font_button.vala
@@ -0,0 +1,154 @@
+/*  font_button.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class FontButton : Gtk.ToggleButton
+	{
+
+		public  signal void changed();
+
+		private string       font_family;
+
+		private Gtk.Label    font_label;
+		private FontMenu     menu;
+
+
+		public FontButton( string?  font_family )
+		{
+			this.font_family = font_family;
+
+			Gtk.HBox hbox = new Gtk.HBox( false, 3 );
+			this.add( hbox );
+
+			font_label = new Gtk.Label( font_family );
+			font_label.set_alignment( 0.0f, 0.5f );
+			font_label.set_size_request( 180, -1 );
+			hbox.pack_start( font_label, true, true, 0 );
+
+			Gtk.Arrow arrow = new Gtk.Arrow( Gtk.ArrowType.DOWN, Gtk.ShadowType.IN );
+			hbox.pack_end( arrow, false, false, 0 );
+
+			this.button_press_event.connect( on_button_press_event );
+
+			menu = new FontMenu();
+			menu.show_all();
+
+			menu.font_changed.connect( on_menu_font_changed );
+			menu.selection_done.connect( on_menu_selection_done );
+
+			this.button_press_event.connect( on_button_press_event );
+		}
+
+		public void set_family( string family )
+		{
+			this.font_family = family;
+			this.font_label.set_text( family );
+		}
+
+		public string get_family()
+		{
+			return this.font_family;
+		}
+
+		private void menu_position_function( Gtk.Menu menu,
+		                                     out int  x,
+		                                     out int  y,
+		                                     out bool push_in )
+		{
+			Gdk.Screen screen = this.get_screen();
+			int w_screen = screen.get_width();
+			int h_screen = screen.get_height();
+
+			Gdk.Window window = this.get_window();
+			int x_window, y_window;
+			window.get_origin( out x_window, out y_window );
+
+			Gtk.Allocation allocation;
+			this.get_allocation( out allocation );
+			int x_this = allocation.x;
+			int y_this = allocation.y;
+			int h_this = allocation.height;
+
+			int w_menu, h_menu;
+			menu.get_size_request( out w_menu, out h_menu );
+
+			x = x_window + x_this;
+			y = y_window + y_this + h_this;
+
+			if ( (y + h_menu) > h_screen )
+			{
+				y = y_window + y_this - h_menu;
+
+				if ( y < 0 )
+				{
+					y = h_screen - h_menu;
+				}
+			}
+
+			if ( (x + w_menu) > w_screen )
+			{
+				x = w_screen - w_menu;
+			}
+
+			push_in = true;
+		}
+
+		private bool on_button_press_event( Gdk.EventButton event )
+		{
+			switch (event.button)
+			{
+
+			case 1:
+				this.set_active( true );
+				menu.popup( null, null, menu_position_function, event.button, event.time );
+				break;
+
+			default:
+				break;
+
+			}
+
+			return false;
+		}
+
+		private void on_menu_font_changed( string family )
+		{
+			this.font_family = family;
+			this.font_label.set_text( family );
+			changed();
+		}
+
+		private void on_menu_selection_done()
+		{
+			this.set_active( false );
+		}
+
+	}
+
+}
+
+
+
+
diff --git a/glabels/font_families.vala b/glabels/font_families.vala
new file mode 100644
index 0000000..b4830f7
--- /dev/null
+++ b/glabels/font_families.vala
@@ -0,0 +1,107 @@
+/*  font_families.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	class FontFamilies
+	{
+
+		public static List<string> all;
+		public static List<string> proportional;
+		public static List<string> fixed_width;
+
+
+		static construct
+		{
+			Pango.FontMap       fontmap;
+			Pango.Context       context;
+			Pango.FontFamily[]  families;
+			int                 i;
+			string              name;
+
+			fontmap = Pango.CairoFontMap.new();
+			context = fontmap.create_context();
+
+			context.list_families( out families );
+
+			for ( i = 0; i < families.length; i++ )
+			{
+				name = families[i].get_name();
+
+				all.insert_sorted( name, string.collate );
+
+				if ( families[i].is_monospace() )
+				{
+					fixed_width.insert_sorted( name, string.collate );
+				}
+				else
+				{
+					proportional.insert_sorted( name, string.collate );
+				}
+
+			}
+
+		}
+
+
+		public string validate_family( string family )
+		{
+			string good_family;
+
+			if ( all.find_custom( family, string.collate ) != null )
+			{
+				good_family = family;
+			}
+			else if ( all.find_custom( "Sans", string.collate ) != null )
+			{
+				good_family = "Sans";
+			}
+			else if ( all != null )
+			{
+				good_family = all.data;
+			}
+			else
+			{
+				good_family = null;
+			}
+
+			return good_family;
+		}
+
+
+		public bool is_family_installed( string family  )
+		{
+			unowned List<string> p;
+
+			p = all.find_custom( family, string.collate );
+
+			return ( p != null );
+		}
+
+
+	}
+
+}
+
+
diff --git a/glabels/font_history.vala b/glabels/font_history.vala
new file mode 100644
index 0000000..15ee153
--- /dev/null
+++ b/glabels/font_history.vala
@@ -0,0 +1,101 @@
+/*  font_history.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class FontHistory : Object
+	{
+
+		public signal void changed();
+
+		private static GLib.Settings history;
+		private int                  max_n;
+
+		private FontFamilies         font_families;
+
+		static construct
+		{
+			history = new GLib.Settings( "org.gnome.glabels-3.history" );
+		}
+
+		public FontHistory( int n )
+		{
+			max_n = n;
+
+			history.changed["recent-fonts"].connect( on_history_changed );
+
+			font_families = new FontFamilies();
+		}
+
+		public void add_familty( string family )
+		{
+			string[] old_families;
+			string[] new_families;
+			int      i, j;
+
+			old_families = history.get_strv( "recent-fonts" );
+
+			new_families = new string[1];
+			new_families += family;
+
+			for ( i = 0, j = 1; (j < (max_n-1)) && (i < old_families.length); i++ )
+			{
+				if ( family != old_families[i] )
+				{
+					new_families += old_families[i];
+				}
+			}
+
+			history.set_strv( "recent-fonts", new_families );
+		}
+
+		public List<string> get_family_list()
+		{
+			string[]     families;
+			List<string> family_list = new List<string>();
+			int          i;
+
+			families = history.get_strv( "recent-fonts" );
+
+			for ( i = 0; i < families.length; i++ )
+			{
+				if ( font_families.is_family_installed( families[i] ) )
+				{
+					family_list.append( families[i] );
+				}
+			}
+
+			return family_list;
+		}
+
+		private void on_history_changed()
+		{
+			changed();
+		}
+
+	}
+
+}
+
+
diff --git a/glabels/font_menu.vala b/glabels/font_menu.vala
new file mode 100644
index 0000000..a68438f
--- /dev/null
+++ b/glabels/font_menu.vala
@@ -0,0 +1,138 @@
+/*  font_menu.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class FontMenu : Gtk.Menu
+	{
+		public  signal void font_changed( string family );
+
+		private	Gtk.MenuItem   recent_menu_item;
+		private	Gtk.Menu       recent_sub_menu;
+
+		private const string[] standard_families = { "Sans", "Serif", "Monospace" };
+
+		private FontHistory    font_history;
+		private FontFamilies   font_families;
+
+		public FontMenu()
+		{
+			int          i;
+			FontMenuItem font_menu_item;
+			Gtk.MenuItem separator_item;
+			Gtk.MenuItem menu_item;
+			List<string> list;
+
+
+			font_families = new FontFamilies();
+
+
+			for ( i = 0; i < standard_families.length; i++ )
+			{
+				font_menu_item = new FontMenuItem( standard_families[i] );
+				this.append( font_menu_item );
+				font_menu_item.activated.connect( on_menu_item_activated );
+			}
+
+
+			separator_item = new Gtk.SeparatorMenuItem();
+			this.append( separator_item );
+
+
+			recent_menu_item = new Gtk.MenuItem.with_label( "Recent fonts" );
+			this.append( recent_menu_item );
+
+			font_history = new FontHistory( 10 );
+			list = font_history.get_family_list();
+			recent_sub_menu = create_font_sub_menu( list );
+			recent_menu_item.set_submenu( recent_sub_menu );
+			recent_menu_item.set_sensitive( list != null );
+
+
+			menu_item = new Gtk.MenuItem.with_label( "Proportional fonts" );
+			this.append( menu_item );
+			menu_item.set_submenu( create_font_sub_menu( font_families.proportional ) );
+			menu_item.set_sensitive( font_families.proportional != null );
+
+
+			menu_item = new Gtk.MenuItem.with_label( "Fixed-width fonts" );
+			this.append( menu_item );
+			menu_item.set_submenu( create_font_sub_menu( font_families.fixed_width ) );
+			menu_item.set_sensitive( font_families.fixed_width != null );
+
+
+			menu_item = new Gtk.MenuItem.with_label( "All fonts" );
+			this.append( menu_item );
+			menu_item.set_submenu( create_font_sub_menu( font_families.all ) );
+			menu_item.set_sensitive( font_families.all != null );
+
+
+			this.show_all();
+
+			font_history.changed.connect( on_font_history_changed );
+
+		}
+
+
+		private void on_menu_item_activated( string family )
+		{
+			font_changed( family );
+		}
+
+
+		private void on_font_history_changed()
+		{
+			List<string> list;
+
+			list = font_history.get_family_list();
+			recent_sub_menu = create_font_sub_menu( list );
+
+			recent_menu_item.set_submenu( recent_sub_menu );
+			recent_menu_item.set_sensitive( list != null );
+		}
+
+
+		private Gtk.Menu create_font_sub_menu( List<string> list )
+		{
+			Gtk.Menu menu = new Gtk.Menu();
+
+			foreach ( string s in list )
+			{
+				FontMenuItem menu_item = new FontMenuItem( s );
+				menu_item.show_all();
+				menu_item.activated.connect( on_menu_item_activated );
+				menu.append( menu_item );
+			}
+
+			return menu;
+		}
+
+	}
+
+}
+
+
+
+
+
diff --git a/glabels/font_menu_item.vala b/glabels/font_menu_item.vala
new file mode 100644
index 0000000..a3628b3
--- /dev/null
+++ b/glabels/font_menu_item.vala
@@ -0,0 +1,85 @@
+/*  font_menu_item.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class FontMenuItem : Gtk.MenuItem
+	{
+		public signal void activated( string family );
+
+		private const int    SAMPLE_W = 32;
+		private const int    SAMPLE_H = 24;
+
+		/* Translators: very short sample text, used in building font menu item icons */
+		private const string short_sample_text    = _("Aa");
+		/* Translators: lower case sample text */
+		private const string lower_case_text      = _("abcdefghijklmnopqrstuvwxyz");
+		/* Translators: upper case sample text */
+		private const string upper_case_text      = _("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+		/* Translators: numbers and special characters sample text */
+		private const string numbers_special_text = _("0123456789 .:,;(*!?)");
+
+		private string       font_family;
+
+		public FontMenuItem( string font_family )
+		{
+			this.font_family = font_family;
+
+			Gtk.HBox hbox = new Gtk.HBox( false, 6 );
+			this.add( hbox );
+
+			FontSample sample = new FontSample( SAMPLE_W, SAMPLE_H, short_sample_text, font_family );
+			hbox.pack_start( sample, false, false, 0 );
+
+			Gtk.Label label = new Gtk.Label( font_family );
+			hbox.pack_start( label, false, false, 0 );
+
+			unowned Pango.Language language = Pango.Language.get_default();
+			string sample_text = language.get_sample_string();
+
+			string tip = "<span font_family=\"%s\" size=\"x-large\" weight=\"bold\">%s\n</span>%s:\n\n<span font_family=\"%s\" size=\"large\">%s\n%s\n%s\n\n%s</span>".printf(
+				font_family, font_family,
+				_("Sample text"),
+				font_family,
+				lower_case_text,
+				upper_case_text,
+				numbers_special_text,
+				sample_text );
+
+			this.set_tooltip_markup( tip );
+
+			this.activate.connect( on_menu_item_activate );
+		}
+
+
+		private void on_menu_item_activate()
+		{
+			activated( font_family );
+		}
+
+
+	}
+
+}
+
diff --git a/glabels/font_sample.vala b/glabels/font_sample.vala
new file mode 100644
index 0000000..508e338
--- /dev/null
+++ b/glabels/font_sample.vala
@@ -0,0 +1,140 @@
+/*  font_sample.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class FontSample : Gtk.DrawingArea
+	{
+		private string sample_text;
+		private string font_family;
+
+		public FontSample( int       w,
+		                   int       h,
+		                   string    sample_text,
+		                   string    font_family )
+		{
+			this.set_has_window( false );
+
+			this.sample_text = sample_text;
+			this.font_family = font_family;
+
+			this.set_size_request( w, h );
+		}
+
+		public override bool draw( Cairo.Context cr )
+		{
+			Gtk.Style             style;
+			double                w, h;
+			Color                 fill_color, line_color;
+			Pango.Layout          layout;
+			Pango.FontDescription desc;
+			Pango.Rectangle       ink_rect, logical_rect;
+			double                layout_x, layout_y, layout_width, layout_height;
+
+			w = get_allocated_width();
+			h = get_allocated_height();
+
+			style = this.get_style();
+			if ( this.is_sensitive() )
+			{
+				fill_color = Color.from_gdk_color( style.light[Gtk.StateType.NORMAL] );
+				line_color = Color.from_gdk_color( style.fg[Gtk.StateType.NORMAL] );
+			}
+			else
+			{
+				fill_color = Color.none();
+				line_color = Color.from_gdk_color( style.fg[Gtk.StateType.INSENSITIVE] );
+			}
+
+			cr.set_antialias( Cairo.Antialias.NONE );
+
+			cr.rectangle( 1, 1, w-2, h-2 );
+
+			cr.set_source_rgba( fill_color.r, fill_color.g, fill_color.b, fill_color.a );
+			cr.fill_preserve();
+
+			cr.set_source_rgb( line_color.r, line_color.g, line_color.b );
+			cr.set_line_width( 1.0 );
+			cr.stroke();
+
+			cr.set_antialias( Cairo.Antialias.DEFAULT );
+
+			layout = Pango.cairo_create_layout( cr );
+
+			desc   = new Pango.FontDescription();
+			desc.set_family( font_family );
+			desc.set_weight( Pango.Weight.NORMAL );
+			desc.set_style( Pango.Style.NORMAL );
+			desc.set_size( (int)(0.6 * (h-1) * Pango.SCALE) );
+
+			layout.set_font_description( desc );
+			layout.set_text( sample_text, -1 );
+			layout.set_width( -1 );
+			layout.get_pixel_extents( out ink_rect, out logical_rect );
+			layout_width  = double.max( logical_rect.width, ink_rect.width );
+			layout_height = double.max( logical_rect.height, ink_rect.height );
+
+			layout_x = (w - layout_width) / 2.0;
+			layout_y = (h - layout_height) / 2.0;
+
+			if (ink_rect.x < logical_rect.x)
+			{
+				layout_x += logical_rect.x - ink_rect.x;
+			}
+
+			if (ink_rect.y < logical_rect.y)
+			{
+				layout_y += logical_rect.y - ink_rect.y;
+			}
+
+			cr.set_source_rgb( line_color.r, line_color.g, line_color.b );
+			cr.move_to( layout_x, layout_y );
+			Pango.cairo_show_layout( cr, layout );
+
+			return false;
+		}
+
+		public override void style_set( Gtk.Style? style )
+		{
+			redraw_canvas();
+		}
+
+		private void redraw_canvas()
+		{
+			var window = get_window ();
+			if (null == window)
+			{
+				return;
+			}
+
+			unowned Cairo.Region region = window.get_clip_region ();
+			// redraw the cairo canvas completely by exposing it
+			window.invalidate_region (region, true);
+			window.process_updates (true);
+		}
+
+
+	}
+
+}
diff --git a/glabels/glabels.vala b/glabels/glabels.vala
new file mode 100644
index 0000000..ca05103
--- /dev/null
+++ b/glabels/glabels.vala
@@ -0,0 +1,95 @@
+/*  glabels.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	class Main
+	{
+
+		private static string[] filenames;
+		private const OptionEntry[] option_entries = {
+			{ "", 0, 0, OptionArg.FILENAME_ARRAY, ref filenames, null, N_("[FILE...]") },
+			{ null }
+		};
+
+
+		internal static int main( string[] args )
+		{
+			OptionContext option_context = new OptionContext("");
+			option_context.set_summary( _("Launch gLabels label and business card designer.") );
+			option_context.add_main_entries( option_entries, Config.GETTEXT_PACKAGE );
+
+			Gtk.init( ref args );
+
+			try
+			{
+				option_context.parse( ref args );
+			}
+			catch ( OptionError e )
+			{
+				stderr.printf( _("%s\nRun '%s --help' to see a full list of available command line options.\n"),
+				               e.message, args[0] );
+				return 0;
+			}
+
+			libglabels.Db.init();
+			libglabels.XmlUtil.init();
+			libglabels.XmlUtil.default_units = libglabels.Units.inch();
+
+			Gtk.IconTheme icon_theme = Gtk.IconTheme.get_default();
+			icon_theme.append_search_path( Path.build_filename( Config.DATADIR, Config.GLABELS_BRANCH, "icons", null ) );
+
+
+			if ( filenames != null )
+			{
+				for ( int i = 0; filenames[i] != null; i++ )
+				{
+					try
+					{
+						Label label = XmlLabel.open_file( filenames[i] );
+						Window window = new Window.from_label( label );
+						window.show_all();
+					}
+					catch ( XmlLabel.XmlError e )
+					{
+						message( "Error opening file: %s", e.message );
+					}
+				}
+			}
+			else
+			{
+				Window window = new Window();
+				window.show_all();
+			}
+
+			Gtk.main();
+
+			return 0;
+		}
+
+	}
+
+}
+
+
diff --git a/glabels/handle.vala b/glabels/handle.vala
new file mode 100644
index 0000000..8e9121b
--- /dev/null
+++ b/glabels/handle.vala
@@ -0,0 +1,243 @@
+/*  label_object_box.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	private const double HANDLE_PIXELS               = 7;
+	private const double HANDLE_OUTLINE_WIDTH_PIXELS = 1;
+
+	private const Color HANDLE_FILL_COLOR    = { 0.0,  0.75,  0.0,  0.4 };
+	private const Color HANDLE_OUTLINE_COLOR = { 0.0,  0.0,   0.0,  0.8 };
+
+
+	public abstract class Handle
+	{
+		public LabelObject    owner { get; protected set; }
+
+		public abstract void draw( Cairo.Context cr );
+		public abstract void cairo_path( Cairo.Context cr );
+
+
+		protected static void draw_at( Cairo.Context cr,
+		                               double        x_handle,
+		                               double        y_handle )
+		{
+			cr.save();
+
+			cr.translate( x_handle, y_handle );
+
+			double scale_x = 1;
+			double scale_y = 1;
+			cr.device_to_user_distance( ref scale_x, ref scale_y );
+			cr.scale( scale_x, scale_y );
+
+			cr.rectangle( -HANDLE_PIXELS/2, -HANDLE_PIXELS/2, HANDLE_PIXELS, HANDLE_PIXELS );
+
+			cr.set_source_rgba( HANDLE_FILL_COLOR.r, HANDLE_FILL_COLOR.g, HANDLE_FILL_COLOR.b, HANDLE_FILL_COLOR.a );
+			cr.fill_preserve();
+                               
+			cr.set_line_width( HANDLE_OUTLINE_WIDTH_PIXELS );
+			cr.set_source_rgba( HANDLE_OUTLINE_COLOR.r, HANDLE_OUTLINE_COLOR.g, HANDLE_OUTLINE_COLOR.b,
+			                    HANDLE_OUTLINE_COLOR.a );
+			cr.stroke();
+
+			cr.restore();
+		}
+
+
+		protected static void cairo_path_at( Cairo.Context cr,
+		                                     double        x_handle,
+		                                     double        y_handle )
+		{
+			cr.save();
+
+			cr.translate( x_handle, y_handle );
+
+			double scale_x = 1;
+			double scale_y = 1;
+			cr.device_to_user_distance( ref scale_x, ref scale_y );
+			cr.scale( scale_x, scale_y );
+
+			cr.rectangle( -HANDLE_PIXELS/2, -HANDLE_PIXELS/2, HANDLE_PIXELS, HANDLE_PIXELS );
+
+			cr.restore();
+		}
+
+	}
+
+
+	public class HandleNorth : Handle
+	{
+		public HandleNorth( LabelObject owner )
+		{
+			this.owner = owner;
+		}
+
+		public override void draw( Cairo.Context cr )
+		{
+			draw_at( cr, owner.w/2, 0 );
+		}
+
+		public override void cairo_path( Cairo.Context cr )
+		{
+			cairo_path_at( cr, owner.w/2, 0 );
+		}
+	}
+
+
+	public class HandleNorthEast : Handle
+	{
+		public HandleNorthEast( LabelObject owner )
+		{
+			this.owner = owner;
+		}
+
+		public override void draw( Cairo.Context cr )
+		{
+			draw_at( cr, owner.w, 0 );
+		}
+
+		public override void cairo_path( Cairo.Context cr )
+		{
+			cairo_path_at( cr, owner.w, 0 );
+		}
+	}
+
+
+	public class HandleEast : Handle
+	{
+		public HandleEast( LabelObject owner )
+		{
+			this.owner = owner;
+		}
+
+		public override void draw( Cairo.Context cr )
+		{
+			draw_at( cr, owner.w, owner.h/2 );
+		}
+
+		public override void cairo_path( Cairo.Context cr )
+		{
+			cairo_path_at( cr, owner.w, owner.h/2 );
+		}
+	}
+
+
+	public class HandleSouthEast : Handle
+	{
+		public HandleSouthEast( LabelObject owner )
+		{
+			this.owner = owner;
+		}
+
+		public override void draw( Cairo.Context cr )
+		{
+			draw_at( cr, owner.w, owner.h );
+		}
+
+		public override void cairo_path( Cairo.Context cr )
+		{
+			cairo_path_at( cr, owner.w, owner.h );
+		}
+	}
+
+
+	public class HandleSouth : Handle
+	{
+		public HandleSouth( LabelObject owner )
+		{
+			this.owner = owner;
+		}
+
+		public override void draw( Cairo.Context cr )
+		{
+			draw_at( cr, owner.w/2, owner.h );
+		}
+
+		public override void cairo_path( Cairo.Context cr )
+		{
+			cairo_path_at( cr, owner.w/2, owner.h );
+		}
+	}
+
+
+	public class HandleSouthWest : Handle
+	{
+		public HandleSouthWest( LabelObject owner )
+		{
+			this.owner = owner;
+		}
+
+		public override void draw( Cairo.Context cr )
+		{
+			draw_at( cr, 0, owner.h );
+		}
+
+		public override void cairo_path( Cairo.Context cr )
+		{
+			cairo_path_at( cr, 0, owner.h );
+		}
+	}
+
+
+	public class HandleWest : Handle
+	{
+		public HandleWest( LabelObject owner )
+		{
+			this.owner = owner;
+		}
+
+		public override void draw( Cairo.Context cr )
+		{
+			draw_at( cr, 0, owner.h/2 );
+		}
+
+		public override void cairo_path( Cairo.Context cr )
+		{
+			cairo_path_at( cr, 0, owner.h/2 );
+		}
+	}
+
+
+	public class HandleNorthWest : Handle
+	{
+		public HandleNorthWest( LabelObject owner )
+		{
+			this.owner = owner;
+		}
+
+		public override void draw( Cairo.Context cr )
+		{
+			draw_at( cr, 0, 0 );
+		}
+
+		public override void cairo_path( Cairo.Context cr )
+		{
+			cairo_path_at( cr, 0, 0 );
+		}
+	}
+
+
+}
+
diff --git a/glabels/help.vala b/glabels/help.vala
new file mode 100644
index 0000000..63d9254
--- /dev/null
+++ b/glabels/help.vala
@@ -0,0 +1,99 @@
+/*  help.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	namespace Help
+	{
+
+		public void display_contents( Gtk.Window window )
+		{
+			try {
+				Gtk.show_uri( window.get_screen(), "ghelp:glabels-4.0", Gtk.get_current_event_time() );
+			}
+			catch ( Error e )
+			{
+				message( "%s", e.message );
+			}
+		}
+
+
+		public void display_about_dialog( Gtk.Window window )
+		{
+			string[] authors = {
+				"Jim Evins",
+				_("See the file AUTHORS for additional credits,"),
+				null
+			};
+
+			string[] documenters = {
+				"Jim Evins",
+				"Mario BlÃttermann",
+				null
+			};
+        
+			string[] artists = {
+				"Jim Evins",
+				null
+			};
+
+			string pixbuf_filename = Path.build_filename( Config.DATADIR, Config.GLABELS_BRANCH, "pixmaps", "glabels-logo.png", null );
+			Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_file( pixbuf_filename );
+			
+			Gtk.AboutDialog about = new Gtk.AboutDialog();
+			about.title         = _("About glabels");
+			about.program_name  = Config.PACKAGE_NAME;
+			about.version       = Config.PACKAGE_VERSION;
+			about.copyright     = "Copyright \xc2\xa9 2001-2012 Jim Evins";
+			about.comments      = _("A label and business card creation program.\n");
+			about.website       = "http://glabels.org";;
+			about.logo          = pixbuf;
+
+			about.authors       = authors;
+			about.documenters   = documenters;
+			about.artists       = artists;
+			about.translator_credits = _("translator-credits");
+			about.license            = _(
+"""gLabels 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 3 of the License, or
+(at your option) any later version.
+
+gLabels 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."""
+				);
+        
+			about.set_destroy_with_parent( true );
+
+			about.run();
+			about.hide();
+
+		}
+
+	}
+
+}
+
diff --git a/glabels/label.vala b/glabels/label.vala
new file mode 100644
index 0000000..693447b
--- /dev/null
+++ b/glabels/label.vala
@@ -0,0 +1,1470 @@
+/*  label.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+// ****************************************************************************************
+// TODO:  do checkpointing in UI code before invoking changes to Label or LabelObject*s.
+// ****************************************************************************************
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	public class Label : Object
+	{
+		public signal void name_changed();
+		public signal void selection_changed();
+		public signal void modified_changed();
+		public signal void size_changed();
+		public signal void changed();
+		public signal void merge_changed();
+
+
+		public unowned List<LabelObject>  object_list { get; private set; }
+
+
+		private TemplateHistory    template_history;
+
+		private static int         untitled_count;
+		private int                untitled_instance;
+
+		private bool               selection_op_flag;
+		private bool               delayed_change_flag;
+
+
+		// TODO: Pixbuf cache
+		// TODO: SVG cache
+
+
+		/* Clipboard storage. */
+		private string?     clipboard_xml_buffer;
+		private string?     clipboard_text;
+		private Gdk.Pixbuf? clipboard_pixbuf;
+
+
+		/* Undo/Redo state */
+		private Queue<LabelState?> undo_stack;
+		private Queue<LabelState?> redo_stack;
+		private bool               cp_cleared_flag;
+		private string             cp_desc;
+
+
+		/**
+		 * Filename
+		 */
+		public string? filename
+		{
+			get { return _filename; }
+
+			set
+			{
+				if ( _filename != value )
+				{
+					_filename = value;
+					name_changed();
+				}
+			}
+		}
+		private string? _filename;
+
+
+		/**
+		 * Compression mode ( 0 = no compression, 9 = max compression )
+		 */
+		public int compression
+		{
+			get { return _compression; }
+
+			set
+			{
+				if ( (value < 0) && (value > 9) )
+				{
+					warning( "Compression mode out of range." );
+					_compression = 9;
+				}
+				else
+				{
+					_compression = value;
+				}
+			}
+		}
+		private int _compression = 9;
+
+
+		/**
+		 * Modified flag
+		 */
+		public bool modified
+		{
+			get { return _modified; }
+
+			set
+			{
+				if ( _modified != value )
+				{
+					_modified = value;
+					if ( !_modified )
+					{
+						time_stamp.get_current_time();
+					}
+					modified_changed();
+				}
+			}
+		}
+		private bool _modified;
+
+		public TimeVal  time_stamp { get; private set; }
+
+
+		/**
+		 * Template
+		 */
+		public Template template
+		{
+			get { return _template; }
+
+			set
+			{
+				if ( _template != value )
+				{
+					_template = value;
+					changed();
+					size_changed();
+					template_history.add_name( template.name );
+					modified = true;
+				}
+			}
+		}
+		private Template _template;
+
+
+		/**
+		 * Rotate
+		 */
+		public bool rotate
+		{
+			get { return _rotate; }
+
+			set
+			{
+				if ( _rotate != value )
+				{
+					_rotate = value;
+					changed();
+					size_changed();
+					modified = true;
+				}
+			}
+		}
+		private bool _rotate;
+
+
+		/**
+		 * Merge
+		 */
+		public Merge merge
+		{
+			get { return _merge; }
+
+			set
+			{
+				if ( _merge != value )
+				{
+					_merge = value;
+					changed();
+					merge_changed();
+					modified = true;
+				}
+			}
+		}
+		private Merge _merge;
+
+
+		/* Default object text properties */
+		public string             default_font_family       { get; set; }
+		public double             default_font_size         { get; set; }
+		public Pango.Weight       default_font_weight       { get; set; default=Pango.Weight.NORMAL; }
+		public bool               default_font_italic_flag  { get; set; }
+		public Color              default_text_color        { get; set; }
+		public Pango.Alignment    default_text_alignment    { get; set; }
+		public double             default_text_line_spacing { get; set; }
+
+		/* Default object line properties */
+		public double             default_line_width        { get; set; }
+		public Color              default_line_color        { get; set; }
+        
+		/* Default object fill properties */
+		public Color              default_fill_color        { get; set; }
+
+
+
+		public Label()
+		{
+			_merge = new MergeNone();
+
+			template_history = new TemplateHistory( 5 );
+
+			undo_stack = new Queue<LabelState?>();
+			redo_stack = new Queue<LabelState?>();
+
+			// TODO: Set default properties from user prefs
+		}
+
+
+
+		public string get_short_name()
+		{
+			if ( filename == null )
+			{
+
+				if ( untitled_instance == 0 )
+				{
+					untitled_instance = ++untitled_count;
+				}
+
+				return "%s %d".printf( _("Untitled"), untitled_instance );
+
+			}
+			else
+			{
+
+				string base_name = Path.get_basename( filename );
+				try
+				{
+					Regex ext_pattern = new Regex( "\\.glabels$" );
+					string short_name = ext_pattern.replace( base_name, -1, 0, "" );
+					return short_name;
+				}
+				catch ( RegexError e )
+				{
+					warning( "%s", e.message );
+					return base_name;
+				}
+
+
+			}
+		}
+
+
+		public bool is_untitled()
+		{
+			return filename == null;
+		}
+
+
+		public void get_size( out double w,
+		                      out double h )
+		{
+			if ( template == null )
+			{
+				w = 0;
+				h = 0;
+				return;
+			}
+
+			TemplateFrame frame = template.frames.first().data;
+
+			if ( !rotate )
+			{
+				frame.get_size( out w, out h );
+			}
+			else
+			{
+				frame.get_size( out h, out w );
+			}
+		}
+
+
+		public void add_object( LabelObject object )
+		{
+			object.parent = this;
+			object_list.append( object );
+
+			object.changed.connect( on_object_changed );
+			object.moved.connect( on_object_moved );
+
+			changed();
+			modified = true;
+		}
+
+
+		public void delete_object( LabelObject object )
+		{
+			object_list.remove( object );
+
+			object.changed.disconnect( on_object_changed );
+			object.moved.disconnect( on_object_moved );
+
+			changed();
+			modified = true;
+		}
+
+
+		private void on_object_changed()
+		{
+			schedule_or_emit_changed_signal();
+		}
+
+
+		private void on_object_moved()
+		{
+			schedule_or_emit_changed_signal();
+		}
+
+
+		public void draw( Cairo.Context cr,
+		                  bool          in_editor,
+		                  MergeRecord?  record )
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				object.draw( cr, in_editor, record );
+			}
+		}
+
+
+		public LabelObject? object_at( Cairo.Context cr,
+		                               double        x_pixels,
+		                               double        y_pixels )
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_located_at( cr, x_pixels, y_pixels ) )
+				{
+					return object;
+				}
+			}
+
+			return null;
+		}
+
+
+		public Handle? handle_at( Cairo.Context cr,
+		                          double        x_pixels,
+		                          double        y_pixels )
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				Handle? handle = object.handle_at( cr, x_pixels, y_pixels );
+
+				if ( handle != null )
+				{
+					return handle;
+				}
+			}
+
+			return null;
+		}
+
+
+		public void select_object( LabelObject object )
+		{
+			object.select();
+			cp_cleared_flag = true;
+			selection_changed();
+		}
+
+
+		public void unselect_object( LabelObject object )
+		{
+			object.unselect();
+			cp_cleared_flag = true;
+			selection_changed();
+		}
+
+
+		public void select_all()
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				object.select();
+			}
+			cp_cleared_flag = true;
+			selection_changed();
+		}
+
+
+		public void unselect_all()
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				object.unselect();
+			}
+			cp_cleared_flag = true;
+			selection_changed();
+		}
+
+
+		public void select_region( LabelRegion region )
+		{
+			double r_x1 = double.min( region.x1, region.x2 );
+			double r_y1 = double.min( region.y1, region.y2 );
+			double r_x2 = double.max( region.x1, region.x2 );
+			double r_y2 = double.max( region.y1, region.y2 );
+
+			foreach ( LabelObject object in object_list )
+			{
+				LabelRegion obj_extent = object.get_extent();
+
+				if ( (obj_extent.x1 >= r_x1) &&
+				     (obj_extent.x2 <= r_x2) &&
+				     (obj_extent.y1 >= r_y1) &&
+				     (obj_extent.y2 <= r_y2) )
+				{
+					object.select();
+				}
+			}
+			cp_cleared_flag = true;
+			selection_changed();
+		}
+
+
+		public bool is_selection_empty()
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					return false;
+				}
+			}
+			return true;
+		}
+
+
+		public bool is_selection_atomic()
+		{
+			int n_selected = 0;
+
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					n_selected++;
+					if ( n_selected > 1 )
+					{
+						return false;
+					}
+				}
+			}
+			return (n_selected == 1);
+		}
+
+
+		public LabelObject? get_1st_selected_object()
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					return object;
+				}
+			}
+			return null;
+		}
+
+
+		public List<LabelObject> get_selection_list()
+		{
+			List<LabelObject> selection_list = null;
+
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					selection_list.append( object );
+				}
+			}
+			return selection_list;
+		}
+
+
+		public bool can_selection_text()
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() && object.can_text() )
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		public bool can_selection_fill()
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() && object.can_fill() )
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		public bool can_selection_line_color()
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() && object.can_line_color() )
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		public bool can_selection_line_width()
+		{
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() && object.can_line_width() )
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		private void schedule_or_emit_changed_signal()
+		{
+			if ( selection_op_flag )
+			{
+				delayed_change_flag = true;
+			}
+			else
+			{
+				modified = true;
+				changed();
+			}
+		}
+
+
+		private void begin_selection_op()
+		{
+			selection_op_flag = true;
+		}
+
+
+		private void end_selection_op()
+		{
+			selection_op_flag = false;
+			if ( delayed_change_flag )
+			{
+				delayed_change_flag = false;
+				changed();
+				modified = true;
+			}
+		}
+
+
+		public void delete_selection()
+		{
+			List<LabelObject> selection_list = get_selection_list();
+
+			foreach ( LabelObject object in selection_list )
+			{
+				delete_object( object );
+			}
+
+			changed();
+			modified = true;
+		}
+
+
+		public void raise_selection_to_top()
+		{
+			List<LabelObject> selection_list = get_selection_list();
+
+			foreach ( LabelObject object in selection_list )
+			{
+				object_list.remove( object );
+			}
+
+			/* Move to end of list, representing front most object */
+			foreach ( LabelObject object in selection_list )
+			{
+				object_list.append( object );
+			}
+
+			changed();
+			modified = true;
+		}
+
+
+		public void lower_selection_to_bottom()
+		{
+			List<LabelObject> selection_list = get_selection_list();
+
+			foreach ( LabelObject object in selection_list )
+			{
+				object_list.remove( object );
+			}
+
+			/* Move to end of list, representing front most object */
+			foreach ( LabelObject object in object_list )
+			{
+				selection_list.append( object );
+			}
+			object_list = selection_list;
+
+			changed();
+			modified = true;
+		}
+
+
+		public void rotate_selection( double theta_degs )
+		{
+			begin_selection_op();
+
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.rotate( theta_degs );
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void rotate_selection_left()
+		{
+			begin_selection_op();
+
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.rotate( -90.0 );
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void rotate_selection_right()
+		{
+			begin_selection_op();
+
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.rotate( 90.0 );
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void flip_selection_horiz()
+		{
+			begin_selection_op();
+
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.flip_horiz();
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void flip_selection_vert()
+		{
+			begin_selection_op();
+
+			foreach ( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.flip_vert();
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void align_selection_left()
+		{
+			if ( is_selection_empty() || is_selection_atomic() )
+			{
+				return;
+			}
+
+			begin_selection_op();
+
+			List<LabelObject> selection_list = get_selection_list();
+
+			/* Find left most edge. */
+			LabelRegion obj_extent = selection_list.first().data.get_extent();
+			double x1_min = obj_extent.x1;
+			foreach ( LabelObject object in selection_list.nth(1) )
+			{
+				obj_extent = object.get_extent();
+				if ( obj_extent.x1 < x1_min ) x1_min = obj_extent.x1;
+			}
+
+			/* Now adjust the object positions to line up the left edges. */
+			foreach ( LabelObject object in selection_list )
+			{
+				obj_extent = object.get_extent();
+				double dx = x1_min - obj_extent.x1;
+				object.set_position_relative( dx, 0 );
+			}
+
+			end_selection_op();
+		}
+
+
+		public void align_selection_right()
+		{
+			if ( is_selection_empty() || is_selection_atomic() )
+			{
+				return;
+			}
+
+			begin_selection_op();
+
+			List<LabelObject> selection_list = get_selection_list();
+
+			/* Find right most edge. */
+			LabelRegion obj_extent = selection_list.first().data.get_extent();
+			double x2_max = obj_extent.x2;
+			foreach ( LabelObject object in selection_list.nth(1) )
+			{
+				obj_extent = object.get_extent();
+				if ( obj_extent.x2 > x2_max ) x2_max = obj_extent.x2;
+			}
+
+			/* Now adjust the object positions to line up the right edges. */
+			foreach ( LabelObject object in selection_list )
+			{
+				obj_extent = object.get_extent();
+				double dx = x2_max - obj_extent.x2;
+				object.set_position_relative( dx, 0 );
+			}
+
+			end_selection_op();
+		}
+
+
+		public void align_selection_hcenter()
+		{
+			if ( is_selection_empty() || is_selection_atomic() )
+			{
+				return;
+			}
+
+			begin_selection_op();
+
+			List<LabelObject> selection_list = get_selection_list();
+			LabelRegion obj_extent;
+
+			/* Find average center of objects. */
+			double xsum = 0;
+			int    n = 0;
+			foreach ( LabelObject object in selection_list )
+			{
+				obj_extent = object.get_extent();
+				xsum += (obj_extent.x1 + obj_extent.x2) / 2.0;
+				n++;
+			}
+			double xavg = xsum / n;
+
+			/* find center of object closest to average center */
+			obj_extent = selection_list.first().data.get_extent();
+			double xcenter = (obj_extent.x1 + obj_extent.x2) / 2.0;
+			double dxmin = Math.fabs( xavg - xcenter );
+			foreach ( LabelObject object in selection_list.nth(1) )
+			{
+				obj_extent = object.get_extent();
+				double dx = Math.fabs( xavg - (obj_extent.x1 + obj_extent.x2)/2.0 );
+				if ( dx < dxmin )
+				{
+					dxmin = dx;
+					xcenter = (obj_extent.x1 + obj_extent.x2) / 2.0;
+				}
+			}
+
+			/* Now adjust the object positions to line up with this center. */
+			foreach ( LabelObject object in selection_list )
+			{
+				obj_extent = object.get_extent();
+				double dx = xcenter - (obj_extent.x1 + obj_extent.x2)/2.0;
+				object.set_position_relative( dx, 0 );
+			}
+
+			end_selection_op();
+		}
+
+
+		public void align_selection_top()
+		{
+			if ( is_selection_empty() || is_selection_atomic() )
+			{
+				return;
+			}
+
+			begin_selection_op();
+
+			List<LabelObject> selection_list = get_selection_list();
+
+			/* Find top most edge. */
+			LabelRegion obj_extent = selection_list.first().data.get_extent();
+			double y1_min = obj_extent.y1;
+			foreach ( LabelObject object in selection_list.nth(1) )
+			{
+				obj_extent = object.get_extent();
+				if ( obj_extent.y1 < y1_min ) y1_min = obj_extent.y1;
+			}
+
+			/* Now adjust the object positions to line up the top edges. */
+			foreach ( LabelObject object in selection_list )
+			{
+				obj_extent = object.get_extent();
+				double dy = y1_min - obj_extent.y1;
+				object.set_position_relative( 0, dy );
+			}
+
+			end_selection_op();
+		}
+
+
+		public void align_selection_bottom()
+		{
+			if ( is_selection_empty() || is_selection_atomic() )
+			{
+				return;
+			}
+
+			begin_selection_op();
+
+			List<LabelObject> selection_list = get_selection_list();
+
+			/* Find bottom most edge. */
+			LabelRegion obj_extent = selection_list.first().data.get_extent();
+			double y2_max = obj_extent.y2;
+			foreach ( LabelObject object in selection_list.nth(1) )
+			{
+				obj_extent = object.get_extent();
+				if ( obj_extent.y2 > y2_max ) y2_max = obj_extent.y2;
+			}
+
+			/* Now adjust the object positions to line up the bottom edges. */
+			foreach ( LabelObject object in selection_list )
+			{
+				obj_extent = object.get_extent();
+				double dy = y2_max - obj_extent.y2;
+				object.set_position_relative( 0, dy );
+			}
+
+			end_selection_op();
+		}
+
+
+		public void align_selection_vcenter()
+		{
+			if ( is_selection_empty() || is_selection_atomic() )
+			{
+				return;
+			}
+
+			begin_selection_op();
+
+			List<LabelObject> selection_list = get_selection_list();
+			LabelRegion obj_extent;
+
+			/* Find average center of objects. */
+			double ysum = 0;
+			int    n = 0;
+			foreach ( LabelObject object in selection_list )
+			{
+				obj_extent = object.get_extent();
+				ysum += (obj_extent.y1 + obj_extent.y2) / 2.0;
+				n++;
+			}
+			double yavg = ysum / n;
+
+			/* find center of object closest to average center */
+			obj_extent = selection_list.first().data.get_extent();
+			double ycenter = (obj_extent.y1 + obj_extent.y2) / 2.0;
+			double dymin = Math.fabs( yavg - ycenter );
+			foreach ( LabelObject object in selection_list.nth(1) )
+			{
+				obj_extent = object.get_extent();
+				double dy = Math.fabs( yavg - (obj_extent.y1 + obj_extent.y2)/2.0 );
+				if ( dy < dymin )
+				{
+					dymin = dy;
+					ycenter = (obj_extent.y1 + obj_extent.y2) / 2.0;
+				}
+			}
+
+			/* Now adjust the object positions to line up with this center. */
+			foreach ( LabelObject object in selection_list )
+			{
+				obj_extent = object.get_extent();
+				double dy = ycenter - (obj_extent.y1 + obj_extent.y2)/2.0;
+				object.set_position_relative( 0, dy );
+			}
+
+			end_selection_op();
+		}
+
+
+		public void center_selection_horiz()
+		{
+			begin_selection_op();
+
+			double w, h;
+			get_size( out w, out h );
+			double x_label_center = w / 2.0;
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					LabelRegion obj_extent = object.get_extent();
+					double x_obj_center = (obj_extent.x1 + obj_extent.x2) / 2.0;
+					double dx = x_label_center - x_obj_center;
+					object.set_position_relative( dx, 0 );
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void center_selection_vert()
+		{
+			begin_selection_op();
+
+			double w, h;
+			get_size( out w, out h );
+			double y_label_center = h / 2.0;
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					LabelRegion obj_extent = object.get_extent();
+					double y_obj_center = (obj_extent.y1 + obj_extent.y2) / 2.0;
+					double dy = y_label_center - y_obj_center;
+					object.set_position_relative( 0, dy );
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void move_selection( double dx,
+		                            double dy )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.set_position_relative( dx, dy );
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_font_family( string font_family )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.font_family = font_family;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_font_size( double font_size )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.font_size = font_size;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_font_weight( Pango.Weight font_weight )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.font_weight = font_weight;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_font_italic_flag( bool font_italic_flag )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.font_italic_flag = font_italic_flag;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_text_alignment( Pango.Alignment text_alignment )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.text_alignment = text_alignment;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_text_line_spacing( double text_line_spacing )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.text_line_spacing = text_line_spacing;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_text_color( ColorNode text_color_node )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.text_color_node = text_color_node;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_line_width( double line_width )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.line_width = line_width;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_line_color( ColorNode line_color_node )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.line_color_node = line_color_node;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void set_selection_fill_color( ColorNode fill_color_node )
+		{
+			begin_selection_op();
+
+			foreach( LabelObject object in object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.fill_color_node = fill_color_node;
+				}
+			}
+
+			end_selection_op();
+		}
+
+
+		public void cut_selection()
+		{
+			copy_selection();
+			delete_selection();
+		}
+
+
+		public void copy_selection()
+		{
+			const Gtk.TargetEntry glabels_targets[] = {
+				{ "application/glabels", 0, 0 },
+				{ "text/xml",            0, 0 }
+			};
+
+			Gtk.Clipboard clipboard = Gtk.Clipboard.get( Gdk.SELECTION_CLIPBOARD );
+
+			List<LabelObject> selection_list = get_selection_list();
+
+			if ( selection_list != null )
+			{
+
+				Gtk.TargetList target_list = new Gtk.TargetList( glabels_targets );
+
+				/*
+				 * Serialize selection by encoding as an XML label document.
+				 */
+				Label label_copy = new Label();
+
+				label_copy.template = template;
+				label_copy.rotate = rotate;
+
+				foreach ( LabelObject object in object_list )
+				{
+					label_copy.add_object( object );
+				}
+
+				// TODO: set clipboard_xml_buffer from label_copy
+
+				/*
+				 * Is it an atomic text selection?  If so, also make available as text.
+				 */
+				if ( is_selection_atomic() /* && TODO: first object is LabelObjectText */ )
+				{
+					target_list.add_text_targets( 1 );
+					// TODO: set clipboard_text from LabelObjectText get_text()
+				}
+
+				/*
+				 * Is it an atomic image selection?  If so, also make available as pixbuf.
+				 */
+				if ( is_selection_atomic() /* && TODO: first object is LabelObjectImage */ )
+				{
+					// TODO: pixbuf = LabelObjectImage get_pixbuf
+					// TODO: if ( pixbuf != null )
+					{
+						target_list.add_image_targets( 2, true );
+						// TODO: set clipboard_pixbuf = pixbuf
+					}
+				}
+
+				Gtk.TargetEntry[] target_table = Gtk.target_table_new_from_list( target_list );
+
+				clipboard.set_with_owner( target_table,
+				                          (Gtk.ClipboardGetFunc)clipboard_get_cb,
+				                          (Gtk.ClipboardClearFunc)clipboard_clear_cb, this );
+
+			}
+
+		}
+
+
+		public void paste()
+		{
+			Gtk.Clipboard clipboard = Gtk.Clipboard.get( Gdk.SELECTION_CLIPBOARD );
+
+			clipboard.request_targets( clipboard_receive_targets_cb );
+		}
+
+
+		public bool can_paste()
+		{
+			Gtk.Clipboard clipboard = Gtk.Clipboard.get( Gdk.SELECTION_CLIPBOARD );
+
+			return ( clipboard.wait_is_target_available( Gdk.Atom.intern("application/glabels", true) ) ||
+			         clipboard.wait_is_text_available()                                                 ||
+			         clipboard.wait_is_image_available() );
+		}
+
+
+		private void clipboard_get_cb( Gtk.Clipboard     clipboard,
+		                               Gtk.SelectionData selection_data,
+		                               uint              info,
+		                               void*             user_data )
+		{
+			switch (info)
+			{
+			case 0:
+				selection_data.set( selection_data.get_target(),
+				                    8,
+				                    (uchar[])clipboard_xml_buffer );
+				break;
+
+			case 1:
+				selection_data.set_text( clipboard_text, -1 );
+				break;
+
+			case 2:
+				selection_data.set_pixbuf( clipboard_pixbuf );
+				break;
+
+			default:
+				assert_not_reached();
+
+			}
+		}
+
+
+		private void clipboard_clear_cb( Gtk.Clipboard clipboard,
+		                                 void*         user_data )
+		{
+			clipboard_xml_buffer = null;
+			clipboard_text       = null;
+			clipboard_pixbuf     = null;
+		}
+
+
+		private void clipboard_receive_targets_cb( Gtk.Clipboard clipboard,
+		                                           Gdk.Atom[]    targets )
+		{
+
+			/*
+			 * Application/glabels
+			 */
+			for ( int i = 0; i < targets.length; i++ )
+			{
+				if ( targets[i].name() == "application/glabels" )
+				{
+					clipboard.request_contents( targets[i], paste_xml_received_cb );
+					return;
+				}
+			}
+
+			/*
+			 * Text
+			 */
+			if ( Gtk.targets_include_text( targets ) )
+			{
+				clipboard.request_text( paste_text_received_cb );
+				return;
+			}
+
+			/*
+			 * Image
+			 */
+			if ( Gtk.targets_include_image( targets, true ) )
+			{
+				clipboard.request_image( paste_image_received_cb );
+				return;
+			}
+
+		}
+
+
+		private void paste_xml_received_cb( Gtk.Clipboard     clipboard,
+		                                    Gtk.SelectionData selection_data )
+		{
+			string xml_buffer = (string)selection_data.get_data();
+
+			/*
+			 * Deserialize XML label document and extract objects.
+			 */
+			// TODO:  label_copy = xml_label_open_buffer( xml_buffer )
+			// unselect all
+			// foreach object in label copy, add to this, select each object as added.
+
+		}
+
+
+		private void paste_text_received_cb( Gtk.Clipboard     clipboard,
+		                                     string?           text )
+		{
+			unselect_all();
+			// TODO:  create new LabelObjectText object from text.  set to a default location, select.
+		}
+
+
+		private void paste_image_received_cb( Gtk.Clipboard     clipboard,
+		                                      Gdk.Pixbuf        pixbuf )
+		{
+			unselect_all();
+			// TODO:  create new LabelObjectImage object from pixbuf.  set to a default location, select.
+		}
+
+
+		public void checkpoint( string description )
+		{
+			/*
+			 * Do not perform consecutive checkpoints that are identical.
+			 * E.g. moving an object by dragging, would produce a large number
+			 * of incremental checkpoints -- what we really want is a single
+			 * checkpoint so that we can undo the entire dragging effort with
+			 * one "undo"
+			 */
+			if ( cp_cleared_flag || (cp_desc == null) || ( description != cp_desc) )
+			{
+
+				/* Sever old redo "thread" */
+				stack_clear(redo_stack);
+
+				/* Save state onto undo stack. */
+				LabelState state = new LabelState( description, this );
+				undo_stack.push_head( state );
+
+				/* Track consecutive checkpoints. */
+				cp_cleared_flag = false;
+				cp_desc         = description;
+			}
+
+		}
+
+
+		public void undo()
+		{
+			LabelState state_old = undo_stack.pop_head();
+			LabelState state_now = new LabelState( state_old.description, this );
+
+			redo_stack.push_head( state_now );
+
+			state_old.restore( this );
+
+			cp_cleared_flag = true;
+
+			selection_changed();
+		}
+
+
+		public void redo()
+		{
+			LabelState state_old = redo_stack.pop_head();
+			LabelState state_now = new LabelState( state_old.description, this );
+
+			undo_stack.push_head( state_now );
+
+			state_old.restore( this );
+
+			cp_cleared_flag = true;
+
+			selection_changed();
+		}
+
+
+		public bool can_undo()
+		{
+			return ( !undo_stack.is_empty() );
+		}
+
+
+		public bool can_redo()
+		{
+			return ( !redo_stack.is_empty() );
+		}
+
+
+		public string get_undo_description()
+		{
+			LabelState state = undo_stack.peek_head();
+			if ( state != null )
+			{
+				return state.description;
+			}
+			else
+			{
+				return "";
+			}
+		}
+
+
+		public string get_redo_description()
+		{
+			LabelState state = redo_stack.peek_head();
+			if ( state != null )
+			{
+				return state.description;
+			}
+			else
+			{
+				return "";
+			}
+		}
+
+
+		private void stack_clear( Queue<LabelState> stack )
+		{
+			while ( stack.pop_head() != null ) {}
+		}
+
+
+	}
+
+}
diff --git a/glabels/label_object.vala b/glabels/label_object.vala
new file mode 100644
index 0000000..51fce90
--- /dev/null
+++ b/glabels/label_object.vala
@@ -0,0 +1,725 @@
+/*  label_object.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public abstract class LabelObject : Object
+	{
+		public signal void changed();
+		public signal void moved();
+
+
+		private   bool         selected;
+		protected List<Handle> handles;
+
+		private   double       aspect_ratio;
+
+
+		/**
+		 * Parent label
+		 */
+		public Label parent { get; set; }
+
+
+		/**
+		 * X coordinate of origin
+		 */
+		public double x0
+		{
+			get { return _x0; }
+
+			set
+			{
+				if ( _x0 != value )
+				{
+					_x0 = value;
+					moved();
+				}
+			}
+		}
+		private double _x0;
+
+
+		/**
+		 * Y coordinate of origin
+		 */
+		public double y0
+		{
+			get { return _y0; }
+
+			set
+			{
+				if ( _y0 != value )
+				{
+					_y0 = value;
+					moved();
+				}
+			}
+		}
+		private double _y0;
+
+
+		/**
+		 * Width of bounding box
+		 */
+		public double w
+		{
+			get { return _w; }
+
+			set
+			{
+				if ( _w != value )
+				{
+					_w = value;
+					aspect_ratio = _h / _w;
+					changed();
+				}
+			}
+		}
+		private double _w;
+
+
+		/**
+		 * Height of bounding box
+		 */
+		public double h
+		{
+			get { return _h; }
+
+			set
+			{
+				if ( _h != value )
+				{
+					_h = value;
+					aspect_ratio = _h / _w;
+					changed();
+				}
+			}
+		}
+		private double _h;
+
+
+		/**
+		 * Transformation matrix
+		 */
+		public Cairo.Matrix matrix
+		{
+			get { return _matrix; }
+
+			set
+			{
+				if ( _matrix != value )
+				{
+					_matrix = value;
+					changed();
+				}
+			}
+		}
+		private Cairo.Matrix _matrix;
+
+
+		/**
+		 * Font family
+		 */
+		public string font_family
+		{
+			get { return _font_family; }
+
+			set
+			{
+				if ( _font_family != value )
+				{
+					_font_family = value;
+					changed();
+				}
+			}
+		}
+		private string _font_family;
+
+
+		/**
+		 * Font size
+		 */
+		public double font_size
+		{
+			get { return _font_size; }
+
+			set
+			{
+				if ( _font_size != value )
+				{
+					_font_size = value;
+					changed();
+				}
+			}
+		}
+		private double _font_size;
+
+
+		/**
+		 * Font weight
+		 */
+		public Pango.Weight font_weight
+		{
+			get { return _font_weight; }
+
+			set
+			{
+				if ( _font_weight != value )
+				{
+					_font_weight = value;
+					changed();
+				}
+			}
+		}
+		private Pango.Weight _font_weight = Pango.Weight.NORMAL;
+
+
+		/**
+		 * Font italic flag
+		 */
+		public bool font_italic_flag
+		{
+			get { return _font_italic_flag; }
+
+			set
+			{
+				if ( _font_italic_flag != value )
+				{
+					_font_italic_flag = value;
+					changed();
+				}
+			}
+		}
+		private bool _font_italic_flag;
+
+
+		/**
+		 * Text color node
+		 */
+		public ColorNode text_color_node
+		{
+			get { return _text_color_node; }
+
+			set
+			{
+				if ( _text_color_node != value )
+				{
+					_text_color_node = value;
+					changed();
+				}
+			}
+		}
+		private ColorNode _text_color_node;
+
+
+		/**
+		 * Text alignment
+		 */
+		public Pango.Alignment text_alignment
+		{
+			get { return _text_alignment; }
+
+			set
+			{
+				if ( _text_alignment != value )
+				{
+					_text_alignment = value;
+					changed();
+				}
+			}
+		}
+		private Pango.Alignment _text_alignment;
+
+
+		/**
+		 * Text line spacing
+		 */
+		public double text_line_spacing
+		{
+			get { return _text_line_spacing; }
+
+			set
+			{
+				if ( _text_line_spacing != value )
+				{
+					_text_line_spacing = value;
+					changed();
+				}
+			}
+		}
+		private double _text_line_spacing;
+
+
+		/**
+		 * Line width
+		 */
+		public double line_width
+		{
+			get { return _line_width; }
+
+			set
+			{
+				if ( _line_width != value )
+				{
+					_line_width = value;
+					changed();
+				}
+			}
+		}
+		private double _line_width;
+
+
+		/**
+		 * Line color node
+		 */
+		public ColorNode line_color_node
+		{
+			get { return _line_color_node; }
+
+			set
+			{
+				if ( _line_color_node != value )
+				{
+					_line_color_node = value;
+					changed();
+				}
+			}
+		}
+		private ColorNode _line_color_node;
+
+
+		/**
+		 * Fill color node
+		 */
+		public ColorNode fill_color_node
+		{
+			get { return _fill_color_node; }
+
+			set
+			{
+				if ( _fill_color_node != value )
+				{
+					_fill_color_node = value;
+					changed();
+				}
+			}
+		}
+		private ColorNode _fill_color_node;
+
+
+		/**
+		 * Shadow state
+		 */
+		public bool shadow_state
+		{
+			get { return _shadow_state; }
+
+			set
+			{
+				if ( _shadow_state != value )
+				{
+					_shadow_state = value;
+					changed();
+				}
+			}
+		}
+		private bool _shadow_state;
+
+
+		/**
+		 * Shadow x offset
+		 */
+		public double shadow_x
+		{
+			get { return _shadow_x; }
+
+			set
+			{
+				if ( _shadow_x != value )
+				{
+					_shadow_x = value;
+					changed();
+				}
+			}
+		}
+		private double _shadow_x;
+
+
+		/**
+		 * Shadow y offset
+		 */
+		public double shadow_y
+		{
+			get { return _shadow_y; }
+
+			set
+			{
+				if ( _shadow_y != value )
+				{
+					_shadow_y = value;
+					changed();
+				}
+			}
+		}
+		private double _shadow_y;
+
+
+		/**
+		 * Shadow color node
+		 */
+		public ColorNode shadow_color_node
+		{
+			get { return _shadow_color_node; }
+
+			set
+			{
+				if ( _shadow_color_node != value )
+				{
+					_shadow_color_node = value;
+					changed();
+				}
+			}
+		}
+		private ColorNode _shadow_color_node;
+
+
+		/**
+		 * Shadow opacity
+		 */
+		public double shadow_opacity
+		{
+			get { return _shadow_opacity; }
+
+			set
+			{
+				if ( _shadow_opacity != value )
+				{
+					_shadow_opacity = value;
+					changed();
+				}
+			}
+		}
+		private double _shadow_opacity;
+
+
+
+		/*
+		 * Methods that concrete LabelObjects must implement.
+		 */
+
+		public abstract LabelObject dup();
+
+		protected abstract void draw_object( Cairo.Context cr, bool in_editor, MergeRecord? record );
+		protected abstract void draw_shadow( Cairo.Context cr, bool in_editor, MergeRecord? record );
+
+		protected abstract bool is_object_located_at( Cairo.Context cr, double x, double y );
+
+
+
+		public LabelObject()
+		{
+			_x0 = 0;
+			_y0 = 0;
+			_matrix = Cairo.Matrix.identity();
+
+			_line_width = 0;
+
+			_shadow_state            = false;
+			_shadow_x                = 3.6;
+			_shadow_y                = 3.6;
+			_shadow_color_node       = ColorNode.from_color( Color.black() );
+			_shadow_opacity          = 0.5;
+
+			selected = false;
+		}
+
+
+		protected void set_common_properties_from_object( LabelObject src_object )
+		{
+			parent            = src_object.parent;
+			x0                = src_object.x0;
+			y0                = src_object.y0;
+			matrix            = src_object.matrix;
+
+			w                 = src_object.w;
+			h                 = src_object.h;
+
+			shadow_state      = src_object.shadow_state;
+			shadow_x          = src_object.shadow_x;
+			shadow_y          = src_object.shadow_y;
+			shadow_color_node = src_object.shadow_color_node;
+			shadow_opacity    = src_object.shadow_opacity;
+		}
+
+
+		public void select()
+		{
+			selected = true;
+		}
+
+
+		public void unselect()
+		{
+			selected = false;
+		}
+
+
+		public bool is_selected()
+		{
+			return selected;
+		}
+
+
+		/*
+		 * Virtual method defaults, these methods are used to determine if generic properties, such as those
+		 * controlled by the PropertyBar, are available for the object.
+		 */
+
+		public virtual bool can_text()
+		{
+			return false;
+		}
+
+		public virtual bool can_fill()
+		{
+			return false;
+		}
+
+		public virtual bool can_line_color()
+		{
+			return false;
+		}
+
+		public virtual bool can_line_width()
+		{
+			return false;
+		}
+
+
+		public void set_position( double x0,
+		                          double y0 )
+		{
+			if ( ( _x0 != x0 ) || ( _y0 != y0 ) )
+			{
+				_x0 = x0;
+				_y0 = y0;
+
+				moved();
+			}
+		}
+
+
+		public void set_position_relative( double dx,
+		                                   double dy )
+		{
+			if ( ( dx != 0 ) || ( dy != 0 ) )
+			{
+				_x0 += dx;
+				_y0 += dy;
+
+				moved();
+			}
+		}
+
+
+		public void set_size( double w,
+		                      double h )
+		{
+			if ( ( _w != w ) || ( _h != h ) )
+			{
+				_w = w;
+				_h = h;
+
+				aspect_ratio = _h / _w;
+
+				changed();
+			}
+		}
+
+
+		public void set_size_honor_aspect( double w,
+		                                   double h )
+		{
+			if ( h > (w * aspect_ratio) )
+			{
+				h = w * aspect_ratio;
+			}
+			else
+			{
+				w = h / aspect_ratio;
+			}
+
+			if ( ( _w != w ) || ( _h != h ) )
+			{
+				_w = w;
+				_h = h;
+
+				changed();
+			}
+		}
+
+
+		public LabelRegion get_extent()
+		{
+			double xa1 =   - line_width/2;
+			double ya1 =   - line_width/2;
+			double xa2 = w + line_width/2;
+			double ya2 =   - line_width/2;
+			double xa3 = w + line_width/2;
+			double ya3 = h + line_width/2;
+			double xa4 =   - line_width/2;
+			double ya4 = h + line_width/2;
+
+			matrix.transform_point( ref xa1, ref ya1 );
+			matrix.transform_point( ref xa2, ref ya2 );
+			matrix.transform_point( ref xa3, ref ya3 );
+			matrix.transform_point( ref xa4, ref ya4 );
+
+			LabelRegion region = LabelRegion();
+			region.x1 = double.min( xa1, double.min( xa2, double.min( xa3, xa4 ) ) ) + x0;
+			region.y1 = double.min( ya1, double.min( ya2, double.min( ya3, ya4 ) ) ) + y0;
+			region.x2 = double.max( xa1, double.max( xa2, double.max( xa3, xa4 ) ) ) + x0;
+			region.y2 = double.max( ya1, double.max( ya2, double.max( ya3, ya4 ) ) ) + y0;
+
+			return region;
+		}
+
+
+		public void rotate( double theta_degs )
+		{
+			if ( theta_degs != 0 )
+			{
+				_matrix.rotate( theta_degs * (Math.PI / 180) );
+				changed();
+			}
+		}
+
+
+		public void flip_horiz()
+		{
+			_matrix.scale( -1, 1 );
+			changed();
+		}
+
+
+		public void flip_vert()
+		{
+			_matrix.scale( 1, -1 );
+			changed();
+		}
+
+
+		public void draw( Cairo.Context cr,
+		                  bool          in_editor,
+		                  MergeRecord?  record )
+		{
+			cr.save();
+			cr.translate( x0, y0 );
+
+			if ( shadow_state )
+			{
+				cr.save();
+				cr.translate( shadow_x, shadow_y );
+				cr.transform( matrix );
+				draw_shadow( cr, in_editor, record );
+				cr.restore();
+			}
+
+			cr.transform( matrix );
+			draw_object( cr, in_editor, record );
+
+			cr.restore();
+		}
+
+
+		public void draw_selection_highlight( Cairo.Context cr )
+		{
+			cr.save();
+			cr.translate( x0, y0 );
+			cr.transform( matrix );
+
+			foreach( Handle handle in handles )
+			{
+				handle.draw( cr );
+			}
+
+			cr.restore();
+		}
+
+
+		public bool is_located_at( Cairo.Context cr,
+		                           double        x_pixels,
+		                           double        y_pixels )
+		{
+			cr.save();
+			cr.translate( x0, y0 );
+			cr.transform( matrix );
+
+			double x = x_pixels;
+			double y = y_pixels;
+			cr.device_to_user( ref x, ref y );
+
+			bool ret_val = is_object_located_at( cr, x, y );
+
+			cr.restore();
+
+			return ret_val;
+		}
+
+
+		public Handle? handle_at(  Cairo.Context cr,
+		                           double        x_pixels,
+		                           double        y_pixels )
+		{
+			Handle ret_val = null;
+
+			cr.save();
+			cr.translate( x0, y0 );
+			cr.transform( matrix );
+
+			double x = x_pixels;
+			double y = y_pixels;
+			cr.device_to_user( ref x, ref y );
+
+			foreach( Handle handle in handles )
+			{
+				handle.cairo_path( cr );
+
+				if ( cr.in_fill( x, y ) )
+				{
+					ret_val = handle;
+					break;
+				}
+			}
+
+			cr.restore();
+
+			return ret_val;
+		}
+
+
+	}
+
+}
diff --git a/glabels/label_object_box.vala b/glabels/label_object_box.vala
new file mode 100644
index 0000000..107934a
--- /dev/null
+++ b/glabels/label_object_box.vala
@@ -0,0 +1,176 @@
+/*  label_object_box.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class LabelObjectBox : LabelObject
+	{
+
+		public LabelObjectBox()
+		{
+			handles.append( new HandleSouthEast( this ) );
+			handles.append( new HandleSouthWest( this ) );
+			handles.append( new HandleNorthEast( this ) );
+			handles.append( new HandleNorthWest( this ) );
+			handles.append( new HandleEast( this ) );
+			handles.append( new HandleSouth( this ) );
+			handles.append( new HandleWest( this ) );
+			handles.append( new HandleNorth( this ) );
+		}
+
+
+		public override bool can_fill()
+		{
+			return true;
+		}
+
+		public override bool can_line_color()
+		{
+			return true;
+		}
+
+		public override bool can_line_width()
+		{
+			return true;
+		}
+
+
+		public override LabelObject dup()
+		{
+			LabelObjectBox copy = new LabelObjectBox();
+
+			copy.set_common_properties_from_object( this );
+
+			return copy;
+		}
+
+
+		public override void draw_object( Cairo.Context cr, bool in_editor, MergeRecord? record )
+		{
+			Color line_color = line_color_node.expand( record );
+			Color fill_color = fill_color_node.expand( record );
+
+			if ( in_editor && line_color_node.field_flag )
+			{
+				line_color = Color.from_rgba( 0, 0, 0, 0.5 );
+			}
+
+			if ( in_editor && fill_color_node.field_flag )
+			{
+				fill_color = Color.from_rgba( 0.5, 0.5, 0.5, 0.5 );
+			}
+
+			cr.rectangle( 0, 0, w, h );
+
+			/* Paint fill color */
+			cr.set_source_rgba( fill_color.r, fill_color.g, fill_color.b, fill_color.a );
+			cr.fill_preserve();
+
+			/* Draw outline */
+			cr.set_source_rgba( line_color.r, line_color.g, line_color.b, line_color.a );
+			cr.set_line_width( line_width );
+			cr.stroke();
+		}
+
+
+		public override void draw_shadow( Cairo.Context cr, bool in_editor, MergeRecord? record )
+		{
+			Color line_color   = line_color_node.expand( record );
+			Color fill_color   = fill_color_node.expand( record );
+			Color shadow_color = shadow_color_node.expand( record );
+
+			if ( in_editor && line_color_node.field_flag )
+			{
+				line_color = Color.from_rgba( 0, 0, 0, 0.5 );
+			}
+
+			if ( in_editor && fill_color_node.field_flag )
+			{
+				fill_color = Color.from_rgba( 0.5, 0.5, 0.5, 0.5 );
+			}
+
+			if ( in_editor && shadow_color_node.field_flag )
+			{
+				shadow_color = Color.black();
+			}
+
+			shadow_color.set_opacity( shadow_opacity );
+
+			if ( fill_color.has_alpha() )
+			{
+				if ( line_color.has_alpha() )
+				{
+					/* Has FILL and OUTLINE: adjust size to account for line width. */
+					cr.rectangle( -line_width/2, -line_width/2, w+line_width, h+line_width );
+				}
+				else
+				{
+					/* Has FILL, but no OUTLINE. */
+					cr.rectangle( 0, 0, w, h );
+				}
+
+				/* Draw shadow */
+				cr.fill();
+			}
+			else
+			{
+				if ( line_color.has_alpha() )
+				{
+					/* Has only OUTLINE. */
+					cr.rectangle( 0, 0, w, h );
+
+					/* Draw shdow of OUTLINE. */
+					cr.set_line_width( line_width );
+					cr.stroke();
+				}
+			}
+		}
+
+
+		public override bool is_object_located_at( Cairo.Context cr, double x, double y )
+		{
+			cr.new_path();
+			cr.rectangle( 0, 0, w, h );
+
+			if ( cr.in_fill( x, y ) )
+			{
+				return true;
+			}
+
+			cr.set_line_width( line_width );
+			if ( cr.in_stroke( x, y ) )
+			{
+				return true;
+			}
+
+			return false;
+		}
+
+
+		// TODO: get_handle_at method.
+
+
+	}
+
+}
diff --git a/glabels/label_region.vala b/glabels/label_region.vala
new file mode 100644
index 0000000..f45ba52
--- /dev/null
+++ b/glabels/label_region.vala
@@ -0,0 +1,35 @@
+/*  label_region.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public struct LabelRegion
+	{
+		public double x1 { get; set; }
+		public double y1 { get; set; }
+		public double x2 { get; set; }
+		public double y2 { get; set; }
+	}
+
+}
diff --git a/glabels/label_state.vala b/glabels/label_state.vala
new file mode 100644
index 0000000..a687dc9
--- /dev/null
+++ b/glabels/label_state.vala
@@ -0,0 +1,93 @@
+/*  label_state.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	public class LabelState
+	{
+		public string   description { get; private set; }
+
+		public bool     modified    { get; private set; }
+		public TimeVal  time_stamp  { get; private set; }
+
+		public Template template    { get; private set; }
+		public bool     rotate      { get; private set; }
+
+		// TODO: Merge
+
+		public unowned List<LabelObject> object_list { get; private set; }
+
+
+		public LabelState( string description,
+		                   Label  label )
+		{
+			this.description = description;
+
+			modified     = label.modified;
+			time_stamp   = label.time_stamp;
+
+			template     = label.template;
+			rotate       = label.rotate;
+
+			// TODO: Merge
+
+			foreach ( LabelObject object in label.object_list )
+			{
+				object_list.append( object.dup() );
+			}
+
+		}
+
+
+		public void restore( Label label )
+		{
+			label.rotate = rotate;
+			label.template = template;
+
+			foreach ( LabelObject object in label.object_list )
+			{
+				label.delete_object( object );
+			}
+
+			foreach ( LabelObject object in object_list )
+			{
+				label.add_object( object.dup() );
+			}
+
+			// TODO: Merge
+
+			if ( !modified &&
+			     (time_stamp.tv_sec  == label.time_stamp.tv_sec) &&
+			     (time_stamp.tv_usec == label.time_stamp.tv_usec) )
+			{
+				label.modified = false;
+			}
+		}
+
+
+	}
+
+}
+
diff --git a/glabels/merge.vala b/glabels/merge.vala
new file mode 100644
index 0000000..63d4a27
--- /dev/null
+++ b/glabels/merge.vala
@@ -0,0 +1,87 @@
+/*  merge.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public enum MergeSrcType { NONE, FIXED, FILE }
+
+	public abstract class Merge : Object
+	{
+		public string       name        { get; construct; }
+		public string       description { get; construct; }
+		public MergeSrcType src_type    { get; protected set; }
+
+
+		public unowned List<MergeRecord> record_list
+		{
+			get { return _record_list; }
+		}
+		private List<MergeRecord> _record_list;
+
+
+		public uint record_count
+		{
+			get { return _record_list.length(); }
+		}
+
+
+		public string? src
+		{
+			get { return _src; }
+
+			set
+			{
+				_record_list = null;
+				_src = value;
+
+				if ( _src != null )
+				{
+					MergeRecord? record;
+
+					this.open();
+					while ( (record = this.get_record()) != null )
+					{
+						_record_list.append( record );
+					}
+					this.close();
+				}
+
+			}
+
+		}
+		private string? _src;
+
+
+		public    abstract List<string>  get_key_list();
+		public    abstract string        get_primary_key();
+		protected abstract void          open();
+		protected abstract void          close();
+		protected abstract MergeRecord?  get_record();
+		public    abstract Merge         dup();
+
+
+		
+	}
+
+}
diff --git a/glabels/merge_factory.vala b/glabels/merge_factory.vala
new file mode 100644
index 0000000..20da5ca
--- /dev/null
+++ b/glabels/merge_factory.vala
@@ -0,0 +1,78 @@
+/*  merge_factory.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class MergeFactory
+	{
+
+		/* TODO: register backends rather than hard code.  Problem gathering varargs of unknown
+		 * type to set Values. */
+
+		public static Merge create_merge( string name )
+		{
+			switch (name)
+			{
+
+			case "Text/Comma":
+				return new MergeText( name, _("Text: Comma Separated Values (CSV)"),
+				                      ',', false );
+
+			case "Text/Comma/Line1Keys":
+				return new MergeText( name, _("Text: Comma Separated Values (CSV) with keys on line 1"),
+				                      ',', true );
+
+			case "Text/Tab":
+				return new MergeText( name, _("Text: Tab Separated Values (TSV)"),
+				                      '\t', false );
+
+			case "Text/Tab/Line1Keys":
+				return new MergeText( name, _("Text: Tab Separated Values (TSV) with keys on line 1"),
+				                      '\t', true );
+
+			case "Text/Colon":
+				return new MergeText( name, _("Text: Colon Separated Values"),
+				                      ':', false );
+
+			case "Text/Colon/Line1Keys":
+				return new MergeText( name, _("Text: Colon Separated Values with keys on line 1"),
+				                      ':', true );
+
+			case "Text/Semicolon":
+				return new MergeText( name, _("Text: Semicolon Separated Values"),
+				                      ';', false );
+
+			case "Text/Semicolon/Line1Keys":
+				return new MergeText( name, _("Text: Semicolon Separated Values with keys on line 1"),
+				                      ';', true );
+
+			default:
+				return new MergeNone();
+
+			}
+		}
+		
+	}
+
+}
diff --git a/glabels/merge_field.vala b/glabels/merge_field.vala
new file mode 100644
index 0000000..87d6037
--- /dev/null
+++ b/glabels/merge_field.vala
@@ -0,0 +1,33 @@
+/*  merge_field.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public struct MergeField
+	{
+		public string key   { get; set; }
+		public string value { get; set; }
+	}
+
+}
diff --git a/glabels/merge_none.vala b/glabels/merge_none.vala
new file mode 100644
index 0000000..dc9ea62
--- /dev/null
+++ b/glabels/merge_none.vala
@@ -0,0 +1,72 @@
+/*  merge_none.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class MergeNone : Merge
+	{
+
+		public MergeNone()
+		{
+			src_type = MergeSrcType.NONE;
+		}
+
+
+		public override List<string> get_key_list()
+		{
+			return new List<string>();
+		}
+
+
+		public override string get_primary_key()
+		{
+			return "";
+		}
+
+
+		protected override void open()
+		{
+		}
+
+
+		protected override void close()
+		{
+		}
+
+
+		protected override MergeRecord? get_record()
+		{
+			return null;
+		}
+
+
+		public override Merge dup()
+		{
+			MergeNone copy = new MergeNone();
+			return copy;
+		}
+		
+	}
+
+}
diff --git a/glabels/merge_record.vala b/glabels/merge_record.vala
new file mode 100644
index 0000000..cf4c73a
--- /dev/null
+++ b/glabels/merge_record.vala
@@ -0,0 +1,47 @@
+/*  merge_record.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class MergeRecord
+	{
+		public         bool              selected   { get; set; }
+		public unowned List<MergeField?> field_list { get; set; }
+
+
+		public string? eval_key( string key )
+		{
+			foreach ( MergeField field in field_list )
+			{
+				if ( field.key == key )
+				{
+					return field.value;
+				}
+			}
+
+			return null;
+		}
+	}
+
+}
diff --git a/glabels/merge_text.vala b/glabels/merge_text.vala
new file mode 100644
index 0000000..a5d18c3
--- /dev/null
+++ b/glabels/merge_text.vala
@@ -0,0 +1,395 @@
+/*  merge_text.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class MergeText : Merge
+	{
+		public int    delim          { get; construct; default=','; }
+		public bool   line1_has_keys { get; construct; default=false; }
+
+
+		private FileStream fp;
+
+		private string[] keys;
+		private int      n_fields_max;
+		
+
+		public MergeText( string name           = "text/csv",
+		                  string description    = "",
+		                  int    delim          = ',',
+		                  bool   line1_has_keys = false )
+		{
+			Object( name:name, description:description, delim:delim, line1_has_keys:line1_has_keys );
+
+			src_type = MergeSrcType.FILE;
+		}
+
+
+		public override List<string> get_key_list()
+		{
+			int n_fields;
+			if ( line1_has_keys )
+			{
+				n_fields = keys.length;
+			}
+			else
+			{
+				n_fields = n_fields_max;
+			}
+
+			List<string> key_list = new List<string>();
+			for ( int i_field = 0; i_field < n_fields; i_field++ )
+			{
+				key_list.append( key_from_index( i_field ) );
+			}
+
+			return key_list;
+		}
+
+
+		private string key_from_index( int i_field )
+		{
+			if ( line1_has_keys && (i_field < keys.length) )
+			{
+				return keys[ i_field ];
+			}
+			else
+			{
+				return "%d".printf( i_field+1 );
+			}
+		}
+
+
+		public override string get_primary_key()
+		{
+			/* For now, let's always assume the first column is the primary key. */
+			return key_from_index( 0 );
+		}
+
+
+		protected override void open()
+		{
+			if ( src == "-" )
+			{
+				fp = FileStream.fdopen( Posix.STDIN_FILENO, "r" );
+			}
+			else
+			{
+				fp = FileStream.open( src, "r" );
+			}
+
+			keys = null;
+			n_fields_max = 0;
+
+			if ( line1_has_keys )
+			{
+				/*
+				 * Extract keys from first line and discard line.
+				 */
+				List<string> line1_fields = parse_line();
+
+				foreach (string field in line1_fields)
+				{
+					keys += field;
+				}
+			}
+		}
+
+
+		protected override void close()
+		{
+			fp = null;
+		}
+
+
+		protected override MergeRecord? get_record()
+		{
+			List<string?> values = parse_line();
+			if ( values == null )
+			{
+				return null;
+			}
+
+			MergeRecord record = new MergeRecord();
+
+			int i_field = 0;
+			foreach ( string value in values )
+			{
+				MergeField field = MergeField();
+				field.key = key_from_index( i_field );
+				field.value = value;
+				i_field++;
+
+				record.field_list.append( field );
+			}
+
+			if ( i_field > n_fields_max )
+			{
+				n_fields_max = i_field;
+			}
+			return record;
+		}
+
+
+		public override Merge dup()
+		{
+			MergeText copy = new MergeText();
+			return copy;
+		}
+
+		
+		private enum State { DELIM,
+		                     QUOTED, QUOTED_QUOTE1, QUOTED_ESCAPED,
+		                     SIMPLE, SIMPLE_ESCAPED,
+		                     DONE }
+
+		/*---------------------------------------------------------------------------
+		 * PRIVATE.  Parse line.                                                     
+		 *                                                                           
+		 * Attempt to be a robust parser of various CSV (and similar) formats.       
+		 *                                                                           
+		 * Based on CSV format described in RFC 4180 section 2.                      
+		 *                                                                           
+		 * Additions to RFC 4180 rules:                                              
+		 *   - delimeters and other special characters may be "escaped" by a leading 
+		 *     backslash (\)                                                         
+		 *   - C escape sequences for newline (\n) and tab (\t) are also translated. 
+		 *   - if quoted text is not followed by a delimeter, any additional text is 
+		 *     concatenated with quoted portion.                                     
+		 *                                                                           
+		 * Returns a list of fields.  A blank line is considered a line with one     
+		 * empty field.  Returns empty (NULL) when done.                             
+		 *--------------------------------------------------------------------------*/
+		private List<string> parse_line()
+		{
+			List<string> list = new List<string>();
+
+			if ( fp == null )
+			{
+				return list;
+			}
+	       
+			State state = State.DELIM;
+			StringBuilder field = new StringBuilder();
+
+			while ( state != State.DONE ) {
+				int c = fp.getc();
+
+				switch (state) {
+
+				case State.DELIM:
+					switch (c) {
+					case '\n':
+						/* last field is empty. */
+						list.append("");
+						state = State.DONE;
+						break;
+					case '\r':
+						/* ignore */
+						state = State.DELIM;
+						break;
+					case FileStream.EOF:
+						/* end of file, no more lines. */
+						state = State.DONE;
+						break;
+					case '"':
+						/* start a quoted field. */
+						state = State.QUOTED;
+						break;
+					case '\\':
+						/* simple field, but 1st character is an escape. */
+						state = State.SIMPLE_ESCAPED;
+						break;
+					default:
+						if ( c == delim )
+						{
+							/* field is empty. */
+							list.append("");
+							state = State.DELIM;
+						}
+						else
+						{
+							/* begining of a simple field. */
+							field.append_c( (char)c );
+							state = State.SIMPLE;
+						}
+						break;
+					}
+					break;
+
+				case State.QUOTED:
+					switch (c) {
+					case FileStream.EOF:
+						/* File ended mid way through quoted item, truncate field. */
+						list.append( field.str );
+						state = State.DONE;
+						break;
+					case '"':
+						/* Possible end of field, but could be 1st of a pair. */
+						state = State.QUOTED_QUOTE1;
+						break;
+					case '\\':
+						/* Escape next character, or special escape, e.g. \n. */
+						state = State.QUOTED_ESCAPED;
+						break;
+					default:
+						/* Use character literally. */
+						field.append_c( (char)c );
+						break;
+					}
+					break;
+
+				case State.QUOTED_QUOTE1:
+					switch (c) {
+					case '\n':
+					case FileStream.EOF:
+						/* line or file ended after quoted item */
+						list.append( field.str );
+						state = State.DONE;
+						break;
+					case '"':
+						/* second quote, insert and stay quoted. */
+						field.append_c( (char)c );
+						state = State.QUOTED;
+						break;
+					case '\r':
+						/* ignore and go to fallback */
+						state = State.SIMPLE;
+						break;
+					default:
+						if ( c == delim )
+						{
+							/* end of field. */
+							list.append( field.str );
+							field.truncate( 0 );
+							state = State.DELIM;
+						}
+						else
+						{
+							/* fallback if not a delim or another quote. */
+							field.append_c( (char)c );
+							state = State.SIMPLE;
+						}
+						break;
+					}
+					break;
+
+				case State.QUOTED_ESCAPED:
+					switch (c) {
+					case FileStream.EOF:
+						/* File ended mid way through quoted item */
+						list.append( field.str );
+						state = State.DONE;
+						break;
+					case 'n':
+						/* Decode "\n" as newline. */
+						field.append_c( (char)'\n' );
+						state = State.QUOTED;
+						break;
+					case 't':
+						/* Decode "\t" as tab. */
+						field.append_c( (char)'\t' );
+						state = State.QUOTED;
+						break;
+					default:
+						/* Use character literally. */
+						field.append_c( (char)c );
+						state = State.QUOTED;
+						break;
+					}
+					break;
+
+				case State.SIMPLE:
+					switch (c) {
+					case '\n':
+					case FileStream.EOF:
+						/* line or file ended */
+						list.append( field.str );
+						state = State.DONE;
+						break;
+					case '\r':
+						/* ignore */
+						state = State.SIMPLE;
+						break;
+					case '\\':
+						/* Escape next character, or special escape, e.g. \n. */
+						state = State.SIMPLE_ESCAPED;
+						break;
+					default:
+						if ( c == delim )
+						{
+							/* end of field. */
+							list.append( field.str );
+							field.truncate( 0 );
+							state = State.DELIM;
+						}
+						else
+						{
+							/* Use character literally. */
+							field.append_c( (char)c );
+							state = State.SIMPLE;
+						}
+						break;
+					}
+					break;
+
+				case State.SIMPLE_ESCAPED:
+					switch (c) {
+					case FileStream.EOF:
+						/* File ended mid way through quoted item */
+						list.append( field.str );
+						state = State.DONE;
+						break;
+					case 'n':
+						/* Decode "\n" as newline. */
+						field.append_c( (char)'\n' );
+						state = State.SIMPLE;
+						break;
+					case 't':
+						/* Decode "\t" as tab. */
+						field.append_c( (char)'\t' );
+						state = State.SIMPLE;
+						break;
+					default:
+						/* Use character literally. */
+						field.append_c( (char)c );
+						state = State.SIMPLE;
+						break;
+					}
+					break;
+
+				default:
+					assert_not_reached();
+				}
+
+			}
+
+			return list;
+		}
+
+
+	}
+
+}
diff --git a/glabels/mini_preview.vala b/glabels/mini_preview.vala
new file mode 100644
index 0000000..49298e6
--- /dev/null
+++ b/glabels/mini_preview.vala
@@ -0,0 +1,417 @@
+/*  mini_preview.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	public class MiniPreview : Gtk.EventBox
+	{
+
+		public signal void clicked( int index );
+
+		public signal void pressed( int index1,
+		                            int index2 );
+
+		public signal void released( int index1,
+		                             int index2 );
+
+
+		private const double MARGIN = 2;
+		private const double SHADOW_OFFSET = 3;
+
+
+		private Gtk.DrawingArea canvas;
+
+		private Template?        template;
+		private Gee.ArrayList<TemplateCoord?> origins;
+		private Gee.ArrayList<TemplateCoord?> centers;
+
+		private int highlight_first;
+		private int highlight_last;
+
+		private bool dragging;
+		private int  first_i;
+		private int  last_i;
+		private int  prev_i;
+
+		private bool update_scheduled_flag;
+
+		private Label? label;
+
+
+
+		public MiniPreview( int height,
+		                    int width )
+		{
+			add_events( Gdk.EventMask.BUTTON_PRESS_MASK   |
+			            Gdk.EventMask.BUTTON_RELEASE_MASK |
+			            Gdk.EventMask.POINTER_MOTION_MASK );
+
+			set_visible_window( false );
+
+			canvas = new Gtk.DrawingArea();
+			canvas.set_has_window( false );
+			add( canvas );
+
+			canvas.set_size_request( width, height );
+
+			canvas.draw.connect( on_draw );
+			canvas.style_set.connect( on_style_set );
+		}
+
+
+		public void set_label( Label label )
+		{
+			this.label = label;
+			set_template( label.template );
+		}
+
+
+		public void set_template_by_name( string name )
+		{
+			Template? template = Db.lookup_template_from_name( name );
+			set_template( template );
+		}
+
+
+		public void set_template( Template? template )
+		{
+			this.template = template;
+
+			if ( template != null )
+			{
+				TemplateFrame frame = template.frames.first().data;
+
+				/*
+				 * Cache list of origins.
+				 */
+				origins = frame.get_origins();
+
+				/*
+				 * Also build list of label centers from origins and cache.
+				 */
+				double w, h;
+				frame.get_size( out w, out h );
+
+				centers = frame.get_origins();
+
+				foreach ( TemplateCoord center in centers )
+				{
+					center.x += w/2;
+					center.y += h/2;
+				}
+
+
+				redraw_canvas();
+			}
+		}
+
+
+		public void highlight_range( int first_label,
+		                             int last_label )
+		{
+			if ( ( first_label != highlight_first ) ||
+			     ( last_label  != highlight_last ) )
+			{
+				highlight_first = first_label;
+				highlight_last  = last_label;
+
+				redraw_canvas();
+			}
+		}
+
+
+		public override bool button_press_event( Gdk.EventButton event )
+		{
+			if ( event.button == 1 )
+			{
+				Cairo.Context cr = Gdk.cairo_create( canvas.get_window() );
+
+				set_transform_and_get_scale( cr );
+
+				double x = event.x;
+				double y = event.y;
+				cr.device_to_user( ref x, ref y );
+
+				int i = find_closest_label( x, y );
+				clicked( i );
+
+				first_i = i;
+				last_i  = i;
+				pressed( first_i, last_i );
+
+				dragging = true;
+				prev_i   = i;
+			}
+
+			return false;
+		}
+
+
+		public override bool button_release_event( Gdk.EventButton event )
+		{
+			if ( event.button == 1 )
+			{
+				dragging = false;
+			}
+			released( first_i, last_i );
+
+			return false;
+		}
+
+		
+		public override bool motion_notify_event( Gdk.EventMotion event )
+		{
+			if ( dragging )
+			{
+				Cairo.Context cr = Gdk.cairo_create( canvas.get_window() );
+
+				set_transform_and_get_scale( cr );
+
+				double x = event.x;
+				double y = event.y;
+				cr.device_to_user( ref x, ref y );
+
+				int i = find_closest_label( x, y );
+
+				if ( i != prev_i )
+				{
+					last_i = prev_i = i;
+					pressed( int.min( first_i, last_i ), int.max( first_i, last_i ) );
+				}
+			}
+
+			return false;
+		}
+
+
+		private void on_style_set( Gtk.Style? previous_style )
+		{
+			redraw_canvas();
+		}
+
+
+		private void redraw_canvas()
+		{
+			var window = get_window ();
+			if (null == window)
+			{
+				return;
+			}
+
+			if ( !update_scheduled_flag )
+			{
+				update_scheduled_flag = true;
+
+				unowned Cairo.Region region = window.get_clip_region ();
+				// redraw the cairo canvas completely by exposing it
+				window.invalidate_region (region, true);
+			}
+
+		}
+
+
+		private double set_transform_and_get_scale(Cairo.Context cr )
+		{
+
+			/* Establish scale and origin. */
+			double w = get_allocated_width();
+			double h = get_allocated_height();
+
+			/* establish scale. */
+			double scale = double.min( (w - 2*MARGIN - 2*SHADOW_OFFSET)/template.page_width,
+			                           (h - 2*MARGIN - 2*SHADOW_OFFSET)/template.page_height );
+
+			/* Find offset to center preview. */
+			double offset_x = (w/scale - template.page_width) / 2.0;
+			double offset_y = (h/scale - template.page_height) / 2.0;
+
+			/* Set transformation. */
+			cr.scale( scale, scale );
+			cr.translate( offset_x, offset_y );
+
+			return scale;
+		}
+
+
+		private int find_closest_label( double x,
+		                                double y )
+		{
+			double dx = x - centers[0].x;
+			double dy = y - centers[0].y;
+			double min_d2 = dx*dx + dy*dy;
+
+			int i = 0;
+			int min_i = 0;
+
+			foreach ( TemplateCoord center in centers )
+			{
+				dx = x - center.x;
+				dy = y - center.y;
+				double d2 = dx*dx + dy*dy;
+
+				if ( d2 < min_d2 )
+				{
+					min_d2 = d2;
+					min_i  = i;
+				}
+				i++;
+			}
+
+			return min_i + 1;
+		}
+
+
+		private bool on_draw( Cairo.Context cr )
+		{
+			update_scheduled_flag = false;
+
+			if ( template != null )
+			{
+				double scale = set_transform_and_get_scale( cr );
+
+				/* update shadow */
+				double shadow_x = SHADOW_OFFSET/scale;
+				double shadow_y = SHADOW_OFFSET/scale;
+
+				draw_shadow( cr, shadow_x, shadow_y );
+
+				draw_paper( cr,	1.0/scale );
+
+				draw_labels( cr, 2.0/scale );
+
+				if ( label != null )
+				{
+					draw_rich_preview( cr );
+				}
+
+			}
+
+			return false;
+		}
+
+
+		private void draw_shadow( Cairo.Context cr,
+		                          double        x,
+		                          double        y )
+		{
+			cr.save();
+
+			cr.rectangle( x, y, template.page_width, template.page_height );
+
+			Gtk.Style style = get_style();
+			Color shadow_color = Color.from_gdk_color( style.dark[Gtk.StateType.NORMAL] );
+			cr.set_source_rgb( shadow_color.r, shadow_color.g, shadow_color.b );
+
+			cr.fill();
+
+			cr.restore();
+		}
+
+
+		private void draw_paper( Cairo.Context cr,
+		                         double        line_width )
+		{
+			cr.save();
+
+			cr.rectangle( 0, 0, template.page_width, template.page_height );
+
+			Gtk.Style style = get_style();
+			Color paper_color = Color.from_gdk_color( style.light[Gtk.StateType.NORMAL] );
+			Color outline_color = Color.from_gdk_color( style.fg[Gtk.StateType.NORMAL] );
+
+			cr.set_source_rgb( paper_color.r, paper_color.g, paper_color.b );
+			cr.fill_preserve();
+
+			cr.set_source_rgb( outline_color.r, outline_color.g, outline_color.b );
+			cr.set_line_width( line_width );
+			cr.stroke();
+
+			cr.restore();
+		}
+
+
+		private void draw_labels( Cairo.Context cr,
+		                          double        line_width )
+		{
+			TemplateFrame frame = template.frames.first().data;
+
+			int n_labels = frame.get_n_labels();
+
+			Gtk.Style style = get_style();
+			Color base_color = Color.from_gdk_color( style.base[Gtk.StateType.SELECTED] );
+			Color highlight_color = Color.from_color_and_opacity( base_color, 0.10 );
+
+			Color outline_color;
+			if ( label != null )
+			{
+				/* Outlines are more subtle when doing a rich preview. */
+				outline_color = Color.from_color_and_opacity( base_color, 0.25 );
+			}
+			else
+			{
+				outline_color = Color.from_color_and_opacity( base_color, 1.00 );
+			}
+
+			for ( int i = 0; i < n_labels; i++ )
+			{
+				cr.save();
+
+				cr.translate( origins[i].x, origins[i].y );
+				frame.cairo_path( cr, false );
+
+				if ( ((i+1) >= highlight_first) &&
+				     ((i+1) <= highlight_last) )
+				{
+					cr.set_source_rgba( highlight_color.r, highlight_color.g, highlight_color.b, highlight_color.a );
+					cr.set_fill_rule( Cairo.FillRule.EVEN_ODD );
+					cr.fill_preserve();
+				}
+
+				cr.set_line_width( line_width );
+				cr.set_source_rgba( outline_color.r, outline_color.g, outline_color.b, outline_color.a );
+				cr.stroke();
+
+				cr.restore();
+			}
+		}
+
+
+		private void draw_rich_preview( Cairo.Context cr )
+		{
+			Print print = new Print();
+
+			print.label = label;
+			print.outline_flag = false;
+			print.reverse_flag = false;
+			print.crop_marks_flag = false;
+
+			/* TODO: test for merge. */
+			print.print_simple_sheet( cr );
+		}
+
+		
+	}
+
+}
\ No newline at end of file
diff --git a/glabels/new_label_dialog.vala b/glabels/new_label_dialog.vala
new file mode 100644
index 0000000..a7a66a5
--- /dev/null
+++ b/glabels/new_label_dialog.vala
@@ -0,0 +1,218 @@
+/*  new_label_dialog.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	class NewLabelDialog : Gtk.Dialog
+	{
+		private Prefs prefs;
+
+
+		private Gtk.IconView  icon_view;
+
+		private Gtk.Entry           search_entry;
+		private string              search_string = "";
+		private Gtk.TreeModelFilter search_filtered_model;
+
+		private Gtk.ListStore model;
+
+
+		public string? template_name { get; private set; }
+
+
+		public NewLabelDialog( Window window )
+		{
+			prefs = new Prefs();
+
+			this.set_transient_for( window );
+			this.set_destroy_with_parent( true );
+
+			this.set_default_size( 788, 600 );
+
+
+			/* Load the user interface. */
+			Gtk.Builder builder = new Gtk.Builder();
+			try
+			{
+				string file = GLib.Path.build_filename( Config.DATADIR, Config.GLABELS_BRANCH, "ui", "new_label_dialog.ui" );
+				string[] objects = { "main_vbox", null };
+				builder.add_objects_from_file( file, objects );
+			}
+			catch ( Error err )
+			{
+				error( "Error: %s", err.message );
+			}
+
+			Gtk.VBox main_vbox = builder.get_object( "main_vbox" ) as Gtk.VBox;
+			((Gtk.Box)get_content_area()).pack_start( main_vbox );
+
+			search_entry = builder.get_object( "search_entry" ) as Gtk.Entry;
+			icon_view    = builder.get_object( "icon_view" )    as Gtk.IconView;
+
+
+			/* Create and set icon view model. */
+			model = new Gtk.ListStore( 3, typeof(string), typeof(Gdk.Pixbuf), typeof(string) );
+
+			search_filtered_model = new Gtk.TreeModelFilter( model, null );
+			search_filtered_model.set_visible_func( search_filter_func );
+
+			icon_view.set_model( search_filtered_model );
+			icon_view.set_text_column( 0 );
+			icon_view.set_pixbuf_column( 1 );
+			icon_view.set_tooltip_column( 2 );
+
+			/* Set "follow-state" property of pixbuf renderer. (pre-light) */
+			List<weak Gtk.CellRenderer> renderer_list = icon_view.cell_area.get_cells();
+			foreach ( Gtk.CellRenderer renderer in renderer_list )
+			{
+				if ( renderer is Gtk.CellRendererPixbuf )
+				{
+					((Gtk.CellRendererPixbuf)renderer).follow_state = true;
+				}
+			}
+
+			/* Intialize model. */
+			foreach ( libglabels.Template template in libglabels.Db.templates )
+			{
+				Gtk.TreeIter iter;
+				model.append( out iter );
+
+				string tooltip = build_tooltip( template );
+
+				model.set( iter,
+				           0, template.name,
+				           1, template.preview_pixbuf,
+				           2, tooltip,
+				           -1);
+			}
+
+			/* Connect signals. */
+			search_entry.changed.connect( on_search_entry_changed );
+			search_entry.icon_release.connect( on_search_entry_clear );
+			icon_view.selection_changed.connect( on_icon_view_selection_changed );
+			icon_view.button_release_event.connect( on_icon_view_button_release_event );
+		}
+
+
+		private string build_tooltip( libglabels.Template template )
+		{
+			libglabels.Units units = prefs.units;
+
+			libglabels.TemplateFrame frame = template.frames.first().data;
+			string size_string = frame.get_size_description( units );
+			string count_string = frame.get_layout_description();
+
+			string tip = "<span weight=\"bold\">%s:  %s\n</span>%s\n%s".printf(
+				template.name, template.description,
+				size_string,
+				count_string );
+
+			return tip;
+		}
+
+
+		private bool search_filter_func( Gtk.TreeModel model, Gtk.TreeIter iter )
+		{
+			if ( search_string == "" )
+			{
+				return true;
+			}
+
+			Value value; 
+			model.get_value( iter, 0, out value );
+			string name = value.get_string();
+
+			string needle   = search_string.casefold();
+			string haystack = name.casefold();
+
+			if ( needle in haystack )
+			{
+				return true;
+			}
+
+			return false;
+		}
+
+
+		private void on_search_entry_clear()
+		{
+			search_entry.set_text( "" );
+		}
+
+
+		private void on_search_entry_changed()
+		{
+			string new_search_string = search_entry.get_text();
+
+			new_search_string.strip();
+			if ( search_string == new_search_string )
+			{
+				return;
+			}
+			search_string = new_search_string;
+
+
+			if ( search_string == "" )
+			{
+				search_entry.secondary_icon_name        = "edit-find-symbolic";
+				search_entry.secondary_icon_activatable = false;
+				search_entry.secondary_icon_sensitive   = false;
+			}
+			else
+			{
+				search_entry.secondary_icon_name        = "edit-clear-symbolic";
+				search_entry.secondary_icon_activatable = true;
+				search_entry.secondary_icon_sensitive   = true;
+			}
+
+			search_filtered_model.refilter();
+		}
+
+
+		private void on_icon_view_selection_changed()
+		{
+			List<Gtk.TreePath> list = icon_view.get_selected_items();
+
+			Gtk.TreeIter iter;
+			if ( search_filtered_model.get_iter( out iter, list.first().data ) )
+			{
+				Value value;
+				search_filtered_model.get_value( iter, 0, out value );
+
+				template_name = value.get_string();
+			}
+		}
+
+
+		private bool on_icon_view_button_release_event( Gdk.EventButton event )
+		{
+			response( Gtk.ResponseType.OK );
+			return true;
+		}
+
+
+	}
+
+}
+
diff --git a/glabels/prefs.vala b/glabels/prefs.vala
new file mode 100644
index 0000000..eaa3f63
--- /dev/null
+++ b/glabels/prefs.vala
@@ -0,0 +1,376 @@
+/*  prefs.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	public class Prefs : Object
+	{
+		private const string DEFAULT_UNITS_STRING_US     = "in";
+		private const string DEFAULT_PAGE_SIZE_US        = "US-Letter";
+
+		private const string DEFAULT_UNITS_STRING_METRIC = "mm";
+		private const string DEFAULT_PAGE_SIZE_METRIC    = "A4";
+
+
+		public signal void changed();
+
+
+		private static GLib.Settings locale;
+		private static GLib.Settings objects;
+		private static GLib.Settings ui;
+
+
+		static construct
+		{
+			locale  = new GLib.Settings( "org.gnome.glabels-3.locale" );
+			objects = new GLib.Settings( "org.gnome.glabels-3.objects" );
+			ui      = new GLib.Settings( "org.gnome.glabels-3.ui" );
+		}
+
+		public Prefs()
+		{
+			locale.changed.connect( on_changed );
+			objects.changed.connect( on_changed );
+			ui.changed.connect( on_changed );
+		}
+
+
+		private void on_changed()
+		{
+			changed();
+		}
+
+
+		public Units units
+		{
+			get
+			{
+				string? units_string = locale.get_string( "units" );
+
+				/* If not set, make educated guess about locale default. */
+				if ( (units_string == null) || (units_string == "") )
+				{
+					string pgsize = Gtk.PaperSize.get_default();
+					if ( pgsize == Gtk.PAPER_NAME_LETTER )
+					{
+						units_string = DEFAULT_UNITS_STRING_US;
+					}
+					else
+					{
+						units_string = DEFAULT_UNITS_STRING_METRIC;
+					}
+				}
+
+				_units = Units.from_id( units_string );
+
+				/* If invalid, make an educated guess from locale. */
+				if ( _units.id == "pt" )
+				{
+					string pgsize = Gtk.PaperSize.get_default();
+					if ( pgsize == Gtk.PAPER_NAME_LETTER )
+					{
+						_units = Units.inch();
+					}
+					else
+					{
+						_units = Units.mm();
+					}
+				}
+
+				return _units;
+			}
+
+			set
+			{
+				locale.set_string( "units", value.id );
+			}
+		}
+		private Units _units;
+
+
+		public string default_page_size
+		{
+			get
+			{
+				_default_page_size = locale.get_string( "default-page-size" );
+
+				/* If not set, make educated guess about locale default. */
+				/* Also proof read the default page size -- it must be a valid id. */
+				/* (For compatability with older versions.) */
+				if ( (_default_page_size == null) ||
+				     (_default_page_size == "")   ||
+				     ( Db.lookup_paper_from_id( _default_page_size ) == null ) )
+				{
+					string pgsize = Gtk.PaperSize.get_default();
+					if ( pgsize == Gtk.PAPER_NAME_LETTER )
+					{
+						_default_page_size = DEFAULT_PAGE_SIZE_US;
+					}
+					else
+					{
+						_default_page_size = DEFAULT_PAGE_SIZE_METRIC;
+					}
+				}
+
+				return _default_page_size;
+			}
+
+			set
+			{
+				locale.set_string( "default-page-size", value );
+			}
+		}
+		private string? _default_page_size;
+
+
+		public string default_font_family
+		{
+			get
+			{
+				_default_font_family = objects.get_string( "default-font-family" );
+				return _default_font_family;
+			}
+
+			set
+			{
+				objects.set_string( "default-font-family", value );
+			}
+		}
+		private string? _default_font_family;
+
+
+		public double default_font_size
+		{
+			get
+			{
+				return objects.get_double( "default-font-size" );
+			}
+
+			set
+			{
+				objects.set_double( "default-font-size", value );
+			}
+		}
+
+
+		public Pango.Weight default_font_weight
+		{
+			get
+			{
+				return EnumUtil.string_to_weight( objects.get_string( "default-font-weight" ) );
+			}
+
+			set
+			{
+				objects.set_string( "default-font-weight", EnumUtil.weight_to_string( value ) );
+			}
+		}
+
+
+		public bool default_font_italic_flag
+		{
+			get
+			{
+				return objects.get_boolean( "default-font-italic-flag" );
+			}
+
+			set
+			{
+				objects.set_boolean( "default-font-italic-flag", value );
+			}
+		}
+
+
+		public Color default_text_color
+		{
+			get
+			{
+				return Color.from_legacy_color( objects.get_uint( "default-text-color" ) );
+			}
+
+			set
+			{
+				objects.set_uint( "default-text-color", value.to_legacy_color() );
+			}
+		}
+
+
+		public Pango.Alignment default_text_alignment
+		{
+			get
+			{
+				return EnumUtil.string_to_align( objects.get_string( "default-text-alignment" ) );
+			}
+
+			set
+			{
+				objects.set_string( "default-text-alignment", EnumUtil.align_to_string( value ) );
+			}
+		}
+
+
+		public double default_text_line_spacing
+		{
+			get
+			{
+				return objects.get_double( "default-text-line-spacing" );
+			}
+
+			set
+			{
+				objects.set_double( "default-text-line-spacing", value );
+			}
+		}
+
+
+		public double default_line_width
+		{
+			get
+			{
+				return objects.get_double( "default-line-width" );
+			}
+
+			set
+			{
+				objects.set_double( "default-line-width", value );
+			}
+		}
+
+
+		public Color default_line_color
+		{
+			get
+			{
+				return Color.from_legacy_color( objects.get_uint( "default-line-color" ) );
+			}
+
+			set
+			{
+				objects.set_uint( "default-line-color", value.to_legacy_color() );
+			}
+		}
+
+
+		public Color default_fill_color
+		{
+			get
+			{
+				return Color.from_legacy_color( objects.get_uint( "default-fill-color" ) );
+			}
+
+			set
+			{
+				objects.set_uint( "default-fill-color", value.to_legacy_color() );
+			}
+		}
+
+
+		public bool main_toolbar_visible
+		{
+			get
+			{
+				return ui.get_boolean( "main-toolbar-visible" );
+			}
+
+			set
+			{
+				ui.set_boolean( "main-toolbar-visible", value );
+			}
+		}
+
+
+		public bool drawing_toolbar_visible
+		{
+			get
+			{
+				return ui.get_boolean( "drawing-toolbar-visible" );
+			}
+
+			set
+			{
+				ui.set_boolean( "drawing-toolbar-visible", value );
+			}
+		}
+
+
+		public bool property_toolbar_visible
+		{
+			get
+			{
+				return ui.get_boolean( "property-toolbar-visible" );
+			}
+
+			set
+			{
+				ui.set_boolean( "property-toolbar-visible", value );
+			}
+		}
+
+
+		public bool grid_visible
+		{
+			get
+			{
+				return ui.get_boolean( "grid-visible" );
+			}
+
+			set
+			{
+				ui.set_boolean( "grid-visible", value );
+			}
+		}
+
+
+		public bool markup_visible
+		{
+			get
+			{
+				return ui.get_boolean( "markup-visible" );
+			}
+
+			set
+			{
+				ui.set_boolean( "markup-visible", value );
+			}
+		}
+
+
+		public int max_recents
+		{
+			get
+			{
+				return ui.get_int( "max-recents" );
+			}
+
+			set
+			{
+				ui.set_int( "max-recents", value );
+			}
+		}
+
+
+	}
+
+}
diff --git a/glabels/print.vala b/glabels/print.vala
new file mode 100644
index 0000000..0fdd7f3
--- /dev/null
+++ b/glabels/print.vala
@@ -0,0 +1,217 @@
+/*  print.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	public class Print
+	{
+		private const double OUTLINE_WIDTH =  0.25;
+		private const double TICK_OFFSET   =  2.25;
+		private const double TICK_LENGTH   = 18.0;
+
+		public Label label           { get; set; }
+		public bool  outline_flag    { get; set; }
+		public bool  reverse_flag    { get; set; }
+		public bool  crop_marks_flag { get; set; }
+
+
+		public void print_simple_sheet( Cairo.Context cr )
+		{
+			if ( crop_marks_flag )
+			{
+				print_crop_marks( cr );
+			}
+
+			TemplateFrame frame = label.template.frames.first().data;
+			Gee.ArrayList<TemplateCoord?> origins = frame.get_origins();
+
+			foreach ( TemplateCoord origin in origins )
+			{
+				print_label( cr, origin.x, origin.y, null );
+			}
+		}
+
+
+		private void print_crop_marks( Cairo.Context cr )
+		{
+			TemplateFrame frame = label.template.frames.first().data;
+
+			double w, h;
+			frame.get_size( out w, out h );
+
+			cr.save();
+
+			cr.set_source_rgb( 0, 0, 0 );
+			cr.set_line_width( OUTLINE_WIDTH );
+
+			foreach ( TemplateLayout layout in frame.layouts )
+			{
+
+				double xmin = layout.x0;
+				double ymin = layout.y0;
+				double xmax = layout.x0 + layout.dx*(layout.nx - 1) + w;
+				double ymax = layout.y0 + layout.dy*(layout.ny - 1) + h;
+
+				for ( int ix=0; ix < layout.nx; ix++ )
+				{
+					double x1 = xmin + ix*layout.dx;
+					double x2 = x1 + w;
+
+					double y1 = double.max((ymin - TICK_OFFSET), 0.0);
+					double y2 = double.max((y1 - TICK_LENGTH), 0.0);
+
+					double y3 = double.min((ymax + TICK_OFFSET), label.template.page_height);
+					double y4 = double.min((y3 + TICK_LENGTH), label.template.page_height);
+
+					cr.move_to( x1, y1 );
+					cr.line_to( x1, y2 );
+					cr.stroke();
+
+					cr.move_to( x2, y1 );
+					cr.line_to( x2, y2 );
+					cr.stroke();
+
+					cr.move_to( x1, y3 );
+					cr.line_to( x1, y4 );
+					cr.stroke();
+
+					cr.move_to( x2, y3 );
+					cr.line_to( x2, y4 );
+					cr.stroke();
+				}
+
+				for (int iy=0; iy < layout.ny; iy++ )
+				{
+					double y1 = ymin + iy*layout.dy;
+					double y2 = y1 + h;
+
+					double x1 = double.max((xmin - TICK_OFFSET), 0.0);
+					double x2 = double.max((x1 - TICK_LENGTH), 0.0);
+
+					double x3 = double.min((xmax + TICK_OFFSET), label.template.page_width);
+					double x4 = double.min((x3 + TICK_LENGTH), label.template.page_width);
+
+					cr.move_to( x1, y1 );
+					cr.line_to( x2, y1 );
+					cr.stroke();
+
+					cr.move_to( x1, y2 );
+					cr.line_to( x2, y2 );
+					cr.stroke();
+
+					cr.move_to( x3, y1 );
+					cr.line_to( x4, y1 );
+					cr.stroke();
+
+					cr.move_to( x3, y2 );
+					cr.line_to( x4, y2 );
+					cr.stroke();
+				}
+
+			}
+
+			cr.restore();
+		}
+
+
+		private void print_label( Cairo.Context cr,
+		                          double        x,
+		                          double        y,
+		                          MergeRecord?  record )
+		{
+
+			double w, h;
+			label.get_size( out w, out h );
+
+			cr.save();
+
+			/* Transform coordinate system to be relative to upper corner */
+			/* of the current label */
+			cr.translate( x, y );
+
+			cr.save();
+
+			clip_to_outline( cr );
+
+			cr.save();
+
+			/* Special transformations. */
+			if ( label.rotate )
+			{
+				cr.rotate( Math.PI/2 );
+				cr.translate( 0, -h );
+			}
+			if ( reverse_flag )
+			{
+                cr.translate( w, 0 );
+                cr.scale( -1, 1 );
+			}
+
+			label.draw( cr, false, record );
+
+			cr.restore(); /* From special transformations. */
+
+			cr.restore(); /* From clip to outline. */
+
+			if ( outline_flag )
+			{
+				draw_outline( cr );
+			}
+
+			cr.restore(); /* From translation. */
+		}
+
+
+		private void draw_outline( Cairo.Context cr )
+		{
+			cr.save();
+
+			cr.set_source_rgb( 0, 0, 0 );
+			cr.set_line_width( OUTLINE_WIDTH );
+
+			TemplateFrame frame = label.template.frames.first().data;
+			frame.cairo_path( cr, false );
+
+			cr.stroke();
+
+			cr.restore();
+		}
+
+
+		private void clip_to_outline( Cairo.Context cr )
+		{
+			TemplateFrame frame = label.template.frames.first().data;
+			frame.cairo_path( cr, true );
+
+			cr.set_fill_rule( Cairo.FillRule.EVEN_ODD );
+			cr.clip();
+		}
+
+
+
+	}
+
+}
+
diff --git a/glabels/template_history.vala b/glabels/template_history.vala
new file mode 100644
index 0000000..11d9c91
--- /dev/null
+++ b/glabels/template_history.vala
@@ -0,0 +1,94 @@
+/*  template_history.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class TemplateHistory : Object
+	{
+
+		public signal void changed();
+
+		private static GLib.Settings history;
+		private int                  max_n;
+
+		static construct
+		{
+			history = new GLib.Settings( "org.gnome.glabels-3.history" );
+		}
+
+		public TemplateHistory( int n )
+		{
+			max_n = n;
+
+			history.changed["recent-templates"].connect( on_history_changed );
+		}
+
+		public void add_name( string template_name )
+		{
+			string[] old_templates;
+			string[] new_templates;
+			int      i, j;
+
+			old_templates = history.get_strv( "recent-templates" );
+
+			new_templates = new string[1];
+			new_templates += template_name;
+
+			for ( i = 0, j = 1; (j < (max_n-1)) && (i < old_templates.length); i++ )
+			{
+				if ( template_name != old_templates[i] )
+				{
+					new_templates += old_templates[i];
+				}
+			}
+
+			history.set_strv( "recent-templates", new_templates );
+		}
+
+		public List<string> get_template_list()
+		{
+			string[]     templates;
+			List<string> template_list = new List<string>();
+			int          i;
+
+			templates = history.get_strv( "recent-templates" );
+
+			for ( i = 0; i < templates.length; i++ )
+			{
+				template_list.append( templates[i] );
+			}
+
+			return template_list;
+		}
+
+		private void on_history_changed()
+		{
+			changed();
+		}
+
+	}
+
+}
+
+
diff --git a/glabels/test.vala b/glabels/test.vala
new file mode 100644
index 0000000..60485a7
--- /dev/null
+++ b/glabels/test.vala
@@ -0,0 +1,221 @@
+
+using glabels;
+
+int main (string[] args) {
+
+    Gtk.init( ref args );
+
+    libglabels.Db.init();
+    libglabels.XmlUtil.init();
+
+    var win = new Gtk.Window();
+    win.set_size_request( 200, 200 );
+    win.border_width = 5;
+    win.title = "Widget test";
+    win.destroy.connect( Gtk.main_quit );
+
+    var frame = new Gtk.Frame( "Example Vala Widget" );
+    win.add( frame );
+
+	var vbox = new Gtk.VBox( false, 3 );
+	frame.add( vbox );
+
+
+	switch (args[1])
+	{
+
+	case "0":
+		stdout.printf( "TEST %s.\n", args[1] );
+        libglabels.Db.print_known_papers();
+        libglabels.Db.print_known_categories();
+        libglabels.Db.print_known_vendors();
+        libglabels.Db.print_known_templates();
+        break;
+
+	case "1":
+		stdout.printf( "TEST %s.\n", args[1] );
+		var color = Color.from_rgb( 0.75, 0.75, 1.0 );
+		var button1 = new glabels.ColorButton( "Xyzzy", color, color );
+		vbox.pack_start( button1 );
+
+		var button2 = new glabels.FontButton( "Sans" );
+		vbox.pack_start( button2 );
+		break;
+
+	case "2":
+		stdout.printf( "TEST %s.\n", args[1] );
+		var mini_preview = new glabels.MiniPreview( 200, 200 );
+		mini_preview.set_template_by_name( "Avery 3612" );
+		vbox.pack_start( mini_preview );
+		break;
+
+	case "3":
+		stdout.printf( "TEST %s.\n", args[1] );
+		Gtk.ListStore list_store = new Gtk.ListStore( 2, typeof(string), typeof(Gdk.Pixbuf) );
+
+		var icon_view = new Gtk.IconView.with_model( list_store );
+		icon_view.set_text_column( 0 );
+		icon_view.set_pixbuf_column( 1 );
+
+		var sw = new Gtk.ScrolledWindow( null, null );
+		sw.add( icon_view );
+		vbox.pack_start( sw );
+
+		foreach ( libglabels.Template template in libglabels.Db.templates )
+		{
+			Gtk.TreeIter iter;
+			list_store.append( out iter );
+
+			list_store.set( iter, 0, template.name, 1, template.preview_pixbuf, -1);
+		}
+		break;
+	
+	case "4":
+		stdout.printf( "TEST %s.\n", args[1] );
+		var label = new glabels.Label();
+		label.template = libglabels.Db.lookup_template_from_name( "Avery 3612" );
+
+		var box = new LabelObjectBox();
+		box.x0 = 36;
+		box.y0 = 36;
+		box.w  = 72;
+		box.h  = 36;
+		box.line_width = 4;
+		box.line_color_node = ColorNode.from_color( Color.black() );
+		box.fill_color_node = ColorNode.from_color( Color.from_rgb( 0, 1, 0 ) );
+
+		label.add_object( box );
+
+		var mini_preview = new glabels.MiniPreview( 200, 200 );
+		mini_preview.set_label( label );
+		vbox.pack_start( mini_preview );
+		break;
+
+	case "4.1":
+		stdout.printf( "TEST %s.\n", args[1] );
+		var label = new glabels.Label();
+		label.template = libglabels.Db.lookup_template_from_name( "Avery 3612" );
+		label.rotate = true;
+
+		var box = new LabelObjectBox();
+		box.x0 = 36;
+		box.y0 = 36;
+		box.w  = 72;
+		box.h  = 36;
+		box.line_width = 4;
+		box.line_color_node = ColorNode.from_color( Color.black() );
+		box.fill_color_node = ColorNode.from_color( Color.from_rgb( 0, 1, 0 ) );
+
+		label.add_object( box );
+
+		var mini_preview = new glabels.MiniPreview( 200, 200 );
+		mini_preview.set_label( label );
+		vbox.pack_start( mini_preview );
+		break;
+
+	case "5":
+		stdout.printf( "TEST %s.\n", args[1] );
+		var label = new glabels.Label();
+		label.template = libglabels.Db.lookup_template_from_name( "Avery 5523" );
+
+		var box = new LabelObjectBox();
+		box.x0 = 36;
+		box.y0 = 36;
+		box.w  = 72;
+		box.h  = 36;
+		box.line_width = 2;
+		box.line_color_node = ColorNode.from_color( Color.black() );
+		box.fill_color_node = ColorNode.from_color( Color.from_rgb( 0, 1, 0 ) );
+
+		label.add_object( box );
+
+		var view = new glabels.View( label );
+		vbox.pack_start( view );
+		break;
+
+	case "5.1":
+		stdout.printf( "TEST %s.\n", args[1] );
+		var label = new glabels.Label();
+		label.template = libglabels.Db.lookup_template_from_name( "Avery 5523" );
+		label.rotate = true;
+
+		var box = new LabelObjectBox();
+		box.x0 = 36;
+		box.y0 = 36;
+		box.w  = 72;
+		box.h  = 36;
+		box.line_width = 2;
+		box.line_color_node = ColorNode.from_color( Color.black() );
+		box.fill_color_node = ColorNode.from_color( Color.from_rgb( 0, 1, 0 ) );
+
+		label.add_object( box );
+
+		var view = new glabels.View( label );
+		vbox.pack_start( view );
+		break;
+
+	case "6":
+		stdout.printf( "TEST %s.\n", args[1] );
+		Merge merge = MergeFactory.create_merge( "Text/Comma" );
+		merge.src = "./UH-UTF8.csv";
+		foreach ( MergeRecord record in merge.record_list )
+		{
+			stdout.printf( "RECORD:\n" );
+			foreach ( MergeField field in record.field_list )
+			{
+				stdout.printf( "[ %s ] = %s\n", field.key, field.value );
+			}
+			stdout.printf( "\n" );
+		}
+		break;
+
+	case "6.1":
+		stdout.printf( "TEST %s.\n", args[1] );
+		Merge merge = MergeFactory.create_merge( "Text/Comma/Line1Keys" );
+		merge.src = "./UH-UTF8.csv";
+		foreach ( MergeRecord record in merge.record_list )
+		{
+			stdout.printf( "RECORD:\n" );
+			foreach ( MergeField field in record.field_list )
+			{
+				stdout.printf( "[ %s ] = %s\n", field.key, field.value );
+			}
+			stdout.printf( "\n" );
+		}
+		break;
+
+	case "7":
+		stdout.printf( "TEST %s.\n", args[1] );
+		glabels.Label label = XmlLabel.open_file( "test1.glabels" );
+		var view = new glabels.View( label );
+		vbox.pack_start( view );
+		break;
+
+	case "8":
+		stdout.printf( "TEST %s.\n", args[1] );
+		libglabels.XmlUtil.default_units = libglabels.Units.inch();
+		glabels.Label label = XmlLabel.open_file( "test1.glabels" );
+		XmlLabel.save_file( label, "test1-out.glabels" );
+		break;
+
+	case "9":
+		stdout.printf( "TEST %s.\n", args[1] );
+		Gtk.IconTheme.get_default().append_search_path( Path.build_filename( Config.DATADIR, "glabels-3.0", "icons", null ) );
+		libglabels.XmlUtil.default_units = libglabels.Units.inch();
+		glabels.Label label = XmlLabel.open_file( "test1.glabels" );
+		var window = new Window.from_label( label );
+		window.show_all();
+		break;
+
+	default:
+		stdout.printf( "NO TEST SELECTED.\n" );
+		break;
+	}
+
+
+    win.show_all();
+    Gtk.main();
+
+    return 0;
+}
+
diff --git a/glabels/ui.vala b/glabels/ui.vala
new file mode 100644
index 0000000..7cfefa3
--- /dev/null
+++ b/glabels/ui.vala
@@ -0,0 +1,1391 @@
+/*  ui.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace glabels
+{
+
+	public class Ui : Gtk.UIManager
+	{
+		private weak Window window;
+		private Prefs  prefs;
+
+		private const Gtk.ActionEntry[] entries = {
+
+			/* Menu entries. */
+			{ "FileMenu",                null, N_("_File") },
+			{ "FileRecentsMenu",         null, N_("Open Recent _Files") },
+			{ "EditMenu",                null, N_("_Edit") },
+			{ "ViewMenu",                null, N_("_View") },
+			{ "ViewMainToolBarMenu",     null, N_("Customize Main Toolbar") },
+			{ "ViewDrawingToolBarMenu",  null, N_("Customize Drawing Toolbar") },
+			{ "ViewPropertyToolBarMenu", null, N_("Customize Properties Toolbar") },
+			{ "ObjectsMenu",             null, N_("_Objects") },
+			{ "ObjectsCreateMenu",       null, N_("_Create") },
+			{ "ObjectsOrderMenu",        null, N_("_Order") },
+			{ "ObjectsRotateFlipMenu",   null, N_("_Rotate/Flip") },
+			{ "ObjectsAlignMenu",        null, N_("_Alignment") },
+			{ "ObjectsCenterMenu",       null, N_("C_enter") },
+			{ "HelpMenu",                null, N_("_Help") },
+
+			/* Popup entries. */
+			{ "ContextMenu", null, N_("Context Menu") },
+			{ "EmptySelectionContextMenu", null, N_("Context Menu") },
+
+			/* File action entries. */
+			{ "FileNew",
+			  "gtk-new",
+			  N_("_New"),
+			  "<control>N",
+			  N_("Create a new file"),
+			  on_file_new },
+
+			{ "FileOpen",
+			  "gtk-open",
+			  N_("_Open..."),
+			  "<control>O",
+			  N_("Open a file"),
+			  on_file_open },
+
+			{ "FileSave",
+			  "gtk-save",
+			  N_("_Save"),
+			  "<control>S",
+			  N_("Save current file"),
+			  on_file_save },
+
+			{ "FileSaveAs",
+			  "gtk-save",
+			  N_("Save _As..."),
+			  "<shift><control>S",
+			  N_("Save the current file to a different name"),
+			  on_file_save_as },
+
+			{ "FilePrint",
+			  "gtk-print",
+			  N_("_Print..."),
+			  "<control>P",
+			  N_("Print the current file"),
+			  on_file_print },
+
+			{ "FileProperties",
+			  "gtk-properties",
+			  N_("Properties..."),
+			  null,
+			  N_("Modify document properties"),
+			  on_file_properties },
+
+			{ "FileTemplateDesigner",
+			  null,
+			  N_("Template _Designer..."),
+			  null,
+			  N_("Create a custom template"),
+			  on_file_template_designer },
+
+			{ "FileClose",
+			  "gtk-close",
+			  N_("_Close"),
+			  "<alt>F4",
+			  N_("Close the current file"),
+			  on_file_close },
+
+			{ "FileQuit",
+			  "gtk-quit",
+			  N_("_Quit"),
+			  "<control>Q",
+			  N_("Quit the program"),
+			  on_file_quit },
+
+
+			/* Edit action entries. */
+			{ "EditUndo",
+			  "gtk-undo",
+			  N_("Undo"),
+			  "<control>Z",
+			  N_("Undo"),
+			  on_edit_undo },
+
+			{ "EditRedo",
+			  "gtk-redo",
+			  N_("Redo"),
+			  "<shift><control>Z",
+			  N_("Redo"),
+			  on_edit_redo },
+
+			{ "EditCut",
+			  "gtk-cut",
+			  N_("Cut"),
+			  "<control>X",
+			  N_("Cut the selection"),
+			  on_edit_cut },
+
+			{ "EditCopy",
+			  "gtk-copy",
+			  N_("Copy"),
+			  "<control>C",
+			  N_("Copy the selection"),
+			  on_edit_copy },
+
+			{ "EditPaste",
+			  "gtk-paste",
+			  N_("Paste"),
+			  "<control>V",
+			  N_("Paste the clipboard"),
+			  on_edit_paste },
+
+			{ "EditDelete",
+			  null,
+			  N_("Delete"),
+			  null,
+			  N_("Delete the selected objects"),
+			  on_edit_delete },
+
+			{ "EditSelectAll",
+			  null,
+			  N_("Select All"),
+			  "<control>A",
+			  N_("Select all objects"),
+			  on_edit_select_all },
+
+			{ "EditUnSelectAll",
+			  null,
+			  N_("Un-select All"),
+			  null,
+			  N_("Remove all selections"),
+			  on_edit_unselect_all },
+
+			{ "EditPreferences",
+			  "gtk-preferences",
+			  N_("Preferences"),
+			  null,
+			  N_("Configure the application"),
+			  on_edit_preferences },
+
+
+			/* View action entries. */
+			{ "ViewZoomIn",
+			  "gtk-zoom-in",
+			  N_("Zoom in"),
+			  null,
+			  N_("Increase magnification"),
+			  on_view_zoomin },
+
+			{ "ViewZoomOut",
+			  "gtk-zoom-out",
+			  N_("Zoom out"),
+			  null,
+			  N_("Decrease magnification"),
+			  on_view_zoomout },
+
+			{ "ViewZoom1to1",
+			  "gtk-zoom-100",
+			  N_("Zoom 1 to 1"),
+			  null,
+			  N_("Restore scale to 100%"),
+			  on_view_zoom1to1 },
+
+			{ "ViewZoomToFit",
+			  "gtk-zoom-fit",
+			  N_("Zoom to fit"),
+			  null,
+			  N_("Set scale to fit window"),
+			  on_view_zoom_to_fit },
+
+
+			/* Objects action entries. */
+			{ "ObjectsArrowMode",
+			  "glabels-arrow",
+			  N_("Select Mode"),
+			  null,
+			  N_("Select, move and modify objects"),
+			  on_objects_arrow_mode },
+
+			{ "ObjectsCreateText",
+			  "glabels-text",
+			  N_("Text"),
+			  null,
+			  N_("Create text object"),
+			  on_objects_create_text },
+
+			{ "ObjectsCreateBox",
+			  "glabels-box",
+			  N_("Box"),
+			  null,
+			  N_("Create box/rectangle object"),
+			  on_objects_create_box },
+
+			{ "ObjectsCreateLine",
+			  "glabels-line",
+			  N_("Line"),
+			  null,
+			  N_("Create line object"),
+			  on_objects_create_line },
+
+			{ "ObjectsCreateEllipse",
+			  "glabels-ellipse",
+			  N_("Ellipse"),
+			  null,
+			  N_("Create ellipse/circle object"),
+			  on_objects_create_ellipse },
+
+			{ "ObjectsCreateImage",
+			  "glabels-image",
+			  N_("Image"),
+			  null,
+			  N_("Create image object"),
+			  on_objects_create_image },
+
+			{ "ObjectsCreateBarcode",
+			  "glabels-barcode",
+			  N_("Barcode"),
+			  null,
+			  N_("Create barcode object"),
+			  on_objects_create_barcode },
+	
+			{ "ObjectsRaise",
+			  "glabels-order-top",
+			  N_("Bring to front"),
+			  null,
+			  N_("Raise object to top"),
+			  on_objects_raise },
+
+			{ "ObjectsLower",
+			  "glabels-order-bottom",
+			  N_("Send to back"),
+			  null,
+			  N_("Lower object to bottom"),
+			  on_objects_lower },
+
+			{ "ObjectsRotateLeft",
+			  "glabels-rotate-left",
+			  N_("Rotate left"),
+			  null,
+			  N_("Rotate object 90 degrees counter-clockwise"),
+			  on_objects_rotate_left },
+
+			{ "ObjectsRotateRight",
+			  "glabels-rotate-right",
+			  N_("Rotate right"),
+			  null,
+			  N_("Rotate object 90 degrees clockwise"),
+			  on_objects_rotate_right },
+
+			{ "ObjectsFlipHorizontal",
+			  "glabels-flip-horiz",
+			  N_("Flip horizontally"),
+			  null,
+			  N_("Flip object horizontally"),
+			  on_objects_flip_horiz },
+
+			{ "ObjectsFlipVertical",
+			  "glabels-flip-vert",
+			  N_("Flip vertically"),
+			  null,
+			  N_("Flip object vertically"),
+			  on_objects_flip_vert },
+
+			{ "ObjectsAlignLeft",
+			  "glabels-align-left",
+			  N_("Align left"),
+			  null,
+			  N_("Align objects to left edges"),
+			  on_objects_align_left },
+
+			{ "ObjectsAlignHCenter",
+			  "glabels-align-hcenter",
+			  N_("Align center"),
+			  null,
+			  N_("Align objects to horizontal centers"),
+			  on_objects_align_hcenter },
+
+			{ "ObjectsAlignRight",
+			  "glabels-align-right",
+			  N_("Align right"),
+			  null,
+			  N_("Align objects to right edges"),
+			  on_objects_align_right },
+
+			{ "ObjectsAlignTop",
+			  "glabels-align-top",
+			  N_("Align top"),
+			  null,
+			  N_("Align objects to top edges"),
+			  on_objects_align_top },
+
+			{ "ObjectsAlignVCenter",
+			  "glabels-align-vcenter",
+			  N_("Align middle"),
+			  null,
+			  N_("Align objects to vertical centers"),
+			  on_objects_align_vcenter },
+
+			{ "ObjectsAlignBottom",
+			  "glabels-align-bottom",
+			  N_("Align bottom"),
+			  null,
+			  N_("Align objects to bottom edges"),
+			  on_objects_align_bottom },
+
+			{ "ObjectsCenterHorizontal",
+			  "glabels-center-horiz",
+			  N_("Center horizontally"),
+			  null,
+			  N_("Center objects to horizontal label center"),
+			  on_objects_center_horiz },
+
+			{ "ObjectsCenterVertical",
+			  "glabels-center-vert",
+			  N_("Center vertically"),
+			  null,
+			  N_("Center objects to vertical label center"),
+			  on_objects_center_vert },
+
+			{ "ObjectsMergeProperties",
+			  "glabels-merge",
+			  N_("Merge properties"),
+			  null,
+			  N_("Edit merge properties"),
+			  on_objects_merge_properties },
+
+
+			/* Help actions entries. */
+			{ "HelpContents",
+			  "gtk-help",
+			  N_("Contents"),
+			  "F1",
+			  N_("Open glabels manual"),
+			  on_help_contents },
+
+			{ "HelpAbout",
+			  "gtk-about",
+			  N_("About..."),
+			  null,
+			  N_("About glabels"),
+			  on_help_about }
+
+		};
+
+
+		private const Gtk.ToggleActionEntry[] toggle_entries = {
+
+			{ "ViewMainToolBar",
+			  null,
+			  N_("Main toolbar"),
+			  null,
+			  N_("Change the visibility of the main toolbar in the current window"),
+			  on_view_main_toolbar_toggled,
+			  true },
+
+			{ "ViewDrawingToolBar",
+			  null,
+			  N_("Drawing toolbar"),
+			  null,
+			  N_("Change the visibility of the drawing toolbar in the current window"),
+			  on_view_drawing_toolbar_toggled,
+			  true },
+
+			{ "ViewPropertyToolBar",
+			  null,
+			  N_("Property toolbar"),
+			  null,
+			  N_("Change the visibility of the property toolbar in the current window"),
+			  on_view_property_bar_toggled,
+			  true },
+
+			{ "ViewGrid",
+			  null,
+			  N_("Grid"),
+			  null,
+			  N_("Change the visibility of the grid in the current window"),
+			  on_view_grid_toggled,
+			  true },
+
+			{ "ViewMarkup",
+			  null,
+			  N_("Markup"),
+			  null,
+			  N_("Change the visibility of markup lines in the current window"),
+			  on_view_markup_toggled,
+			  true }
+
+		};
+
+
+		private static const string ui_info = """
+			<ui>
+			
+				<menubar name='MenuBar'>
+					<menu action='FileMenu'>
+						<menuitem action='FileNew' />
+						<menuitem action='FileOpen' />
+						<menuitem action='FileRecentsMenu' />
+						<separator />
+						<menuitem action='FileSave' />
+						<menuitem action='FileSaveAs' />
+						<separator />
+						<menuitem action='FilePrint' />
+						<separator />
+						<menuitem action='FileProperties' />
+						<menuitem action='FileTemplateDesigner' />
+						<separator />
+						<menuitem action='FileClose' />
+						<menuitem action='FileQuit' />
+					</menu>
+					<menu action='EditMenu'>
+						<menuitem action='EditUndo' />
+						<menuitem action='EditRedo' />
+						<separator />
+						<menuitem action='EditCut' />
+						<menuitem action='EditCopy' />
+						<menuitem action='EditPaste' />
+						<menuitem action='EditDelete' />
+						<separator />
+						<menuitem action='EditSelectAll' />
+						<menuitem action='EditUnSelectAll' />
+						<separator />
+						<menuitem action='EditPreferences' />
+					</menu>
+					<menu action='ViewMenu'>
+						<menuitem action='ViewMainToolBar' />
+						<menuitem action='ViewDrawingToolBar' />
+						<menuitem action='ViewPropertyToolBar' />
+						<separator />
+						<menuitem action='ViewGrid' />
+						<menuitem action='ViewMarkup' />
+						<separator />
+						<menuitem action='ViewZoomIn' />
+						<menuitem action='ViewZoomOut' />
+						<menuitem action='ViewZoom1to1' />
+						<menuitem action='ViewZoomToFit' />
+					</menu>
+					<menu action='ObjectsMenu'>
+						<menuitem action='ObjectsArrowMode' always-show-image='true' />
+						<menu action='ObjectsCreateMenu'>
+							<menuitem action='ObjectsCreateText' always-show-image='true' />
+							<menuitem action='ObjectsCreateBox' always-show-image='true' />
+							<menuitem action='ObjectsCreateLine' always-show-image='true' />
+							<menuitem action='ObjectsCreateEllipse' always-show-image='true' />
+							<menuitem action='ObjectsCreateImage' always-show-image='true' />
+							<menuitem action='ObjectsCreateBarcode' always-show-image='true' />
+						</menu>
+						<separator />
+						<menu action='ObjectsOrderMenu'>
+							<menuitem action='ObjectsRaise' always-show-image='true' />
+							<menuitem action='ObjectsLower' always-show-image='true' />
+						</menu>
+						<menu action='ObjectsRotateFlipMenu'>
+							<menuitem action='ObjectsRotateLeft' always-show-image='true' />
+							<menuitem action='ObjectsRotateRight' always-show-image='true' />
+							<menuitem action='ObjectsFlipHorizontal' always-show-image='true' />
+							<menuitem action='ObjectsFlipVertical' always-show-image='true' />
+						</menu>
+						<menu action='ObjectsAlignMenu'>
+							<menuitem action='ObjectsAlignLeft' always-show-image='true' />
+							<menuitem action='ObjectsAlignHCenter' always-show-image='true' />
+							<menuitem action='ObjectsAlignRight' always-show-image='true' />
+						        <separator />
+							<menuitem action='ObjectsAlignTop' always-show-image='true' />
+							<menuitem action='ObjectsAlignVCenter' always-show-image='true' />
+							<menuitem action='ObjectsAlignBottom' always-show-image='true' />
+						</menu>
+						<menu action='ObjectsCenterMenu'>
+							<menuitem action='ObjectsCenterHorizontal' always-show-image='true' />
+							<menuitem action='ObjectsCenterVertical' always-show-image='true' />
+						</menu>
+						<separator />
+						<menuitem action='ObjectsMergeProperties' />
+					</menu>
+					<menu action='HelpMenu'>
+						<menuitem action='HelpContents' />
+						<menuitem action='HelpAbout' />
+					</menu>
+				</menubar>
+			
+				<toolbar name='MainToolBar'>
+					<toolitem action='FileNew' />
+					<toolitem action='FileOpen' />
+					<toolitem action='FileSave' />
+					<separator />
+					<toolitem action='FilePrint' />
+					<separator />
+					<toolitem action='EditCut' />
+					<toolitem action='EditCopy' />
+					<toolitem action='EditPaste' />
+				</toolbar>
+			
+				<toolbar name='DrawingToolBar'>
+					<toolitem action='ObjectsArrowMode' />
+					<separator />
+					<toolitem action='ObjectsCreateText' />
+					<toolitem action='ObjectsCreateBox' />
+					<toolitem action='ObjectsCreateLine' />
+					<toolitem action='ObjectsCreateEllipse' />
+					<toolitem action='ObjectsCreateImage' />
+					<toolitem action='ObjectsCreateBarcode' />
+					<separator />
+					<toolitem action='ViewZoomIn' />
+					<toolitem action='ViewZoomOut' />
+					<toolitem action='ViewZoom1to1' />
+					<toolitem action='ViewZoomToFit' />
+					<separator />
+					<toolitem action='ObjectsMergeProperties' />
+				</toolbar>
+			
+				<popup action='ContextMenu'>
+					<menu action='ObjectsOrderMenu'>
+						<menuitem action='ObjectsRaise' always-show-image='true' />
+						<menuitem action='ObjectsLower' always-show-image='true' />
+					</menu>
+					<menu action='ObjectsRotateFlipMenu'>
+						<menuitem action='ObjectsRotateLeft' always-show-image='true' />
+						<menuitem action='ObjectsRotateRight' always-show-image='true' />
+						<menuitem action='ObjectsFlipHorizontal' always-show-image='true' />
+						<menuitem action='ObjectsFlipVertical' always-show-image='true' />
+					</menu>
+					<menu action='ObjectsAlignMenu'>
+						<menuitem action='ObjectsAlignLeft' always-show-image='true' />
+						<menuitem action='ObjectsAlignHCenter' always-show-image='true' />
+						<menuitem action='ObjectsAlignRight' always-show-image='true' />
+						<separator />
+						<menuitem action='ObjectsAlignTop' always-show-image='true' />
+						<menuitem action='ObjectsAlignVCenter' always-show-image='true' />
+						<menuitem action='ObjectsAlignBottom' always-show-image='true' />
+					</menu>
+					<menu action='ObjectsCenterMenu'>
+						<menuitem action='ObjectsCenterHorizontal' always-show-image='true' />
+						<menuitem action='ObjectsCenterVertical' always-show-image='true' />
+					</menu>
+					<separator />
+					<menuitem action='EditCut' />
+					<menuitem action='EditCopy' />
+					<menuitem action='EditPaste' />
+					<menuitem action='EditDelete' />
+				</popup>
+			
+				<popup action='EmptySelectionContextMenu'>
+					<menuitem action='EditPaste' />
+				</popup>
+			
+			</ui>
+			""";
+		
+
+		static string[] doc_verbs = {
+			"/ui/MenuBar/FileMenu/FileProperties",
+			"/ui/MenuBar/FileMenu/FileSave",
+			"/ui/MenuBar/FileMenu/FileSaveAs",
+			"/ui/MenuBar/FileMenu/FilePrint",
+			"/ui/MenuBar/FileMenu/FileClose",
+			"/ui/MenuBar/EditMenu/EditUndo",
+			"/ui/MenuBar/EditMenu/EditRedo",
+			"/ui/MenuBar/EditMenu/EditCut",
+			"/ui/MenuBar/EditMenu/EditCopy",
+			"/ui/MenuBar/EditMenu/EditDelete",
+			"/ui/MenuBar/EditMenu/EditSelectAll",
+			"/ui/MenuBar/EditMenu/EditUnSelectAll",
+			"/ui/MenuBar/ViewMenu/ViewZoomIn",
+			"/ui/MenuBar/ViewMenu/ViewZoomOut",
+			"/ui/MenuBar/ViewMenu/ViewZoom1to1",
+			"/ui/MenuBar/ViewMenu/ViewZoomToFit",
+			"/ui/MenuBar/ViewMenu/ViewGrid",
+			"/ui/MenuBar/ViewMenu/ViewMarkup",
+			"/ui/MenuBar/ObjectsMenu/ObjectsArrowMode",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCreateMenu",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCreateMenu/ObjectsCreateText",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCreateMenu/ObjectsCreateLine",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCreateMenu/ObjectsCreateBox",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCreateMenu/ObjectsCreateEllipse",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCreateMenu/ObjectsCreateImage",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCreateMenu/ObjectsCreateBarcode",
+			"/ui/MenuBar/ObjectsMenu/ObjectsOrderMenu",
+			"/ui/MenuBar/ObjectsMenu/ObjectsOrderMenu/ObjectsRaise",
+			"/ui/MenuBar/ObjectsMenu/ObjectsOrderMenu/ObjectsLower",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu/ObjectsRotateLeft",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu/ObjectsRotateRight",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu/ObjectsFlipHorizontal",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu/ObjectsFlipVertical",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignLeft",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignRight",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignHCenter",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignTop",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignBottom",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignVCenter",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCenterMenu",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCenterMenu/ObjectsCenterHorizontal",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCenterMenu/ObjectsCenterVertical",
+			"/ui/MenuBar/ObjectsMenu/ObjectsMergeProperties"
+		};
+
+		static string[] doc_modified_verbs = {
+			"/ui/MenuBar/FileMenu/FileSave"
+		};
+
+		static string[] paste_verbs = {
+			"/ui/MenuBar/EditMenu/EditPaste"
+		};
+
+		static string[] selection_verbs = {
+			"/ui/MenuBar/EditMenu/EditCut",
+			"/ui/MenuBar/EditMenu/EditCopy",
+			"/ui/MenuBar/EditMenu/EditDelete",
+			"/ui/MenuBar/EditMenu/EditUnSelectAll",
+			"/ui/MenuBar/ObjectsMenu/ObjectsOrderMenu",
+			"/ui/MenuBar/ObjectsMenu/ObjectsOrderMenu/ObjectsRaise",
+			"/ui/MenuBar/ObjectsMenu/ObjectsOrderMenu/ObjectsLower",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu/ObjectsRotateLeft",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu/ObjectsRotateRight",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu/ObjectsFlipHorizontal",
+			"/ui/MenuBar/ObjectsMenu/ObjectsRotateFlipMenu/ObjectsFlipVertical",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCenterMenu",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCenterMenu/ObjectsCenterHorizontal",
+			"/ui/MenuBar/ObjectsMenu/ObjectsCenterMenu/ObjectsCenterVertical"
+		};
+
+		static string[] atomic_selection_verbs = {
+		};
+
+		static string[] multi_selection_verbs = {
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignLeft",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignRight",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignHCenter",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignTop",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignBottom",
+			"/ui/MenuBar/ObjectsMenu/ObjectsAlignMenu/ObjectsAlignVCenter"
+		};
+
+
+		public Ui( Window window )
+		{
+			this.window = window;
+			this.prefs = new Prefs();
+
+			connect_proxy.connect( on_connect_proxy );
+			disconnect_proxy.connect( on_disconnect_proxy );
+
+			Gtk.ActionGroup actions = new Gtk.ActionGroup( "Actions" );
+			actions.set_translation_domain( "" );
+			actions.add_actions( entries, this );
+			actions.add_toggle_actions( toggle_entries, this );
+
+			insert_action_group( actions, 0 );
+			window.add_accel_group( get_accel_group() );
+
+			try
+			{
+				add_ui_from_string( ui_info, ui_info.length );
+			}
+			catch ( Error e )
+			{
+				message( "building menus failed: %s", e.message );
+			}
+
+			/* Set the toolbar styles according to prefs */
+			set_app_main_toolbar_style();
+			set_app_drawing_toolbar_style();
+		
+			/* Set view grid and markup visibility according to prefs */
+			set_view_style();
+		
+			/* TODO: add an Open Recents Submenu */
+
+			set_verb_list_sensitive( doc_verbs, false );
+			set_verb_list_sensitive( paste_verbs, false );
+		}
+
+
+		public void update_all( View view )
+		{
+			Label label = view.label;
+
+			set_verb_list_sensitive( doc_verbs, true );
+
+			set_verb_sensitive( "/ui/MenuBar/EditMenu/EditUndo", label.can_undo() );
+			set_verb_sensitive( "/ui/MenuBar/EditMenu/EditRedo", label.can_redo() );
+
+			set_verb_list_sensitive( doc_modified_verbs, label.modified );
+
+			set_verb_sensitive( "/ui/MenuBar/ViewMenu/ViewZoomIn", !view.is_zoom_max() );
+			set_verb_sensitive( "/ui/MenuBar/ViewMenu/ViewZoomOut", !view.is_zoom_min() );
+
+			set_verb_list_sensitive( selection_verbs, !label.is_selection_empty() );
+
+			set_verb_list_sensitive( atomic_selection_verbs, label.is_selection_atomic() );
+
+			set_verb_list_sensitive( multi_selection_verbs,
+			                         !label.is_selection_empty() && !label.is_selection_atomic() );
+		}
+
+
+		public void update_modified_verbs( Label label )
+		{
+			set_verb_list_sensitive( doc_modified_verbs, label.modified );
+		}
+
+
+		public void update_selection_verbs( View view,
+		                                    bool has_focus )
+		{
+			if ( has_focus )
+			{
+				set_verb_list_sensitive( selection_verbs, !view.label.is_selection_empty() );
+
+				set_verb_list_sensitive( atomic_selection_verbs,
+				                         view.label.is_selection_atomic() );
+
+				set_verb_list_sensitive( multi_selection_verbs,
+				                         !view.label.is_selection_empty() &&
+				                         !view.label.is_selection_atomic() );
+			}
+			else
+			{
+				set_verb_list_sensitive( selection_verbs, false );
+				set_verb_list_sensitive( atomic_selection_verbs, false );
+				set_verb_list_sensitive( multi_selection_verbs, false );
+			}
+		}
+
+
+		public void update_zoom_verbs( View view )
+		{
+			set_verb_sensitive( "/ui/MenuBar/ViewMenu/ViewZoomIn", !view.is_zoom_max() );
+			set_verb_sensitive( "/ui/MenuBar/ViewMenu/ViewZoomOut", !view.is_zoom_min() );
+		}
+
+
+		public void update_paste_verbs( bool can_paste )
+		{
+			set_verb_list_sensitive( paste_verbs, can_paste );
+		}
+
+
+		public void update_undo_redo_verbs( Label label )
+		{
+			Gtk.MenuItem  menu_item;
+			string        description;
+			string        menu_label;
+
+			menu_item = (Gtk.MenuItem)get_widget( "/MenuBar/EditMenu/EditUndo" );
+			description = label.get_undo_description();
+			menu_label = "%s: %s".printf( _("Undo"), description );
+			menu_item.set_label( menu_label );
+
+			menu_item = (Gtk.MenuItem)get_widget( "/MenuBar/EditMenu/EditRedo" );
+			description = label.get_redo_description();
+			menu_label = "%s: %s".printf( _("Redo"), description );
+			menu_item.set_label( menu_label );
+
+			set_verb_sensitive( "/ui/MenuBar/EditMenu/EditUndo", label.can_undo() );
+			set_verb_sensitive( "/ui/MenuBar/EditMenu/EditRedo", label.can_redo() );
+		}
+
+
+		private void set_app_main_toolbar_style()
+		{
+			/* Updated view menu */
+			set_verb_state( "/ui/ViewMenu/ViewMainToolBar", prefs.main_toolbar_visible );
+
+			Gtk.Toolbar toolbar = (Gtk.Toolbar)get_widget( "/MainToolBar" );
+
+			if ( prefs.main_toolbar_visible )
+			{
+				toolbar.show_all();
+			}
+			else
+			{
+				toolbar.hide();
+			}
+		}
+
+
+		private void set_app_drawing_toolbar_style()
+		{
+			/* Updated view menu */
+			set_verb_state( "/ui/MenuBar/ViewMenu/ViewDrawingToolBar",
+			                prefs.drawing_toolbar_visible );
+
+			Gtk.Toolbar toolbar = (Gtk.Toolbar)get_widget( "/DrawingToolBar" );
+
+			toolbar.set_style( Gtk.ToolbarStyle.ICONS );
+
+			if ( prefs.drawing_toolbar_visible )
+			{
+				toolbar.show_all();
+			}
+			else
+			{
+				toolbar.hide();
+			}
+		}
+
+
+		private void set_view_style()
+		{
+			set_verb_state( "/ui/MenuBar/ViewMenu/ViewGrid", prefs.grid_visible );
+
+			set_verb_state( "/ui/MenuBar/ViewMenu/ViewMarkup", prefs.markup_visible );
+		}
+
+
+		private void on_connect_proxy( Gtk.Action action,
+		                               Gtk.Widget proxy )
+		{
+			if ( proxy is Gtk.MenuItem )
+			{
+				((Gtk.MenuItem)proxy).select.connect( on_menu_item_select );
+				((Gtk.MenuItem)proxy).deselect.connect( on_menu_item_deselect );
+			}
+		}
+
+
+		private void on_disconnect_proxy( Gtk.Action action,
+		                                  Gtk.Widget proxy )
+		{
+			if ( proxy is Gtk.MenuItem )
+			{
+				((Gtk.MenuItem)proxy).select.disconnect( on_menu_item_select );
+				((Gtk.MenuItem)proxy).deselect.disconnect( on_menu_item_deselect );
+			}
+		}
+
+
+		private void on_menu_item_select( Gtk.MenuItem proxy )
+		{
+			Gtk.Action? action = proxy.get_data( "gtk-action" );
+			return_if_fail( action != null );
+	
+			string? message = action.get_data( "tooltip" );
+			if ( message != null )
+			{
+				window.statusbar.push( window.menu_tips_context_id, message );
+			}
+		}
+
+
+		private void on_menu_item_deselect( Gtk.MenuItem proxy )
+		{
+			window.statusbar.pop( window.menu_tips_context_id );
+		}
+
+
+		private void set_verb_sensitive( string  cname,
+		                                 bool    sensitive )
+		{
+			Gtk.Action action = get_action( cname );
+
+			if ( action != null )
+			{
+				action.set_sensitive( sensitive );
+			}
+		}
+
+
+		private void set_verb_list_sensitive(  string[] vlist,
+		                                       bool     sensitive )
+		{
+			foreach ( string verb in vlist )
+			{
+				Gtk.Action action = get_action( verb );
+
+				if ( action != null )
+				{
+					action.set_sensitive( sensitive );
+				}
+			}
+		}
+
+
+		private void set_verb_state( string cname,
+		                             bool   state )
+		{
+			Gtk.ToggleAction action = (Gtk.ToggleAction)get_action( cname );
+
+			if ( action != null )
+			{
+				action.set_active( state );
+			}
+		}
+
+
+
+		/*****************************
+		 * Actions
+		 ****************************/
+
+		private void on_file_new( Gtk.Action action )
+		{
+			File.new_label( window );
+		}
+
+
+		private void on_file_properties( Gtk.Action action )
+		{
+			stdout.printf( "gl_file_properties (GL_VIEW(window->view)->label, window)\n" );
+		}
+
+
+		private void on_file_template_designer( Gtk.Action action )
+		{
+			stdout.printf( """dialog = gl_template_designer_new (GTK_WINDOW(window));
+			               gtk_widget_show (dialog);\n""" );
+		}
+
+
+		private void on_file_open( Gtk.Action action )
+		{
+			File.open( window );
+		}
+
+
+		private void on_file_open_recent (Gtk.RecentChooser chooser )
+		{
+			/*
+			GtkRecentInfo *item;
+			gchar         *utf8_filename;
+
+			item = gtk_recent_chooser_get_current_item (chooser);
+			if (!item)
+				return;
+
+			utf8_filename = gl_recent_get_utf8_filename (item);
+
+			gl_debug (DEBUG_COMMANDS, "Selected %s\n", utf8_filename);
+			gl_file_open_recent (utf8_filename, window );
+
+			gtk_recent_info_unref (item);
+			*/
+		}
+
+
+		private void on_file_save( Gtk.Action action )
+		{
+			File.save( window.view.label, window );
+		}
+
+
+		private void on_file_save_as( Gtk.Action action )
+		{
+			File.save_as( window.view.label, window );
+		}
+
+
+		private void on_file_print( Gtk.Action action )
+		{
+			/*
+			glPrintOpDialog         *op;
+			GtkPrintOperationResult  result;
+
+			op = gl_print_op_dialog_new (GL_VIEW(window->view)->label);
+
+			if (window->print_settings)
+			{
+				gl_print_op_set_settings (GL_PRINT_OP (op), window->print_settings);
+			}
+
+			result = gtk_print_operation_run (GTK_PRINT_OPERATION (op),
+			                                  GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
+			                                  GTK_WINDOW (window ),
+			                                  NULL);
+
+			if ( result == GTK_PRINT_OPERATION_RESULT_APPLY )
+			{
+				gl_print_op_free_settings (window->print_settings);
+				window->print_settings = gl_print_op_get_settings (GL_PRINT_OP (op));
+			}
+			*/
+		}
+
+
+		private void on_file_close( Gtk.Action action )
+		{
+			File.close( window );
+		}
+
+
+		private void on_file_quit( Gtk.Action action )
+		{
+			File.exit();
+		}
+
+
+		private void on_edit_undo( Gtk.Action action )
+		{
+			stdout.printf( "gl_label_undo (GL_LABEL (GL_VIEW (window->view)->label));\n" );
+		}
+
+
+		private void on_edit_redo( Gtk.Action action )
+		{
+			stdout.printf( "gl_label_redo (GL_LABEL (GL_VIEW (window->view)->label));\n" );
+		}
+
+
+		private void on_edit_cut( Gtk.Action action )
+		{
+			stdout.printf( "gl_label_cut_selection (window->label);\n" );
+		}
+
+
+		private void on_edit_copy( Gtk.Action action )
+		{
+			stdout.printf( "gl_label_copy_selection (window->label);\n" );
+		}
+
+
+		private void on_edit_paste( Gtk.Action action )
+		{
+			stdout.printf( "gl_label_paste (window->label);\n" );
+		}
+
+
+		private void on_edit_delete( Gtk.Action action )
+		{
+			stdout.printf( "gl_label_delete_selection (GL_VIEW(window->view)->label);\n" );
+		}
+
+
+		private void on_edit_select_all( Gtk.Action action )
+		{
+			window.view.label.select_all();
+		}
+
+
+		private void on_edit_unselect_all( Gtk.Action action )
+		{
+			window.view.label.unselect_all();
+		}
+
+
+		private void on_edit_preferences( Gtk.Action action )
+		{
+			/*
+			static GtkWidget *dialog = NULL;
+
+			if (dialog != NULL)
+			{
+				gtk_window_present (GTK_WINDOW (dialog));
+				gtk_window_set_transient_for (GTK_WINDOW (dialog),        
+				                              GTK_WINDOW(window ));
+
+			} else {
+                
+				dialog = gl_prefs_dialog_new (GTK_WINDOW(window ));
+
+				g_signal_connect (G_OBJECT (dialog), "destroy",
+				                  G_CALLBACK (gtk_widget_destroyed), &dialog);
+        
+				gtk_widget_show (dialog);
+
+			}
+			*/
+		}
+
+
+		private void on_view_main_toolbar_toggled( Gtk.Action action )
+		{
+			Gtk.ToggleAction toggle_action = (Gtk.ToggleAction)action;
+			
+			prefs.main_toolbar_visible = toggle_action.active;
+			set_app_main_toolbar_style();
+		}
+
+
+		private void on_view_drawing_toolbar_toggled( Gtk.Action action )
+		{
+			Gtk.ToggleAction toggle_action = (Gtk.ToggleAction)action;
+			
+			prefs.drawing_toolbar_visible = toggle_action.active;
+			set_app_drawing_toolbar_style();
+		}
+
+
+		private void on_view_property_bar_toggled( Gtk.Action action )
+		{
+			/*
+			gboolean     state;
+
+			state =  gtk_toggle_action_get_active (action);
+
+			gl_prefs_model_set_property_toolbar_visible (gl_prefs, state);
+			if (state) {
+				gtk_widget_show (GTK_WIDGET (window->property_bar));
+			} else {
+				gtk_widget_hide (GTK_WIDGET (window->property_bar));
+			}
+			*/
+		}
+
+
+		private void on_view_grid_toggled (Gtk.Action action )
+		{
+			Gtk.ToggleAction toggle_action = (Gtk.ToggleAction)action;
+
+			bool state =  toggle_action.get_active();
+
+			if ( window.view != null )
+			{
+				window.view.grid_visible = state;
+			}
+			prefs.grid_visible = state;
+		}
+
+
+		private void on_view_markup_toggled (Gtk.Action action )
+		{
+			Gtk.ToggleAction toggle_action = (Gtk.ToggleAction)action;
+
+			bool state =  toggle_action.get_active();
+
+			if ( window.view != null )
+			{
+				window.view.markup_visible = state;
+			}
+			prefs.markup_visible = state;
+		}
+
+
+		private void on_view_zoomin( Gtk.Action action )
+		{
+			window.view.zoom_in();
+		}
+
+
+		private void on_view_zoomout( Gtk.Action action )
+		{
+			window.view.zoom_out();
+		}
+
+
+		private void on_view_zoom1to1( Gtk.Action action )
+		{
+			window.view.zoom_1to1();
+		}
+
+
+		private void on_view_zoom_to_fit( Gtk.Action action )
+		{
+			window.view.zoom_to_fit();
+		}
+
+
+		private void on_objects_arrow_mode( Gtk.Action action )
+		{
+			window.view.arrow_mode();
+		}
+
+
+		private void on_objects_create_text( Gtk.Action action )
+		{
+			/*
+			if (window->view != NULL) {
+				gl_view_object_create_mode (GL_VIEW(window->view),
+				                            GL_LABEL_OBJECT_TEXT);
+			}
+			*/
+		}
+
+
+		private void on_objects_create_box( Gtk.Action action )
+		{
+			/*
+			if (window->view != NULL) {
+				gl_view_object_create_mode (GL_VIEW(window->view),
+				                            GL_LABEL_OBJECT_BOX);
+			}
+			*/
+		}
+
+
+		private void on_objects_create_line( Gtk.Action action )
+		{
+			/*
+			if (window->view != NULL) {
+				gl_view_object_create_mode (GL_VIEW(window->view),
+				                            GL_LABEL_OBJECT_LINE);
+			}
+			*/
+		}
+
+
+		private void on_objects_create_ellipse( Gtk.Action action )
+		{
+			/*
+			if (window->view != NULL) {
+				gl_view_object_create_mode (GL_VIEW(window->view),
+				                            GL_LABEL_OBJECT_ELLIPSE);
+			}
+			*/
+		}
+
+
+		private void on_objects_create_image( Gtk.Action action )
+		{
+			/*
+			if (window->view != NULL) {
+				gl_view_object_create_mode (GL_VIEW(window->view),
+				                            GL_LABEL_OBJECT_IMAGE);
+			}
+			*/
+		}
+
+
+		private void on_objects_create_barcode( Gtk.Action action )
+		{
+			/*
+			if (window->view != NULL) {
+				gl_view_object_create_mode (GL_VIEW(window->view),
+				                            GL_LABEL_OBJECT_BARCODE);
+			}
+			*/
+		}
+
+
+		private void on_objects_raise( Gtk.Action action )
+		{
+			window.view.label.raise_selection_to_top();
+		}
+
+
+		private void on_objects_lower( Gtk.Action action )
+		{
+			window.view.label.lower_selection_to_bottom();
+		}
+
+
+		private void on_objects_rotate_left( Gtk.Action action )
+		{
+			window.view.label.rotate_selection_left();
+		}
+
+
+		private void on_objects_rotate_right( Gtk.Action action )
+		{
+			window.view.label.rotate_selection_right();
+		}
+
+
+		private void on_objects_flip_horiz( Gtk.Action action )
+		{
+			window.view.label.flip_selection_horiz();
+		}
+
+
+		private void on_objects_flip_vert( Gtk.Action action )
+		{
+			window.view.label.flip_selection_vert();
+		}
+
+
+		private void on_objects_align_left( Gtk.Action action )
+		{
+			window.view.label.align_selection_left();
+		}
+
+
+		private void on_objects_align_right( Gtk.Action action )
+		{
+			window.view.label.align_selection_right();
+		}
+
+
+		private void on_objects_align_hcenter( Gtk.Action action )
+		{
+			window.view.label.align_selection_hcenter();
+		}
+
+
+		private void on_objects_align_top( Gtk.Action action )
+		{
+			window.view.label.align_selection_top();
+		}
+
+
+		private void on_objects_align_bottom( Gtk.Action action )
+		{
+			window.view.label.align_selection_bottom();
+		}
+
+
+		private void on_objects_align_vcenter( Gtk.Action action )
+		{
+			window.view.label.align_selection_vcenter();
+		}
+
+
+		private void on_objects_center_horiz( Gtk.Action action )
+		{
+			window.view.label.center_selection_horiz();
+		}
+
+
+		private void on_objects_center_vert( Gtk.Action action )
+		{
+			window.view.label.center_selection_vert();
+		}
+
+
+		private void on_objects_merge_properties( Gtk.Action action )
+		{
+			/*
+			if (window->merge_dialog) {
+
+				gtk_window_present (GTK_WINDOW(window->merge_dialog));
+				gtk_window_set_transient_for (GTK_WINDOW (window->merge_dialog),
+				                              GTK_WINDOW (window ));
+
+			} else {
+
+				window->merge_dialog =
+					g_object_ref (
+						gl_merge_properties_dialog_new (GL_VIEW(window->view)->label,
+						                                GTK_WINDOW(window )) );
+
+				g_signal_connect (G_OBJECT(window->merge_dialog), "destroy",
+				                  G_CALLBACK (gtk_widget_destroyed),
+				                  &window->merge_dialog);
+
+				gtk_widget_show (GTK_WIDGET (window->merge_dialog));
+
+			}
+			*/
+		}
+
+
+		private void on_help_contents( Gtk.Action action )
+		{
+			Help.display_contents( window );
+		}
+
+
+		private void on_help_about( Gtk.Action action )
+		{
+			Help.display_about_dialog( window );
+		}
+
+	}
+
+}
+
+
diff --git a/glabels/units_util.vala b/glabels/units_util.vala
new file mode 100644
index 0000000..36edb13
--- /dev/null
+++ b/glabels/units_util.vala
@@ -0,0 +1,96 @@
+/*  units_util.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	namespace UnitsUtil
+	{
+
+		/**
+		 * Get step size for desired units.
+		 */
+		public double get_step_size( Units units )
+		{
+
+			switch (units.id)
+			{
+			case "pt":
+                return 0.1;     /* points */
+			case "in":
+                return 0.001;   /* inches */
+			case "mm":
+                return 0.1;     /* mm */
+			default:
+                warning( "Illegal units" );    /* Should not happen */
+                return 1.0;
+			}
+		}
+
+
+		/**
+		 * Get precision for desired units.
+		 */
+		public int get_precision( Units units )
+		{
+
+			switch (units.id)
+			{
+			case "pt":
+                return 1;       /* points */
+			case "in":
+                return 3;       /* inches */
+			case "mm":
+                return 1;       /* mm */
+			default:
+                warning( "Illegal units" );    /* Should not happen */
+                return 1;
+			}
+		}
+
+
+		/**
+		 * Get grid size for desired units.
+		 */
+		public double get_grid_size( Units units )
+		{
+
+			switch (units.id)
+			{
+			case "pt":
+				return 10.0;
+			case "in":
+				return 0.125 * units.points_per_unit;
+			case "mm":
+				return 5     * units.points_per_unit;
+			default:
+				warning( "Illegal units" );    /* Should not happen */
+				return 10;
+			}
+		}
+
+
+	}
+
+}
diff --git a/glabels/view.vala b/glabels/view.vala
new file mode 100644
index 0000000..2bc3ac9
--- /dev/null
+++ b/glabels/view.vala
@@ -0,0 +1,1232 @@
+/*  view.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	public class View : Gtk.ScrolledWindow
+	{
+		private const Color BG_COLOR          = { 0.8,   0.8,   0.8,   1.0 };
+		private const Color SHADOW_COLOR      = { 0.2,   0.2,   0.2,   1.0 };
+		private const Color PAPER_COLOR       = { 1.0,   1.0,   1.0,   1.0 };
+		private const Color GRID_COLOR        = { 0.753, 0.753, 0.753, 1.0 };
+		private const Color MARKUP_COLOR      = { 0.94,  0.39,  0.39,  1.0 };
+		private const Color OUTLINE_COLOR     = { 0,     0,     0,     1.0 };
+		private const Color SELECT_LINE_COLOR = { 0,     0,     1,     0.5 };
+		private const Color SELECT_FILL_COLOR = { 0.75,  0.75,  1,     0.5 };
+
+		private const double POINTS_PER_MM = 2.8346456692;
+
+		private const double GRID_LINE_WIDTH_PIXELS   = 1;
+		private const double MARKUP_LINE_WIDTH_PIXELS = 1;
+		private const double OUTLINE_WIDTH_PIXELS     = 1;
+		private const double SELECT_LINE_WIDTH_PIXELS = 3;
+
+		private const int ZOOMTOFIT_PAD = 16;
+		private const int SHADOW_OFFSET_PIXELS = ZOOMTOFIT_PAD/4;
+
+		private const double zooms[] = { 8, 6, 4, 3, 2, 1.5, 1, 0.75, 0.67, 0.50, 0.33, 0.25, 0.15, 0.10 };
+
+
+		private enum State { IDLE, ARROW_SELECT_REGION, ARROW_MOVE, ARROW_RESIZE, CREATE_DRAG }
+
+		private enum CreateType { BOX, ELLIPSE, LINE, IMAGE, TEXT, BARCODE }
+
+
+		public signal void context_menu_activate( uint event_button, uint event_time );
+		public signal void zoom_changed( double zoom );
+		public signal void pointer_moved( double x, double y );
+		public signal void pointer_exit();
+		public signal void mode_changed();
+
+
+		private Prefs        prefs;
+
+		private Gtk.Layout   canvas;
+
+		private double       grid_spacing;
+		private bool         zoom_to_fit_flag;
+		private double       home_scale;
+
+		private bool         update_scheduled_flag;
+		private double       x0;
+		private double       y0;
+		private double       scale;
+
+		private bool         in_object_create_mode;
+
+		private State        state;
+
+		/* ARROW_SELECT_REGION state */
+		private bool         select_region_visible;
+		private LabelRegion  select_region;
+
+		/* ARROW_MOVE state */
+		private double       move_last_x;
+		private double       move_last_y;
+
+		/* ARROW_RESIZE state */
+		private LabelObject? resize_object;
+		private Handle?      resize_handle;
+		private bool         resize_honor_aspect;
+
+		/* CREATE_DRAG state */
+		private CreateType   create_object_type;
+		private LabelObject? create_object;
+
+
+
+		public Label  label        { get; private set; }
+
+		public double zoom         { get; private set; }
+
+		private bool _markup_visible;
+		public bool markup_visible
+		{
+			get
+			{
+				return _markup_visible;
+			}
+
+			set
+			{
+				_markup_visible = value;
+				update();
+			}
+		}
+
+		private bool _grid_visible;
+		public bool grid_visible
+		{
+			get
+			{
+				return _grid_visible;
+			}
+
+			set
+			{
+				_grid_visible = value;
+				update();
+			}
+		}
+
+
+		public View( Label label )
+		{
+			this.label = label;
+
+			prefs = new Prefs();
+
+			canvas = new Gtk.Layout( null, null );
+			this.set_policy( Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC );
+			this.add( canvas );
+
+			canvas.modify_bg( Gtk.StateType.NORMAL, BG_COLOR.to_gdk_color() );
+			canvas.set_can_focus( true );
+
+			canvas.add_events( Gdk.EventMask.FOCUS_CHANGE_MASK   |
+			                   Gdk.EventMask.ENTER_NOTIFY_MASK   |
+			                   Gdk.EventMask.LEAVE_NOTIFY_MASK   |
+			                   Gdk.EventMask.POINTER_MOTION_MASK |
+			                   Gdk.EventMask.BUTTON_PRESS_MASK   |
+			                   Gdk.EventMask.BUTTON_RELEASE_MASK |
+			                   Gdk.EventMask.KEY_PRESS_MASK );
+
+			grid_visible          = true;
+			grid_spacing          = UnitsUtil.get_grid_size( prefs.units );
+			markup_visible        = true;
+			in_object_create_mode = false;
+			zoom                  = 1.0;
+			zoom_to_fit_flag      = false;
+			home_scale            = determine_home_scale();
+
+			prefs.changed.connect( on_prefs_changed );
+			canvas.draw.connect( on_draw );
+			canvas.realize.connect( on_realize );
+			canvas.size_allocate.connect( on_size_allocate );
+			canvas.screen_changed.connect( on_screen_changed );
+			canvas.focus_in_event.connect( on_focus_in_event );
+			canvas.focus_out_event.connect( on_focus_out_event );
+			canvas.enter_notify_event.connect( on_enter_notify_event );
+			canvas.leave_notify_event.connect( on_leave_notify_event );
+			canvas.motion_notify_event.connect( on_motion_notify_event );
+			canvas.button_press_event.connect( on_button_press_event );
+			canvas.button_release_event.connect( on_button_release_event );
+			canvas.key_press_event.connect( on_key_press_event );
+			label.changed.connect( on_label_changed );
+			label.selection_changed.connect( on_label_changed );
+			label.size_changed.connect( on_label_size_changed );
+		}
+
+
+		private double determine_home_scale()
+		{
+			if ( !canvas.has_screen() )
+			{
+				return 1.0;
+			}
+
+			Gdk.Screen screen = canvas.get_screen();
+
+			double screen_width_pixels  = screen.get_width();
+			double screen_width_mm      = screen.get_width_mm();
+			double screen_height_pixels = screen.get_height();
+			double screen_height_mm     = screen.get_width_mm();
+
+			double x_pixels_per_mm = screen_width_pixels / screen_width_mm;
+			double y_pixels_per_mm = screen_height_pixels / screen_height_mm;
+
+			double scale = ( x_pixels_per_mm + y_pixels_per_mm ) / (2 * POINTS_PER_MM);
+
+			/* Make sure scale is somewhat sane. */
+			if ( (scale < 0.25) || (scale > 4.0) )
+			{
+				scale = 1.0;
+			}
+
+			return scale;
+		}
+
+
+		/**
+		 * Zoom in one "notch"
+		 */
+		public void zoom_in()
+		{
+			/* find closest standard zoom. */
+
+			/* start with 2nd largest scale */
+			int i_min = 1;
+			double dist_min = Math.fabs( zooms[1] - zoom );
+
+			for ( int i = 2; i < zooms.length; i++ )
+			{
+				double dist = Math.fabs( zooms[i] - zoom );
+				if ( dist < dist_min )
+				{
+					i_min = i;
+					dist_min = dist;
+				}
+			}
+
+			/* zoom in one notch */
+			set_zoom_real( zooms[i_min-1], false );
+		}
+
+
+		/**
+		 * Zoom out one "notch"
+		 */
+		public void zoom_out()
+		{
+			/* find closest standard zoom. */
+
+			/* start with largest scale, end on 2nd smallest */
+			int i_min = 0;
+			double dist_min = Math.fabs( zooms[0] - zoom );
+
+			for ( int i = 1; i < (zooms.length-1); i++ )
+			{
+				double dist = Math.fabs( zooms[i] - zoom );
+				if ( dist < dist_min )
+				{
+					i_min = i;
+					dist_min = dist;
+				}
+			}
+
+			/* zoom in one notch */
+			set_zoom_real( zooms[i_min+1], false );
+		}
+
+
+		/**
+		 * Set zoom to 1 to 1
+		 */
+		public void zoom_1to1()
+		{
+			set_zoom_real( 1.0, false );
+		}
+
+
+		/**
+		 * Zoom to fit
+		 */
+		public void zoom_to_fit()
+		{
+			if ( !has_screen() )
+			{
+				/* Delay until realized. */
+				zoom_to_fit_flag = true;
+				return;
+			}
+
+			int w_view = get_allocated_width();
+			int h_view = get_allocated_height();
+
+			double w_label, h_label;
+			label.get_size( out w_label, out h_label );
+
+			/* Calculate best scale */
+			double x_scale = (double)( w_view - ZOOMTOFIT_PAD ) / w_label;
+			double y_scale = (double)( h_view - ZOOMTOFIT_PAD ) / h_label;
+			double scale = double.min( x_scale, y_scale );
+
+			/* Limit */
+			scale = double.min( scale, zooms[0]*home_scale );
+			scale = double.max( scale, zooms[zooms.length - 1]*home_scale );
+
+			set_zoom_real( scale/home_scale, true );
+		}
+
+
+		private void set_zoom_real( double zoom,
+		                            bool   zoom_to_fit_flag )
+		{
+			/* Limit, if needed. */
+			zoom = double.min( zoom, zooms[0] );
+			zoom = double.max( zoom, zooms[ zooms.length-1 ] );
+
+			if ( this.zoom != zoom )
+			{
+				this.zoom             = zoom;
+				this.zoom_to_fit_flag = zoom_to_fit_flag;
+
+				update();
+
+				zoom_changed( zoom );
+			}
+		}
+
+
+		public bool is_zoom_max()
+		{
+			return ( zoom >= zooms[0] );
+		}
+
+
+		public bool is_zoom_min()
+		{
+			return ( zoom <= zooms[zooms.length-1] );
+		}
+
+
+		public void arrow_mode()
+		{
+			Gdk.Window window = canvas.get_window();
+
+			Gdk.Cursor cursor = new Gdk.Cursor( Gdk.CursorType.LEFT_PTR );
+			window.set_cursor( cursor );
+
+			in_object_create_mode = false;
+			state = State.IDLE;
+		}
+
+
+		private void on_prefs_changed()
+		{
+			grid_spacing = UnitsUtil.get_grid_size( prefs.units );
+			update();
+		}
+
+
+		private void update()
+		{
+			var window = canvas.get_window();
+			if (null == window)
+			{
+				return;
+			}
+
+			if ( !update_scheduled_flag )
+			{
+				update_scheduled_flag = true;
+
+				Gdk.Rectangle rect = Gdk.Rectangle();
+
+				rect.x      = 0;
+				rect.y      = 0;
+				rect.width  = canvas.get_allocated_width();
+				rect.height = canvas.get_allocated_height();
+
+				window.invalidate_rect( rect, true );
+			}
+		}
+
+
+		private bool on_draw( Cairo.Context cr )
+		{
+			update_scheduled_flag = false;
+
+			Gdk.Window bin_window = canvas.get_bin_window();
+			Cairo.Context bin_cr = Gdk.cairo_create( bin_window );
+
+			draw_layers( bin_window, bin_cr );
+
+			return false;
+		}
+
+
+		private void draw_layers( Gdk.Window    window,
+		                          Cairo.Context cr )
+		{
+			this.scale = zoom * home_scale;
+
+			double w, h;
+			label.get_size( out w, out h );
+
+			canvas.set_size( (int)(w*scale + 8), (int)(h*scale + 8) );
+
+			int canvas_w = window.get_width();
+			int canvas_h = window.get_height();
+
+			this.x0 = (canvas_w/scale - w) / 2;
+			this.y0 = (canvas_h/scale - h) / 2;
+
+			cr.save();
+
+			cr.scale( scale, scale );
+			cr.translate( x0, y0 );
+
+			draw_bg_layer( cr );
+			draw_grid_layer( cr );
+			draw_markup_layer( cr );
+			draw_objects_layer( cr );
+			draw_fg_layer( cr );
+			draw_highlight_layer( cr );
+			draw_select_region_layer( cr );
+
+			cr.restore();
+		}
+
+
+		private void set_frame_path( Cairo.Context cr,
+		                             TemplateFrame frame )
+		{
+			cr.save();
+
+			if (label.rotate)
+			{
+				double w, h;
+				frame.get_size( out w, out h );
+
+				/* Canvas coordinates are relative to content, so we must rotate the frame. */
+				cr.rotate( -Math.PI / 2 );
+				cr.translate( -w, 0 );
+			}
+
+			frame.cairo_path( cr, false );
+
+			cr.restore();
+		}
+
+		private void draw_bg_layer( Cairo.Context cr )
+		{
+			TemplateFrame frame = label.template.frames.first().data;
+
+			double w, h;
+			frame.get_size( out w, out h );
+
+			cr.save();
+			cr.translate( SHADOW_OFFSET_PIXELS/scale, SHADOW_OFFSET_PIXELS/scale );
+
+			set_frame_path( cr, frame );
+			cr.set_source_rgb( SHADOW_COLOR.r, SHADOW_COLOR.g, SHADOW_COLOR.b );
+			cr.set_fill_rule( Cairo.FillRule.EVEN_ODD );
+			cr.fill();
+			cr.restore();
+
+			cr.save();
+			set_frame_path( cr, frame );
+			cr.set_source_rgb( PAPER_COLOR.r, PAPER_COLOR.g, PAPER_COLOR.b );
+			cr.set_fill_rule( Cairo.FillRule.EVEN_ODD );
+			cr.fill();
+			cr.restore();
+		}
+
+
+		private void draw_grid_layer( Cairo.Context cr )
+		{
+			if ( grid_visible )
+			{
+				TemplateFrame frame = label.template.frames.first().data;
+
+				double w, h;
+				label.get_size( out w, out h );
+        
+				double x0, y0;
+				if ( frame is TemplateFrameRect )
+				{
+					x0 = grid_spacing;
+					y0 = grid_spacing;
+				}
+				else
+				{
+					/* round labels, adjust grid to line up with center of label. */
+					x0 = Math.fmod( w/2, grid_spacing );
+					y0 = Math.fmod( h/2, grid_spacing );
+				}
+
+				cr.save();
+
+				set_frame_path( cr, frame );
+				cr.clip();
+
+				cr.set_antialias( Cairo.Antialias.NONE );
+				cr.set_line_width( GRID_LINE_WIDTH_PIXELS/scale );
+				cr.set_source_rgb( GRID_COLOR.r, GRID_COLOR.g, GRID_COLOR.b );
+
+				for ( double x = x0; x < w; x += grid_spacing )
+				{
+					cr.move_to( x, 0 );
+					cr.line_to( x, h );
+					cr.stroke();
+				}
+
+				for ( double y = y0; y < h; y += grid_spacing )
+				{
+					cr.move_to( 0, y );
+					cr.line_to( w, y );
+					cr.stroke();
+				}
+
+				cr.restore();
+			}
+		}
+
+
+		private void draw_markup_layer( Cairo.Context cr )
+		{
+			if ( markup_visible )
+			{
+				TemplateFrame frame = label.template.frames.first().data;
+
+				cr.save();
+
+				if (label.rotate)
+				{
+					double w, h;
+					frame.get_size( out w, out h );
+
+					/* Markup is relative to the normal orientation of label. */
+					cr.rotate( -Math.PI / 2 );
+					cr.translate( -w, 0 );
+				}
+
+				cr.set_line_width( MARKUP_LINE_WIDTH_PIXELS / scale );
+				cr.set_source_rgb( MARKUP_COLOR.r, MARKUP_COLOR.g, MARKUP_COLOR.b );
+
+				foreach ( TemplateMarkup markup in frame.markups )
+				{
+					markup.cairo_path( cr, frame );
+					cr.stroke();
+				}
+
+				cr.restore();
+			}
+		}
+
+
+		private void draw_objects_layer( Cairo.Context cr )
+		{
+			label.draw( cr, true, null );
+		}
+
+
+		private void draw_fg_layer( Cairo.Context cr )
+		{
+			TemplateFrame frame = label.template.frames.first().data;
+
+			set_frame_path( cr, frame );
+
+			cr.set_line_width( OUTLINE_WIDTH_PIXELS/scale );
+			cr.set_source_rgb( OUTLINE_COLOR.r, OUTLINE_COLOR.g, OUTLINE_COLOR.b );
+			cr.stroke();
+		}
+
+
+		private void draw_highlight_layer( Cairo.Context cr )
+		{
+			cr.save();
+			cr.set_antialias( Cairo.Antialias.NONE );
+
+			foreach ( LabelObject object in label.object_list )
+			{
+				if ( object.is_selected() )
+				{
+					object.draw_selection_highlight( cr );
+				}
+			}
+
+			cr.restore();
+		}
+
+
+		private void draw_select_region_layer( Cairo.Context cr )
+		{
+			if ( select_region_visible )
+			{
+				double x1 = double.min( select_region.x1, select_region.x2 );
+				double y1 = double.min( select_region.y1, select_region.y2 );
+				double w  = Math.fabs( select_region.x2 - select_region.x1 );
+				double h  = Math.fabs( select_region.y2 - select_region.y1 );
+
+				cr.save();
+
+				cr.set_antialias( Cairo.Antialias.NONE );
+
+				cr.rectangle( x1, y1, w, h );
+
+				cr.set_source_rgba( SELECT_FILL_COLOR.r, SELECT_FILL_COLOR.g, SELECT_FILL_COLOR.b,
+				                    SELECT_FILL_COLOR.a );
+				cr.fill_preserve();
+
+				cr.set_line_width( SELECT_LINE_WIDTH_PIXELS/scale );
+				cr.set_source_rgba( SELECT_LINE_COLOR.r, SELECT_LINE_COLOR.g, SELECT_LINE_COLOR.b,
+				                    SELECT_LINE_COLOR.a );
+				cr.stroke();
+
+				cr.restore();
+			}
+		}
+
+
+		private void on_realize()
+		{
+			if ( zoom_to_fit_flag )
+			{
+				/* Maintain best fit zoom */
+				zoom_to_fit();
+			}
+		}
+
+
+		private void on_size_allocate( Gtk.Allocation allocation )
+		{
+			Gtk.Adjustment hadjustment = canvas.get_hadjustment();
+			Gtk.Adjustment vadjustment = canvas.get_vadjustment();
+
+			hadjustment.set_page_size( allocation.width );
+			hadjustment.set_page_increment( allocation.width / 2 );
+
+			vadjustment.set_page_size( allocation.height );
+			vadjustment.set_page_increment( allocation.height / 2 );
+
+			hadjustment.changed();
+			vadjustment.changed();
+
+			if ( zoom_to_fit_flag )
+			{
+				/* Maintain best fit zoom */
+				zoom_to_fit();
+			}
+		}
+
+
+		private void on_screen_changed()
+		{
+			if ( canvas.has_screen() )
+			{
+
+				home_scale = determine_home_scale();
+
+				if ( zoom_to_fit_flag )
+				{
+					/* Maintain best fit zoom */
+					zoom_to_fit();
+				}
+			}
+		}
+
+
+		private void on_label_changed()
+		{
+			update();
+		}
+
+
+		private void on_label_size_changed()
+		{
+			Gtk.Adjustment hadjustment = canvas.get_hadjustment();
+			Gtk.Adjustment vadjustment = canvas.get_vadjustment();
+
+			hadjustment.changed();
+			vadjustment.changed();
+
+			update();
+		}
+
+
+		private bool on_focus_in_event( Gdk.EventFocus event )
+		{
+			return false;
+		}
+
+
+		private bool on_focus_out_event( Gdk.EventFocus event )
+		{
+			return false;
+		}
+
+
+		private bool on_enter_notify_event( Gdk.EventCrossing event )
+		{
+			return false;
+		}
+
+
+		private bool on_leave_notify_event( Gdk.EventCrossing event )
+		{
+			pointer_exit();
+			return false;
+		}
+
+
+		private bool on_motion_notify_event( Gdk.EventMotion event )
+		{
+			bool return_value = false;
+
+			Gdk.Window bin_window = canvas.get_bin_window();
+			Gdk.Window window = canvas.get_window();
+
+			Cairo.Context cr = Gdk.cairo_create( bin_window );
+
+			/*
+			 * Translate to label coordinates
+			 */
+			cr.scale( scale, scale );
+			cr.translate( x0, y0 );
+
+			double x = event.x;
+			double y = event.y;
+			cr.device_to_user( ref x, ref y );
+
+			/*
+			 * Emit signal regardless of mode
+			 */
+			pointer_moved( x, y );
+
+			/*
+			 * Handle event as appropriate for mode
+			 */
+			if ( !in_object_create_mode )
+			{
+				switch (state)
+				{
+
+				case State.IDLE:
+					Gdk.Cursor cursor;
+					Handle? handle;
+					if ( label.is_selection_atomic() &&
+					     (handle = label.handle_at( cr, event.x, event.y )) != null )
+					{
+					     cursor = new Gdk.Cursor( Gdk.CursorType.CROSSHAIR );
+					}
+					else if ( label.object_at( cr, event.x, event.y) != null )
+					{
+						cursor = new Gdk.Cursor( Gdk.CursorType.FLEUR );
+					}
+					else
+					{
+						cursor = new Gdk.Cursor( Gdk.CursorType.LEFT_PTR );
+					}
+					window.set_cursor( cursor );
+					break;
+
+				case State.ARROW_SELECT_REGION:
+					select_region.x2 = x;
+					select_region.y2 = y;
+					update();
+					break;
+
+				case State.ARROW_MOVE:
+					label.move_selection( (x - move_last_x), (y - move_last_y) );
+					move_last_x = x;
+					move_last_y = y;
+					break;
+
+				case State.ARROW_RESIZE:
+					handle_resize_motion( cr, event.x, event.y );
+					break;
+
+				default:
+					warning( "Invalid arrow state." );      /*Should not happen!*/
+					break;
+
+				}
+				return_value = true;
+
+			}
+			else
+			{
+
+				if ( state != State.IDLE )
+				{
+					switch ( create_object_type )
+					{
+					case CreateType.BOX:
+						/* TODO */
+						break;
+					case CreateType.ELLIPSE:
+						/* TODO */
+						break;
+					case CreateType.LINE: 
+						/* TODO */
+						break;
+					case CreateType.IMAGE:
+						/* TODO */
+						break;
+					case CreateType.TEXT:
+						/* TODO */
+						break;
+					case CreateType.BARCODE:
+						/* TODO */
+						break;
+					default:
+						warning( "Invalid create type." );   /* Should not happen! */
+						break;
+					}
+				}
+
+			}
+
+			return return_value;
+		}
+
+
+		private bool on_button_press_event( Gdk.EventButton event )
+		{
+			bool return_value = false;
+
+			canvas.grab_focus();
+
+			Gdk.Window bin_window = canvas.get_bin_window();
+
+			Cairo.Context cr = Gdk.cairo_create( bin_window );
+
+			/*
+			 * Translate to label coordinates
+			 */
+			cr.scale( scale, scale );
+			cr.translate( x0, y0 );
+
+			double x = event.x;
+			double y = event.y;
+			cr.device_to_user( ref x, ref y );
+
+			switch (event.button)
+			{
+
+			case 1:
+				/*
+				 * Handle event as appropriate for mode
+				 */
+				if ( !in_object_create_mode )
+				{
+					LabelObject object;
+					Handle? handle = null;
+					if ( label.is_selection_atomic() &&
+					     (handle = label.handle_at( cr, event.x, event.y )) != null )
+					{
+						resize_object = handle.owner;
+						resize_handle = handle;
+						resize_honor_aspect = (event.state & Gdk.ModifierType.CONTROL_MASK) != 0;
+
+						state = State.ARROW_RESIZE;
+					}
+					else if ( (object = label.object_at( cr, event.x, event.y)) != null )
+					{
+						if ( (event.state & Gdk.ModifierType.CONTROL_MASK) != 0 )
+						{
+							if ( object.is_selected() )
+							{
+								/* Un-selecting a selected item */
+								label.unselect_object( object );
+							}
+							else
+							{
+								/* Add to current selection */
+								label.select_object( object );
+							}
+						}
+						else
+						{
+							if ( !object.is_selected() )
+							{
+								/* remove any selections before adding */
+								label.unselect_all();
+								/* Add to current selection */
+								label.select_object( object );
+							}
+						}
+
+						move_last_x = x;
+						move_last_y = y;
+
+						state = State.ARROW_MOVE;
+					}
+					else
+					{
+						if ( (event.state & Gdk.ModifierType.CONTROL_MASK) == 0 )
+						{
+							label.unselect_all();
+						}
+
+						select_region_visible = true;
+						select_region.x1 = x;
+						select_region.y1 = y;
+						select_region.x2 = x;
+						select_region.y2 = y;
+
+						state = State.ARROW_SELECT_REGION;
+					}
+
+					return_value = true;
+				}
+				else
+				{
+
+					if ( state != State.IDLE )
+					{
+						switch ( create_object_type )
+						{
+						case CreateType.BOX:
+							/* TODO */
+							break;
+						case CreateType.ELLIPSE:
+							/* TODO */
+							break;
+						case CreateType.LINE: 
+							/* TODO */
+							break;
+						case CreateType.IMAGE:
+							/* TODO */
+							break;
+						case CreateType.TEXT:
+							/* TODO */
+							break;
+						case CreateType.BARCODE:
+							/* TODO */
+							break;
+						default:
+							warning( "Invalid create type." );   /* Should not happen! */
+							break;
+						}
+
+						state = State.CREATE_DRAG;
+
+						return_value = true;
+					}
+
+				}
+				event.device.grab( bin_window, Gdk.GrabOwnership.APPLICATION, false,
+				                   (Gdk.EventMask.BUTTON1_MOTION_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK),
+				                   null, event.time );
+				break;
+
+			case 3:
+				context_menu_activate( event.button, event.time );
+				return_value = true;
+				break;
+
+			default:
+				break;
+
+			}
+
+			return return_value;
+		}
+
+
+		private bool on_button_release_event( Gdk.EventButton event )
+		{
+			bool return_value = false;
+
+			canvas.grab_focus();
+
+			Gdk.Window bin_window = canvas.get_bin_window();
+			Gdk.Window window = canvas.get_window();
+
+			Cairo.Context cr = Gdk.cairo_create( bin_window );
+
+			/*
+			 * Translate to label coordinates
+			 */
+			cr.scale( scale, scale );
+			cr.translate( x0, y0 );
+
+			double x = event.x;
+			double y = event.y;
+			cr.device_to_user( ref x, ref y );
+
+			switch (event.button)
+			{
+
+			case 1:
+				event.device.ungrab( event.time );
+				/*
+				 * Handle event as appropriate for mode
+				 */
+				if ( !in_object_create_mode )
+				{
+
+					switch (state)
+					{
+					case State.ARROW_RESIZE:
+						resize_object = null;
+						state = State.IDLE;
+						break;
+
+					case State.ARROW_SELECT_REGION:
+						select_region_visible = false;
+						select_region.x2 = x;
+						select_region.y2 = y;
+						label.select_region( select_region );
+						update();
+						state = State.IDLE;
+						break;
+
+					default:
+						state = State.IDLE;
+						break;
+					}
+
+					return_value = true;
+
+				}
+				else
+				{
+
+					switch ( create_object_type )
+					{
+					case CreateType.BOX:
+						/* TODO */
+						break;
+					case CreateType.ELLIPSE:
+						/* TODO */
+						break;
+					case CreateType.LINE: 
+						/* TODO */
+						break;
+					case CreateType.IMAGE:
+						/* TODO */
+						break;
+					case CreateType.TEXT:
+						/* TODO */
+						break;
+					case CreateType.BARCODE:
+						/* TODO */
+						break;
+					default:
+						warning( "Invalid create type." );   /* Should not happen! */
+						break;
+					}
+
+					Gdk.Cursor cursor = new Gdk.Cursor( Gdk.CursorType.LEFT_PTR );
+					window.set_cursor( cursor );
+
+					label.select_object( create_object );
+
+					in_object_create_mode = false;
+					state = State.IDLE;
+					
+				}
+				break;
+
+			default:
+				break;
+
+			}
+
+			return return_value;
+		}
+
+
+		private bool on_key_press_event( Gdk.EventKey event )
+		{
+			if ( !in_object_create_mode && (state == State.IDLE) )
+			{
+				switch (event.keyval)
+				{
+
+				case Gdk.Key.Left:
+				case Gdk.Key.KP_Left:
+					label.move_selection( (-1 / zoom), 0 );
+					break;
+
+				case Gdk.Key.Up:
+				case Gdk.Key.KP_Up:
+					label.move_selection( 0, (-1 / zoom) );
+					break;
+
+				case Gdk.Key.Right:
+				case Gdk.Key.KP_Right:
+					label.move_selection( (1 / zoom), 0 );
+					break;
+
+				case Gdk.Key.Down:
+				case Gdk.Key.KP_Down:
+					label.move_selection( 0, (1 / zoom) );
+					break;
+
+				case Gdk.Key.Delete:
+				case Gdk.Key.KP_Delete:
+					label.delete_selection();
+					Gdk.Window window = canvas.get_window();
+					Gdk.Cursor cursor = new Gdk.Cursor( Gdk.CursorType.LEFT_PTR );
+					window.set_cursor( cursor );
+					break;
+
+				default:
+					return false;
+
+				}
+			}
+
+			return true;
+		}
+
+
+		private void handle_resize_motion( Cairo.Context cr,
+		                                   double        x_pixels,
+		                                   double        y_pixels )
+		{
+			cr.save();
+
+			/*
+			 * Change to item relative coordinates
+			 */
+			cr.translate( resize_object.x0, resize_object.y0 );
+			cr.transform( resize_object.matrix );
+
+			/*
+			 * Initialize origin and 2 corners in object relative coordinates.
+			 */
+			double x0 = 0.0;
+			double y0 = 0.0;
+
+			double x1 = 0.0;
+			double y1 = 0.0;
+
+			double x2 = resize_object.w;
+			double y2 = resize_object.h;
+
+			/*
+			 * Translate x,y into object relative coordinates.
+			 */
+			double x = x_pixels;
+			double y = y_pixels;
+			cr.device_to_user( ref x, ref y );
+
+			/*
+			 * Calculate new size
+			 */
+			double w, h;
+			if ( resize_handle is HandleNorthWest )
+			{
+				w = double.max( x2 - x, 0 );
+				h = double.max( y2 - y, 0 );
+			}
+			else if ( resize_handle is HandleNorth )
+			{
+				w = x2 - x1;
+				h = double.max( y2 - y, 0 );
+			}
+			else if ( resize_handle is HandleNorthEast )
+			{
+				w = double.max( x - x1, 0 );
+				h = double.max( y2 - y, 0 );
+			}
+			else if ( resize_handle is HandleEast )
+			{
+				w = double.max( x - x1, 0 );
+				h = y2 - y1;
+			}
+			else if ( resize_handle is HandleSouthEast )
+			{
+				w = double.max( x - x1, 0 );
+				h = double.max( y - y1, 0 );
+			}
+			else if ( resize_handle is HandleSouth )
+			{
+				w = x2 - x1;
+				h = double.max( y - y1, 0 );
+			}
+			else if ( resize_handle is HandleSouthWest )
+			{
+				w = double.max( x2 - x, 0 );
+				h = double.max( y - y1, 0 );
+			}
+			else if ( resize_handle is HandleWest )
+			{
+				w = double.max( x2 - x, 0 );
+				h = y2 - y1;
+			}
+			else
+			{
+				assert_not_reached();
+			}
+
+			/*
+			 * Set size
+			 */
+			if ( resize_honor_aspect )
+			{
+				resize_object.set_size_honor_aspect( w, h );
+			}
+			else
+			{
+				resize_object.set_size( w, h );
+			}
+
+			/*
+			 * Adjust origin, if needed.
+			 */
+			if ( resize_handle is HandleNorthWest )
+			{
+				x0 += x2 - resize_object.w;
+				y0 += y2 - resize_object.h;
+			}
+			else if ( (resize_handle is HandleNorth) || (resize_handle is HandleNorthEast) )
+			{
+				y0 += y2 - resize_object.h;
+			}
+			else if ( (resize_handle is HandleWest) || (resize_handle is HandleSouthWest) )
+			{
+				x0 += x2 - resize_object.w;
+			}
+
+			/*
+			 * Put new origin back into world coordinates and set.
+			 */
+			cr.user_to_device( ref x0, ref y0 );
+			cr.restore();
+			cr.device_to_user( ref x0, ref y0 );
+			resize_object.set_position( x0, y0 );
+		}
+
+
+	}
+
+}
diff --git a/glabels/window.vala b/glabels/window.vala
new file mode 100644
index 0000000..d73313e
--- /dev/null
+++ b/glabels/window.vala
@@ -0,0 +1,279 @@
+/*  window.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	public class Window : Gtk.Window
+	{
+		private const int DEFAULT_WINDOW_WIDTH  = 788;
+		private const int DEFAULT_WINDOW_HEIGHT = 600;
+
+		private const int ZOOM_INFO_WIDTH   =  75;
+		private const int CURSOR_INFO_WIDTH = 150;
+
+
+		public static unowned List<weak Window> window_list { get; private set; }
+
+
+		private Gtk.HBox      content_hbox;
+
+		public  View?         view { get; private set; }
+
+		public  Gtk.Statusbar statusbar { get; private set; }
+		private Gtk.Label     zoom_info_label;
+		private Gtk.Label     cursor_info_label;
+		public  uint          menu_tips_context_id { get; private set; }
+
+		private Gtk.Menu      context_menu;
+		private Gtk.Menu      empty_selection_context_menu;
+
+		private Prefs         prefs;
+		private Ui            ui;
+
+
+		public Window()
+		{
+			prefs = new Prefs();
+
+			Gtk.VBox vbox1 = new Gtk.VBox( false, 0 );
+			add( vbox1 );
+
+			ui = new Ui( this );
+			vbox1.pack_start( ui.get_widget( "/MenuBar" ), false, false, 0 );
+			vbox1.pack_start( ui.get_widget( "/MainToolBar" ), false, false, 0 );
+			vbox1.pack_start( ui.get_widget( "/DrawingToolBar" ), false, false, 0 );
+
+			content_hbox = new Gtk.HBox( false, 0 );
+			vbox1.pack_start( content_hbox, true, true, 0 );
+
+			Gtk.HBox status_hbox = new Gtk.HBox( false, 0 );
+			vbox1.pack_start( status_hbox, false, false, 0 );
+
+			statusbar = new Gtk.Statusbar();
+			status_hbox.pack_start( statusbar, true, true, 0 );
+
+			zoom_info_label = new Gtk.Label( null );
+			zoom_info_label.set_size_request( ZOOM_INFO_WIDTH, -1 );
+
+			Gtk.Frame zoom_info_frame = new Gtk.Frame( null );
+			zoom_info_frame.set_shadow_type( Gtk.ShadowType.IN );
+			zoom_info_frame.add( zoom_info_label );
+			zoom_info_frame.show_all();
+			status_hbox.pack_end( zoom_info_frame, false, false, 0 );
+
+			cursor_info_label = new Gtk.Label( null );
+			cursor_info_label.set_size_request( CURSOR_INFO_WIDTH, -1 );
+
+			Gtk.Frame cursor_info_frame = new Gtk.Frame( null );
+			cursor_info_frame.set_shadow_type( Gtk.ShadowType.IN );
+			cursor_info_frame.add( cursor_info_label );
+			cursor_info_frame.show_all();
+			status_hbox.pack_end( cursor_info_frame, false, false, 0 );
+
+			this.set_default_size( DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT );
+
+			this.delete_event.connect( on_delete_event );
+
+			menu_tips_context_id = statusbar.get_context_id( "menu_tips" );
+
+			context_menu = (Gtk.Menu)ui.get_widget( "/ContextMenu" );
+			empty_selection_context_menu = (Gtk.Menu)ui.get_widget( "/EmptySelectionContextMenu" );
+
+			window_list.append( this );
+		}
+
+
+		public Window.from_label( Label label )
+		{
+			this();
+			set_label( label );
+		}
+
+
+		public Window.from_file( string filename )
+		{
+			this();
+
+			string abs_filename = FileUtil.make_absolute( filename );
+			Label label = XmlLabel.open_file( abs_filename );
+
+			set_label( label );
+		}
+
+
+		~Window()
+		{
+			window_list.remove( this );
+
+			if ( window_list.length() == 0 )
+			{
+				Gtk.main_quit();
+			}
+		}
+
+
+		public bool is_empty()
+		{
+			return view == null;
+		}
+
+
+		public void set_label( Label label )
+		{
+			label.modified = false;
+			set_window_title( label );
+
+/*
+			MiniPreview mini_preview = new MiniPreview( 200, 200 );
+			mini_preview.set_label( label );
+			content_hbox.pack_start( mini_preview, true, true, 0 );
+*/
+
+			view = new View( label );
+			content_hbox.pack_start( view, true, true, 0 );
+			view.show_all();
+
+			view.zoom_to_fit();
+
+			view.grid_visible = prefs.grid_visible;
+			view.markup_visible = prefs.markup_visible;
+
+			ui.update_all( view );
+
+			string zoom_string = "%3.0f%%".printf( 100*view.zoom );
+			zoom_info_label.set_text( zoom_string );
+
+			label.name_changed.connect( on_name_changed );
+			label.modified_changed.connect( on_modified_changed );
+			label.selection_changed.connect( on_selection_changed );
+			label.changed.connect( on_label_changed );
+			view.context_menu_activate.connect( on_context_menu_activate );
+			view.zoom_changed.connect( on_zoom_changed );
+			view.pointer_moved.connect( on_pointer_moved );
+			view.pointer_exit.connect( on_pointer_exit );
+			this.set_focus.connect( on_set_focus );
+
+			/* TODO: clipboard changed. */
+			/* TODO: set copy/paste sensitivity. */
+		}
+
+
+		private void set_window_title( Label label )
+		{
+			string name = label.get_short_name();
+
+			if ( label.modified )
+			{
+				set_title( "%s %s - gLabels".printf( name, _("(modified)") ) );
+			}
+			else
+			{
+				set_title( "%s - gLabels".printf( name ) );
+			}
+		}
+
+
+		private bool on_delete_event()
+		{
+			File.close( this );
+
+			return true;
+		}
+
+
+		private void on_selection_changed()
+		{
+			ui.update_selection_verbs( view, true );
+		}
+
+
+		private void on_context_menu_activate( uint button, uint activate_time )
+		{
+			if ( view.label.is_selection_empty() )
+			{
+				empty_selection_context_menu.popup( null, null, null, button, activate_time );
+			}
+			else
+			{
+				context_menu.popup( null, null, null, button, activate_time );
+			}
+		}
+
+
+		private void on_zoom_changed()
+		{
+			zoom_info_label.set_text( "%3.0f%%".printf( 100*view.zoom ) );
+
+			ui.update_zoom_verbs( view );
+		}
+
+
+		private void on_pointer_moved( double x,
+		                               double y )
+		{
+			Units units = prefs.units;
+			int precision = UnitsUtil.get_precision( units );
+
+			cursor_info_label.set_text( "%.*f, %.*f".printf( precision, x*units.units_per_point,
+			                                                 precision, y*units.units_per_point ) );
+		}
+
+
+		private void on_pointer_exit()
+		{
+			cursor_info_label.set_text( "" );
+		}
+
+
+		private void on_name_changed()
+		{
+			set_window_title( view.label );
+		}
+
+
+		private void on_modified_changed()
+		{
+			set_window_title( view.label );
+
+			/* TODO: update modified verbs. */
+		}
+
+
+		private void on_label_changed()
+		{
+			/* TODO: update undo/redo verbs. */
+		}
+
+
+		private void on_set_focus()
+		{
+			/* TODO: set copy paste sensitivity. */
+		}
+
+
+	}
+
+}
+
diff --git a/glabels/xml_label.vala b/glabels/xml_label.vala
new file mode 100644
index 0000000..19d7dac
--- /dev/null
+++ b/glabels/xml_label.vala
@@ -0,0 +1,504 @@
+/*  xml_label.vala
+ *
+ *  Copyright (C) 2012  Jim Evins <evins snaught com>
+ *
+ *  This file is part of gLabels.
+ *
+ *  gLabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  gLabels 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 gLabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	namespace XmlLabel
+	{
+
+		public errordomain XmlError
+		{
+			OPEN_ERROR,
+			UNKNOWN_MEDIA,
+			PARSE_ERROR,
+			SAVE_ERROR
+		}
+
+		/* TODO: pull HUGE from libxml vapi file when available. */
+		const int MY_XML_PARSE_HUGE = 1 << 19;
+
+
+		public Label open_file( string utf8_filename ) throws XmlError
+		{
+
+			string filename;
+			try
+			{
+				filename = Filename.from_utf8( utf8_filename, -1, null, null );
+			}
+			catch ( ConvertError e )
+			{
+				throw new XmlError.OPEN_ERROR( "Utf8 filename conversion error." );
+			}
+
+			unowned Xml.Doc* doc = Xml.Parser.read_file( filename, null, MY_XML_PARSE_HUGE );
+			if ( doc == null )
+			{
+				throw new XmlError.PARSE_ERROR( "xmlReadFile error." );
+			}
+
+			/* TODO:
+			xmlXIncludeProcess (doc);
+			xmlReconciliateNs (doc, xmlDocGetRootElement (doc));
+			*/
+
+			Label label = parse_doc( doc );
+			label.filename = filename;
+
+			return label;
+		}
+
+
+		public Label open_buffer( string buffer ) throws XmlError
+		{
+
+			unowned Xml.Doc* doc = Xml.Parser.read_doc( buffer, null, null, MY_XML_PARSE_HUGE );
+			if ( doc == null )
+			{
+				throw new XmlError.PARSE_ERROR( "xmlReadDoc error." );
+			}
+
+			Label? label = parse_doc( doc );
+
+			return label;
+		}
+
+
+		private Label parse_doc( Xml.Doc doc ) throws XmlError
+		{
+			unowned Xml.Node* root = doc.get_root_element();
+			if ( (root == null) || (root->name == null) ) {
+				throw new XmlError.PARSE_ERROR( "No document root." );
+			}
+
+#if TODO
+			/* Try compatability mode 0.4 */
+			if (xmlSearchNsByHref (doc, root, (xmlChar *)COMPAT04_NAME_SPACE))
+			{
+				message( "Importing from glabels 0.4 format" );
+				return gl_xml_label_04_parse( root, status );
+			}
+
+			/* Test for current namespaces. */
+			if ( !xmlSearchNsByHref (doc, root, (xmlChar *)COMPAT20_NAME_SPACE) &&
+			     !xmlSearchNsByHref (doc, root, (xmlChar *)COMPAT22_NAME_SPACE) &&
+			     !xmlSearchNsByHref (doc, root, (xmlChar *)LGL_XML_NAME_SPACE) )
+			{
+				message( "Unknown glabels Namespace -- Using %s", XML_NAME_SPACE );
+			}
+#endif
+
+			if ( root->name != "Glabels-document" )
+			{
+				throw new XmlError.PARSE_ERROR( "Root node != \"Glabels-document\"." );
+			}
+
+			Label label = parse_glabels_document_node( root );
+			label.compression = doc.get_compress_mode();
+
+			return label;
+		}
+
+
+		private Label parse_glabels_document_node( Xml.Node node ) throws XmlError
+		{
+			Label label = new Label();
+
+			/* Pass 1, extract data nodes to pre-load cache. */
+			for ( unowned Xml.Node* child = node.children; child != null; child = child->next )
+			{
+				if ( child->name == "Data" )
+				{
+					/* TODO: xml_parse_data (child_node, label); */
+				}
+			}
+
+			/* Pass 2, now extract everything else. */
+			for ( unowned Xml.Node* child = node.children; child != null; child = child->next )
+			{
+				switch (child->name)
+				{
+
+				case "Template":
+					Template? template = XmlTemplate.parse_template_node( child );
+					if ( template == null )
+					{
+						throw new XmlError.PARSE_ERROR( "Bad template." );
+					}
+					label.template = template;
+					break;
+
+				case "Objects":
+					parse_objects_node( child, label );
+					break;
+
+				case "Merge":
+					parse_merge_node( child, label );
+					break;
+
+				case "Data":
+					/* Handled in pass 1. */
+					break;
+
+				default:
+					if ( child->is_text() == 0 )
+					{
+						message( "Unexpected %s child: \"%s\"", node.name, child->name );
+					}
+					break;
+				}
+			}
+
+			return label;
+		}
+
+
+		private void parse_objects_node( Xml.Node node,
+		                                 Label    label )
+		{
+			label.rotate = XmlUtil.get_prop_bool( node, "rotate", false );
+
+			for ( unowned Xml.Node* child = node.children; child != null; child = child->next )
+			{
+				switch (child->name)
+				{
+
+				case "Object-text":
+					/* TODO. */
+					break;
+
+				case "Object-box":
+					parse_object_box_node( child, label );
+					break;
+
+				case "Object-ellipse":
+					/* TODO. */
+					break;
+
+				case "Object-line":
+					/* TODO. */
+					break;
+
+				case "Object-image":
+					/* TODO. */
+					break;
+
+				case "Object-barcode":
+					/* TODO. */
+					break;
+
+				default:
+					if ( child->is_text() == 0 )
+					{
+						message( "Unexpected %s child: \"%s\"", node.name, child->name );
+					}
+					break;
+				}
+			}
+
+		}
+
+
+		private void parse_object_box_node( Xml.Node node,
+		                                    Label    label )
+		{
+			LabelObjectBox object = new LabelObjectBox();
+
+		
+			/* position attrs */
+			object.x0 = XmlUtil.get_prop_length( node, "x", 0.0 );
+			object.y0 = XmlUtil.get_prop_length( node, "y", 0.0 );
+
+			/* size attrs */
+			object.w = XmlUtil.get_prop_length( node, "w", 0 );
+			object.h = XmlUtil.get_prop_length( node, "h", 0 );
+
+			/* line attrs */
+			object.line_width = XmlUtil.get_prop_length( node, "line_width", 1.0 );
+	
+			{
+				string key        = XmlUtil.get_prop_string( node, "line_color_field", null );
+				bool   field_flag = key != null;
+				Color  color      = Color.from_legacy_color( XmlUtil.get_prop_uint( node, "line_color", 0 ) );
+				object.line_color_node = ColorNode( field_flag, color, key );
+			}
+
+			/* fill attrs */
+			{
+				string key        = XmlUtil.get_prop_string( node, "fill_color_field", null );
+				bool   field_flag = key != null;
+				Color  color      = Color.from_legacy_color( XmlUtil.get_prop_uint( node, "fill_color", 0 ) );
+				object.fill_color_node = ColorNode( field_flag, color, key );
+			}
+	
+			/* affine attrs */
+			parse_affine_attrs( node, object );
+
+			/* shadow attrs */
+			parse_shadow_attrs( node, object );
+
+			label.add_object( object );
+		}
+
+
+		private void parse_affine_attrs( Xml.Node    node,
+		                                 LabelObject object )
+		{
+			double           a[6];
+
+			a[0] = XmlUtil.get_prop_double( node, "a0", 0.0 );
+			a[1] = XmlUtil.get_prop_double( node, "a1", 0.0 );
+			a[2] = XmlUtil.get_prop_double( node, "a2", 0.0 );
+			a[3] = XmlUtil.get_prop_double( node, "a3", 0.0 );
+			a[4] = XmlUtil.get_prop_double( node, "a4", 0.0 );
+			a[5] = XmlUtil.get_prop_double( node, "a5", 0.0 );
+
+			object.matrix = Cairo.Matrix( a[0], a[1], a[2], a[3], a[4], a[5] );
+		}
+
+
+		private void parse_shadow_attrs( Xml.Node    node,
+		                                 LabelObject object )
+		{
+			object.shadow_state = XmlUtil.get_prop_bool( node, "shadow", false );
+
+			if (object.shadow_state)
+			{
+				object.shadow_x = XmlUtil.get_prop_length( node, "shadow_x", 0.0 );
+				object.shadow_y = XmlUtil.get_prop_length( node, "shadow_y", 0.0 );
+		
+				string key        = XmlUtil.get_prop_string( node, "shadow_color_field", null );
+				bool   field_flag = key != null;
+				Color  color      = Color.from_legacy_color( XmlUtil.get_prop_uint( node, "shadow_color", 0 ) );
+				object.shadow_color_node = ColorNode( field_flag, color, key );
+
+				object.shadow_opacity = XmlUtil.get_prop_double( node, "shadow_opacity", 1.0 );
+			}
+		}
+
+
+		private void parse_merge_node( Xml.Node  node,
+		                               Label     label )
+		{
+			Merge merge = MergeFactory.create_merge( XmlUtil.get_prop_string( node, "type", null ) );
+
+			merge.src  = XmlUtil.get_prop_string( node, "src", null );
+
+			label.merge = merge;
+		}
+
+
+		public void save_file( Label label,
+		                       string utf8_filename ) throws XmlError
+		{
+			Xml.Doc doc = create_doc( label );
+
+			string filename;
+			try
+			{
+				filename = Filename.from_utf8( utf8_filename, -1, null, null );
+			}
+			catch ( ConvertError e )
+			{
+				throw new XmlError.OPEN_ERROR( "Utf8 filename conversion error." );
+			}
+
+			if ( doc.save_format_file( filename, 1 ) < 0 )
+			{
+				throw new XmlError.SAVE_ERROR( "Problem saving xml file." );
+			}
+
+			label.filename = utf8_filename;
+			label.modified = false;
+		}
+
+
+		public void save_buffer( Label      label,
+		                         out string buffer ) throws XmlError
+		{
+			Xml.Doc doc = create_doc( label );
+
+			int length;
+			doc.dump_memory( out buffer, out length );
+			if ( length <= 0 )
+			{
+				throw new XmlError.SAVE_ERROR( "Problem saving xml buffer." );
+			}
+
+			label.modified = false;
+		}
+
+
+		private Xml.Doc create_doc( Label label )
+		{
+			Xml.Doc doc = new Xml.Doc( "1.0" );
+			unowned Xml.Node* root_node = new Xml.Node( null, "Glabels-document" );
+			doc.set_root_element( root_node );
+			unowned Xml.Ns *ns = new Xml.Ns( root_node, NAME_SPACE, null );
+			root_node->ns = ns;
+
+			XmlTemplate.create_template_node( label.template, root_node, ns );
+
+			create_objects_node( root_node, ns, label );
+
+			if ( !(label.merge is MergeNone) )
+			{
+				create_merge_node( root_node, ns, label );
+			}
+
+			create_data_node( doc, root_node, ns, label );
+
+			return doc;
+		}
+
+
+		private void create_objects_node( Xml.Node root,
+		                                  Xml.Ns   ns,
+		                                  Label    label )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Objects" );
+
+			XmlUtil.set_prop_string( node, "id", "0" );
+			XmlUtil.set_prop_bool( node, "rotate", label.rotate );
+
+			foreach ( LabelObject object in label.object_list )
+			{
+				if ( object is LabelObjectBox )
+				{
+					create_object_box_node( node, ns, (LabelObjectBox)object );
+				}
+				else /* TODO: other object types. */
+				{
+					message( "Unknown label object." );
+				}
+			}
+		}
+
+
+		private void create_object_box_node( Xml.Node       parent,
+		                                     Xml.Ns         ns,
+		                                     LabelObjectBox object )
+		{
+			unowned Xml.Node *node = parent.new_child( ns, "Object-box" );
+
+			/* position attrs */
+			XmlUtil.set_prop_length( node, "x", object.x0 );
+			XmlUtil.set_prop_length( node, "y", object.y0 );
+
+			/* size attrs */
+			XmlUtil.set_prop_length( node, "w", object.w );
+			XmlUtil.set_prop_length( node, "h", object.h );
+
+			/* line attrs */
+			XmlUtil.set_prop_length( node, "line_width", object.line_width );
+			if ( object.line_color_node.field_flag )
+			{
+				XmlUtil.set_prop_string( node, "line_color_field", object.line_color_node.key );
+			}
+			else
+			{
+				XmlUtil.set_prop_uint_hex( node, "line_color", object.line_color_node.color.to_legacy_color() );
+			}
+
+			/* fill attrs */
+			if ( object.fill_color_node.field_flag )
+			{
+				XmlUtil.set_prop_string( node, "fill_color_field", object.fill_color_node.key );
+			}
+			else
+			{
+				XmlUtil.set_prop_uint_hex( node, "fill_color", object.fill_color_node.color.to_legacy_color() );
+			}
+
+			/* affine attrs */
+			create_affine_attrs( node, object );
+
+			/* shadow attrs */
+			create_shadow_attrs( node, object );
+		}
+
+
+		private void create_affine_attrs( Xml.Node    node,
+		                                  LabelObject object )
+		{
+			XmlUtil.set_prop_double( node, "a0", object.matrix.xx );
+			XmlUtil.set_prop_double( node, "a1", object.matrix.yx );
+			XmlUtil.set_prop_double( node, "a2", object.matrix.xy );
+			XmlUtil.set_prop_double( node, "a3", object.matrix.yy );
+			XmlUtil.set_prop_double( node, "a4", object.matrix.x0 );
+			XmlUtil.set_prop_double( node, "a5", object.matrix.y0 );
+		}
+
+
+		private void create_shadow_attrs( Xml.Node    node,
+		                                  LabelObject object )
+		{
+			if ( object.shadow_state )
+			{
+				XmlUtil.set_prop_bool( node, "shadow", object.shadow_state );
+
+				XmlUtil.set_prop_length( node, "shadow_x", object.shadow_x );
+				XmlUtil.set_prop_length( node, "shadow_y", object.shadow_y );
+
+				if ( object.shadow_color_node.field_flag )
+				{
+					XmlUtil.set_prop_string( node, "shadow_color_field", object.shadow_color_node.key );
+				}
+				else
+				{
+					XmlUtil.set_prop_uint_hex( node, "shadow_color", object.shadow_color_node.color.to_legacy_color() );
+				}
+
+				XmlUtil.set_prop_double( node, "shadow_opacity", object.shadow_opacity );
+
+			}
+		}
+
+
+		private void create_merge_node( Xml.Node root,
+		                                Xml.Ns   ns,
+		                                Label    label )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Merge" );
+
+			XmlUtil.set_prop_string( node, "type", label.merge.name );
+			XmlUtil.set_prop_string( node, "src", label.merge.src );
+		}
+
+
+		private void create_data_node( Xml.Doc  doc,
+		                               Xml.Node root,
+		                               Xml.Ns   ns,
+		                               Label    label )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Data" );
+
+			/* TODO */
+		}
+
+	}
+
+}
+
diff --git a/libglabels/Makefile.am b/libglabels/Makefile.am
index f901346..29b5930 100644
--- a/libglabels/Makefile.am
+++ b/libglabels/Makefile.am
@@ -1,66 +1,62 @@
-configdir = $(datadir)/$(LIBGLABELS_BRANCH)
+NULL = 
+
+lib_LTLIBRARIES = libglabels-4.0.la
+
+libglabels_4_0_la_SOURCES = \
+	category.vala \
+	db.vala \
+	mini_preview_pixbuf.vala \
+	paper.vala \
+	str_util.vala \
+	template_coord.vala \
+	template_frame_cd.vala \
+	template_frame_ellipse.vala \
+	template_frame_rect.vala \
+	template_frame_round.vala \
+	template_frame.vala \
+	template_layout.vala \
+	template_markup_circle.vala \
+	template_markup_ellipse.vala \
+	template_markup_line.vala \
+	template_markup_margin.vala \
+	template_markup_rect.vala \
+	template_markup.vala \
+	template.vala \
+	units.vala \
+	vendor.vala \
+	xml_category.vala \
+	xml_paper.vala \
+	xml_template.vala \
+	xml_util.vala \
+	xml_vendor.vala \
+	$(NULL)
 
 INCLUDES = \
-	$(LIBGLABELS_CFLAGS)				\
-	-DLIBGLABELS_CONFIG_DIR=\""$(configdir)"\" \
-	$(DISABLE_DEPRECATED_CFLAGS)
-
-libglabels_3_0_la_LDFLAGS=\
-        -version-info $(LIBGLABELS_API_VERSION) \
-        $(LIBGLABELS_LIBS) \
-        -no-undefined 
-
-lib_LTLIBRARIES = libglabels-3.0.la
-
-libglabels_3_0_la_SOURCES =	\
-	libglabels-private.h	\
-	lgl-db.h		\
-	lgl-db.c		\
-	lgl-units.h		\
-	lgl-units.c		\
-	lgl-paper.h		\
-	lgl-paper.c		\
-	lgl-category.h		\
-	lgl-category.c		\
-	lgl-vendor.h		\
-	lgl-vendor.c		\
-	lgl-template.h		\
-	lgl-template.c		\
-	lgl-xml-paper.h		\
-	lgl-xml-paper.c		\
-	lgl-xml-category.h	\
-	lgl-xml-category.c	\
-	lgl-xml-vendor.h	\
-	lgl-xml-vendor.c	\
-	lgl-xml-template.h	\
-	lgl-xml-template.c	\
-	lgl-xml.h		\
-	lgl-xml.c		\
-	lgl-str.h		\
-	lgl-str.c
-
-libglabels_3_0includedir=$(includedir)/$(LIBGLABELS_BRANCH)
-
-libglabels_3_0include_HEADERS = 	\
-	libglabels.h
-
-libglabels_3_0subincludedir=$(includedir)/$(LIBGLABELS_BRANCH)/libglabels
-
-libglabels_3_0subinclude_HEADERS =  \
-	lgl-db.h                \
-	lgl-units.h		\
-	lgl-paper.h		\
-	lgl-category.h		\
-	lgl-template.h		\
-	lgl-xml-paper.h		\
-	lgl-xml-category.h	\
-	lgl-xml-template.h	\
-	lgl-xml.h		\
-	lgl-str.h			
-
-EXTRA_DIST =			\
-	$(LIBGLABELS_BRANCH).pc.in
-
-pkgconfigdir = $(libdir)/pkgconfig
-pkgconfig_DATA = $(LIBGLABELS_BRANCH).pc
+	-include config.h \
+	$(LIBGLABELS_CFLAGS) \
+	-DLOCALEDIR=\""$(localedir)"\" \
+	-DDATADIR=\""$(datadir)"\" \
+	-DLIBGLABELS_BRANCH=\""$(LIBGLABELS_BRANCH)"\" \
+	$(NULL)
+
+VALAFLAGS = \
+	--vapidir=$(srcdir)/../vapi \
+	--pkg config \
+	--pkg posix \
+	--pkg gobject-2.0 \
+	--pkg libxml-2.0 \
+	--pkg cairo \
+	--pkg gdk-pixbuf-2.0 \
+	--pkg gee-1.0 \
+	--header=libglabels-4.h \
+	--vapi=libglabels-4.vapi \
+	$(NULL)
+
+EXTRA_DIST = \
+	libglabels-4.h \
+	libglabels-4.vapi \
+	$(NULL)
+
+DISTCLEANFILES = \
+	$(NULL)
 
diff --git a/libglabels/lgl-str.h b/libglabels/category.vala
similarity index 52%
rename from libglabels/lgl-str.h
rename to libglabels/category.vala
index 3c096b0..ad5ee8d 100644
--- a/libglabels/lgl-str.h
+++ b/libglabels/category.vala
@@ -1,6 +1,6 @@
-/*
- *  lgl-str.h
- *  Copyright (C) 2007-2010  Jim Evins <evins snaught com>.
+/*  category.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
  *
  *  This file is part of libglabels.
  *
@@ -18,33 +18,25 @@
  *  along with libglabels.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __LGL_STR_H__
-#define __LGL_STR_H__
-
-#include <glib.h>
-
-G_BEGIN_DECLS
 
-gint   lgl_str_utf8_casecmp    (const gchar *s1,
-                                const gchar *s2);
+using GLib;
 
-gint   lgl_str_part_name_cmp   (const gchar *s1,
-                                const gchar *s2);
+namespace libglabels
+{
 
-gchar *lgl_str_format_fraction (gdouble      x);
+	public struct Category
+	{
+		public string   id   { get; private set; } /* Unique ID of category */
+		public string   name { get; private set; } /* Localized name of category */
 
-G_END_DECLS
+		public Category( string id,
+		                 string name )
+		{
+			this.id   = id;
+			this.name = name;
+		}
 
+	}
 
-#endif /* __LGL_STR_H__ */
+}
 
-
-
-/*
- * Local Variables:       -- emacs
- * mode: C                -- emacs
- * c-basic-offset: 8      -- emacs
- * tab-width: 8           -- emacs
- * indent-tabs-mode: nil  -- emacs
- * End:                   -- emacs
- */
diff --git a/libglabels/db.vala b/libglabels/db.vala
new file mode 100644
index 0000000..b75c7bf
--- /dev/null
+++ b/libglabels/db.vala
@@ -0,0 +1,788 @@
+/*  db.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public class Db : Object
+	{
+
+		public signal void changed();
+
+		private class StaticChangedProxy
+		{
+			internal signal void changed();
+
+			internal void emit_changed()
+			{
+				changed();
+			}
+		}
+
+		private static StaticChangedProxy static_changed_proxy;
+
+		public static unowned List<Paper?>    papers         { get; private set; }
+		public static unowned List<string>    paper_ids      { get; private set; }
+		public static unowned List<string>    paper_names    { get; private set; }
+
+		public static unowned List<Category?> categories     { get; private set; }
+		public static unowned List<string>    category_ids   { get; private set; }
+		public static unowned List<string>    category_names { get; private set; }
+
+		public static unowned List<Vendor?>   vendors        { get; private set; }
+		public static unowned List<string>    vendor_names   { get; private set; }
+
+		public static unowned List<Template>  templates      { get; private set; }
+
+
+		public static void init()
+		{
+			/* Force construction of static fields. */
+			new Db();
+		}
+
+
+		static construct
+		{
+			static_changed_proxy = new StaticChangedProxy();
+
+			papers         = null;
+			paper_ids      = null;
+			paper_names    = null;
+
+			categories     = null;
+			category_ids   = null;
+			category_names = null;
+
+			vendors        = null;
+			vendor_names   = null;
+
+			templates      = null;
+
+			/*
+			 * Paper definitions
+			 */
+			read_papers();
+
+			/* Create and append an "Other" entry. */
+			/* Translators: "Other" here means other page size.  Meaning a page size
+			 * other than the standard ones that libglabels knows about such as
+			 * "letter", "A4", etc. */
+			Paper paper_other = Paper( "Other", _("Other"), 0.0, 0.0, null );
+			register_paper( paper_other );
+
+			/*
+			 * Category definitions
+			 */
+			read_categories();
+
+			/* Create and append a "User defined" entry. */
+			Category category_user = Category( "user-defined", _("User defined") );
+			register_category( category_user );
+
+			/*
+			 * Vendor definitions
+			 */
+			read_vendors();
+
+			/*
+			 * Templates
+			 */
+			read_templates();
+
+			/* Create and append generic full page templates. */
+			foreach ( string paper_id in paper_ids )
+			{
+				if ( !is_paper_id_other( paper_id ) )
+				{
+					Template template = new Template.full_page( paper_id );
+					register_template( template );
+				}
+			}
+		}
+
+
+		construct
+		{
+			static_changed_proxy.changed.connect( on_proxy_changed );
+		}
+
+
+		private void on_proxy_changed()
+		{
+			changed();
+		}
+
+
+		internal static void register_paper( Paper paper )
+		{
+			if ( lookup_paper_from_id( paper.id ) == null )
+			{
+				papers.append( paper );
+				paper_ids.append( paper.id );
+				paper_names.append( paper.name );
+			}
+			else
+			{
+				message( "Duplicate paper id: %s.", paper.id );
+			}
+		}
+
+
+		public static Paper? lookup_paper_from_name( string? name )
+		{
+			if ( name == null )
+			{
+				return papers.first().data;
+			}
+
+			foreach ( Paper paper in papers )
+			{
+				if ( paper.name == name )
+				{
+					return paper;
+				}
+			}
+
+			return null;
+		}
+
+
+		public static Paper? lookup_paper_from_id( string? id )
+		{
+			if ( id == null )
+			{
+				return papers.first().data;
+			}
+
+			foreach ( Paper paper in papers )
+			{
+				if ( paper.id == id )
+				{
+					return paper;
+				}
+			}
+
+			return null;
+		}
+
+
+		public static string? lookup_paper_id_from_name( string? name )
+		{
+			if ( name != null )
+			{
+				Paper paper = lookup_paper_from_name( name );
+				return paper.id;
+			}
+
+			return null;
+		}
+
+
+		public static string? lookup_paper_name_from_id( string? id )
+		{
+			if ( id != null )
+			{
+				Paper paper = lookup_paper_from_id( id );
+				return paper.name;
+			}
+
+			return null;
+		}
+
+
+		public static bool is_paper_id_known( string? id )
+		{
+			if ( id == null )
+			{
+				return false;
+			}
+
+			foreach (Paper paper in papers)
+			{
+				if ( paper.id == id )
+				{
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+
+		public static bool is_paper_id_other( string? id )
+		{
+			return ( id == "Other" );
+		}
+
+
+		internal static void register_category( Category category )
+		{
+			if ( lookup_category_from_id( category.id ) == null )
+			{
+				categories.append( category );
+				category_ids.append( category.id );
+				category_names.append( category.name );
+			}
+			else
+			{
+				message( "Duplicate category id: %s.", category.id );
+			}
+		}
+
+
+		public static Category? lookup_category_from_name( string? name )
+		{
+			if ( name == null )
+			{
+				return categories.first().data;
+			}
+
+			foreach ( Category category in categories )
+			{
+				if ( category.name == name )
+				{
+					return category;
+				}
+			}
+
+			return null;
+		}
+
+
+		public static Category? lookup_category_from_id( string? id )
+		{
+			if ( id == null )
+			{
+				return categories.first().data;
+			}
+
+			foreach ( Category category in categories )
+			{
+				if ( category.id == id )
+				{
+					return category;
+				}
+			}
+
+			return null;
+		}
+
+
+		public static string? lookup_category_id_from_name( string? name )
+		{
+			if ( name != null )
+			{
+				Category category = lookup_category_from_name( name );
+				return category.id;
+			}
+
+			return null;
+		}
+
+
+		public static string? lookup_category_name_from_id( string? id )
+		{
+			if ( id != null )
+			{
+				Category category = lookup_category_from_id( id );
+				return category.name;
+			}
+
+			return null;
+		}
+
+
+		public static bool is_category_id_known( string? id )
+		{
+			if ( id == null )
+			{
+				return false;
+			}
+
+			foreach (Category category in categories)
+			{
+				if ( category.id == id )
+				{
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+
+		internal static void register_vendor( Vendor vendor )
+		{
+			if ( lookup_vendor_from_name( vendor.name ) == null )
+			{
+				vendors.append( vendor );
+				vendor_names.append( vendor.name );
+			}
+			else
+			{
+				message( "Duplicate vendor name: %s.", vendor.name );
+			}
+		}
+
+
+		public static Vendor? lookup_vendor_from_name( string? name )
+		{
+			if ( name == null )
+			{
+				return vendors.first().data;
+			}
+
+			foreach ( Vendor vendor in vendors )
+			{
+				if ( vendor.name == name )
+				{
+					return vendor;
+				}
+			}
+
+			return null;
+		}
+
+
+		public static string? lookup_url_from_name( string? name )
+		{
+			if ( name != null )
+			{
+				Vendor vendor = lookup_vendor_from_name( name );
+				return vendor.url;
+			}
+
+			return null;
+		}
+
+
+		public static bool is_vendor_name_known( string? name )
+		{
+			if ( name == null )
+			{
+				return false;
+			}
+
+			foreach (Vendor vendor in vendors)
+			{
+				if ( vendor.name == name )
+				{
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+
+		internal static void register_template( Template template )
+		{
+			if ( !does_template_exist( template.brand, template.part ) )
+			{
+				templates.insert_sorted( template, compare_template_names );
+			}
+			else
+			{
+				message( "Duplicate template: %s %s.",
+				         template.brand, template.part );
+			}
+		}
+
+
+		public static void register_user_template( Template template )
+		{
+			return_if_fail( !does_template_exist( template.brand, template.part ) );
+			return_if_fail( is_paper_id_known( template.paper_id ) );
+
+			string dir = Path.build_filename( Environment.get_user_config_dir(),
+			                                  "libglabels", "templates",
+			                                  null );
+			DirUtils.create_with_parents( dir, 0775 ); /* Make sure dir exists. */
+
+			string filename = "%s_%s.template".printf( template.brand, template.part );
+			string abs_filename = Path.build_filename( dir, filename, null );
+
+			int bytes_written = XmlTemplate.write_template_to_file( template, abs_filename );
+
+			return_if_fail( bytes_written > 0 );
+
+			Template template_copy = template.dup();
+			template_copy.add_category( "user-defined" );
+			register_template( template_copy );
+
+			static_changed_proxy.emit_changed();
+		}
+
+
+		public static void delete_user_template_by_name( string name )
+		{
+			Template template = lookup_template_from_name( name );
+
+			return_if_fail( template != null );
+			return_if_fail( template.does_category_match( "user-defined" ) );
+
+			string dir = Path.build_filename( Environment.get_user_config_dir(),
+			                                  "libglabels", "templates",
+			                                  null );
+
+			string filename = "%s_%s.template".printf( template.brand, template.part );
+			string abs_filename = Path.build_filename( dir, filename, null );
+			return_if_fail( FileUtils.test( abs_filename, FileTest.EXISTS ) );
+
+			FileUtils.unlink( abs_filename );
+
+			templates.remove( template );
+		}
+
+
+		public static void delete_user_template_by_brand_part( string brand,
+		                                                       string part )
+		{
+			string name = "%s %s".printf( brand, part );
+
+			delete_user_template_by_name( name );
+		}
+
+
+		public static Template? lookup_template_from_name( string? name )
+		{
+			if ( name == null )
+			{
+				return templates.first().data;
+			}
+
+			foreach ( Template template in templates )
+			{
+				if ( template.name == name )
+				{
+					return template;
+				}
+			}
+
+			return null;
+		}
+
+
+		public static Template? lookup_template_from_brand_part( string? brand,
+		                                                         string? part )
+		{
+			if ( (brand == null) || (part == null) )
+			{
+				return templates.first().data;
+			}
+
+			foreach ( Template template in templates )
+			{
+				if ( (template.brand == brand) && (template.part == part) )
+				{
+					return template;
+				}
+			}
+
+			return null;
+		}
+
+
+		public static bool does_template_exist( string? brand,
+		                                        string? part )
+		{
+			return ( lookup_template_from_brand_part( brand, part ) != null );
+		}
+
+
+		/************************************/
+		/* Debug methods.                   */
+		/************************************/
+
+		public static void print_known_papers()
+		{
+			stdout.printf( "KNOWN PAPERS:\n" );
+
+			foreach (Paper paper in papers)
+			{
+				stdout.printf( "paper id=\"%s\", name=\"%s\" width=%gpts, height=%gpts\n",
+				               paper.id,
+				               paper.name,
+				               paper.width,
+				               paper.height );
+			}
+
+			stdout.printf( "\n" );
+		}
+
+
+		public static void print_known_categories()
+		{
+			stdout.printf( "KNOWN CATEGORIES:\n" );
+
+			foreach (Category category in categories)
+			{
+				stdout.printf( "category id=\"%s\", name=\"%s\"\n",
+				               category.id,
+				               category.name );
+			}
+
+			stdout.printf( "\n" );
+		}
+
+
+		public static void print_known_vendors()
+		{
+			stdout.printf( "KNOWN VENDORS:\n" );
+
+			foreach (Vendor vendor in vendors)
+			{
+				stdout.printf( "vendor name=\"%s\", url=\"%s\"\n",
+				               vendor.name,
+				               vendor.url );
+			}
+
+			stdout.printf( "\n" );
+		}
+
+
+		public static void print_known_templates()
+		{
+			stdout.printf( "KNOWN TEMPLATES:\n" );
+
+			foreach (Template template in templates)
+			{
+				stdout.printf( "template brand=\"%s\", part=\"%s\", description=\"%s\"\n",
+				               template.brand,
+				               template.part,
+				               template.description );
+			}
+
+			stdout.printf( "\n" );
+		}
+
+
+		/************************************/
+		/* Methods to initialize db.        */
+		/************************************/
+
+		private static void read_papers()
+		{
+			string data_dir;
+
+			data_dir = Path.build_filename( Config.DATADIR, Config.LIBGLABELS_BRANCH, "templates", null );
+			read_paper_files_from_dir( data_dir );
+
+			data_dir = Path.build_filename( Environment.get_user_config_dir(), "libglabels", "templates", null );
+			read_paper_files_from_dir( data_dir );
+
+			data_dir = Path.build_filename( Environment.get_home_dir(), ".glabels", null );
+			read_paper_files_from_dir( data_dir );
+
+			if ( papers == null )
+			{
+				critical( "Unable to locate paper size definitions. Libglabels may not be installed correctly!" );
+			}
+		}
+
+
+		private static void read_paper_files_from_dir( string dirname )
+		{
+			if ( FileUtils.test( dirname, FileTest.IS_DIR ) )
+			{
+				Dir dir;
+
+				try {
+					dir = Dir.open( dirname, 0 );
+				}
+				catch( Error e )
+				{
+					message( "cannot open data directory: %s", e.message );
+					return;
+				}
+
+				string? filename;
+
+				while ( (filename = dir.read_name()) != null )
+				{
+					if ( filename == "paper-sizes.xml" )
+					{
+						string full_filename = Path.build_filename( dirname, filename, null );
+						XmlPaper.read_papers_from_file( full_filename );
+					}
+				}
+
+			}
+		}
+
+
+		private static void read_categories()
+		{
+			string data_dir;
+
+			data_dir = Path.build_filename( Config.DATADIR, Config.LIBGLABELS_BRANCH, "templates", null );
+			read_category_files_from_dir( data_dir );
+
+			data_dir = Path.build_filename( Environment.get_user_config_dir(), "libglabels", "templates", null );
+			read_category_files_from_dir( data_dir );
+
+			data_dir = Path.build_filename( Environment.get_home_dir(), ".glabels", null );
+			read_category_files_from_dir( data_dir );
+
+			if ( categories == null )
+			{
+				critical( "Unable to locate any category definitions. Libglabels may not be installed correctly!" );
+			}
+		}
+
+
+		private static void read_category_files_from_dir( string dirname )
+		{
+			if ( FileUtils.test( dirname, FileTest.IS_DIR ) )
+			{
+				Dir dir;
+
+				try {
+					dir = Dir.open( dirname, 0 );
+				}
+				catch( Error e )
+				{
+					message( "cannot open data directory: %s", e.message );
+					return;
+				}
+
+				string? filename;
+
+				while ( (filename = dir.read_name()) != null )
+				{
+					if ( filename == "categories.xml" )
+					{
+						string full_filename = Path.build_filename( dirname, filename, null );
+						XmlCategory.read_categories_from_file( full_filename );
+					}
+				}
+
+			}
+		}
+
+
+		private static void read_vendors()
+		{
+			string data_dir;
+
+			data_dir = Path.build_filename( Config.DATADIR, Config.LIBGLABELS_BRANCH, "templates", null );
+			read_vendor_files_from_dir( data_dir );
+
+			data_dir = Path.build_filename( Environment.get_user_config_dir(), "libglabels", "templates", null );
+			read_vendor_files_from_dir( data_dir );
+
+			data_dir = Path.build_filename( Environment.get_home_dir(), ".glabels", null );
+			read_vendor_files_from_dir( data_dir );
+
+			if ( vendors == null )
+			{
+				critical( "Unable to locate any vendor definitions. Libglabels may not be installed correctly!" );
+			}
+		}
+
+
+		private static void read_vendor_files_from_dir( string dirname )
+		{
+			if ( FileUtils.test( dirname, FileTest.IS_DIR ) )
+			{
+				Dir dir;
+
+				try {
+					dir = Dir.open( dirname, 0 );
+				}
+				catch( Error e )
+				{
+					message( "cannot open data directory: %s", e.message );
+					return;
+				}
+
+				string? filename;
+
+				while ( (filename = dir.read_name()) != null )
+				{
+					if ( filename == "vendors.xml" )
+					{
+						string full_filename = Path.build_filename( dirname, filename, null );
+						XmlVendor.read_vendors_from_file( full_filename );
+					}
+				}
+
+			}
+		}
+
+
+		private static void read_templates()
+		{
+			string data_dir;
+
+			data_dir = Path.build_filename( Config.DATADIR, Config.LIBGLABELS_BRANCH, "templates", null );
+			read_template_files_from_dir( data_dir );
+
+			data_dir = Path.build_filename( Environment.get_user_config_dir(), "libglabels", "templates", null );
+			read_template_files_from_dir( data_dir );
+
+			data_dir = Path.build_filename( Environment.get_home_dir(), ".glabels", null );
+			read_template_files_from_dir( data_dir );
+
+			if ( templates == null )
+			{
+				critical( "Unable to locate any template definitions. Libglabels may not be installed correctly!" );
+			}
+		}
+
+
+		private static void read_template_files_from_dir( string dirname )
+		{
+			if ( FileUtils.test( dirname, FileTest.IS_DIR ) )
+			{
+				Dir dir;
+
+				try {
+					dir = Dir.open( dirname, 0 );
+				}
+				catch( Error e )
+				{
+					message( "cannot open data directory: %s", e.message );
+					return;
+				}
+
+				string? filename;
+
+				while ( (filename = dir.read_name()) != null )
+				{
+					if ( filename.has_suffix( "-templates.xml" ) ||
+						 filename.has_suffix( ".template" ) )
+					{
+						string full_filename = Path.build_filename( dirname, filename, null );
+						XmlTemplate.read_templates_from_file( full_filename );
+					}
+				}
+
+			}
+		}
+
+
+	}
+
+}
diff --git a/libglabels/mini_preview_pixbuf.vala b/libglabels/mini_preview_pixbuf.vala
new file mode 100644
index 0000000..cad9a0b
--- /dev/null
+++ b/libglabels/mini_preview_pixbuf.vala
@@ -0,0 +1,156 @@
+/*  mini_preview_pixbuf.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	private struct ColorStruct
+	{
+		double r;
+		double g;
+		double b;
+	}
+
+	private const ColorStruct paper         = { 0.85,  0.85,  0.85 };
+	private const ColorStruct paper_outline = { 0.0,   0.0,   0.0  };
+	private const ColorStruct label         = { 0.95,  0.95,  0.95  };
+	private const ColorStruct label_outline = { 0.25,  0.25,  0.25 };
+
+	private const double paper_outline_width_pixels = 1.0;
+	private const double label_outline_width_pixels = 1.0;
+
+
+	public class MiniPreviewPixbuf
+	{
+		public Gdk.Pixbuf pixbuf { get; private set; }
+
+		public MiniPreviewPixbuf( Template template,
+		                          int      width,
+		                          int      height )
+		{
+			pixbuf = new Gdk.Pixbuf( Gdk.Colorspace.RGB, true, 8, width, height );
+
+			Cairo.ImageSurface surface = new Cairo.ImageSurface.for_data( pixbuf.get_pixels(),
+			                                                              Cairo.Format.RGB24,
+			                                                              pixbuf.get_width(),
+			                                                              pixbuf.get_height(),
+			                                                              pixbuf.get_rowstride() );
+			Cairo.Context cr = new Cairo.Context( surface );
+
+			/* Clear pixbuf */
+			cr.save();
+			cr.set_operator( Cairo.Operator.CLEAR );
+			cr.paint();
+			cr.restore();
+
+			cr.set_antialias( Cairo.Antialias.GRAY );
+
+			/* Set scale and offset */
+			double w = width - 1;
+			double h = height - 1;
+			double scale;
+			if ( (w/template.page_width) > (h/template.page_height) )
+			{
+				scale = h / template.page_height;
+			}
+			else
+			{
+				scale = w / template.page_width;
+			}
+			double offset_x = (width/scale - template.page_width) / 2.0;
+			double offset_y = (height/scale - template.page_height) / 2.0;
+			cr.identity_matrix();
+			cr.scale( scale, scale );
+			cr.translate( offset_x, offset_y );
+
+			/* Draw paper and label outlines */
+			draw_paper( cr, template, scale );
+			draw_label_outlines( cr, template, scale );
+		}
+
+
+		private void draw_paper( Cairo.Context cr,
+		                         Template      template,
+		                         double        scale )
+		{
+			cr.save();
+
+			cr.rectangle( 0, 0, template.page_width, template.page_height );
+
+			cr.set_source_rgb( paper.r, paper.g, paper.b );
+			cr.fill_preserve();
+
+			cr.set_line_width( paper_outline_width_pixels/scale );
+			cr.set_source_rgb( paper_outline.r, paper_outline.g, paper_outline.b );
+			cr.stroke();
+
+			cr.restore();
+		}
+
+
+		private void draw_label_outlines( Cairo.Context cr,
+		                                  Template      template,
+		                                  double        scale )
+		{
+			cr.save();
+
+			cr.set_line_width( label_outline_width_pixels/scale );
+
+			TemplateFrame frame = template.frames.first().data;
+
+			Gee.ArrayList<TemplateCoord?> origins = frame.get_origins();
+
+			foreach ( TemplateCoord origin in origins )
+			{
+				draw_label_outline( cr, frame, origin.x, origin.y );
+			}
+
+			cr.restore();
+		}
+
+
+		private void draw_label_outline( Cairo.Context  cr,
+		                                 TemplateFrame  frame,
+		                                 double         x0,
+		                                 double         y0 )
+		{
+			cr.save();
+
+			cr.translate( x0, y0 );
+
+			frame.cairo_path( cr, false );
+
+			cr.set_source_rgb( label.r, label.g, label.b );
+			cr.set_fill_rule( Cairo.FillRule.EVEN_ODD );
+			cr.fill_preserve();
+
+			cr.set_source_rgb( label_outline.r, label_outline.g, label_outline.b );
+			cr.stroke();
+
+			cr.restore();
+		}
+
+
+	}
+
+}
diff --git a/libglabels/paper.vala b/libglabels/paper.vala
new file mode 100644
index 0000000..bf52777
--- /dev/null
+++ b/libglabels/paper.vala
@@ -0,0 +1,51 @@
+/*  paper.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public struct Paper
+	{
+		public string   id       { get; private set; }   /* Unique ID of category */
+		public string   name     { get; private set; }   /* Localized name of category */
+		public double   width    { get; private set; }   /* Width (in points) */
+		public double   height   { get; private set; }   /* Width (in points) */
+		public string   pwg_size { get; private set; }   /* PWG 5101.1-2002 size name */
+
+		public Paper( string  id,
+		              string  name,
+		              double  width,
+		              double  height,
+		              string? pwg_size )
+		{
+			this.id       = id;
+			this.name     = name;
+			this.width    = width;
+			this.height   = height;
+			this.pwg_size = pwg_size;
+		}
+
+	}
+
+}
+
diff --git a/libglabels/str_util.vala b/libglabels/str_util.vala
new file mode 100644
index 0000000..5a4c8bb
--- /dev/null
+++ b/libglabels/str_util.vala
@@ -0,0 +1,213 @@
+/*  str_util.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using Math;
+
+namespace libglabels
+{
+
+	namespace StrUtil
+	{
+
+		public string format_fraction( double x )
+		{
+			const double   FRAC_EPSILON = 0.00005;
+			const double[] denom        = {  1.0,  2.0, 3.0,  4.0,  8.0,  16.0,  32.0,  0.0 };
+			string[] denom_string = { "1", "â", "â", "â", "â", "ââ", "ââ", null };
+			string[] num_string   = {  "â",  "Â",  "Â",  "Â",  "â",  "â",  "â",  "â",  "â",  "â",
+			                           "Ââ", "ÂÂ", "ÂÂ", "ÂÂ", "Ââ", "Ââ", "Ââ", "Ââ", "Ââ", "Ââ",
+			                           "Ââ", "ÂÂ", "ÂÂ", "ÂÂ", "Ââ", "Ââ", "Ââ", "Ââ", "Ââ", "Ââ",
+			                           "Ââ", "ÂÂ" };
+			int i;
+			double product, remainder;
+			int n, d;
+
+			for ( i=0; denom[i] != 0.0; i++ )
+			{
+				product = x * denom[i];
+				remainder = fabs(product - ((int)(product+0.5)));
+				if ( remainder < FRAC_EPSILON ) break;
+			}
+
+			if ( denom[i] == 0.0 )
+			{
+				/* None of our denominators work. */
+				return "%.5g".printf( x );
+			}
+			if ( denom[i] == 1.0 )
+			{
+				/* Simple integer. */
+				return "%.0f".printf( x );
+			}
+			n = (int)( x * denom[i] + 0.5 );
+			d = (int)denom[i];
+			if ( n > d )
+			{
+				return "%d%s/%s".printf( (n/d), num_string[n%d], denom_string[i] );
+			}
+			else
+			{
+				return "%s/%s".printf( num_string[n%d], denom_string[i] );
+			}
+		}
+
+
+		/**
+		 * Compare part names
+		 * @s1: string to compare with s2.
+		 * @s2: string to compare with s1.
+		 *
+		 * Compare two UTF-8 strings representing part names or numbers.  This function
+		 * uses a natural sort order:
+		 *
+		 *  - Ignores case.
+		 *
+		 *  - Strings are divided into chunks (numeric and non-numeric)
+		 *
+		 *  - Non-numeric chunks are compared character by character
+		 *
+		 *  - Numerical chunks are compared numerically, so that "20" precedes "100".
+		 *
+		 *  - Comparison of chunks is performed left to right until the first difference
+		 *    is encountered or all chunks evaluate as equal.
+		 *
+		 * This function should be used only on strings that are known to be encoded
+		 * in UTF-8 or a compatible UTF-8 subset.
+		 *
+		 * Numeric chunks are converted to 64 bit unsigned integers for comparison,
+		 * so the behaviour may be unpredictable for numeric chunks that exceed
+		 * 18446744073709551615.
+		 *
+		 * Returns: 0 if the strings match, a negative value if s1 < s2,
+		 *          or a positive value if s1 > s2.
+		 *
+		 */
+		public int compare_part_names( string s1,
+		                               string s2 )
+		{
+			if ( s1 == s2 ) return 0;
+			if ( s1 == "" ) return -1;
+			if ( s2 == "" ) return 1;
+
+			string folded_s1 = s1.casefold( -1 );
+			string folded_s2 = s2.casefold( -1 );
+
+			int i1 = 0;
+			int i2 = 0;
+			int result = 0;
+			bool done = false;
+
+			while ( (result == 0) && !done )
+			{
+				string chunk1, chunk2;
+				bool   isnum1, isnum2;
+
+				if ( folded_s1.get_char( i1 ).isdigit() )
+				{
+					chunk1 = span_digits( folded_s1, ref i1 );
+					isnum1 = true;
+				}
+				else
+				{
+					chunk1 = span_non_digits( folded_s1, ref i1 );
+					isnum1 = false;
+				}
+                
+				if ( folded_s2.get_char( i2 ).isdigit() )
+				{
+					chunk2 = span_digits( folded_s2, ref i2 );
+					isnum2 = true;
+				}
+				else
+				{
+					chunk2 = span_non_digits( folded_s2, ref i2 );
+					isnum2 = false;
+				}
+
+				if ( ( chunk1 == "" ) && ( chunk2 == "" ) )
+				{
+					/* Case 1: Both are empty. */
+					done = true;
+				}
+				else if ( isnum1 && isnum2 )
+				{
+					/* Case 2: They both contain numbers */
+					uint64 n1 = uint64.parse( chunk1 );
+					uint64 n2 = uint64.parse( chunk2 );
+
+					if ( n1 < n2 ) result = -1;
+					else if ( n1 > n2 ) result =  1;
+				}
+				else
+				{
+					/* Case 3: One or both do not contain numbers */
+					if ( chunk1 < chunk2 ) result = -1;
+					else if( chunk1 > chunk2 ) result = 1;
+				}
+
+			}
+
+			return result;
+		}
+
+
+		private string span_digits( string s, ref int i )
+		{
+			StringBuilder chunk = new StringBuilder();
+
+			bool not_end;
+			unichar c;
+			int j;
+			for ( j = i, not_end = s.get_next_char( ref j, out c );
+			      not_end && c.isdigit();
+			      not_end = s.get_next_char( ref j, out c ) )
+			{
+				chunk.append_unichar( c );
+				i = j; /* only advance i, if character is used. */
+			}
+
+			return chunk.str;
+		}
+
+
+		private string span_non_digits( string s, ref int i )
+		{
+			StringBuilder chunk = new StringBuilder();
+
+			bool not_end;
+			unichar c;
+			int j;
+			for ( j = i, not_end = s.get_next_char( ref j, out c );
+			      not_end && !c.isdigit();
+			      not_end = s.get_next_char( ref j, out c ) )
+			{
+				chunk.append_unichar( c );
+				i = j; /* only advance i, if character is used. */
+			}
+
+			return chunk.str;
+		}
+
+
+	}
+
+}
diff --git a/libglabels/template.vala b/libglabels/template.vala
new file mode 100644
index 0000000..d563449
--- /dev/null
+++ b/libglabels/template.vala
@@ -0,0 +1,252 @@
+/*  template.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using Math;
+
+namespace libglabels
+{
+
+	private const double EPSILON = 0.5;	 /* Allowed error when comparing dimensions. (0.5pts ~= .007in ~= .2mm) */
+
+	private const int PREVIEW_PIXBUF_SIZE = 72;
+
+
+	public int compare_template_names( Template a, Template b )
+	{
+		return ( StrUtil.compare_part_names( a.name, b.name ) );
+	}
+
+
+	public class Template
+	{
+		public string    brand       { get; private set; }
+		public string    part        { get; private set; }
+		public string    equiv_part  { get; set; }
+
+		public string    name        { get; private set; }
+
+		public string    description { get; private set; }
+
+		public string    paper_id    { get; private set; }
+		public double    page_width  { get; private set; }
+		public double    page_height { get; private set; }
+
+		public Gdk.Pixbuf preview_pixbuf { get; private set; }
+
+
+		/* Meta information */
+		public string               product_url  { get; set; }
+		public unowned List<string> category_ids { get; private set; }
+
+		/* List of label frames.  Currently glabels only supports a single label frame per template. */
+		public unowned List<TemplateFrame> frames { get; private set; }
+
+
+		public Template( string   brand,
+						 string   part,
+						 string   description,
+						 string   paper_id,
+						 double   page_width,
+						 double   page_height )
+		{
+			this.brand       = brand;
+			this.part        = part;
+			this.description = description;
+			this.paper_id    = paper_id;
+			this.page_width  = page_width;
+			this.page_height = page_height;
+
+			this.name       = "%s %s".printf( brand, part );
+		}
+
+
+		public Template.full_page( string paper_id )
+		{
+			Paper? paper = Db.lookup_paper_from_id( paper_id );
+			if ( paper != null )
+			{
+				string part = "%s-Full-Page".printf( paper.id );
+				string desc = _("%s full page").printf( paper.name );
+
+				this( "Generic", part, desc, paper.id, paper.width, paper.height );
+
+				TemplateFrame frame = new TemplateFrameRect( "0",
+															 paper.width,
+															 paper.height,
+															 0, 0, 0 );
+				frame.add_layout( new TemplateLayout( 1, 1, 0, 0, 0, 0 ) );
+				frame.add_markup( new TemplateMarkupMargin( 9 ) );
+
+				this.add_frame( frame );
+
+				this.init_preview_pixbuf();
+								  
+			}
+			else
+			{
+				error( "Cannot create full page template for \"%s\"", paper_id );
+			}
+		}
+
+
+		public Template.from_equiv( string brand,
+									string part,
+									string equiv_part )
+		{
+			Template? template = Db.lookup_template_from_brand_part( brand, equiv_part );
+			if ( template != null )
+			{
+				this( brand, part, template.description,
+					  template.paper_id, template.page_width, template.page_height );
+
+				this.equiv_part     = equiv_part;
+				this.product_url    = template.product_url;
+				this.preview_pixbuf = template.preview_pixbuf;
+
+				foreach (string category_id in template.category_ids)
+				{
+					add_category( category_id );
+				}
+
+				foreach (TemplateFrame frame in template.frames)
+				{
+					add_frame( frame.dup() );
+				}
+			}
+			else
+			{
+				error( "Cannot create equivalent template for \"%s\", \"%s\". Forward references not supported.",
+					   brand, equiv_part );
+			}
+		}
+
+
+		public Template dup()
+		{
+			Template copy = new Template( brand, part, description,
+										  paper_id, page_width, page_height );
+
+			copy.equiv_part     = equiv_part;
+			copy.product_url    = product_url;
+			copy.preview_pixbuf = preview_pixbuf;
+
+			foreach (string category_id in category_ids)
+			{
+				copy.add_category( category_id );
+			}
+
+			foreach (TemplateFrame frame in frames)
+			{
+				copy.add_frame( frame.dup() );
+			}
+
+			return copy;
+		}
+
+
+		public bool is_match( Template template2 )
+		{
+			return ( (template2.brand == brand) && (template2.part == part) );
+		}
+
+
+		public bool does_category_match( string? category_id )
+		{
+			if ( category_id == null )
+			{
+				return true;
+			}
+
+			foreach ( string my_category_id in category_ids )
+			{
+				if ( my_category_id == category_id )
+				{
+					return true;
+				}
+			}
+
+			return false;
+		}
+
+
+		public bool is_similar( Template template2 )
+		{
+			if ( (template2.paper_id    != paper_id)   ||
+				 (template2.page_width  != page_width) ||
+				 (template2.page_height != page_height) )
+			{
+				return false;
+			}
+
+			TemplateFrame frame1 = frames.first().data;
+			TemplateFrame frame2 = template2.frames.first().data;
+
+			if ( !frame1.is_similar( frame2 ) )
+			{
+				return false;
+			}
+
+			foreach ( TemplateLayout layout1 in frame1.layouts )
+			{
+				bool match_found = false;
+				foreach ( TemplateLayout layout2 in frame2.layouts )
+				{
+					if ( layout1.is_similar( layout2 ) )
+					{
+						match_found = true;
+						break;
+					}
+				}
+				if ( !match_found )
+				{
+					return false;
+				}
+
+			}
+
+			return true;
+		}
+
+
+		public void add_frame( TemplateFrame frame )
+		{
+			frames.append( frame );
+		}
+
+
+		public void add_category( string category_id )
+		{
+			category_ids.append( category_id );
+		}
+
+
+		public void init_preview_pixbuf()
+		{
+			MiniPreviewPixbuf pb = new MiniPreviewPixbuf( this, PREVIEW_PIXBUF_SIZE, PREVIEW_PIXBUF_SIZE );
+
+			preview_pixbuf = pb.pixbuf;
+		}
+
+
+	}
+
+}
diff --git a/libglabels/template_coord.vala b/libglabels/template_coord.vala
new file mode 100644
index 0000000..902167c
--- /dev/null
+++ b/libglabels/template_coord.vala
@@ -0,0 +1,70 @@
+/*  template_coord.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public struct TemplateCoord
+	{
+		public double   x { get; set; } /* x coordinate of label left edge relative to left edge of paper */
+		public double   y { get; set; } /* y coordinate of label top edge relative to top edge of paper */
+
+		public TemplateCoord( double x,
+		                      double y )
+		{
+			this.x = x;
+			this.y = y;
+		}
+
+		public int compare( TemplateCoord b )
+		{
+			if ( this.y < b.y )
+			{
+				return -1;
+			}
+			else if ( this.y > b.y )
+			{
+				return 1;
+			}
+			else
+			{
+				if ( this.x < b.x )
+				{
+					return -1;
+				}
+				else if ( this.x > b.x )
+				{
+					return 1;
+				}
+				else
+				{
+					return 0; /* hopefull 2 label frames won't have the same origin. */
+				}
+			}
+		}
+
+
+	}
+
+}
+
diff --git a/libglabels/template_frame.vala b/libglabels/template_frame.vala
new file mode 100644
index 0000000..c87274b
--- /dev/null
+++ b/libglabels/template_frame.vala
@@ -0,0 +1,123 @@
+/*  template_frame.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public abstract class TemplateFrame
+	{
+		public string      id    { get; protected set; default = "0"; }
+
+		public unowned List<TemplateLayout> layouts { get; protected set; }
+		public unowned List<TemplateMarkup> markups { get; protected set; }
+
+
+		public abstract TemplateFrame dup();
+
+		public abstract void get_size( out double w, out double h );
+
+		public abstract bool is_similar( TemplateFrame frame2 );
+
+		public abstract string get_size_description( Units units );
+
+		public abstract void cairo_path( Cairo.Context cr,
+		                                 bool          waste_flag );
+
+
+		public int get_n_labels()
+		{
+			int n_labels = 0;
+
+			foreach ( TemplateLayout layout in layouts )
+			{
+				n_labels += layout.nx * layout.ny;
+			}
+
+			return n_labels;
+		}
+
+
+		public string get_layout_description()
+		{
+			string description;
+			int    n_labels = get_n_labels();
+
+			if ( layouts.length() == 1 )
+			{
+				TemplateLayout layout = layouts.first().data;
+
+				/*
+				 * Translators: 1st %d = number of labels across a page,
+				 *              2nd %d = number of labels down a page,
+				 *              3rd %d = total number of labels on a page (sheet).
+				 */
+				description = _("%d à %d (%d per sheet)").printf( layout.nx, layout.ny, n_labels);
+			}
+			else
+			{
+				/* Translators: %d is the total number of labels on a page (sheet). */
+				description = _("%d per sheet").printf( n_labels );
+			}
+
+			return description;
+		}
+
+
+		public Gee.ArrayList<TemplateCoord?> get_origins()
+		{
+			Gee.ArrayList<TemplateCoord?> origins = new Gee.ArrayList<TemplateCoord?>();
+
+			foreach (TemplateLayout layout in layouts)
+			{
+				for ( int iy = 0; iy < layout.ny; iy++ )
+				{
+					for ( int ix = 0; ix < layout.nx; ix++ )
+					{
+						origins.add( TemplateCoord( ix*layout.dx + layout.x0,
+						                            iy*layout.dy + layout.y0 ) );
+					}
+				}
+			}
+
+			origins.sort();
+
+			return origins;
+		}
+
+
+		public void add_layout( TemplateLayout layout )
+		{
+			layouts.append( layout );
+		}
+
+
+		public void add_markup( TemplateMarkup markup )
+		{
+			markups.append( markup );
+		}
+
+
+	}
+
+
+}
diff --git a/libglabels/template_frame_cd.vala b/libglabels/template_frame_cd.vala
new file mode 100644
index 0000000..679d540
--- /dev/null
+++ b/libglabels/template_frame_cd.vala
@@ -0,0 +1,182 @@
+/*  template_frame_cd.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using Math;
+
+namespace libglabels
+{
+
+	public class TemplateFrameCD : TemplateFrame
+	{
+		public double    r1      { get; protected set; }
+		public double    r2      { get; protected set; }
+
+		public double    w       { get; protected set; }
+		public double    h       { get; protected set; }
+
+		public double    waste   { get; protected set; }
+
+
+		public TemplateFrameCD( string id,
+		                        double r1,
+		                        double r2,
+		                        double w,
+		                        double h,
+		                        double waste )
+		{
+			this.id      = id;
+
+			this.r1      = r1;
+			this.r2      = r2;
+
+			this.w       = w;
+			this.h       = h;
+
+			this.waste   = waste;
+		}
+
+
+		public override TemplateFrame dup()
+		{
+			TemplateFrameCD copy = new TemplateFrameCD( id, r1, r2, w, h, waste );
+
+			foreach (TemplateLayout layout in layouts)
+			{
+				copy.add_layout( layout.dup() );
+			}
+
+			foreach (TemplateMarkup markup in markups)
+			{
+				copy.add_markup( markup.dup() );
+			}
+
+			return (TemplateFrame)copy;
+		}
+
+
+		public override void get_size( out double w,
+		                               out double h )
+		{
+			if ( this.w == 0 )
+			{
+				w = 2.0 * r1;
+			}
+			else
+			{
+				w = this.w;
+			}
+
+			if ( this.h == 0 )
+			{
+				h = 2.0 *r1;
+			}
+			else
+			{
+				h = this.h;
+			}
+		}
+
+
+		public override bool is_similar( TemplateFrame frame2 )
+		{
+			if ( frame2 is TemplateFrameCD )
+			{
+				TemplateFrameCD cd2 = (TemplateFrameCD) frame2;
+
+				if ( (fabs( w  - cd2.w )  <= EPSILON) &&
+				     (fabs( h  - cd2.h )  <= EPSILON) &&
+				     (fabs( r1 - cd2.r1 ) <= EPSILON) &&
+				     (fabs( r2 - cd2.r2 ) <= EPSILON) )
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		public override string  get_size_description( Units units )
+		{
+			string description;
+
+			string units_string    = units.name;
+			double units_per_point = units.units_per_point;
+
+			if ( units.id == "in" )
+			{
+				string dstr = StrUtil.format_fraction( 2 * r1 * units_per_point );
+
+				description = "%s %s %s".printf( dstr, units_string, _("diameter") );
+			}
+			else
+			{
+				description = "%.5g %s %s".printf( 2 * r1 * units_per_point,
+				                                   units_string,
+				                                   _("diameter") );
+			}
+
+			return description;
+		}
+
+
+		public override void cairo_path( Cairo.Context cr,
+		                                 bool          waste_flag )
+		{
+			double w, h;
+
+			get_size( out w, out h );
+
+			double xc = w/2.0;
+			double yc = h/2.0;
+
+			double waste = 0.0;
+			if (waste_flag)
+			{
+				waste = this.waste;
+			}
+
+			/*
+			 * Outer path (may be clipped in the case of a business card type CD)
+			 */
+			double theta1 = Math.acos( w / (2.0*r1) );
+			double theta2 = Math.asin( h / (2.0*r1) );
+
+			cr.new_path();
+			cr.arc( xc, yc, r1+waste, theta1, theta2 );
+			cr.arc( xc, yc, r1+waste, Math.PI-theta2, Math.PI-theta1 );
+			cr.arc( xc, yc, r1+waste, Math.PI+theta1, Math.PI+theta2 );
+			cr.arc( xc, yc, r1+waste, 2*Math.PI-theta2, 2*Math.PI-theta1 );
+			cr.close_path();
+
+
+			/*
+			 * Inner path (hole)
+			 */
+			cr.new_sub_path();
+			cr.arc( xc, yc, r2-waste, 0.0, 2*Math.PI );
+			cr.close_path();
+		}
+
+
+	}
+
+}
diff --git a/libglabels/template_frame_ellipse.vala b/libglabels/template_frame_ellipse.vala
new file mode 100644
index 0000000..8e9d366
--- /dev/null
+++ b/libglabels/template_frame_ellipse.vala
@@ -0,0 +1,157 @@
+/*  template_frame_ellipse.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using Math;
+
+namespace libglabels
+{
+
+	public class TemplateFrameEllipse : TemplateFrame
+	{
+
+		private const int ARC_FINE = 2;
+
+
+		public double    w       { get; protected set; }
+		public double    h       { get; protected set; }
+
+		public double    waste   { get; protected set; }
+
+
+		public TemplateFrameEllipse( string id,
+		                             double w,
+		                             double h,
+		                             double waste )
+		{
+			this.id      = id;
+
+			this.w       = w;
+			this.h       = h;
+
+			this.waste   = waste;
+		}
+
+
+		public override TemplateFrame dup()
+		{
+			TemplateFrameEllipse copy = new TemplateFrameEllipse( id, w, h, waste );
+
+			foreach (TemplateLayout layout in layouts)
+			{
+				copy.add_layout( layout.dup() );
+			}
+
+			foreach (TemplateMarkup markup in markups)
+			{
+				copy.add_markup( markup.dup() );
+			}
+
+			return (TemplateFrame)copy;
+		}
+
+
+		public override void get_size( out double w,
+		                               out double h )
+		{
+			w = this.w;
+			h = this.h;
+		}
+
+
+		public override bool is_similar( TemplateFrame frame2 )
+		{
+			if ( frame2 is TemplateFrameEllipse )
+			{
+				TemplateFrameEllipse ellipse2 = (TemplateFrameEllipse) frame2;
+
+				if ( (fabs( w - ellipse2.w) <= EPSILON) &&
+					 (fabs( h - ellipse2.h) <= EPSILON) )
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		public override string  get_size_description( Units units )
+		{
+			string description;
+
+			string units_string    = units.name;
+			double units_per_point = units.units_per_point;
+
+			if ( units.id == "in" )
+			{
+				string xstr = StrUtil.format_fraction( w * units_per_point );
+				string ystr = StrUtil.format_fraction( h * units_per_point );
+
+				description = "%s à %s %s".printf( xstr, ystr, units_string );
+			}
+			else
+			{
+				description = "%.5g à %.5g %s".printf( w * units_per_point,
+													   h * units_per_point,
+													   units_string );
+			}
+
+			return description;
+		}
+
+
+		public override void cairo_path( Cairo.Context cr,
+		                                 bool          waste_flag )
+		{
+			double w, h;
+
+			w = this.w;
+			h = this.h;
+
+			double waste = 0.0;
+			if (waste_flag)
+			{
+				waste = this.waste;
+			}
+
+			cr.save();
+
+			cr.translate( -waste, -waste );
+			double rx = (w+waste)/2.0;
+			double ry = (h+waste)/2.0;
+
+			cr.new_path();
+			cr.move_to( 2*rx, ry );
+			for ( int i_theta = ARC_FINE; i_theta <= 360; i_theta += ARC_FINE )
+			{
+				double x = rx + rx*Math.cos( i_theta * Math.PI / 180.0 );
+				double y = ry + ry*Math.sin( i_theta * Math.PI / 180.0 );
+				cr.line_to( x, y );
+			}
+			cr.close_path();
+
+			cr.restore();
+		}
+
+
+	}
+
+}
diff --git a/libglabels/template_frame_rect.vala b/libglabels/template_frame_rect.vala
new file mode 100644
index 0000000..818ae27
--- /dev/null
+++ b/libglabels/template_frame_rect.vala
@@ -0,0 +1,160 @@
+/*  template_frame_rect.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using Math;
+
+namespace libglabels
+{
+
+	public class TemplateFrameRect : TemplateFrame
+	{
+		public double    w       { get; protected set; }
+		public double    h       { get; protected set; }
+
+		public double    r       { get; protected set; }
+
+		public double    x_waste { get; protected set; }
+		public double    y_waste { get; protected set; }
+
+
+		public TemplateFrameRect( string id,
+		                          double w,
+		                          double h,
+		                          double r,
+		                          double x_waste,
+		                          double y_waste )
+		{
+			this.id      = id;
+
+			this.w       = w;
+			this.h       = h;
+
+			this.r       = r;
+
+			this.x_waste = x_waste;
+			this.y_waste = y_waste;
+		}
+
+
+		public override TemplateFrame dup()
+		{
+			TemplateFrameRect copy = new TemplateFrameRect( id, w, h, r, x_waste, y_waste );
+
+			foreach (TemplateLayout layout in layouts)
+			{
+				copy.add_layout( layout.dup() );
+			}
+
+			foreach (TemplateMarkup markup in markups)
+			{
+				copy.add_markup( markup.dup() );
+			}
+
+			return (TemplateFrame)copy;
+		}
+
+
+		public override void get_size( out double w,
+		                               out double h )
+		{
+			w = this.w;
+			h = this.h;
+		}
+
+
+		public override bool is_similar( TemplateFrame frame2 )
+		{
+			if ( frame2 is TemplateFrameRect )
+			{
+				TemplateFrameRect rect2 = (TemplateFrameRect) frame2;
+
+				if ( (fabs( w - rect2.w) <= EPSILON) &&
+					 (fabs( h - rect2.h) <= EPSILON) )
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		public override string  get_size_description( Units units )
+		{
+			string description;
+
+			string units_string    = units.name;
+			double units_per_point = units.units_per_point;
+
+			if ( units.id == "in" )
+			{
+				string xstr = StrUtil.format_fraction( w * units_per_point );
+				string ystr = StrUtil.format_fraction( h * units_per_point );
+
+				description = "%s à %s %s".printf( xstr, ystr, units_string );
+			}
+			else
+			{
+				description = "%.5g à %.5g %s".printf( w * units_per_point,
+													   h * units_per_point,
+													   units_string );
+			}
+
+			return description;
+		}
+
+
+		public override void cairo_path( Cairo.Context cr,
+		                                 bool          waste_flag )
+		{
+			double x_waste = 0.0;
+			double y_waste = 0.0;
+
+			double w, h;
+
+			w = this.w;
+			h = this.h;
+
+			if (waste_flag)
+			{
+				x_waste = this.x_waste;
+				y_waste = this.y_waste;
+			}
+
+			if ( r == 0.0 )
+			{
+				cr.rectangle( -x_waste, -y_waste, w+2*x_waste, h+2*y_waste );
+			}
+			else
+			{
+				cr.new_path();
+				cr.arc_negative( r-x_waste,   r-y_waste,   r, 3*Math.PI/2, Math.PI );
+				cr.arc_negative( r-x_waste,   h-r+y_waste, r, Math.PI,     Math.PI/2 );
+				cr.arc_negative( w-r+x_waste, h-r+y_waste, r, Math.PI/2,   0.0 );
+				cr.arc_negative( w-r+x_waste, r-y_waste,   r, 2*Math.PI,   3*Math.PI/2 );
+				cr.close_path();
+			}
+		}
+
+
+	}
+
+}
diff --git a/libglabels/template_frame_round.vala b/libglabels/template_frame_round.vala
new file mode 100644
index 0000000..be47c76
--- /dev/null
+++ b/libglabels/template_frame_round.vala
@@ -0,0 +1,128 @@
+/*  template_frame_round.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using Math;
+
+namespace libglabels
+{
+
+	public class TemplateFrameRound : TemplateFrame
+	{
+		public double    r       { get; protected set; }
+
+		public double    waste   { get; protected set; }
+
+
+		public TemplateFrameRound( string id,
+		                           double r,
+		                           double waste )
+		{
+			this.id      = id;
+
+			this.r       = r;
+
+			this.waste   = waste;
+		}
+
+
+		public override TemplateFrame dup()
+		{
+			TemplateFrameRound copy = new TemplateFrameRound( id, r, waste );
+
+			foreach (TemplateLayout layout in layouts)
+			{
+				copy.add_layout( layout.dup() );
+			}
+
+			foreach (TemplateMarkup markup in markups)
+			{
+				copy.add_markup( markup.dup() );
+			}
+
+			return (TemplateFrame)copy;
+		}
+
+
+		public override void get_size( out double w,
+		                               out double h )
+		{
+			w = h = 2.0 * r;
+		}
+
+
+		public override bool is_similar( TemplateFrame frame2 )
+		{
+			if ( frame2 is TemplateFrameRound )
+			{
+				TemplateFrameRound round2 = (TemplateFrameRound) frame2;
+
+				if ( fabs( r - round2.r) <= EPSILON )
+				{
+					return true;
+				}
+			}
+			return false;
+		}
+
+
+		public override string  get_size_description( Units units )
+		{
+			string description;
+
+			string units_string    = units.name;
+			double units_per_point = units.units_per_point;
+
+			if ( units.id == "in" )
+			{
+				string dstr = StrUtil.format_fraction( 2 * r * units_per_point );
+
+				description = "%s %s %s".printf( dstr, units_string, _("diameter") );
+			}
+			else
+			{
+				description = "%.5g %s %s".printf( 2 * r * units_per_point,
+												   units_string,
+												   _("diameter") );
+			}
+
+			return description;
+		}
+
+
+		public override void cairo_path( Cairo.Context cr,
+		                                 bool          waste_flag )
+		{
+			double waste = 0.0;
+			if (waste_flag)
+			{
+				waste = this.waste;
+			}
+
+			cr.new_path();
+			cr.arc( r, r, r+waste, 0.0, 2*Math.PI );
+			cr.close_path();
+		}
+
+
+	}
+
+}
diff --git a/libglabels/template_layout.vala b/libglabels/template_layout.vala
new file mode 100644
index 0000000..a27d855
--- /dev/null
+++ b/libglabels/template_layout.vala
@@ -0,0 +1,86 @@
+/*  template_layout.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+using Math;
+
+namespace libglabels
+{
+
+	public class TemplateLayout
+	{
+		public int    nx    { get; protected set; }
+		public int    ny    { get; protected set; }
+
+		public double x0    { get; protected set; }
+		public double y0    { get; protected set; }
+
+		public double dx    { get; protected set; }
+		public double dy    { get; protected set; }
+
+
+		public TemplateLayout( int    nx,
+		                       int    ny,
+		                       double x0,
+		                       double y0,
+		                       double dx,
+		                       double dy )
+		{
+			this.nx = nx;
+			this.ny = ny;
+
+			this.x0 = x0;
+			this.y0 = y0;
+
+			this.dx = dx;
+			this.dy = dy;
+		}
+
+
+		public TemplateLayout dup()
+		{
+			TemplateLayout copy = new TemplateLayout( nx, ny, x0, y0, dx, dy );
+
+			return copy;
+		}
+
+
+		public bool is_similar( TemplateLayout layout2 )
+		{
+			if ( (nx == layout2.nx)                &&
+			     (ny == layout2.ny)                &&
+			     (fabs(x0 - layout2.x0) < EPSILON) &&
+			     (fabs(y0 - layout2.y0) < EPSILON) &&
+			     (fabs(dx - layout2.dx) < EPSILON) &&
+			     (fabs(dy - layout2.dy) < EPSILON) )
+			{
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}
+
+			
+	}
+
+}
diff --git a/libglabels/libglabels-private.h b/libglabels/template_markup.vala
similarity index 50%
rename from libglabels/libglabels-private.h
rename to libglabels/template_markup.vala
index 43ea4cd..88dbb63 100644
--- a/libglabels/libglabels-private.h
+++ b/libglabels/template_markup.vala
@@ -1,6 +1,6 @@
-/*
- *  libglabels-private.h
- *  Copyright (C) 2004-2009  Jim Evins <evins snaught com>.
+/*  template_markup.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
  *
  *  This file is part of libglabels.
  *
@@ -18,32 +18,18 @@
  *  along with libglabels.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __LIBGLABELS_PRIVATE_H__
-#define __LIBGLABELS_PRIVATE_H__
-
-#include <glib.h>
-
-#include "lgl-str.h"
-#include "lgl-template.h"
-
-#undef  G_LOG_DOMAIN
-#define G_LOG_DOMAIN "LibGlabels"
 
-#define UTF8_EQUAL(s1,s2) (!lgl_str_utf8_casecmp (s1, s2))
-#define ASCII_EQUAL(s1,s2) (!g_ascii_strcasecmp (s1, s2))
+using GLib;
 
-void _lgl_db_register_template_internal (const lglTemplate   *template);
+namespace libglabels
+{
 
+	public abstract class TemplateMarkup
+	{
+		public abstract TemplateMarkup dup();
 
-#endif /* __LIBGLABELS_PRIVATE_H__ */
+		public abstract void cairo_path( Cairo.Context cr,
+		                                 TemplateFrame frame );
+	}
 
-
-
-/*
- * Local Variables:       -- emacs
- * mode: C                -- emacs
- * c-basic-offset: 8      -- emacs
- * tab-width: 8           -- emacs
- * indent-tabs-mode: nil  -- emacs
- * End:                   -- emacs
- */
+}
diff --git a/libglabels/template_markup_circle.vala b/libglabels/template_markup_circle.vala
new file mode 100644
index 0000000..ece0477
--- /dev/null
+++ b/libglabels/template_markup_circle.vala
@@ -0,0 +1,63 @@
+/*  template_markup_circle.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public class TemplateMarkupCircle : TemplateMarkup
+	{
+		public double x0  { get; private set; }
+		public double y0  { get; private set; }
+
+		public double r   { get; private set; }
+
+
+		public TemplateMarkupCircle( double x0,
+		                             double y0,
+		                             double r )
+		{
+			this.x0 = x0;
+			this.y0 = y0;
+
+			this.r  = r;
+		}
+
+
+		public override TemplateMarkup dup()
+		{
+			TemplateMarkupCircle copy = new TemplateMarkupCircle( x0, y0, r );
+
+			return (TemplateMarkup)copy;
+		}
+
+
+		public override void cairo_path( Cairo.Context cr,
+		                                 TemplateFrame frame )
+		{
+			cr.arc( x0, y0, r, 0, 2*Math.PI );
+			cr.close_path();
+		}
+
+	}
+
+}
diff --git a/libglabels/template_markup_ellipse.vala b/libglabels/template_markup_ellipse.vala
new file mode 100644
index 0000000..988980a
--- /dev/null
+++ b/libglabels/template_markup_ellipse.vala
@@ -0,0 +1,83 @@
+/*  template_markup_ellipse.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public class TemplateMarkupEllipse : TemplateMarkup
+	{
+		private const int ARC_FINE = 2;
+
+
+		public double x1  { get; private set; }
+		public double y1  { get; private set; }
+
+		public double w   { get; private set; }
+		public double h   { get; private set; }
+
+
+		public TemplateMarkupEllipse( double x1,
+		                              double y1,
+		                              double w,
+		                              double h )
+		{
+			this.x1 = x1;
+			this.y1 = y1;
+
+			this.w  = w;
+			this.h  = h;
+		}
+
+
+		public override TemplateMarkup dup()
+		{
+			TemplateMarkupEllipse copy = new TemplateMarkupEllipse( x1, y1, w, h );
+
+			return (TemplateMarkup)copy;
+		}
+
+
+		public override void cairo_path( Cairo.Context cr,
+		                                 TemplateFrame frame )
+		{
+			cr.save();
+
+			cr.translate( x1, y1 );
+
+			cr.new_path();
+			cr.move_to( w, h/2 );
+			for ( int i_theta = ARC_FINE; i_theta <= 360; i_theta += ARC_FINE )
+			{
+				double x = (w/2) + (w/2) * Math.cos( i_theta * Math.PI / 180 );
+				double y = (h/2) + (h/2) * Math.sin( i_theta * Math.PI / 180 );
+
+				cr.line_to( x, y );
+			}
+			cr.close_path();
+
+			cr.restore();
+		}
+
+	}
+
+}
diff --git a/libglabels/template_markup_line.vala b/libglabels/template_markup_line.vala
new file mode 100644
index 0000000..d2ef427
--- /dev/null
+++ b/libglabels/template_markup_line.vala
@@ -0,0 +1,66 @@
+/*  template_markup_line.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public class TemplateMarkupLine : TemplateMarkup
+	{
+		public double x1  { get; private set; }
+		public double y1  { get; private set; }
+
+		public double x2  { get; private set; }
+		public double y2  { get; private set; }
+
+
+		public TemplateMarkupLine( double x1,
+		                           double y1,
+		                           double x2,
+		                           double y2 )
+		{
+			this.x1 = x1;
+			this.y1 = y1;
+
+			this.x2 = x2;
+			this.y2 = y2;
+		}
+
+
+		public override TemplateMarkup dup()
+		{
+			TemplateMarkupLine copy = new TemplateMarkupLine( x1, y1, x2, y2 );
+
+			return (TemplateMarkup)copy;
+		}
+
+
+		public override void cairo_path( Cairo.Context cr,
+		                                 TemplateFrame frame )
+		{
+			cr.move_to( x1, y1 );
+			cr.line_to( x2, y2 );
+		}
+
+	}
+
+}
diff --git a/libglabels/template_markup_margin.vala b/libglabels/template_markup_margin.vala
new file mode 100644
index 0000000..66475c7
--- /dev/null
+++ b/libglabels/template_markup_margin.vala
@@ -0,0 +1,171 @@
+/*  template_markup_margin.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public class TemplateMarkupMargin : TemplateMarkup
+	{
+		private const int ARC_FINE = 2;
+
+
+		public double size  { get; private set; } /* Margin size */
+
+
+		public TemplateMarkupMargin( double size )
+		{
+			this.size = size;
+		}
+
+
+		public override TemplateMarkup dup()
+		{
+			TemplateMarkupMargin copy = new TemplateMarkupMargin( size );
+
+			return (TemplateMarkup)copy;
+		}
+
+
+		public override void cairo_path( Cairo.Context cr,
+		                                 TemplateFrame frame )
+		{
+			if ( frame is TemplateFrameRect )
+			{
+				rect_cairo_path( cr, (TemplateFrameRect)frame );
+			}
+			else if ( frame is TemplateFrameEllipse )
+			{
+				ellipse_cairo_path( cr, (TemplateFrameEllipse)frame );
+			}
+			else if ( frame is TemplateFrameRound )
+			{
+				round_cairo_path( cr, (TemplateFrameRound)frame );
+			}
+			else if ( frame is TemplateFrameCD )
+			{
+				cd_cairo_path( cr, (TemplateFrameCD)frame );
+			}
+			else
+			{
+				assert_not_reached();
+			}
+		}
+
+
+		private void rect_cairo_path( Cairo.Context     cr,
+		                              TemplateFrameRect frame )
+		{
+			double w, h;
+			frame.get_size( out w, out h );
+
+			w = w - 2*size;
+			h = h - 2*size;
+			double r = double.max( frame.r - size, 0.0);
+
+			if ( r == 0 )
+			{
+				cr.rectangle( size, size, w, h );
+			}
+			else
+			{
+				cr.new_path();
+				cr.arc_negative( size+r,   size+r,   r, 3*Math.PI/2, Math.PI );
+				cr.arc_negative( size+r,   size+h-r, r, Math.PI,     Math.PI/2 );
+				cr.arc_negative( size+w-r, size+h-r, r, Math.PI/2,   0 );
+				cr.arc_negative( size+w-r, size+r,   r, 2*Math.PI,   3*Math.PI/2 );
+				cr.close_path();
+			}
+		}
+
+
+		private void ellipse_cairo_path( Cairo.Context        cr,
+		                                 TemplateFrameEllipse frame )
+		{
+			double w, h;
+			frame.get_size( out w, out h );
+
+			w = w - 2*size;
+			h = h - 2*size;
+
+			cr.save();
+			cr.translate(size, size);
+
+			cr.new_path();
+			cr.move_to( w, h/2 );
+			for ( int i_theta = ARC_FINE; i_theta <= 360; i_theta += ARC_FINE )
+			{
+				double x = (w/2) + (w/2) * Math.cos( i_theta * Math.PI / 180 );
+				double y = (h/2) + (h/2) * Math.sin( i_theta * Math.PI / 180 );
+
+				cr.line_to( x, y );
+			}
+			cr.close_path();
+
+			cr.restore();
+		}
+
+
+		private void round_cairo_path( Cairo.Context      cr,
+		                               TemplateFrameRound frame )
+		{
+			cr.arc( frame.r, frame.r, frame.r-size, 0, 2*Math.PI );
+			cr.close_path();
+		}
+
+
+		private void cd_cairo_path( Cairo.Context   cr,
+		                            TemplateFrameCD frame )
+		{
+			double w, h;
+			frame.get_size( out w, out h );
+
+			double xc = w/2;
+			double yc = h/2;
+
+			double r1 = frame.r1 - size;
+			double r2 = frame.r2 + size;
+
+			/*
+			 * Outer path (may be clipped)
+			 */
+			double theta1 = Math.acos( (w-2*size) / (2*r1) );
+			double theta2 = Math.asin( (h-2*size) / (2*r1) );
+
+			cr.new_path();
+			cr.arc( xc, yc, r1, theta1,           theta2 );
+			cr.arc( xc, yc, r1, Math.PI-theta2,   Math.PI-theta1 );
+			cr.arc( xc, yc, r1, Math.PI+theta1,   Math.PI+theta2 );
+			cr.arc( xc, yc, r1, 2*Math.PI-theta2, 2*Math.PI-theta1 );
+			cr.close_path();
+
+
+			/* Inner path (hole) */
+			cr.new_sub_path();
+			cr.arc( xc, yc, r2, 0.0, 2*Math.PI );
+			cr.close_path();
+		}
+
+
+	}
+
+}
diff --git a/libglabels/template_markup_rect.vala b/libglabels/template_markup_rect.vala
new file mode 100644
index 0000000..c14f72b
--- /dev/null
+++ b/libglabels/template_markup_rect.vala
@@ -0,0 +1,82 @@
+/*  template_markup_rect.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public class TemplateMarkupRect : TemplateMarkup
+	{
+		public double x1  { get; private set; }
+		public double y1  { get; private set; }
+
+		public double w   { get; private set; }
+		public double h   { get; private set; }
+
+		public double r   { get; private set; }
+
+
+		public TemplateMarkupRect( double x1,
+		                           double y1,
+		                           double w,
+		                           double h,
+		                           double r )
+		{
+			this.x1 = x1;
+			this.y1 = y1;
+
+			this.w  = w;
+			this.h  = h;
+
+			this.r  = r;
+		}
+
+
+		public override TemplateMarkup dup()
+		{
+			TemplateMarkupRect copy = new TemplateMarkupRect( x1, y1, w, h, r );
+
+			return (TemplateMarkup)copy;
+		}
+
+
+		public override void cairo_path( Cairo.Context cr,
+		                                 TemplateFrame frame )
+		{
+			if ( r == 0 )
+			{
+				cr.rectangle( x1, y1, w, h );
+			}
+			else
+			{
+				cr.new_path();
+				cr.arc_negative( x1+r,   y1+r,   r, 3*Math.PI/2, Math.PI );
+				cr.arc_negative( x1+r,   y1+h-r, r, Math.PI,     Math.PI/2 );
+				cr.arc_negative( x1+w-r, y1+h-r, r, Math.PI/2,   0 );
+				cr.arc_negative( x1+w-r, y1+r,   r, 2*Math.PI,   3*Math.PI/2 );
+				cr.close_path();
+			}
+		}
+
+	}
+
+}
diff --git a/libglabels/units.vala b/libglabels/units.vala
new file mode 100644
index 0000000..70b1b2d
--- /dev/null
+++ b/libglabels/units.vala
@@ -0,0 +1,137 @@
+/*  units.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	private const double POINTS_PER_POINT =  1.0; /* internal units are points. */
+	private const double POINTS_PER_INCH  = 72.0;
+	private const double POINTS_PER_MM    =  2.83464566929;
+	private const double POINTS_PER_CM    =  (10.0*POINTS_PER_MM);
+	private const double POINTS_PER_PICA  =  (1.0/12.0);
+
+
+	public struct Units
+	{
+		public string id              { get; protected set; }
+		public string name            { get; protected set; }
+		public double points_per_unit { get; protected set; }
+
+		public double units_per_point { get{ return 1.0 / points_per_unit; } }
+
+
+		private Units( string id,
+		               string name,
+		               double points_per_unit )
+		{
+			this.id              = id;
+			this.name            = name;
+			this.points_per_unit = points_per_unit;
+		}
+
+
+		public Units.from_id( string id )
+		{
+			string name;
+			double points_per_unit;
+
+			switch (id)
+			{
+
+			case "pt":
+				name            = _("points");
+				points_per_unit = POINTS_PER_POINT;
+				break;
+
+			case "in":
+				name            = _("inches");
+				points_per_unit = POINTS_PER_INCH;
+				break;
+
+			case "mm":
+				name            = _("mm");
+				points_per_unit = POINTS_PER_MM;
+				break;
+
+			case "cm":
+				name            = _("cm");
+				points_per_unit = POINTS_PER_CM;
+				break;
+
+			case "pc":
+				name            = _("picas");
+				points_per_unit = POINTS_PER_PICA;
+				break;
+
+			case "":
+				/* Missing or empty units id defaults to points. */
+				id              = "pt";
+				name            = _("points");
+				points_per_unit = POINTS_PER_POINT;
+				break;
+
+			default:
+				warning( "Unknown Units.id \"%s\"", id );
+				id              = "pt";
+				name            = _("points");
+				points_per_unit = POINTS_PER_POINT;
+				break;
+
+			}
+
+			this( id, name, points_per_unit );
+		}
+
+		public Units.point()
+		{
+			this( "pt", _("points"), POINTS_PER_POINT );
+		}
+
+
+		public Units.inch()
+		{
+			this( "in", _("inches"), POINTS_PER_INCH );
+		}
+
+
+		public Units.mm()
+		{
+			this( "mm", _("mm"), POINTS_PER_MM );
+		}
+
+
+		public Units.cm()
+		{
+			this( "cm", _("cm"), POINTS_PER_CM );
+		}
+
+
+		public Units.pica()
+		{
+			this( "pc", _("picas"), POINTS_PER_PICA );
+		}
+
+
+	}
+
+}
diff --git a/libglabels/lgl-xml-paper.h b/libglabels/vendor.vala
similarity index 50%
rename from libglabels/lgl-xml-paper.h
rename to libglabels/vendor.vala
index e4704c6..269072a 100644
--- a/libglabels/lgl-xml-paper.h
+++ b/libglabels/vendor.vala
@@ -1,6 +1,6 @@
-/*
- *  lgl-xml-paper.h
- *  Copyright (C) 2003-2010  Jim Evins <evins snaught com>.
+/*  vendor.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
  *
  *  This file is part of libglabels.
  *
@@ -18,34 +18,25 @@
  *  along with libglabels.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef __LGL_XML_PAPER_H__
-#define __LGL_XML_PAPER_H__
-
-#include <glib.h>
-#include <libxml/tree.h>
-
-#include "lgl-paper.h"
-
-G_BEGIN_DECLS
 
-GList       *lgl_xml_paper_read_papers_from_file (gchar        *utf8_filename);
+using GLib;
 
-GList       *lgl_xml_paper_parse_papers_doc      (xmlDocPtr     papers_doc);
+namespace libglabels
+{
 
-lglPaper    *lgl_xml_paper_parse_paper_node      (xmlNodePtr    paper_node);
+	public struct Vendor
+	{
+		public string   name { get; private set; } /* Vendor name */
+		public string   url  { get; private set; } /* Vendor URL */
 
+		public Vendor( string name,
+		               string url )
+		{
+			this.name = name;
+			this.url  = url;
+		}
 
-G_END_DECLS
+	}
 
-#endif /* __LGL_XML_PAPER_H__ */
+}
 
-
-
-/*
- * Local Variables:       -- emacs
- * mode: C                -- emacs
- * c-basic-offset: 8      -- emacs
- * tab-width: 8           -- emacs
- * indent-tabs-mode: nil  -- emacs
- * End:                   -- emacs
- */
diff --git a/libglabels/xml_category.vala b/libglabels/xml_category.vala
new file mode 100644
index 0000000..634426a
--- /dev/null
+++ b/libglabels/xml_category.vala
@@ -0,0 +1,104 @@
+/*  xml_category.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	namespace XmlCategory
+	{
+
+		internal void read_categories_from_file( string utf8_filename )
+		{
+			string filename;
+			try
+			{
+				filename = Filename.from_utf8( utf8_filename, -1, null, null );
+			}
+			catch ( ConvertError e )
+			{
+				message("Utf8 filename conversion: %s", e.message);
+				return;
+			}
+
+			unowned Xml.Doc* doc = Xml.Parser.parse_file( filename );
+			if ( doc == null )
+			{
+				message("\"%s\" is not a glabels category file (not XML)", filename);
+				return;
+			}
+
+			parse_categories_doc( doc );
+		}
+
+
+		internal void parse_categories_doc( Xml.Doc doc )
+		{
+			unowned Xml.Node* root = doc.get_root_element();
+			if ( (root == null) || (root->name == null) )
+			{
+				message("\"%s\" is not a glabels category file (no root node)", doc.name);
+				return;
+			}
+
+			if ( root->name != "Glabels-categories" )
+			{
+				message ("\"%s\" is not a glabels category file (wrong root node)", doc.name);
+				return;
+			}
+
+			for ( unowned Xml.Node* node = root->children; node != null; node = node->next )
+			{
+				if ( node->name == "Category" )
+				{
+					Category category = parse_category_node( node );
+					Db.register_category( category );
+				}
+				else
+				{
+					if ( node->is_text() == 0 )
+					{
+						if ( node->name != "comment" )
+						{
+							message("bad node = \"%s\"", node->name);
+						}
+					}
+				}
+			}
+
+		}
+
+
+		internal Category parse_category_node( Xml.Node node )
+		{
+			string? id       = XmlUtil.get_prop_string( node, "id", null );
+			string? name     = XmlUtil.get_prop_i18n_string( node, "name", null );
+
+			Category category = Category( id, name );
+
+			return category;
+		}
+
+
+	}
+
+}
diff --git a/libglabels/xml_paper.vala b/libglabels/xml_paper.vala
new file mode 100644
index 0000000..ac72d8a
--- /dev/null
+++ b/libglabels/xml_paper.vala
@@ -0,0 +1,109 @@
+/*  xml_paper.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	namespace XmlPaper
+	{
+
+		internal void read_papers_from_file( string utf8_filename )
+		{
+			string filename;
+			try
+			{
+				filename = Filename.from_utf8( utf8_filename, -1, null, null );
+			}
+			catch ( ConvertError e )
+			{
+				message("Utf8 filename conversion: %s", e.message);
+				return;
+			}
+
+			unowned Xml.Doc* doc = Xml.Parser.parse_file( filename );
+			if ( doc == null )
+			{
+				message("\"%s\" is not a glabels paper file (not XML)", filename);
+				return;
+			}
+
+			parse_papers_doc( doc );
+		}
+
+
+		internal void parse_papers_doc( Xml.Doc doc )
+		{
+			unowned Xml.Node* root = doc.get_root_element();
+			if ( (root == null) || (root->name == null) )
+			{
+				message("\"%s\" is not a glabels paper file (no root node)", doc.name);
+				return;
+			}
+
+			if ( root->name != "Glabels-paper-sizes" )
+			{
+				message ("\"%s\" is not a glabels paper file (wrong root node)", doc.name);
+				return;
+			}
+
+			for ( unowned Xml.Node* node = root->children; node != null; node = node->next )
+			{
+				if ( node->name == "Paper-size" )
+				{
+					Paper paper = parse_paper_node( node );
+					Db.register_paper( paper );
+				}
+				else
+				{
+					if ( node->is_text() == 0 )
+					{
+						if ( node->name != "comment" )
+						{
+							message("bad node = \"%s\"", node->name);
+						}
+					}
+				}
+			}
+
+		}
+
+
+		internal Paper parse_paper_node( Xml.Node node )
+		{
+			string? id       = XmlUtil.get_prop_string( node, "id", null );
+			string? name     = XmlUtil.get_prop_i18n_string( node, "name", null );
+
+			double width     = XmlUtil.get_prop_length( node, "width",  0 );
+			double height    = XmlUtil.get_prop_length( node, "height", 0 );
+
+			string? pwg_size = XmlUtil.get_prop_string( node, "pwg_size", null );
+
+			Paper paper = Paper( id, name, width, height, pwg_size );
+
+			return paper;
+		}
+
+
+	}
+
+}
diff --git a/libglabels/xml_template.vala b/libglabels/xml_template.vala
new file mode 100644
index 0000000..aff1719
--- /dev/null
+++ b/libglabels/xml_template.vala
@@ -0,0 +1,744 @@
+/*  xml_template.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public const string NAME_SPACE = "http://glabels.org/xmlns/3.0/";;
+
+	namespace XmlTemplate
+	{
+
+		internal void read_templates_from_file( string utf8_filename )
+		{
+			string filename;
+			try
+			{
+				filename = Filename.from_utf8( utf8_filename, -1, null, null );
+			}
+			catch ( ConvertError e )
+			{
+				message("Utf8 filename conversion: %s", e.message);
+				return;
+			}
+
+			unowned Xml.Doc* doc = Xml.Parser.parse_file( filename );
+			if ( doc == null )
+			{
+				message("\"%s\" is not a glabels template file (not XML)", filename);
+				return;
+			}
+
+			parse_templates_doc( doc );
+		}
+
+
+		internal void parse_templates_doc( Xml.Doc doc )
+		{
+			unowned Xml.Node* root = doc.get_root_element();
+			if ( (root == null) || (root->name == null) )
+			{
+				message("\"%s\" is not a glabels template file (no root node)", doc.name);
+				return;
+			}
+
+			if ( root->name != "Glabels-templates" )
+			{
+				message ("\"%s\" is not a glabels template file (wrong root node)", doc.name);
+				return;
+			}
+
+			for ( unowned Xml.Node* node = root->children; node != null; node = node->next )
+			{
+				if ( node->name == "Template" )
+				{
+					Template template = parse_template_node( node );
+					Db.register_template( template );
+				}
+				else
+				{
+					if ( node->is_text() == 0 )
+					{
+						if ( node->name != "comment" )
+						{
+							message("bad node = \"%s\"", node->name);
+						}
+					}
+				}
+			}
+
+		}
+
+
+		public Template? parse_template_node( Xml.Node template_node )
+		{
+			Template? template = null;
+
+			string? brand = XmlUtil.get_prop_string( template_node, "brand", null );
+			string? part  = XmlUtil.get_prop_string( template_node, "part", null );
+
+			if ( (brand == null) || (part == null) )
+			{
+				/* Try the deprecated "name" property. */
+				string? name = XmlUtil.get_prop_string( template_node, "name", null );
+				if ( name != null )
+				{
+					string[] fields = name.split( " ", 2 );
+					brand = fields[0];
+					part  = fields[1];
+				}
+				else
+				{
+					message("Missing name or brand/part attributes.");
+					return null;
+				}
+			}
+
+
+			string? equiv_part = XmlUtil.get_prop_string( template_node, "equiv", null );
+
+			if ( equiv_part != null )
+			{
+
+				template = new Template.from_equiv( brand, part, equiv_part );
+
+				for ( unowned Xml.Node* node = template_node.children; node != null; node = node->next )
+				{
+					if ( node->name == "Meta" )
+					{
+						parse_meta_node( node, template );
+					}
+					else if ( node->is_text() == 0 )
+					{
+						if ( node->name != "comment" )
+						{
+							message("bad node = \"%s\"", node->name);
+						}
+					}
+				}
+
+			}
+			else
+			{
+
+				string? description = XmlUtil.get_prop_i18n_string( template_node, "description", null );
+				string? paper_id    = XmlUtil.get_prop_string( template_node, "size", null );
+
+				if ( !Db.is_paper_id_other( paper_id ) )
+				{
+					Paper? paper = Db.lookup_paper_from_id( paper_id );
+					if ( paper == null )
+					{
+						message("Invalid paper ID \"%s\".", paper_id );
+						return null;
+					}
+
+					template = new Template( brand, part, description,
+					                         paper.id, paper.width, paper.height );
+				}
+				else
+				{
+					double width  = XmlUtil.get_prop_length( template_node, "width", 0 );
+					double height = XmlUtil.get_prop_length( template_node, "height", 0 );
+					template = new Template( brand, part, description,
+					                         paper_id, width, height );
+				}
+
+				for ( unowned Xml.Node* node = template_node.children; node != null; node = node->next )
+				{
+					if ( node->name == "Meta" )
+					{
+						parse_meta_node( node, template );
+					}
+					else if ( node->name == "Label-rectangle" )
+					{
+						parse_label_rectangle_node( node, template );
+					}
+					else if ( node->name == "Label-ellipse" )
+					{
+						parse_label_ellipse_node( node, template );
+					}
+					else if ( node->name == "Label-round" )
+					{
+						parse_label_round_node( node, template );
+					}
+					else if ( node->name == "Label-cd" )
+					{
+						parse_label_cd_node( node, template );
+					}
+					else if ( node->is_text() == 0 )
+					{
+						if ( node->name != "comment" )
+						{
+							message("bad node = \"%s\"", node->name);
+						}
+					}
+				}
+
+				template.init_preview_pixbuf();
+
+			}
+
+			return template;
+		}
+
+
+		private void parse_meta_node( Xml.Node meta_node,
+		                              Template template )
+		{
+			string? product_url = XmlUtil.get_prop_string( meta_node, "product_url", null );
+			if ( product_url != null )
+			{
+				template.product_url = product_url;
+			}
+			
+			string? category_id = XmlUtil.get_prop_string( meta_node, "category", null );
+			if ( category_id != null )
+			{
+				template.add_category( category_id );
+			}
+
+			parse_empty_node_common( meta_node );
+		}
+
+
+		private void parse_label_rectangle_node( Xml.Node label_node,
+		                                         Template template )
+		{
+			double x_waste, y_waste;
+
+			string? id = XmlUtil.get_prop_string( label_node, "id", null );
+
+			double w = XmlUtil.get_prop_length( label_node, "width", 0 );
+			double h = XmlUtil.get_prop_length( label_node, "height", 0 );
+			double r = XmlUtil.get_prop_length( label_node, "round", 0 );
+
+			if ( XmlUtil.get_prop_string( label_node, "waste", null ) != null )
+			{
+				x_waste = y_waste = XmlUtil.get_prop_length( label_node, "waste", 0 );
+			}
+			else
+			{
+				x_waste = XmlUtil.get_prop_length( label_node, "x_waste", 0 );
+				y_waste = XmlUtil.get_prop_length( label_node, "y_waste", 0 );
+			}
+
+			TemplateFrame frame = new TemplateFrameRect( id, w, h, r, x_waste, y_waste );
+			template.add_frame( frame );
+
+			parse_label_node_common( label_node, frame );
+		}
+
+
+		private void parse_label_ellipse_node( Xml.Node label_node,
+		                                       Template template )
+		{
+			string? id = XmlUtil.get_prop_string( label_node, "id", null );
+
+			double w     = XmlUtil.get_prop_length( label_node, "width", 0 );
+			double h     = XmlUtil.get_prop_length( label_node, "height", 0 );
+			double waste = XmlUtil.get_prop_length( label_node, "waste", 0 );
+
+			TemplateFrame frame = new TemplateFrameEllipse( id, w, h, waste );
+			template.add_frame( frame );
+
+			parse_label_node_common( label_node, frame );
+		}
+
+
+		private void parse_label_round_node( Xml.Node label_node,
+		                                     Template template )
+		{
+			string? id = XmlUtil.get_prop_string( label_node, "id", null );
+
+			double r     = XmlUtil.get_prop_length( label_node, "radius", 0 );
+			double waste = XmlUtil.get_prop_length( label_node, "waste", 0 );
+
+			TemplateFrame frame = new TemplateFrameRound( id, r, waste );
+			template.add_frame( frame );
+
+			parse_label_node_common( label_node, frame );
+		}
+
+
+		private void parse_label_cd_node( Xml.Node label_node,
+		                                  Template template )
+		{
+			string? id = XmlUtil.get_prop_string( label_node, "id", null );
+
+			double r1    = XmlUtil.get_prop_length( label_node, "radius", 0 );
+			double r2    = XmlUtil.get_prop_length( label_node, "hole", 0 );
+			double w     = XmlUtil.get_prop_length( label_node, "width", 0 );
+			double h     = XmlUtil.get_prop_length( label_node, "height", 0 );
+			double waste = XmlUtil.get_prop_length( label_node, "waste", 0 );
+
+			TemplateFrame frame = new TemplateFrameCD( id, r1, r2, w, h, waste );
+			template.add_frame( frame );
+
+			parse_label_node_common( label_node, frame );
+		}
+
+
+		private void parse_label_node_common( Xml.Node      label_node,
+		                                      TemplateFrame frame )
+		{
+			for ( unowned Xml.Node* node = label_node.children; node != null; node = node->next )
+			{
+				if ( node->name == "Layout" )
+				{
+					parse_layout_node( node, frame );
+				}
+				else if ( node->name == "Markup-margin" )
+				{
+					parse_markup_margin_node( node, frame );
+				}
+				else if ( node->name == "Markup-line" )
+				{
+					parse_markup_line_node( node, frame );
+				}
+				else if ( node->name == "Markup-circle" )
+				{
+					parse_markup_circle_node( node, frame );
+				}
+				else if ( node->name == "Markup-rect" )
+				{
+					parse_markup_rect_node( node, frame );
+				}
+				else if ( node->name == "Markup-ellipse" )
+				{
+					parse_markup_ellipse_node( node, frame );
+				}
+				else if ( node->is_text() == 0 )
+				{
+					if ( node->name != "comment" )
+					{
+						message("bad node = \"%s\"", node->name);
+					}
+				}
+
+			}
+		}
+
+
+		private void parse_layout_node( Xml.Node      label_node,
+		                                TemplateFrame frame )
+		{
+			int nx    = XmlUtil.get_prop_int( label_node, "nx", 1 );
+			int ny    = XmlUtil.get_prop_int( label_node, "ny", 1 );
+
+			double x0 = XmlUtil.get_prop_length( label_node, "x0", 0 );
+			double y0 = XmlUtil.get_prop_length( label_node, "y0", 0 );
+
+			double dx = XmlUtil.get_prop_length( label_node, "dx", 0 );
+			double dy = XmlUtil.get_prop_length( label_node, "dy", 0 );
+
+			frame.add_layout( new TemplateLayout( nx, ny, x0, y0, dx, dy ) );
+
+			parse_empty_node_common( label_node );
+		}
+
+
+		private void parse_markup_margin_node( Xml.Node      label_node,
+		                                       TemplateFrame frame )
+		{
+			double size = XmlUtil.get_prop_length( label_node, "size", 0 );
+
+			frame.add_markup( new TemplateMarkupMargin( size ) );
+
+			parse_empty_node_common( label_node );
+		}
+
+
+		private void parse_markup_line_node( Xml.Node      label_node,
+		                                     TemplateFrame frame )
+		{
+			double x1 = XmlUtil.get_prop_length( label_node, "x1", 0 );
+			double y1 = XmlUtil.get_prop_length( label_node, "y1", 0 );
+			double x2 = XmlUtil.get_prop_length( label_node, "x2", 0 );
+			double y2 = XmlUtil.get_prop_length( label_node, "y2", 0 );
+
+			frame.add_markup( new TemplateMarkupLine( x1, y1, x2, y2 ) );
+
+			parse_empty_node_common( label_node );
+		}
+
+
+		private void parse_markup_circle_node( Xml.Node      label_node,
+		                                       TemplateFrame frame )
+		{
+			double x0 = XmlUtil.get_prop_length( label_node, "x0", 0 );
+			double y0 = XmlUtil.get_prop_length( label_node, "y0", 0 );
+			double r  = XmlUtil.get_prop_length( label_node, "radius", 0 );
+
+			frame.add_markup( new TemplateMarkupCircle( x0, y0, r ) );
+
+			parse_empty_node_common( label_node );
+		}
+
+
+		private void parse_markup_rect_node( Xml.Node      label_node,
+		                                     TemplateFrame frame )
+		{
+			double x1 = XmlUtil.get_prop_length( label_node, "x1", 0 );
+			double y1 = XmlUtil.get_prop_length( label_node, "y1", 0 );
+			double w  = XmlUtil.get_prop_length( label_node, "w", 0 );
+			double h  = XmlUtil.get_prop_length( label_node, "h", 0 );
+			double r  = XmlUtil.get_prop_length( label_node, "r", 0 );
+
+			frame.add_markup( new TemplateMarkupRect( x1, y1, w, h, r ) );
+
+			parse_empty_node_common( label_node );
+		}
+
+
+		private void parse_markup_ellipse_node( Xml.Node      label_node,
+		                                        TemplateFrame frame )
+		{
+			double x1 = XmlUtil.get_prop_length( label_node, "x1", 0 );
+			double y1 = XmlUtil.get_prop_length( label_node, "y1", 0 );
+			double w  = XmlUtil.get_prop_length( label_node, "w", 0 );
+			double h  = XmlUtil.get_prop_length( label_node, "h", 0 );
+
+			frame.add_markup( new TemplateMarkupEllipse( x1, y1, w, h ) );
+
+			parse_empty_node_common( label_node );
+		}
+
+
+		private void parse_empty_node_common( Xml.Node empty_node )
+		{
+			for ( unowned Xml.Node* node = empty_node.children; node != null; node = node->next )
+			{
+				if ( node->is_text() == 0 )
+				{
+					if ( node->name != "comment" )
+					{
+						message("bad node = \"%s\"", node->name);
+					}
+				}
+
+			}
+		}
+
+
+		internal int write_templates_to_file( List<Template> templates,
+		                                      string         utf8_filename )
+		{
+			Xml.Doc doc = new Xml.Doc( "1.0" );
+			unowned Xml.Node *root_node = new Xml.Node(null, "Glabels-templates");
+			doc.set_root_element( root_node );
+			unowned Xml.Ns *ns = new Xml.Ns( root_node, NAME_SPACE, "glabels" );
+			root_node->ns = ns;
+
+			foreach ( Template template in templates )
+			{
+				create_template_node( template, root_node, ns );
+			}
+
+			string filename;
+			try
+			{
+				filename = Filename.from_utf8( utf8_filename, -1, null, null );
+			}
+			catch ( ConvertError e )
+			{
+				message("Utf8 filename conversion: %s", e.message);
+				return 0;
+			}
+
+			doc.set_compress_mode( 0 );
+			return doc.save_format_file( filename, 1 );
+		}
+
+
+		internal int write_template_to_file( Template template,
+		                                     string   utf8_filename )
+		{
+			List<Template> templates = null;
+
+			templates.append( template );
+
+			return write_templates_to_file( templates, utf8_filename );
+		}
+
+
+		public void create_template_node( Template template,
+		                                  Xml.Node root,
+		                                  Xml.Ns   ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Template" );
+
+			XmlUtil.set_prop_string( node, "brand", template.brand );
+			XmlUtil.set_prop_string( node, "part",  template.part );
+
+			XmlUtil.set_prop_string( node, "size", template.paper_id );
+			if ( template.paper_id == "Other" )
+			{
+				XmlUtil.set_prop_length( node, "width",  template.page_width );
+				XmlUtil.set_prop_length( node, "height", template.page_height );
+			}
+
+			XmlUtil.set_prop_string( node, "description", template.description );
+
+			create_meta_node( "product_url", template.product_url, node, ns );
+			foreach ( string category_id in template.category_ids )
+			{
+				create_meta_node( "category", category_id, node, ns );
+			}
+
+			foreach ( TemplateFrame frame in template.frames )
+			{
+				create_label_node( frame, node, ns );
+			}
+		}
+
+
+		private void create_meta_node( string    attr,
+		                               string?   value,
+		                               Xml.Node  root,
+		                               Xml.Ns    ns )
+		{
+			if ( value != null )
+			{
+				unowned Xml.Node *node = root.new_child( ns, "Meta" );
+				XmlUtil.set_prop_string( node, attr, value );
+			}
+		}
+
+
+		private void create_label_node( TemplateFrame frame,
+		                                Xml.Node      root,
+		                                Xml.Ns        ns )
+		{
+			if ( frame is TemplateFrameRect )
+			{
+				create_label_rectangle_node( (TemplateFrameRect)frame, root, ns );
+			}
+			else if ( frame is TemplateFrameEllipse )
+			{
+				create_label_ellipse_node( (TemplateFrameEllipse)frame, root, ns );
+			}
+			else if ( frame is TemplateFrameRound )
+			{
+				create_label_round_node( (TemplateFrameRound)frame, root, ns );
+			}
+			else if ( frame is TemplateFrameCD )
+			{
+				create_label_cd_node( (TemplateFrameCD)frame, root, ns );
+			}
+			else
+			{
+				error( "Unknown label style" );
+			}
+
+		}
+
+
+		private void create_label_rectangle_node( TemplateFrameRect frame,
+		                                          Xml.Node          root,
+		                                          Xml.Ns            ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Label-rectangle" );
+
+			XmlUtil.set_prop_string( node, "id",      frame.id );
+			XmlUtil.set_prop_length( node, "width",   frame.w );
+			XmlUtil.set_prop_length( node, "height",  frame.h );
+			XmlUtil.set_prop_length( node, "round",   frame.r );
+			XmlUtil.set_prop_length( node, "x_waste", frame.x_waste );
+			XmlUtil.set_prop_length( node, "y_waste", frame.y_waste );
+
+			create_label_node_common( frame, node, ns );
+		}
+
+
+		private void create_label_ellipse_node( TemplateFrameEllipse frame,
+		                                        Xml.Node             root,
+		                                        Xml.Ns               ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Label-ellipse" );
+
+			XmlUtil.set_prop_string( node, "id",      frame.id );
+			XmlUtil.set_prop_length( node, "width",   frame.w );
+			XmlUtil.set_prop_length( node, "height",  frame.h );
+			XmlUtil.set_prop_length( node, "waste",   frame.waste );
+
+			create_label_node_common( frame, node, ns );
+		}
+
+
+		private void create_label_round_node( TemplateFrameRound frame,
+		                                      Xml.Node           root,
+		                                      Xml.Ns             ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Label-round" );
+
+			XmlUtil.set_prop_string( node, "id",      frame.id );
+			XmlUtil.set_prop_length( node, "radius",  frame.r );
+			XmlUtil.set_prop_length( node, "waste",   frame.waste );
+
+			create_label_node_common( frame, node, ns );
+		}
+
+
+		private void create_label_cd_node( TemplateFrameCD frame,
+		                                   Xml.Node        root,
+		                                   Xml.Ns          ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Label-cd" );
+
+			XmlUtil.set_prop_string( node, "id",      frame.id );
+			XmlUtil.set_prop_length( node, "radius",  frame.r1 );
+			XmlUtil.set_prop_length( node, "hole",    frame.r2 );
+			XmlUtil.set_prop_length( node, "waste",   frame.waste );
+			if ( frame.w != 0 )
+			{
+				XmlUtil.set_prop_length( node, "width",   frame.w );
+			}
+			if ( frame.h != 0 )
+			{
+				XmlUtil.set_prop_length( node, "height",  frame.h );
+			}
+
+			create_label_node_common( frame, node, ns );
+		}
+
+
+		private void create_label_node_common( TemplateFrame   frame,
+		                                       Xml.Node        node,
+		                                       Xml.Ns          ns )
+		{
+			foreach ( TemplateMarkup markup in frame.markups )
+			{
+				if ( markup is TemplateMarkupMargin )
+				{
+					create_markup_margin_node( (TemplateMarkupMargin)markup, node, ns );
+				}
+				else if ( markup is TemplateMarkupLine )
+				{
+					create_markup_line_node( (TemplateMarkupLine)markup, node, ns );
+				}
+				else if ( markup is TemplateMarkupCircle )
+				{
+					create_markup_circle_node( (TemplateMarkupCircle)markup, node, ns );
+				}
+				else if ( markup is TemplateMarkupRect )
+				{
+					create_markup_rect_node( (TemplateMarkupRect)markup, node, ns );
+				}
+				else if ( markup is TemplateMarkupEllipse )
+				{
+					create_markup_ellipse_node( (TemplateMarkupEllipse)markup, node, ns );
+				}
+				else
+				{
+					error( "Unknown markup type" );
+				}
+			}
+
+			foreach ( TemplateLayout layout in frame.layouts )
+			{
+				create_layout_node( layout, node, ns );
+			}
+		}
+
+
+		private void create_markup_margin_node( TemplateMarkupMargin   markup,
+		                                        Xml.Node               root,
+		                                        Xml.Ns                 ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Markup-margin" );
+
+			XmlUtil.set_prop_length( node, "size", markup.size );
+		}
+
+
+		private void create_markup_line_node( TemplateMarkupLine   markup,
+		                                      Xml.Node             root,
+		                                      Xml.Ns               ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Markup-line" );
+
+			XmlUtil.set_prop_length( node, "x1", markup.x1 );
+			XmlUtil.set_prop_length( node, "y1", markup.y1 );
+			XmlUtil.set_prop_length( node, "x2", markup.x2 );
+			XmlUtil.set_prop_length( node, "y2", markup.y2 );
+		}
+
+
+		private void create_markup_circle_node( TemplateMarkupCircle   markup,
+		                                        Xml.Node               root,
+		                                        Xml.Ns                 ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Markup-circle" );
+
+			XmlUtil.set_prop_length( node, "x0",     markup.x0 );
+			XmlUtil.set_prop_length( node, "y0",     markup.y0 );
+			XmlUtil.set_prop_length( node, "radius", markup.r );
+		}
+
+
+		private void create_markup_rect_node( TemplateMarkupRect   markup,
+		                                      Xml.Node             root,
+		                                      Xml.Ns               ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Markup-rect" );
+
+			XmlUtil.set_prop_length( node, "x1", markup.x1 );
+			XmlUtil.set_prop_length( node, "y1", markup.y1 );
+			XmlUtil.set_prop_length( node, "w",  markup.w );
+			XmlUtil.set_prop_length( node, "h",  markup.h );
+			XmlUtil.set_prop_length( node, "r",  markup.r );
+		}
+
+
+		private void create_markup_ellipse_node( TemplateMarkupEllipse  markup,
+		                                         Xml.Node               root,
+		                                         Xml.Ns                 ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Markup-ellipse" );
+
+			XmlUtil.set_prop_length( node, "x1", markup.x1 );
+			XmlUtil.set_prop_length( node, "y1", markup.y1 );
+			XmlUtil.set_prop_length( node, "w",  markup.w );
+			XmlUtil.set_prop_length( node, "h",  markup.h );
+		}
+
+
+		private void create_layout_node( TemplateLayout  layout,
+		                                 Xml.Node        root,
+		                                 Xml.Ns          ns )
+		{
+			unowned Xml.Node *node = root.new_child( ns, "Layout" );
+
+			XmlUtil.set_prop_int(    node, "nx", layout.nx );
+			XmlUtil.set_prop_int(    node, "ny", layout.ny );
+			XmlUtil.set_prop_length( node, "x0", layout.x0 );
+			XmlUtil.set_prop_length( node, "y0", layout.y0 );
+			XmlUtil.set_prop_length( node, "dx", layout.dx );
+			XmlUtil.set_prop_length( node, "dy", layout.dy );
+		}
+
+
+	}
+
+}
diff --git a/libglabels/xml_util.vala b/libglabels/xml_util.vala
new file mode 100644
index 0000000..b36d4ee
--- /dev/null
+++ b/libglabels/xml_util.vala
@@ -0,0 +1,244 @@
+/*  xml_util.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	public class XmlUtil
+	{
+		public static Units default_units { get; set; }
+
+
+		public static void init()
+		{
+			/* Force construction of static fields. */
+			new XmlUtil();
+		}
+
+
+		static construct {
+			default_units = Units.point();
+		}
+
+
+		public static string? get_prop_string( Xml.Node node,
+		                                       string   property,
+		                                       string?  default_val )
+		{
+			string? val;
+
+			val = node.get_prop( property );
+			if ( val != null )
+			{
+				return val;
+			}
+
+			return default_val;
+		}
+
+
+		public static string? get_prop_i18n_string( Xml.Node node,
+		                                            string   property,
+		                                            string?  default_val )
+		{
+			string  i18n_property = "_%s".printf( property );
+			string? val;
+
+			val = node.get_prop( i18n_property );
+
+			if ( val != null )
+			{
+				return dgettext( null, val );
+			}
+
+			val = node.get_prop( property );
+			if ( val != null )
+			{
+				return val;
+			}
+
+			return default_val;
+		}
+
+
+		public static double get_prop_double( Xml.Node node,
+		                                      string   property,
+		                                      double   default_val )
+		{
+			string? val_string;
+
+			val_string = node.get_prop( property );
+			if ( val_string != null )
+			{
+				return double.parse( val_string );
+			}
+
+			return default_val;
+		}
+
+
+		public static bool get_prop_bool( Xml.Node node,
+		                                  string   property,
+		                                  bool     default_val )
+		{
+			string? val_string;
+
+			val_string = node.get_prop( property );
+			if ( val_string != null )
+			{
+				switch (val_string)
+				{
+				case "True":
+				case "TRUE":
+				case "true":
+				case "1":
+					return true;
+
+				default:
+					return bool.parse( val_string );
+				}
+			}
+
+			return default_val;
+		}
+
+
+		public static int get_prop_int( Xml.Node node,
+		                                string   property,
+		                                int      default_val )
+		{
+			string? val_string;
+
+			val_string = node.get_prop( property );
+			if ( val_string != null )
+			{
+				return int.parse( val_string );
+			}
+
+			return default_val;
+		}
+
+
+		public static uint get_prop_uint( Xml.Node node,
+		                                  string   property,
+		                                  uint     default_val )
+		{
+			string? val_string;
+
+			val_string = node.get_prop( property );
+			if ( val_string != null )
+			{
+				return (uint)uint64.parse( val_string );
+			}
+
+			return default_val;
+		}
+
+
+		public static double get_prop_length( Xml.Node node,
+		                                      string   property,
+		                                      double   default_val )
+		{
+			string? val_string;
+
+			val_string = node.get_prop( property );
+			if ( val_string != null )
+			{
+				double val;
+				string units_id = string.nfill( val_string.length, 0 );
+
+				val_string.scanf( "%lg%s", out val, units_id );
+
+				Units units = Units.from_id( units_id );
+				val *= units.points_per_unit;
+
+				return val;
+			}
+
+			return default_val;
+		}
+
+
+		public static void set_prop_string( Xml.Node node,
+		                                    string   property,
+		                                    string?  val )
+		{
+			if ( val != null )
+			{
+				node.set_prop( property, val );
+			}
+		}
+
+
+		public static void set_prop_double( Xml.Node node,
+		                                    string   property,
+		                                    double   val )
+		{
+			string val_string = "%g".printf( val );
+			node.set_prop( property, val_string );
+		}
+
+
+		public static void set_prop_bool( Xml.Node node,
+		                                  string   property,
+		                                  bool   val )
+		{
+			node.set_prop( property, val ? "true" : "false" );
+		}
+
+
+		public static void set_prop_int( Xml.Node node,
+		                                 string   property,
+		                                 int      val )
+		{
+			string val_string = "%d".printf( val );
+			node.set_prop( property, val_string );
+		}
+
+
+		public static void set_prop_uint_hex( Xml.Node node,
+		                                      string   property,
+		                                      uint     val )
+		{
+			string val_string = "0x%08x".printf( val );
+			node.set_prop( property, val_string );
+		}
+
+
+		public static void set_prop_length( Xml.Node node,
+		                                    string   property,
+		                                    double   val )
+		{
+			string units_id = default_units.id;
+			double units_per_point = default_units.units_per_point;
+
+			val *= units_per_point;
+
+			string val_string = "%g%s".printf( val, units_id );
+			node.set_prop( property, val_string );
+		}
+
+
+	}
+
+}
\ No newline at end of file
diff --git a/libglabels/xml_vendor.vala b/libglabels/xml_vendor.vala
new file mode 100644
index 0000000..683470c
--- /dev/null
+++ b/libglabels/xml_vendor.vala
@@ -0,0 +1,104 @@
+/*  xml_vendor.vala
+ *
+ *  Copyright (C) 2011  Jim Evins <evins snaught com>
+ *
+ *  This file is part of libglabels.
+ *
+ *  libglabels 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 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  libglabels 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 libglabels.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+using GLib;
+
+namespace libglabels
+{
+
+	namespace XmlVendor
+	{
+
+		internal void read_vendors_from_file( string utf8_filename )
+		{
+			string filename;
+			try
+			{
+				filename = Filename.from_utf8( utf8_filename, -1, null, null );
+			}
+			catch ( ConvertError e )
+			{
+				message("Utf8 filename conversion: %s", e.message);
+				return;
+			}
+
+			unowned Xml.Doc* doc = Xml.Parser.parse_file( filename );
+			if ( doc == null )
+			{
+				message("\"%s\" is not a glabels vendor file (not XML)", filename);
+				return;
+			}
+
+			parse_vendors_doc( doc );
+		}
+
+
+		internal void parse_vendors_doc( Xml.Doc doc )
+		{
+			unowned Xml.Node* root = doc.get_root_element();
+			if ( (root == null) || (root->name == null) )
+			{
+				message("\"%s\" is not a glabels vendor file (no root node)", doc.name);
+				return;
+			}
+
+			if ( root->name != "Glabels-vendors" )
+			{
+				message ("\"%s\" is not a glabels vendor file (wrong root node)", doc.name);
+				return;
+			}
+
+			for ( unowned Xml.Node* node = root->children; node != null; node = node->next )
+			{
+				if ( node->name == "Vendor" )
+				{
+					Vendor vendor = parse_vendor_node( node );
+					Db.register_vendor( vendor );
+				}
+				else
+				{
+					if ( node->is_text() == 0 )
+					{
+						if ( node->name != "comment" )
+						{
+							message("bad node = \"%s\"", node->name);
+						}
+					}
+				}
+			}
+
+		}
+
+
+		internal Vendor parse_vendor_node( Xml.Node node )
+		{
+			string? name     = XmlUtil.get_prop_string( node, "name", null );
+			string? url      = XmlUtil.get_prop_string( node, "url", null );
+
+			Vendor vendor = Vendor( name, url );
+
+			return vendor;
+		}
+
+
+	}
+
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 24905c7..a849cb5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,252 +1,15 @@
-# List of source files containing translatable strings.
-
-src/bc-backends.c
-src/bc-backends.h
-src/bc-builtin.c
-src/bc-builtin.h
-src/bc-gnubarcode.c
-src/bc-gnubarcode.h
-src/bc-iec16022.c
-src/bc-iec16022.h
-src/bc-iec18004.c
-src/bc-iec18004.h
-src/bc-zint.c
-src/bc-zint.h
-src/builder-util.c
-src/builder-util.h
-src/cairo-ellipse-path.c
-src/cairo-ellipse-path.h
-src/cairo-label-path.c
-src/cairo-label-path.h
-src/cairo-markup-path.c
-src/cairo-markup-path.h
-src/color.c
-src/color.h
-src/color-combo-button.c
-src/color-combo-button.h
-src/color-combo.c
-src/color-combo-color-menu-item.c
-src/color-combo-color-menu-item.h
-src/color-combo.h
-src/color-combo-menu.c
-src/color-combo-menu.h
-src/color-history-model.c
-src/color-history-model.h
-src/color-swatch.c
-src/color-swatch.h
-src/combo-util.c
-src/combo-util.h
-src/critical-error-handler.c
-src/critical-error-handler.h
-src/debug.c
-src/debug.h
-src/field-button.c
-src/field-button.h
-src/field-button-menu.c
-src/field-button-menu.h
-src/file.c
-src/file.h
-src/file-util.c
-src/file-util.h
-src/font-combo.c
-src/font-combo.h
-src/font-combo-menu.c
-src/font-combo-menu.h
-src/font-combo-menu-item.c
-src/font-combo-menu-item.h
-src/font-history.c
-src/font-history.h
-src/font-history-model.c
-src/font-history-model.h
-src/font-sample.c
-src/font-sample.h
-src/font-util.c
-src/font-util.h
-src/glabels-batch.c
-src/glabels.c
-src/label-barcode.c
-src/label-barcode.h
-src/label-box.c
-src/label-box.h
-src/label.c
-src/label-ellipse.c
-src/label-ellipse.h
-src/label.h
-src/label-image.c
-src/label-image.h
-src/label-line.c
-src/label-line.h
-src/label-object.c
-src/label-object.h
-src/label-text.c
-src/label-text.h
-src/media-select.c
-src/media-select.h
-src/merge.c
-src/merge.h
-src/merge-evolution.c
-src/merge-evolution.h
-src/merge-init.c
-src/merge-init.h
-src/merge-properties-dialog.c
-src/merge-properties-dialog.h
-src/merge-text.c
-src/merge-text.h
-src/merge-vcard.c
-src/merge-vcard.h
-src/mini-label-preview.c
-src/mini-label-preview.h
-src/mini-preview.c
-src/mini-preview.h
-src/mini-preview-pixbuf.c
-src/mini-preview-pixbuf-cache.c
-src/mini-preview-pixbuf-cache.h
-src/mini-preview-pixbuf.h
-src/new-label-dialog.c
-src/new-label-dialog.h
-src/object-editor-bc-page.c
-src/object-editor.c
-src/object-editor-data-page.c
-src/object-editor-edit-page.c
-src/object-editor-fill-page.c
-src/object-editor.h
-src/object-editor-image-page.c
-src/object-editor-line-page.c
-src/object-editor-lsize-page.c
-src/object-editor-position-page.c
-src/object-editor-private.h
-src/object-editor-shadow-page.c
-src/object-editor-size-page.c
-src/object-editor-text-page.c
-src/pixbuf-cache.c
-src/pixbuf-cache.h
-src/prefs.c
-src/prefs.h
-src/prefs-dialog.c
-src/prefs-dialog.h
-src/prefs-model.c
-src/prefs-model.h
-src/print.c
-src/print.h
-src/print-op.c
-src/print-op-dialog.c
-src/print-op-dialog.h
-src/print-op.h
-src/message-bar.c
-src/message-bar.h
-src/recent.c
-src/recent.h
-src/str-util.c
-src/str-util.h
-src/svg-cache.c
-src/svg-cache.h
-src/template-designer.c
-src/template-designer.h
-src/template-history.c
-src/template-history.h
-src/template-history-model.c
-src/template-history-model.h
-src/text-node.c
-src/text-node.h
-src/ui.c
-src/ui.h
-src/ui-commands.c
-src/ui-commands.h
-src/ui-property-bar.c
-src/ui-property-bar.h
-src/ui-sidebar.c
-src/ui-sidebar.h
-src/ui-util.c
-src/ui-util.h
-src/units-util.c
-src/units-util.h
-src/view.c
-src/view.h
-src/view-barcode.c
-src/view-barcode.h
-src/view-box.c
-src/view-box.h
-src/view-ellipse.c
-src/view-ellipse.h
-src/view-image.c
-src/view-image.h
-src/view-line.c
-src/view-line.h
-src/view-text.c
-src/view-text.h
-src/warning-handler.c
-src/warning-handler.h
-src/wdgt-chain-button.c
-src/wdgt-chain-button.h
-src/window.c
-src/window.h
-src/xml-label-04.c
-src/xml-label-04.h
-src/xml-label.c
-src/xml-label.h
-
-libglabels/lgl-category.c
-libglabels/lgl-category.h
-libglabels/lgl-db.c
-libglabels/lgl-db.h
-libglabels/lgl-paper.c
-libglabels/lgl-paper.h
-libglabels/lgl-str.c
-libglabels/lgl-str.h
-libglabels/lgl-template.c
-libglabels/lgl-template.h
-libglabels/lgl-units.c
-libglabels/lgl-units.h
-libglabels/lgl-xml.c
-libglabels/lgl-xml.h
-libglabels/lgl-xml-category.c
-libglabels/lgl-xml-category.h
-libglabels/lgl-xml-paper.c
-libglabels/lgl-xml-paper.h
-libglabels/lgl-xml-template.c
-libglabels/lgl-xml-template.h
-libglabels/libglabels-private.h
-
-[type: gettext/glade]data/ui/merge-properties-dialog.ui
-[type: gettext/glade]data/ui/media-select.ui
-[type: gettext/glade]data/ui/new-label-dialog.ui
-[type: gettext/glade]data/ui/object-editor.ui
-[type: gettext/glade]data/ui/prefs-dialog.ui
-[type: gettext/glade]data/ui/property-bar.ui
-[type: gettext/glade]data/ui/template-designer.ui
-[type: gettext/glade]data/ui/print-op-dialog-custom-widget.ui
-
-data/schemas/org.gnome.glabels-3.gschema.xml.in.in
-
-[type: gettext/ini]data/desktop/glabels-3.0.desktop.in
-
-[type: gettext/xml]data/mime/glabels-3.0.xml.in
-
-[type: gettext/xml]templates/paper-sizes.xml
-[type: gettext/xml]templates/categories.xml
-[type: gettext/xml]templates/ascom-iso-templates.xml
-[type: gettext/xml]templates/avery-us-templates.xml
-[type: gettext/xml]templates/avery-iso-templates.xml
-[type: gettext/xml]templates/avery-other-templates.xml
-[type: gettext/xml]templates/brother-other-templates.xml
-[type: gettext/xml]templates/databecker-iso-templates.xml
-[type: gettext/xml]templates/dataline-iso-templates.xml
-[type: gettext/xml]templates/decadry-iso-templates.xml
-[type: gettext/xml]templates/desmat-templates.xml
-[type: gettext/xml]templates/dymo-other-templates.xml
-[type: gettext/xml]templates/endisch-templates.xml
-[type: gettext/xml]templates/geha-iso-templates.xml
-[type: gettext/xml]templates/hama-iso-templates.xml
-[type: gettext/xml]templates/herma-iso-templates.xml
-[type: gettext/xml]templates/jac-iso-templates.xml
-[type: gettext/xml]templates/maco-us-templates.xml
-[type: gettext/xml]templates/meritline-us-templates.xml
-[type: gettext/xml]templates/microapp-templates.xml
-[type: gettext/xml]templates/misc-us-templates.xml
-[type: gettext/xml]templates/misc-iso-templates.xml
-[type: gettext/xml]templates/misc-other-templates.xml
-[type: gettext/xml]templates/pearl-iso-templates.xml
-[type: gettext/xml]templates/sheetlabels-us-templates.xml
-[type: gettext/xml]templates/uline-us-templates.xml
-[type: gettext/xml]templates/worldlabel-us-templates.xml
-[type: gettext/xml]templates/zweckform-iso-templates.xml
+[encoding: UTF-8]
+# List of source files which contain translatable strings.
+glabels/color.vala
+glabels/color_button.vala
+glabels/color_history.vala
+glabels/color_menu.vala
+glabels/color_menu_item.vala
+glabels/color_swatch.vala
+glabels/font_button.vala
+glabels/font_families.vala
+glabels/font_history.vala
+glabels/font_menu.vala
+glabels/font_menu_item.vala
+glabels/font_sample.vala
+glabels/test.vala
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index bdb5b80..e69de29 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -1,3 +0,0 @@
-# List of source files containing translatable strings that should be ignored.
-
-data/schemas/org.gnome.glabels-3.gschema.xml.in
diff --git a/vapi/Makefile.am b/vapi/Makefile.am
new file mode 100644
index 0000000..29cd822
--- /dev/null
+++ b/vapi/Makefile.am
@@ -0,0 +1,5 @@
+noinst_DATA = \
+	config.vapi
+
+EXTRA_DIST = \
+	$(noinst_DATA)
diff --git a/vapi/config.vapi b/vapi/config.vapi
new file mode 100644
index 0000000..97bb6dd
--- /dev/null
+++ b/vapi/config.vapi
@@ -0,0 +1,18 @@
+[CCode (prefix = "", lower_case_cprefix = "", cheader_filename = "config.h")]
+namespace Config
+{
+	/* Package information */
+	public const string PACKAGE_NAME;
+	public const string PACKAGE_STRING;
+	public const string PACKAGE_VERSION;
+
+	/* Gettext package */
+	public const string GETTEXT_PACKAGE;
+
+	/* Configured paths - these variables are not present in config.h, they are
+	 * passed to underlying C code as cmd line macros. */
+	public const string LOCALEDIR;  /* /usr/local/share/locale  */
+	public const string DATADIR; /* /usr/local/share */
+	public const string GLABELS_BRANCH;
+	public const string LIBGLABELS_BRANCH;
+}



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