[glabels/vala] Re-organization of document model.



commit 901a8895ea646ce1b0aff70b776a0eea749f8021
Author: Jim Evins <evins snaught com>
Date:   Fri Apr 20 22:49:28 2012 -0400

    Re-organization of document model.

 glabels/Makefile.am          |    4 +
 glabels/file.vala            |   10 +-
 glabels/label.vala           |  515 +-----------------------------------------
 glabels/mini_preview.vala    |   16 +-
 glabels/model.vala           |   52 +++++
 glabels/model_clipboard.vala |  239 +++++++++++++++++++
 glabels/model_print.vala     |  223 ++++++++++++++++++
 glabels/model_undo_redo.vala |  160 +++++++++++++
 glabels/object_editor.vala   |   24 +-
 glabels/print_op.vala        |   16 +-
 glabels/print_op_dialog.vala |    4 +-
 glabels/property_editor.vala |    8 +-
 glabels/ui.vala              |  103 ++++------
 glabels/view.vala            |   84 ++++----
 glabels/window.vala          |   21 +-
 15 files changed, 813 insertions(+), 666 deletions(-)
---
diff --git a/glabels/Makefile.am b/glabels/Makefile.am
index 1c0e83b..da71fea 100644
--- a/glabels/Makefile.am
+++ b/glabels/Makefile.am
@@ -43,6 +43,10 @@ glabels_4_SOURCES = \
 	merge_record.vala \
 	merge_text.vala \
 	message_bar.vala \
+	model.vala \
+	model_clipboard.vala \
+	model_print.vala \
+	model_undo_redo.vala \
 	mini_preview.vala \
 	new_label_dialog.vala \
 	object_editor.vala \
diff --git a/glabels/file.vala b/glabels/file.vala
index 22613c9..5a66163 100644
--- a/glabels/file.vala
+++ b/glabels/file.vala
@@ -411,9 +411,9 @@ namespace glabels
 		}
 
 
-		public void print( Label label, Window window )
+		public void print( Model model, Window window )
 		{
-			PrintOpDialog op = new PrintOpDialog( label );
+			PrintOpDialog op = new PrintOpDialog( model );
 
 			op.run( Gtk.PrintOperationAction.PRINT_DIALOG, window );
 			/* TODO: save print settings? */
@@ -426,14 +426,14 @@ namespace glabels
 
 			if ( !window.is_empty() )
 			{
-				if ( window.view.label.modified )
+				if ( window.model.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() );
+					                                window.model.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 );
@@ -450,7 +450,7 @@ namespace glabels
 					switch (ret)
 					{
 					case Gtk.ResponseType.YES:
-						close_flag = save( window.view.label, window );
+						close_flag = save( window.model.label, window );
 						break;
 					case Gtk.ResponseType.NO:
 						close_flag = true;
diff --git a/glabels/label.vala b/glabels/label.vala
index e839b1b..e83a26d 100644
--- a/glabels/label.vala
+++ b/glabels/label.vala
@@ -55,33 +55,6 @@ namespace glabels
 		// 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;
-
-
-		/* Print Model */
-		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; private set; }
-
-		public bool  outline_flag    { get; set; }
-		public bool  reverse_flag    { get; set; }
-		public bool  crop_marks_flag { get; set; }
-
-		public int   n_pages         { get; set; default = 1; }
-
-
 		/**
 		 * Filename
 		 */
@@ -237,9 +210,6 @@ namespace glabels
 
 			template_history = new TemplateHistory( 5 );
 
-			undo_stack = new Queue<LabelState?>();
-			redo_stack = new Queue<LabelState?>();
-
 			// TODO: Set default properties from user prefs
 		}
 
@@ -400,7 +370,6 @@ namespace glabels
 		public void select_object( LabelObject object )
 		{
 			object.select();
-			cp_cleared_flag = true;
 			selection_changed();
 		}
 
@@ -408,7 +377,6 @@ namespace glabels
 		public void unselect_object( LabelObject object )
 		{
 			object.unselect();
-			cp_cleared_flag = true;
 			selection_changed();
 		}
 
@@ -419,7 +387,6 @@ namespace glabels
 			{
 				object.select();
 			}
-			cp_cleared_flag = true;
 			selection_changed();
 		}
 
@@ -430,7 +397,6 @@ namespace glabels
 			{
 				object.unselect();
 			}
-			cp_cleared_flag = true;
 			selection_changed();
 		}
 
@@ -454,7 +420,6 @@ namespace glabels
 					object.select();
 				}
 			}
-			cp_cleared_flag = true;
 			selection_changed();
 		}
 
@@ -646,12 +611,12 @@ namespace glabels
 				object_list.remove( object );
 			}
 
-			/* Move to end of list, representing front most object */
-			foreach ( LabelObject object in object_list )
+			/* Move to front of list, representing rear most object */
+			selection_list.reverse();
+			foreach ( LabelObject object in selection_list )
 			{
-				selection_list.append( object );
+				object_list.prepend( object );
 			}
-			object_list = selection_list;
 
 			changed();
 			modified = true;
@@ -1189,478 +1154,6 @@ namespace glabels
 		}
 
 
-		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 ) {}
-		}
-
-
-		/*
-		 * Print Model
-		 */
-		public void print_simple_sheet( Cairo.Context cr, int i_page )
-		{
-			if ( crop_marks_flag )
-			{
-				print_crop_marks( cr );
-			}
-
-			TemplateFrame frame = 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 = 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), template.page_height);
-					double y4 = double.min((y3 + TICK_LENGTH), 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), template.page_width);
-					double x4 = double.min((x3 + TICK_LENGTH), 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;
-			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 ( rotate )
-			{
-				cr.rotate( Math.PI/2 );
-				cr.translate( 0, -h );
-			}
-			if ( reverse_flag )
-			{
-				cr.translate( w, 0 );
-				cr.scale( -1, 1 );
-			}
-
-			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 = template.frames.first().data;
-			frame.cairo_path( cr, false );
-
-			cr.stroke();
-
-			cr.restore();
-		}
-
-
-		private void clip_to_outline( Cairo.Context cr )
-		{
-			TemplateFrame frame = template.frames.first().data;
-			frame.cairo_path( cr, true );
-
-			cr.set_fill_rule( Cairo.FillRule.EVEN_ODD );
-			cr.clip();
-		}
-
-
 	}
 
 }
diff --git a/glabels/mini_preview.vala b/glabels/mini_preview.vala
index d7ecb5d..154b2d1 100644
--- a/glabels/mini_preview.vala
+++ b/glabels/mini_preview.vala
@@ -57,7 +57,7 @@ namespace glabels
 
 		private bool update_scheduled_flag;
 
-		private Label? label;
+		private Model? model;
 
 
 
@@ -81,12 +81,12 @@ namespace glabels
 		}
 
 
-		public void set_label( Label label )
+		public void set_model( Model model )
 		{
-			this.label = label;
-			set_template( label.template );
+			this.model = model;
+			set_template( model.label.template );
 
-			label.changed.connect( on_label_changed );
+			model.label.changed.connect( on_label_changed );
 		}
 
 
@@ -309,7 +309,7 @@ namespace glabels
 
 				draw_labels( cr, 2.0/scale );
 
-				if ( label != null )
+				if ( model != null )
 				{
 					draw_rich_preview( cr );
 				}
@@ -372,7 +372,7 @@ namespace glabels
 			Color highlight_color = Color.from_color_and_opacity( base_color, 0.10 );
 
 			Color outline_color;
-			if ( label != null )
+			if ( model != null )
 			{
 				/* Outlines are more subtle when doing a rich preview. */
 				outline_color = Color.from_color_and_opacity( base_color, 0.25 );
@@ -409,7 +409,7 @@ namespace glabels
 		private void draw_rich_preview( Cairo.Context cr )
 		{
 			/* TODO: test for merge. */
-			label.print_simple_sheet( cr, 0 );
+			model.print.print_simple_sheet( cr, 0 );
 		}
 
 		
diff --git a/glabels/model.vala b/glabels/model.vala
new file mode 100644
index 0000000..8211165
--- /dev/null
+++ b/glabels/model.vala
@@ -0,0 +1,52 @@
+/*  model.vala
+ *
+ *  Copyright (C) 2011-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/>.
+ */
+
+
+// ****************************************************************************************
+// TODO:  do checkpointing in UI code before invoking changes to Label or LabelObject*s.
+// ****************************************************************************************
+
+using GLib;
+using libglabels;
+
+namespace glabels
+{
+
+	public class Model
+	{
+		public Label          label     { get; private set; }
+		public ModelClipboard clipboard { get; private set; }
+		public ModelUndoRedo  undo_redo { get; private set; }
+		public ModelPrint     print     { get; private set; }
+
+
+		public Model( Label label )
+		{
+			this.label = label;
+
+			clipboard = new ModelClipboard( label );
+			undo_redo = new ModelUndoRedo( label );
+			print     = new ModelPrint( label );
+		}
+
+
+	}
+
+}
diff --git a/glabels/model_clipboard.vala b/glabels/model_clipboard.vala
new file mode 100644
index 0000000..1d485c7
--- /dev/null
+++ b/glabels/model_clipboard.vala
@@ -0,0 +1,239 @@
+/*  model_clipboard.vala
+ *
+ *  Copyright (C) 2011-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 ModelClipboard : GLib.Object
+	{
+		private Label label;
+
+		private string?     clipboard_xml_buffer;
+		private string?     clipboard_text;
+		private Gdk.Pixbuf? clipboard_pixbuf;
+
+
+		public ModelClipboard( Label label )
+		{
+			this.label = label;
+		}
+
+
+		public void cut_selection()
+		{
+			copy_selection();
+			label.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 = label.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 = label.template;
+				label_copy.rotate = label.rotate;
+
+				foreach ( LabelObject object in selection_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 ( label.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 ( label.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 )
+			// label.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 )
+		{
+			label.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 )
+		{
+			label.unselect_all();
+			// TODO:  create new LabelObjectImage object from pixbuf.  set to a default location, select.
+		}
+
+
+	}
+
+}
diff --git a/glabels/model_print.vala b/glabels/model_print.vala
new file mode 100644
index 0000000..917e9d3
--- /dev/null
+++ b/glabels/model_print.vala
@@ -0,0 +1,223 @@
+/*  model_print.vala
+ *
+ *  Copyright (C) 2011-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 ModelPrint
+	{
+		private Label label;
+
+		private const double OUTLINE_WIDTH =  0.25;
+		private const double TICK_OFFSET   =  2.25;
+		private const double TICK_LENGTH   = 18.0;
+
+		public bool  outline_flag    { get; set; }
+		public bool  reverse_flag    { get; set; }
+		public bool  crop_marks_flag { get; set; }
+
+		public int   n_pages         { get; set; default = 1; }
+
+
+		public ModelPrint( Label label )
+		{
+			this.label = label;
+		}
+
+
+		public void print_simple_sheet( Cairo.Context cr, int i_page )
+		{
+			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/model_undo_redo.vala b/glabels/model_undo_redo.vala
new file mode 100644
index 0000000..bfd62d7
--- /dev/null
+++ b/glabels/model_undo_redo.vala
@@ -0,0 +1,160 @@
+/*  model_undo_redo.vala
+ *
+ *  Copyright (C) 2011-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 ModelUndoRedo
+	{
+		private Label label;
+
+		private Queue<LabelState?> undo_stack;
+		private Queue<LabelState?> redo_stack;
+		private bool               cp_cleared_flag;
+		private string             cp_desc;
+
+
+		public ModelUndoRedo( Label label )
+		{
+			this.label = label;
+
+			undo_stack = new Queue<LabelState?>();
+			redo_stack = new Queue<LabelState?>();
+
+			label.selection_changed.connect( on_selection_changed );
+		}
+
+
+		private void on_selection_changed()
+		{
+			cp_cleared_flag = true;
+		}
+
+
+		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, label );
+				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, label );
+
+			redo_stack.push_head( state_now );
+
+			state_old.restore( label );
+
+			cp_cleared_flag = true;
+
+			label.selection_changed();
+		}
+
+
+		public void redo()
+		{
+			LabelState state_old = redo_stack.pop_head();
+			LabelState state_now = new LabelState( state_old.description, label );
+
+			undo_stack.push_head( state_now );
+
+			state_old.restore( label );
+
+			cp_cleared_flag = true;
+
+			label.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/object_editor.vala b/glabels/object_editor.vala
index 054edee..7236c3c 100644
--- a/glabels/object_editor.vala
+++ b/glabels/object_editor.vala
@@ -30,7 +30,7 @@ namespace glabels
 		private Prefs           prefs;
 		private Units           units;
 
-		private Label           label;
+		private Model           model;
 		private LabelObject?    object;
 
 
@@ -184,17 +184,17 @@ namespace glabels
 		}
 
 
-		public void set_label( Label label )
+		public void set_model( Model model )
 		{
-			this.label = label;
+			this.model = model;
 
 			on_label_size_changed();
 			on_merge_changed();
 			on_selection_changed();
 
-			label.size_changed.connect( on_label_size_changed );
-			label.merge_changed.connect( on_merge_changed );
-			label.selection_changed.connect( on_selection_changed );
+			model.label.size_changed.connect( on_label_size_changed );
+			model.label.merge_changed.connect( on_merge_changed );
+			model.label.selection_changed.connect( on_selection_changed );
 		}
 
 
@@ -244,9 +244,9 @@ namespace glabels
 				object.changed.disconnect( on_object_changed );
 			}
 
-			if ( label.is_selection_atomic() )
+			if ( model.label.is_selection_atomic() )
 			{
-				object = label.get_1st_selected_object();
+				object = model.label.get_1st_selected_object();
 
 				if ( object is LabelObjectBox  )
 				{
@@ -304,11 +304,11 @@ namespace glabels
 
 		private void on_label_size_changed()
 		{
-			if ( label != null )
+			if ( model != null )
 			{
 				double w_max, h_max;
 
-				label.get_size( out w_max, out h_max );
+				model.label.get_size( out w_max, out h_max );
 
 				double wh_max = double.max( w_max*units.units_per_point, h_max*units.units_per_point );
 
@@ -324,7 +324,7 @@ namespace glabels
 
 		private void on_merge_changed()
 		{
-			if ( label.merge is MergeNone )
+			if ( model.label.merge is MergeNone )
 			{
 				line_color_button.clear_keys();
 				fill_color_button.clear_keys();
@@ -332,7 +332,7 @@ namespace glabels
 			}
 			else
 			{
-				List<string> key_list = label.merge.get_key_list();
+				List<string> key_list = model.label.merge.get_key_list();
 				line_color_button.set_keys( key_list );
 				fill_color_button.set_keys( key_list );
 				shadow_color_button.set_keys( key_list );
diff --git a/glabels/print_op.vala b/glabels/print_op.vala
index 54585ff..fef06a0 100644
--- a/glabels/print_op.vala
+++ b/glabels/print_op.vala
@@ -27,12 +27,12 @@ namespace glabels
 
 	public class PrintOp : Gtk.PrintOperation
 	{
-		public Label     label       { get; private set; }
+		public Model     model       { get; private set; }
 
 
-		public PrintOp( Label label )
+		public PrintOp( Model model )
 		{
-			this.label = label;
+			this.model = model;
 
 			set_page_size();
 
@@ -43,7 +43,7 @@ namespace glabels
 
 		private void set_page_size()
 		{
-			Paper? paper = Db.lookup_paper_from_id( label.template.paper_id );
+			Paper? paper = Db.lookup_paper_from_id( model.label.template.paper_id );
 
 			Gtk.PaperSize psize;
 			if ( paper == null )
@@ -54,7 +54,7 @@ namespace glabels
 			else if ( Db.is_paper_id_other( paper.id ) )
 			{
 				psize = new Gtk.PaperSize.custom( paper.id, paper.name,
-				                                  label.template.page_width, label.template.page_height,
+				                                  model.label.template.page_width, model.label.template.page_height,
 				                                  Gtk.Unit.POINTS );
 			}
 			else
@@ -70,7 +70,7 @@ namespace glabels
 
 		private void on_begin_print( Gtk.PrintContext context )
 		{
-			set_n_pages( label.n_pages );
+			set_n_pages( model.print.n_pages );
 		}
 
 
@@ -78,9 +78,9 @@ namespace glabels
 		{
 			Cairo.Context cr = context.get_cairo_context();
 
-			if ( label.merge is MergeNone )
+			if ( model.label.merge is MergeNone )
 			{
-				label.print_simple_sheet( cr, i_page );
+				model.print.print_simple_sheet( cr, i_page );
 			}
 		}
 
diff --git a/glabels/print_op_dialog.vala b/glabels/print_op_dialog.vala
index e37c65f..110707f 100644
--- a/glabels/print_op_dialog.vala
+++ b/glabels/print_op_dialog.vala
@@ -28,9 +28,9 @@ namespace glabels
 	public class PrintOpDialog : PrintOp
 	{
 
-		public PrintOpDialog( Label label )
+		public PrintOpDialog( Model model )
 		{
-			base( label );
+			base( model );
 		}
 
 
diff --git a/glabels/property_editor.vala b/glabels/property_editor.vala
index 171612f..cb5aaff 100644
--- a/glabels/property_editor.vala
+++ b/glabels/property_editor.vala
@@ -27,7 +27,7 @@ namespace glabels
 
 	class PropertyEditor : Gtk.Box
 	{
-		private Label           label;
+		private Model           model;
 
 		private Gtk.Notebook    property_editor_notebook;
 
@@ -67,12 +67,12 @@ namespace glabels
 		}
 
 
-		public void set_label( Label label )
+		public void set_model( Model model )
 		{
-			this.label = label;
+			this.model = model;
 
 			property_editor_notebook.set_sensitive( true );
-			layout_preview.set_label( label );
+			layout_preview.set_model( model );
 		}
 
 
diff --git a/glabels/ui.vala b/glabels/ui.vala
index 6f0cf84..ffa31fd 100644
--- a/glabels/ui.vala
+++ b/glabels/ui.vala
@@ -33,7 +33,6 @@ namespace glabels
 
 			/* 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") },
@@ -437,7 +436,6 @@ namespace glabels
 					<menu action='FileMenu'>
 						<menuitem action='FileNew' />
 						<menuitem action='FileOpen' />
-						<menuitem action='FileRecentsMenu' />
 						<separator />
 						<menuitem action='FileSave' />
 						<menuitem action='FileSaveAs' />
@@ -709,8 +707,6 @@ namespace glabels
 			/* 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 );
 		}
@@ -718,46 +714,45 @@ namespace glabels
 
 		public void update_all( View view )
 		{
-			Label label = view.label;
+			Model model = view.model;
 
 			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_sensitive( "/ui/MenuBar/EditMenu/EditUndo", model.undo_redo.can_undo() );
+			set_verb_sensitive( "/ui/MenuBar/EditMenu/EditRedo", model.undo_redo.can_redo() );
 
-			set_verb_list_sensitive( doc_modified_verbs, label.modified );
+			set_verb_list_sensitive( doc_modified_verbs, model.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( selection_verbs, !model.label.is_selection_empty() );
 
-			set_verb_list_sensitive( atomic_selection_verbs, label.is_selection_atomic() );
+			set_verb_list_sensitive( atomic_selection_verbs, model.label.is_selection_atomic() );
 
 			set_verb_list_sensitive( multi_selection_verbs,
-			                         !label.is_selection_empty() && !label.is_selection_atomic() );
+			                         !model.label.is_selection_empty() && !model.label.is_selection_atomic() );
 		}
 
 
-		public void update_modified_verbs( Label label )
+		public void update_modified_verbs( Model model )
 		{
-			set_verb_list_sensitive( doc_modified_verbs, label.modified );
+			set_verb_list_sensitive( doc_modified_verbs, model.label.modified );
 		}
 
 
-		public void update_selection_verbs( View view,
-		                                    bool has_focus )
+		public void update_selection_verbs( Model model, bool view_has_focus )
 		{
-			if ( has_focus )
+			if ( view_has_focus )
 			{
-				set_verb_list_sensitive( selection_verbs, !view.label.is_selection_empty() );
+				set_verb_list_sensitive( selection_verbs, !model.label.is_selection_empty() );
 
 				set_verb_list_sensitive( atomic_selection_verbs,
-				                         view.label.is_selection_atomic() );
+				                         model.label.is_selection_atomic() );
 
 				set_verb_list_sensitive( multi_selection_verbs,
-				                         !view.label.is_selection_empty() &&
-				                         !view.label.is_selection_atomic() );
+				                         !model.label.is_selection_empty() &&
+				                         !model.label.is_selection_atomic() );
 			}
 			else
 			{
@@ -781,24 +776,24 @@ namespace glabels
 		}
 
 
-		public void update_undo_redo_verbs( Label label )
+		public void update_undo_redo_verbs( Model model )
 		{
 			Gtk.MenuItem  menu_item;
 			string        description;
 			string        menu_label;
 
 			menu_item = (Gtk.MenuItem)get_widget( "/MenuBar/EditMenu/EditUndo" );
-			description = label.get_undo_description();
+			description = model.undo_redo.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();
+			description = model.undo_redo.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() );
+			set_verb_sensitive( "/ui/MenuBar/EditMenu/EditUndo", model.undo_redo.can_undo() );
+			set_verb_sensitive( "/ui/MenuBar/EditMenu/EditRedo", model.undo_redo.can_redo() );
 		}
 
 
@@ -959,41 +954,21 @@ namespace glabels
 		}
 
 
-		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 );
+			File.save( window.model.label, window );
 		}
 
 
 		private void on_file_save_as( Gtk.Action action )
 		{
-			File.save_as( window.view.label, window );
+			File.save_as( window.model.label, window );
 		}
 
 
 		private void on_file_print( Gtk.Action action )
 		{
-			File.print( window.view.label, window );
+			File.print( window.model, window );
 		}
 
 
@@ -1047,13 +1022,13 @@ namespace glabels
 
 		private void on_edit_select_all( Gtk.Action action )
 		{
-			window.view.label.select_all();
+			window.model.label.select_all();
 		}
 
 
 		private void on_edit_unselect_all( Gtk.Action action )
 		{
-			window.view.label.unselect_all();
+			window.model.label.unselect_all();
 		}
 
 
@@ -1238,85 +1213,85 @@ namespace glabels
 
 		private void on_objects_raise( Gtk.Action action )
 		{
-			window.view.label.raise_selection_to_top();
+			window.model.label.raise_selection_to_top();
 		}
 
 
 		private void on_objects_lower( Gtk.Action action )
 		{
-			window.view.label.lower_selection_to_bottom();
+			window.model.label.lower_selection_to_bottom();
 		}
 
 
 		private void on_objects_rotate_left( Gtk.Action action )
 		{
-			window.view.label.rotate_selection_left();
+			window.model.label.rotate_selection_left();
 		}
 
 
 		private void on_objects_rotate_right( Gtk.Action action )
 		{
-			window.view.label.rotate_selection_right();
+			window.model.label.rotate_selection_right();
 		}
 
 
 		private void on_objects_flip_horiz( Gtk.Action action )
 		{
-			window.view.label.flip_selection_horiz();
+			window.model.label.flip_selection_horiz();
 		}
 
 
 		private void on_objects_flip_vert( Gtk.Action action )
 		{
-			window.view.label.flip_selection_vert();
+			window.model.label.flip_selection_vert();
 		}
 
 
 		private void on_objects_align_left( Gtk.Action action )
 		{
-			window.view.label.align_selection_left();
+			window.model.label.align_selection_left();
 		}
 
 
 		private void on_objects_align_right( Gtk.Action action )
 		{
-			window.view.label.align_selection_right();
+			window.model.label.align_selection_right();
 		}
 
 
 		private void on_objects_align_hcenter( Gtk.Action action )
 		{
-			window.view.label.align_selection_hcenter();
+			window.model.label.align_selection_hcenter();
 		}
 
 
 		private void on_objects_align_top( Gtk.Action action )
 		{
-			window.view.label.align_selection_top();
+			window.model.label.align_selection_top();
 		}
 
 
 		private void on_objects_align_bottom( Gtk.Action action )
 		{
-			window.view.label.align_selection_bottom();
+			window.model.label.align_selection_bottom();
 		}
 
 
 		private void on_objects_align_vcenter( Gtk.Action action )
 		{
-			window.view.label.align_selection_vcenter();
+			window.model.label.align_selection_vcenter();
 		}
 
 
 		private void on_objects_center_horiz( Gtk.Action action )
 		{
-			window.view.label.center_selection_horiz();
+			window.model.label.center_selection_horiz();
 		}
 
 
 		private void on_objects_center_vert( Gtk.Action action )
 		{
-			window.view.label.center_selection_vert();
+			window.model.label.center_selection_vert();
 		}
 
 
diff --git a/glabels/view.vala b/glabels/view.vala
index 8135013..8dc2b96 100644
--- a/glabels/view.vala
+++ b/glabels/view.vala
@@ -134,22 +134,22 @@ namespace glabels
 		}
 
 
-		private Label? _label;
-		public Label? label
+		private Model? _model;
+		public Model? model
 		{
 			get
 			{
-				return _label;
+				return _model;
 			}
 
 			set
 			{
-				_label = value;
-				if ( _label != null )
+				_model = value;
+				if ( _model != null )
 				{
-					label.changed.connect( on_label_changed );
-					label.selection_changed.connect( on_label_changed );
-					label.size_changed.connect( on_label_size_changed );
+					model.label.changed.connect( on_label_changed );
+					model.label.selection_changed.connect( on_label_changed );
+					model.label.size_changed.connect( on_label_size_changed );
 
 					canvas.focus_in_event.connect( on_focus_in_event );
 					canvas.focus_out_event.connect( on_focus_out_event );
@@ -305,7 +305,7 @@ namespace glabels
 			int h_view = get_allocated_height();
 
 			double w_label, h_label;
-			label.get_size( out w_label, out h_label );
+			model.label.get_size( out w_label, out h_label );
 
 			/* Calculate best scale */
 			double x_scale = (double)( w_view - ZOOMTOFIT_PAD ) / w_label;
@@ -432,12 +432,12 @@ namespace glabels
 		private void draw_layers( Gdk.Window    window,
 		                          Cairo.Context cr )
 		{
-			if ( label != null )
+			if ( model != null )
 			{
 				this.scale = zoom * home_scale;
 
 				double w, h;
-				label.get_size( out w, out h );
+				model.label.get_size( out w, out h );
 
 				canvas.set_size( (int)(w*scale + 8), (int)(h*scale + 8) );
 
@@ -470,7 +470,7 @@ namespace glabels
 		{
 			cr.save();
 
-			if (label.rotate)
+			if ( model.label.rotate )
 			{
 				double w, h;
 				frame.get_size( out w, out h );
@@ -487,7 +487,7 @@ namespace glabels
 
 		private void draw_bg_layer( Cairo.Context cr )
 		{
-			TemplateFrame frame = label.template.frames.first().data;
+			TemplateFrame frame = model.label.template.frames.first().data;
 
 			double w, h;
 			frame.get_size( out w, out h );
@@ -514,10 +514,10 @@ namespace glabels
 		{
 			if ( grid_visible )
 			{
-				TemplateFrame frame = label.template.frames.first().data;
+				TemplateFrame frame = model.label.template.frames.first().data;
 
 				double w, h;
-				label.get_size( out w, out h );
+				model.label.get_size( out w, out h );
         
 				double x0, y0;
 				if ( frame is TemplateFrameRect )
@@ -564,11 +564,11 @@ namespace glabels
 		{
 			if ( markup_visible )
 			{
-				TemplateFrame frame = label.template.frames.first().data;
+				TemplateFrame frame = model.label.template.frames.first().data;
 
 				cr.save();
 
-				if (label.rotate)
+				if ( model.label.rotate )
 				{
 					double w, h;
 					frame.get_size( out w, out h );
@@ -594,13 +594,13 @@ namespace glabels
 
 		private void draw_objects_layer( Cairo.Context cr )
 		{
-			label.draw( cr, true, null );
+			model.label.draw( cr, true, null );
 		}
 
 
 		private void draw_fg_layer( Cairo.Context cr )
 		{
-			TemplateFrame frame = label.template.frames.first().data;
+			TemplateFrame frame = model.label.template.frames.first().data;
 
 			set_frame_path( cr, frame );
 
@@ -615,7 +615,7 @@ namespace glabels
 			cr.save();
 			cr.set_antialias( Cairo.Antialias.NONE );
 
-			foreach ( LabelObject object in label.object_list )
+			foreach ( LabelObject object in model.label.object_list )
 			{
 				if ( object.is_selected() )
 				{
@@ -782,12 +782,12 @@ namespace glabels
 				case State.IDLE:
 					Gdk.Cursor cursor;
 					Handle? handle;
-					if ( label.is_selection_atomic() &&
-					     (handle = label.handle_at( cr, event.x, event.y )) != null )
+					if ( model.label.is_selection_atomic() &&
+					     (handle = model.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 )
+					else if ( model.label.object_at( cr, event.x, event.y) != null )
 					{
 						cursor = new Gdk.Cursor( Gdk.CursorType.FLEUR );
 					}
@@ -805,7 +805,7 @@ namespace glabels
 					break;
 
 				case State.ARROW_MOVE:
-					label.move_selection( (x - move_last_x), (y - move_last_y) );
+					model.label.move_selection( (x - move_last_x), (y - move_last_y) );
 					move_last_x = x;
 					move_last_y = y;
 					break;
@@ -892,8 +892,8 @@ namespace glabels
 				{
 					LabelObject object;
 					Handle? handle = null;
-					if ( label.is_selection_atomic() &&
-					     (handle = label.handle_at( cr, event.x, event.y )) != null )
+					if ( model.label.is_selection_atomic() &&
+					     (handle = model.label.handle_at( cr, event.x, event.y )) != null )
 					{
 						resize_object = handle.owner;
 						resize_handle = handle;
@@ -901,19 +901,19 @@ namespace glabels
 
 						state = State.ARROW_RESIZE;
 					}
-					else if ( (object = label.object_at( cr, event.x, event.y)) != null )
+					else if ( (object = model.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 );
+								model.label.unselect_object( object );
 							}
 							else
 							{
 								/* Add to current selection */
-								label.select_object( object );
+								model.label.select_object( object );
 							}
 						}
 						else
@@ -921,9 +921,9 @@ namespace glabels
 							if ( !object.is_selected() )
 							{
 								/* remove any selections before adding */
-								label.unselect_all();
+								model.label.unselect_all();
 								/* Add to current selection */
-								label.select_object( object );
+								model.label.select_object( object );
 							}
 						}
 
@@ -936,7 +936,7 @@ namespace glabels
 					{
 						if ( (event.state & Gdk.ModifierType.CONTROL_MASK) == 0 )
 						{
-							label.unselect_all();
+							model.label.unselect_all();
 						}
 
 						select_region_visible = true;
@@ -955,8 +955,6 @@ namespace glabels
 
 					if ( state == State.IDLE )
 					{
-						LabelObject object;
-
 						switch ( create_object_type )
 						{
 						case CreateType.BOX:
@@ -984,7 +982,7 @@ namespace glabels
 
 						create_object.set_position( x, y );
 						create_object.set_size( 0, 0 );
-						label.add_object( create_object );
+						model.label.add_object( create_object );
 
 						create_x0 = x;
 						create_y0 = y;
@@ -1057,7 +1055,7 @@ namespace glabels
 						select_region_visible = false;
 						select_region.x2 = x;
 						select_region.y2 = y;
-						label.select_region( select_region );
+						model.label.select_region( select_region );
 						update();
 						state = State.IDLE;
 						break;
@@ -1076,8 +1074,8 @@ namespace glabels
 					Gdk.Cursor cursor = new Gdk.Cursor( Gdk.CursorType.LEFT_PTR );
 					window.set_cursor( cursor );
 
-					label.unselect_all();
-					label.select_object( create_object );
+					model.label.unselect_all();
+					model.label.select_object( create_object );
 
 					in_object_create_mode = false;
 					state = State.IDLE;
@@ -1103,27 +1101,27 @@ namespace glabels
 
 				case Gdk.Key.Left:
 				case Gdk.Key.KP_Left:
-					label.move_selection( (-1 / zoom), 0 );
+					model.label.move_selection( (-1 / zoom), 0 );
 					break;
 
 				case Gdk.Key.Up:
 				case Gdk.Key.KP_Up:
-					label.move_selection( 0, (-1 / zoom) );
+					model.label.move_selection( 0, (-1 / zoom) );
 					break;
 
 				case Gdk.Key.Right:
 				case Gdk.Key.KP_Right:
-					label.move_selection( (1 / zoom), 0 );
+					model.label.move_selection( (1 / zoom), 0 );
 					break;
 
 				case Gdk.Key.Down:
 				case Gdk.Key.KP_Down:
-					label.move_selection( 0, (1 / zoom) );
+					model.label.move_selection( 0, (1 / zoom) );
 					break;
 
 				case Gdk.Key.Delete:
 				case Gdk.Key.KP_Delete:
-					label.delete_selection();
+					model.label.delete_selection();
 					Gdk.Window window = canvas.get_window();
 					Gdk.Cursor cursor = new Gdk.Cursor( Gdk.CursorType.LEFT_PTR );
 					window.set_cursor( cursor );
diff --git a/glabels/window.vala b/glabels/window.vala
index d8dc042..73c26dc 100644
--- a/glabels/window.vala
+++ b/glabels/window.vala
@@ -39,6 +39,7 @@ namespace glabels
 
 		private Gtk.HBox       content_hbox;
 
+		public  Model?         model { get; private set; }
 		public  View?          view { get; private set; }
 
 		public  Gtk.Statusbar  statusbar { get; private set; }
@@ -155,18 +156,20 @@ namespace glabels
 
 		public bool is_empty()
 		{
-			return view.label == null;
+			return model == null;
 		}
 
 
 		public void set_label( Label label )
 		{
+			model = new Model( label );
+
 			label.modified = false;
 			set_window_title( label );
 
-			property_editor.set_label( label );
-			view.label = label;
-			object_editor.set_label( label );
+			property_editor.set_model( model );
+			view.model = model;
+			object_editor.set_model( model );
 
 			view.zoom_to_fit();
 
@@ -218,13 +221,13 @@ namespace glabels
 
 		private void on_selection_changed()
 		{
-			ui.update_selection_verbs( view, true );
+			ui.update_selection_verbs( model, true );
 		}
 
 
 		private void on_context_menu_activate( uint button, uint activate_time )
 		{
-			if ( view.label.is_selection_empty() )
+			if ( model.label.is_selection_empty() )
 			{
 				empty_selection_context_menu.popup( null, null, null, button, activate_time );
 			}
@@ -262,14 +265,14 @@ namespace glabels
 
 		private void on_name_changed()
 		{
-			set_window_title( view.label );
+			set_window_title( model.label );
 		}
 
 
 		private void on_modified_changed()
 		{
-			set_window_title( view.label );
-			ui.update_modified_verbs( view.label );
+			set_window_title( model.label );
+			ui.update_modified_verbs( model );
 		}
 
 



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