changeset 403:e8fb1a159800

updated to version of svg-edit Revision r489
author Reimar Bauer <rb.proj AT googlemail DOT com>
date Thu, 27 Aug 2009 23:24:24 +0200
parents 50a89a6cdf6a
children 01cb43413b59
files 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/Makefile htdocs/svg-edit/firefox-extension/build.sh htdocs/svg-edit/firefox-extension/chrome.manifest htdocs/svg-edit/firefox-extension/content/SVG-edit-overlay.js htdocs/svg-edit/firefox-extension/content/SVG-edit-overlay.xul htdocs/svg-edit/firefox-extension/handlers.js htdocs/svg-edit/firefox-extension/install.rdf htdocs/svg-edit/firefox-extension/mk_xpi.sh htdocs/svg-edit/minify.sh htdocs/svg-edit/opera-widget/Makefile htdocs/svg-edit/opera-widget/config.xml htdocs/svg-edit/opera-widget/handlers.js htdocs/svg-edit/opera-widget/index.html htdocs/svg-edit/opera-widget/style.css htdocs/svg-edit/yuicompressor-2.4.2.jar
diffstat 19 files changed, 850 insertions(+), 479 deletions(-) [+]
line wrap: on
line diff
--- a/htdocs/svg-edit/editor/svg-editor.css	Tue Aug 25 22:20:21 2009 +0200
+++ b/htdocs/svg-edit/editor/svg-editor.css	Thu Aug 27 23:24:24 2009 +0200
@@ -3,9 +3,6 @@
 }
 
 #svg_editor {
-	position: relative;
-	width: 680px;
-	height: 640px;
 	font-size: 8pt;
 	font-family: Verdana, Helvetica, Arial;
 	color: #000000;
@@ -29,18 +26,15 @@
 }
 
 #svg_editor #svgcanvas {
-	position: relative;
 	background-color: #FFFFFF;
 	text-align: center;
 	vertical-align: middle;
-	width: 100%;
+	width: 640px;
 	height: 480px;
-    apple-dashboard-region:dashboard-region(control rectangle 0px 0px 0px 0px); /* for widget regions that shouldn't react to dragging */
- }
+	-apple-dashboard-region:dashboard-region(control rectangle 0px 0px 0px 0px); /* for widget regions that shouldn't react to dragging */
 }
 
 #svg_editor div#palette_holder {
-    position: relative;
 	overflow-x: scroll;
 	overflow-y: hidden;
 	height: 31px;
@@ -63,12 +57,11 @@
 }
 
 #svg_editor div#workarea {
-	position: relative;
-    width: 100%;
-	top: 40px;
+	position:absolute;
+	top: 70px;
 	left: 40px;
 	right: 2px;
-	bottom: 0px;
+	bottom: 60px;
 	background-color: #A0A0A0;
 	border: 1px solid #808080;
 	overflow: auto;
@@ -81,21 +74,24 @@
 }
 
 #svg_editor #logo {
-        display: None;
 	position: absolute;
-	top: 1px;
-	left: 1px;
-	width: 30px;
-	height: 30px;
+	top: 4px;
+	left: 4px;
 	padding: 0px;
 }
 
+#svg_editor #logo a img {
+	border: 0;
+	width: 32px;
+	height: 32px;
+}
+
 #svg_editor #tools_top {
 	position: absolute;
 	left: 38px;
 	right: 2px;
 	top: 2px;
-	height: 58px;
+	height: 68px;
 	border-bottom: none;
 }
 
@@ -149,6 +145,10 @@
 	vertical-align: 12px;
 }
 
+#svg_editor #multiselected_panel .selected_tool {
+	vertical-align: 12px;
+}
+
 #svg_editor #text_panel .text_tool {
 	vertical-align: 12px;
 }
@@ -172,21 +172,26 @@
 /* TODO: figure out why there is a couple pixels between
    the tools with a flyout arrow */
 #svg_editor .flyout_arrow_horiz {
-    position: relative;
-    float: right;
-    top: -13px;
-    left: -5px;
-    margin-bottom: -13px;
+	float: right;
+	position: relative;
+	top: -13px;
+	left: -5px;
+	margin-bottom: -13px;
 }
+
 #svg_editor .tool_button, #svg_editor .tool_button_current, #svg_editor .tool_button_disabled {
-    height: 24px;
-    width: 24px;
-    margin: 2px;
-
- }
+	height: 24px;
+	width: 24px;
+	margin: 2px;
+	padding: 2px;
+	border-left: 1px solid #FFFFFF;
+	border-top: 1px solid #FFFFFF;
+	border-right: 1px solid #808080;
+	border-bottom: 1px solid #808080;
+	cursor: pointer;
+}
 
 #svg_editor .tool_button_current {
-	position: relative;
 	border-left: 1px solid #808080;
 	border-top: 1px solid #808080;
 	border-right: 1px solid #FFFFFF;
@@ -195,13 +200,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;
@@ -211,13 +214,13 @@
 
 #svg_editor #color_picker {
 	position: absolute;
-    display: none;
+	display: none;
 	background: #E8E8E8;
 }
 
 #svg_editor .tools_flyout {
 	position: absolute;
-    display: none;
+	display: none;
 	cursor: pointer;
 }
 
@@ -246,7 +249,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;
@@ -266,27 +268,24 @@
 }
 
 #svg_editor #tools_bottom {
-	position: relative;
-    top: 40px;
+	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;
 }
 
 #svg_editor #tools_bottom_2 {
 	width: 250px;
-    float: left;
+	float: left;
 }
 
 #svg_editor #tools_bottom_3 {
-    position: relative;
 }
 
 #svg_editor #copyright {
@@ -297,7 +296,7 @@
 	display: none;
 }
 
-#svg_source_overlay {
+#svg_source_editor #svg_source_overlay {
 	position: absolute;
 	top: 0px;
 	right: 0px;
@@ -307,7 +306,7 @@
 	opacity: 0.6;
 }
 
-#svg_source_container {
+#svg_source_editor #svg_source_container {
 	position: absolute;
 	top: 30px;
 	left: 30px;
@@ -318,7 +317,7 @@
 	text-align: center;
 }
 
-#svg_source_textarea {
+#svg_source_editor #svg_source_textarea {
 	position: relative;
 	width: 95%;
 	top: 5px;
@@ -326,11 +325,21 @@
 	padding: 5px;
 	font-size: 12px;
 }
-#tool_source_back {
-    text-align: left;
-    padding-left: 20px;
+
+#svg_source_editor #tool_source_back {
+	text-align: left;
+	padding-left: 20px;
 }
--
-#tool_source_back button {
-    margin: 10px;
-}
\ No newline at end of file
+
+#svg_source_editor button {
+	padding: 5px 2px 6px 28px;
+	margin: 5px 20px 0 0;	
+}
+
+#tool_source_save {
+	background: #E8E8E8 url(images/save.png) no-repeat 2px 2px;
+}
+
+#tool_source_cancel {
+	background: #E8E8E8 url(images/cancel.png) no-repeat 2px 2px;
+}
--- a/htdocs/svg-edit/editor/svg-editor.html	Tue Aug 25 22:20:21 2009 +0200
+++ b/htdocs/svg-edit/editor/svg-editor.html	Thu Aug 27 23:24:24 2009 +0200
@@ -20,6 +20,8 @@
 <!--script type="text/javascript" src="svgcanvas.min.js"></script-->
 <script type="text/javascript" src="svg-editor.js"></script>
 <!--script type="text/javascript" src="svg-editor.min.js"></script-->
+
+<!-- Add script with custom handlers here -->
 <title>SVG-edit demo</title>
 </head>
 <body>
@@ -33,7 +35,7 @@
 
 <div id="logo">
 	<a href="http://svg-edit.googlecode.com/" target="_blank" title="SVG-edit Home Page">
-		<img src="images/logo.png" width="32" height="32"/>
+		<img src="images/logo.png" alt="logo" />
 	</a>
 </div>
 
@@ -42,7 +44,7 @@
 	<div>
 		<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" eeclass="tool_button" id="tool_open" src="images/open.png" title="Open Image [O]" alt="Open"/>
+		<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"/>
 	</div>
@@ -88,10 +90,16 @@
 		<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_sep" src="images/sep.png" alt="|"/>
 		<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">
@@ -271,8 +279,8 @@
 	<div id="svg_source_overlay"></div>
 	<div id="svg_source_container">
 		<div id="tool_source_back" class="toolbar_button">
-			<button id="tool_source_save"><img src="images/save.png" alt="save" /> Save</button>
-			<button id="tool_source_cancel"><img src="images/cancel.png" alt="cancel" />Cancel</button>
+			<button id="tool_source_save">Save</button>
+			<button id="tool_source_cancel">Cancel</button>
 		</div>
 		<form>
 			<textarea id="svg_source_textarea"></textarea>
@@ -280,9 +288,5 @@
 	</div>
 </div>
 
-<script type="text/javascript">
-$(document).ready(svg_edit_setup);
-</script>
-
 </body>
 </html>
--- a/htdocs/svg-edit/editor/svg-editor.js	Tue Aug 25 22:20:21 2009 +0200
+++ b/htdocs/svg-edit/editor/svg-editor.js	Thu Aug 27 23:24:24 2009 +0200
@@ -10,26 +10,12 @@
 
 	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');
 		$('#tool_select').addClass('tool_button_current');
-		$('#styleoverrides').text('*{cursor:move;pointer-events:all} svg{cursor:default}');
+		$('#styleoverrides').text('#svgcanvas svg *{cursor:move;pointer-events:all} #svgcanvas svg{cursor:default}');
 		svgCanvas.setMode('select');
 	};
 
@@ -39,6 +25,8 @@
 	var selectedElement = null;
 	var multiselected = false;
 	var editingsource = false;
+	var length_attrs = ['x','y','x1','x2','y1','y2','cx','cy','width','height','r','rx','ry','width','height','radius'];
+	var length_types = ['em','ex','px','cm','mm','in','pt','pc','%'];
 	
 	var fillPaint = new $.jGraduate.Paint({solidColor: "FF0000"}); // solid red
 	var strokePaint = new $.jGraduate.Paint({solidColor: "000000"}); // solid black
@@ -48,45 +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) {
-		if(window.opera && window.opera.io && window.opera.io.filesystem)
-		{
-			try {
-				window.opera.io.filesystem.browseForSave(
-					new Date().getTime(), /* mountpoint name */
-					"", /* default location */
-					function(file) {
-						try {
-							if (file) {
-								var fstream = file.open(file, "w");
-								fstream.write(svg, "UTF-8");
-								fstream.close();
-							}
-						}
-						catch(e) {
-							console.log("Write to file failed.");
-						}
-					}, 
-					false /* not persistent */
-				);
-			}
-			catch(e) {
-				console.log("Save file failed.");
-			}
-		}
-		else
-		{
-          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));
 	};
 
 	// called when we've selected a different element
@@ -208,6 +158,13 @@
 	// updates the context panel tools based on the selected element
 	var updateContextPanel = function() {
 		var elem = selectedElement;
+		
+		// No need to update anything else in rotate mode
+		if (svgCanvas.getMode() == 'rotate' && elem != null) {
+			$('#angle').val(svgCanvas.getRotationAngle(elem));
+			return;
+		}
+		
 		$('#selected_panel, #multiselected_panel, #rect_panel, #circle_panel,\
 			#ellipse_panel, #line_panel, #text_panel').hide();
 		if (elem != null) {
@@ -332,7 +289,31 @@
 	});
 
 	$('.attr_changer').change(function() {
-		svgCanvas.changeSelectedAttribute(this.getAttribute("alt"), this.value);
+		var attr = this.getAttribute("alt");
+		var val = this.value;
+		var valid = false;
+		if($.inArray(attr, length_attrs) != -1) {
+			if(!isNaN(val)) {
+				valid = true;
+			} else {
+				//TODO: Allow the values in length_types, then uncomment this:  
+// 				val = val.toLowerCase();
+// 				$.each(length_types, function(i, unit) {
+// 					if(valid) return;
+// 					var re = new RegExp('^-?[\\d\\.]+' + unit + '$');
+// 					if(re.test(val)) valid = true;
+// 				});
+			}
+		} else valid = true;
+		
+		if(!valid) {
+			alert('Invalid value given for' + $(this).attr('title').replace('Change','')
+				+ '.');
+			this.value = selectedElement.getAttribute(attr);
+			return false;
+		} 
+		
+		svgCanvas.changeSelectedAttribute(attr, val);
 	});
 
 	$('.palette_item').click(function(evt){
@@ -391,7 +372,7 @@
 	var clickSelect = function() {
 		if (toolButtonClick('#tool_select')) {
 			svgCanvas.setMode('select');
-			$('#styleoverrides').text('*{cursor:move;pointer-events:all} svg{cursor:default}');
+			$('#styleoverrides').text('#svgcanvas svg *{cursor:move;pointer-events:all}, #svgcanvas svg{cursor:default}');
 		}
 	};
 
@@ -412,21 +393,21 @@
 			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 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(){
@@ -434,21 +415,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(){
@@ -541,26 +522,26 @@
 	var clickClone = function(){
 		svgCanvas.cloneSelectedElements();
 	};
-	
+
 	var clickAlignLeft = function(){
-		svgCanvas.alignSelectedElements('l');
+		svgCanvas.alignSelectedElements('l', $('#align_relative_to option:selected').val() );
 	};
 	var clickAlignCenter = function(){
-		svgCanvas.alignSelectedElements('c');
+		svgCanvas.alignSelectedElements('c', $('#align_relative_to option:selected').val() );
 	};
 	var clickAlignRight = function(){
-		svgCanvas.alignSelectedElements('r');
+		svgCanvas.alignSelectedElements('r', $('#align_relative_to option:selected').val() );
 	};
 	var clickAlignTop = function(){
-		svgCanvas.alignSelectedElements('t');
+		svgCanvas.alignSelectedElements('t', $('#align_relative_to option:selected').val() );
 	};
 	var clickAlignMiddle = function(){
-		svgCanvas.alignSelectedElements('m');
+		svgCanvas.alignSelectedElements('m', $('#align_relative_to option:selected').val() );
 	};
 	var clickAlignBottom = function(){
-		svgCanvas.alignSelectedElements('b');
+		svgCanvas.alignSelectedElements('b', $('#align_relative_to option:selected').val() );
 	};
-	
+
 	var showSourceEditor = function(){
 		if (editingsource) return;
 		editingsource = true;
@@ -712,8 +693,8 @@
 			['shift+down', moveToBottomSelected],
 			['shift+left', function(){rotateSelected(0)}],
 			['shift+right', function(){rotateSelected(1)}],
-			['shift+9', selectPrev],
-			['shift+0', selectNext],
+			['shift+O', selectPrev],
+			['shift+P', selectNext],
 			['up', function(evt){moveSelected(0,-1);evt.preventDefault();}],
 			['down', function(evt){moveSelected(0,1);evt.preventDefault();}],
 			['left', function(evt){moveSelected(-1,0);evt.preventDefault();}],
@@ -729,6 +710,10 @@
 			var disable = !(item.length > 2 && !item[2]);
 			$(document).bind('keydown', {combi:item[0], disableInInput: disable}, item[1]);
 		});
+		
+		$('.attr_changer').bind('keydown', {combi:'return', disableInInput: false}, 
+			function(evt) {$(this).change();evt.preventDefault();}
+		);
 	}
 	
 	setKeyBindings();
@@ -918,13 +903,20 @@
 	$('#stroke_width').SpinButton({ min: 1, max: 99, step: 1, callback: changeStrokeWidth });
 	$('#angle').SpinButton({ min: -180, max: 180, step: 5, callback: changeRotationAngle });
 
-	// if Opera and in widget form, enable the Open button
-	if (window.opera) {
-		opera.postError('opera.io=' + opera.io);
-		if(opera && opera.io && opera.io.filesystem) {
+	svgCanvas.setCustomHandlers = function(opts) {
+		if(opts.open) {
 			$('#tool_open').show();
+			svgCanvas.bind("opened", opts.open);
+		}
+		if(opts.save) {
+			svgCanvas.bind("saved", opts.save);
 		}
 	}
 
 	return svgCanvas;
 };
+
+// This happens when the page is loaded
+$(function() {
+	svgCanvas = svg_edit_setup();
+});
--- a/htdocs/svg-edit/editor/svgcanvas.js	Tue Aug 25 22:20:21 2009 +0200
+++ b/htdocs/svg-edit/editor/svgcanvas.js	Thu Aug 27 23:24:24 2009 +0200
@@ -1,7 +1,8 @@
 /*
 TODOs for Rotator:
 
-- resize rotated elements must work properly with the selector (aligned to the axis of rotation)
+- 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
@@ -30,7 +31,7 @@
 	"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"],
 	"radialGradient": ["id", "cx", "cy", "fx", "fy", "gradientTransform", "gradientUnits", "r", "spreadMethod"],
-	"rect": ["fill", "fill-opacity", "height", "id", "stroke", "stroke-dasharray", "stroke-linecap", "stroke-linejoin", "stroke-opacity", "stroke-width", "transform", "width", "x", "y"],
+	"rect": ["fill", "fill-opacity", "height", "id", "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"],
@@ -219,13 +220,31 @@
 		this.selectorGrips = {	"nw":null,
 								"n":null,
 								"ne":null,
-								"w":null,
 								"e":null,
+								"se":null,
+								"s":null,
 								"sw":null,
-								"s":null,
-								"se":null
+								"w":null
 								};
-
+		this.rotateGripConnector = this.selectorGroup.appendChild( addSvgElementFromJson({
+							"element": "line",
+							"attr": {
+								"id": ("selectorGrip_rotate_connector_" + this.id),
+								"stroke": "blue",
+								"stroke-width": "1",
+							}
+						}) );
+		this.rotateGrip = this.selectorGroup.appendChild( addSvgElementFromJson({
+							"element": "circle",
+							"attr": {
+								"id": ("selectorGrip_rotate_" + this.id),
+								"fill": "lime",
+								"r": 4,
+								"stroke": "blue",
+								"stroke-width": 2
+							}
+						}) );
+		
 		// add the corner grips
 		for (dir in this.selectorGrips) {
 			this.selectorGrips[dir] = this.selectorGroup.appendChild( 
@@ -250,15 +269,42 @@
 				current_mode = "resize";
 				current_resize_mode = this.id.substr(13,this.id.indexOf("_",13)-13);
 			});
+			$('#selectorGrip_rotate_'+id).mousedown( function() {
+				current_mode = "rotate";
+			});
 		}
 
 		this.showGrips = function(show) {
 			// TODO: use suspendRedraw() here
+			var bShow = show ? "inline" : "none";
+			this.rotateGrip.setAttribute("display", bShow);
+			this.rotateGripConnector.setAttribute("display", bShow);
+			var elem = this.selectedElement;
+			if(elem && elem.tagName == "text") bShow = "none";
 			for (dir in this.selectorGrips) {
-				this.selectorGrips[dir].setAttribute("display", show ? "inline" : "none");
+				this.selectorGrips[dir].setAttribute("display", bShow);
 			}
 		};
-
+		
+		// Updates cursors for corner grips on rotation so arrows point the right way
+		this.updateGripCursors = function(angle) {
+			var dir_arr = [];
+			var steps = Math.round(angle / 45);
+			if(steps < 0) steps += 8;
+			for (dir in this.selectorGrips) {
+				dir_arr.push(dir);
+			}
+			while(steps > 0) {
+				dir_arr.push(dir_arr.shift());
+				steps--;
+			}
+			var i = 0;
+			for (dir in this.selectorGrips) {
+				this.selectorGrips[dir].setAttribute('style', ("cursor:" + dir_arr[i] + "-resize"));
+				i++;
+			};
+		};
+		
 		this.resize = function(cur_bbox) {
 			var selectedBox = this.selectorRect;
 			var selectedGrips = this.selectorGrips;
@@ -271,44 +317,50 @@
 			if (selected.tagName == "text") {
 				offset += 2;
 			}
-			var bbox = cur_bbox || canvas.getBBox(this.selectedElement);
+			var oldbox = canvas.getBBox(this.selectedElement);
+			var bbox = cur_bbox || oldbox;
 			var l=bbox.x-offset, t=bbox.y-offset, w=bbox.width+(offset<<1), h=bbox.height+(offset<<1);
-			// TODO: use suspendRedraw() here
-			selectedBox.setAttribute("x", l);
-			selectedBox.setAttribute("y", t);
-			selectedBox.setAttribute("width", w);
-			selectedBox.setAttribute("height", h);
-			selectedGrips.nw.setAttribute("x", l-3);
-			selectedGrips.nw.setAttribute("y", t-3);
-			selectedGrips.ne.setAttribute("x", l+w-3);
-			selectedGrips.ne.setAttribute("y", t-3);
-			selectedGrips.sw.setAttribute("x", l-3);
-			selectedGrips.sw.setAttribute("y", t+h-3);
-			selectedGrips.se.setAttribute("x", l+w-3);
-			selectedGrips.se.setAttribute("y", t+h-3);
-			selectedGrips.n.setAttribute("x", l+w/2-3);
-			selectedGrips.n.setAttribute("y", t-3);
-			selectedGrips.w.setAttribute("x", l-3);
-			selectedGrips.w.setAttribute("y", t+h/2-3);
-			selectedGrips.e.setAttribute("x", l+w-3);
-			selectedGrips.e.setAttribute("y", t+h/2-3);
-			selectedGrips.s.setAttribute("x", l+w/2-3);
-			selectedGrips.s.setAttribute("y", t+h-3);
+			var sr_handle = svgroot.suspendRedraw(100);
+			assignAttributes(selectedBox, {
+				'x': l,
+				'y': t,
+				'width': w,
+				'height': h
+			});
+			
+			var gripCoords = {
+				nw: [l-3, 		t-3],
+				ne: [l+w-3, 	t-3],
+				sw: [l-3, 		t+h-3],
+				se: [l+w-3, 	t+h-3],
+				n:  [l+w/2-3, 	t-3],
+				w:	[l-3, 		t+h/2-3],
+				e:	[l+w-3, 	t+h/2-3],
+				s:	[l+w/2-3, 	t+h-3]
+			};
+			$.each(gripCoords, function(dir, coords) {
+				assignAttributes(selectedGrips[dir], {
+					x: coords[0], y: coords[1]
+				});
+			});
+			
+			assignAttributes(this.rotateGripConnector, { x1: l+w/2, y1: t-20, x2: l+w/2, y2: t });
+			assignAttributes(this.rotateGrip, { cx: l+w/2, cy: t-20 });
 			
 			// empty out the transform attribute
 			this.selectorGroup.setAttribute("transform", "");
 			this.selectorGroup.removeAttribute("transform");
 			
 			// align selector group with element coordinate axes
-			var transform = this.selectedElement.getAttribute("transform");
-			if (transform && transform != "") {
-//				this.selectorGroup.setAttribute("transform", transform);
-				var rotind = transform.indexOf("rotate(");
-				if (rotind != -1) {
-					var rotstr = transform.substr(rotind, transform.indexOf(')',rotind)+1);
-					this.selectorGroup.setAttribute("transform", rotstr);
-				}
+			var elem = this.selectedElement;
+			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;
+				this.selectorGroup.setAttribute("transform", "rotate("+angle+" " + cx + "," + cy + ")");
 			}
+			svgroot.unsuspendRedraw(sr_handle);
 		};
 
 		// now initialize the selector
@@ -418,11 +470,14 @@
 		return canvas.updateElementFromJson(data)
 	};
 
-	var assignAttributes = function(node, attrs) {
-		var handle = svgroot.suspendRedraw(60);
+	var assignAttributes = function(node, attrs, suspendLength) {
+		if(!suspendLength) suspendLength = 0;
+		var handle = svgroot.suspendRedraw(suspendLength);
+
 		for (i in attrs) {
 			node.setAttributeNS(null, i, attrs[i]);
 		}
+		
 		svgroot.unsuspendRedraw(handle);
 	};
 
@@ -460,7 +515,7 @@
 			shape = svgdoc.createElementNS(svgns, data.element);
 			svgroot.appendChild(shape);
 		}
-		assignAttributes(shape, data.attr);
+		assignAttributes(shape, data.attr, 100);
 		cleanupElement(shape);
 		return shape;
 	};
@@ -517,6 +572,8 @@
 	var undoStackPointer = 0;
 	var undoStack = [];
 
+	var curBBoxes = [];
+
 	// This method sends back an array or a NodeList full of elements that
 	// intersect the multi-select rubber-band-box.
 	// 
@@ -529,29 +586,39 @@
 	var getIntersectionList = function(rect) {
 		if (rubberBox == null) { return null; }
 
+		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
+				}
+			}
+		}
+		
 		var resultList = null;
 		try {
 			resultList = svgroot.getIntersectionList(rect, null);
 		} catch(e) { }
 
-		if (resultList == null || typeof(resultList.item) != "function") 
-		{
+		if (resultList == null || typeof(resultList.item) != "function") {
 			resultList = [];
 
 			var rubberBBox = rubberBox.getBBox();
-			var nodes = svgroot.childNodes;
-			var i = svgroot.childNodes.length;
+			var i = curBBoxes.length;
 			while (i--) {
-				// need to do this since the defs has no bbox and causes an exception
-				// to be thrown in Mozilla
-				try {
-					if (nodes[i].id != "selectorParentGroup" &&
-						Utils.rectsIntersect(rubberBBox, canvas.getBBox(nodes[i]))) 
-					{
-						resultList.push(nodes[i]);
-					}
-				} catch(e) {
-					// do nothing, this element did not have a bbox
+				if (Utils.rectsIntersect(rubberBBox, curBBoxes[i].bbox))  {
+					resultList.push(curBBoxes[i].elem);
 				}
 			}
 		}
@@ -651,7 +718,7 @@
 
 	var removeUnusedGrads = function() {
 		var defs = svgroot.getElementsByTagNameNS(svgns, "defs");
-		if(!defs.length) return;
+		if(!defs || !defs.length) return;
 		
 		var all_els = svgroot.getElementsByTagNameNS(svgns, '*');
 		var grad_uses = [];
@@ -790,8 +857,12 @@
 
 		// after this point, we have some change to this element
 
-		var remapx = function(x) {return parseInt(((x-box.x)/box.width)*selectedBBox.width + selectedBBox.x);}
-		var remapy = function(y) {return parseInt(((y-box.y)/box.height)*selectedBBox.height + selectedBBox.y);}
+		var remap = function(x,y) {
+				return { 
+							'x':parseInt(((x-box.x)/box.width)*selectedBBox.width + selectedBBox.x),
+							'y':parseInt(((y-box.y)/box.height)*selectedBBox.height + selectedBBox.y)
+							};					
+			};
 		var scalew = function(w) {return parseInt(w*selectedBBox.width/box.width);}
 		var scaleh = function(h) {return parseInt(h*selectedBBox.height/box.height);}
 
@@ -803,8 +874,57 @@
 		var angle = canvas.getRotationAngle(selected);
 		var pointGripContainer = document.getElementById("polypointgrip_container");
 		if (angle) {
-			var cx = remapx(box.x + box.width/2),
-				cy = remapy(box.y + box.height/2);
+			// 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 cx = null, cy = null;
+			
+			var bFoundScale = false;
+			var tlist = selected.transform.baseVal;
+			var t = tlist.numberOfItems;
+			while (t--) {
+				var xform = tlist.getItem(t);
+				if (xform.type == 3) {
+					bFoundScale = true;
+					break;
+				}
+			}
+			
+			// if this was a resize, find the new cx,cy
+			if (bFoundScale) {
+				var alpha = angle * Math.PI / 180.0;
+			
+				// rotate new opposite corners of bbox by angle at old center
+				var dx = selectedBBox.x - tr_x,
+					dy = selectedBBox.y - tr_y,
+					r = Math.sqrt(dx*dx + dy*dy),
+					theta = Math.atan2(dy,dx) + alpha;
+				var left = r * Math.cos(theta) + tr_x,
+					top = r * Math.sin(theta) + tr_y;
+			
+				dx += selectedBBox.width;
+				dy += selectedBBox.height;
+				r = Math.sqrt(dx*dx + dy*dy);
+				theta = Math.atan2(dy,dx) + alpha;			
+				var right = r * Math.cos(theta) + tr_x,
+					bottom = r * Math.sin(theta) + tr_y;
+			
+				// now find mid-point of line between top-left and bottom-right to find new center
+				cx = parseInt(left + (right-left)/2);
+				cy = parseInt(top + (bottom-top)/2);
+			
+				// now that we know the center and the axis-aligned width/height, calculate the x,y
+				selectedBBox.x = parseInt(cx - selectedBBox.width/2),
+				selectedBBox.y = parseInt(cy - selectedBBox.height/2);
+			}
+			// if it was not a resize, then it was a translation only
+			else {
+				var tx = selectedBBox.x - box.x,
+					ty = selectedBBox.y - box.y;
+				cx = tr_x + tx;
+				cy = tr_y + ty;
+			}
+			
 			var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
 			selected.setAttribute("transform", rotate);
 			if(pointGripContainer) {
@@ -835,8 +955,8 @@
 			var newpoints = "";
 			for (var i = 0; i < len; ++i) {
 				var pt = list.getItem(i);
-				var x = remapx(pt.x), y = remapy(pt.y);
-				newpoints += x + "," + y + " ";
+				pt = remap(pt.x,pt.y);
+				newpoints += pt.x + "," + pt.y + " ";
 			}
 			selected.setAttributeNS(null, "points", newpoints);
 			break;
@@ -846,7 +966,8 @@
 			changes["d"] = selected.getAttribute("d");
 			var M = selected.pathSegList.getItem(0);
 			var curx = M.x, cury = M.y;
-			var newd = "M" + remapx(curx) + "," + remapy(cury);
+			var pt = remap(curx,cury);
+			var newd = "M" + pt.x + "," + pt.y;
 			var segList = selected.pathSegList;
 			var len = segList.numberOfItems;
 			// for all path segments in the path, we first turn them into relative path segments,
@@ -927,55 +1048,62 @@
 			changes["y1"] = selected.getAttribute("y1");
 			changes["x2"] = selected.getAttribute("x2");
 			changes["y2"] = selected.getAttribute("y2");
-			var handle = svgroot.suspendRedraw(1000);
-			selected.setAttribute("x1", remapx(changes["x1"]));
-			selected.setAttribute("y1", remapy(changes["y1"]));
-			selected.setAttribute("x2", remapx(changes["x2"]));
-			selected.setAttribute("y2", remapy(changes["y2"]));
-			svgroot.unsuspendRedraw(handle);
+			var pt1 = remap(changes["x1"],changes["y1"]),
+				pt2 = remap(changes["x2"],changes["y2"]);
+			assignAttributes(selected, {
+				'x1': pt1.x,
+				'y1': pt1.y,
+				'x2': pt2.x,
+				'y2': pt2.y,
+			}, 1000);
 			break;
 		case "circle":
 			changes["cx"] = selected.getAttribute("cx");
 			changes["cy"] = selected.getAttribute("cy");
 			changes["r"] = selected.getAttribute("r");
-			var handle = svgroot.suspendRedraw(1000);
-			selected.setAttribute("cx", remapx(changes["cx"]));
-			selected.setAttribute("cy", remapy(changes["cy"]));
-			// take the minimum of the new selected box's dimensions for the new circle radius
-			selected.setAttribute("r", Math.min(selectedBBox.width/2,selectedBBox.height/2));
-			svgroot.unsuspendRedraw(handle);
+			var pt = remap(changes["cx"], changes["cy"]);
+			assignAttributes(selected, {
+				'cx': pt.x,
+				'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)
+			}, 1000);
 			break;
 		case "ellipse":
 			changes["cx"] = selected.getAttribute("cx");
 			changes["cy"] = selected.getAttribute("cy");
 			changes["rx"] = selected.getAttribute("rx");
 			changes["ry"] = selected.getAttribute("ry");
-			var handle = svgroot.suspendRedraw(1000);
-			selected.setAttribute("cx", remapx(changes["cx"]));
-			selected.setAttribute("cy", remapy(changes["cy"]));
-			selected.setAttribute("rx", scalew(changes["rx"]));
-			selected.setAttribute("ry", scaleh(changes["ry"]));
-			svgroot.unsuspendRedraw(handle);
+			var pt = remap(changes["cx"], changes["cy"]);
+			assignAttributes(selected, {
+				'cx': pt.x,
+				'cy': pt.y,
+				'rx': scalew(changes["rx"]),
+				'ry': scaleh(changes["ry"])
+			}, 1000);
 			break;
 		case "text":
 			changes["x"] = selected.getAttribute("x");
 			changes["y"] = selected.getAttribute("y");
-			var handle = svgroot.suspendRedraw(1000);
-			selected.setAttribute("x", remapx(changes["x"]));
-			selected.setAttribute("y", remapy(changes["y"]));
-			svgroot.unsuspendRedraw(handle);
+			var pt = remap(changes["x"], changes["y"]);
+			assignAttributes(selected, {
+				'x': pt.x,
+				'y': pt.y
+			}, 1000);
 			break;
 		case "rect":
 			changes["x"] = selected.getAttribute("x");
 			changes["y"] = selected.getAttribute("y");
 			changes["width"] = selected.getAttribute("width");
 			changes["height"] = selected.getAttribute("height");
-			var handle = svgroot.suspendRedraw(1000);
-			selected.setAttribute("x", remapx(changes["x"]));
-			selected.setAttribute("y", remapy(changes["y"]));
-			selected.setAttribute("width", scalew(changes["width"]));
-			selected.setAttribute("height", scaleh(changes["height"]));
-			svgroot.unsuspendRedraw(handle);
+			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;
 		default: // rect
 			console.log("Unknown shape type: " + selected.tagName);
@@ -1029,7 +1157,7 @@
 		}
 		
 		if(showGrips) {
-			selectorManager.requestSelector(selectedElements[0]).showGrips(elem.tagName != "text");
+			selectorManager.requestSelector(selectedElements[0]).showGrips(true);
 		}
 	};
 
@@ -1071,12 +1199,12 @@
 		if($.inArray(current_mode, ['select', 'resize']) == -1) {
 			addGradient();
 		}
+		start_x = x;
+		start_y = y;
 		
 		switch (current_mode) {
 			case "select":
 				started = true;
-				start_x = x;
-				start_y = y;
 				current_resize_mode = "none";
 				var t = evt.target;
 				// WebKit returns <div> when the canvas is clicked, Firefox/Opera return <svg>
@@ -1096,11 +1224,13 @@
 					if (rubberBox == null) {
 						rubberBox = selectorManager.getRubberBandBox();
 					}
-					rubberBox.setAttribute("x", start_x);
-					rubberBox.setAttribute("y", start_y);
-					rubberBox.setAttribute("width", 0);
-					rubberBox.setAttribute("height", 0);
-					rubberBox.setAttribute("display", "inline");
+					assignAttributes(rubberBox, {
+						'x': start_x,
+						'y': start_y,
+						'width': 0,
+						'height': 0,
+						'display': 'inline'
+					}, 100);
 				}
 				break;
 			case "resize":
@@ -1249,6 +1379,25 @@
 				if (id.substr(0,14) == "polypointgrip_") {
 					current_poly_pt_drag = parseInt(id.substr(14));
 				}
+
+				if(current_poly_pt_drag == -1) {
+					canvas.clearSelection();
+					canvas.setMode("multiselect");
+					if (rubberBox == null) {
+						rubberBox = selectorManager.getRubberBandBox();
+					}
+					assignAttributes(rubberBox, {
+						'x': start_x,
+						'y': start_y,
+						'width': 0,
+						'height': 0,
+						'display': 'inline'
+					}, 100);
+				}
+
+				break;
+			case "rotate":
+				started = true;
 				break;
 			default:
 				console.log("Unknown mode in mousedown: " + current_mode);
@@ -1290,8 +1439,8 @@
 							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;
+								var cx = box.x + box.width/2,
+									cy = box.y + box.height/2;
 								ts += [" rotate(", angle, " ", cx, ",", cy, ")"].join('');
 
  								var r = Math.sqrt( dx*dx + dy*dy );
@@ -1308,10 +1457,12 @@
 				}
 				break;
 			case "multiselect":
-				rubberBox.setAttribute("x", Math.min(start_x,x));
-				rubberBox.setAttribute("y", Math.min(start_y,y));
-				rubberBox.setAttribute("width", Math.abs(x-start_x));
-				rubberBox.setAttribute("height", Math.abs(y-start_y));
+				assignAttributes(rubberBox, {
+					'x': Math.min(start_x,x),
+					'y': Math.min(start_y,y),
+					'width': Math.abs(x-start_x),
+					'height': Math.abs(y-start_y)
+				},100);
 
 				// this code will probably be faster than using getIntersectionList(), but
 				// not as accurate (only grabs an element if the mouse happens to pass over
@@ -1354,7 +1505,6 @@
 					height=box.height, dx=(x-start_x), dy=(y-start_y);
 								
 				// if rotated, adjust the dx,dy values
-				console.log(box);
 				var angle = canvas.getRotationAngle(selected);
 				if (angle) {
  					var r = Math.sqrt( dx*dx + dy*dy );
@@ -1390,8 +1540,8 @@
 				var ts = [" translate(", (left+tx), ",", (top+ty), ") scale(", sx, ",", sy,
 							") translate(", -(left+tx), ",", -(top+ty), ")"].join('');
 				if (angle) {
-					var cx = left + width/2;//selectedBBox.x + selectedBBox.width/2,
-						cy = top + height/2;//selectedBBox.y + selectedBBox.height/2;
+					var cx = left+width/2,
+						cy = top+height/2;
 					ts = ["rotate(", angle, " ", cx, ",", cy, ")", ts].join('')
 				}
 				selected.setAttribute("transform", ts);
@@ -1402,8 +1552,8 @@
 				if (ty) {
 					selectedBBox.y = top+dy;
 				}
-				selectedBBox.width = width*sx;
-				selectedBBox.height = height*sy;
+				selectedBBox.width = parseInt(width*sx);
+				selectedBBox.height = parseInt(height*sy);
 				// normalize selectedBBox
 				if (selectedBBox.width < 0) {
 					selectedBBox.x += selectedBBox.width;
@@ -1416,10 +1566,10 @@
 				selectorManager.requestSelector(selected).resize(selectedBBox);
 				break;
 			case "text":
-				var handle = svgroot.suspendRedraw(1000);
-				shape.setAttribute("x", x);
-				shape.setAttribute("y", y);
-				svgroot.unsuspendRedraw(handle);
+				assignAttributes(shape,{
+					'x': x,
+					'y': y
+				},1000);
 				break;
 			case "line":
 				var handle = svgroot.suspendRedraw(1000);
@@ -1429,20 +1579,20 @@
 				break;
 			case "square":
 				var size = Math.max( Math.abs(x - start_x), Math.abs(y - start_y) );
-				var handle = svgroot.suspendRedraw(1000);
-				shape.setAttributeNS(null, "width", size);
-				shape.setAttributeNS(null, "height", size);
-				shape.setAttributeNS(null, "x", start_x < x ? start_x : start_x - size);
-				shape.setAttributeNS(null, "y", start_y < y ? start_y : start_y - size);
-				svgroot.unsuspendRedraw(handle);
+				assignAttributes(shape,{
+					'width': size,
+					'height': size,
+					'x': start_x < x ? start_x : start_x - size,
+					'y': start_y < y ? start_y : start_y - size
+				},1000);
 				break;
 			case "rect":
-				var handle = svgroot.suspendRedraw(1000);
-				shape.setAttributeNS(null, "x", Math.min(start_x,x));
-				shape.setAttributeNS(null, "y", Math.min(start_y,y));
-				shape.setAttributeNS(null, "width", Math.abs(x-start_x));
-				shape.setAttributeNS(null, "height", Math.abs(y-start_y));
-				svgroot.unsuspendRedraw(handle);
+				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");
@@ -1496,9 +1646,9 @@
 					var angle = canvas.getRotationAngle(current_poly) * Math.PI / 180.0;
 					if (angle) {
 						// extract the shape's (potentially) old 'center' from the transform attribute
-						var matched_numbers = current_poly.getAttribute('transform').match(/([\d\.\-\+]+)/g);
-						var cx = parseFloat(matched_numbers[1]), 
-							cy = parseFloat(matched_numbers[2]);
+						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 r = Math.sqrt( dx*dx + dy*dy );
 						var theta = Math.atan2(dy,dx) - angle;						
@@ -1536,6 +1686,10 @@
 					}
 				}
 				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));
+				break;
 			default:
 				break;
 		}
@@ -1562,9 +1716,11 @@
 		while(i) {
 			i -= 2;
 			var grip = document.getElementById("polypointgrip_"+i/2);
-			grip.setAttribute("cx", current_poly_pts[i]);
-			grip.setAttribute("cy", current_poly_pts[i+1]);
-			grip.setAttribute("display", "inline");
+			assignAttributes(grip, {
+				'cx': current_poly_pts[i],
+				'cy': current_poly_pts[i+1],
+				'display': 'inline'
+			});
 		}
 	};
 
@@ -1584,14 +1740,16 @@
 		// create it
 		if (!pointGrip) {
 			pointGrip = document.createElementNS(svgns, "circle");
-			pointGrip.id = "polypointgrip_" + index;
-			pointGrip.setAttribute("display", "none");
-			pointGrip.setAttribute("r", 4);
-			pointGrip.setAttribute("fill", "#0F0");
-			pointGrip.setAttribute("stroke", "#00F");
-			pointGrip.setAttribute("stroke-width", 2);
-			pointGrip.setAttribute("cursor", "move");
-			pointGrip.setAttribute("pointer-events", "all");
+			assignAttributes(pointGrip, {
+				'id': "polypointgrip_" + index,
+				'display': "none",
+				'r': 4,
+				'fill': "#0F0",
+				'stroke': "#00F",
+				'stroke-width': 2,
+				'cursor': 'move',
+				"pointer-events": "all"
+			});
 			pointGrip = pointGripContainer.appendChild(pointGrip);
 
 			var grip = $('#polypointgrip_'+index);
@@ -1600,9 +1758,11 @@
 		}
 
 		// set up the point grip element and display it
-		pointGrip.setAttribute("cx", x);
-		pointGrip.setAttribute("cy", y);
-		pointGrip.setAttribute("display", "inline");
+		assignAttributes(pointGrip, {
+			'cx': x,
+			'cy': y,
+			'display': "inline",
+		});
 	};
 
 	// - in create mode, the element's opacity is set properly, we create an InsertElementCommand
@@ -1627,6 +1787,7 @@
 			case "multiselect":
 				if (rubberBox != null) {
 					rubberBox.setAttribute("display", "none");
+					curBBoxes = [];
 				}
 				current_mode = "select";
 			case "select":
@@ -1646,7 +1807,7 @@
 							current_font_family = selected.getAttribute("font-family");
 						}
 
-						selectorManager.requestSelector(selected).showGrips(selected.tagName != "text");
+						selectorManager.requestSelector(selected).showGrips(true);
 					}
 					// if it was being dragged/resized
 					if (x != start_x || y != start_y) {
@@ -1693,6 +1854,8 @@
 									current_poly_pts.push(cury);
 								} // for each segment
 								canvas.clearSelection();
+								// save the poly's bbox
+								selectedBBoxes[0] = canvas.getBBox(current_poly);
 								addAllPointGripsToPoly();
 							} // going into polyedit mode
 							else {
@@ -1784,9 +1947,11 @@
 				var stretchy = document.getElementById("poly_stretch_line");
 				if (!stretchy) {
 					stretchy = document.createElementNS(svgns, "line");
-					stretchy.id = "poly_stretch_line";
-					stretchy.setAttribute("stroke-width", "0.5");
-					stretchy.setAttribute("stroke", "blue");
+					assignAttributes(stretchy, {
+						'id': "poly_stretch_line",
+						'stroke': "blue",
+						'stroke-width': "0.5"
+					});
 					stretchy = document.getElementById("selectorParentGroup").appendChild(stretchy);
 				}
 				stretchy.setAttribute("display", "inline");
@@ -1811,10 +1976,12 @@
 						}
 					});
 					// set stretchy line to first point
-					stretchy.setAttribute("x1", x);
-					stretchy.setAttribute("y1", y);
-					stretchy.setAttribute("x2", x);
-					stretchy.setAttribute("y2", y);
+					assignAttributes(stretchy, {
+						'x1': x,
+						'y1': y,
+						'x2': x,
+						'y2': y
+					});
 					addPointGripToPoly(x,y);
 				}
 				else {
@@ -1866,10 +2033,12 @@
 						poly.setAttribute("d", d_attr);
 
 						// set stretchy line to latest point
-						stretchy.setAttribute("x1", x);
-						stretchy.setAttribute("y1", y);
-						stretchy.setAttribute("x2", x);
-						stretchy.setAttribute("y2", y);
+						assignAttributes(stretchy, {
+							'x1': x,
+							'y1': y,
+							'x2': x,
+							'y2': y
+						});
 						addPointGripToPoly(x,y);
 					}
 					keep = true;
@@ -1890,6 +2059,11 @@
 					canvas.addToSelection([evt.target]);
 				}
 				break;
+			case "rotate":
+				keep = true;
+				element = null;
+				current_mode = "select";
+				break;
 			default:
 				console.log("Unknown mode in mouseup: " + current_mode);
 				break;
@@ -1912,38 +2086,9 @@
 
 // public functions
 
-	this.open = function() {
-		if(window.opera && window.opera.io && window.opera.io.filesystem)
-		{
-			try {
-				window.opera.io.filesystem.browseForFile(
-					new Date().getTime(), /* mountpoint name */
-					"", /* default location */
-					function(file) {
-						try {
-							if (file) {
-								fstream = file.open(file, "r");
-								var output = "";
-								while (!fstream.eof) {
-									output += fstream.readLine("UTF-16");
-								}
-								
-								canvas.setSvgString(output); /* 'this' is bound to the filestream object here */
-							}
-						}
-						catch(e) {
-							console.log("Reading file failed.");
-						}
-					},
-					false, /* not persistent */
-					false, /* no multiple selections */
-					"*.svg" /* file extension filter */
-				);
-			}
-			catch(e) {
-				console.log("Open file failed.");
-			}
-		}
+	this.open = function(str) {
+		// Nothing by default, handled by optional widget/extention
+		call("opened", str);
 	};
 
 	this.save = function() {
@@ -2128,11 +2273,10 @@
 			// 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) );
-	
 				// get next id and set it on the grad
 				grad.id = getNextId();
 			}
@@ -2270,8 +2414,8 @@
 
 	this.getBBox = function(elem) {
 		var selected = elem || selectedElements[0];
-		
-		if(selected.textContent == '') {
+
+		if(elem.nodeName == 'text' && selected.textContent == '') {
 			selected.textContent = 'a'; // Some character needed for the selector to use.
 			var ret = selected.getBBox();
 			selected.textContent = '';
@@ -2311,7 +2455,7 @@
 		if(pointGripContainer) {
 			pointGripContainer.setAttribute("transform", rotate);
 		}
-
+		selectorManager.requestSelector(selectedElements[0]).updateGripCursors(val);
 	};
 
 	this.each = function(cb) {
@@ -2423,12 +2567,38 @@
 				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));
 			}
 		}
@@ -2499,8 +2669,17 @@
 			var selected = selectedElements[i];
 			if (selected != null) {
 				selectedBBoxes[i] = this.getBBox(selected);
-				selectedBBoxes[i].x += dx;
-				selectedBBoxes[i].y += dy;
+				// dx and dy could be arrays
+				if (dx.constructor == Array) {
+					selectedBBoxes[i].x += dx[i];
+				} else {
+					selectedBBoxes[i].x += dx;
+				}
+				if (dy.constructor == Array) {
+					selectedBBoxes[i].y += dy[i];
+				} else {
+					selectedBBoxes[i].y += dy;
+				}
 				var cmd = recalculateSelectedDimensions(i);
 				if (cmd) {
 					batchCmd.addSubCommand(cmd);
@@ -2595,6 +2774,7 @@
 		var len = copiedElements.length;
 		for (var i = 0; i < len; ++i) {
 			var elem = copiedElements[i];
+			elem.removeAttribute("id");
 			elem.id = getNextId();
 			svgroot.appendChild(elem);
 			batchCmd.addSubCommand(new InsertElementCommand(elem));
@@ -2609,175 +2789,123 @@
 	};
 
 	// aligns selected elements (type is a char - see switch below for explanation)
-	this.alignSelectedElements = function(type) {
-		var minx = 100000, maxx = -100000, miny = 100000, maxy = -100000;
+	// relative_to can be "selected", "largest", "smallest", "page"
+	this.alignSelectedElements = function(type, relative_to) {
+		var bboxes = [], angles = [];
+		var minx = Number.MAX_VALUE, maxx = Number.MIN_VALUE, miny = Number.MAX_VALUE, maxy = Number.MIN_VALUE;
+		var curwidth = Number.MIN_VALUE, curheight = Number.MIN_VALUE;
 		var len = selectedElements.length;
 		if (!len) return;
 		for (var i = 0; i < len; ++i) {
 			if (selectedElements[i] == null) break;
 			var elem = selectedElements[i];
-			switch (elem.tagName) {
-				case 'circle':
-					if (parseInt(elem.getAttribute('cx')) - parseInt(elem.getAttribute('r')) < minx) minx = parseInt(elem.getAttribute('cx')) - parseInt(elem.getAttribute('r'));
-					if (parseInt(elem.getAttribute('cx')) + parseInt(elem.getAttribute('r')) > maxx) maxx = parseInt(elem.getAttribute('cx')) + parseInt(elem.getAttribute('r'));
-					if (parseInt(elem.getAttribute('cy')) - parseInt(elem.getAttribute('r')) < miny) miny = parseInt(elem.getAttribute('cy')) - parseInt(elem.getAttribute('r'));
-					if (parseInt(elem.getAttribute('cy')) + parseInt(elem.getAttribute('r')) > maxy) maxy = parseInt(elem.getAttribute('cy')) + parseInt(elem.getAttribute('r'));
-					break;
-				case 'ellipse':
-					if (parseInt(elem.getAttribute('cx')) - parseInt(elem.getAttribute('rx')) < minx) minx = parseInt(elem.getAttribute('cx')) - parseInt(elem.getAttribute('rx'));
-					if (parseInt(elem.getAttribute('cx')) + parseInt(elem.getAttribute('rx')) > maxx) maxx = parseInt(elem.getAttribute('cx')) + parseInt(elem.getAttribute('rx'));
-					if (parseInt(elem.getAttribute('cy')) - parseInt(elem.getAttribute('ry')) < miny) miny = parseInt(elem.getAttribute('cy')) - parseInt(elem.getAttribute('ry'));
-					if (parseInt(elem.getAttribute('cy')) + parseInt(elem.getAttribute('ry')) > maxy) maxy = parseInt(elem.getAttribute('cy')) + parseInt(elem.getAttribute('ry'));
-					break;
-				case 'rect':
-					if (parseInt(elem.getAttribute('x'))                                         < minx) minx = parseInt(elem.getAttribute('x'));
-					if (parseInt(elem.getAttribute('x')) + parseInt(elem.getAttribute('width'))  > maxx) maxx = parseInt(elem.getAttribute('x')) + parseInt(elem.getAttribute('width'));
-					if (parseInt(elem.getAttribute('y'))                                         < minx) miny = parseInt(elem.getAttribute('y'));
-					if (parseInt(elem.getAttribute('y')) + parseInt(elem.getAttribute('height')) > maxx) maxy = parseInt(elem.getAttribute('y')) + parseInt(elem.getAttribute('height'));
+			bboxes[i] = this.getBBox(elem);
+			// TODO: could make the following code block as part of getBBox() and add a parameter 
+			//       to that function
+			// if element is rotated, get angle and rotate the 4 corners of the bbox and get
+			// the new axis-aligned bbox
+			angles[i] = this.getRotationAngle(elem) * Math.PI / 180.0;
+			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 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],
+							[bboxes[i].x - cx, bboxes[i].y + bboxes[i].height - cy] ];
+				var j = 4;
+				while (j--) {
+					var x = pts[j][0],
+						y = pts[j][1],
+						r = Math.sqrt( x*x + y*y );
+					var theta = Math.atan2(y,x) + angles[i];
+					x = parseInt(r * Math.cos(theta) + cx);
+					y = parseInt(r * Math.sin(theta) + cy);
+
+					// now set the bbox for the shape after it's been rotated
+					if (x < rminx) rminx = x;
+					if (y < rminy) rminy = y;
+					if (x > rmaxx) rmaxx = x;
+					if (y > rmaxy) rmaxy = y;
+				}
+				
+				bboxes[i].x = rminx;
+				bboxes[i].y = rminy;
+				bboxes[i].width = rmaxx - rminx;
+				bboxes[i].height = rmaxy - rminy;
+			}
+			
+			// now bbox is axis-aligned and handles rotation
+			switch (relative_to) {
+				case 'smallest':
+					if ( (type == 'l' || type == 'c' || type == 'r') && (curwidth == Number.MIN_VALUE || curwidth > bboxes[i].width) ||
+					     (type == 't' || type == 'm' || type == 'b') && (curheight == Number.MIN_VALUE || curheight > bboxes[i].height) ) {
+						minx = bboxes[i].x;
+						miny = bboxes[i].y;
+						maxx = bboxes[i].x + bboxes[i].width;
+						maxy = bboxes[i].y + bboxes[i].height;
+						curwidth = bboxes[i].width;
+						curheight = bboxes[i].height;
+					}
 					break;
-				case 'line':
-					if (parseInt(elem.getAttribute('x1')) < minx) minx = parseInt(elem.getAttribute('x1'));
-					if (parseInt(elem.getAttribute('x2')) < minx) minx = parseInt(elem.getAttribute('x2'));
-					if (parseInt(elem.getAttribute('x1')) > maxx) maxx = parseInt(elem.getAttribute('x1'));
-					if (parseInt(elem.getAttribute('x2')) > maxx) maxx = parseInt(elem.getAttribute('x2'));
-					if (parseInt(elem.getAttribute('y1')) < miny) miny = parseInt(elem.getAttribute('y1'));
-					if (parseInt(elem.getAttribute('y2')) < miny) miny = parseInt(elem.getAttribute('y2'));
-					if (parseInt(elem.getAttribute('y1')) > maxy) maxy = parseInt(elem.getAttribute('y1'));
-					if (parseInt(elem.getAttribute('y2')) > maxy) maxy = parseInt(elem.getAttribute('y2'));
+				case 'largest':
+					if ( (type == 'l' || type == 'c' || type == 'r') && (curwidth == Number.MIN_VALUE || curwidth < bboxes[i].width) ||
+					     (type == 't' || type == 'm' || type == 'b') && (curheight == Number.MIN_VALUE || curheight < bboxes[i].height) ) {
+						minx = bboxes[i].x;
+						miny = bboxes[i].y;
+						maxx = bboxes[i].x + bboxes[i].width;
+						maxy = bboxes[i].y + bboxes[i].height;
+						curwidth = bboxes[i].width;
+						curheight = bboxes[i].height;
+					}
 					break;
-				case 'path':
-					// TODO: implement
-					break;
-				case 'polygon':
-					// TODO: implement
-					break;
-				case 'polyline':
-					// TODO: implement
-					break;
-				case 'text':
-					// TODO: how to handle text?
+				default: // 'selected'
+					if (bboxes[i].x < minx) minx = bboxes[i].x;
+					if (bboxes[i].y < miny) miny = bboxes[i].y;
+					if (bboxes[i].x + bboxes[i].width > maxx) maxx = bboxes[i].x + bboxes[i].width;
+					if (bboxes[i].y + bboxes[i].height > maxy) maxy = bboxes[i].y + bboxes[i].height;
 					break;
 			}
+		} // loop for each element to find the bbox and adjust min/max
+
+		if (relative_to == 'page') {
+			minx = 0;
+			miny = 0;
+			maxx = svgroot.getAttribute('width');
+			maxy = svgroot.getAttribute('height');
 		}
-		var len = selectedElements.length;
+
+		var dx = new Array(len);
+		var dy = new Array(len);
 		for (var i = 0; i < len; ++i) {
 			if (selectedElements[i] == null) break;
 			var elem = selectedElements[i];
-			switch (elem.tagName) {
-				case 'circle':
-					switch (type) {
-					case 'l': // left (horizontal)
-						elem.setAttribute('cx', minx+parseInt(elem.getAttribute('r')));
-						break;
-					case 'c': // center (horizontal)
-						elem.setAttribute('cx', (minx+maxx)/2);
-						break;
-					case 'r': // right (horizontal)
-						elem.setAttribute('cx', maxx-parseInt(elem.getAttribute('r')));
-						break;
-					case 't': // top (vertical)
-						elem.setAttribute('cy', miny+parseInt(elem.getAttribute('r')));
-						break;
-					case 'm': // middle (vertical)
-						elem.setAttribute('cy', (miny+maxy)/2);
-						break;
-					case 'b': // bottom (vertical)
-						elem.setAttribute('cy', maxy-parseInt(elem.getAttribute('r')));
-						break;
-					}
-					break;
-				case 'ellipse':
-					switch (type) {
-					case 'l': // left (horizontal)
-						elem.setAttribute('cx', minx+parseInt(elem.getAttribute('rx')));
-						break;
-					case 'c': // center (horizontal)
-						elem.setAttribute('cx', (minx+maxx)/2);
-						break;
-					case 'r': // right (horizontal)
-						elem.setAttribute('cx', maxx-parseInt(elem.getAttribute('rx')));
-						break;
-					case 't': // top (vertical)
-						elem.setAttribute('cy', miny+parseInt(elem.getAttribute('ry')));
-						break;
-					case 'm': // middle (vertical)
-						elem.setAttribute('cy', (miny+maxy)/2);
-						break;
-					case 'b': // bottom (vertical)
-						elem.setAttribute('cy', maxy-parseInt(elem.getAttribute('ry')));
-						break;
-					}
+			var bbox = bboxes[i];
+			dx[i] = 0;
+			dy[i] = 0;
+			switch (type) {
+				case 'l': // left (horizontal)
+					dx[i] = minx - bbox.x;
 					break;
-				case 'rect':
-					switch (type) {
-					case 'l': // left (horizontal)
-						elem.setAttribute('x', minx);
-						break;
-					case 'c': // center (horizontal)
-						elem.setAttribute('x', (minx+maxx-parseInt(elem.getAttribute('width')))/2);
-						break;
-					case 'r': // right (horizontal)
-						elem.setAttribute('x', maxx-parseInt(elem.getAttribute('width')));
-						break;
-					case 't': // top (vertical)
-						elem.setAttribute('y', miny);
-						break;
-					case 'm': // middle (vertical)
-						elem.setAttribute('y', (miny+maxy-parseInt(elem.getAttribute('height')))/2);
-						break;
-					case 'b': // bottom (vertical)
-						elem.setAttribute('y', maxy-parseInt(elem.getAttribute('height')));
-						break;
-					}
+				case 'c': // center (horizontal)
+					dx[i] = (minx+maxx)/2 - (bbox.x + bbox.width/2);
 					break;
-				case 'line':
-					switch (type) {
-					case 'l': // left (horizontal)
-						var d = Math.min(parseInt(elem.getAttribute('x1')), parseInt(elem.getAttribute('x2'))) - minx;
-						elem.setAttribute('x1', parseInt(elem.getAttribute('x1')) - d);
-						elem.setAttribute('x2', parseInt(elem.getAttribute('x2')) - d);
-						break;
-					case 'c': // center (horizontal)
-						var d = Math.abs(parseInt(elem.getAttribute('x1')) - parseInt(elem.getAttribute('x2')));
-						elem.setAttribute('x1', (minx+maxx-d)/2 );
-						elem.setAttribute('x2', (minx+maxx+d)/2 );
-						break;
-					case 'r': // right (horizontal)
-						var d = Math.max(parseInt(elem.getAttribute('x1')), parseInt(elem.getAttribute('x2'))) - maxx;
-						elem.setAttribute('x1', parseInt(elem.getAttribute('x1')) - d);
-						elem.setAttribute('x2', parseInt(elem.getAttribute('x2')) - d);
-						break;
-					case 't': // top (vertical)
-						var d = Math.min(parseInt(elem.getAttribute('y1')), parseInt(elem.getAttribute('y2'))) - miny;
-						elem.setAttribute('y1', parseInt(elem.getAttribute('y1'))-d);
-						elem.setAttribute('y2', parseInt(elem.getAttribute('y2'))-d);
-						break;
-					case 'm': // middle (vertical)
-						var d = Math.abs(parseInt(elem.getAttribute('y1')) - parseInt(elem.getAttribute('y2')));
-						elem.setAttribute('y1', (miny+maxy-d)/2 );
-						elem.setAttribute('y2', (miny+maxy+d)/2 );
-						break;
-					case 'b': // bottom (vertical)
-						var d = Math.max(parseInt(elem.getAttribute('y1')), parseInt(elem.getAttribute('y2'))) - maxy;
-						elem.setAttribute('y1', parseInt(elem.getAttribute('y1')) - d);
-						elem.setAttribute('y2', parseInt(elem.getAttribute('y2')) - d);
-						break;
-					}
+				case 'r': // right (horizontal)
+					dx[i] = maxx - (bbox.x + bbox.width);
 					break;
-				case 'path':
-					// TODO: implement
+				case 't': // top (vertical)
+					dy[i] = miny - bbox.y;
 					break;
-				case 'polygon':
-					// TODO: implement
+				case 'm': // middle (vertical)
+					dy[i] = (miny+maxy)/2 - (bbox.y + bbox.height/2);
 					break;
-				case 'polyline':
-					// TODO: implement
-					break;
-				case 'text':
-					// TODO: how to handle text?
+				case 'b': // bottom (vertical)
+					dy[i] = maxy - (bbox.y + bbox.height);
 					break;
 			}
 		}
+		this.moveSelectedElements(dx,dy);
 	};
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/firefox-extension/Makefile	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,12 @@
+ZIP = zip
+PACKAGE = svgedit-2.3
+FILEMASK = \*.html \*.css \*.js \*.txt \*.png \*.jpg \*.gif \*.svg \*.xml \*.manifest \*.rdf \*.xul
+
+clean:
+	rm -f *.*~
+	rm -rf ./content/editor/
+
+all: clean
+	rm -f $(PACKAGE).xpi
+	cp -r ../editor ./content/
+	$(ZIP) $(PACKAGE).xpi -r . -i $(FILEMASK)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/firefox-extension/build.sh	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,8 @@
+#!/bin/sh
+DST="content/editor"
+if [ -e "${DST}" ]; then
+    rm -rf "${DST}"
+fi
+cp -R ../editor content/
+SVNS=`find content/editor -name '.svn'`
+rm -rf ${SVNS}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/firefox-extension/chrome.manifest	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,2 @@
+content	SVG-edit	content/
+overlay	chrome://browser/content/browser.xul	chrome://SVG-edit/content/SVG-edit-overlay.xul
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/firefox-extension/content/SVG-edit-overlay.js	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,6 @@
+function start_svg_edit() {
+    var url = "chrome://SVG-edit/content/editor/svg-editor.html";
+
+    window.openDialog(url, "SVG Editor",
+		      "width=1024,height=700,menubar=no,toolbar=no");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/firefox-extension/content/SVG-edit-overlay.xul	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!DOCTYPE overlay SYSTEM "chrome://chromelist/locale/ChromeListOverlay.dtd" >
+<overlay id="SVGEditToolsOverlay"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/x-javascript"
+       src="chrome://SVG-edit/content/SVG-edit-overlay.js" />
+
+<menupopup id="menu_ToolsPopup">
+       <menuitem insertafter="devToolsSeparator" label="SVG Editor"
+       oncommand="start_svg_edit();" />
+</menupopup>
+
+</overlay>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/firefox-extension/handlers.js	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,55 @@
+// 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);
+	    if(readflag)
+		fp.init(window, "Pick a SVG file", fp.modeOpen);
+	    else
+		fp.init(window, "Pick a SVG file", fp.modeSave);
+	    fp.defaultExtension = "*.svg";
+	    fp.show();
+	    return fp.file;
+	}
+	
+	svgCanvas.setCustomHandlers({
+		'open':function() {
+		    try {
+			netscape.security.PrivilegeManager.
+			enablePrivilege("UniversalXPConnect");
+			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.
+					    read(sInputStream.available()));
+		    } catch(e) {
+                        console.log("Exception while attempting to load" + e);
+		    }
+		},
+		'save':function(svg, str) {
+			try {
+				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);
+				out.flush();
+				out.close();
+				} catch(e) {
+				alert(e);
+		    }
+		}
+	});
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/firefox-extension/install.rdf	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+  <Description about="urn:mozilla:extension:file:chrome"
+                   em:package="content" />
+  <Description about="urn:mozilla:install-manifest">
+    <!-- required properties -->
+    <em:id>svg-edit@googlegroups.com</em:id>
+    <em:version>2.2</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>
+      </Description>
+    </em:targetApplication>
+    <em:name>SVG-edit</em:name>
+  </Description>
+</RDF>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/firefox-extension/mk_xpi.sh	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+./build.sh
+zip -r ../svg-edit.xpi *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/minify.sh	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,13 @@
+#!/bin/sh
+YUI=yuicompressor-*.jar
+
+# minify spin button
+java -jar $YUI editor/spinbtn/JQuerySpinBtn.js > editor/spinbtn/JQuerySpinBtn.min.js
+
+# minify SVG-edit files
+java -jar $YUI editor/svg-editor.js > editor/svg-editor.min.js
+java -jar $YUI editor/svgcanvas.js > editor/svgcanvas.min.js
+
+# CSS files do not work remotely
+# java -jar $YUI editor/spinbtn/JQuerySpinBtn.css > editor/spinbtn/JQuerySpinBtn.min.css
+# java -jar $YUI editor/svg-editor.css > editor/svg-editor.min.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/opera-widget/Makefile	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,12 @@
+ZIP = zip
+PACKAGE = svgedit-2.3
+FILEMASK = \*.html \*.css \*.js \*.txt \*.png \*.jpg \*.gif \*.svg \*.xml
+
+clean:
+	rm -f *.*~
+	rm -rf ./editor/
+
+all: clean
+	rm -f $(PACKAGE).wgt
+	cp -r ../editor .
+	$(ZIP) $(PACKAGE).wgt -r . -i $(FILEMASK)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/opera-widget/config.xml	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<widget>
+  <widgetname>SVG Edit</widgetname>
+  <description>
+    A simple SVG Editor.
+  </description>
+  <width>800</width>
+  <height>600</height>
+  <id>
+    <name>SVG Edit</name>
+    <revised>2009-08</revised>
+  </id>
+  <feature name="http://xmlns.opera.com/fileio">
+    <param name="folderhint" value="home" />
+  </feature>
+</widget>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/opera-widget/handlers.js	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,62 @@
+// 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.opera && window.opera.io && window.opera.io.filesystem) {
+		svgCanvas.setCustomHandlers({
+			'open':function() {
+				try {
+					window.opera.io.filesystem.browseForFile(
+						new Date().getTime(), /* mountpoint name */
+						"", /* default location */
+						function(file) {
+							try {
+								if (file) {
+									fstream = file.open(file, "r");
+									var output = "";
+									while (!fstream.eof) {
+										output += fstream.readLine();
+									}
+									
+									svgCanvas.setSvgString(output); /* 'this' is bound to the filestream object here */
+								}
+							}
+							catch(e) {
+								console.log("Reading file failed.");
+							}
+						},
+						false, /* not persistent */
+						false, /* no multiple selections */
+						"*.svg" /* file extension filter */
+					);
+				}
+				catch(e) {
+					console.log("Open file failed.");
+				}
+
+			},
+			'save':function(svg) {
+				try {
+					window.opera.io.filesystem.browseForSave(
+						new Date().getTime(), /* mountpoint name */
+						"", /* default location */
+						function(file) {
+							try {
+								if (file) {
+									var fstream = file.open(file, "w");
+									fstream.write(svg, "UTF-8");
+									fstream.close();
+								}
+							}
+							catch(e) {
+								console.log("Write to file failed.");
+							}
+						}, 
+						false /* not persistent */
+					);
+				}
+				catch(e) {
+					console.log("Save file failed.");
+				}
+			}
+		});
+	}
+});
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/opera-widget/index.html	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,11 @@
+<html>
+	<head>
+		<title>SVG Edit</title>
+		<link rel="stylesheet" type="text/css" href="style.css">
+	</head>
+	<body>
+		<object id="container" data="editor/svg-editor.html">
+			Failed to load for some reason.
+		</object>
+	</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/svg-edit/opera-widget/style.css	Thu Aug 27 23:24:24 2009 +0200
@@ -0,0 +1,2 @@
+body { margin: 0px; padding: 0px; }
+#container { width: 100%; height: 100%; border: none; }
Binary file htdocs/svg-edit/yuicompressor-2.4.2.jar has changed