Fix Issue 158: Problems with redo/undo and rotated elements. Refactor of code for Issue 170.

git-svn-id: http://svg-edit.googlecode.com/svn/trunk@553 eee81c28-f429-11dd-99c0-75d572ba1ddd
master
Jeff Schiller 2009-09-04 05:10:48 +00:00
parent 9ead34fca1
commit c6847f23ad
1 changed files with 123 additions and 38 deletions

View File

@ -37,6 +37,9 @@ var svgWhiteList = {
"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"], "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 // These command objects are used for the Undo/Redo stack
// attrs contains the values that the attributes had before the change // attrs contains the values that the attributes had before the change
function ChangeElementCommand(elem, attrs, text) { function ChangeElementCommand(elem, attrs, text) {
@ -60,6 +63,19 @@ function ChangeElementCommand(elem, attrs, text) {
else this.elem.removeAttribute(attr); else this.elem.removeAttribute(attr);
} }
} }
// relocate rotational transform, if necessary
if (attr != "transform") {
var angle = canvas.getRotationAngle(elem);
if (angle) {
var bbox = elem.getBBox();
var cx = bbox.x + bbox.width/2,
cy = bbox.y + bbox.height/2;
var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
if (rotate != elem.getAttribute("transform")) {
elem.setAttribute("transform", rotate);
}
}
}
return true; return true;
}; };
@ -74,6 +90,19 @@ function ChangeElementCommand(elem, attrs, text) {
else this.elem.removeAttribute(attr); else this.elem.removeAttribute(attr);
} }
} }
// relocate rotational transform, if necessary
if (attr != "transform") {
var angle = canvas.getRotationAngle(elem);
if (angle) {
var bbox = elem.getBBox();
var cx = bbox.x + bbox.width/2,
cy = bbox.y + bbox.height/2;
var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
if (rotate != elem.getAttribute("transform")) {
elem.setAttribute("transform", rotate);
}
}
}
return true; return true;
}; };
@ -170,8 +199,6 @@ function BatchCommand(text) {
this.isEmpty = function() { return this.stack.length == 0; }; this.isEmpty = function() { return this.stack.length == 0; };
} }
function SvgCanvas(c)
{
// private members // private members
// ************************************************************************************** // **************************************************************************************
@ -1300,7 +1327,6 @@ function SvgCanvas(c)
case "line": case "line":
started = true; started = true;
var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width; var stroke_w = cur_shape.stroke_width == 0?1:cur_shape.stroke_width;
console.log(stroke_w);
addSvgElementFromJson({ addSvgElementFromJson({
"element": "line", "element": "line",
"attr": { "attr": {
@ -1409,7 +1435,8 @@ function SvgCanvas(c)
break; break;
case "rotate": case "rotate":
started = true; started = true;
canvas.setRotationAngle(canvas.getRotationAngle()); // we are starting an undoable change (a drag-rotation)
canvas.beginUndoableChange("transform", selectedElements);
break; break;
default: default:
console.log("Unknown mode in mousedown: " + current_mode); console.log("Unknown mode in mousedown: " + current_mode);
@ -1718,10 +1745,9 @@ function SvgCanvas(c)
} }
break; break;
case "rotate": case "rotate":
canvas.dragging = true;
var box = canvas.getBBox(selected),cx = box.x + box.width/2, cy = box.y + box.height/2; var box = canvas.getBBox(selected),cx = box.x + box.width/2, cy = box.y + box.height/2;
var angle = parseInt(((Math.atan2(cy-y,cx-x) * (180/Math.PI))-90) % 360); var angle = parseInt(((Math.atan2(cy-y,cx-x) * (180/Math.PI))-90) % 360);
canvas.setRotationAngle(angle<-180?(360+angle):angle); canvas.setRotationAngle(angle<-180?(360+angle):angle, true);
break; break;
default: default:
break; break;
@ -1811,7 +1837,6 @@ function SvgCanvas(c)
started = false; started = false;
var element = svgdoc.getElementById(getId()); var element = svgdoc.getElementById(getId());
var keep = false; var keep = false;
canvas.dragging = false;
switch (current_mode) switch (current_mode)
{ {
// intentionally fall-through to select here // intentionally fall-through to select here
@ -2190,6 +2215,11 @@ function SvgCanvas(c)
keep = true; keep = true;
element = null; element = null;
current_mode = "select"; current_mode = "select";
canvas.setRotationAngle(canvas.getRotationAngle(), true);
var batchCmd = canvas.finishUndoableChange();
if (!batchCmd.isEmpty()) {
addCommandToHistory(batchCmd);
}
break; break;
default: default:
console.log("Unknown mode in mouseup: " + current_mode); console.log("Unknown mode in mouseup: " + current_mode);
@ -2563,15 +2593,19 @@ function SvgCanvas(c)
return 0; return 0;
}; };
this.setRotationAngle = function(val) { this.setRotationAngle = function(val,preventUndo) {
var elem = selectedElements[0]; var elem = selectedElements[0];
// we use the actual element's bbox (not the calculated one) since the // we use the actual element's bbox (not the calculated one) since the
// calculated bbox's center can change depending on the angle // calculated bbox's center can change depending on the angle
var bbox = elem.getBBox(); //this.getBBox(elem); var bbox = elem.getBBox();
var rotate = "rotate(" + val + " " + var cx = (bbox.x+bbox.width/2), cy = (bbox.y+bbox.height/2);
(bbox.x+bbox.width/2) + "," + var rotate = "rotate(" + val + " " + cx + "," + cy + ")";
(bbox.y+bbox.height/2) + ")"; if (preventUndo) {
this.changeSelectedAttribute("transform", rotate); this.changeSelectedAttributeNoUndo("transform", rotate, selectedElements);
}
else {
this.changeSelectedAttribute("transform",rotate,selectedElements);
}
var pointGripContainer = document.getElementById("polypointgrip_container"); var pointGripContainer = document.getElementById("polypointgrip_container");
if(elem.nodeName == "path" && pointGripContainer) { if(elem.nodeName == "path" && pointGripContainer) {
pointGripContainer.setAttribute("transform", rotate); pointGripContainer.setAttribute("transform", rotate);
@ -2684,27 +2718,49 @@ function SvgCanvas(c)
return clone; return clone;
} }
// If you want to change all selectedElements, ignore the elems argument. // New functions for refactoring of Undo/Redo
// If you want to change only a subset of selectedElements, then send the
// subset to this function in the elems argument. // this is the stack that stores the original values, the elements and
this.changeSelectedAttribute = function(attr, val, elems) { // the attribute name for begin/finish
var elems = elems || selectedElements; var undoChangeStackPointer = -1;
var batchCmd = new BatchCommand("Change " + attr); 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 i = elems.length;
var handle = svgroot.suspendRedraw(1000); var oldValues = new Array(i), elements = new Array(i);
while(i--) { while (i--) {
var elem = elems[i]; var elem = elems[i];
if (elem == null) continue; if (elem == null) continue;
elements[i] = elem;
var oldval = (attr == "#text" ? elem.textContent : elem.getAttribute(attr)); oldValues[i] = elem.getAttribute(attrName);
// if the attribute was currently not set, store an empty string (cannot store null) }
if (oldval == null) { oldval = ""; } undoableChangeStack[p] = {'attrName': attrName,
if (oldval != val) { '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 i = elems.length;
while (i--) {
var elem = elems[i];
if (elem == null) continue;
var oldval = attr == "#text" ? elem.textContent : elem.getAttribute(attr);
if (oldval == null) oldval = "";
if (oldval != newValue) {
if (attr == "#text") { if (attr == "#text") {
elem.textContent = val; elem.textContent = newValue;
elem = canvas.quickClone(elem); elem = canvas.quickClone(elem);
} }
else elem.setAttribute(attr, val); else elem.setAttribute(attr, newValue);
selectedBBoxes[i] = this.getBBox(elem); selectedBBoxes[i] = this.getBBox(elem);
// Use the Firefox quickClone hack for text elements with gradients or // Use the Firefox quickClone hack for text elements with gradients or
// where other text attributes are changed. // where other text attributes are changed.
@ -2717,12 +2773,8 @@ function SvgCanvas(c)
setTimeout(function() { setTimeout(function() {
selectorManager.requestSelector(elem).resize(selectedBBoxes[i]); selectorManager.requestSelector(elem).resize(selectedBBoxes[i]);
},0); },0);
var changes = {};
changes[attr] = oldval;
// if this element was rotated, and we changed the position of this element // 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 // we need to update the rotational transform attribute
// of our changeset
var angle = canvas.getRotationAngle(elem); var angle = canvas.getRotationAngle(elem);
if (angle && attr != "transform") { if (angle && attr != "transform") {
var cx = selectedBBoxes[i].x + selectedBBoxes[i].width/2, var cx = selectedBBoxes[i].x + selectedBBoxes[i].width/2,
@ -2730,16 +2782,49 @@ function SvgCanvas(c)
var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join(''); var rotate = ["rotate(", angle, " ", cx, ",", cy, ")"].join('');
if (rotate != elem.getAttribute("transform")) { if (rotate != elem.getAttribute("transform")) {
elem.setAttribute("transform", rotate); elem.setAttribute("transform", rotate);
changes['transform'] = rotate;
} }
} }
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes, attr)); } // 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));
} }
} }
svgroot.unsuspendRedraw(handle); 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;
canvas.beginUndoableChange(attr, elems);
var i = elems.length;
canvas.changeSelectedAttributeNoUndo(attr, val, elems);
var batchCmd = canvas.finishUndoableChange();
if (!batchCmd.isEmpty()) { if (!batchCmd.isEmpty()) {
if(!canvas.dragging) addCommandToHistory(batchCmd); addCommandToHistory(batchCmd);
call("changed", elems);
} }
}; };