changeset 414:16562b44f9ae

updated to version of svg-edit Revision r628
author Reimar Bauer <rb.proj AT googlemail DOT com>
date Fri, 11 Sep 2009 22:33:42 +0200
parents 6b1cc30131d6
children 17a44134af07
files htdocs/svg-edit/editor/jgraduate/css/jGraduate-0.2.0.css htdocs/svg-edit/editor/jgraduate/jquery.jgraduate.js htdocs/svg-edit/editor/svg-editor.css htdocs/svg-edit/editor/svg-editor.html htdocs/svg-edit/editor/svg-editor.js htdocs/svg-edit/editor/svgcanvas.js htdocs/svg-edit/firefox-extension/chrome.manifest htdocs/svg-edit/firefox-extension/handlers.js htdocs/svg-edit/firefox-extension/install.rdf htdocs/svg-edit/opera-widget/config.xml htdocs/svg-edit/wave/svg-edit.xml
diffstat 11 files changed, 1290 insertions(+), 680 deletions(-) [+]
line wrap: on
line diff
--- a/htdocs/svg-edit/editor/jgraduate/css/jGraduate-0.2.0.css	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/editor/jgraduate/css/jGraduate-0.2.0.css	Fri Sep 11 22:33:42 2009 +0200
@@ -52,6 +52,7 @@
 	display: none;
 	border: outset 1px #666;
 	padding: 10px 7px 5px 5px;
+	overflow: auto;
 }
 
 .jGraduate_tabs {
@@ -63,7 +64,7 @@
 }
 
 div.jGraduate_Swatch {
-	display: inline-block;
+	float: left;
 	margin: 8px;
 }
 div.jGraduate_GradContainer {
@@ -90,13 +91,13 @@
 }
 
 div.jGraduate_OpacityField {
-	margin-top: 110px;
-	margin-left: -10px;
+	position: absolute;
+	bottom: 25px;
+	left: 292px;
 }
 
 div.jGraduate_Form {
-	display: inline-block;
-	vertical-align: top;
+	float: left;
 	width: 140px;
 	margin: -3px 3px 0px 4px;
 }
@@ -107,8 +108,7 @@
 }
 
 div.jGraduate_OkCancel {
-	display: inline-block;
-	vertical-align: top;	
+	float: left;
 	width: 113px;
 }
 
@@ -123,13 +123,17 @@
 }
 
 .colorBox {
-	display: inline-block;
+	float: left;
 	height: 16px;
 	width: 16px;
 	border: 1px solid #808080;
-	vertical-align: -7px;
 	cursor: pointer;
-	margin: 4px;
+	margin: 4px 4px 4px 30px;
+}
+
+.colorBox + label {
+	float: left;
+	margin-top: 7px;
 }
 
 label.jGraduate_Form_Heading {
@@ -151,6 +155,7 @@
 	margin: 2px;
 	width: 110px;
 	text-align: center;
+	overflow: auto;
 }
 
 div.jGraduate_LightBox {
--- a/htdocs/svg-edit/editor/jgraduate/jquery.jgraduate.js	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/editor/jgraduate/jquery.jgraduate.js	Fri Sep 11 22:33:42 2009 +0200
@@ -387,8 +387,9 @@
 			// handle dragging the stop around the swatch
             var draggingStop = null;
             var startx = -1, starty = -1;
-            // for whatever reason, Opera does not allow $('image.stop') here
-            $('.stop').mousedown(function(evt) {
+            // for whatever reason, Opera does not allow $('image.stop') here,
+            // and Firefox 1.5 does not allow $('.stop')
+            $('.stop, #color_picker_jGraduate_GradContainer image').mousedown(function(evt) {
             	draggingStop = this;
             	startx = evt.clientX;
             	starty = evt.clientY;
--- a/htdocs/svg-edit/editor/svg-editor.css	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/editor/svg-editor.css	Fri Sep 11 22:33:42 2009 +0200
@@ -3,9 +3,6 @@
 }
 
 #svg_editor {
-    position: relative;
-    width: 660px;
-    height: 640px;
 	font-size: 8pt;
 	font-family: Verdana, Helvetica, Arial;
 	color: #000000;
@@ -29,7 +26,6 @@
 }
 
 #svg_editor #svgcanvas {
-    position: relative;
 	background-color: #FFFFFF;
 	text-align: center;
 	vertical-align: middle;
@@ -39,7 +35,6 @@
 }
 
 #svg_editor div#palette_holder {
-    position: relative;
 	overflow-x: scroll;
 	overflow-y: hidden;
 	height: 31px;
@@ -62,12 +57,11 @@
 }
 
 #svg_editor div#workarea {
-	position: relative;
-    width: 100%;
-	top: 80px;
+	position:absolute;
+	top: 70px;
 	left: 40px;
 	right: 2px;
-	bottom: 0px;
+	bottom: 60px;
 	background-color: #A0A0A0;
 	border: 1px solid #808080;
 	overflow: auto;
@@ -80,7 +74,6 @@
 }
 
 #svg_editor #logo {
-    display: None;
 	position: absolute;
 	top: 4px;
 	left: 4px;
@@ -98,7 +91,7 @@
 	left: 38px;
 	right: 2px;
 	top: 2px;
-	height: 58px;
+	height: 68px;
 	border-bottom: none;
 }
 
@@ -110,7 +103,7 @@
 	position: absolute;
 	border-right: none;
 	width: 36px;
-	top: 78px;
+	top: 68px;
 	left: 2px;
 }
 
@@ -128,6 +121,10 @@
 	display: none;
 }
 
+#svg_editor #g_panel {
+	display: none;
+}
+
 #svg_editor #rect_panel {
 	display: none;
 }
@@ -144,6 +141,10 @@
 	display: none;
 }
 
+#svg_editor #image_panel {
+	display: none;
+}
+
 #svg_editor #text_panel {
 	display: none;
 }
@@ -164,6 +165,10 @@
 	vertical-align: 12px;
 }
 
+#svg_editor #image_panel .image_tool {
+	vertical-align: 12px;
+}
+
 #svg_editor #circle_panel .circle_tool {
 	vertical-align:12px;
 }
@@ -199,7 +204,6 @@
 }
 
 #svg_editor .tool_button_current {
-    position: relative;
 	border-left: 1px solid #808080;
 	border-top: 1px solid #808080;
 	border-right: 1px solid #FFFFFF;
@@ -208,13 +212,11 @@
 }
 
 #svg_editor .tool_button_disabled {
-    position: relative;
 	opacity: 0.5;
 	cursor: default;
 }
 
 #svg_editor .tool_sep {
-    position: relative;
 	width: 2px;
 	height: 24px;
 	margin: 2px;
@@ -259,7 +261,6 @@
 /* TODO: figure out what more-specific selector causes me to write this atrocity and not
          simply .tool_flyout_button */
 #svg_editor #tools_rect .tool_flyout_button, #svg_editor #tools_ellipse .tool_flyout_button {
-    position: relative;
 	float: left;
 	background-color: #E8E8E8;
 	border-left: 1px solid #FFFFFF;
@@ -279,16 +280,14 @@
 }
 
 #svg_editor #tools_bottom {
-	position: relative;
-    top: 80px;
+	position: absolute;
 	left: 40px;
-	right: 0px;
-	bottom: 0px;
-	height: 0px;
+	right: 2px;
+	bottom: 2px;
+	height: 60px;
 }
 
 #svg_editor #tools_bottom_1 {
-    display: none;
 	width: 115px;
 	float: left;
 }
@@ -299,7 +298,6 @@
 }
 
 #svg_editor #tools_bottom_3 {
-    position: relative;
 }
 
 #svg_editor #copyright {
--- a/htdocs/svg-edit/editor/svg-editor.html	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/editor/svg-editor.html	Fri Sep 11 22:33:42 2009 +0200
@@ -13,9 +13,9 @@
 <!--script type="text/javascript" src="js-hotkeys/jquery.hotkeys-0.7.9.js"></script-->
 <script type="text/javascript" src="jgraduate/jpicker-1.0.9.min.js"></script>
 <script type="text/javascript" src="jgraduate/jquery.jgraduate.js"></script>
+<!--script type="text/javascript" src="jgraduate/jquery.jgraduate.min.js"></script-->
 <script type="text/javascript" src="spinbtn/JQuerySpinBtn.js"></script>
 <!--script type="text/javascript" src="spinbtn/JQuerySpinBtn.min.js"></script-->
-<!--script type="text/javascript" src="jgraduate/jquery.jgraduate.min.js"></script-->
 <script type="text/javascript" src="svgcanvas.js"></script>
 <!--script type="text/javascript" src="svgcanvas.min.js"></script-->
 <script type="text/javascript" src="svg-editor.js"></script>
@@ -76,6 +76,7 @@
 			<option value="0.3">30 %</option>
 			<option value="0.2">20 %</option>
 			<option value="0.1">10 %</option>
+			<option value="0">0 %</option>
 		</select>
 		<span class="selected_tool">angle:</span>
 		<input id="angle" class="selected_tool" title="Change rotation angle" alt="Rotation Angle" size="2" value="0" type="text"/>
@@ -100,6 +101,13 @@
 		<option value="smallest">smallest object</option>
 		<option value="page">page</option>
 		</select>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<img class="tool_button" id="tool_group" src="images/shape_group.png" title="Group Elements [G]" alt="Group"/>
+	</div>
+
+	<div id="g_panel">
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<img class="tool_button" id="tool_ungroup" src="images/shape_ungroup.png" title="Ungroup Elements [G]" alt="Ungroup"/>
 	</div>
 
 	<div id="rect_panel">
@@ -116,6 +124,21 @@
 		<input id="rect_rx" size="3" value="0" class="rect_tool" type="text" title="Change Rectangle Corner Radius" alt="Corner Radius"/>
 	</div>
 
+	<div id="image_panel">
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<label class="image_tool">x:</label>
+		<input id="image_x" class="image_tool attr_changer" title="Change image X coordinate" alt="x" size="3"/>
+		<label class="image_tool">y:</label>
+		<input id="image_y" class="image_tool attr_changer" title="Change image Y coordinate" alt="y" size="3"/>
+		<label class="image_tool">width:</label>
+		<input id="image_width" class="image_tool attr_changer" title="Change image width" alt="width" size="3"/>
+		<label class="image_tool">height:</label>
+		<input id="image_height" class="image_tool attr_changer" title="Change image height" alt="height" size="3"/>
+    <label class="image_tool">url:</label>
+    <input id="image_url" class="image_tool" type="text" title="Change URL" size="35"/>
+  </div>
+
+
 	<div id="circle_panel">
 		<img class="tool_sep" src="images/sep.png" alt="|"/>
 		<label class="circle_tool">cx:</label>
@@ -196,6 +219,7 @@
 	<img class="flyout_arrow_horiz" src="images/flyouth.png"/>
 	<img class="tool_button" id="tool_text" src="images/text.png" title="Text Tool [6]" alt="Text"/>
 	<img class="tool_button" id="tool_poly" src="images/polygon.png" title="Poly Tool [7]" alt="Poly"/>
+	<img class="tool_button" id="tool_image" src="images/image.png" title="Image Tool [8]" alt="Image"/>
 </div> <!-- tools_left -->
 
 <div id="tools_bottom" class="tools_panel">
@@ -240,7 +264,7 @@
 
 	<div id="tools_bottom_3">
 		<div id="palette_holder"><div id="palette" title="Click to change fill color, shift-click to change stroke color"></div></div>
-		<div id="copyright">Powered by <a href="http://svg-edit.googlecode.com/" target="_blank">SVG-edit v2.3-Alpha</a></div>
+		<div id="copyright">Powered by <a href="http://svg-edit.googlecode.com/" target="_blank">SVG-edit v2.4-unstable</a></div>
 	</div>
 </div>
 
--- a/htdocs/svg-edit/editor/svg-editor.js	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/editor/svg-editor.js	Fri Sep 11 22:33:42 2009 +0200
@@ -10,22 +10,7 @@
 
 	var isMac = false; //(navigator.platform.indexOf("Mac") != -1);
 	var modKey = ""; //(isMac ? "meta+" : "ctrl+");
-    var htdocs = document.getElementById("htdocs").innerHTML.replace('<!-- ', '').replace(' -->', '');
-    var svg = document.getElementById("svgdata");
 	var svgCanvas = new SvgCanvas(document.getElementById("svgcanvas"));
-    var filecontent =  svg.innerHTML;
-     // because moin uses currently HTML 4.0.1 we send the data as a comment in the HTML code
-    filecontent = filecontent.replace('<!-- ', '').replace(' -->', '');
-    if (filecontent != '') {
-         if (!svgCanvas.setSvgString(filecontent)) {
-             if( !confirm('There were parsing errors in your SVG source.\nRevert back to original SVG source?') ) {
-                return false;
-             }
-         }
-         svgCanvas.clearSelection();
-    }
-
-
 
 	var setSelectMode = function() {
 		$('.tool_button_current').removeClass('tool_button_current').addClass('tool_button');
@@ -51,17 +36,7 @@
 	// with a gradient will appear black in Firefox, etc.  See bug 308590
 	// https://bugzilla.mozilla.org/show_bug.cgi?id=308590
 	var saveHandler = function(window,svg) {
-        var titlename = window.content.parent.document.title.split('-')[0];
-        titlename = titlename.replace(' ', '');
-        var svg_data = Utils.encode64(svg);
-        var pagename = titlename.split(':')[0];
-        var svg_target = titlename.split(':')[1];
-        $.post(
-               pagename,
-               {'action': "SvgEditor", 'do': "save", 'target': svg_target, 'svg_data': svg_data}
-               );
-
-	//	window.open("data:image/svg+xml;base64," + Utils.encode64(svg));
+		window.open("data:image/svg+xml;base64," + Utils.encode64(svg));
 	};
 
 	// called when we've selected a different element
@@ -88,9 +63,10 @@
 		for (var i = 0; i < elems.length; ++i) {
 			var elem = elems[i];
 			// if the element changed was the svg, then it could be a resolution change
-			if (elem && elem.tagName == "svg") {
-				changeResolution(parseInt(elem.getAttribute("width")),
-								 parseInt(elem.getAttribute("height")));
+			if (elem && elem.tagName == "svg" && elem.getAttribute("viewBox")) {
+				var vb = elem.getAttribute("viewBox").split(' ');
+				changeResolution(parseInt(vb[2]),
+								 parseInt(vb[3]));
 			}
 		}
 
@@ -106,7 +82,10 @@
 
 	// updates the toolbar (colors, opacity, etc) based on the selected element
 	var updateToolbar = function() {
-		if (selectedElement != null) {
+		if (selectedElement != null && 
+			selectedElement.tagName != "image" &&
+			selectedElement.tagName != "g")
+		{
 			// get opacity values
 			var fillOpacity = parseFloat(selectedElement.getAttribute("fill-opacity"));
 			if (isNaN(fillOpacity)) {
@@ -120,13 +99,15 @@
 
 			// update fill color and opacity
 			var fillColor = selectedElement.getAttribute("fill")||"none";
-			svgCanvas.setFillColor(fillColor);
-			svgCanvas.setFillOpacity(fillOpacity);
+			// prevent undo on these canvas changes
+			svgCanvas.setFillColor(fillColor, true);
+			svgCanvas.setFillOpacity(fillOpacity, true);
 
 			// update stroke color and opacity
 			var strokeColor = selectedElement.getAttribute("stroke")||"none";
-			svgCanvas.setStrokeColor(strokeColor);
-			svgCanvas.setStrokeOpacity(strokeOpacity);
+			// prevent undo on these canvas changes
+			svgCanvas.setStrokeColor(strokeColor, true);
+			svgCanvas.setStrokeOpacity(strokeOpacity, true);
 
 			fillOpacity *= 100;
 			strokeOpacity *= 100;
@@ -190,15 +171,17 @@
 			return;
 		}
 		
-		$('#selected_panel, #multiselected_panel, #rect_panel, #circle_panel,\
-			#ellipse_panel, #line_panel, #text_panel').hide();
+		$('#selected_panel, #multiselected_panel, #g_panel, #rect_panel, #circle_panel,\
+			#ellipse_panel, #line_panel, #text_panel, #image_panel').hide();
 		if (elem != null) {
 			$('#angle').val(svgCanvas.getRotationAngle(elem));
 			$('#selected_panel').show();
 			
 			// update contextual tools here
 			var panels = {
+				g: [],
 				rect: ['radius','x','y','width','height'],
+				image: ['x','y','width','height'],
 				circle: ['cx','cy','r'],
 				ellipse: ['cx','cy','rx','ry'],
 				line: ['x1','y1','x2','y2'], 
@@ -239,6 +222,10 @@
 						$('#text').focus().select();
 					}
 				}
+				else if(el_name == 'image') {
+          			var xlinkNS="http://www.w3.org/1999/xlink";
+          			$('#image_url').val(elem.getAttributeNS(xlinkNS, "href"));
+        		}
 			}
 		} // if (elem != null)
 		else if (multiselected) {
@@ -265,6 +252,8 @@
 	$('#text').focus( function(){ textBeingEntered = true; } );
 	$('#text').blur( function(){ textBeingEntered = false; } );
 
+  
+  
 	// bind the selected event to our function that handles updates to the UI
 	svgCanvas.bind("selected", selectedChanged);
 	svgCanvas.bind("changed", elementChanged);
@@ -286,7 +275,11 @@
 	}
 	
 	var changeStrokeWidth = function(ctl) {
-		svgCanvas.setStrokeWidth(ctl.value);
+		var val = ctl.value;
+		if(val == 0 && selectedElement && $.inArray(selectedElement.nodeName, ['line', 'polyline']) != -1) {
+			val = ctl.value = 1;
+		}
+		svgCanvas.setStrokeWidth(val);
 	}
 	
 	var changeRotationAngle = function(ctl) {
@@ -297,6 +290,9 @@
 		svgCanvas.setStrokeStyle(this.options[this.selectedIndex].value);
 	});
 
+	// Lose focus for select elements when changed (Allows keyboard shortcuts to work better)
+	$('select').change(function(){$(this).blur();});
+
 	$('#group_opacity').change(function(){
 		svgCanvas.setOpacity(this.options[this.selectedIndex].value);
 	});
@@ -312,6 +308,10 @@
 	$('#text').keyup(function(){
 		svgCanvas.setTextContent(this.value);
 	});
+  
+  $('#image_url').keyup(function(){
+    svgCanvas.setImageURL(this.value); 
+  });
 
 	$('.attr_changer').change(function() {
 		var attr = this.getAttribute("alt");
@@ -340,6 +340,13 @@
 		
 		svgCanvas.changeSelectedAttribute(attr, val);
 	});
+	
+	// Prevent selection of elements when shift-clicking
+	$('#palette').mouseover(function() {
+		var inp = $('<input type="hidden">');
+		$(this).append(inp);
+		inp.focus().remove();
+	});
 
 	$('.palette_item').click(function(evt){
 		var picker = (evt.shiftKey ? "stroke" : "fill");
@@ -361,15 +368,19 @@
 		
 		if (evt.shiftKey) {
 			strokePaint = paint;
-			svgCanvas.setStrokeColor(color);
-			if (color != 'none') {
+			if (svgCanvas.getStrokeColor() != color) {
+				svgCanvas.setStrokeColor(color);
+			}
+			if (color != 'none' && svgCanvas.getStrokeOpacity() != 1) {
 				svgCanvas.setStrokeOpacity(1.0);
 				$("#stroke_opacity").html("100 %");
 			}
 		} else {
 			fillPaint = paint;
-			svgCanvas.setFillColor(color);
-			if (color != 'none') {
+			if (svgCanvas.getFillColor() != color) {
+				svgCanvas.setFillColor(color);
+			}
+			if (color != 'none' && svgCanvas.getFillOpacity() != 1) {
 				svgCanvas.setFillOpacity(1.0);
 				$("#fill_opacity").html("100 %");
 			}
@@ -418,21 +429,27 @@
 			flyoutspeed = 'normal';
 			svgCanvas.setMode('square');
 		}
-		$('#tools_rect_show').attr('src', htdocs + 'images/square.png');
+		$('#tools_rect_show').attr('src', 'images/square.png');
 	};
 
 	var clickRect = function(){
 		if (toolButtonClick('#tools_rect_show')) {
 			svgCanvas.setMode('rect');
 		}
-		$('#tools_rect_show').attr('src', htdocs + 'images/rect.png');
+		$('#tools_rect_show').attr('src', 'images/rect.png');
+	};
+
+	var clickImage = function(){
+		if (toolButtonClick('#tool_image')) {
+			svgCanvas.setMode('image');
+		}
 	};
 
 	var clickFHRect = function(){
 		if (toolButtonClick('#tools_rect_show')) {
 			svgCanvas.setMode('fhrect');
 		}
-		$('#tools_rect_show').attr('src', htdocs + 'images/freehand-square.png');
+		$('#tools_rect_show').attr('src', 'images/freehand-square.png');
 	};
 
 	var clickCircle = function(){
@@ -440,21 +457,21 @@
 			flyoutspeed = 'normal';
 			svgCanvas.setMode('circle');
 		}
-		$('#tools_ellipse_show').attr('src', htdocs + 'images/circle.png');
+		$('#tools_ellipse_show').attr('src', 'images/circle.png');
 	};
 
 	var clickEllipse = function(){
 		if (toolButtonClick('#tools_ellipse_show')) {
 			svgCanvas.setMode('ellipse');
 		}
-		$('#tools_ellipse_show').attr('src', htdocs + 'images/ellipse.png');
+		$('#tools_ellipse_show').attr('src', 'images/ellipse.png');
 	};
 
 	var clickFHEllipse = function(){
 		if (toolButtonClick('#tools_ellipse_show')) {
 			svgCanvas.setMode('fhellipse');
 		}
-		$('#tools_ellipse_show').attr('src', htdocs + 'images/freehand-circle.png');
+		$('#tools_ellipse_show').attr('src', 'images/freehand-circle.png');
 	};
 
 	var clickText = function(){
@@ -544,6 +561,17 @@
 			svgCanvas.redo();
 	};
 	
+	var clickGroup = function(){
+		// group
+		if (multiselected) {
+			svgCanvas.groupSelectedElements();
+		}
+		// ungroup
+		else {
+			svgCanvas.ungroupSelectedElement();
+		}
+	};
+	
 	var clickClone = function(){
 		svgCanvas.cloneSelectedElements();
 	};
@@ -567,6 +595,13 @@
 		svgCanvas.alignSelectedElements('b', $('#align_relative_to option:selected').val() );
 	};
 
+	var clickZoom = function(zoomIn) {
+		var res = svgCanvas.getResolution();
+		var multiplier = zoomIn? res.zoom * 2 : res.zoom * 0.5;
+		setResolution(res.w * multiplier, res.h * multiplier, true);
+		svgCanvas.setZoom(multiplier);
+	};
+
 	var showSourceEditor = function(){
 		if (editingsource) return;
 		editingsource = true;
@@ -628,6 +663,7 @@
 	$('#tool_circle').mouseup(clickCircle);
 	$('#tool_ellipse').mouseup(clickEllipse);
 	$('#tool_fhellipse').mouseup(clickFHEllipse);
+	$('#tool_image').mouseup(clickImage);
 	$('#tool_text').click(clickText);
 	$('#tool_poly').click(clickPoly);
 	$('#tool_clear').click(clickClear);
@@ -644,6 +680,8 @@
 	$('#tool_redo').click(clickRedo);
 	$('#tool_clone').click(clickClone);
 	$('#tool_clone_multi').click(clickClone);
+	$('#tool_group').click(clickGroup);
+	$('#tool_ungroup').click(clickGroup);
 	$('#tool_alignleft').click(clickAlignLeft);
 	$('#tool_aligncenter').click(clickAlignCenter);
 	$('#tool_alignright').click(clickAlignRight);
@@ -692,7 +730,6 @@
 			var button = document.getElementById(shortcutButtons[i]);
 			var title = button.title;
 			var index = title.indexOf("Ctrl+");
-			console.log(index);
 			button.title = [title.substr(0,index), "Cmd+", title.substr(index+5)].join('');
 		}
 	}
@@ -709,6 +746,7 @@
 			['5', clickEllipse],
 			['6', clickText],
 			['7', clickPoly],
+			['8', clickImage],
 			[modKey+'N', function(evt){clickClear();evt.preventDefault();}],
 			[modKey+'S', function(evt){editingsource?saveSourceEditor():clickSave();evt.preventDefault();}],
 			[modKey+'O', function(evt){clickOpen();evt.preventDefault();}],
@@ -720,6 +758,8 @@
 			['shift+right', function(){rotateSelected(1)}],
 			['shift+O', selectPrev],
 			['shift+P', selectNext],
+			['ctrl+up', function(evt){clickZoom(true);evt.preventDefault();}],
+			['ctrl+down', function(evt){clickZoom();evt.preventDefault();}],
 			['up', function(evt){moveSelected(0,-1);evt.preventDefault();}],
 			['down', function(evt){moveSelected(0,1);evt.preventDefault();}],
 			['left', function(evt){moveSelected(-1,0);evt.preventDefault();}],
@@ -728,7 +768,8 @@
 			[modKey+'y', function(evt){clickRedo();evt.preventDefault();}],
 			[modKey+'u', function(evt){showSourceEditor();evt.preventDefault();}],
 			[modKey+'c', function(evt){clickClone();evt.preventDefault();}],
-			['esc', cancelSourceEditor, false],
+			[modKey+'g', function(evt){clickGroup();evt.preventDefault();}],
+			['esc', cancelSourceEditor, false]
 		];
 		
 		$.each(keys,function(i,item) {
@@ -749,11 +790,9 @@
 	var colorPicker = function(elem) {
 		var picker = elem.attr('id') == 'stroke_color' ? 'stroke' : 'fill';
 		var opacity = (picker == 'stroke' ? $('#stroke_opacity') : $('#fill_opacity'));
-
 		var paint = (picker == 'stroke' ? strokePaint : fillPaint);
 		var title = (picker == 'stroke' ? 'Pick a Stroke Paint and Opacity' : 'Pick a Fill Paint and Opacity');
 		var was_none = false;
-		
 		if (paint.type == "none") {
 			// if it was none, then set to solid white
 			paint = new $.jGraduate.Paint({solidColor: 'ffffff'});
@@ -790,7 +829,7 @@
 				else {
 					svgCanvas.setFillPaint(paint, true);
 				}
-				
+				updateToolbar();
 				$('#color_picker').hide();
 			},
 			function(p) {
@@ -897,8 +936,19 @@
 			}
 		});
 		if(!found) $('#resolution').val('Custom');
-		
-		$('#svgcanvas').css( { 'width': x, 'height': y } );
+		var zoom = svgCanvas.getResolution().zoom;
+		setResolution(x * zoom, y * zoom);
+	}
+	
+	function setResolution(w, h, center) {
+		$('#svgcanvas').css( { 'width': w, 'height': h } );
+		if(center) {
+			var w_area = $('#workarea');
+			var scroll_y = h/2 - w_area.height()/2;
+			var scroll_x = w/2 - w_area.width()/2;
+			w_area[0].scrollTop = scroll_y;
+			w_area[0].scrollLeft = scroll_x;
+		}
 	}
 
 	$('#resolution').change(function(){
@@ -925,7 +975,7 @@
 	});
 
 	$('#rect_rx').SpinButton({ min: 0, max: 1000, step: 1, callback: changeRectRadius });
-	$('#stroke_width').SpinButton({ min: 1, max: 99, step: 1, callback: changeStrokeWidth });
+	$('#stroke_width').SpinButton({ min: 0, max: 99, step: 1, callback: changeStrokeWidth });
 	$('#angle').SpinButton({ min: -180, max: 180, step: 5, callback: changeRotationAngle });
 
 	svgCanvas.setCustomHandlers = function(opts) {
--- a/htdocs/svg-edit/editor/svgcanvas.js	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/editor/svgcanvas.js	Fri Sep 11 22:33:42 2009 +0200
@@ -1,15 +1,3 @@
-/*
-TODOs for Rotator:
-
-- resize with negative width/height causes problems with selector (and upon release, kablooie)
-- fix resize when mouseup
-- show the proper resize cursor based on the rotation
-- add a rotator line/handle to the selector group
-- respond to mouse down on the rotator handle to start 'rotate' mode
-- respond to mouse move in rotate mode to change the rotation of the element
-- upon mouse up in rotate mode go back to select mode
-
-*/
 if(!window.console) {
   window.console = new function() {
     this.log = function(str) {};
@@ -18,25 +6,29 @@
 }
 
 // this defines which elements and attributes that we support
-// TODO: add <g> elements to this
 // TODO: add <a> elements to this
-// TODO: add xmlns:xlink attr to <svg> element
+// TODO: add <tspan> to this
 var svgWhiteList = {
-	"circle": ["cx", "cy", "fill", "fill-opacity", "id", "r", "stroke", "stroke-dasharray", "stroke-opacity", "stroke-width", "transform"],
+	"circle": ["cx", "cy", "fill", "fill-opacity", "id", "opacity", "r", "stroke", "stroke-dasharray", "stroke-opacity", "stroke-width", "transform"],
 	"defs": [],
-	"ellipse": ["cx", "cy", "fill", "fill-opacity", "id", "rx", "ry", "stroke", "stroke-dasharray", "stroke-opacity", "stroke-width", "transform"],
-	"line": ["fill", "fill-opacity", "id", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-opacity", "stroke-width",  "transform", "x1", "x2", "y1", "y2"],
+	"ellipse": ["cx", "cy", "fill", "fill-opacity", "id", "opacity", "rx", "ry", "stroke", "stroke-dasharray", "stroke-opacity", "stroke-width", "transform"],
+	"g": ["id", "transform"],
+	"image": ["height", "id", "opacity", "transform", "width", "x", "xlink:href", "xlink:title", "y"],
+	"line": ["fill", "fill-opacity", "id", "opacity", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-opacity", "stroke-width",  "transform", "x1", "x2", "y1", "y2"],
 	"linearGradient": ["id", "gradientTransform", "gradientUnits", "spreadMethod", "x1", "x2", "y1", "y2"],
-	"path": ["d", "fill", "fill-opacity", "id", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform"],
-	"polygon": ["id", "fill", "fill-opacity", "points", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform"],
-	"polyline": ["id", "points", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform"],
+	"path": ["d", "fill", "fill-opacity", "id", "opacity", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform"],
+	"polygon": ["id", "fill", "fill-opacity", "id", "opacity", "points", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform"],
+	"polyline": ["id", "fill", "fill-opacity", "opacity", "points", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform"],
 	"radialGradient": ["id", "cx", "cy", "fx", "fy", "gradientTransform", "gradientUnits", "r", "spreadMethod"],
-	"rect": ["fill", "fill-opacity", "height", "id", "rx", "ry", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform", "width", "x", "y"],
+	"rect": ["fill", "fill-opacity", "height", "id", "opacity", "rx", "ry", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform", "width", "x", "y"],
 	"stop": ["id", "offset", "stop-color", "stop-opacity"],
-	"svg": ["id", "height", "transform", "width", "xmlns"],
-	"text": ["fill", "fill-opacity", "font-family", "font-size", "font-style", "font-weight", "id", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform", "x", "y"],
+	"svg": ["id", "height", "transform", "viewBox", "width", "xmlns", "xmlns:xlink"],
+	"text": ["fill", "fill-opacity", "font-family", "font-size", "font-style", "font-weight", "id", "opacity", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform", "text-anchor", "x", "y"],
 };
 
+function SvgCanvas(c)
+{
+
 // These command objects are used for the Undo/Redo stack
 // attrs contains the values that the attributes had before the change
 function ChangeElementCommand(elem, attrs, text) {
@@ -50,6 +42,7 @@
 	}
 
 	this.apply = function() {
+		var bChangedTransform = false;
 		for( attr in this.newValues ) {
 			if (this.newValues[attr]) {
 				if (attr == "#text") this.elem.textContent = this.newValues[attr];
@@ -59,11 +52,26 @@
 				if (attr != "#text") this.elem.textContent = "";
 				else this.elem.removeAttribute(attr);
 			}
+			if (attr == "transform") { bChangedTransform = true; }
+		}
+		// relocate rotational transform, if necessary
+		if(!bChangedTransform) {
+			var angle = canvas.getRotationAngle(elem);
+			if (angle) {
+				var bbox = elem.getBBox();
+				var cx = parseInt(bbox.x + bbox.width/2),
+					cy = parseInt(bbox.y + bbox.height/2);
+				var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
+				if (rotate != elem.getAttribute("transform")) {
+					elem.setAttribute("transform", rotate);
+				}
+			}
 		}
 		return true;
 	};
 
 	this.unapply = function() {
+		var bChangedTransform = false;
 		for( attr in this.oldValues ) {
 			if (this.oldValues[attr]) {
 				if (attr == "#text") this.elem.textContent = this.oldValues[attr];
@@ -73,6 +81,20 @@
 				if (attr == "#text") this.elem.textContent = "";
 				else this.elem.removeAttribute(attr);
 			}
+			if (attr == "transform") { bChangedTransform = true; }
+		}
+		// relocate rotational transform, if necessary
+		if(!bChangedTransform) {
+			var angle = canvas.getRotationAngle(elem);
+			if (angle) {
+				var bbox = elem.getBBox();
+				var cx = parseInt(bbox.x + bbox.width/2),
+					cy = parseInt(bbox.y + bbox.height/2);
+				var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
+				if (rotate != elem.getAttribute("transform")) {
+					elem.setAttribute("transform", rotate);
+				}
+			}
 		}
 		return true;
 	};
@@ -112,7 +134,7 @@
 
 function MoveElementCommand(elem, oldNextSibling, oldParent, text) {
 	this.elem = elem;
-	this.text = text ? ("Move " + elem.tagName + " to " + text) : ("Move " + elem.tagName + "top/bottom");
+	this.text = text ? ("Move " + elem.tagName + " to " + text) : ("Move " + elem.tagName);
 	this.oldNextSibling = oldNextSibling;
 	this.oldParent = oldParent;
 	this.newNextSibling = elem.nextSibling;
@@ -170,8 +192,6 @@
 	this.isEmpty = function() { return this.stack.length == 0; };
 }
 
-function SvgCanvas(c)
-{
 // private members
 
 	// **************************************************************************************
@@ -284,6 +304,7 @@
 			for (dir in this.selectorGrips) {
 				this.selectorGrips[dir].setAttribute("display", bShow);
 			}
+			if(elem) this.updateGripCursors(canvas.getRotationAngle(elem));
 		};
 		
 		// Updates cursors for corner grips on rotation so arrows point the right way
@@ -321,6 +342,10 @@
 			var bbox = cur_bbox || oldbox;
 			var l=bbox.x-offset, t=bbox.y-offset, w=bbox.width+(offset<<1), h=bbox.height+(offset<<1);
 			var sr_handle = svgroot.suspendRedraw(100);
+			l*=current_zoom;
+			t*=current_zoom;
+			w*=current_zoom;
+			h*=current_zoom;
 			assignAttributes(selectedBox, {
 				'x': l,
 				'y': t,
@@ -356,8 +381,8 @@
 			var transform = elem.getAttribute("transform");
 			var angle = canvas.getRotationAngle(elem);
 			if (angle) {
-				var cx = oldbox.x + oldbox.width/2, //l + w/2, 
-					cy = oldbox.y + oldbox.height/2; //t + h/2;
+				var cx = parseInt(oldbox.x + oldbox.width/2) * current_zoom
+					cy = parseInt(oldbox.y + oldbox.height/2) * current_zoom;
 				this.selectorGroup.setAttribute("transform", "rotate("+angle+" " + cx + "," + cy + ")");
 			}
 			svgroot.unsuspendRedraw(sr_handle);
@@ -383,18 +408,21 @@
 
 		// local reference to this object
 		var mgr = this;
-		// private function
-		var initGroup = function() {
+
+		this.initGroup = function() {
 			mgr.selectorParentGroup = addSvgElementFromJson({
 											"element": "g",
 											"attr": {"id": "selectorParentGroup"}
 										});
+			mgr.selectorMap = {};
+			mgr.selectors = [];
+			mgr.rubberBandBox = null;
+			mgr.update();
 		};
 
 		this.requestSelector = function(elem) {
 			if (elem == null) return null;
 			var N = this.selectors.length;
-
 			// if we've already acquired one for this element, return it
 			if (typeof(this.selectorMap[elem.id]) == "object") {
 				this.selectorMap[elem.id].locked = true;
@@ -462,7 +490,7 @@
 			return this.rubberBandBox;
 		};
 
-		initGroup();
+		this.initGroup();
 	}
 	// **************************************************************************************
 
@@ -508,12 +536,12 @@
 		var shape = svgdoc.getElementById(data.attr.id);
 		// if shape is a path but we need to create a rect/ellipse, then remove the path
 		if (shape && data.element != shape.tagName) {
-			svgroot.removeChild(shape);
+			svgzoom.removeChild(shape);
 			shape = null;
 		}
 		if (!shape) {
 			shape = svgdoc.createElementNS(svgns, data.element);
-			svgroot.appendChild(shape);
+			svgzoom.appendChild(shape);
 		}
 		assignAttributes(shape, data.attr, 100);
 		cleanupElement(shape);
@@ -523,7 +551,7 @@
 	var canvas = this;
 	var container = c;
 	var svgns = "http://www.w3.org/2000/svg";
-
+	var xlinkns = "http://www.w3.org/1999/xlink";
 	var idprefix = "svg_";
 	var svgdoc  = c.ownerDocument;
 	var svgroot = svgdoc.createElementNS(svgns, "svg");
@@ -531,7 +559,16 @@
 	svgroot.setAttribute("height", 480);
 	svgroot.setAttribute("id", "svgroot");
 	svgroot.setAttribute("xmlns", svgns);
+	svgroot.setAttribute("xmlns:xlink", xlinkns);
 	container.appendChild(svgroot);
+	var svgzoom = svgdoc.createElementNS(svgns, "svg");
+	svgzoom.setAttribute('id', 'svgzoom');
+	svgzoom.setAttribute('viewBox', '0 0 640 480');
+	svgzoom.setAttribute("xmlns", svgns);
+	svgzoom.setAttribute("xmlns:xlink", xlinkns);
+	svgroot.appendChild(svgzoom);
+	var comment = svgdoc.createComment(" created with SVG-edit - http://svg-edit.googlecode.com/ ");
+	svgzoom.appendChild(comment);
 
 	var d_attr = null;
 	var started = false;
@@ -540,19 +577,33 @@
 	var start_y = null;
 	var current_mode = "select";
 	var current_resize_mode = "none";
-	var current_fill = "#FF0000";
-	var current_stroke = "#000000";
-	var current_stroke_paint = null;
-	var current_fill_paint = null;
-	var current_stroke_width = 5;
-	var current_stroke_style = "none";
-	var current_opacity = 1;
-	var current_stroke_opacity = 1;
-	var current_fill_opacity = 1;
-	var current_text_fill = "#000000";
-	var current_text_stroke_width = 0;
-	var current_font_size = "12pt";
-	var current_font_family = "serif";
+	
+	var all_properties = {
+		shape: {
+			fill: "#FF0000",
+			fill_paint: null,
+			fill_opacity: 1,
+			stroke: "#000000",
+			stroke_paint: null,
+			stroke_opacity: 1,
+			stroke_width: 5,
+			stroke_style: 'none',
+			opacity: 1
+		}
+	};
+	
+	all_properties.text = $.extend(true, {}, all_properties.shape);
+	$.extend(all_properties.text, {
+		fill: "#000000",
+		stroke_width: 0,
+		font_size: '12pt',
+		font_family: 'serif'
+	});
+
+	var cur_shape = all_properties.shape;
+	var cur_text = all_properties.text;
+	var cur_properties = cur_shape;
+	
 	var freehand_min_x = null;
 	var freehand_max_x = null;
 	var freehand_min_y = null;
@@ -560,11 +611,14 @@
 	var current_poly = null;
 	var current_poly_pts = [];
 	var current_poly_pt_drag = -1;
+	var current_poly_oldd = null;
+	var current_zoom = 1;
 	// this will hold all the currently selected elements
 	// default size of 1 until it needs to grow bigger
 	var selectedElements = new Array(1); 
 	// this holds the selected's bbox
 	var selectedBBoxes = new Array(1);
+	var justSelected = null;
 	// this object manages selectors for us
 	var selectorManager = new SelectorManager();
 	var rubberBox = null;
@@ -588,22 +642,7 @@
 
 		if(!curBBoxes.length) {
 			// Cache all bboxes
-
-			var nodes = svgroot.childNodes;
-			var i = svgroot.childNodes.length;
-			
-			while (i--) {
-				// need to do this since the defs has no bbox and causes an exception
-				// to be thrown in Mozilla
-				try {
-					var box = canvas.getBBox(nodes[i]);
-					if (nodes[i].id != "selectorParentGroup" && box) {
-						curBBoxes.push({'elem':nodes[i], 'bbox':box});
-					}
-				} catch(e) {
-					// do nothing, this element did not have a bbox
-				}
-			}
+			curBBoxes = canvas.getVisibleElements(true);
 		}
 		
 		var resultList = null;
@@ -615,6 +654,9 @@
 			resultList = [];
 
 			var rubberBBox = rubberBox.getBBox();
+			$.each(rubberBBox, function(key, val) {
+				rubberBBox[key] = val / current_zoom;
+			});
 			var i = curBBoxes.length;
 			while (i--) {
 				if (Utils.rectsIntersect(rubberBBox, curBBoxes[i].bbox))  {
@@ -717,10 +759,10 @@
 	};
 
 	var removeUnusedGrads = function() {
-		var defs = svgroot.getElementsByTagNameNS(svgns, "defs");
+		var defs = svgzoom.getElementsByTagNameNS(svgns, "defs");
 		if(!defs || !defs.length) return;
 		
-		var all_els = svgroot.getElementsByTagNameNS(svgns, '*');
+		var all_els = svgzoom.getElementsByTagNameNS(svgns, '*');
 		var grad_uses = [];
 		
 		$.each(all_els, function(i, el) {
@@ -737,9 +779,8 @@
 			} 
 		});
 		
-		var lgrads = svgroot.getElementsByTagNameNS(svgns, "linearGradient");
+		var lgrads = svgzoom.getElementsByTagNameNS(svgns, "linearGradient");
 		var grad_ids = [];
-
 		var i = lgrads.length;
 		while (i--) {
 			var grad = lgrads[i];
@@ -760,6 +801,15 @@
 			}
 		}
 	}
+	
+	var svgCanvasToString = function() {
+		// remove unused gradients
+		removeUnusedGrads();
+		
+		var output = svgToString(svgzoom, 0);
+		
+		return output;
+	}
 
 	var svgToString = function(elem, indent) {
 		var out = new Array();
@@ -773,7 +823,20 @@
 			for (i=attrs.length-1; i>=0; i--) {
 				attr = attrs.item(i);
 				if (attr.nodeValue != "") {
-					out.push(" "); out.push(attr.nodeName); out.push("=\""); 
+					//Opera bug turns N.N to N,N in some locales
+					if (window.opera && attr.nodeName == 'opacity' && /^\d+,\d+$/.test(attr.nodeValue)) {
+						attr.nodeValue = attr.nodeValue.replace(',','.');
+					}
+					out.push(" "); 
+					// map various namespaces to our fixed namespace prefixes
+					// TODO: put this into a map and do a look-up instead of if-else
+					if (attr.namespaceURI == 'http://www.w3.org/1999/xlink') {
+						out.push('xlink:');
+					}
+					else if(attr.namespaceURI == 'http://www.w3.org/2000/xmlns/' && attr.localName != 'xmlns') {
+						out.push('xmlns:');
+					}
+					out.push(attr.localName); out.push("=\""); 
 					out.push(attr.nodeValue); out.push("\"");
 				}
 			}
@@ -825,7 +888,7 @@
 
 		var i = selectedElements.length;
 		while(i--) {
-			var cmd = recalculateSelectedDimensions(i);
+			var cmd = recalculateDimensions(selectedElements[i],selectedBBoxes[i]);
 			if (cmd) {
 				batchCmd.addSubCommand(cmd);
 			}
@@ -843,15 +906,15 @@
 					's', 's', 't', 't' ];
 
 	// this function returns the command which resulted from the selected change
-	var recalculateSelectedDimensions = function(i) {
-		var selected = selectedElements[i];
-		if (selected == null) return null;
-		var selectedBBox = selectedBBoxes[i];
+	var recalculateDimensions = function(selected,selectedBBox) {
+		if (selected == null || selectedBBox == null) return null;
 		var box = canvas.getBBox(selected);
 
 		// if we have not moved/resized, then immediately leave
-		if (box.x == selectedBBox.x && box.y == selectedBBox.y &&
-			box.width == selectedBBox.width && box.height == selectedBBox.height) {
+		var xform = selected.getAttribute("transform");
+		if ( (!xform || xform == "") && box.x == selectedBBox.x && box.y == selectedBBox.y &&
+				box.width == selectedBBox.width && box.height == selectedBBox.height) 
+		{
 			return null;
 		}
 
@@ -866,17 +929,15 @@
 		var scalew = function(w) {return parseInt(w*selectedBBox.width/box.width);}
 		var scaleh = function(h) {return parseInt(h*selectedBBox.height/box.height);}
 
-		var changes = {};
-
+		var batchCmd = new BatchCommand("Transform");
+		
 		// if there was a rotation transform, re-set it, otherwise empty out the transform attribute
-		// This fixes Firefox 2- behavior - which does not reset values when the attribute has
-		// been removed, see https://bugzilla.mozilla.org/show_bug.cgi?id=320622
 		var angle = canvas.getRotationAngle(selected);
 		var pointGripContainer = document.getElementById("polypointgrip_container");
 		if (angle) {
 			// this is our old center upon which we have rotated the shape
-			var tr_x = box.x + box.width/2, 
-				tr_y = box.y + box.height/2;
+			var tr_x = parseInt(box.x + box.width/2),
+				tr_y = parseInt(box.y + box.height/2);
 			var cx = null, cy = null;
 			
 			var bFoundScale = false;
@@ -927,11 +988,18 @@
 			
 			var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
 			selected.setAttribute("transform", rotate);
+			// if we were rotated, store just the old rotation (not other transforms) on the
+			// undo stack
+			var changes = {};
+			changes["transform"] = ["rotate(", angle, " ", tr_x, ",", tr_y, ")"].join('');
+			batchCmd.addSubCommand(new ChangeElementCommand(selected, changes));
 			if(pointGripContainer) {
 				pointGripContainer.setAttribute("transform", rotate);
 			}
 		}
 		else {
+			// This fixes Firefox 2- behavior - which does not reset values when the attribute has
+			// been removed, see https://bugzilla.mozilla.org/show_bug.cgi?id=320622
 			selected.setAttribute("transform", "");
 			selected.removeAttribute("transform");
 			if(pointGripContainer) {
@@ -939,9 +1007,54 @@
 				pointGripContainer.removeAttribute("transform");
 			}
 		}
+		
+		// if it's a group, transfer the transform attribute to each child element
+		// and recursively call recalculateDimensions()
+		if (selected.tagName == "g") {
+			var children = selected.childNodes;
+			var i = children.length;
+			while (i--) {
+				var child = children.item(i);
+				if (child.nodeType == 1) {
+					var childBox = child.getBBox();
+					if (childBox) {
+						// TODO: to fix the rotation problem, we must account for the
+						// child's rotation in the bbox adjustment
+						
+						// If the child is rotated at all, we should figure out the rotated
+						// bbox before the group's transform, remap all four corners of the bbox
+						// via the group's transform, then determine the new angle and the new center
+						/*
+						var childAngle = canvas.getRotationAngle(child) * Math.PI / 180.0;
+						var left = childBox.x - gcx, 
+							top = childBox.y - gcy,
+							right = childBox.x + childBox.width - gcx,
+							bottom = childBox.y + childBox.height - gcy;
+						
+						var ptTopLeft = remap(left,top),
+							ptTopRight = remap(right,top),
+							ptBottomLeft = remap(left,bottom),
+							ptBottomRight = remap(right,bottom);
+						*/
+						var pt = remap(childBox.x,childBox.y),
+							w = scalew(childBox.width),
+							h = scaleh(childBox.height);
+						childBox.x = pt.x; childBox.y = pt.y;
+						childBox.width = w; childBox.height = h;
+						batchCmd.addSubCommand(recalculateDimensions(child, childBox));
+					}
+				}
+			}
+			return batchCmd;
+		}	
+
+		var changes = {};
 
 		switch (selected.tagName)
 		{
+		case "g":
+			// do work here :P
+			break;
 		// NOTE: at the moment, there's no way to create an actual polygon element except by 
 		// editing source or importing from somewhere else but we'll cover it here anyway
 		// polygon is handled just like polyline
@@ -1067,7 +1180,7 @@
 				'cy': pt.y,
 	
 				// take the minimum of the new selected box's dimensions for the new circle radius
-				'r': Math.min(selectedBBox.width/2,selectedBBox.height/2)
+				'r': parseInt(Math.min(selectedBBox.width/2,selectedBBox.height/2))
 			}, 1000);
 			break;
 		case "ellipse":
@@ -1092,6 +1205,20 @@
 				'y': pt.y
 			}, 1000);
 			break;
+    
+		case "image":
+			changes["x"] = selected.getAttribute("x");
+			changes["y"] = selected.getAttribute("y");
+			changes["width"] = selected.getAttribute("width");
+			changes["height"] = selected.getAttribute("height");
+			var pt = remap(changes["x"], changes["y"]);
+			assignAttributes(selected, {
+				'x': pt.x,
+				'y': pt.y,
+				'width': scalew(changes["width"]),
+				'height': scaleh(changes["height"])
+			}, 1000);
+			break;
 		case "rect":
 			changes["x"] = selected.getAttribute("x");
 			changes["y"] = selected.getAttribute("y");
@@ -1110,8 +1237,9 @@
 			break;
 		}
 		if (changes) {
-			return new ChangeElementCommand(selected, changes);
+			batchCmd.addSubCommand(new ChangeElementCommand(selected, changes));
 		}
+		return batchCmd;
 	};
 
 // public events
@@ -1129,9 +1257,9 @@
 		call("selected", selectedElements);
 	};
 
+	// TODO: do we need to worry about selectedBBoxes here?
 	this.addToSelection = function(elemsToAdd, showGrips) {
 		if (elemsToAdd.length == 0) { return; }
-
 		// find the first null in our selectedElements array
 		var j = 0;
 		while (j < selectedElements.length) {
@@ -1140,7 +1268,7 @@
 			}
 			++j;
 		}
-
+		
 		// now add each element consecutively
 		var i = elemsToAdd.length;
 		while (i--) {
@@ -1151,23 +1279,31 @@
 			if (selectedElements.indexOf(elem) == -1) {
 				selectedElements[j] = elem;
 				selectedBBoxes[j++] = this.getBBox(elem);
-				selectorManager.requestSelector(elem);
+				var sel = selectorManager.requestSelector(elem);
+				if (selectedElements.length > 1) {
+					sel.showGrips(false);
+				}
 				call("selected", selectedElements);
 			}
 		}
-		
+
 		if(showGrips) {
 			selectorManager.requestSelector(selectedElements[0]).showGrips(true);
 		}
+		else if (selectedElements.length > 1) {
+			selectorManager.requestSelector(selectedElements[0]).showGrips(false);
+		}
 	};
 
-	// 
+	// updates the canvas arrays selectedElements and selectedBBoxes
+	// TODO: could use slice here to make this faster?
 	this.removeFromSelection = function(elemsToRemove) {
 		if (selectedElements[0] == null) { return; }
 		if (elemsToRemove.length == 0) { return; }
 
 		// find every element and remove it from our array copy
 		var newSelectedItems = new Array(selectedElements.length);
+		var newSelectedBBoxes = new Array(selectedBBoxes.length);
 		var j = 0;
 		var len = selectedElements.length;
 		for (var i = 0; i < len; ++i) {
@@ -1175,6 +1311,7 @@
 			if (elem) {
 				// keep the item
 				if (elemsToRemove.indexOf(elem) == -1) {
+					newSelectedBBoxes[j] = selectedBBoxes[i];
 					newSelectedItems[j++] = elem;
 				}
 				else { // remove the item and its selector
@@ -1184,6 +1321,7 @@
 		}
 		// the copy becomes the master now
 		selectedElements = newSelectedItems;
+		selectedBBoxes = newSelectedBBoxes;
 	};
 
 	// in mouseDown :
@@ -1193,12 +1331,18 @@
 	//   and do nothing else
 	var mouseDown = function(evt)
 	{
-		var x = evt.pageX - container.parentNode.offsetLeft + container.parentNode.scrollLeft;
-		var y = evt.pageY - container.parentNode.offsetTop + container.parentNode.scrollTop;
+		var mouse_x = evt.pageX - container.parentNode.offsetLeft + container.parentNode.scrollLeft;
+		var mouse_y = evt.pageY - container.parentNode.offsetTop + container.parentNode.scrollTop;
 		
+		evt.preventDefault();
+    
 		if($.inArray(current_mode, ['select', 'resize']) == -1) {
 			addGradient();
 		}
+		
+		x = mouse_x / current_zoom;
+		y = mouse_y / current_zoom;
+		
 		start_x = x;
 		start_y = y;
 		
@@ -1207,13 +1351,25 @@
 				started = true;
 				current_resize_mode = "none";
 				var t = evt.target;
+				// if this element is in a group, go up until we reach the top-level group
+				// TODO: once we implement Layers, the top-level groups will be layers so
+				// we will want to stop just before then (parentNode.parentNode)
+				// TODO: once we implement links, we also would have to check for <a> elements
+				while (t.parentNode.tagName == "g") {
+					t = t.parentNode;
+				}
 				// WebKit returns <div> when the canvas is clicked, Firefox/Opera return <svg>
 				var nodeName = t.nodeName.toLowerCase();
 				if (nodeName != "div" && nodeName != "svg") {
 					// if this element is not yet selected, clear selection and select it
 					if (selectedElements.indexOf(t) == -1) {
-						canvas.clearSelection();
+						// only clear selection if shift is not pressed (otherwise, add 
+						// element to selection)
+						if (!evt.shiftKey) {
+							canvas.clearSelection();
+						}
 						canvas.addToSelection([t]);
+						justSelected = t;
 						current_poly = null;
 					}
 					// else if it's a poly, go into polyedit mode in mouseup
@@ -1224,6 +1380,8 @@
 					if (rubberBox == null) {
 						rubberBox = selectorManager.getRubberBandBox();
 					}
+					start_x *= current_zoom;
+					start_y *= current_zoom;
 					assignAttributes(rubberBox, {
 						'x': start_x,
 						'y': start_y,
@@ -1245,19 +1403,20 @@
 				start_x = x;
 				start_y = y;
 				d_attr = x + "," + y + " ";
+				var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width;
 				addSvgElementFromJson({
 					"element": "polyline",
 					"attr": {
 						"points": d_attr,
 						"id": getNextId(),
 						"fill": "none",
-						"stroke": current_stroke,
-						"stroke-width": current_stroke_width,
-						"stroke-dasharray": current_stroke_style,
-						"stroke-opacity": current_stroke_opacity,
+						"stroke": cur_shape.stroke,
+						"stroke-width": stroke_w,
+						"stroke-dasharray": cur_shape.stroke_style,
+						"stroke-opacity": cur_shape.stroke_opacity,
 						"stroke-linecap": "round",
 						"stroke-linejoin": "round",
-						"opacity": current_opacity / 2
+						"opacity": cur_shape.opacity / 2
 					}
 				});
 				freehand_min_x = x;
@@ -1265,6 +1424,23 @@
 				freehand_min_y = y;
 				freehand_max_y = y;
 				break;
+			case "image":
+				started = true;
+				start_x = x;
+				start_y = y;
+				var newImage = addSvgElementFromJson({
+					"element": "image",
+					"attr": {
+						"x": x,
+						"y": y,
+						"width": 0,
+						"height": 0,
+						"id": getNextId(),
+						"opacity": cur_shape.opacity / 2
+					}
+				});
+        		newImage.setAttributeNS(xlinkns, "href", "images/logo.png");
+				break;
 			case "square":
 				// FIXME: once we create the rect, we lose information that this was a square
 				// (for resizing purposes this could be important)
@@ -1280,18 +1456,19 @@
 						"width": 0,
 						"height": 0,
 						"id": getNextId(),
-						"fill": current_fill,
-						"stroke": current_stroke,
-						"stroke-width": current_stroke_width,
-						"stroke-dasharray": current_stroke_style,
-						"stroke-opacity": current_stroke_opacity,
-						"fill-opacity": current_fill_opacity,
-						"opacity": current_opacity / 2
+						"fill": cur_shape.fill,
+						"stroke": cur_shape.stroke,
+						"stroke-width": cur_shape.stroke_width,
+						"stroke-dasharray": cur_shape.stroke_style,
+						"stroke-opacity": cur_shape.stroke_opacity,
+						"fill-opacity": cur_shape.fill_opacity,
+						"opacity": cur_shape.opacity / 2
 					}
 				});
 				break;
 			case "line":
 				started = true;
+				var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width;
 				addSvgElementFromJson({
 					"element": "line",
 					"attr": {
@@ -1300,12 +1477,12 @@
 						"x2": x,
 						"y2": y,
 						"id": getNextId(),
-						"stroke": current_stroke,
-						"stroke-width": current_stroke_width,
-						"stroke-dasharray": current_stroke_style,
-						"stroke-opacity": current_stroke_opacity,
+						"stroke": cur_shape.stroke,
+						"stroke-width": stroke_w,
+						"stroke-dasharray": cur_shape.stroke_style,
+						"stroke-opacity": cur_shape.stroke_opacity,
 						"fill": "none",
-						"opacity": current_opacity / 2
+						"opacity": cur_shape.opacity / 2
 					}
 				});
 				break;
@@ -1318,13 +1495,13 @@
 						"cy": y,
 						"r": 0,
 						"id": getNextId(),
-						"fill": current_fill,
-						"stroke": current_stroke,
-						"stroke-width": current_stroke_width,
-						"stroke-dasharray": current_stroke_style,
-						"stroke-opacity": current_stroke_opacity,
-						"fill-opacity": current_fill_opacity,
-						"opacity": current_opacity / 2
+						"fill": cur_shape.fill,
+						"stroke": cur_shape.stroke,
+						"stroke-width": cur_shape.stroke_width,
+						"stroke-dasharray": cur_shape.stroke_style,
+						"stroke-opacity": cur_shape.stroke_opacity,
+						"fill-opacity": cur_shape.fill_opacity,
+						"opacity": cur_shape.opacity / 2
 					}
 				});
 				break;
@@ -1338,13 +1515,13 @@
 						"rx": 0,
 						"ry": 0,
 						"id": getNextId(),
-						"fill": current_fill,
-						"stroke": current_stroke,
-						"stroke-width": current_stroke_width,
-						"stroke-dasharray": current_stroke_style,
-						"stroke-opacity": current_stroke_opacity,
-						"fill-opacity": current_fill_opacity,
-						"opacity": current_opacity / 2
+						"fill": cur_shape.fill,
+						"stroke": cur_shape.stroke,
+						"stroke-width": cur_shape.stroke_width,
+						"stroke-dasharray": cur_shape.stroke_style,
+						"stroke-opacity": cur_shape.stroke_opacity,
+						"fill-opacity": cur_shape.fill_opacity,
+						"opacity": cur_shape.opacity / 2
 					}
 				});
 				break;
@@ -1356,16 +1533,17 @@
 						"x": x,
 						"y": y,
 						"id": getNextId(),
-						"fill": current_text_fill,
-						"stroke": current_stroke,
-						"stroke-width": current_text_stroke_width,
-						"stroke-dasharray": current_stroke_style,
-						"stroke-opacity": current_stroke_opacity,
-						"fill-opacity": current_fill_opacity,
+						"fill": cur_text.fill,
+						"stroke": cur_shape.stroke,
+						"stroke-width": cur_text.stroke_width,
+						"stroke-dasharray": cur_shape.stroke_style,
+						"stroke-opacity": cur_shape.stroke_opacity,
+						"fill-opacity": cur_shape.fill_opacity,
 						// fix for bug where text elements were always 50% opacity
-						"opacity": current_opacity,
-						"font-size": current_font_size,
-						"font-family": current_font_family
+						"opacity": cur_shape.opacity,
+						"font-size": cur_text.font_size,
+						"font-family": cur_text.font_family,
+						"text-anchor": "middle"
 					}
 				});
 				newText.textContent = "text";
@@ -1375,6 +1553,7 @@
 				break;
 			case "polyedit":
 				started = true;
+				current_poly_oldd = current_poly.getAttribute("d");
 				var id = evt.target.id;
 				if (id.substr(0,14) == "polypointgrip_") {
 					current_poly_pt_drag = parseInt(id.substr(14));
@@ -1398,6 +1577,8 @@
 				break;
 			case "rotate":
 				started = true;
+				// we are starting an undoable change (a drag-rotation)
+				canvas.beginUndoableChange("transform", selectedElements);
 				break;
 			default:
 				console.log("Unknown mode in mousedown: " + current_mode);
@@ -1414,49 +1595,56 @@
 	{
 		if (!started) return;
 		var selected = selectedElements[0];
-		var x = evt.pageX - container.parentNode.offsetLeft + container.parentNode.scrollLeft;
-		var y = evt.pageY - container.parentNode.offsetTop + container.parentNode.scrollTop;
+		var mouse_x = evt.pageX - container.parentNode.offsetLeft + container.parentNode.scrollLeft;
+		var mouse_y = evt.pageY - container.parentNode.offsetTop + container.parentNode.scrollTop;
 		var shape = svgdoc.getElementById(getId());
+    
+    	x = mouse_x / current_zoom;
+    	y = mouse_y / current_zoom;
+    
+    	evt.preventDefault();
+    
 		switch (current_mode)
 		{
 			case "select":
-				// we temporarily use a translate on the element being dragged
+				// we temporarily use a translate on the element(s) being dragged
 				// this transform is removed upon mousing up and the element is 
 				// relocated to the new location
 				if (selectedElements[0] != null) {
 					var dx = x - start_x;
 					var dy = y - start_y;
-					
 					if (dx != 0 || dy != 0) {
 						var ts = ["translate(",dx,",",dy,")"].join('');
 						var len = selectedElements.length;
 						for (var i = 0; i < len; ++i) {
 							var selected = selectedElements[i];
 							if (selected == null) break;
-
 							var box = canvas.getBBox(selected);
 							selectedBBoxes[i].x = box.x + dx;
 							selectedBBoxes[i].y = box.y + dy;
 							var angle = canvas.getRotationAngle(selected);
 							if (angle) {
-								var cx = box.x + box.width/2,
-									cy = box.y + box.height/2;
-								ts += [" rotate(", angle, " ", cx, ",", cy, ")"].join('');
-
+								var cx = parseInt(box.x + box.width/2),
+									cy = parseInt(box.y + box.height/2);
+								var xform = ts + [" rotate(", angle, " ", cx, ",", cy, ")"].join('');
  								var r = Math.sqrt( dx*dx + dy*dy );
 								var theta = Math.atan2(dy,dx) - angle * Math.PI / 180.0;
-								dx = r * Math.cos(theta);
-								dy = r * Math.sin(theta);
+								selected.setAttribute("transform", xform);
+								box.x += r * Math.cos(theta); box.y += r * Math.sin(theta);
 							}
-							selected.setAttribute("transform", ts);
+							else {
+								selected.setAttribute("transform", ts);
+								box.x += dx; box.y += dy;
+							}
 							// update our internal bbox that we're tracking while dragging
-							box.x += dx; box.y += dy;
 							selectorManager.requestSelector(selected).resize(box);
 						}
 					}
 				}
 				break;
 			case "multiselect":
+				x *= current_zoom;
+				y *= current_zoom;
 				assignAttributes(rubberBox, {
 					'x': Math.min(start_x,x),
 					'y': Math.min(start_y,y),
@@ -1525,44 +1713,69 @@
 				var ts = null;
 				var tx = 0, ty = 0;
 				var sy = (height+dy)/height, sx = (width+dx)/width;
+				// if we are dragging on the north side, then adjust the scale factor and ty
 				if(current_resize_mode.indexOf("n") != -1) {
 					sy = (height-dy)/height;
 					ty = height;
 				}
+				
+				// if we dragging on the east side, then adjust the scale factor and tx
 				if(current_resize_mode.indexOf("w") != -1) {
 					sx = (width-dx)/width;
 					tx = width;
 				}
 				
-				var selectedBBox = selectedBBoxes[0];				
-
 				// find the rotation transform and prepend it
 				var ts = [" translate(", (left+tx), ",", (top+ty), ") scale(", sx, ",", sy,
 							") translate(", -(left+tx), ",", -(top+ty), ")"].join('');
 				if (angle) {
-					var cx = left+width/2,
-						cy = top+height/2;
+					var cx = parseInt(left+width/2),
+						cy = parseInt(top+height/2);
 					ts = ["rotate(", angle, " ", cx, ",", cy, ")", ts].join('')
 				}
 				selected.setAttribute("transform", ts);
+
+				var selectedBBox = selectedBBoxes[0];				
+
+				// reset selected bbox top-left position
+				selectedBBox.x = left;
+				selectedBBox.y = top;
 				
+				// if this is a translate, adjust the box position
 				if (tx) {
-					selectedBBox.x = left+dx;
+					selectedBBox.x += dx;
 				}
 				if (ty) {
-					selectedBBox.y = top+dy;
+					selectedBBox.y += dy;
 				}
+				
+				// update box width/height
 				selectedBBox.width = parseInt(width*sx);
 				selectedBBox.height = parseInt(height*sy);
+
 				// normalize selectedBBox
 				if (selectedBBox.width < 0) {
-					selectedBBox.x += selectedBBox.width;
-					selectedBBox.width *= -1;//-selectedBBox.width;
+					selectedBBox.width *= -1;
+					// if we are dragging on the east side and scaled negatively
+					if(current_resize_mode.indexOf("e") != -1 && sx < 0) {
+						selectedBBox.x = box.x - selectedBBox.width;
+					}
+					else {
+						selectedBBox.x -= selectedBBox.width;
+					}
 				}
 				if (selectedBBox.height < 0) {
-					selectedBBox.y += selectedBBox.height;
-					selectedBBox.height *= -1;//-selectedBBox.height;
+					selectedBBox.height *= -1;
+					// if we are dragging on the south side and scaled negatively
+					if(current_resize_mode.indexOf("s") != -1 && sy < 0) {
+						selectedBBox.y = box.y - selectedBBox.height;
+					}
+					else {
+						selectedBBox.y -= selectedBBox.height;
+					}
 				}
+				
+				
 				selectorManager.requestSelector(selected).resize(selectedBBox);
 				break;
 			case "text":
@@ -1594,6 +1807,14 @@
 					'y': Math.min(start_y,y)
 				},1000);
 				break;
+			case "image":
+				assignAttributes(shape,{
+					'width': Math.abs(x-start_x),
+					'height': Math.abs(y-start_y),
+					'x': Math.min(start_x,x),
+					'y': Math.min(start_y,y)
+				},1000);
+				break;
 			case "circle":
 				var cx = shape.getAttributeNS(null, "cx");
 				var cy = shape.getAttributeNS(null, "cy");
@@ -1625,8 +1846,8 @@
 			case "poly":
 				var line = document.getElementById("poly_stretch_line");
 				if (line) {
-					line.setAttribute("x2", x);
-					line.setAttribute("y2", y);
+					line.setAttribute("x2", x *= current_zoom);
+					line.setAttribute("y2", y *= current_zoom);
 				}
 				break;
 			case "polyedit":
@@ -1636,20 +1857,13 @@
 					
 					// if the image is rotated, then we must modify the x,y mouse coordinates
 					// and rotate them into the shape's rotated coordinate system
-					
-					// FIXME: the problem is that the element's rotation is controlled by 
-					// two things: an angle and a rotation point (the center of the element).
-					// If the element's bbox is changed, its center changes.  In this case,
-					// we keep the rotation center where it is (parse it out from the transform
-					// attribute), and move the poly point appropriately.  This looks good while
-					// dragging, but looks funny when you subsequently rotate the element again.
 					var angle = canvas.getRotationAngle(current_poly) * Math.PI / 180.0;
 					if (angle) {
-						// extract the shape's (potentially) old 'center' from the transform attribute
+						// calculate the shape's old center that was used for rotation
 						var box = selectedBBoxes[0];
-						var cx = box.x + box.width/2, 
-							cy = box.y + box.height/2;
-						var dx = x - cx, dy = y - cy;
+						var cx = parseInt(box.x + box.width/2), 
+							cy = parseInt(box.y + box.height/2);
+						var dx = mouse_x - cx, dy = mouse_y - cy;
  						var r = Math.sqrt( dx*dx + dy*dy );
 						var theta = Math.atan2(dy,dx) - angle;						
 						x = cx + r * Math.cos(theta);
@@ -1668,7 +1882,7 @@
 						cury = current_poly_pts[1];
 					arr[0] = ["M", curx, ",", cury].join('');
 					for (var j = 1; j < len; ++j) {
-						var px = current_poly_pts[j*2], py = current_poly_pts[j*2+1];
+						var px = current_poly_pts[j*2]/current_zoom, py = current_poly_pts[j*2+1]/current_zoom;
 						arr[j] = ["l", parseInt(px-curx), ",", parseInt(py-cury)].join('');
 						curx = px;
 						cury = py;
@@ -1676,29 +1890,27 @@
 					if (closedPath) {
 						arr[len] = "z";
 					}
+					// we don't want to undo this, we are in the middle of a drag
 					current_poly.setAttribute("d", arr.join(' '));
 
 					// move the point grip
 					var grip = document.getElementById("polypointgrip_" + current_poly_pt_drag);
 					if (grip) {
-						grip.setAttribute("cx", x);
-						grip.setAttribute("cy", y);
+						grip.setAttribute("cx", mouse_x);
+						grip.setAttribute("cy", mouse_y);
 					}
 				}
 				break;
 			case "rotate":
-				var box = canvas.getBBox(selected),cx = box.x + box.width/2, cy = box.y + box.height/2;
-				canvas.setRotationAngle(parseInt(((Math.atan2(cy-y,cx-x)  * (180/Math.PI))-90) % 360));
+				var box = canvas.getBBox(selected),
+					cx = parseInt(box.x + box.width/2), 
+					cy = parseInt(box.y + box.height/2);
+				var angle = parseInt(((Math.atan2(cy-y,cx-x)  * (180/Math.PI))-90) % 360);
+				canvas.setRotationAngle(angle<-180?(360+angle):angle, true);
 				break;
 			default:
 				break;
 		}
-		// TODO: should we fire the change event here?  I'm thinking only fire
-		// this event when the user mouses up.  That's when the action (create,
-		// move, resize, draw) has finished
-		// Only question is whether in Wave Gadget mode whether we want to see the 
-		// person live-dragging the element around (for instance)
-//		call("changed", selected);
 	};
 
 	var removeAllPointGripsFromPoly = function() {
@@ -1707,24 +1919,31 @@
 		while(i--) {
 			document.getElementById("polypointgrip_"+i).setAttribute("display", "none");
 		}
-		document.getElementById("poly_stretch_line").setAttribute("display", "none");
+		var line = document.getElementById("poly_stretch_line");
+		if (line) line.setAttribute("display", "none");
 	};
 
 	var addAllPointGripsToPoly = function() {
 		// loop through and hide all pointgrips
-		var i = current_poly_pts.length;
-		while(i) {
-			i -= 2;
+		var len = current_poly_pts.length;
+		for (var i = 0; i < len; i += 2) {
 			var grip = document.getElementById("polypointgrip_"+i/2);
-			assignAttributes(grip, {
-				'cx': current_poly_pts[i],
-				'cy': current_poly_pts[i+1],
-				'display': 'inline'
-			});
+			if (grip) {
+				assignAttributes(grip, {
+					'cx': current_poly_pts[i],
+					'cy': current_poly_pts[i+1],
+					'display': 'inline'
+				});
+			}
+			else {
+				addPointGripToPoly(current_poly_pts[i], current_poly_pts[i+1],i/2);
+			}
 		}
+		var pointGripContainer = document.getElementById("polypointgrip_container");
+		pointGripContainer.setAttribute("transform", current_poly.getAttribute("transform"));
 	};
 
-	var addPointGripToPoly = function(x,y) {
+	var addPointGripToPoly = function(x,y,index) {
 		// create the container of all the point grips
 		var pointGripContainer = document.getElementById("polypointgrip_container");
 		if (!pointGripContainer) {
@@ -1733,9 +1952,6 @@
 			pointGripContainer.id = "polypointgrip_container";
 		}
 
-		// get index of this point
-		var index = current_poly_pts.length/2 - 1;
-
 		var pointGrip = document.getElementById("polypointgrip_"+index);
 		// create it
 		if (!pointGrip) {
@@ -1772,11 +1988,15 @@
 	//   this is done in when we recalculate the selected dimensions()
 	var mouseUp = function(evt)
 	{
+		var tempJustSelected = justSelected;
+		justSelected = null;
 		if (!started) return;
 
-		var x = evt.pageX - container.parentNode.offsetLeft + container.parentNode.scrollLeft;
-		var y = evt.pageY - container.parentNode.offsetTop + container.parentNode.scrollTop;
-
+		var mouse_x = evt.pageX - container.parentNode.offsetLeft + container.parentNode.scrollLeft;
+		var mouse_y = evt.pageY - container.parentNode.offsetTop + container.parentNode.scrollTop;
+		var x = mouse_x / current_zoom;
+		var y = mouse_y / current_zoom;
+		
 		started = false;
 		var element = svgdoc.getElementById(getId());
 		var keep = false;
@@ -1796,15 +2016,15 @@
 					if (selectedElements[1] == null) {
 						// set our current stroke/fill properties to the element's
 						var selected = selectedElements[0];
-						current_fill = selected.getAttribute("fill");
-						current_fill_opacity = selected.getAttribute("fill-opacity");
-						current_stroke = selected.getAttribute("stroke");
-						current_stroke_opacity = selected.getAttribute("stroke-opacity");
-						current_stroke_width = selected.getAttribute("stroke-width");
-						current_stroke_style = selected.getAttribute("stroke-dasharray");
+						cur_shape.fill = selected.getAttribute("fill");
+						cur_shape.fill_opacity = selected.getAttribute("fill-opacity");
+						cur_shape.stroke = selected.getAttribute("stroke");
+						cur_shape.stroke_opacity = selected.getAttribute("stroke-opacity");
+						cur_shape.stroke_width = selected.getAttribute("stroke-width");
+						cur_shape.stroke_style = selected.getAttribute("stroke-dasharray");
 						if (selected.tagName == "text") {
-							current_font_size = selected.getAttribute("font-size");
-							current_font_family = selected.getAttribute("font-family");
+							cur_text.font_size = selected.getAttribute("font-size");
+							cur_text.font_family = selected.getAttribute("font-family");
 						}
 
 						selectorManager.requestSelector(selected).showGrips(true);
@@ -1820,9 +2040,9 @@
 					}
 					// no change in position/size, so maybe we should move to polyedit
 					else {
+						var t = evt.target;
 						// TODO: this causes a poly that was just going to be selected to go straight to polyedit
 						if (selectedElements[0].nodeName == "path" && selectedElements[1] == null) {
-							var t = evt.target;
 							if (current_poly == t) {
 								current_mode = "polyedit";
 
@@ -1830,8 +2050,8 @@
 								current_poly_pts = [];
 								var segList = t.pathSegList;
 								var curx = segList.getItem(0).x, cury = segList.getItem(0).y;
-								current_poly_pts.push(curx);
-								current_poly_pts.push(cury);
+								current_poly_pts.push(curx * current_zoom);
+								current_poly_pts.push(cury * current_zoom);
 								var len = segList.numberOfItems;
 								for (var i = 1; i < len; ++i) {
 									var l = segList.getItem(i);
@@ -1850,8 +2070,8 @@
 										curx += x;
 										cury += y;
 									} // type 5 (rel line)
-									current_poly_pts.push(curx);
-									current_poly_pts.push(cury);
+									current_poly_pts.push(curx * current_zoom);
+									current_poly_pts.push(cury * current_zoom);
 								} // for each segment
 								canvas.clearSelection();
 								// save the poly's bbox
@@ -1861,8 +2081,12 @@
 							else {
 								current_poly = t;
 							}
-						} // no change in mouse position
-					}
+						} // if it was a path
+						// else, if it was selected and this is a shift-click, remove it from selection
+						else if (evt.shiftKey && tempJustSelected != t) {
+							canvas.removeFromSelection([t]);
+						}
+					} // no change in mouse position
 				}
 				// we return immediately from select so that the obj_num is not incremented
 				return;
@@ -1872,13 +2096,17 @@
 				break;
 			case "line":
 				keep = (element.getAttribute('x1') != element.getAttribute('x2') ||
-				        element.getAttribute('y1') == element.getAttribute('y2'));
+				        element.getAttribute('y1') != element.getAttribute('y2'));
 				break;
 			case "square":
 			case "rect":
 				keep = (element.getAttribute('width') != 0 ||
 				        element.getAttribute('height') != 0);
 				break;
+			case "image":
+				keep = (element.getAttribute('width') != 0 ||
+				        element.getAttribute('height') != 0);
+				break;
 			case "circle":
 				keep = (element.getAttribute('r') != 0);
 				break;
@@ -1897,13 +2125,13 @@
 							"rx": (freehand_max_x - freehand_min_x) / 2,
 							"ry": (freehand_max_y - freehand_min_y) / 2,
 							"id": getId(),
-							"fill": current_fill,
-							"stroke": current_stroke,
-							"stroke-width": current_stroke_width,
-							"stroke-dasharray": current_stroke_style,
-							"opacity": current_opacity,
-							"stroke-opacity": current_stroke_opacity,
-							"fill-opacity": current_fill_opacity
+							"fill": cur_shape.fill,
+							"stroke": cur_shape.stroke,
+							"stroke-width": cur_shape.stroke_width,
+							"stroke-dasharray": cur_shape.stroke_style,
+							"opacity": cur_shape.opacity,
+							"stroke-opacity": cur_shape.stroke_opacity,
+							"fill-opacity": cur_shape.fill_opacity
 						}
 					});
 					call("changed",[element]);
@@ -1921,13 +2149,13 @@
 							"width": (freehand_max_x - freehand_min_x),
 							"height": (freehand_max_y - freehand_min_y),
 							"id": getId(),
-							"fill": current_fill,
-							"stroke": current_stroke,
-							"stroke-width": current_stroke_width,
-							"stroke-dasharray": current_stroke_style,
-							"opacity": current_opacity,
-							"stroke-opacity": current_stroke_opacity,
-							"fill-opacity": current_fill_opacity
+							"fill": cur_shape.fill,
+							"stroke": cur_shape.stroke,
+							"stroke-width": cur_shape.stroke_width,
+							"stroke-dasharray": cur_shape.stroke_style,
+							"opacity": cur_shape.opacity,
+							"stroke-opacity": cur_shape.stroke_opacity,
+							"fill-opacity": cur_shape.fill_opacity
 						}
 					});
 					call("changed",[element]);
@@ -1943,7 +2171,6 @@
 				element = null;
 				// continue to be set to true so that mouseMove happens
 				started = true;
-
 				var stretchy = document.getElementById("poly_stretch_line");
 				if (!stretchy) {
 					stretchy = document.createElementNS(svgns, "line");
@@ -1966,23 +2193,23 @@
 						"attr": {
 							"d": d_attr,
 							"id": getNextId(),
-							"fill": current_fill,
-							"fill-opacity": current_fill_opacity,
-							"stroke": current_stroke,
-							"stroke-width": current_stroke_width,
-							"stroke-dasharray": current_stroke_style,
-							"stroke-opacity": current_stroke_opacity,
-							"opacity": current_opacity / 2
+							"fill": cur_shape.fill,
+							"fill-opacity": cur_shape.fill_opacity,
+							"stroke": cur_shape.stroke,
+							"stroke-width": cur_shape.stroke_width,
+							"stroke-dasharray": cur_shape.stroke_style,
+							"stroke-opacity": cur_shape.stroke_opacity,
+							"opacity": cur_shape.opacity / 2
 						}
 					});
 					// set stretchy line to first point
 					assignAttributes(stretchy, {
-						'x1': x,
-						'y1': y,
-						'x2': x,
-						'y2': y
+						'x1': mouse_x,
+						'y1': mouse_y,
+						'x2': mouse_x,
+						'y2': mouse_y
 					});
-					addPointGripToPoly(x,y);
+					addPointGripToPoly(mouse_x,mouse_y,0);
 				}
 				else {
 					// determine if we clicked on an existing point
@@ -1998,7 +2225,7 @@
 							break;
 						}
 					}
-
+					
 					// get poly element that we are in the process of creating
 					var poly = svgdoc.getElementById(getId());
 
@@ -2034,12 +2261,12 @@
 
 						// set stretchy line to latest point
 						assignAttributes(stretchy, {
-							'x1': x,
-							'y1': y,
-							'x2': x,
-							'y2': y
+							'x1': mouse_x,
+							'y1': mouse_y,
+							'x2': mouse_x,
+							'y2': mouse_y
 						});
-						addPointGripToPoly(x,y);
+						addPointGripToPoly(mouse_x,mouse_y,(current_poly_pts.length/2 - 1));
 					}
 					keep = true;
 				}
@@ -2050,7 +2277,102 @@
 				// if we were dragging a poly point, stop it now
 				if (current_poly_pt_drag != -1) {
 					current_poly_pt_drag = -1;
-				}
+					
+					var batchCmd = new BatchCommand("Edit Poly");
+					// the attribute changes we want to undo
+					var oldvalues = {};
+					oldvalues["d"] = current_poly_oldd;
+					
+					// If the poly was rotated, we must now pay the piper:
+					// Every poly point must be rotated into the rotated coordinate system of 
+					// its old center, then determine the new center, then rotate it back
+					var angle = canvas.getRotationAngle(current_poly) * Math.PI / 180.0;
+					if (angle) {
+						var box = canvas.getBBox(current_poly);
+						var oldbox = selectedBBoxes[0];
+						var oldcx = parseInt(oldbox.x + oldbox.width/2),
+							oldcy = parseInt(oldbox.y + oldbox.height/2),
+							newcx = parseInt(box.x + box.width/2),
+							newcy = parseInt(box.y + box.height/2);
+						
+						// un-rotate the new center to the proper position
+						var dx = newcx - oldcx,
+							dy = newcy - oldcy;
+						var r = Math.sqrt(dx*dx + dy*dy);
+						var theta = Math.atan2(dy,dx) + angle;
+						newcx = parseInt(r * Math.cos(theta) + oldcx);
+						newcy = parseInt(r * Math.sin(theta) + oldcy);
+						
+						var i = current_poly_pts.length;
+						while (i) {
+							i -= 2;
+							dx = current_poly_pts[i] - oldcx;
+							dy = current_poly_pts[i+1] - oldcy;
+							
+							// rotate the point around the old center
+							r = Math.sqrt(dx*dx + dy*dy);
+							theta = Math.atan2(dy,dx) + angle;
+							current_poly_pts[i] = dx = r * Math.cos(theta) + oldcx;
+							current_poly_pts[i+1] = dy = r * Math.sin(theta) + oldcy;
+							
+							// dx,dy should now hold the actual coordinates of each
+							// point after being rotated
+
+							// now we want to rotate them around the new center in the reverse direction
+							dx -= newcx;
+							dy -= newcy;
+							
+							r = Math.sqrt(dx*dx + dy*dy);
+							theta = Math.atan2(dy,dx) - angle;
+							
+							current_poly_pts[i] = parseInt(r * Math.cos(theta) + newcx);
+							current_poly_pts[i+1] = parseInt(r * Math.sin(theta) + newcy);
+						} // loop for each point
+						
+						// now set the d attribute to the new value of current_poly_pts
+						var oldd = current_poly.getAttribute("d");
+						var closedPath = (oldd[oldd.length-1] == 'z' || oldd[oldd.length-1] == 'Z');
+						var len = current_poly_pts.length/2;
+						var arr = new Array(len+1);
+						var curx = current_poly_pts[0],
+							cury = current_poly_pts[1];
+						arr[0] = ["M", curx, ",", cury].join('');
+						assignAttributes(document.getElementById("polypointgrip_0"), 
+										{"cx":curx,"cy":cury}, 100);
+						for (var j = 1; j < len; ++j) {
+							var px = current_poly_pts[j*2], py = current_poly_pts[j*2+1];
+							arr[j] = ["l", parseInt(px-curx), ",", parseInt(py-cury)].join('');
+							curx = px;
+							cury = py;
+							assignAttributes(document.getElementById("polypointgrip_"+j), 
+										{"cx":px,"cy":py}, 100);
+						}
+						if (closedPath) {
+							arr[len] = "z";
+						}
+						current_poly.setAttribute("d", arr.join(' '));
+
+						box = canvas.getBBox(current_poly);						
+						selectedBBoxes[0].x = box.x; selectedBBoxes[0].y = box.y;
+						selectedBBoxes[0].width = box.width; selectedBBoxes[0].height = box.height;
+						
+						// now we must set the new transform to be rotated around the new center
+						var rotate = "rotate(" + (angle * 180.0 / Math.PI) + " " + newcx + "," + newcy + ")";
+						oldvalues["transform"] = current_poly.getAttribute("rotate");
+						current_poly.setAttribute("transform", rotate);
+						
+						var pointGripContainer = document.getElementById("polypointgrip_container");
+						if(pointGripContainer) {
+							pointGripContainer.setAttribute("transform", rotate);
+						}
+					} // if rotated
+
+					batchCmd.addSubCommand(new ChangeElementCommand(current_poly, oldvalues, "poly points"));
+					addCommandToHistory(batchCmd);
+					call("changed", [current_poly]);
+					
+					// make these changes undo-able
+				} // if (current_poly_pt_drag != -1)
 				// else, move back to select mode
 				else {
 					current_mode = "select";
@@ -2063,6 +2385,10 @@
 				keep = true;
 				element = null;
 				current_mode = "select";
+				var batchCmd = canvas.finishUndoableChange();
+				if (!batchCmd.isEmpty()) { 
+					addCommandToHistory(batchCmd);
+				}
 				break;
 			default:
 				console.log("Unknown mode in mouseup: " + current_mode);
@@ -2073,7 +2399,7 @@
 			element = null;
 		} else if (element != null) {
 			canvas.addedNew = true;
-			element.setAttribute("opacity", current_opacity);
+			element.setAttribute("opacity", cur_shape.opacity);
 			cleanupElement(element);
 			selectorManager.update();
 			canvas.addToSelection([element], true);
@@ -2095,18 +2421,14 @@
 		// remove the selected outline before serializing
 		this.clearSelection();
 		
-		// remove unused gradients
-		removeUnusedGrads();
-		
 		var str = "<?xml version=\"1.0\" standalone=\"no\"?>\n";
 		// no need for doctype, see http://jwatt.org/svg/authoring/#doctype-declaration
-		str += svgToString(svgroot, 0);
+		str += svgCanvasToString();
 		call("saved", str);
 	};
 
 	this.getSvgString = function() {
-		removeUnusedGrads();
-		return svgToString(svgroot, 0);
+		return svgCanvasToString();
 	};
 
 	// this function returns false if the set was unsuccessful, true otherwise
@@ -2123,21 +2445,53 @@
 			var batchCmd = new BatchCommand("Change Source");
 
 			// save our old selectorParentGroup
-			selectorManager.selectorParentGroup = svgroot.removeChild(selectorManager.selectorParentGroup);
+			// not needed anymore, we can keep svgroot forever (and just replace svgzoom)
+			// TODO: reset zoom level on svgroot
+//			selectorManager.selectorParentGroup = svgroot.removeChild(selectorManager.selectorParentGroup);
 
-        	// remove old root
-    	    var oldroot = container.removeChild(svgroot);
-			batchCmd.addSubCommand(new RemoveElementCommand(oldroot, container));
+        	// remove old svg document
+    	    var oldzoom = svgroot.removeChild(svgzoom);
+			batchCmd.addSubCommand(new RemoveElementCommand(oldzoom, svgroot));
         
-    	    // set new root
-        	svgroot = container.appendChild(svgdoc.importNode(newDoc.documentElement, true));
-			batchCmd.addSubCommand(new InsertElementCommand(svgroot));
+    	    // set new svg document
+        	svgzoom = svgroot.appendChild(svgdoc.importNode(newDoc.documentElement, true));
+			svgzoom.setAttribute('id', 'svgzoom');
+			// determine proper size
+			var w, h;
+			if (svgzoom.getAttribute("viewBox")) {
+				var vb = svgzoom.getAttribute("viewBox").split(' ');
+				w = vb[2];
+				h = vb[3];
+			}
+			// handle old content that doesn't have a viewBox
+			else {
+				w = svgzoom.getAttribute("width");
+				h = svgzoom.getAttribute("height");
+				svgzoom.setAttribute("viewBox", ["0", "0", w, h].join(" "));
+			}
+			// just to be safe, remove any width/height from text so that they are 100%/100%
+			svgzoom.removeAttribute('width');
+			svgzoom.removeAttribute('height');
+			batchCmd.addSubCommand(new InsertElementCommand(svgzoom));
+
+			// update root to the correct size
+			var changes = {};
+			changes['width'] = svgroot.getAttribute('width');
+			changes['height'] = svgroot.getAttribute('height');
+			svgroot.setAttribute('width', w);
+			svgroot.setAttribute('height', h);
+			batchCmd.addSubCommand(new ChangeElementCommand(svgroot, changes));
+			
+			// reset zoom
+			current_zoom = 1;
 
 			// add back in parentSelectorGroup
-			svgroot.appendChild(selectorManager.selectorParentGroup);
+			// not needed anymore
+//			svgroot.appendChild(selectorManager.selectorParentGroup);
+			selectorManager.update();
 
 			addCommandToHistory(batchCmd);
-			call("changed", [svgroot]);
+			call("changed", [svgzoom]);
 		} catch(e) {
 			console.log(e);
 			return false;
@@ -2150,6 +2504,7 @@
 		var nodes = svgroot.childNodes;
 		var len = svgroot.childNodes.length;
 		var i = 0;
+		current_poly_pts = [];
 		this.clearSelection();
 		for(var rep = 0; rep < len; rep++){
 			if (nodes[i].nodeType == 1) { // element node
@@ -2160,15 +2515,29 @@
 		}
 		// clear the undo stack
 		resetUndoStack();
+		// reset the selector manager
+		selectorManager.initGroup();
+		// reset the rubber band box
+		rubberBox = selectorManager.getRubberBandBox();
+		
 		call("cleared");
 	};
+	
+	this.clearPoly = function() {
+		removeAllPointGripsFromPoly();
+		current_poly = null;
+		current_poly_pts = [];
+	};
 
 	this.getResolution = function() {
-		return [svgroot.getAttribute("width"), svgroot.getAttribute("height")];
+// 		return [svgroot.getAttribute("width"), svgroot.getAttribute("height")];
+		var vb = svgzoom.getAttribute("viewBox").split(' ');
+		return {'w':vb[2], 'h':vb[3], 'zoom': current_zoom};
 	};
 	this.setResolution = function(x, y) {
-		var w = svgroot.getAttribute("width"),
-			h = svgroot.getAttribute("height");
+		var res = canvas.getResolution();
+		var w = res.w, h = res.h;
+		var batchCmd = new BatchCommand("Change Image Dimensions");
 
 		var handle = svgroot.suspendRedraw(1000);
 		
@@ -2176,7 +2545,7 @@
 			canvas.clearSelection();
 
 			// Get bounding box
-			var bbox = svgroot.getBBox();
+			var bbox = svgzoom.getBBox();
 			
 			if(bbox) {
 				x = bbox.x + bbox.width;
@@ -2186,15 +2555,29 @@
 				return;
 			}
 		}
-		
-		svgroot.setAttribute("width", x);
-		svgroot.setAttribute("height", y);
+		svgroot.setAttribute('width', x * current_zoom);
+		svgroot.setAttribute('height', y * current_zoom);
+		batchCmd.addSubCommand(new ChangeElementCommand(svgroot, {"width":w, "height":h}));
+
+		svgzoom.setAttribute("viewBox", ["0 0", x, y].join(' '));
+		batchCmd.addSubCommand(new ChangeElementCommand(svgzoom, {"viewBox": ["0 0", w, h].join(' ')}));
 
 		svgroot.unsuspendRedraw(handle);
-		addCommandToHistory(new ChangeElementCommand(svgroot, {"width":w,"height":h}, "resolution"));
-		call("changed", [svgroot]);
+		
+		addCommandToHistory(batchCmd);
+		call("changed", [svgzoom]);
 	};
 
+	this.setZoom = function(zoomlevel) {
+		var res = canvas.getResolution();
+		svgroot.setAttribute("width", res.w * zoomlevel);
+		svgroot.setAttribute("height", res.h * zoomlevel);
+		current_zoom = zoomlevel;
+		if(selectedElements[0]) {
+			selectorManager.requestSelector(selectedElements[0]).resize();
+		}
+	}
+
 	this.getMode = function() {
 		return current_mode;
 	};
@@ -2204,40 +2587,38 @@
 		if (current_mode == "poly" && current_poly_pts.length > 0) {
 			var elem = svgdoc.getElementById(getId());
 			elem.parentNode.removeChild(elem);
-			removeAllPointGripsFromPoly();
+			canvas.clearPoly();
 			canvas.clearSelection();
 			started = false;
-			current_poly = null;
-			current_poly_pts = [];
 		}
 		else if (current_mode == "polyedit") {
-			removeAllPointGripsFromPoly();
-			current_poly = null;
-			current_poly_pts = [];
+			canvas.clearPoly();
 		}
+		
+		cur_properties = (selectedElements[0] && selectedElements[0].nodeName == 'text') ? cur_text : cur_shape;
 		current_mode = name;
 	};
 
 	this.getStrokeColor = function() {
-		return current_stroke;
+		return cur_properties.stroke;
 	};
 
-	this.setStrokeColor = function(val) {
-		current_stroke = val;
-		this.changeSelectedAttribute("stroke", val);
+	this.setStrokeColor = function(val,preventUndo) {
+		cur_shape.stroke = val;
+		cur_properties.stroke_paint = {type:"solidColor"};
+		if (!preventUndo) 
+			this.changeSelectedAttribute("stroke", val);
+		else 
+			this.changeSelectedAttributeNoUndo("stroke", val);
 	};
 
 	this.getFillColor = function() {
-		return current_fill;
+		return cur_properties.fill;
 	};
 
-	this.setFillColor = function(val) {
-		if(selectedElements[0] != null && selectedElements[0].nodeName == 'text') {
-			current_text_fill = val;
-		} else {
-			current_fill = val;
-		}
-
+	this.setFillColor = function(val,preventUndo) {
+		cur_properties.fill = val;
+		cur_properties.fill_paint = {type:"solidColor"};
 		// take out any path/line elements when setting fill
 		var elems = [];
 		var i = selectedElements.length;
@@ -2247,17 +2628,22 @@
 				elems.push(elem);
 			}
 		}
-		if (elems.length > 0) 
-			this.changeSelectedAttribute("fill", val, elems);
+		if (elems.length > 0) {
+			if (!preventUndo) 
+				this.changeSelectedAttribute("fill", val, elems);
+			else
+				this.changeSelectedAttributeNoUndo("fill", val, elems);
+		}
 	};
 
 	var findDefs = function() {
-		var defs = svgroot.getElementsByTagNameNS(svgns, "defs");
+		var defs = svgzoom.getElementsByTagNameNS(svgns, "defs");
 		if (defs.length > 0) {
 			defs = defs[0];
 		}
 		else {
-			defs = svgroot.insertBefore( svgdoc.createElementNS(svgns, "defs" ), svgroot.firstChild);
+			// first child is a comment, so call nextSibling
+			defs = svgzoom.insertBefore( svgdoc.createElementNS(svgns, "defs" ), svgzoom.firstChild.nextSibling);
 		}
 		return defs;
 	};
@@ -2265,15 +2651,11 @@
 	var addGradient = function() {
 		$.each(['stroke','fill'],function(i,type) {
 			
-			if(type == 'stroke' && (!current_stroke_paint || current_stroke_paint.type == "solidColor")) return;
-			if(type == 'fill' && (!current_fill_paint || current_fill_paint.type == "solidColor")) return;
-			
+			if(!cur_properties[type + '_paint'] || cur_properties[type + '_paint'].type == "solidColor") return;
 			var grad = canvas[type + 'Grad'];
-			
 			// find out if there is a duplicate gradient already in the defs
 			var duplicate_grad = findDuplicateGradient(grad);
 			var defs = findDefs();
-
 			// no duplicate found, so import gradient into defs
 			if (!duplicate_grad) {
 				grad = defs.appendChild( svgdoc.importNode(grad, true) );
@@ -2284,7 +2666,6 @@
 				grad = duplicate_grad;
 			}
 			var functype = type=='fill'?'Fill':'Stroke';
-			
 			canvas['set'+ functype +'Color']("url(#" + grad.id + ")");
 		});
 	}
@@ -2333,83 +2714,95 @@
 	};
 
 	this.setStrokePaint = function(p, addGrad) {
-		current_stroke_paint = new $.jGraduate.Paint(p);
-		if (current_stroke_paint.type == "solidColor") {
-			this.setStrokeColor("#"+current_stroke_paint.solidColor);
-		}
-		else if(current_stroke_paint.type == "linearGradient") {
-			canvas.strokeGrad = current_stroke_paint.linearGradient;
-			if(addGrad) addGradient(); 
-		}
-		else {
-//			console.log("none!");
+		// make a copy
+		var p = new $.jGraduate.Paint(p);
+		this.setStrokeOpacity(p.alpha/100);
+
+		// now set the current paint object
+		cur_properties.stroke_paint = p;
+		if (p.type == "solidColor") {
+			this.setStrokeColor("#"+p.solidColor);
 		}
-		this.setStrokeOpacity(current_stroke_paint.alpha/100);
-	};
-
-	this.setFillPaint = function(p, addGrad) {
-		// copy the incoming paint object
-		current_fill_paint = new $.jGraduate.Paint(p);
-		if (current_fill_paint.type == "solidColor") {
-			this.setFillColor("#"+current_fill_paint.solidColor);
-		}
-		else if(current_fill_paint.type == "linearGradient") {
-			canvas.fillGrad = current_fill_paint.linearGradient;
+		else if(p.type == "linearGradient") {
+			canvas.strokeGrad = p.linearGradient;
 			if(addGrad) addGradient(); 
 		}
 		else {
 //			console.log("none!");
 		}
-		this.setFillOpacity(current_fill_paint.alpha/100);
+	};
+
+	this.setFillPaint = function(p, addGrad) {
+		// make a copy
+		var p = new $.jGraduate.Paint(p);
+		this.setFillOpacity(p.alpha/100, true);
+
+		// now set the current paint object
+		cur_properties.fill_paint = p;
+		if (p.type == "solidColor") {
+			this.setFillColor("#"+p.solidColor);
+		}
+		else if(p.type == "linearGradient") {
+			canvas.fillGrad = p.linearGradient;
+			if(addGrad) addGradient(); 
+		}
+		else {
+//			console.log("none!");
+		}
 	};
 
 	this.getStrokeWidth = function() {
-		return current_stroke_width;
+		return cur_properties.stroke_width;
 	};
 
 	this.setStrokeWidth = function(val) {
-		if(selectedElements[0] != null && selectedElements[0].nodeName == 'text') {
-			current_text_stroke_width = val;
-		} else {
-			current_stroke_width = val;
+		if(val == 0 && $.inArray(current_mode, ['line', 'path']) == -1) {
+			canvas.setStrokeWidth(1);
 		}
+		cur_properties.stroke_width = val;
 		this.changeSelectedAttribute("stroke-width", val);
 	};
 
 	this.getStrokeStyle = function() {
-		return current_stroke_style;
+		return cur_shape.stroke_style;
 	};
 
 	this.setStrokeStyle = function(val) {
-		current_stroke_style = val;
+		cur_shape.stroke_style = val;
 		this.changeSelectedAttribute("stroke-dasharray", val);
 	};
 
 	this.getOpacity = function() {
-		return current_opacity;
+		return cur_shape.opacity;
 	};
 
 	this.setOpacity = function(val) {
-		current_opacity = val;
+		cur_shape.opacity = val;
 		this.changeSelectedAttribute("opacity", val);
 	};
 
 	this.getFillOpacity = function() {
-		return current_fill_opacity;
+		return cur_shape.fill_opacity;
 	};
 
-	this.setFillOpacity = function(val) {
-		current_fill_opacity = val;
-		this.changeSelectedAttribute("fill-opacity", val);
+	this.setFillOpacity = function(val, preventUndo) {
+		cur_shape.fill_opacity = val;
+		if (!preventUndo)
+			this.changeSelectedAttribute("fill-opacity", val);
+		else
+			this.changeSelectedAttributeNoUndo("fill-opacity", val);
 	};
 
 	this.getStrokeOpacity = function() {
-		return current_stroke_opacity;
+		return cur_shape.stroke_opacity;
 	};
 
-	this.setStrokeOpacity = function(val) {
-		current_stroke_opacity = val;
-		this.changeSelectedAttribute("stroke-opacity", val);
+	this.setStrokeOpacity = function(val, preventUndo) {
+		cur_shape.stroke_opacity = val;
+		if (!preventUndo)
+			this.changeSelectedAttribute("stroke-opacity", val);
+		else
+			this.changeSelectedAttributeNoUndo("stroke-opacity", val);
 	};
 
 	this.getBBox = function(elem) {
@@ -2429,6 +2822,7 @@
 
 	this.getRotationAngle = function(elem) {
 		var selected = elem || selectedElements[0];
+		if(!elem.transform) return null;
 		// find the rotation transform (if any) and set it
 		var tlist = selected.transform.baseVal;
 		var t = tlist.numberOfItems;
@@ -2442,17 +2836,21 @@
 		return 0;
 	};
 
-	this.setRotationAngle = function(val) {
+	this.setRotationAngle = function(val,preventUndo) {
 		var elem = selectedElements[0];
 		// we use the actual element's bbox (not the calculated one) since the 
 		// calculated bbox's center can change depending on the angle
-		var bbox = elem.getBBox(); //this.getBBox(elem);
-		var rotate = "rotate(" + val + " " + 
-									(bbox.x+bbox.width/2) + "," +
-									(bbox.y+bbox.height/2) + ")";
-		this.changeSelectedAttribute("transform", rotate);
+		var bbox = elem.getBBox();
+		var cx = parseInt(bbox.x+bbox.width/2), cy = parseInt(bbox.y+bbox.height/2);
+		var rotate = "rotate(" + val + " " + cx + "," + cy + ")";
+		if (preventUndo) {
+			this.changeSelectedAttributeNoUndo("transform", rotate, selectedElements);
+		}
+		else {
+			this.changeSelectedAttribute("transform",rotate,selectedElements);
+		}
 		var pointGripContainer = document.getElementById("polypointgrip_container");
-		if(pointGripContainer) {
+		if(elem.nodeName == "path" && pointGripContainer) {
 			pointGripContainer.setAttribute("transform", rotate);
 		}
 		selectorManager.requestSelector(selectedElements[0]).updateGripCursors(val);
@@ -2463,7 +2861,9 @@
 	};
 
 	this.bind = function(event, f) {
+	  var old = events[event];
 		events[event] = f;
+		return old;
 	};
 
 	this.setIdPrefix = function(p) {
@@ -2510,20 +2910,20 @@
 	};
 
 	this.getFontFamily = function() {
-		return current_font_family;
+		return cur_text.font_family;
 	};
 
 	this.setFontFamily = function(val) {
-    	current_font_family = val;
+    	cur_text.font_family = val;
 		this.changeSelectedAttribute("font-family", val);
 	};
 
 	this.getFontSize = function() {
-		return current_font_size;
+		return cur_text.font_size;
 	};
 
 	this.setFontSize = function(val) {
-		current_font_size = val;
+		cur_text.font_size = val;
 		this.changeSelectedAttribute("font-size", val);
 	};
 
@@ -2537,6 +2937,10 @@
 		this.changeSelectedAttribute("#text", val);
 	};
 
+	this.setImageURL = function(val) {
+		this.changeSelectedAttribute("#href", val);
+	};
+
 	this.setRectRadius = function(val) {
 		var selected = selectedElements[0];
 		if (selected != null && selected.tagName == "rect") {
@@ -2549,63 +2953,130 @@
 			}
 		}
 	};
+	
+	this.quickClone = function(elem) {
+		// Hack for Firefox bugs where text element features aren't updated
+		if(navigator.userAgent.indexOf('Gecko/') == -1) return elem;
+		var clone = elem.cloneNode(true)
+		elem.parentNode.insertBefore(clone, elem);
+		elem.parentNode.removeChild(elem);
+		canvas.clearSelection();
+		canvas.addToSelection([clone],true);
+		return clone;
+	}
+
+	// New functions for refactoring of Undo/Redo
+	
+	// this is the stack that stores the original values, the elements and
+	// the attribute name for begin/finish
+	var undoChangeStackPointer = -1;
+	var undoableChangeStack = [];
+	
+	// This function tells the canvas to remember the old values of the 
+	// attrName attribute for each element sent in.  The elements and values 
+	// are stored on a stack, so the next call to finishUndoableChange() will 
+	// pop the elements and old values off the stack, gets the current values
+	// from the DOM and uses all of these to construct the undo-able command.
+	this.beginUndoableChange = function(attrName, elems) {
+		var p = ++undoChangeStackPointer;
+		var i = elems.length;
+		var oldValues = new Array(i), elements = new Array(i);
+		while (i--) {
+			var elem = elems[i];
+			if (elem == null) continue;
+			elements[i] = elem;
+			oldValues[i] = elem.getAttribute(attrName);
+		}
+		undoableChangeStack[p] = {'attrName': attrName,
+								'oldValues': oldValues,
+								'elements': elements};
+	};
+	
+	// This function makes the changes to the elements and then 
+	// fires the 'changed' event 
+	this.changeSelectedAttributeNoUndo = function(attr, newValue, elems) {
+		var handle = svgroot.suspendRedraw(1000);
+		var elems = elems || selectedElements;
+		var i = elems.length;
+		while (i--) {
+			var elem = elems[i];
+			if (elem == null) continue;
+			// only allow the transform attribute to change on <g> elements, slightly hacky
+			if (elem.tagName == "g" && attr != "transform") continue;
+			var oldval = attr == "#text" ? elem.textContent : elem.getAttribute(attr);
+			if (oldval == null)  oldval = "";
+			if (oldval != newValue) {
+				if (attr == "#text") {
+					elem.textContent = newValue;
+					elem = canvas.quickClone(elem);
+				} else if (attr == "#href") {
+					elem.setAttributeNS(xlinkns, "href", newValue);
+        		}
+				else elem.setAttribute(attr, newValue);
+				selectedBBoxes[i] = this.getBBox(elem);
+				// Use the Firefox quickClone hack for text elements with gradients or
+				// where other text attributes are changed. 
+				if(elem.nodeName == 'text') {
+					if((newValue+'').indexOf('url') == 0 || $.inArray(attr, ['font-size','font-family','x','y']) != -1) {
+						elem = canvas.quickClone(elem);
+					}
+				}
+				// Timeout needed for Opera & Firefox
+				setTimeout(function() {
+					selectorManager.requestSelector(elem).resize(selectedBBoxes[i]);
+				},0);
+				// if this element was rotated, and we changed the position of this element
+				// we need to update the rotational transform attribute 
+				var angle = canvas.getRotationAngle(elem);
+				if (angle && attr != "transform") {
+					var cx = parseInt(selectedBBoxes[i].x + selectedBBoxes[i].width/2),
+						cy = parseInt(selectedBBoxes[i].y + selectedBBoxes[i].height/2);
+					var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
+					if (rotate != elem.getAttribute("transform")) {
+						elem.setAttribute("transform", rotate);
+					}
+				}
+			} // if oldValue != newValue
+		} // for each elem
+		svgroot.unsuspendRedraw(handle);		
+		call("changed", elems);
+	};
+	
+	// This function returns a BatchCommand object which summarizes the
+	// change since beginUndoableChange was called.  The command can then
+	// be added to the command history
+	this.finishUndoableChange = function() {
+		var p = undoChangeStackPointer--;
+		var changeset = undoableChangeStack[p];
+		var i = changeset['elements'].length;
+		var attrName = changeset['attrName'];
+		var batchCmd = new BatchCommand("Change " + attrName);
+		while (i--) {
+			var elem = changeset['elements'][i];
+			if (elem == null) continue;
+			var changes = {};
+			changes[attrName] = changeset['oldValues'][i];
+			if (changes[attrName] != elem.getAttribute(attrName)) {
+				batchCmd.addSubCommand(new ChangeElementCommand(elem, changes, attrName));
+			}
+		}
+		undoableChangeStack[p] = null;
+		return batchCmd;
+	};
 
 	// If you want to change all selectedElements, ignore the elems argument.
 	// If you want to change only a subset of selectedElements, then send the
 	// subset to this function in the elems argument.
 	this.changeSelectedAttribute = function(attr, val, elems) {
 		var elems = elems || selectedElements;
-		var batchCmd = new BatchCommand("Change " + attr);
+		canvas.beginUndoableChange(attr, elems);
 		var i = elems.length;
-		var handle = svgroot.suspendRedraw(1000);
-		while(i--) {
-			var elem = elems[i];
-			if (elem == null) continue;
-			
-			var oldval = (attr == "#text" ? elem.textContent : elem.getAttribute(attr));
-			if (oldval != val) {
-				if (attr == "#text") elem.textContent = val;
-				else elem.setAttribute(attr, val);
-				selectedBBoxes[i] = this.getBBox(elem);
-				if(elem.nodeName == 'text' && (val+'').indexOf('url') == 0) {
-					// Hack for Firefox new-gradient-on-text-bug
-					// Seems like the only way a new gradient is accepted is by creating a new elem
-					elem.setAttribute(attr, val);
-					var clone = elem.cloneNode(true)
-					elem.parentNode.insertBefore(clone, elem);
-					elem.parentNode.removeChild(elem);
-					elem = clone;
-					canvas.clearSelection();
-					canvas.addToSelection([elem],true);
-				}
-				// Timeout needed for Opera & Firefox
-				setTimeout(function() {
-					selectorManager.requestSelector(elem).resize(selectedBBoxes[i]);
-				},0);
-				var changes = {};
-				changes[attr] = oldval;
 
-				// if this element was rotated, and we changed the position of this element
-				// we need to update the rotational transform attribute and store it as part
-				// of our changeset
-				var angle = canvas.getRotationAngle(elem);
-				if (angle) {
-					var cx = selectedBBoxes[i].x + selectedBBoxes[i].width/2,
-						cy = selectedBBoxes[i].y + selectedBBoxes[i].height/2;
-					var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
-					if (rotate != elem.getAttribute("transform")) {
-						elem.setAttribute("transform", rotate);
-						changes['transform'] = rotate;
-					}
-				}
-				
-				batchCmd.addSubCommand(new ChangeElementCommand(elem, changes, attr));
-			}
-		}
-		svgroot.unsuspendRedraw(handle);
+		canvas.changeSelectedAttributeNoUndo(attr, val, elems);
+
+		var batchCmd = canvas.finishUndoableChange();
 		if (!batchCmd.isEmpty()) { 
 			addCommandToHistory(batchCmd);
-			call("changed", elems);
 		}
 	};
 
@@ -2616,6 +3087,7 @@
 	this.deleteSelectedElements = function() {
 		var batchCmd = new BatchCommand("Delete Elements");
 		var len = selectedElements.length;
+		var selectedCopy = []; //selectedElements is being deleted
 		for (var i = 0; i < len; ++i) {
 			var selected = selectedElements[i];
 			if (selected == null) break;
@@ -2625,11 +3097,104 @@
 			// this will unselect the element and remove the selectedOutline
 			selectorManager.releaseSelector(t);
 			var elem = parent.removeChild(t);
+			selectedCopy.push(selected) //for the copy
 			selectedElements[i] = null;
 			batchCmd.addSubCommand(new RemoveElementCommand(elem, parent));
 		}
 		if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
-		call("selected", selectedElements);
+		call("selected", selectedCopy);
+	};
+	
+	this.groupSelectedElements = function() {
+		var batchCmd = new BatchCommand("Group Elements");
+		
+		// create and insert the group element
+		var g = addSvgElementFromJson({
+								"element": "g",
+								"attr": {
+									"id": getNextId()
+								}
+							});
+		batchCmd.addSubCommand(new InsertElementCommand(g));
+		
+		// now move all children into the group
+		var i = selectedElements.length;
+		while (i--) {
+			var elem = selectedElements[i];
+			if (elem == null) continue;
+			var oldNextSibling = elem.nextSibling;
+			var oldParent = elem.parentNode;
+			g.appendChild(elem);
+			batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent));			
+		}
+		if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
+		
+		// ensure selectors are at bottom and update selection
+		selectorManager.update();
+		canvas.clearSelection();
+		canvas.addToSelection([g]);
+	};
+
+	// TODO: when transferring group's rotational transform to the children, must deal
+	// with children who are already rotated within the group
+	this.ungroupSelectedElement = function() {
+		var g = selectedElements[0];
+		if (g.tagName == "g") {
+			var batchCmd = new BatchCommand("Ungroup Elements");
+			var parent = g.parentNode;
+			var anchor = g.previousSibling;
+			var children = new Array(g.childNodes.length);
+			var xform = g.getAttribute("transform");
+			var i = 0;
+			var gbox = g.getBBox(),
+				gx = gbox.x + gbox.width/2,
+				gy = gbox.y + gbox.height/2;
+			while (g.firstChild) {
+				var elem = g.firstChild;
+				var oldNextSibling = elem.nextSibling;
+				var oldParent = elem.parentNode;
+				children[i++] = elem = parent.insertBefore(elem, anchor);
+				batchCmd.addSubCommand(new MoveElementCommand(elem, oldNextSibling, oldParent));
+				if (xform) {
+					var angle = canvas.getRotationAngle(g) * Math.PI / 180.0;
+					var childBox = elem.getBBox();
+					var cx = childBox.x + childBox.width/2,
+						cy = childBox.y + childBox.height/2,
+						dx = cx - gx,
+						dy = cy - gy,
+						r = Math.sqrt(dx*dx + dy*dy);
+					angle += Math.atan2(dy,dx);
+					var newcx = r * Math.cos(angle) + gx,
+						newcy = r * Math.sin(angle) + gy;
+					childBox.x += (newcx - cx);
+					childBox.y += (newcy - cy);
+					elem.setAttribute("transform", xform);
+					batchCmd.addSubCommand(recalculateDimensions(elem, childBox));
+				}
+			}
+			
+			// remove transform and make it undo-able
+			if (xform) {
+				var changes = {};
+				changes["transform"] = xform;
+				g.setAttribute("transform", "");
+				g.removeAttribute("transform");				
+				batchCmd.addSubCommand(new ChangeElementCommand(g, changes));
+			}
+
+			// remove the group from the selection			
+			canvas.clearSelection();
+			
+			// delete the group element (but make undo-able)
+			g = parent.removeChild(g);
+			batchCmd.addSubCommand(new RemoveElementCommand(g, parent));
+
+			if (!batchCmd.isEmpty()) addCommandToHistory(batchCmd);
+			
+			// ensure selectors are at bottom and update selection
+			selectorManager.update();
+			canvas.addToSelection(children);
+		}
 	};
 
 	this.moveToTopSelectedElement = function() {
@@ -2651,7 +3216,8 @@
 			var oldParent = t.parentNode;
 			var oldNextSibling = t.nextSibling;
 			if (oldNextSibling == selectorManager.selectorParentGroup) oldNextSibling = null;
-			var firstChild = t.parentNode.firstChild;
+			// first child is a comment, so call nextSibling
+			var firstChild = t.parentNode.firstChild.nextSibling;
 			if (firstChild.tagName == 'defs') {
 				firstChild = firstChild.nextSibling;
 			}
@@ -2662,6 +3228,8 @@
 
 	this.moveSelectedElements = function(dx,dy,undoable) {
 		// if undoable is not sent, default to true
+		dx *= current_zoom;
+		dy *= current_zoom;
 		var undoable = undoable || true;
 		var batchCmd = new BatchCommand("position");
 		var i = selectedElements.length;
@@ -2680,7 +3248,7 @@
 				} else {
 					selectedBBoxes[i].y += dy;
 				}
-				var cmd = recalculateSelectedDimensions(i);
+				var cmd = recalculateDimensions(selected,selectedBBoxes[i]);
 				if (cmd) {
 					batchCmd.addSubCommand(cmd);
 				}
@@ -2694,30 +3262,44 @@
 		}
 	};
 
+	this.getVisibleElements = function(includeBBox) {
+		var nodes = svgzoom.childNodes;
+		var i = nodes.length;
+		var contentElems = [];
+		
+		while (i--) {
+			var elem = nodes[i];
+			try {
+				var box = canvas.getBBox(elem);
+				if (box) {
+					var item = includeBBox?{'elem':elem, 'bbox':box}:elem;
+					contentElems.push(item);
+				}
+			} catch(e) {}
+		}
+		return contentElems;
+	}
+	
 	this.cycleElement = function(next) {
 		var cur_elem = selectedElements[0];
 		var elem = false;
+		var all_elems = this.getVisibleElements();
 		if (cur_elem == null) {
-			if(next) {
-				var elem = svgroot.firstChild;
-				if (elem.tagName == 'defs') {
-					elem = elem.nextSibling;
-				}
-			} else {
-				var elem = svgroot.lastChild;
-				var id = elem.getAttribute('id');
-				if(!id || id.indexOf('svg_') != 0){
-					elem = elem.previousSibling;
-				}
-			}
+			var num = next?all_elems.length-1:0;
+			elem = all_elems[num];
 		} else {
-			var type = next?'next':'previous';
-			var elem = cur_elem[type + 'Sibling'];
-			if(!elem) return;
-			
-			var id = elem.getAttribute('id');
-			if(!id || id.indexOf('svg_') != 0) {
-				return;
+			var i = all_elems.length;
+			while(i--) {
+				if(all_elems[i] == cur_elem) {
+					var num = next?i-1:i+1;
+					if(num >= all_elems.length) {
+						num = 0;
+					} else if(num < 0) {
+						num = all_elems.length-1;
+					} 
+					elem = all_elems[num];
+					break;
+				} 
 			}
 		}		
 		canvas.clearSelection();
@@ -2747,6 +3329,7 @@
 	this.undo = function() {
 		if (undoStackPointer > 0) {
 			this.clearSelection();
+			removeAllPointGripsFromPoly();
 			var cmd = undoStack[--undoStackPointer];
 			cmd.unapply();
 			call("changed", cmd.elements());
@@ -2764,19 +3347,24 @@
 	// this creates deep DOM copies (clones) of all selected elements
 	this.cloneSelectedElements = function() {
 		var batchCmd = new BatchCommand("Clone Elements");
-		var copiedElements = [];
+		// find all the elements selected (stop at first null)
 		var len = selectedElements.length;
 		for (var i = 0; i < len; ++i) {
-			if (selectedElements[i] == null) break;
-			copiedElements.push(selectedElements[i].cloneNode(true));
+			var elem = selectedElements[i];
+			if (elem == null) break;
 		}
+		// use slice to quickly get the subset of elements we need
+		var copiedElements = selectedElements.slice(0,i);
 		this.clearSelection();
-		var len = copiedElements.length;
-		for (var i = 0; i < len; ++i) {
-			var elem = copiedElements[i];
+		// note that we loop in the reverse way because of the way elements are added
+		// to the selectedElements array (top-first)
+		var i = copiedElements.length;
+		while (i--) {
+			// clone each element and replace it within copiedElements
+			var elem = copiedElements[i] = copiedElements[i].cloneNode(true);
 			elem.removeAttribute("id");
 			elem.id = getNextId();
-			svgroot.appendChild(elem);
+			svgzoom.appendChild(elem);
 			batchCmd.addSubCommand(new InsertElementCommand(elem));
 		}
 
@@ -2808,8 +3396,8 @@
 			if (angles[i]) {
 				var rminx = Number.MAX_VALUE, rminy = Number.MAX_VALUE, 
 					rmaxx = Number.MIN_VALUE, rmaxy = Number.MIN_VALUE;
-				var cx = bboxes[i].x + bboxes[i].width/2,
-					cy = bboxes[i].y + bboxes[i].height/2;
+				var cx = parseInt(bboxes[i].x + bboxes[i].width/2),
+					cy = parseInt(bboxes[i].y + bboxes[i].height/2);
 				var pts = [ [bboxes[i].x - cx, bboxes[i].y - cy], 
 							[bboxes[i].x + bboxes[i].width - cx, bboxes[i].y - cy],
 							[bboxes[i].x + bboxes[i].width - cx, bboxes[i].y + bboxes[i].height - cy],
--- a/htdocs/svg-edit/firefox-extension/chrome.manifest	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/firefox-extension/chrome.manifest	Fri Sep 11 22:33:42 2009 +0200
@@ -1,2 +1,2 @@
-content	SVG-edit	content/
-overlay	chrome://browser/content/browser.xul	chrome://SVG-edit/content/SVG-edit-overlay.xul
+content	svg-edit	content/
+overlay	chrome://browser/content/browser.xul	chrome://svg-edit/content/svg-edit-overlay.xul
--- a/htdocs/svg-edit/firefox-extension/handlers.js	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/firefox-extension/handlers.js	Fri Sep 11 22:33:42 2009 +0200
@@ -1,7 +1,7 @@
 // Note: This JavaScript file must be included as the last script on the main HTML editor page to override the open/save handlers
 $(function() {
 	if(!window.Components) return;
-	
+
 	function moz_file_picker(readflag) {
 	    var fp = window.Components.classes["@mozilla.org/filepicker;1"].
 		createInstance(Components.interfaces.nsIFilePicker);
@@ -13,7 +13,7 @@
 	    fp.show();
 	    return fp.file;
 	}
-	
+
 	svgCanvas.setCustomHandlers({
 		'open':function() {
 		    try {
@@ -22,12 +22,12 @@
 			var file = moz_file_picker(true);
 			if(!file)
 			    return(null);
-			
-                        var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);                      
-                        inputStream.init(file, 0x01, 00004, null);
-                        var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);                   
-                        sInputStream.init(inputStream);
-                        svgCanvas.setSvgString(sInputStream.
+
+			var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
+			inputStream.init(file, 0x01, 00004, null);
+			var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
+			sInputStream.init(inputStream);
+			svgCanvas.setSvgString(sInputStream.
 					    read(sInputStream.available()));
 		    } catch(e) {
                         console.log("Exception while attempting to load" + e);
@@ -38,10 +38,10 @@
 				var file = moz_file_picker(false);
 				if(!file)
 					return;
-				
+
 							if (!file.exists())
 					file.create(0, 0664);
-				
+
 				var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
 				out.init(file, 0x20 | 0x02, 00004,null);
 				out.write(str, str.length);
@@ -52,4 +52,4 @@
 		    }
 		}
 	});
-});
\ No newline at end of file
+});
--- a/htdocs/svg-edit/firefox-extension/install.rdf	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/firefox-extension/install.rdf	Fri Sep 11 22:33:42 2009 +0200
@@ -7,13 +7,13 @@
   <Description about="urn:mozilla:install-manifest">
     <!-- required properties -->
     <em:id>svg-edit@googlegroups.com</em:id>
-    <em:version>2.2</em:version>
+    <em:version>2.3</em:version>
     <em:type>2</em:type>
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
         <em:minVersion>1.5</em:minVersion>
-        <em:maxVersion>3.*</em:maxVersion>
+        <em:maxVersion>3.5.*</em:maxVersion>
       </Description>
     </em:targetApplication>
     <em:name>SVG-edit</em:name>
--- a/htdocs/svg-edit/opera-widget/config.xml	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/opera-widget/config.xml	Fri Sep 11 22:33:42 2009 +0200
@@ -8,7 +8,7 @@
   <height>600</height>
   <id>
     <name>SVG Edit</name>
-    <revised>2009-08</revised>
+    <revised>2009-09</revised>
   </id>
   <feature name="http://xmlns.opera.com/fileio">
     <param name="folderhint" value="home" />
--- a/htdocs/svg-edit/wave/svg-edit.xml	Mon Sep 07 20:49:09 2009 +0200
+++ b/htdocs/svg-edit/wave/svg-edit.xml	Fri Sep 11 22:33:42 2009 +0200
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <Module>
-<ModulePrefs title="SVG-edit" height="600">
-<Require feature="rpc" />
-</ModulePrefs>
-<Content type="html">
-<![CDATA[
+  <ModulePrefs title="SVG-edit" height="780" author="SVG-edit Developers">
+    <Require feature="wave" /> 
+  </ModulePrefs>
+  <Content type="html">
+    <![CDATA[     
 
-<script type="text/javascript" src="http://wave-api.appspot.com/public/wave.js"></script>
+<base href="http://svg-edit.googlecode.com/svn/trunk/editor/svg-editor.html"></base>
 <link rel="stylesheet" href="http://svg-edit.googlecode.com/svn/trunk/editor/jpicker/jpicker.css" type="text/css"/>
 <link rel="stylesheet" href="http://svg-edit.googlecode.com/svn/trunk/editor/svg-editor.css" type="text/css"/>
 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
@@ -16,7 +16,10 @@
 <script type="text/javascript" src="http://svg-edit.googlecode.com/svn/trunk/editor/spinbtn/JQuerySpinBtn.js"></script>
 <script type="text/javascript" src="http://svg-edit.googlecode.com/svn/trunk/editor/svgcanvas.js"></script>
 <script type="text/javascript" src="http://svg-edit.googlecode.com/svn/trunk/editor/svg-editor.js"></script>
- 
+<script type="text/javascript" src="http://svg-edit.googlecode.com/svn/trunk/wave/json2.js"></script>
+<script type="text/javascript" src="http://svg-edit.googlecode.com/svn/trunk/wave/wave.js"></script>
+
+
 
 <div id="svg_editor">
 
@@ -25,29 +28,40 @@
 <div id="svgcanvas"></div>
 </div>
 
+<div id="logo">
+	<a href="http://svg-edit.googlecode.com/" target="_blank" title="SVG-edit Home Page">
+		<img src="images/logo.png" alt="logo" />
+	</a>
+</div>
+
 <div id="tools_top" class="tools_panel">
 	<!-- File-like buttons: New, Save, Source -->
 	<div>
-		<img class="tool_button" id="tool_clear" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/clear.png" title="New Image [N]" alt="Clear" />
-		<img class="tool_button" id="tool_save" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/save.png" title="Save Image [S]" alt="Save"/>
-		<img class="tool_button" id="tool_source" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/source.png" title="Edit Source [U]" alt="Source"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<img class="tool_button" id="tool_clear" src="images/clear.png" title="New Image [N]" alt="Clear" />
+		<img style="display:none" class="tool_button" id="tool_open" src="images/open.png" title="Open Image [O]" alt="Open"/>
+		<img class="tool_button" id="tool_save" src="images/save.png" title="Save Image [S]" alt="Save"/>
+		<img class="tool_button" id="tool_source" src="images/source.png" title="Edit Source [U]" alt="Source"/>
+    <img class="tool_button" title="Wave" src="images/wave.png" alt="Wave State" onclick="alert(wave.getState().toString())" />
+    <img class="tool_button" src="images/source.png" title="Evil Eval" alt="Eval" onclick="eval(prompt('Execute Stuff'));" />
 	</div>
 
     <!-- History buttons -->
 	<div>
-		<img class="tool_sep" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/sep.png" alt="|"/>
-		<img class="tool_button tool_button_disabled" id="tool_undo" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/undo.png" title="Undo [Z]" alt="Undo" />
-		<img class="tool_button tool_button_disabled" id="tool_redo" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/redo.png" title="Redo [Shift+Z/Y]" alt="Redo"/>
-		<img class="tool_button" id="tool_paste" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/paste.png" title="Paste Element [V]" alt="Paste"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<img class="tool_button tool_button_disabled" id="tool_undo" src="images/undo.png" title="Undo [Z]" alt="Undo" />
+		<img class="tool_button tool_button_disabled" id="tool_redo" src="images/redo.png" title="Redo [Y]" alt="Redo"/>
 	</div>
 
-	<!-- Buttons when something a single element is selected -->
+	<!-- Buttons when a single element is selected -->
 	<div id="selected_panel">
-		<img class="tool_sep" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/sep.png" alt="|"/>
-		<img class="tool_button" id="tool_copy" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/copy.png" title="Copy Element [C]" alt="Copy"/>
-		<img class="tool_button" id="tool_delete" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/delete.png" title="Delete Element [Delete/Backspace]" alt="Delete"/>
-		<img class="tool_button" id="tool_move_top" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/move_top.png" title="Move to Top [Shift+Up]" alt="Top"/>
-		<img class="tool_button" id="tool_move_bottom" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/move_bottom.png" title="Move to Bottom [Shift+Down]" alt="Bottom"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<img class="tool_button" id="tool_clone" src="images/clone.png" title="Clone Element [C]" alt="Copy"/>
+		<img class="tool_button" id="tool_delete" src="images/delete.png" title="Delete Element [Delete/Backspace]" alt="Delete"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<img class="tool_button" id="tool_move_top" src="images/move_top.png" title="Move to Top [Shift+Up]" alt="Top"/>
+		<img class="tool_button" id="tool_move_bottom" src="images/move_bottom.png" title="Move to Bottom [Shift+Down]" alt="Bottom"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
 		<select id="group_opacity" class="selected_tool" title="Change selected item opacity">
 			<option selected="selected" value="1">100 %</option>
 			<option value="0.9">90 %</option>
@@ -59,32 +73,49 @@
 			<option value="0.3">30 %</option>
 			<option value="0.2">20 %</option>
 			<option value="0.1">10 %</option>
-		</select>		
+			<option value="0">0 %</option>
+		</select>
+		<span class="selected_tool">angle:</span>
+		<input id="angle" class="selected_tool" title="Change rotation angle" alt="Rotation Angle" size="2" value="0" type="text"/>
 	</div>
 
-	<!-- Buttons when something a single element is selected -->
+	<!-- Buttons when multiple elements are selected -->
 	<div id="multiselected_panel">
-		<img class="tool_sep" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/sep.png" alt="|"/>
-		<img class="tool_button" id="tool_copy_multi" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/copy.png" title="Copy Elements [C]" alt="Copy"/>
-		<img class="tool_button" id="tool_delete_multi" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/delete.png" title="Delete Selected Elements [Delete/Backspace]" alt="Delete"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<img class="tool_button" id="tool_clone_multi" src="images/clone.png" title="Clone Elements [C]" alt="Clone"/>
+		<img class="tool_button" id="tool_delete_multi" src="images/delete.png" title="Delete Selected Elements [Delete/Backspace]" alt="Delete"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<img class="tool_button" id="tool_alignleft" src="images/align-left.png" title="Align Left" alt="Left"/>
+		<img class="tool_button" id="tool_aligncenter" src="images/align-center.png" title="Align Center" alt="Center"/>
+		<img class="tool_button" id="tool_alignright" src="images/align-right.png" title="Align Right" alt="Right"/>
+		<img class="tool_button" id="tool_aligntop" src="images/align-top.png" title="Align Top" alt="Top"/>
+		<img class="tool_button" id="tool_alignmiddle" src="images/align-middle.png" title="Align Middle" alt="Middle"/>
+		<img class="tool_button" id="tool_alignbottom" src="images/align-bottom.png" title="Align Bottom" alt="Bottom"/>
+		<span class="selected_tool">relative to:</span>
+		<select id="align_relative_to" class="selected_tool" title="Align relative to ...">
+		<option value="selected">selected objects</option>
+		<option value="largest">largest object</option>
+		<option value="smallest">smallest object</option>
+		<option value="page">page</option>
+		</select>
 	</div>
 
 	<div id="rect_panel">
-		<img class="tool_sep" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/sep.png" alt="|"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
 		<label class="rect_tool">x:</label>
 		<input id="rect_x" class="rect_tool attr_changer" title="Change rectangle X coordinate" alt="x" size="3"/>
 		<label class="rect_tool">y:</label>
 		<input id="rect_y" class="rect_tool attr_changer" title="Change rectangle Y coordinate" alt="y" size="3"/>
 		<label class="rect_tool">width:</label>
-		<input id="rect_w" class="rect_tool attr_changer" title="Change rectangle width" alt="width" size="3"/>
+		<input id="rect_width" class="rect_tool attr_changer" title="Change rectangle width" alt="width" size="3"/>
 		<label class="rect_tool">height:</label>
-		<input id="rect_h" class="rect_tool attr_changer" title="Change rectangle height" alt="height" size="3"/>
+		<input id="rect_height" class="rect_tool attr_changer" title="Change rectangle height" alt="height" size="3"/>
 		<label class="rect_tool">Corner Radius:</label>
-		<input id="rect_radius" size="3" value="0" class="rect_tool" type="text" title="Change Rectangle Corner Radius" alt="Corner Radius"/>
+		<input id="rect_rx" size="3" value="0" class="rect_tool" type="text" title="Change Rectangle Corner Radius" alt="Corner Radius"/>
 	</div>
 
 	<div id="circle_panel">
-		<img class="tool_sep" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/sep.png" alt="|"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
 		<label class="circle_tool">cx:</label>
 		<input id="circle_cx" class="circle_tool attr_changer" title="Change circle's cx coordinate" alt="cx" size="3"/>
 		<label class="circle_tool">cy:</label>
@@ -94,7 +125,7 @@
 	</div>
 
 	<div id="ellipse_panel">
-		<img class="tool_sep" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/sep.png" alt="|"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
 		<label class="ellipse_tool">cx:</label>
 		<input id="ellipse_cx" class="ellipse_tool attr_changer" title="Change ellipse's cx coordinate" alt="cx" size="3"/>
 		<label class="ellipse_tool">cy:</label>
@@ -106,7 +137,7 @@
 	</div>
 
 	<div id="line_panel">
-		<img class="tool_sep" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/sep.png" alt="|"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
 		<label class="line_tool">x1:</label>
 		<input id="line_x1" class="line_tool attr_changer" title="Change line's starting x coordinate" alt="x1" size="3"/>
 		<label class="line_tool">y1:</label>
@@ -118,9 +149,13 @@
 	</div>
 
 	<div id="text_panel">
-		<img class="tool_sep" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/sep.png" alt="|"/>
-		<img class="tool_button" id="tool_bold" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/bold.png" title="Bold Text [B]" alt="Bold"/>
-		<img class="tool_button" id="tool_italic" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/italic.png" title="Italic Text [I]" alt="Italic"/>
+		<img class="tool_sep" src="images/sep.png" alt="|"/>
+		<label class="text_tool">x:</label>
+		<input id="text_x" class="text_tool attr_changer" title="Change text X coordinate" alt="x" size="3"/>
+		<label class="text_tool">y:</label>
+		<input id="text_y" class="text_tool attr_changer" title="Change text Y coordinate" alt="y" size="3"/>
+		<img class="tool_button" id="tool_bold" src="images/bold.png" title="Bold Text [B]" alt="Bold"/>
+		<img class="tool_button" id="tool_italic" src="images/italic.png" title="Italic Text [I]" alt="Italic"/>
 		<select id="font_family" class="text_tool" title="Change Font Family">
 			<option selected="selected" value="serif">serif</option>
 			<option value="sans-serif">sans-serif</option>
@@ -150,13 +185,15 @@
 </div> <!-- tools_top -->
 
 <div id="tools_left" class="tools_panel">
-	<img class="tool_button_current" id="tool_select" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/select.png" title="Select Tool [1]" alt="Select"/><br/>
-	<img class="tool_button" id="tool_path" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/path.png" title="Pencil Tool [2]" alt="Pencil"/><br/>
-	<img class="tool_button" id="tool_line" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/line.png" title="Line Tool [3]" alt="Line"/><br/>
-	<img class="tool_button" id="tools_rect_show" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/square.png" title="Square/Rect Tool [4/Shift+4]" alt="Square"/><br/>
-	<img class="tool_button" id="tools_ellipse_show" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/circle.png" title="Ellipse/Circle Tool [5/Shift+5]" alt="Circle"/><br/>
-	<img class="tool_button" id="tool_text" src="http://svg-edit.googlecode.com/svn/trunk/editor/images/text.png" title="Text Tool [6]" alt="Text"/>
-	<img class="tool_button" id="tool_poly" src="images/polygon.png" title="Poly Tool [7]" alt="Poly"/>	
+	<img class="tool_button_current" id="tool_select" src="images/select.png" title="Select Tool [1]" alt="Select"/><br/>
+	<img class="tool_button" id="tool_path" src="images/path.png" title="Pencil Tool [2]" alt="Pencil"/><br/>
+	<img class="tool_button" id="tool_line" src="images/line.png" title="Line Tool [3]" alt="Line"/><br/>
+	<img class="tool_button" id="tools_rect_show" src="images/square.png" title="Square/Rect Tool [4/Shift+4]" alt="Square"/>
+	<img class="flyout_arrow_horiz" src="images/flyouth.png"/>
+	<img class="tool_button" id="tools_ellipse_show" src="images/circle.png" title="Ellipse/Circle Tool [5/Shift+5]" alt="Circle"/><br/>
+	<img class="flyout_arrow_horiz" src="images/flyouth.png"/>
+	<img class="tool_button" id="tool_text" src="images/text.png" title="Text Tool [6]" alt="Text"/>
+	<img class="tool_button" id="tool_poly" src="images/polygon.png" title="Poly Tool [7]" alt="Poly"/>
 </div> <!-- tools_left -->
 
 <div id="tools_bottom" class="tools_panel">
@@ -168,6 +205,8 @@
 			<option>1024x768</option>
 			<option>1280x960</option>
 			<option>1600x1200</option>
+			<option>Fit to Content</option>
+			<option>Custom</option>
 		</select>
 	</div>
 
@@ -176,13 +215,13 @@
 		<tr>
 			<td>fill:</td>
 			<td><div id="fill_color" class="color_block"  title="Change fill color"></div></td>
-			<td><div id="fill_opacity">N/A</div></td>
+			<td><div id="fill_opacity">100%</div></td>
 		</tr><tr>
 			<td>stroke:</td>
 			<td><div id="stroke_color" class="color_block" title="Change stroke color"></div></td>
 			<td><div id="stroke_opacity">100 %</div></td>
 			<td>
-				<input id="stroke_width" title="Change stroke width" alt="Stroke Width" size="2" value="1" type="text"/>
+				<input id="stroke_width" title="Change stroke width" alt="Stroke Width" size="2" value="5" type="text"/>
 			</td>
 			<td>
 				<select id="stroke_style" title="Change stroke dash style">
@@ -199,7 +238,7 @@
 
 	<div id="tools_bottom_3">
 		<div id="palette_holder"><div id="palette" title="Click to change fill color, shift-click to change stroke color"></div></div>
-		<div id="copyright">Powered by <a href="http://svg-edit.googlecode.com/" target="_blank">SVG-edit v2.3-preAlpha</a></div>
+		<div id="copyright">Powered by <a href="http://svg-edit.googlecode.com/" target="_blank">SVG-edit v2.3-Beta</a></div>
 	</div>
 </div>
 
@@ -237,112 +276,17 @@
 <div id="svg_source_editor">
 	<div id="svg_source_overlay"></div>
 	<div id="svg_source_container">
-		<div id="tool_source_back" class="toolbar_button"></div>
+		<div id="tool_source_back" class="toolbar_button">
+			<button id="tool_source_save">Load</button>
+			<button id="tool_source_cancel">Cancel</button>
+		</div>
 		<form>
 			<textarea id="svg_source_textarea"></textarea>
 		</form>
 	</div>
 </div>
 
-<script type="text/javascript">
-
-	var svgCanvas = null;
-
-	function stateUpdated() {
-		// 'state' is an object of key-value pairs that map ids to JSON serialization of SVG elements
-		// 'keys' is an array of all the keys in the state
-		var state = wave.getState();
-		var keys = state.getKeys();
-		svgCanvas.each(function(e) {
-			// 'this' is the SVG DOM element node (ellipse, rect, etc)
-			// 'e' is an integer describing the position within the document
-			var k = this.id;
-			var v = state.get(k);
-			if (v) {
-				var ob;
-				eval("ob=" + v); // FIXME: Yes, I'm using eval... Dirty, dirty..
-				if (ob) {
-					svgCanvas.updateElementFromJson(ob);
-				} else {
-					var node = svgdoc.getElementById(k);
-					if (node) node.parentNode.removeChild(node);
-				}
-				keys.remove(k);
-			} else {
-				this.parentNode.removeChild(this);
-			}
-		});
 
-		// New nodes
-		for (var k in keys) {
-			var ob;
-			var v = state.get(keys[k]);
-			eval("ob=" + v); // FIXME: Yes, I'm using eval... Dirty, dirty..
-			if (ob) svgCanvas.updateElementFromJson(ob)
-		}
-	}
-	
-	function myPrintJson(a, b, e) {
-  		if(!a || typeof a.valueOf() != "object") {
-    		if(typeof a == "string")return"'" + a + "'";
-			else if(a instanceof Function)return"[function]";
-    		return"" + a
-  		}
-  		var c = [], f = wave.util.isArray_(a), d = f ? "[]" : "{}", h = b ? "\n" : "", k = b ? " " : "", l = 0, g = e || 1;
-  		b || (g = 0);
-  		c.push(d.charAt(0));
-		for(var i in a) {
-			var j = a[i];
-    		l++ > 0 && c.push(", ");
-    		if(f)
-    			c.push(myPrintJson(j, b, g + 1));
-    		else {
-      			c.push(h);
-      			c.push(wave.util.toSpaces_(g));
-      			c.push("'" + i + "':");
-      			c.push(k);
-      			c.push(myPrintJson(j, b, g + 1))
-    		}
-  		}
-  		if(!f) {
-    		c.push(h);
-    		c.push(wave.util.toSpaces_(g - 1))
-  		}
-  		c.push(d.charAt(1));
-  		return c.join("")
-	}	
-
-	function sendDelta(svgCanvas, elem) {
-		if (!wave) return;
-		var delta = {};
-		var attrs = {};
-		var a = elem.attributes;
-		for (var i = 0; i < a.length; i++) {
-			attrs[a.item(i).nodeName] = a.item(i).nodeValue;
-		}
-		var ob = { element: elem.nodeName, attr: attrs };
-		// wave.util.printJson has a bug where keys  are not quoted like 'stroke-width'
-		delta[elem.id] = myPrintJson(ob, true);
-		wave.getState().submitDelta(delta);
-	}
-	
-	function getId(objnum) {
-		return "svg_"+wave.getViewer().getId()+"_"+objnum;
-	}
-
-	function main() {
-		svgCanvas = svg_edit_setup();
-		if (wave && wave.isInWaveContainer()) {
-			wave.setStateCallback(stateUpdated);
-		}
-		svgCanvas.bind("changed", sendDelta);
-		svgCanvas.bind("cleared", stateUpdated);
-		svgCanvas.bind("getid", getId);
-	}
-
-	gadgets.util.registerOnLoadHandler(main);
-
-</script>
-]]>
-</Content>
+    ]]>
+  </Content>
 </Module>