1 /* 2 * Copyright 2010 Scriptoid s.r.l 3 */ 4 5 6 /** 7 *Stack holds all the figures on the screen 8 *It will also hold the groups formed on the screen 9 *@this {Stack} 10 *@constructor 11 **/ 12 function Stack(){ 13 /**Keeps all the figures on the canvas*/ 14 this.figures = []; 15 16 /**Keeps all the groups in the canvas*/ 17 this.groups = []; 18 19 /**Keeps current generated Id. Not for direct access*/ 20 this.currentId = 0; 21 22 /**Keeps a map like (figure Id, figure index). It is similar to an index*/ 23 this.idToIndex = []; 24 25 /**Type used in serialization*/ 26 this.oType = 'Stack'; 27 } 28 29 30 31 /**Creates a {Stack} out of JSON parsed object 32 *@param {JSONObject} o - the JSON parsed object 33 *@return {Stack} a newly constructed Stack 34 *@author Alex Gheorghiu <alex@scriptoid.com> 35 **/ 36 Stack.load = function(o){ 37 var newStack = new Stack(); //empty constructor 38 39 40 newStack.figures = Figure.loadArray(o.figures); 41 newStack.groups = Group.loadArray(o.groups); 42 newStack.figureSelectedIndex = o.figureSelectedIndex; 43 newStack.currentId = o.currentId; 44 newStack.idToIndex = o.idToIndex; 45 46 return newStack; 47 } 48 49 50 Stack.prototype = { 51 52 53 /**Creates a {Group} based on a set of figure IDs 54 *@param {Array} figureIds - all the ids of {Figure}s 55 *@return {Number} - the id of newly created Group 56 **/ 57 groupCreate:function (figureIds){ 58 59 //we should allow to create more than one temporary group 60 for(var i=0; i<this.groups.length; i++){ 61 if(this.groups[i].permanent == false){ 62 throw 'Stack::groupCreate() You can not create new groups while you have temporary groups alive'; 63 } 64 } 65 66 //create group 67 var g = new Group(); 68 69 //add figures to group 70 for(var i=0;i<figureIds.length; i++){ 71 var f = this.figureGetById(figureIds[i]); 72 f.groupId = g.id; 73 } 74 var bounds = g.getBounds(); 75 g.rotationCoords.push(new Point(bounds[0]+(bounds[2]-bounds[0])/2, bounds[1] + (bounds[3] - bounds[1]) / 2)); 76 g.rotationCoords.push(new Point(bounds[0]+(bounds[2]-bounds[0])/2, bounds[1])); 77 78 //save group to stack 79 this.groups.push(g); 80 81 return g.id; 82 }, 83 84 85 /**Finds a {Group} by it's id 86 *@param {Number} groupId - the {Group}'s id 87 *@return {Group} founded group of null if none finded 88 **/ 89 groupGetById : function(groupId){ 90 for(var i=0; i<this.groups.length; i++){ 91 if(this.groups[i].id == groupId){ 92 return this.groups[i]; 93 } 94 } 95 return null; 96 }, 97 98 /**Destroy a group by it's Id 99 * Also remove the figures from group (figure.groupId = -1) 100 * @param {Number} groupId - the id of the group 101 **/ 102 groupDestroy:function(groupId){ 103 var index = -1; 104 for(var i=0; i<this.groups.length; i++ ){ 105 if(this.groups[i].id == groupId){ 106 index = i; 107 break; 108 } 109 } 110 111 if(index > -1){ 112 //remove Group 113 this.groups.splice(index, 1); 114 115 //remove the Figures from Group 116 var groupFigures = this.figureGetByGroupId(groupId); 117 for(var i=0; i<groupFigures.length; i++ ){ 118 groupFigures[i].groupId = -1; 119 } 120 } 121 }, 122 123 /**See if this stack is equal to another. It is a shallow compare. 124 *@param {Stack} anotherStack - the other stack object 125 *@return {Boolean} - true if equals, false otherwise 126 **/ 127 equals: function(anotherStack){ 128 var msg = ''; 129 if(!anotherStack instanceof Stack){ 130 return false; 131 msg += 'not same class'; 132 } 133 134 135 136 //test figures 137 if(this.figures.length != anotherStack.figures.length){ 138 msg += 'not same nr of figures'; 139 return false; 140 } 141 142 143 for(var i =0; i<this.figures.length; i++){ 144 if( !this.figures[i].equals(anotherStack.figures[i]) ){ 145 msg += 'figures not the same'; 146 return false; 147 } 148 } 149 150 151 152 //test groups 153 if(this.groups.length != anotherStack.groups.length){ 154 msg += 'not same nr of groups'; 155 return false; 156 } 157 158 for(var i =0; i<this.groups.length; i++){ 159 if( !this.groups[i].equals(anotherStack.groups[i]) ){ 160 msg += 'groups not the same'; 161 return false; 162 } 163 } 164 165 166 167 //test idToIndex 168 for(var i =0; i<this.figures.idToIndex; i++){ 169 if(this.idToIndex[i] != undefined //if not undefined 170 && anotherStack.idToIndex != undefined //if not undefined 171 && this.idToIndex[i] != anotherStack.idToIndex[i] ) 172 { 173 174 msg += 'not same idToIndex'; 175 return false; 176 } 177 } 178 179 if(this.currentId != anotherStack.currentId){ 180 msg += 'not same currentId'; 181 return false; 182 } 183 184 if(msg != ''){ 185 alert(msg); 186 } 187 188 return true; 189 190 }, 191 192 /**Generates an returns a new unique ID 193 *@return {Number} - next id*/ 194 generateId:function(){ 195 return this.currentId++; 196 }, 197 198 /**Adds a figure to the Stack of figures 199 *@param {Figure} figure - the figure to add 200 **/ 201 figureAdd:function(figure){ 202 this.figures.push(figure); 203 this.idToIndex[figure.id] = this.figures.length-1; 204 }, 205 206 /*code taken from ConnectionPoint.removeConnector 207 *@param {Figure} figure - the figure to remove 208 *@deprecated 209 **/ 210 figureRemove_deprecated:function(figure){ 211 var index = -1; 212 for(var i=0; i<this.figures.length; i++ ){ 213 if(this.figures[i] == figure){ 214 index = i; 215 break; 216 } 217 } 218 CONNECTOR_MANAGER.connectionPointRemoveAllByParent(figure.id); 219 if(index > -1){ 220 this.figures.splice(index, 1); 221 for(var i=index; i<this.figures.length; i++){ 222 this.idToIndex[this.figures[i].id]=i; 223 } 224 } 225 if(index<this.figureSelectedIndex && index!=-1){ 226 this.figureSelectedIndex--; 227 } 228 }, 229 230 /**Removes a figure by it's id 231 *@param {Number} figId - the {Figure}'s id 232 *@author Alex Gheorghiu <alex@scriptoid.com> 233 **/ 234 figureRemoveById :function(figId){ 235 var index = -1; 236 for(var i=0; i<this.figures.length; i++ ){ 237 if(this.figures[i].id == figId){ 238 index = i; 239 break; 240 } 241 } 242 243 if(index > -1){ 244 //remove figure 245 this.figures.splice(index, 1); 246 247 //reindex 248 this.reindex(); 249 } 250 }, 251 252 253 /**Recreates the index (id, index) 254 *@author Alex Gheorghiu <alex@scriptoid.com> 255 **/ 256 reindex : function(){ 257 for(var i=0; i<this.figures.length; i++){ 258 this.idToIndex[this.figures[i].id] = i; 259 } 260 }, 261 262 /**Deletes all the figure and reset any index*/ 263 reset:function(){ 264 this.figures = []; 265 this.figureSelectedIndex = -1; 266 this.currentId = 0; 267 }, 268 269 /**Find the storage index of a figure 270 *@param {Figure} figure - the figure you search for 271 *@return {Number} - the index where you can find the Figure or -1 if not founded 272 **/ 273 getIndex:function(figure){ 274 for(var i=0; i<this.figures.length; i++){ 275 if(this.figures[i]==figure){ 276 return i; 277 } 278 } 279 return -1; 280 }, 281 282 283 284 /**Returns all figures from a group 285 *@param {Number} groupId - the id of the group 286 *@return {Array} - the {Array} of {Figure}s that belong to the group 287 **/ 288 figureGetByGroupId:function(groupId){ 289 var groupFigures = []; 290 for(var i=0; i<this.figures.length; i++){ 291 if(this.figures[i].groupId == groupId){ 292 groupFigures.push(this.figures[i]); 293 } 294 } 295 296 return groupFigures; 297 }, 298 299 300 /**Returns all figures ids from a group 301 *@param {Number} groupId - the id of the group 302 *@return {Array} - the {Array} of {Number}s that belong to the group 303 **/ 304 figureGetIdsByGroupId:function(groupId){ 305 var groupFiguresIds = []; 306 for(var i=0; i<this.figures.length; i++){ 307 if(this.figures[i].groupId == groupId){ 308 groupFiguresIds.push(this.figures[i].id); 309 } 310 } 311 312 return groupFiguresIds; 313 }, 314 315 /**Returns a figure by id 316 *@param {Number} id - the id of the figure 317 *@return {Figure} - the figure object or null if no figure with that id found 318 *TODO: use idToIndex to speed up the search....well there is no search at all :) 319 **/ 320 figureGetById:function(id){ 321 for(var i=0; i<this.figures.length; i++){ 322 if(this.figures[i].id == id){ 323 return this.figures[i]; 324 } 325 } 326 return null; 327 }, 328 329 330 331 /** 332 *Returns the Figure's id if there is a figure for the given coordinates 333 *It will return the first figure we found from top to bottom (Z-order) 334 *@param {Number} x - the value on Ox axis 335 *@param {Number} y - the value on Ox axis 336 *@return {Number} - the id of the figure or -1 if none found 337 *@author Alex Gheorghiu <alex@scriptoid.com> 338 **/ 339 figureGetByXY:function(x,y){ 340 var id = -1; 341 for(var i= this.figures.length-1; i>=0; i--){ 342 if(this.figures[i].contains(x, y)){ 343 id = this.figures[i].id; 344 break; 345 /* 346 *Some old stuff left from a previous version. 347 *TODO: delete it if no longer needed after the grouping has been (re)done 348 *we always want to get the group of a figure, not the figure itself where possible 349 *(this section only does anything when we change the order of some items 350 *var figure = this.figures[i]; 351 *While the figure belongs to a group or the group is part of another group 352 *we will go up in the ancestor hierarchy 353 * while(figure.groupId >= 0){ 354 * figure = this.figureGetById(figure.groupId); 355 * } 356 return figure; 357 */ 358 } //end if 359 }//end for 360 return id; 361 }, 362 363 364 /* Sets the z index of a figure 365 * @param {Figure} figure - the figure to move 366 * @param {Number} position - the Z index. The bigger value means closer to user (last painted); 367 * @deprecated 368 * */ 369 setPosition_deprecated:function(figure, position){ 370 var figureIndex=-1; 371 for (var i=0; i<this.figures.length; i++){ 372 if(this.figures[i]==figure){ 373 figureIndex=i; 374 } 375 } 376 if(figureIndex!=-1 && position>=0 && position<this.figures.length){ 377 //tempFigures=[]; 378 //tempFigures.spli 379 //if(position<figureIndex){ 380 var tempFigure=this.figures.splice(figureIndex,1); //the figure to move 381 this.figures.splice(position,0,tempFigure[0]); 382 //} 383 var added=false 384 for(var i=0; i<this.figures.length; i++){ 385 this.idToIndex[this.figures[i].id] = i; 386 } 387 this.figureSelectedIndex=position; 388 //this.figures=tempFigures; 389 } 390 }, 391 392 /** Sets the new z position of a currently selected figure (if present) 393 * It actually swap figures. 394 * <p/> 395 * Note: it's just a simple switch between current position and new position 396 * <p/> 397 * Zack: Is it just a switch? All you are doing is swapping, what if the user didn't want to swap, but shift up 398 * using this method, if you have 5 figures, and bring the very back one to the front, the front figure 399 * is moved to the very back, surely the correct solution is to move everything back 1, and move the selected 400 * figure to the front 401 * <p/> 402 * Alex: What you are saying is an insert at a certain position which is not what we want with this method. 403 * Maybe we should rename it swapToPosition(...) or swapIntoPosition(...) 404 * <p/> 405 * @param {Number} figureId - the id of the {Figure} 406 * @param {Number} newPosition - the new Z index of the figure. The bigger the value, close to user (last painted); 407 * @author Alex Gheorghiu <alex@scriptoid.com> 408 409 * */ 410 swapToPosition:function(figureId, newPosition){ 411 var oldPosition = this.idToIndex[figureId]; 412 413 if(oldPosition != -1 /**oldPosition valid*/ 414 && newPosition >= 0 && newPosition < this.figures.length /**newPosition in vector bounds*/){ 415 416 //update idToIndex index 417 this.idToIndex[figureId] = newPosition; 418 this.idToIndex[this.figures[newPosition].id] = oldPosition; 419 420 //switch figures 421 var temp = this.figures[oldPosition]; 422 this.figures[oldPosition] = this.figures[newPosition]; 423 this.figures[newPosition] = temp; 424 } 425 }, 426 427 428 /** 429 *Insert a figure into a position and shifts all other figures 430 *Used by moveToBack and moveToFront, sets the selected figure to the selected position, and rotates all other figures away 431 *@example 432 *[0] = 0 433 *[1] = 1 434 *[2] = 2 435 * 436 *change to 437 * 438 *@example 439 *[0] = 1 440 *[1] = 2 441 *[2] = 0 442 * 443 *@example 444 *figureA 445 *figureB 446 *figureC 447 * 448 *change to 449 * 450 *@example 451 *figureB 452 *figureC 453 *figureA 454 *@param {Number} figureId - the id of the figure 455 *@param {Number} newPosition - the new position of the figure 456 *@author Zack Newsham 457 */ 458 setPosition:function(figureId, newPosition){ 459 //are we moving forward or back? 460 var oldPosition = this.idToIndex[figureId]; 461 var temp = this.figures[oldPosition]; 462 var direction = -1;//move to back 463 if(oldPosition < newPosition){//move to front 464 direction = 1; 465 } 466 Log.info(direction); 467 //while i between oldposition and new position, move 1 in given direction 468 for(var i = oldPosition; i != newPosition; i+=direction){ 469 this.figures[i] = this.figures[i + direction];//set the figure 470 this.idToIndex[this.figures[i].id] = i;//change the index 471 } 472 this.figures[newPosition] = temp; //replace the temp 473 this.idToIndex[this.figures[newPosition].id] = newPosition; 474 }, 475 476 477 /**Test if an (x,y) is over a figure 478 *@param {Number} x - the x coordinates 479 *@param {Number} y - the y coordinates 480 *@return {Boolean} - true if over a figure, false otherwise 481 **/ 482 figureIsOver:function(x, y){ 483 var found = false; 484 for(var i=0; i< this.figures.length; i++){ 485 var figure = this.figures[i]; 486 if(figure.contains(x, y)){ 487 found = true; 488 break; 489 } 490 } 491 return found; 492 }, 493 494 495 /**Paints all {Figure}s from back to top (Z order) 496 *@param {Context} context - the 2D context 497 **/ 498 paint:function(context){ 499 //paint figures 500 for (var i=0; i<this.figures.length; i++) { 501 502 503 if(!context.save){ 504 alert("save() no present") 505 } 506 context.save(); 507 508 this.figures[i].paint(context); 509 context.restore(); 510 511 //if we are connecting something we should pains connection points too 512 if(state == STATE_CONNECTOR_PICK_FIRST || state == STATE_CONNECTOR_PICK_SECOND 513 || state == STATE_CONNECTOR_MOVE_POINT ) 514 { 515 CONNECTOR_MANAGER.connectionPointPaint(context, this.figures[i].id); 516 } 517 518 }//end for 519 520 //paint handlers for selected figure 521 if(state == STATE_FIGURE_SELECTED){ 522 var f = this.figureGetById(selectedFigureId) 523 HandleManager.figureSet(f); 524 //alert('Paint handles'); 525 HandleManager.paint(context); 526 } 527 else if(state == STATE_CONNECTOR_SELECTED){ 528 var c = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId) 529 HandleManager.figureSet(c); 530 HandleManager.paint(context); 531 } 532 //GROUPS 533 else if(state == STATE_GROUP_SELECTED){ 534 var g = this.groupGetById(selectedGroupId) 535 HandleManager.figureSet(g); 536 HandleManager.paint(context); 537 } 538 539 540 CONNECTOR_MANAGER.connectorPaint(context, selectedConnectorId); 541 if(state == STATE_SELECTING_MULTIPLE){ 542 selectionArea.paint(context); 543 Log.info(selectionArea.toString()); 544 } 545 }, 546 547 /**Convert all stack to SVG representation 548 *@return {String} - the SVG string representation*/ 549 toSVG : function(){ 550 var svg = ' '; 551 552 for (var i=0; i<this.figures.length; i++) { 553 svg += this.figures[i].toSVG(); 554 } 555 556 return svg; 557 } 558 } 559 560 561 562 563 564 565