You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
973 lines
40 KiB
Plaintext
973 lines
40 KiB
Plaintext
11 months ago
|
/**
|
||
|
*Handles are created on-the-fly for a figure. They are completelly managed by the HandleManager
|
||
|
*Each handle is responsable for an action. The {Handle} does not need to keep a reference to the parent shape
|
||
|
*as the HandleManager will do that.
|
||
|
*@constructor
|
||
|
*@this {Handle}
|
||
|
*@param {String} type - the type of handle
|
||
|
**/
|
||
|
function Handle(type){
|
||
|
/**Type of Handle*/
|
||
|
this.type = type;
|
||
|
|
||
|
/*These are stupidly initialized to 0 but they should not be present at all...
|
||
|
*anyway they got set to the proper values in HandleManager::handleGetAll() function*/
|
||
|
|
||
|
/**The center of the circle (x coordinates)*/
|
||
|
this.x = 0;
|
||
|
|
||
|
/**The center of the circle (y coordinates)*/
|
||
|
this.y = 0;
|
||
|
|
||
|
/**Used by Connector handles, to not display redundant handles (i.e. when they are on the same line)*/
|
||
|
this.visible = true;
|
||
|
}
|
||
|
|
||
|
/**It's a (static) vector of handle types
|
||
|
* Note: R - stand for rotation
|
||
|
* Note: More handles might be added in the future : like handles to increase the number of edges for a hexagone
|
||
|
* Those handles will be specific for a figure
|
||
|
**/
|
||
|
Handle.types = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'r' ]; //DO NOT CHANGE THE ORDER OF THESE VALUES
|
||
|
|
||
|
/**It's a (static) vector of connector types*/
|
||
|
Handle.connectorTypes = ['ns', 'ew'];
|
||
|
|
||
|
/**Creates a {Handle} out of JSON parsed object
|
||
|
*@param {JSONObject} o - the JSON parsed object
|
||
|
*@return {Handle} a newly constructed Handle
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
**/
|
||
|
Handle.load = function(o){
|
||
|
var newHandle = new Handle(o.type);
|
||
|
newHandle.x = o.x;
|
||
|
newHandle.y = o.y;
|
||
|
newHandle.visible = o.visible;
|
||
|
return newHandle;
|
||
|
}
|
||
|
|
||
|
/**Creates an array of handles from an array of {JSONObject}s
|
||
|
*@param {Array} v - the array of JSONObjects
|
||
|
*@return an {Array} of {Handle}s
|
||
|
*@author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
**/
|
||
|
Handle.loadArray = function(v){
|
||
|
var newHandles = [];
|
||
|
for(var i=0; i< v.length; i++){
|
||
|
newHandles.push(Handle.load(v[i]));
|
||
|
}
|
||
|
return newHandles;
|
||
|
}
|
||
|
|
||
|
/**Default handle radius*/
|
||
|
Handle.RADIUS = 3;
|
||
|
|
||
|
Handle.prototype = {
|
||
|
|
||
|
constructor : Handle,
|
||
|
|
||
|
/**Compares to another Handle
|
||
|
*@param {Handle} group - - the other glue
|
||
|
*@return {Boolean} - true if equals, false otherwise
|
||
|
**/
|
||
|
equals : function(anotherHandle){
|
||
|
if(!anotherHandle instanceof Handle){
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return this.type == anotherHandle.type
|
||
|
&& this.x == anotherHandle.x
|
||
|
&& this.y == anotherHandle.y
|
||
|
&& this.visible == anotherHandle.visible;
|
||
|
},
|
||
|
|
||
|
|
||
|
actionFigure : function(lastMove, newX, newY){
|
||
|
var m = this.actionShape(lastMove, newX, newY);
|
||
|
if(m[0] == 'rotate'){
|
||
|
var cmdRotate = new FigureRotateCommand(HandleManager.shape.id, m[1], m[2]);
|
||
|
cmdRotate.execute();
|
||
|
History.addUndo(cmdRotate);
|
||
|
} else if(m[0] == 'scale'){
|
||
|
var cmdScale = new FigureScaleCommand(HandleManager.shape.id, m[1], m[2]);
|
||
|
cmdScale.execute();
|
||
|
History.addUndo(cmdScale);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
actionContainer : function(lastMove, newX, newY){
|
||
|
var m = this.actionShape(lastMove, newX, newY);
|
||
|
if(m[0] == 'rotate'){
|
||
|
//simply ingnore rotate
|
||
|
throw "Handles.js -> actionContainer -> rotate should be disabled for Container"
|
||
|
// var cmdRotate = new ContainerRotateCommand(HandleManager.shape.id, m[1], m[2]);
|
||
|
// cmdRotate.execute();
|
||
|
// History.addUndo(cmdRotate);
|
||
|
} else if(m[0] == 'scale'){
|
||
|
var cmdScale = new ContainerScaleCommand(HandleManager.shape.id, m[1], m[2]);
|
||
|
cmdScale.execute();
|
||
|
History.addUndo(cmdScale);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
actionGroup : function(lastMove, newX, newY){
|
||
|
var m = this.actionShape(lastMove, newX, newY);
|
||
|
if(m[0] == 'rotate'){
|
||
|
var cmdRotate = new GroupRotateCommand(HandleManager.shape.id, m[1], m[2]);
|
||
|
cmdRotate.execute();
|
||
|
History.addUndo(cmdRotate);
|
||
|
} else if(m[0] == 'scale'){
|
||
|
var cmdScale = new GroupScaleCommand(HandleManager.shape.id, m[1], m[2]);
|
||
|
cmdScale.execute();
|
||
|
History.addUndo(cmdScale);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Handle actions for Figure
|
||
|
*
|
||
|
*@param {Array } lastMove - an array that will hold [x,y] of last x & y coordinates
|
||
|
*@param {Number} newX - new X coordinate
|
||
|
*@param {Number} newY - new Y coordinate
|
||
|
*@return {Array} of two matrixes direct and revers
|
||
|
**/
|
||
|
actionShape: function(lastMove, newX, newY){
|
||
|
var matrixes = [];
|
||
|
|
||
|
var figBounds = HandleManager.shape.getBounds();
|
||
|
|
||
|
var transX = 0; //the amount of translation on Ox
|
||
|
var transY = 0; //the amount of translation on Oy
|
||
|
var scaleX = 1; //the scale percentage on Ox
|
||
|
var scaleY = 1; //the scale percentage on Oy
|
||
|
var arc = false;
|
||
|
|
||
|
//see if we have a resize and prepare the figure by moving it back to Origin and "unrotate" it
|
||
|
if(this.type == 'r'){
|
||
|
//rotationCoords[0] is always the center of the shape, we clone it as when we do -rotationCoords[0].x, it is set to 0.
|
||
|
var center = HandleManager.shape.rotationCoords[0].clone();
|
||
|
var endAngle = Util.getAngle(HandleManager.shape.rotationCoords[0],new Point(newX,newY));
|
||
|
var startAngle = Util.getAngle(HandleManager.shape.rotationCoords[0],HandleManager.shape.rotationCoords[1]);//new Point(lastMove[0],lastMove[1])
|
||
|
var rotAngle = endAngle - startAngle;
|
||
|
|
||
|
|
||
|
// HandleManager.shape.transform(Matrix.translationMatrix(-center.x, -center.y))
|
||
|
// HandleManager.shape.transform(Matrix.rotationMatrix(rotAngle))
|
||
|
// HandleManager.shape.transform(Matrix.translationMatrix(center.x, center.y));
|
||
|
|
||
|
var equivTransfMatrix = Matrix.mergeTransformations(
|
||
|
Matrix.translationMatrix(-center.x, -center.y),
|
||
|
Matrix.rotationMatrix(rotAngle),
|
||
|
Matrix.translationMatrix(center.x,center.y)
|
||
|
);
|
||
|
|
||
|
|
||
|
//TODO: make somehow to compute the inverse of it.
|
||
|
//@see http://en.wikipedia.org/wiki/Transformation_matrix#Rotation to find inverses
|
||
|
|
||
|
var inverseTransfMatrix = Matrix.mergeTransformations(
|
||
|
Matrix.translationMatrix(-center.x, -center.y),
|
||
|
Matrix.rotationMatrix(-rotAngle),
|
||
|
Matrix.translationMatrix(center.x,center.y)
|
||
|
);
|
||
|
|
||
|
matrixes = ['rotate', equivTransfMatrix, inverseTransfMatrix];
|
||
|
}
|
||
|
else{ //if not "rotate" (figure), "updown", "leftright" (connector)
|
||
|
//find the angle by which the figure was rotated (for any figure this is initally 0 so if != than 0 we have a rotation)
|
||
|
var angle = Util.getAngle(HandleManager.shape.rotationCoords[0], HandleManager.shape.rotationCoords[1]);
|
||
|
|
||
|
//save initial figure's center
|
||
|
var oldCenter = HandleManager.shape.rotationCoords[0].clone();
|
||
|
|
||
|
|
||
|
|
||
|
//move the new [x,y] to the "un-rotated" and "un-translated" space
|
||
|
var p = new Point(newX,newY);
|
||
|
p.transform(Matrix.translationMatrix(-oldCenter.x,-oldCenter.y));
|
||
|
p.transform(Matrix.rotationMatrix(-angle));
|
||
|
p.transform(Matrix.translationMatrix(oldCenter.x,oldCenter.y));
|
||
|
newX = p.x;
|
||
|
newY = p.y;
|
||
|
|
||
|
var handlerPoint=new Point(this.x,this.y) //Handler's center point (used to draw it's circle)
|
||
|
//rotate that as well.
|
||
|
handlerPoint.transform(Matrix.translationMatrix(-oldCenter.x,-oldCenter.y));
|
||
|
handlerPoint.transform(Matrix.rotationMatrix(-angle));
|
||
|
handlerPoint.transform(Matrix.translationMatrix(oldCenter.x,oldCenter.y));
|
||
|
|
||
|
|
||
|
|
||
|
switch(this.type){
|
||
|
case 'n':
|
||
|
/*move the xOy coodinates at the bottom of the figure and then scale*/
|
||
|
transY = figBounds[3];
|
||
|
if(newY < figBounds[3]-5){ //North must not get too close to South
|
||
|
scaleY = (figBounds[3]-newY)/(figBounds[3] - handlerPoint.y);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 's':
|
||
|
/*move the xOy coodinates at the top of the figure (superfluous as we are there already) and then scale*/
|
||
|
transY = figBounds[1];
|
||
|
if(newY > figBounds[1]+5){ //South must not get too close to North
|
||
|
scaleY = (newY-figBounds[1])/(handlerPoint.y-figBounds[1]);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'w':
|
||
|
/*move the xOy coordinates at the right of the figure and then scale*/
|
||
|
transX = figBounds[2];
|
||
|
if(newX < figBounds[2]-5){ //West(newX) must not get too close to East(figBounds[2])
|
||
|
scaleX = (figBounds[2]-newX)/(figBounds[2]-handlerPoint.x);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'e':
|
||
|
/*move the xOy coodinates at the left of the figure (superfluous as we are there already) and then scale*/
|
||
|
transX = figBounds[0];
|
||
|
if(newX > figBounds[0]+5){
|
||
|
scaleX = (newX-figBounds[0])/(handlerPoint.x-figBounds[0]);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'nw':
|
||
|
/*You can think as a combined North and West action*/
|
||
|
transX = figBounds[2];
|
||
|
transY = figBounds[3];
|
||
|
if(newX<figBounds[2]-5 && newY<figBounds[3]-5){
|
||
|
scaleY = (figBounds[3]-newY) /(figBounds[3]-handlerPoint.y);
|
||
|
scaleX = (figBounds[2]-newX) / (figBounds[2]-handlerPoint.x);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'ne':
|
||
|
transX = figBounds[0]
|
||
|
transY = figBounds[3];
|
||
|
if(newX>figBounds[0]+5 && newY<figBounds[3]-5){
|
||
|
scaleX = (newX-figBounds[0])/(handlerPoint.x-figBounds[0]);
|
||
|
scaleY = (figBounds[3]-newY)/(figBounds[3]-handlerPoint.y);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'sw':
|
||
|
transX = figBounds[2]
|
||
|
transY = figBounds[1];
|
||
|
if(newX<figBounds[2]-5 && newY>figBounds[1]+5){
|
||
|
scaleX = (figBounds[2]-newX)/((figBounds[2]-handlerPoint.x));
|
||
|
scaleY = (newY-figBounds[1])/(handlerPoint.y-figBounds[1]);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'se':
|
||
|
transX = figBounds[0];
|
||
|
transY = figBounds[1];
|
||
|
if(newX>figBounds[0]+5 && newY>figBounds[1]+5){
|
||
|
scaleY= (newY-figBounds[1]) / (handlerPoint.y-figBounds[1]);
|
||
|
scaleX= (newX-figBounds[0]) / (handlerPoint.x-figBounds[0]);
|
||
|
}
|
||
|
break;
|
||
|
}//end switch
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/*By default the NW, NE, SW and SE are scalling keeping the ratio
|
||
|
*but you can use SHIFT to cause a free (no keep ratio) change
|
||
|
*So, if no SHIFT pressed we force a "keep ration" resize
|
||
|
**/
|
||
|
if(!SHIFT_PRESSED && transX != 0 && transY != 0){//keep ratios, only affects ne/nw resize
|
||
|
|
||
|
//if we are scaling along the x axis (West or East resize), with an arc(behaves like corner) then scale relative to x movement
|
||
|
//TODO: what's the reason for this?
|
||
|
if(this.getCursor()=="w-resize" || this.getCursor()=="e-resize"){
|
||
|
scaleY = scaleX;
|
||
|
}
|
||
|
else { //for everything else, scale based on y
|
||
|
scaleX = scaleY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//move the figure to origine and "unrotate" it
|
||
|
var matrixToOrigin = Matrix.mergeTransformations(
|
||
|
Matrix.translationMatrix(-oldCenter.x,-oldCenter.y),
|
||
|
Matrix.rotationMatrix(-angle),
|
||
|
Matrix.translationMatrix(oldCenter.x,oldCenter.y)
|
||
|
);
|
||
|
|
||
|
// HandleManager.shape.transform(Matrix.translationMatrix(-oldCenter.x,-oldCenter.y));
|
||
|
// HandleManager.shape.transform(Matrix.rotationMatrix(-angle));
|
||
|
// HandleManager.shape.transform(Matrix.translationMatrix(oldCenter.x,oldCenter.y));
|
||
|
//
|
||
|
// //scale matrix
|
||
|
// HandleManager.shape.transform(Matrix.translationMatrix(-transX, -transY));
|
||
|
// HandleManager.shape.transform(Matrix.scaleMatrix(scaleX, scaleY))
|
||
|
// HandleManager.shape.transform(Matrix.translationMatrix(transX, transY));
|
||
|
//
|
||
|
//
|
||
|
// //move and rotate the figure back to its original coordinates
|
||
|
// HandleManager.shape.transform(Matrix.translationMatrix(-oldCenter.x,-oldCenter.y));
|
||
|
// HandleManager.shape.transform(Matrix.rotationMatrix(angle));
|
||
|
// HandleManager.shape.transform(Matrix.translationMatrix(oldCenter.x,oldCenter.y));
|
||
|
// HandleManager.shape.transform(directMatrix);
|
||
|
|
||
|
//scale matrix
|
||
|
var scaleMatrix = Matrix.mergeTransformations(
|
||
|
Matrix.translationMatrix(-transX, -transY),
|
||
|
Matrix.scaleMatrix(scaleX, scaleY),
|
||
|
Matrix.translationMatrix(transX, transY)
|
||
|
);
|
||
|
|
||
|
var unscaleMatrix = Matrix.mergeTransformations(
|
||
|
Matrix.translationMatrix(-transX, -transY),
|
||
|
Matrix.scaleMatrix(1/scaleX, 1/scaleY),
|
||
|
Matrix.translationMatrix(transX, transY)
|
||
|
);
|
||
|
|
||
|
//move and rotate the figure back to its original coordinates
|
||
|
var matrixBackFromOrigin = Matrix.mergeTransformations(
|
||
|
Matrix.translationMatrix(-oldCenter.x,-oldCenter.y),
|
||
|
Matrix.rotationMatrix(angle),
|
||
|
Matrix.translationMatrix(oldCenter.x,oldCenter.y)
|
||
|
);
|
||
|
|
||
|
var directMatrix = Matrix.mergeTransformations(matrixToOrigin, scaleMatrix, matrixBackFromOrigin);
|
||
|
var reverseMatrix = Matrix.mergeTransformations(matrixToOrigin, unscaleMatrix, matrixBackFromOrigin);
|
||
|
|
||
|
|
||
|
matrixes = ['scale', directMatrix, reverseMatrix];
|
||
|
|
||
|
|
||
|
} //end else
|
||
|
|
||
|
return matrixes;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
*Handle actions for Connector
|
||
|
*
|
||
|
*@param {Array } lastMove - an array that will hold [x,y] of last x & y coordinates
|
||
|
*@param {Number} newX - new X coordinate
|
||
|
*@param {Number} newY - new Y coordinate
|
||
|
**/
|
||
|
actionConnector: function(lastMove, newX, newY){
|
||
|
switch(this.type){
|
||
|
case 'v':
|
||
|
var index;
|
||
|
//find the two turning points this handle is in between
|
||
|
for(var i = 1; i < HandleManager.shape.turningPoints.length-1; i++){
|
||
|
if(HandleManager.shape.turningPoints[i-1].y == HandleManager.shape.turningPoints[i].y
|
||
|
&& HandleManager.shape.turningPoints[i].y == this.y
|
||
|
&& Math.min(HandleManager.shape.turningPoints[i].x, HandleManager.shape.turningPoints[i-1].x) <= this.x
|
||
|
&& Math.max(HandleManager.shape.turningPoints[i].x, HandleManager.shape.turningPoints[i-1].x) >= this.x)
|
||
|
{
|
||
|
index = i;
|
||
|
}
|
||
|
}
|
||
|
var deltaY = newY - lastMove[1]; //Take changes on Oy
|
||
|
var translationMatrix = Matrix.translationMatrix(0, deltaY); //Generate translation matrix
|
||
|
|
||
|
/*TODO: make changes to DIAGRAMO.debugSolutions here
|
||
|
* because, otherwise, those changes are not reflected in debug painting of Connector
|
||
|
*/
|
||
|
//Pick turning points neighbours and translate them
|
||
|
HandleManager.shape.turningPoints[index-1].transform(translationMatrix);
|
||
|
HandleManager.shape.turningPoints[index].transform(translationMatrix);
|
||
|
|
||
|
// add new changes in {Connector}
|
||
|
HandleManager.shape.addUserChange({
|
||
|
align: Connector.USER_CHANGE_VERTICAL_ALIGN,
|
||
|
delta: deltaY,
|
||
|
index: index - 1
|
||
|
});
|
||
|
HandleManager.shape.addUserChange({
|
||
|
align: Connector.USER_CHANGE_VERTICAL_ALIGN,
|
||
|
delta: deltaY,
|
||
|
index: index
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case 'h':
|
||
|
var index;
|
||
|
//find the two turning points this handle is in between
|
||
|
for(var i = 1; i < HandleManager.shape.turningPoints.length-1; i++){
|
||
|
if(HandleManager.shape.turningPoints[i-1].x == HandleManager.shape.turningPoints[i].x
|
||
|
&& HandleManager.shape.turningPoints[i].x == this.x
|
||
|
&& Math.min(HandleManager.shape.turningPoints[i].y, HandleManager.shape.turningPoints[i-1].y) <= this.y
|
||
|
&& Math.max(HandleManager.shape.turningPoints[i].y, HandleManager.shape.turningPoints[i-1].y) >= this.y)
|
||
|
{
|
||
|
index = i;
|
||
|
}
|
||
|
}
|
||
|
var deltaX = newX-lastMove[0]; //Take changes on Ox
|
||
|
var translationMatrix = Matrix.translationMatrix(deltaX, 0); //Generate translation matrix
|
||
|
|
||
|
/*TODO: make changes to DIAGRAMO.debugSolution here
|
||
|
* because, otherwise, those changes are not reflected in debug painting of Connector
|
||
|
*/
|
||
|
//Pick turning points neighbours and translate them
|
||
|
HandleManager.shape.turningPoints[index-1].transform(translationMatrix);
|
||
|
HandleManager.shape.turningPoints[index].transform(translationMatrix);
|
||
|
|
||
|
// add new changes in {Connector}
|
||
|
HandleManager.shape.addUserChange({
|
||
|
align: Connector.USER_CHANGE_HORIZONTAL_ALIGN,
|
||
|
delta: deltaX,
|
||
|
index: index - 1
|
||
|
});
|
||
|
HandleManager.shape.addUserChange({
|
||
|
align: Connector.USER_CHANGE_HORIZONTAL_ALIGN,
|
||
|
delta: deltaX,
|
||
|
index: index
|
||
|
});
|
||
|
break;
|
||
|
}
|
||
|
HandleManager.shape.updateMiddleText();
|
||
|
},
|
||
|
|
||
|
/**Handle an action. Simply dispatch to the correct handler
|
||
|
*@param {Array } lastMove - an array that will hold [x,y] of last x & y coordinates
|
||
|
*@param {Number} newX - new X coordinate
|
||
|
*@param {Number} newY - new Y coordinate
|
||
|
**/
|
||
|
action: function(lastMove, newX, newY){
|
||
|
if(lastMove == null || lastMove.length != 2){
|
||
|
throw 'Handle:action() Last move is wrong';
|
||
|
}
|
||
|
if(HandleManager.shape instanceof Connector){
|
||
|
this.actionConnector(lastMove, newX, newY);
|
||
|
}
|
||
|
else if(HandleManager.shape instanceof Figure){
|
||
|
this.actionFigure(lastMove, newX, newY);
|
||
|
}
|
||
|
else if(HandleManager.shape instanceof Group){
|
||
|
this.actionGroup(lastMove, newX, newY);
|
||
|
}
|
||
|
else if(HandleManager.shape instanceof Container){
|
||
|
this.actionContainer(lastMove, newX, newY);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
|
||
|
/**This is the method you have to call to paint a handler
|
||
|
* All handles will be circles...so we avoid to much of the computing for rectangle handles
|
||
|
* They will have a filling color (green) and a stoke (black)
|
||
|
* @param {Context} context - the 2D context
|
||
|
**/
|
||
|
paint : function(context){
|
||
|
context.save();
|
||
|
|
||
|
//fill the handler
|
||
|
context.beginPath();
|
||
|
context.arc(this.x, this.y, Handle.RADIUS, 0, Math.PI*2, false);
|
||
|
context.fillStyle = "rgb(0,255,0)";
|
||
|
context.closePath();
|
||
|
context.fill();
|
||
|
|
||
|
//stroke the handler
|
||
|
context.beginPath();
|
||
|
context.arc(this.x, this.y, Handle.RADIUS, 0, Math.PI*2, false);
|
||
|
context.strokeStyle = "rgb(0,0,0)";
|
||
|
context.lineWidth = defaultThinLineWidth;
|
||
|
context.closePath();
|
||
|
context.stroke();
|
||
|
|
||
|
|
||
|
// if(this.type == 'r'){
|
||
|
// var line = new Line(new Point(this.x,this.y), new Point(HandleManager.handles[1].x,HandleManager.handles[1].y));
|
||
|
// line.style.dashLength = 3;
|
||
|
// line.style.strokeStyle = "grey";
|
||
|
// line.style.lineWidth = defaultThinLineWidth;
|
||
|
// line.paint(context);
|
||
|
// }
|
||
|
|
||
|
// context.restore();
|
||
|
},
|
||
|
|
||
|
|
||
|
/**See if the handle contains a point
|
||
|
*@param {Number} x - the x coordinate of the point
|
||
|
*@param {Number} y - the y coordinate of the point
|
||
|
**/
|
||
|
contains:function(x,y){
|
||
|
var p=new Point(this.x,this.y);
|
||
|
return p.near(x,y, Handle.RADIUS);
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
*Get a handle bounds
|
||
|
**/
|
||
|
getBounds : function(){
|
||
|
return [this.x - Handle.RADIUS, this.y - Handle.RADIUS, this.x + Handle.RADIUS,this.y + Handle.RADIUS];
|
||
|
},
|
||
|
|
||
|
|
||
|
/**
|
||
|
*Transform the Handle through a matrix
|
||
|
*@param {Matrix} matrix - the matrix that will perform the transformation
|
||
|
**/
|
||
|
transform: function(matrix){
|
||
|
var p=new Point(this.x,this.y)
|
||
|
p.transform(matrix);
|
||
|
this.x=p.x;
|
||
|
this.y=p.y;
|
||
|
},
|
||
|
|
||
|
|
||
|
/**Get the specific cursor for this handle. Cursor is ONLY a visual clue for
|
||
|
* the user to know how to move his mouse.
|
||
|
*
|
||
|
*Behaviour:
|
||
|
* If North handle is in the North we have 'Up/Down arrow" cursor
|
||
|
* If North handle is in the West (so it has "Left/Right arrow") (or anything different that North)
|
||
|
* we have 'Left/Right arrow' cursor but the figure will expand as follows:
|
||
|
* - rotate back to initial position
|
||
|
* - expand North
|
||
|
* - rotate back to current position
|
||
|
* - repaint
|
||
|
* @see <a href="http://www.w3schools.com/css/pr_class_cursor.asp">http://www.w3schools.com/css/pr_class_cursor.asp</a> for cusor values
|
||
|
* @author Zack Newsham <zack_newsham@yahoo.co.uk>
|
||
|
* @author Alex Gheorghiu <alex@scriptoid.com>
|
||
|
**/
|
||
|
|
||
|
getCursor:function(){
|
||
|
if(HandleManager.shape instanceof Connector){
|
||
|
if(this.visible == false){
|
||
|
return "";
|
||
|
}
|
||
|
if(this.type == 'v'){
|
||
|
return 'ns-resize';
|
||
|
}
|
||
|
else{
|
||
|
return 'ew-resize';
|
||
|
}
|
||
|
} //end if Connector
|
||
|
else if(HandleManager.shape instanceof Figure ){
|
||
|
if(this.visible == false){
|
||
|
return "";
|
||
|
}
|
||
|
if(this.type == 'r'){
|
||
|
return 'move';
|
||
|
}
|
||
|
|
||
|
var figureBounds = HandleManager.shape.getBounds(); //get figure's bounds
|
||
|
var figureCenter = new Point(figureBounds[0] + ((figureBounds[2]-figureBounds[0])/2),
|
||
|
(figureBounds[1] + ((figureBounds[3] - figureBounds[1])/2)) ); //get figure's center
|
||
|
|
||
|
//find north
|
||
|
var closestToNorthIndex = -1; //keeps the index of closest handle to North
|
||
|
var minAngleToNorth = 2 * Math.PI; //keeps the smallest (angular) distante to North
|
||
|
var myIndex = -1;
|
||
|
|
||
|
for(var i=0; i<HandleManager.handles.length-1; i++){
|
||
|
var handleCenter = new Point(HandleManager.handles[i].x, HandleManager.handles[i].y);
|
||
|
var angle = Util.getAngle(figureCenter,handleCenter); //get the angle between those 2 points 0=n
|
||
|
|
||
|
if(angle <= Math.PI){ //quadrant I or II
|
||
|
if(angle < minAngleToNorth){
|
||
|
minAngleToNorth = angle;
|
||
|
closestToNorthIndex = i;
|
||
|
}
|
||
|
}
|
||
|
else{ //quadrant III or IV
|
||
|
if(2 * Math.PI - angle < minAngleToNorth){
|
||
|
minAngleToNorth = 2 * Math.PI - angle
|
||
|
closestToNorthIndex = i;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//alert("closest to North is : " + closestToNorthIndex);
|
||
|
for(var k=0; k<8; k++){ //there will always be 8 resize handlers
|
||
|
//we do not use modulo 9 as we want to ignore the "rotate" handle
|
||
|
if(HandleManager.handles[(closestToNorthIndex + k) % 8] == this){
|
||
|
return Handle.types[k]+"-resize";
|
||
|
}
|
||
|
}
|
||
|
//end if Figure
|
||
|
} else if(HandleManager.shape instanceof Container){
|
||
|
if(this.visible == false){
|
||
|
return "";
|
||
|
}
|
||
|
if(this.type == 'r'){
|
||
|
return 'move';
|
||
|
}
|
||
|
|
||
|
var figureBounds = HandleManager.shape.getBounds(); //get figure's bounds
|
||
|
var figureCenter = new Point(figureBounds[0] + ((figureBounds[2]-figureBounds[0])/2),
|
||
|
(figureBounds[1] + ((figureBounds[3] - figureBounds[1])/2)) ); //get figure's center
|
||
|
|
||
|
//find north
|
||
|
var closestToNorthIndex = -1; //keeps the index of closest handle to North
|
||
|
var minAngleToNorth = 2 * Math.PI; //keeps the smallest (angular) distante to North
|
||
|
var myIndex = -1;
|
||
|
|
||
|
for(var i=0; i<HandleManager.handles.length-1; i++){
|
||
|
var handleCenter = new Point(HandleManager.handles[i].x, HandleManager.handles[i].y);
|
||
|
var angle = Util.getAngle(figureCenter,handleCenter); //get the angle between those 2 points 0=n
|
||
|
|
||
|
if(angle <= Math.PI){ //quadrant I or II
|
||
|
if(angle < minAngleToNorth){
|
||
|
minAngleToNorth = angle;
|
||
|
closestToNorthIndex = i;
|
||
|
}
|
||
|
}
|
||
|
else{ //quadrant III or IV
|
||
|
if(2 * Math.PI - angle < minAngleToNorth){
|
||
|
minAngleToNorth = 2 * Math.PI - angle
|
||
|
closestToNorthIndex = i;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//alert("closest to North is : " + closestToNorthIndex);
|
||
|
for(var k=0; k<8; k++){ //there will always be 8 resize handlers
|
||
|
//we do not use modulo 9 as we want to ignore the "rotate" handle
|
||
|
if(HandleManager.handles[(closestToNorthIndex + k) % 8] == this){
|
||
|
return Handle.types[k]+"-resize";
|
||
|
}
|
||
|
}
|
||
|
//end if Figure
|
||
|
} else if(HandleManager.shape instanceof Group){
|
||
|
if(this.visible == false){
|
||
|
return "";
|
||
|
}
|
||
|
// only rotate handle enabled for a group
|
||
|
if(this.type == 'r'){
|
||
|
return 'move';
|
||
|
}
|
||
|
|
||
|
//end if Group
|
||
|
}
|
||
|
|
||
|
return "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**HandleManager will act like a Singleton (even not defined as one)
|
||
|
* You will attach a Figure to it and he will be in charge with the figure manipulation
|
||
|
* @constructor
|
||
|
* @this {HandleManager}
|
||
|
**/
|
||
|
function HandleManager(){
|
||
|
}
|
||
|
|
||
|
/**The shape (figure or connector) that the HandleManager will manage*/
|
||
|
HandleManager.shape = null;
|
||
|
|
||
|
/**An {Array} with current handles*/
|
||
|
HandleManager.handles = [];
|
||
|
|
||
|
/**An {Array} with connector handles*/
|
||
|
HandleManager.connectorHandles = [];
|
||
|
|
||
|
/**Selection rectangle (the rectangle upon the Handles will stay in case of a Figure/Group)*/
|
||
|
HandleManager.selectRect = null;
|
||
|
|
||
|
/**Currently selected handle*/
|
||
|
HandleManager.handleSelectedIndex = -1;
|
||
|
|
||
|
/**Distance from shape where to draw the handles*/
|
||
|
HandleManager.handleOffset = 0;//JS: I want handles to be on figure corners, because then we can correctly see figure with no stroke (was 10)
|
||
|
|
||
|
/**Get selected handle or null if no handler selected*/
|
||
|
HandleManager.handleGetSelected = function(){
|
||
|
if(HandleManager.handleSelectedIndex!=-1){
|
||
|
return HandleManager.handles[HandleManager.handleSelectedIndex];
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**Use this method to set a new shape (Figure or Connetor) to this manager.
|
||
|
* Every time a new figure is set, old handles will dissapear (got erased by new figure's handles)
|
||
|
**/
|
||
|
HandleManager.shapeSet = function (shape) {
|
||
|
HandleManager.shape = shape;
|
||
|
|
||
|
//1. clear old/previous handles
|
||
|
HandleManager.handles = [];
|
||
|
|
||
|
//2. setup/add handles for this figure
|
||
|
if (shape instanceof Connector) {
|
||
|
HandleManager.selectRect = null;
|
||
|
|
||
|
//we don't want to affect the start or end points
|
||
|
for (var i = 1; i < shape.turningPoints.length - 2; i++) {
|
||
|
var h;
|
||
|
|
||
|
/*
|
||
|
*Create a new handle ONLY if previous, current and next turning points are not colinear
|
||
|
**/
|
||
|
if (
|
||
|
/*Previous points are not collinear and next points are either
|
||
|
* non colinear or last 2 coincide (this case appears when dragging).
|
||
|
*
|
||
|
* Basically ensure the segment [i, i+1] is not on same line with
|
||
|
* [i-1, i] or [i+1, i+2]*/
|
||
|
(
|
||
|
!Util.collinearity(HandleManager.shape.turningPoints[i - 1], HandleManager.shape.turningPoints[i], HandleManager.shape.turningPoints[i + 1])
|
||
|
&&
|
||
|
(!Util.collinearity(HandleManager.shape.turningPoints[i], HandleManager.shape.turningPoints[i + 1], HandleManager.shape.turningPoints[i + 2])
|
||
|
|| HandleManager.shape.turningPoints[i + 1].equals(HandleManager.shape.turningPoints[i + 2]) /*Next two coincide? TODO: Do we really need this? Explain*/
|
||
|
)
|
||
|
)
|
||
|
|
||
|
||
|
||
|
/*Previous points are non colinear or first 2 of them coincide and next points are not colinear*/
|
||
|
(
|
||
|
(!Util.collinearity(HandleManager.shape.turningPoints[i - 1], HandleManager.shape.turningPoints[i], HandleManager.shape.turningPoints[i + 1])
|
||
|
|| HandleManager.shape.turningPoints[i - 1].equals(HandleManager.shape.turningPoints[i]) /*Previous two coincide? TODO: Do we really need this? Explain*/)
|
||
|
&&
|
||
|
!Util.collinearity(HandleManager.shape.turningPoints[i], HandleManager.shape.turningPoints[i + 1], HandleManager.shape.turningPoints[i + 2])
|
||
|
)
|
||
|
|
||
|
) {
|
||
|
if (shape.turningPoints[i].x === shape.turningPoints[i + 1].x) { //same vertical
|
||
|
h = new Handle("h");
|
||
|
h.x = HandleManager.shape.turningPoints[i].x;
|
||
|
h.y = (HandleManager.shape.turningPoints[i].y + HandleManager.shape.turningPoints[i + 1].y) / 2;
|
||
|
|
||
|
}
|
||
|
else if (shape.turningPoints[i].y === shape.turningPoints[i + 1].y) { // same horizontal
|
||
|
h = new Handle("v");
|
||
|
h.x = (HandleManager.shape.turningPoints[i].x + HandleManager.shape.turningPoints[i + 1].x) / 2;
|
||
|
h.y = HandleManager.shape.turningPoints[i].y;
|
||
|
}
|
||
|
alert(h);
|
||
|
if (h) { // Did we created a Handle?
|
||
|
h.visible = true; // make it visible
|
||
|
HandleManager.handles.push(h); // add it to HandleManager
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (shape instanceof Figure || shape instanceof Group) {
|
||
|
//find Figure's angle
|
||
|
var angle = Util.getAngle(HandleManager.shape.rotationCoords[0], HandleManager.shape.rotationCoords[1]);
|
||
|
|
||
|
//rotate it back to "normal" space (from current space)
|
||
|
HandleManager.shape.transform(Matrix.rotationMatrix(-angle), false);
|
||
|
HandleManager.selectRect = new Polygon();
|
||
|
|
||
|
//construct bounds of the Figure in "normal" space
|
||
|
var bounds = HandleManager.shape.getBounds();
|
||
|
HandleManager.selectRect.points = [];
|
||
|
HandleManager.selectRect.addPoint(new Point(bounds[0] - HandleManager.handleOffset, bounds[1] - HandleManager.handleOffset)); //top left
|
||
|
HandleManager.selectRect.addPoint(new Point(bounds[2] + HandleManager.handleOffset, bounds[1] - HandleManager.handleOffset)); //top right
|
||
|
HandleManager.selectRect.addPoint(new Point(bounds[2] + HandleManager.handleOffset, bounds[3] + HandleManager.handleOffset)); //bottom right
|
||
|
HandleManager.selectRect.addPoint(new Point(bounds[0] - HandleManager.handleOffset, bounds[3] + HandleManager.handleOffset)); //bottom left
|
||
|
|
||
|
bounds = HandleManager.selectRect.getBounds();
|
||
|
|
||
|
//update current handles
|
||
|
var handle = new Handle("nw"); //NW
|
||
|
handle.x = bounds[0];
|
||
|
handle.y = bounds[1];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("n"); //N
|
||
|
handle.x = bounds[0] + (bounds[2] - bounds[0]) / 2;
|
||
|
handle.y = bounds[1];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("ne"); //NE
|
||
|
handle.x = bounds[2];
|
||
|
handle.y = bounds[1];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("e"); //E
|
||
|
handle.x = bounds[2];
|
||
|
handle.y = bounds[1] + (bounds[3] - bounds[1]) / 2;
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("se"); //SE
|
||
|
handle.x = bounds[2];
|
||
|
handle.y = bounds[3];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("s"); //S
|
||
|
handle.x = bounds[0] + (bounds[2] - bounds[0]) / 2;
|
||
|
handle.y = bounds[3];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("sw"); //SW
|
||
|
handle.x = bounds[0];
|
||
|
handle.y = bounds[3];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("w"); //W
|
||
|
handle.x = bounds[0];
|
||
|
handle.y = bounds[1] + (bounds[3] - bounds[1]) / 2;
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
|
||
|
// handle = new Handle("r"); //Rotation
|
||
|
// handle.x = bounds[0]+(bounds[2]-bounds[0])/2;
|
||
|
// //JS: because handleOffset is 0, we still need to see rotation handle
|
||
|
// if (HandleManager.handleOffset!=0){
|
||
|
// handle.y = bounds[1] - HandleManager.handleOffset * 1.5;
|
||
|
// }else{
|
||
|
// handle.y = bounds[1] - 15;
|
||
|
// }
|
||
|
// HandleManager.handles.push(handle);
|
||
|
|
||
|
|
||
|
HandleManager.selectRect.transform(Matrix.rotationMatrix(angle));
|
||
|
|
||
|
//rotate figure from "normal" space to current space
|
||
|
HandleManager.shape.transform(Matrix.rotationMatrix(angle), false);
|
||
|
if (shape instanceof Figure) {
|
||
|
if (shape.primitives[0] instanceof Text && shape.primitives.length == 1) {
|
||
|
for (var i = 0; i < HandleManager.handles.length; i++) {
|
||
|
HandleManager.handles[i].visible = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//now transform the handles from "normal" space too
|
||
|
for (var i = 0; i < HandleManager.handles.length; i++) {
|
||
|
HandleManager.handles[i].transform(Matrix.rotationMatrix(angle));
|
||
|
}
|
||
|
} else if (shape instanceof Container) {
|
||
|
var bounds = HandleManager.shape.getBounds();
|
||
|
|
||
|
HandleManager.selectRect = new Polygon();
|
||
|
HandleManager.selectRect.points = [];
|
||
|
HandleManager.selectRect.addPoint(new Point(bounds[0] - HandleManager.handleOffset, bounds[1] - HandleManager.handleOffset)); //top left
|
||
|
HandleManager.selectRect.addPoint(new Point(bounds[2] + HandleManager.handleOffset, bounds[1] - HandleManager.handleOffset)); //top right
|
||
|
HandleManager.selectRect.addPoint(new Point(bounds[2] + HandleManager.handleOffset, bounds[3] + HandleManager.handleOffset)); //bottom right
|
||
|
HandleManager.selectRect.addPoint(new Point(bounds[0] - HandleManager.handleOffset, bounds[3] + HandleManager.handleOffset)); //bottom left
|
||
|
|
||
|
bounds = HandleManager.selectRect.getBounds();
|
||
|
|
||
|
//update current handles
|
||
|
var handle = new Handle("nw"); //NW
|
||
|
handle.x = bounds[0];
|
||
|
handle.y = bounds[1];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("n"); //N
|
||
|
handle.x = bounds[0] + (bounds[2] - bounds[0]) / 2;
|
||
|
handle.y = bounds[1];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("ne"); //NE
|
||
|
handle.x = bounds[2];
|
||
|
handle.y = bounds[1];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("e"); //E
|
||
|
handle.x = bounds[2];
|
||
|
handle.y = bounds[1] + (bounds[3] - bounds[1]) / 2;
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("se"); //SE
|
||
|
handle.x = bounds[2];
|
||
|
handle.y = bounds[3];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("s"); //S
|
||
|
handle.x = bounds[0] + (bounds[2] - bounds[0]) / 2;
|
||
|
handle.y = bounds[3];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("sw"); //SW
|
||
|
handle.x = bounds[0];
|
||
|
handle.y = bounds[3];
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
handle = new Handle("w"); //W
|
||
|
handle.x = bounds[0];
|
||
|
handle.y = bounds[1] + (bounds[3] - bounds[1]) / 2;
|
||
|
HandleManager.handles.push(handle);
|
||
|
|
||
|
|
||
|
// handle = new Handle("r"); //Rotation
|
||
|
// handle.x = bounds[0]+(bounds[2]-bounds[0])/2;
|
||
|
// //JS: because handleOffset is 0, we still need to see rotation handle
|
||
|
// if (HandleManager.handleOffset!=0){
|
||
|
// handle.y = bounds[1] - HandleManager.handleOffset * 1.5;
|
||
|
// }else{
|
||
|
// handle.y = bounds[1] - 15;
|
||
|
// }
|
||
|
// HandleManager.handles.push(handle);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**Returns all handles for a shape (figure or connector).
|
||
|
*It does not mean that the HandleManager keeps records of all Handles for a
|
||
|
*Figure but more likely they are computed on-the-fly
|
||
|
*@return an {Array} of {Handle} that you can further use to manage the figure
|
||
|
**/
|
||
|
HandleManager.handleGetAll = function(){
|
||
|
return HandleManager.handles;
|
||
|
}
|
||
|
|
||
|
/**Returns the handle from a certain coordinates
|
||
|
*@param {Number} x - the value on Ox
|
||
|
*@param {Number} y - the value on Oy
|
||
|
***/
|
||
|
HandleManager.handleGet = function(x,y){
|
||
|
for(var i=0; i<HandleManager.handles.length; i++){
|
||
|
if(HandleManager.handles[i].contains(x,y)){
|
||
|
return HandleManager.handles[i];
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*Select the handle from a certain coordinates
|
||
|
*@param {Number} x - the value on Ox
|
||
|
*@param {Number} y - the value on Oy
|
||
|
**/
|
||
|
HandleManager.handleSelectXY = function(x,y){
|
||
|
HandleManager.handleSelectedIndex=-1;
|
||
|
for (var i=0; i<HandleManager.handles.length; i++){
|
||
|
if(HandleManager.handles[i].contains(x,y)){
|
||
|
HandleManager.handleSelectedIndex = i;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*Clear HandleManager.
|
||
|
**/
|
||
|
HandleManager.clear = function(){
|
||
|
HandleManager.handleSelectedIndex = -1;
|
||
|
HandleManager.shape = null;
|
||
|
HandleManager.handles = [];
|
||
|
}
|
||
|
|
||
|
/**Paint the Handles, actually the HandleManager will delegate each paint to
|
||
|
*the proper Handle to paint
|
||
|
*@param {Context} context - the 2D context
|
||
|
**/
|
||
|
HandleManager.paint = function(context){
|
||
|
var handles = HandleManager.handleGetAll(); //calling this sets the coordinates
|
||
|
|
||
|
//paint first the selection rectangle
|
||
|
context.save();
|
||
|
|
||
|
//paint selection rectangle (if present - only for Figure and Group)
|
||
|
if(HandleManager.selectRect != null){
|
||
|
//alert("Handle manager paint!");
|
||
|
HandleManager.selectRect.style.strokeStyle = "grey";
|
||
|
HandleManager.selectRect.style.lineWidth = defaultThinLineWidth;
|
||
|
HandleManager.selectRect.paint(context);
|
||
|
}
|
||
|
|
||
|
//now paint handles
|
||
|
for(var i=0; i<handles.length; i++){
|
||
|
if(handles[i].visible == true){
|
||
|
handles[i].paint(context);
|
||
|
}
|
||
|
}
|
||
|
context.restore()
|
||
|
}
|