[glabels/vala] Added image object support.



commit 843e9e53bb083f8b40dac8ac19d4e4eb6c1ddc50
Author: Jim Evins <evins snaught com>
Date:   Sun Aug 26 11:52:58 2012 -0400

    Added image object support.

 configure.ac                      |    2 +
 data/ui/object_editor.ui          |  169 ++++++++++++++--
 glabels/Makefile.am               |    6 +
 glabels/TMP_gdk_key.vala          |   19 --
 glabels/TMP_gdk_pixdata.vapi      |    4 +
 glabels/field_button.vala         |    1 +
 glabels/label.vala                |   25 ++-
 glabels/label_object.vala         |    6 +
 glabels/label_object_box.vala     |    7 +
 glabels/label_object_ellipse.vala |    7 +
 glabels/label_object_image.vala   |  401 +++++++++++++++++++++++++++++++++++++
 glabels/label_object_line.vala    |    7 +
 glabels/label_object_text.vala    |    7 +
 glabels/object_editor.vala        |  120 +++++++++++
 glabels/pixbuf_cache.vala         |  125 ++++++++++++
 glabels/pixmaps.vapi              |   13 ++
 glabels/pixmaps/checkerboard.xpm  |   29 +++
 glabels/svg_cache.vala            |  191 ++++++++++++++++++
 glabels/ui.vala                   |   21 +--
 glabels/view.vala                 |   28 +++-
 glabels/xml_label.vala            |  281 ++++++++++++++++++++++++--
 21 files changed, 1390 insertions(+), 79 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 629f957..12ac679 100644
--- a/configure.ac
+++ b/configure.ac
@@ -75,6 +75,7 @@ LIBXML_REQUIRED=2.7.8
 GEE_REQUIRED=0.6.2.1
 CAIRO_REQUIRED=1.10.2
 GDK_PIXBUF_REQUIRED=2.24.0
+LIBRSVG_REQUIRED=2.36.0
 
 PKG_CHECK_MODULES(GLABELS, [\
 			   glib-2.0 >= $GLIB_REQUIRED \
@@ -82,6 +83,7 @@ PKG_CHECK_MODULES(GLABELS, [\
 			   gtk+-3.0 >= $GTK_REQUIRED \
 			   libxml-2.0 >= $LIBXML_REQUIRED \
 			   gee-1.0 >= $GEE_REQUIRED \
+                           librsvg-2.0 > $LIBRSVG_REQUIRED \
 ])
 
 AC_SUBST(GLABELS_CFLAGS)
diff --git a/data/ui/object_editor.ui b/data/ui/object_editor.ui
index 9489c0a..768e4f4 100644
--- a/data/ui/object_editor.ui
+++ b/data/ui/object_editor.ui
@@ -8,18 +8,16 @@
     <property name="step_increment">0.5</property>
     <property name="page_increment">10</property>
   </object>
+  <object class="GtkAdjustment" id="line_angle_adjustment">
+    <property name="upper">360</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
   <object class="GtkAdjustment" id="line_length_adjustment">
     <property name="upper">100</property>
     <property name="step_increment">0.01</property>
     <property name="page_increment">1</property>
   </object>
-  <object class="GtkAdjustment" id="line_width_adjustment">
-    <property name="lower">0.25</property>
-    <property name="upper">10</property>
-    <property name="value">1</property>
-    <property name="step_increment">0.25</property>
-    <property name="page_increment">1</property>
-  </object>
   <object class="GtkAdjustment" id="line_spacing_adjustment">
     <property name="lower">1</property>
     <property name="upper">5</property>
@@ -27,6 +25,13 @@
     <property name="step_increment">0.10000000000000001</property>
     <property name="page_increment">10</property>
   </object>
+  <object class="GtkAdjustment" id="line_width_adjustment">
+    <property name="lower">0.25</property>
+    <property name="upper">10</property>
+    <property name="value">1</property>
+    <property name="step_increment">0.25</property>
+    <property name="page_increment">1</property>
+  </object>
   <object class="GtkAdjustment" id="shadow_opacity_adjustment">
     <property name="upper">100</property>
     <property name="value">1</property>
@@ -760,6 +765,139 @@
               </packing>
             </child>
             <child>
+              <object class="GtkBox" id="image_page_box">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="valign">start</property>
+                <property name="border_width">6</property>
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <child>
+                  <object class="GtkFrame" id="frame2">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="valign">start</property>
+                    <property name="label_xalign">0</property>
+                    <property name="shadow_type">none</property>
+                    <child>
+                      <object class="GtkAlignment" id="alignment10">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="left_padding">12</property>
+                        <child>
+                          <object class="GtkGrid" id="grid9">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="valign">start</property>
+                            <property name="hexpand">True</property>
+                            <property name="row_spacing">6</property>
+                            <property name="column_spacing">12</property>
+                            <child>
+                              <object class="GtkRadioButton" id="image_file_radio">
+                                <property name="label" translatable="yes">File:</property>
+                                <property name="use_action_appearance">False</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="use_action_appearance">False</property>
+                                <property name="xalign">0</property>
+                                <property name="active">True</property>
+                                <property name="draw_indicator">True</property>
+                              </object>
+                              <packing>
+                                <property name="left_attach">0</property>
+                                <property name="top_attach">0</property>
+                                <property name="width">1</property>
+                                <property name="height">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkRadioButton" id="image_key_radio">
+                                <property name="label" translatable="yes">Key:</property>
+                                <property name="use_action_appearance">False</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="use_action_appearance">False</property>
+                                <property name="xalign">0</property>
+                                <property name="active">True</property>
+                                <property name="draw_indicator">True</property>
+                                <property name="group">image_file_radio</property>
+                              </object>
+                              <packing>
+                                <property name="left_attach">0</property>
+                                <property name="top_attach">1</property>
+                                <property name="width">1</property>
+                                <property name="height">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkFileChooserButton" id="image_filebutton">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="hexpand">True</property>
+                                <property name="orientation">vertical</property>
+                              </object>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="top_attach">0</property>
+                                <property name="width">1</property>
+                                <property name="height">1</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox" id="image_key_box">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="hexpand">True</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <placeholder/>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="top_attach">1</property>
+                                <property name="width">1</property>
+                                <property name="height">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                    <child type="label">
+                      <object class="GtkLabel" id="label18">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="label" translatable="yes">&lt;b&gt;File&lt;/b&gt;</property>
+                        <property name="use_markup">True</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child type="tab">
+              <object class="GtkLabel" id="label13">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="label" translatable="yes">Image</property>
+              </object>
+              <packing>
+                <property name="position">1</property>
+                <property name="tab_fill">False</property>
+              </packing>
+            </child>
+            <child>
               <object class="GtkBox" id="line_fill_page_box">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
@@ -948,7 +1086,7 @@
                 </child>
               </object>
               <packing>
-                <property name="position">1</property>
+                <property name="position">2</property>
               </packing>
             </child>
             <child type="tab">
@@ -959,7 +1097,7 @@
                 <property name="label" translatable="yes">Line/Fill</property>
               </object>
               <packing>
-                <property name="position">1</property>
+                <property name="position">2</property>
                 <property name="tab_fill">False</property>
               </packing>
             </child>
@@ -1405,7 +1543,7 @@
                 </child>
               </object>
               <packing>
-                <property name="position">2</property>
+                <property name="position">3</property>
               </packing>
             </child>
             <child type="tab">
@@ -1416,7 +1554,7 @@
                 <property name="label" translatable="yes">Position/Size</property>
               </object>
               <packing>
-                <property name="position">2</property>
+                <property name="position">3</property>
                 <property name="tab_fill">False</property>
               </packing>
             </child>
@@ -1637,7 +1775,7 @@
                 </child>
               </object>
               <packing>
-                <property name="position">3</property>
+                <property name="position">4</property>
                 <property name="tab_fill">False</property>
               </packing>
             </child>
@@ -1649,7 +1787,7 @@
                 <property name="label" translatable="yes">Shadow</property>
               </object>
               <packing>
-                <property name="position">3</property>
+                <property name="position">4</property>
                 <property name="tab_fill">False</property>
               </packing>
             </child>
@@ -1687,11 +1825,6 @@
       <widget name="label46"/>
     </widgets>
   </object>
-  <object class="GtkAdjustment" id="line_angle_adjustment">
-    <property name="upper">360</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>
diff --git a/glabels/Makefile.am b/glabels/Makefile.am
index 1cf97ae..ad12464 100644
--- a/glabels/Makefile.am
+++ b/glabels/Makefile.am
@@ -7,6 +7,7 @@ SUBDIRS = \
 bin_PROGRAMS = glabels-4
 
 glabels_4_SOURCES = \
+	TMP_gdk_pixdata.vapi \
 	glabels.vala \
 	color.vala \
 	color_button.vala \
@@ -34,6 +35,7 @@ glabels_4_SOURCES = \
 	label_object.vala \
 	label_object_box.vala \
 	label_object_ellipse.vala \
+	label_object_image.vala \
 	label_object_line.vala \
 	label_object_shape.vala \
 	label_object_text.vala \
@@ -54,10 +56,13 @@ glabels_4_SOURCES = \
 	new_label_dialog.vala \
 	object_editor.vala \
 	outline.vala \
+	pixbuf_cache.vala \
+	pixmaps.vapi \
 	prefs.vala \
 	print_op.vala \
 	print_op_dialog.vala \
 	property_editor.vala \
+	svg_cache.vala \
 	template_history.vala \
 	text_line.vala \
 	text_lines.vala \
@@ -88,6 +93,7 @@ VALAFLAGS = \
 	--pkg gtk+-3.0 \
 	--pkg libxml-2.0 \
 	--pkg gee-1.0 \
+	--pkg librsvg-2.0 \
 	--pkg libglabels-4 \
 	$(NULL)
 
diff --git a/glabels/TMP_gdk_pixdata.vapi b/glabels/TMP_gdk_pixdata.vapi
new file mode 100644
index 0000000..860b701
--- /dev/null
+++ b/glabels/TMP_gdk_pixdata.vapi
@@ -0,0 +1,4 @@
+namespace Gdk {
+	[CCode (cheader_filename = "gdk-pixbuf/gdk-pixdata.h", cprefix = "gdk_")]
+	public void pixdata_from_pixbuf( out Gdk.Pixdata pixdata, Gdk.Pixbuf pixbuf, bool use_rle = false );
+}
diff --git a/glabels/field_button.vala b/glabels/field_button.vala
index f3c7e35..ce77bc5 100644
--- a/glabels/field_button.vala
+++ b/glabels/field_button.vala
@@ -55,6 +55,7 @@ namespace glabels
 			}
 			else
 			{
+				klabel.set_text( _("(None)") );
 				klabel_is_key = true;
 			}
 
diff --git a/glabels/label.vala b/glabels/label.vala
index fdbbce7..0a46a06 100644
--- a/glabels/label.vala
+++ b/glabels/label.vala
@@ -41,18 +41,8 @@ namespace glabels
 
 		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
+		public PixbufCache        pixbuf_cache { get; private set; }
+		public SvgCache           svg_cache    { get; private set; }
 
 
 		/**
@@ -203,6 +193,14 @@ namespace glabels
 		public Color              default_fill_color        { get; set; }
 
 
+		private TemplateHistory    template_history;
+
+		private static int         untitled_count;
+		private int                untitled_instance;
+
+		private bool               selection_op_flag;
+		private bool               delayed_change_flag;
+
 
 		public Label()
 		{
@@ -210,6 +208,9 @@ namespace glabels
 
 			template_history = new TemplateHistory( 5 );
 
+			pixbuf_cache = new PixbufCache();
+			svg_cache    = new SvgCache();
+
 			// TODO: Set default properties from user prefs
 		}
 
diff --git a/glabels/label_object.vala b/glabels/label_object.vala
index 0cf6849..469fc86 100644
--- a/glabels/label_object.vala
+++ b/glabels/label_object.vala
@@ -156,6 +156,12 @@ namespace glabels
 
 
 		/**
+		 * Image parameters interface
+		 */
+		public virtual TextNode        filename_node       { get; set; }
+
+
+		/**
 		 * Shape parameters interface
 		 */
 		public virtual double          line_width          { get; set; }
diff --git a/glabels/label_object_box.vala b/glabels/label_object_box.vala
index af1ca2f..eae8a23 100644
--- a/glabels/label_object_box.vala
+++ b/glabels/label_object_box.vala
@@ -27,6 +27,13 @@ namespace glabels
 	public class LabelObjectBox : LabelObjectShape
 	{
 
+		public LabelObjectBox.with_parent( Label parent )
+		{
+			this();
+			parent.add_object( this );
+		}
+
+
 		public override LabelObject dup()
 		{
 			LabelObjectBox copy = new LabelObjectBox();
diff --git a/glabels/label_object_ellipse.vala b/glabels/label_object_ellipse.vala
index 7a21ed0..1e883cd 100644
--- a/glabels/label_object_ellipse.vala
+++ b/glabels/label_object_ellipse.vala
@@ -30,6 +30,13 @@ namespace glabels
 		private const int ARC_FINE = 2;
 
 
+		public LabelObjectEllipse.with_parent( Label parent )
+		{
+			this();
+			parent.add_object( this );
+		}
+
+
 		public override LabelObject dup()
 		{
 			LabelObjectEllipse copy = new LabelObjectEllipse();
diff --git a/glabels/label_object_image.vala b/glabels/label_object_image.vala
new file mode 100644
index 0000000..2180211
--- /dev/null
+++ b/glabels/label_object_image.vala
@@ -0,0 +1,401 @@
+/*  label_object_image.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 LabelObjectImage : LabelObject
+	{
+		private const double MIN_IMAGE_SIZE = 1.0;
+
+		private enum FileType { NONE, PIXBUF, SVG }
+
+
+		private static Gdk.Pixbuf? default_pixbuf = null;
+
+
+		public LabelObjectImage.with_parent( Label parent )
+		{
+			this();
+			parent.add_object( this );
+		}
+
+
+		/**
+		 * Filename
+		 */
+		public override TextNode filename_node
+		{
+			get { return _filename_node; }
+
+			set
+			{
+				if ( (_filename_node == null) || !_filename_node.equal( value ) )
+				{
+					update_filename_node( value );
+					changed();
+				}
+			}
+		}
+		private TextNode _filename_node;
+
+
+		/**
+		 * Width
+		 */
+		public override double w
+		{
+			get { return base.w; }
+
+			set
+			{
+				if ( value < MIN_IMAGE_SIZE )
+				{
+					base.w = MIN_IMAGE_SIZE;
+				}
+				else
+				{
+					base.w = value;
+				}
+			}
+		}
+
+
+		/**
+		 * Height
+		 */
+		public override double h
+		{
+			get { return base.h; }
+
+			set
+			{
+				if ( value < MIN_IMAGE_SIZE )
+				{
+					base.h = MIN_IMAGE_SIZE;
+				}
+				else
+				{
+					base.h = value;
+				}
+			}
+		}
+
+
+		private FileType type;
+
+		private Gdk.Pixbuf?  pixbuf;
+		private Rsvg.Handle? svg_handle;
+		
+
+		public LabelObjectImage()
+		{
+			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 ) );
+
+			type       = FileType.NONE;
+			pixbuf     = null;
+			svg_handle = null;
+
+			if ( default_pixbuf == null )
+			{
+				Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_xpm_data( Pixmaps.checkerboard_xpm );
+				default_pixbuf = pixbuf.scale_simple( 128, 128, Gdk.InterpType.NEAREST );
+			}
+		}
+
+
+		public override LabelObject dup()
+		{
+			LabelObjectImage copy = new LabelObjectImage();
+
+			copy.set_common_properties_from_object( this );
+
+			return copy;
+		}
+
+
+		public override void draw_object( Cairo.Context cr, bool in_editor, MergeRecord? record )
+		{
+			cr.save();
+
+			switch (type)
+			{
+
+			case FileType.PIXBUF:
+				double image_w = pixbuf.width;
+				double image_h = pixbuf.height;
+				cr.rectangle( 0, 0, w, h );
+				cr.scale( w/image_w, h/image_h );
+				Gdk.cairo_set_source_pixbuf( cr, pixbuf, 0, 0 );
+				cr.fill();
+				break;
+
+			case FileType.SVG:
+				double image_w = svg_handle.width;
+				double image_h = svg_handle.height;
+				cr.scale( w/image_w, h/image_h );
+				svg_handle.render_cairo( cr );
+				break;
+
+			default:
+				double image_w = default_pixbuf.width;
+				double image_h = default_pixbuf.height;
+				cr.rectangle( 0, 0, w, h );
+				cr.scale( w/image_w, h/image_h );
+				Gdk.cairo_set_source_pixbuf( cr, default_pixbuf, 0, 0 );
+				cr.fill();
+				break;
+
+			}
+
+			cr.restore();
+		}
+
+
+		public override void draw_shadow( Cairo.Context cr, bool in_editor, MergeRecord? record )
+		{
+			Color shadow_color = shadow_color_node.expand( record );
+			if ( shadow_color_node.field_flag && in_editor )
+			{
+				shadow_color = Color.black();
+			}
+			shadow_color.set_opacity( shadow_opacity );
+
+			cr.save();
+
+			switch (type)
+			{
+
+			case FileType.PIXBUF:
+				Gdk.Pixbuf shadow_pixbuf = shadow_pixbuf( pixbuf, shadow_color );
+				double image_w = shadow_pixbuf.width;
+				double image_h = shadow_pixbuf.height;
+				cr.rectangle( 0, 0, w, h );
+				cr.scale( w/image_w, h/image_h );
+				Gdk.cairo_set_source_pixbuf( cr, shadow_pixbuf, 0, 0 );
+				cr.fill();
+				break;
+
+			case FileType.SVG:
+				/* TODO: handle SVG shadows properly. */
+				cr.rectangle( 0, 0, w, h );
+				cr.set_source_rgba( shadow_color.r, shadow_color.g, shadow_color.b, shadow_color.a );
+				cr.fill();
+				break;
+
+			default:
+				cr.rectangle( 0, 0, w, h );
+				cr.set_source_rgba( shadow_color.r, shadow_color.g, shadow_color.b, shadow_color.a );
+				cr.fill();
+				break;
+
+			}
+
+			cr.restore();
+		}
+
+
+		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;
+			}
+
+			return false;
+		}
+
+
+		private void update_filename_node( TextNode new_node )
+		{
+			/* Remove reference to previous image. */
+			switch (type)
+			{
+			case FileType.NONE:
+				break;
+
+			case FileType.PIXBUF:
+				parent.pixbuf_cache.remove_pixbuf( _filename_node.data );
+				break;
+
+			case FileType.SVG:
+				parent.svg_cache.remove_svg( _filename_node.data );
+				break;
+
+			default:
+				assert_not_reached();
+			}
+
+			/* Update */
+			_filename_node = new_node;
+
+			/* Set the new file type and load pixbuf or SVG. */
+			if ( !_filename_node.field_flag && (_filename_node.data != null) )
+			{
+				if ( _filename_node.data.has_suffix( ".svg" ) || _filename_node.data.has_suffix( ".SVG" ) )
+				{
+					svg_handle = parent.svg_cache.get_handle( _filename_node.data );
+					if ( svg_handle != null )
+					{
+						type       = FileType.SVG;
+						pixbuf     = null;
+					}
+					else
+					{
+						type       = FileType.NONE;
+						pixbuf     = null;
+					}
+				}
+				else
+				{
+					pixbuf     = parent.pixbuf_cache.get_pixbuf( _filename_node.data );
+					if ( pixbuf != null )
+					{
+						type       = FileType.PIXBUF;
+						svg_handle = null;
+					}
+					else
+					{
+						type       = FileType.NONE;
+						svg_handle = null;
+					}
+				}
+			}
+			else
+			{
+				type       = FileType.NONE;
+				svg_handle = null;
+				pixbuf     = null;
+			}
+
+			/* Treat current size as a bounding box, scale image to maintain aspect
+			 * ratio while fitting it in this bounding box. */
+			double image_w, image_h;
+			switch ( type )
+			{
+			case FileType.PIXBUF:
+				image_w = pixbuf.width;
+				image_h = pixbuf.height;
+				break;
+			case FileType.SVG:
+				image_w = svg_handle.width;
+				image_h = svg_handle.height;
+				break;
+			default:
+				image_w = default_pixbuf.width;
+				image_h = default_pixbuf.height;
+				break;
+			}
+			double aspect_ratio = image_h / image_w;
+			if ( h > w*aspect_ratio )
+			{
+				h = w * aspect_ratio;
+			}
+			else
+			{
+				w = h / aspect_ratio;
+			}
+
+		}
+
+	}
+
+
+	Gdk.Pixbuf? shadow_pixbuf ( Gdk.Pixbuf pixbuf, Color shadow_color )
+	{
+		uchar shadow_r = (uchar)(shadow_color.r * 255.0);
+		uchar shadow_g = (uchar)(shadow_color.g * 255.0);
+		uchar shadow_b = (uchar)(shadow_color.b * 255.0);
+
+		/* extract pixels and parameters from source pixbuf. */
+		uchar* buf_src         = pixbuf.pixels;
+		int    bits_per_sample = pixbuf.bits_per_sample;
+		int    n_channels      = pixbuf.n_channels;
+		bool   src_has_alpha   = pixbuf.has_alpha;
+		int    width           = pixbuf.width;
+		int    height          = pixbuf.height;
+		int    src_rowstride   = pixbuf.rowstride;
+
+		/* validate assumptions about source pixbuf. */
+		if ( buf_src == null) return null;
+		if ( bits_per_sample != 8 ) return null;
+		if ( (n_channels < 3) || (n_channels > 4) ) return null;
+		if ( width <= 0 ) return null;
+		if ( height <= 0 ) return null;
+		if ( src_rowstride <= 0 ) return null;
+
+		/* Allocate a destination pixbuf */
+		Gdk.Pixbuf dest_pixbuf = new Gdk.Pixbuf( Gdk.Colorspace.RGB, true, bits_per_sample, width, height );
+		int        dest_rowstride = dest_pixbuf.rowstride;
+		uchar*     buf_dest       = dest_pixbuf.pixels;
+		if ( buf_dest == null ) {
+			return null;
+		}
+
+		/* Process pixels: set rgb components and composite alpha with shadow_opacity. */
+		uchar* p_src  = buf_src;
+		uchar* p_dest = buf_dest;
+		for ( int iy=0; iy < height; iy++ )
+		{
+        
+			p_src  = buf_src + iy*src_rowstride;
+			p_dest = buf_dest + iy*dest_rowstride;
+
+			for ( int ix=0; ix < width; ix++ )
+			{
+
+				p_src += 3; /* skip RGB */
+
+				*p_dest++ = shadow_r;
+				*p_dest++ = shadow_g;
+				*p_dest++ = shadow_b;
+
+				if ( src_has_alpha )
+				{
+					*p_dest++ = *p_src++ * shadow_color.a;
+				}
+				else
+				{
+					*p_dest++ = shadow_color.a * 255.0;
+				}
+
+
+			}
+
+		}
+
+		return dest_pixbuf;
+	}
+
+
+}
diff --git a/glabels/label_object_line.vala b/glabels/label_object_line.vala
index 65cad48..4c52e2a 100644
--- a/glabels/label_object_line.vala
+++ b/glabels/label_object_line.vala
@@ -29,6 +29,13 @@ namespace glabels
 
 		private const double SELECTION_SLOP_PIXELS = 4.0;
 
+		public LabelObjectLine.with_parent( Label parent )
+		{
+			this();
+			parent.add_object( this );
+		}
+
+
 		/**
 		 * Line width
 		 */
diff --git a/glabels/label_object_text.vala b/glabels/label_object_text.vala
index ffff25e..a27af10 100644
--- a/glabels/label_object_text.vala
+++ b/glabels/label_object_text.vala
@@ -40,6 +40,13 @@ namespace glabels
 		public bool auto_shrink { get; set; default = false; }
 
 
+		public LabelObjectText.with_parent( Label parent )
+		{
+			this();
+			parent.add_object( this );
+		}
+
+
 		/**
 		 * Raw width of bounding box
 		 */
diff --git a/glabels/object_editor.vala b/glabels/object_editor.vala
index 6445824..472b17a 100644
--- a/glabels/object_editor.vala
+++ b/glabels/object_editor.vala
@@ -40,6 +40,7 @@ namespace glabels
 		private Gtk.Notebook     notebook;
 
 		private Gtk.Box          text_page_box;
+		private Gtk.Box          image_page_box;
 		private Gtk.Box          line_fill_page_box;
 		private Gtk.Box          pos_size_page_box;
 		private Gtk.Box          shadow_page_box;
@@ -63,6 +64,12 @@ namespace glabels
 		private Gtk.Box          text_insert_field_box;
 		private FieldButton      text_insert_field_button;
 
+		private Gtk.RadioButton  image_file_radio;
+		private Gtk.RadioButton  image_key_radio;
+		private Gtk.FileChooserButton   image_filebutton;
+		private Gtk.Box          image_key_box;
+		private FieldButton      image_key_button;
+
 		private Gtk.SpinButton   line_width_spin;
 		private Gtk.Box          line_color_box;
 		private ColorButton      line_color_button;
@@ -116,6 +123,9 @@ namespace glabels
 		private ulong sigid_text_line_spacing_spin_changed;
 		private ulong sigid_text_insert_field_button_key_selected;
 
+		private ulong sigid_image_filebutton_selection_changed;
+		private ulong sigid_image_key_button_changed;
+
 		private ulong sigid_line_width_spin_changed;
 		private ulong sigid_line_color_button_changed;
 
@@ -177,6 +187,7 @@ namespace glabels
 
 			/* Notebook pages. */
 			text_page_box      = builder.get_object( "text_page_box" )      as Gtk.Box;
+			image_page_box     = builder.get_object( "image_page_box" )      as Gtk.Box;
 			line_fill_page_box = builder.get_object( "line_fill_page_box" ) as Gtk.Box;
 			pos_size_page_box  = builder.get_object( "pos_size_page_box" )  as Gtk.Box;
 			shadow_page_box    = builder.get_object( "shadow_page_box" )    as Gtk.Box;
@@ -238,6 +249,20 @@ namespace glabels
 				text_insert_field_button.key_selected.connect( on_text_insert_field_button_key_selected );
 
 
+			/* Image widgets. */
+			image_file_radio        = builder.get_object( "image_file_radio" )        as Gtk.RadioButton;
+			image_key_radio         = builder.get_object( "image_key_radio" )         as Gtk.RadioButton;
+			image_filebutton        = builder.get_object( "image_filebutton" )        as Gtk.FileChooserButton;
+			image_key_box           = builder.get_object( "image_key_box" )           as Gtk.Box;
+
+			image_key_button = new FieldButton( null );
+			image_key_box.pack_start( image_key_button, true, true, 0 );
+
+			sigid_image_filebutton_selection_changed =
+				image_filebutton.selection_changed.connect( on_image_filebutton_selection_changed );
+			sigid_image_key_button_changed = image_key_button.changed.connect( on_image_key_button_changed );
+
+
 			/* Line widgets. */
 			line_width_spin         = builder.get_object( "line_width_spin" )         as Gtk.SpinButton;
 			line_color_box          = builder.get_object( "line_color_box" )          as Gtk.Box;
@@ -403,6 +428,7 @@ namespace glabels
 					title_label.set_text( "<b>%s</b>".printf( _("Text object properties") ) );
 
 					text_page_box.show_all();
+					image_page_box.hide();
 					line_fill_page_box.hide();
 					pos_size_page_box.show_all();
 					shadow_page_box.show_all();
@@ -416,6 +442,7 @@ namespace glabels
 					title_label.set_text( "<b>%s</b>".printf( _("Box object properties") ) );
 
 					text_page_box.hide();
+					image_page_box.hide();
 					line_fill_page_box.show_all();
 					pos_size_page_box.show_all();
 					shadow_page_box.show_all();
@@ -429,6 +456,7 @@ namespace glabels
 					title_label.set_text( "<b>%s</b>".printf( _("Ellipse object properties") ) );
 
 					text_page_box.hide();
+					image_page_box.hide();
 					line_fill_page_box.show_all();
 					pos_size_page_box.show_all();
 					shadow_page_box.show_all();
@@ -442,6 +470,7 @@ namespace glabels
 					title_label.set_text( "<b>%s</b>".printf( _("Line object properties") ) );
 
 					text_page_box.hide();
+					image_page_box.hide();
 					line_fill_page_box.show_all();
 					pos_size_page_box.show_all();
 					shadow_page_box.show_all();
@@ -450,6 +479,20 @@ namespace glabels
 					rect_size_frame.hide();
 					size_reset_image_button.hide();
 				}
+				else if ( object is LabelObjectImage )
+				{
+					title_image.set_from_icon_name( "glabels-image", Gtk.IconSize.LARGE_TOOLBAR );
+					title_label.set_text( "<b>%s</b>".printf( _("Image object properties") ) );
+
+					text_page_box.hide();
+					image_page_box.show_all();
+					line_fill_page_box.hide();
+					pos_size_page_box.show_all();
+					shadow_page_box.show_all();
+
+					line_size_frame.hide();
+					size_reset_image_button.hide();
+				}
 				else
 				{
 					assert_not_reached();
@@ -465,6 +508,8 @@ namespace glabels
 				load_text_valign_toggles();
 				load_text_line_spacing_spin();
 				load_text_textview();
+				load_image_filebutton();
+				load_image_key_button();
 				load_line_width_spin();
 				load_line_color_button();
 				load_fill_color_button();
@@ -531,18 +576,25 @@ namespace glabels
 			{
 				text_color_button.clear_keys();
 				text_insert_field_button.clear_keys();
+				image_key_button.clear_keys();
 				line_color_button.clear_keys();
 				fill_color_button.clear_keys();
 				shadow_color_button.clear_keys();
+
+				image_file_radio.set_active( true );
+				image_key_radio.set_sensitive( false );
 			}
 			else
 			{
 				List<string> key_list = model.label.merge.get_key_list();
 				text_color_button.set_keys( key_list );
 				text_insert_field_button.set_keys( key_list );
+				image_key_button.set_keys( key_list );
 				line_color_button.set_keys( key_list );
 				fill_color_button.set_keys( key_list );
 				shadow_color_button.set_keys( key_list );
+
+				image_key_radio.set_sensitive( true );
 			}
 		}
 
@@ -948,6 +1000,74 @@ namespace glabels
 
 
 		/******************************
+		 * image_filebutton
+		 ******************************/
+		private void on_image_filebutton_selection_changed()
+		{
+			if ( object != null )
+			{
+				string filename = image_filebutton.get_filename();
+				if ( filename != null )
+				{
+					object.filename_node = new TextNode( false, filename );
+				}
+			}
+		}
+
+		private void load_image_filebutton()
+		{
+			if ( (object != null) && (object.filename_node != null) )
+			{
+				if ( !object.filename_node.field_flag )
+				{
+					image_file_radio.set_active( true );
+
+					GLib.SignalHandler.block( (void*)image_filebutton, sigid_image_filebutton_selection_changed );
+
+					if ( object.filename_node.data != null )
+					{
+						image_filebutton.set_filename( object.filename_node.data );
+					}
+					else
+					{
+						image_filebutton.unselect_all();
+					}
+
+					GLib.SignalHandler.unblock( (void*)image_filebutton, sigid_image_filebutton_selection_changed );
+				}
+			}
+		}
+
+
+		/******************************
+		 * image_key_button
+		 ******************************/
+		private void on_image_key_button_changed()
+		{
+			if ( object != null )
+			{
+				object.filename_node = new TextNode( true, image_key_button.get_key() );
+			}
+		}
+
+		private void load_image_key_button()
+		{
+			if ( (object != null) && (object.filename_node != null) )
+			{
+				if ( object.filename_node.field_flag )
+				{
+					image_key_radio.set_active( true );
+
+					GLib.SignalHandler.block( (void*)image_key_button, sigid_image_key_button_changed );
+
+					image_key_button.set_key( object.filename_node.data );
+
+					GLib.SignalHandler.unblock( (void*)image_key_button, sigid_image_key_button_changed );
+				}
+			}
+		}
+
+		/******************************
 		 * line_width_spin
 		 ******************************/
 		private void on_line_width_spin_changed()
diff --git a/glabels/pixbuf_cache.vala b/glabels/pixbuf_cache.vala
new file mode 100644
index 0000000..dd51f5e
--- /dev/null
+++ b/glabels/pixbuf_cache.vala
@@ -0,0 +1,125 @@
+/*  pixbuf_cache.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 PixbufCache
+	{
+
+		private class Record : Object
+		{
+			public int        use_count;
+			public Gdk.Pixbuf pixbuf;
+
+			public Record( Gdk.Pixbuf pixbuf )
+			{
+				this.use_count = 0;
+				this.pixbuf    = pixbuf;
+			}
+
+			public void increment()
+			{
+				use_count++;
+			}
+
+			public int decrement()
+			{
+				use_count--;
+				return use_count;
+			}
+		}
+
+
+		private Gee.HashMap<string,Record> map;
+
+
+		public PixbufCache()
+		{
+			map = new Gee.HashMap<string,Record>();
+		}
+
+
+		public void add_pixbuf( string name, Gdk.Pixbuf pixbuf )
+		{
+			if ( map.has_key( name ) )
+			{
+				/* pixbuf already in the cache. */
+				return;
+			}
+
+			Record record = new Record( pixbuf );
+			map.set( name, record );
+		}
+
+
+		public Gdk.Pixbuf? get_pixbuf( string name )
+		{
+			if ( map.has_key( name ) )
+			{
+				Record record = map.get( name );
+				record.increment();
+				return record.pixbuf;
+			}
+
+			try {
+				Gdk.Pixbuf? pixbuf = new Gdk.Pixbuf.from_file( name );
+				Record record = new Record( pixbuf );
+				record.increment();
+				map.set( name, record );
+
+				return pixbuf;
+			}
+			catch ( Error e )
+			{
+				return null;
+			}
+		}
+
+
+		public void remove_pixbuf( string name )
+		{
+			Record? record = map.get( name );
+			if ( record.decrement() == 0 )
+			{
+				map.unset( name );
+			}
+		}
+
+
+		public List<string> get_name_list()
+		{
+			List<string> list = new List<string>();
+
+			foreach ( string name in map.keys )
+			{
+				list.append( name );
+			}
+
+			return list;
+		}
+
+
+	}
+
+}
diff --git a/glabels/pixmaps.vapi b/glabels/pixmaps.vapi
new file mode 100644
index 0000000..cae016f
--- /dev/null
+++ b/glabels/pixmaps.vapi
@@ -0,0 +1,13 @@
+namespace glabels
+{
+
+	[CCode (prefix = "", lower_case_cprefix = "", cheader_filename = "pixmaps/checkerboard.xpm")]
+	namespace Pixmaps
+	{
+
+		[CCode (array_length = false, type = "char**")]
+		public string[] checkerboard_xpm;
+
+	}
+
+}
diff --git a/glabels/pixmaps/checkerboard.xpm b/glabels/pixmaps/checkerboard.xpm
new file mode 100644
index 0000000..33ab7b5
--- /dev/null
+++ b/glabels/pixmaps/checkerboard.xpm
@@ -0,0 +1,29 @@
+/* XPM */
+static char * checkerboard_xpm[] = {
+"24 24 2 1",
+" 	c #CCCCCC",
+".	c #000000",
+"..  ..  ..  ..  ..  ..  ",
+"..  ..  ..  ..  ..  ..  ",
+"  ..  ..  ..  ..  ..  ..",
+"  ..  ..  ..  ..  ..  ..",
+"..  ..  ..  ..  ..  ..  ",
+"..  ..  ..  ..  ..  ..  ",
+"  ..  ..  ..  ..  ..  ..",
+"  ..  ..  ..  ..  ..  ..",
+"..  ..  ..  ..  ..  ..  ",
+"..  ..  ..  ..  ..  ..  ",
+"  ..  ..  ..  ..  ..  ..",
+"  ..  ..  ..  ..  ..  ..",
+"..  ..  ..  ..  ..  ..  ",
+"..  ..  ..  ..  ..  ..  ",
+"  ..  ..  ..  ..  ..  ..",
+"  ..  ..  ..  ..  ..  ..",
+"..  ..  ..  ..  ..  ..  ",
+"..  ..  ..  ..  ..  ..  ",
+"  ..  ..  ..  ..  ..  ..",
+"  ..  ..  ..  ..  ..  ..",
+"..  ..  ..  ..  ..  ..  ",
+"..  ..  ..  ..  ..  ..  ",
+"  ..  ..  ..  ..  ..  ..",
+"  ..  ..  ..  ..  ..  .."};
diff --git a/glabels/svg_cache.vala b/glabels/svg_cache.vala
new file mode 100644
index 0000000..2cfd0ce
--- /dev/null
+++ b/glabels/svg_cache.vala
@@ -0,0 +1,191 @@
+/*  svg_cache.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 SvgCache
+	{
+
+		private class Record : Object
+		{
+			public int         use_count;
+			public Rsvg.Handle handle;
+			public string      svg;
+
+			public Record( Rsvg.Handle handle, string svg )
+			{
+				this.use_count = 0;
+				this.handle    = handle;
+				this.svg       = svg;
+			}
+
+			public Record.from_svg( string svg ) throws Error
+			{
+				Rsvg.Handle? handle;
+
+				try {
+					handle = new Rsvg.Handle.from_data( svg.data, svg.length );
+				}
+				catch ( Error e )
+				{
+					handle = null;
+					throw e;
+				}
+
+				this( handle, svg );
+			}
+
+			public Record.from_file( string file_name ) throws Error
+			{
+				Rsvg.Handle? handle;
+				string       svg;
+
+				try {
+					FileUtils.get_contents( file_name, out svg );
+					handle = new Rsvg.Handle.from_data( svg.data, svg.length );
+				}
+				catch ( Error e )
+				{
+					handle = null;
+					svg    = "";
+					throw e;
+				}
+
+				this( handle, svg );
+			}
+
+			public void increment()
+			{
+				use_count++;
+			}
+
+			public int decrement()
+			{
+				use_count--;
+				return use_count;
+			}
+		}
+
+
+		private Gee.HashMap<string,Record> map;
+
+
+		public SvgCache()
+		{
+			map = new Gee.HashMap<string,Record>();
+		}
+
+
+		public void add_svg( string name, string svg )
+		{
+			if ( map.has_key( name ) )
+			{
+				/* svg already in the cache. */
+				return;
+			}
+
+			try {
+				map.set( name, new Record.from_svg( svg ) );
+			}
+			catch ( Error e )
+			{
+			}
+		}
+
+
+		public string? get_svg( string name )
+		{
+			if ( map.has_key( name ) )
+			{
+				Record record = map.get( name );
+				record.increment();
+				return record.svg;
+			}
+
+			try
+			{
+				Record record = new Record.from_file( name );
+				record.increment();
+				map.set( name, record );
+
+				return record.svg;
+			}
+			catch ( Error e )
+			{
+				return null;
+			}
+		}
+
+
+		public Rsvg.Handle? get_handle( string name )
+		{
+			if ( map.has_key( name ) )
+			{
+				Record record = map.get( name );
+				record.increment();
+				return record.handle;
+			}
+
+			try
+			{
+				Record record = new Record.from_file( name );
+				record.increment();
+				map.set( name, record );
+
+				return record.handle;
+			}
+			catch ( Error e )
+			{
+				return null;
+			}
+		}
+
+
+		public void remove_svg( string name )
+		{
+			Record? record = map.get( name );
+			if ( record.decrement() == 0 )
+			{
+				map.unset( name );
+				record.unref();
+			}
+		}
+
+
+		public List<string> get_name_list()
+		{
+			List<string> list = new List<string>();
+
+			foreach ( string name in map.keys )
+			{
+				list.append( name );
+			}
+
+			return list;
+		}
+
+
+	}
+
+}
diff --git a/glabels/ui.vala b/glabels/ui.vala
index 2b3b82e..973ed4a 100644
--- a/glabels/ui.vala
+++ b/glabels/ui.vala
@@ -1150,15 +1150,15 @@ namespace glabels
 		}
 
 
-		private void on_objects_create_text( Gtk.Action action )
+		private void on_objects_create_box( Gtk.Action action )
 		{
-			window.view.create_text_mode();
+			window.view.create_box_mode();
 		}
 
 
-		private void on_objects_create_box( Gtk.Action action )
+		private void on_objects_create_ellipse( Gtk.Action action )
 		{
-			window.view.create_box_mode();
+			window.view.create_ellipse_mode();
 		}
 
 
@@ -1168,20 +1168,15 @@ namespace glabels
 		}
 
 
-		private void on_objects_create_ellipse( Gtk.Action action )
+		private void on_objects_create_image( Gtk.Action action )
 		{
-			window.view.create_ellipse_mode();
+			window.view.create_image_mode();
 		}
 
 
-		private void on_objects_create_image( Gtk.Action action )
+		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_IMAGE);
-			}
-			*/
+			window.view.create_text_mode();
 		}
 
 
diff --git a/glabels/view.vala b/glabels/view.vala
index d48429d..a22cbf4 100644
--- a/glabels/view.vala
+++ b/glabels/view.vala
@@ -429,6 +429,28 @@ namespace glabels
 		}
 
 
+		public void create_image_mode()
+		{
+			Gdk.Window window = canvas.get_window();
+
+			try
+			{
+				Gdk.Pixbuf pixbuf = Gdk.Pixbuf.from_pixdata( Cursor.image_pixdata, false );
+				Gdk.Cursor cursor = new Gdk.Cursor.from_pixbuf( Gdk.Display.get_default(),
+				                                                pixbuf, CURSOR_X_HOTSPOT, CURSOR_Y_HOTSPOT );
+				window.set_cursor( cursor );
+			}
+			catch ( Error err )
+			{
+				error( "%s\n", err.message );
+			}
+
+			in_object_create_mode = true;
+			create_object_type    = CreateType.IMAGE;
+			state                 = State.IDLE;
+		}
+
+
 		public void create_text_mode()
 		{
 			Gdk.Window window = canvas.get_window();
@@ -909,7 +931,9 @@ namespace glabels
 						create_object.set_size( x - create_x0, y - create_y0 );
 						break;
 					case CreateType.IMAGE:
-						/* TODO */
+						create_object.set_position( double.min( x, create_x0 ), double.min( y, create_y0 ) );
+						create_object.set_size( double.max( x, create_x0 ) - double.min( x, create_x0 ),
+						                        double.max( y, create_y0 ) - double.min( y, create_y0 ) );
 						break;
 					case CreateType.TEXT:
 						create_object.set_position( double.min( x, create_x0 ), double.min( y, create_y0 ) );
@@ -1037,7 +1061,7 @@ namespace glabels
 							create_object = new LabelObjectLine()    as LabelObject;
 							break;
 						case CreateType.IMAGE:
-							/* TODO */
+							create_object = new LabelObjectImage()   as LabelObject;
 							break;
 						case CreateType.TEXT:
 							create_object = new LabelObjectText()    as LabelObject;
diff --git a/glabels/xml_label.vala b/glabels/xml_label.vala
index f882558..edb253a 100644
--- a/glabels/xml_label.vala
+++ b/glabels/xml_label.vala
@@ -131,7 +131,7 @@ namespace glabels
 			{
 				if ( child->name == "Data" )
 				{
-					/* TODO: xml_parse_data (child_node, label); */
+					parse_data_node( child, label );
 				}
 			}
 
@@ -194,11 +194,11 @@ namespace glabels
 					break;
 
 				case "Object-line":
-					/* TODO. */
+					parse_object_line_node( child, label );
 					break;
 
 				case "Object-image":
-					/* TODO. */
+					parse_object_image_node( child, label );
 					break;
 
 				case "Object-barcode":
@@ -224,7 +224,7 @@ namespace glabels
 		private void parse_object_box_node( Xml.Node node,
 		                                    Label    label )
 		{
-			LabelObjectBox object = new LabelObjectBox();
+			LabelObjectBox object = new LabelObjectBox.with_parent( label );
 
 		
 			/* position attrs */
@@ -258,15 +258,13 @@ namespace glabels
 
 			/* shadow attrs */
 			parse_shadow_attrs( node, object );
-
-			label.add_object( object );
 		}
 
 
 		private void parse_object_ellipse_node( Xml.Node node,
 		                                        Label    label )
 		{
-			LabelObjectEllipse object = new LabelObjectEllipse();
+			LabelObjectEllipse object = new LabelObjectEllipse.with_parent( label );
 
 		
 			/* position attrs */
@@ -300,15 +298,86 @@ namespace glabels
 
 			/* shadow attrs */
 			parse_shadow_attrs( node, object );
+		}
+
+
+		private void parse_object_line_node( Xml.Node node,
+		                                     Label    label )
+		{
+			LabelObjectLine object = new LabelObjectLine.with_parent( label );
+
+		
+			/* 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, "dx", 0 );
+			object.h = XmlUtil.get_prop_length( node, "dy", 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 );
+			}
+
+			/* affine attrs */
+			parse_affine_attrs( node, object );
+
+			/* shadow attrs */
+			parse_shadow_attrs( node, object );
+		}
+
+
+		private void parse_object_image_node( Xml.Node node,
+		                                      Label    label )
+		{
+			LabelObjectImage object = new LabelObjectImage.with_parent( label );
+
+		
+			/* position attrs */
+			object.x0 = XmlUtil.get_prop_length( node, "x", 0.0 );
+			object.y0 = XmlUtil.get_prop_length( node, "y", 0.0 );
+
+			/* src or field attrs */
+			string src = XmlUtil.get_prop_string( node, "src", null );
+			if ( src != null )
+			{
+				object.filename_node = new TextNode( false, src );
+			}
+			else
+			{
+				string field = XmlUtil.get_prop_string( node, "field", null );
+				if ( field != null )
+				{
+					object.filename_node = new TextNode( true, field );
+				}
+				else
+				{
+					message( "Missing Object-image src or field attr" );
+				}
+			}
+					
+			/* size attrs, must be set after filename because setting filename may adjust these. */
+			object.w = XmlUtil.get_prop_length( node, "w", 0 );
+			object.h = XmlUtil.get_prop_length( node, "h", 0 );
+
+			/* affine attrs */
+			parse_affine_attrs( node, object );
 
-			label.add_object( object );
+			/* shadow attrs */
+			parse_shadow_attrs( node, object );
 		}
 
 
 		private void parse_object_text_node( Xml.Node node,
 		                                     Label    label )
 		{
-			LabelObjectText object = new LabelObjectText();
+			LabelObjectText object = new LabelObjectText.with_parent( label );
 
 		
 			/* position attrs */
@@ -350,8 +419,6 @@ namespace glabels
 					break;
 				}
 			}
-
-			label.add_object( object );
 		}
 
 
@@ -475,6 +542,70 @@ namespace glabels
 		}
 
 
+		private void parse_data_node( Xml.Node  node,
+		                              Label     label )
+		{
+
+			for ( unowned Xml.Node* child = node.children; child != null; child = child->next )
+			{
+				switch (child->name)
+				{
+
+				case "Pixdata":
+					parse_pixdata_node( child, label );
+					break;
+
+				case "File":
+					parse_file_node( child, label );
+					break;
+
+				default:
+					if ( child->is_text() == 0 )
+					{
+						message( "Unexpected %s child: \"%s\"", node.name, child->name );
+					}
+					break;
+
+				}
+			}
+
+		}
+
+
+		private void parse_pixdata_node( Xml.Node  node,
+		                                 Label     label )
+		{
+			string name = XmlUtil.get_prop_string( node, "name", null );
+			string base64 = node.get_content();
+
+			uchar[] stream = Base64.decode( base64 );
+			Gdk.Pixdata pixdata = Gdk.Pixdata();
+			if ( pixdata.deserialize( stream ) )
+			{
+				Gdk.Pixbuf pixbuf = Gdk.Pixbuf.from_pixdata( pixdata, true );
+				label.pixbuf_cache.add_pixbuf( name, pixbuf );
+			}
+		}
+
+
+		private void parse_file_node( Xml.Node  node,
+		                              Label     label )
+		{
+			string name   = XmlUtil.get_prop_string( node, "name",   null );
+			string format = XmlUtil.get_prop_string( node, "format", null );
+
+			if ( (format == "SVG") || (format == "Svg") || (format == "svg") )
+			{
+				string content = node.get_content();
+				label.svg_cache.add_svg( name, content );
+			}
+			else
+			{
+				message( "Unknown embedded file format: \"%s\"", format );
+			}
+		}
+
+
 		public void save_file( Label label,
 		                       string utf8_filename ) throws XmlError
 		{
@@ -552,15 +683,23 @@ namespace glabels
 			{
 				if ( object is LabelObjectBox )
 				{
-					create_object_box_node( node, ns, (LabelObjectBox)object );
+					create_object_box_node( node, ns, object as LabelObjectBox );
 				}
 				else if ( object is LabelObjectEllipse )
 				{
-					create_object_ellipse_node( node, ns, (LabelObjectEllipse)object );
+					create_object_ellipse_node( node, ns, object as LabelObjectEllipse );
+				}
+				else if ( object is LabelObjectLine )
+				{
+					create_object_line_node( node, ns, object as LabelObjectLine );
+				}
+				else if ( object is LabelObjectImage )
+				{
+					create_object_image_node( node, ns, object as LabelObjectImage );
 				}
 				else if ( object is LabelObjectText )
 				{
-					create_object_text_node( node, ns, (LabelObjectText)object );
+					create_object_text_node( node, ns, object as LabelObjectText );
 				}
 				else /* TODO: other object types. */
 				{
@@ -656,6 +795,71 @@ namespace glabels
 		}
 
 
+		private void create_object_line_node( Xml.Node        parent,
+		                                      Xml.Ns          ns,
+		                                      LabelObjectLine object )
+		{
+			unowned Xml.Node *node = parent.new_child( ns, "Object-line" );
+
+			/* 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, "dx", object.w );
+			XmlUtil.set_prop_length( node, "dy", 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() );
+			}
+
+			/* affine attrs */
+			create_affine_attrs( node, object );
+
+			/* shadow attrs */
+			create_shadow_attrs( node, object );
+		}
+
+
+		private void create_object_image_node( Xml.Node         parent,
+		                                       Xml.Ns           ns,
+		                                       LabelObjectImage object )
+		{
+			unowned Xml.Node *node = parent.new_child( ns, "Object-image" );
+
+			/* 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 );
+
+			/* src or field attr */
+			if ( object.filename_node.field_flag )
+			{
+				XmlUtil.set_prop_string( node, "field", object.filename_node.data );
+			}
+			else
+			{
+				XmlUtil.set_prop_string( node, "src", object.filename_node.data );
+			}
+
+			/* affine attrs */
+			create_affine_attrs( node, object );
+
+			/* shadow attrs */
+			create_shadow_attrs( node, object );
+		}
+
+
 		private void create_object_text_node( Xml.Node        parent,
 		                                      Xml.Ns          ns,
 		                                      LabelObjectText object )
@@ -805,7 +1009,54 @@ namespace glabels
 		{
 			unowned Xml.Node *node = root.new_child( ns, "Data" );
 
-			/* TODO */
+			foreach ( string name in label.pixbuf_cache.get_name_list() )
+			{
+				create_pixdata_node( node, ns, label, name );
+			}
+
+			foreach ( string name in label.svg_cache.get_name_list() )
+			{
+				create_file_svg_node( doc, node, ns, label, name );
+			}
+		}
+
+
+		private void create_pixdata_node( Xml.Node root,
+		                                  Xml.Ns   ns,
+		                                  Label    label,
+		                                  string   name )
+		{
+			Gdk.Pixbuf pixbuf = label.pixbuf_cache.get_pixbuf( name );
+			if ( pixbuf != null )
+			{
+				Gdk.Pixdata pixdata = Gdk.Pixdata();
+				Gdk.pixdata_from_pixbuf( out pixdata, pixbuf, false );
+				uint8[] stream = pixdata.serialize();
+				string base64 = GLib.Base64.encode( stream );
+				
+				unowned Xml.Node *node = root.new_child( ns, "Pixdata", base64 );
+				XmlUtil.set_prop_string( node, "name", name );
+				XmlUtil.set_prop_string( node, "encoding", "Base64" );
+			}
+		}
+
+
+		private void create_file_svg_node( Xml.Doc  doc,
+		                                   Xml.Node root,
+		                                   Xml.Ns   ns,
+		                                   Label    label,
+		                                   string   name )
+		{
+			string? svg_data = label.svg_cache.get_svg( name );
+			if ( svg_data != null )
+			{
+				unowned Xml.Node *node = root.new_child( ns, "File" );
+				XmlUtil.set_prop_string( node, "name", name );
+				XmlUtil.set_prop_string( node, "format", "SVG" );
+
+				unowned Xml.Node *cdata_section_node = doc.new_cdata_block( svg_data, svg_data.length );
+				node->add_child( cdata_section_node );
+			}
 		}
 
 	}



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