1 /** 2 * @fileOverview 3 * @author <a href="mailto:ovaraksin@googlemail.com">Oleg Varaksin</a> 4 * @version 0.2 5 */ 6 7 /** 8 * Whiteboard designer class for element drawing. 9 * @class 10 * @param witeboardConfig whiteboard's configuration {@link WhiteboardConfig} 11 * @param whiteboardId whiteboard's id 12 * @param user user (user name) working with this whiteboard 13 * @param pubSubUrl URL for bidirectional communication 14 * @param pubSubTransport transport protocol "long-polling" | "streaming" | "websocket" 15 */ 16 WhiteboardDesigner = function(witeboardConfig, whiteboardId, user, pubSubUrl, pubSubTransport) { 17 /** 18 * Whiteboard's configuration {@link WhiteboardConfig}. 19 * @public 20 * @type WhiteboardConfig 21 */ 22 this.config = witeboardConfig; 23 /** 24 * Whiteboard's id. 25 * @public 26 * @type uuid 27 */ 28 this.whiteboardId = whiteboardId; 29 /** 30 * User which works with this whiteboard. 31 * @public 32 * @type string 33 */ 34 this.user = user; 35 /** 36 * URL for bidirectional communication. 37 * @public 38 * @type string 39 */ 40 this.pubSubUrl = pubSubUrl; 41 /** 42 * Transport protocol "long-polling" | "streaming" | "websocket". 43 * @public 44 * @type string 45 */ 46 this.pubSubTransport = pubSubTransport; 47 /** 48 * Logging flag, true - logging is visible, false - otherwise. 49 * @public 50 * @type boolean 51 */ 52 this.logging = false; 53 54 // create jQuery objects for whiteboard container and dialogs 55 var whiteboard = jQuery("#" + this.config.ids.whiteboard); 56 var dialogInputText = jQuery("#" + this.config.ids.dialogInputText); 57 var dialogInputImage = jQuery("#" + this.config.ids.dialogInputImage); 58 var dialogIcons = jQuery("#" + this.config.ids.dialogIcons); 59 var dialogResize = jQuery("#" + this.config.ids.dialogResize); 60 61 var offsetLeft = whiteboard.offset().left; 62 var offsetTop = whiteboard.offset().top; 63 64 var idSubviewProperties = "#pinnedSubview"; 65 var dragDropStart = false; 66 var lastHoverObj = null; 67 var selectedObj = null; 68 var wbElements = {}; 69 var _self = this; 70 71 jQuery.extend(whiteboard, { 72 "lineEl": {"path": null, "pathArray": null}, 73 "imageEl": {"cx": 0, "cy": 0}, 74 "iconEl": {"cx": 0, "cy": 0}, 75 "textEl": {"cx": 0, "cy": 0} 76 }); 77 78 /** 79 * Action mode. 80 * @private 81 * @type object 82 */ 83 var modeSwitcher = { 84 "textMode": false, 85 "freeLineMode": false, 86 "straightLineMode": false, 87 "rectangleMode": false, 88 "circleMode": false, 89 "ellipseMode": false, 90 "imageMode": false, 91 "iconMode": false, 92 "selectMode": false, 93 "moveMode": false, 94 "bringFrontMode": false, 95 "bringBackMode": false, 96 "cloneMode": false, 97 "removeMode": false, 98 "clearMode": false, 99 "resizeMode": false 100 }; 101 102 /** 103 * Raphael's canvas. 104 * @private 105 * @type Raphael's paper 106 */ 107 var paper = Raphael(this.config.ids.whiteboard, whiteboard.width(), whiteboard.height()); 108 109 // public access ======================= 110 111 /** Switches the mode if user selects any action (like "Input Text" or "Draw Circle"). 112 * @public 113 * @param mode mode defined in the variable modeSwitcher. 114 * @param cursor cursor type, e.g. "default", "move", "wait". 115 */ 116 this.switchToMode = function(mode, cursor) { 117 for (var name in modeSwitcher) { 118 modeSwitcher[name] = false; 119 } 120 modeSwitcher[mode] = true; 121 whiteboard.css("cursor", cursor); 122 } 123 124 /** Gets currently selected whiteboard element. 125 * @public 126 * @returns {Raphael's element} currently selected element 127 */ 128 this.getSelectedObject = function() { 129 return selectedObj; 130 } 131 132 /** Draws begin point of the free line and registers mouse handlers. 133 * @public 134 * @param x X-coordinate. 135 * @param y Y-coordinate. 136 */ 137 this.drawFreeLineBegin = function(x, y) { 138 whiteboard.lineEl.path = paper.path("M" + (x - offsetLeft) + "," + (y - offsetTop)); 139 setElementProperties(whiteboard.lineEl.path, this.config.properties.freeLine); 140 whiteboard.bind("mousemove.mmu", mousemoveHandler); 141 whiteboard.one("mouseup.mmu", mouseupHandler); 142 } 143 144 /** Draws begin point of the straight line and registers mouse handlers. 145 * @public 146 * @param x X-coordinate. 147 * @param y Y-coordinate. 148 */ 149 this.drawStraightLineBegin = function(x, y) { 150 whiteboard.lineEl.pathArray = []; 151 whiteboard.lineEl.pathArray[0] = ["M", x - offsetLeft, y - offsetTop]; 152 whiteboard.lineEl.path = paper.path(whiteboard.lineEl.pathArray); 153 setElementProperties(whiteboard.lineEl.path, this.config.properties.straightLine); 154 whiteboard.bind("mousemove.mmu", mousemoveHandler); 155 whiteboard.one("mouseup.mmu", mouseupHandler); 156 } 157 158 /** Draws text with default properties. 159 * @public 160 * @param x text message. 161 */ 162 this.drawText = function(inputText) { 163 if (inputText !== "") { 164 var textElement = paper.text(whiteboard.textEl.cx, whiteboard.textEl.cy, inputText); 165 var realTextProps = jQuery.extend({}, this.config.properties.text, {"text": inputText}); 166 setElementProperties(textElement, realTextProps); 167 var hb = drawHelperBox(textElement, this.config.classTypes.text, this.config.properties.text.rotation, null, true, null); 168 wbElements[hb.uuid] = hb; 169 170 // send changes to server 171 this.sendChanges({ 172 "action": "create", 173 "element": { 174 "type": this.config.classTypes.text, 175 "properties": { 176 "uuid": hb.uuid, 177 "x": whiteboard.textEl.cx, 178 "y": whiteboard.textEl.cy, 179 "rotationDegree": this.config.properties.text.rotation, 180 "text": inputText, 181 "fontFamily": textElement.attr("font-family"), 182 "fontSize": textElement.attr("font-size"), 183 "fontWeight": textElement.attr("font-weight"), 184 "fontStyle": textElement.attr("font-style"), 185 "color": textElement.attr("fill") 186 } 187 } 188 }); 189 190 this.showProperties('editText'); 191 this.transferTextPropertiesToDialog(whiteboard.textEl.cx, whiteboard.textEl.cy, realTextProps); 192 } 193 } 194 195 /** Draws image with default properties. 196 * @public 197 * @param inputUrl image URL. 198 * @param width image width. 199 * @param height image height. 200 */ 201 this.drawImage = function(inputUrl, width, height) { 202 if (inputUrl !== "") { 203 var imageElement = paper.image(inputUrl, whiteboard.imageEl.cx, whiteboard.imageEl.cy, width, height); 204 var hb = drawHelperBox(imageElement, this.config.classTypes.image, this.config.properties.image.rotation, null, true, null); 205 wbElements[hb.uuid] = hb; 206 this.showProperties('editImage'); 207 208 // send changes to server 209 this.sendChanges({ 210 "action": "create", 211 "element": { 212 "type": this.config.classTypes.image, 213 "properties": { 214 "uuid": hb.uuid, 215 "x": whiteboard.imageEl.cx, 216 "y": whiteboard.imageEl.cy, 217 "rotationDegree": this.config.properties.image.rotation, 218 "url": inputUrl, 219 "width": width, 220 "height": height 221 } 222 } 223 }); 224 225 this.transferImagePropertiesToDialog(whiteboard.imageEl.cx, whiteboard.imageEl.cy, { 226 "width": width, 227 "height": height, 228 "rotation": this.config.properties.image.rotation 229 }); 230 } 231 } 232 233 /** Draws rectangle with default properties. 234 * @public 235 * @param x X-coordinate. 236 * @param y Y-coordinate. 237 */ 238 this.drawRectangle = function(x, y) { 239 var rectElement = paper.rect(x - offsetLeft, y - offsetTop, 160, 100, 0); 240 rectElement.scale(1, 1); // workaround for webkit based browsers 241 setElementProperties(rectElement, this.config.properties.rectangle); 242 var hb = drawHelperBox(rectElement, this.config.classTypes.rectangle, this.config.properties.rectangle.rotation, null, true, null); 243 wbElements[hb.uuid] = hb; 244 245 // send changes to server 246 this.sendChanges({ 247 "action": "create", 248 "element": { 249 "type": this.config.classTypes.rectangle, 250 "properties": { 251 "uuid": hb.uuid, 252 "x": x - offsetLeft, 253 "y": y - offsetTop, 254 "rotationDegree": this.config.properties.rectangle.rotation, 255 "width": rectElement.attr("width"), 256 "height": rectElement.attr("height"), 257 "cornerRadius": rectElement.attr("r"), 258 "backgroundColor": rectElement.attr("fill"), 259 "borderColor": rectElement.attr("stroke"), 260 "borderWidth": rectElement.attr("stroke-width"), 261 "borderStyle": getDasharrayValue(rectElement.attr("stroke-dasharray")), 262 "backgroundOpacity": rectElement.attr("fill-opacity"), 263 "borderOpacity": rectElement.attr("stroke-opacity") 264 } 265 } 266 }); 267 268 this.showProperties('editRectangle'); 269 this.transferRectanglePropertiesToDialog(x - offsetLeft, y - offsetTop, this.config.properties.rectangle); 270 } 271 272 /** Draws circle with default properties. 273 * @public 274 * @param x X-coordinate. 275 * @param y Y-coordinate. 276 */ 277 this.drawCircle = function(x, y) { 278 var circleElement = paper.circle(x - offsetLeft, y - offsetTop, 70); 279 circleElement.scale(1, 1); // workaround for webkit based browsers 280 setElementProperties(circleElement, this.config.properties.circle); 281 var hb = drawHelperBox(circleElement, this.config.classTypes.circle, this.config.properties.circle.rotation, null, true, null); 282 wbElements[hb.uuid] = hb; 283 284 // send changes to server 285 this.sendChanges({ 286 "action": "create", 287 "element": { 288 "type": this.config.classTypes.circle, 289 "properties": { 290 "uuid": hb.uuid, 291 "x": x - offsetLeft, 292 "y": y - offsetTop, 293 "rotationDegree": this.config.properties.circle.rotation, 294 "radius": circleElement.attr("r"), 295 "backgroundColor": circleElement.attr("fill"), 296 "borderColor": circleElement.attr("stroke"), 297 "borderWidth": circleElement.attr("stroke-width"), 298 "borderStyle": getDasharrayValue(circleElement.attr("stroke-dasharray")), 299 "backgroundOpacity": circleElement.attr("fill-opacity"), 300 "borderOpacity": circleElement.attr("stroke-opacity") 301 } 302 } 303 }); 304 305 this.showProperties('editCircle'); 306 this.transferCirclePropertiesToDialog(x - offsetLeft, y - offsetTop, this.config.properties.circle); 307 } 308 309 /** Draws ellipse with default properties. 310 * @public 311 * @param x X-coordinate. 312 * @param y Y-coordinate. 313 */ 314 this.drawEllipse = function(x, y) { 315 var ellipseElement = paper.ellipse(x - offsetLeft, y - offsetTop, 80, 50); 316 ellipseElement.scale(1, 1); // workaround for webkit based browsers 317 setElementProperties(ellipseElement, this.config.properties.ellipse); 318 var hb = drawHelperBox(ellipseElement, this.config.classTypes.ellipse, this.config.properties.ellipse.rotation, null, true, null); 319 wbElements[hb.uuid] = hb; 320 321 // send changes to server 322 this.sendChanges({ 323 "action": "create", 324 "element": { 325 "type": this.config.classTypes.ellipse, 326 "properties": { 327 "uuid": hb.uuid, 328 "x": x - offsetLeft, 329 "y": y - offsetTop, 330 "rotationDegree": this.config.properties.ellipse.rotation, 331 "hRadius": ellipseElement.attr("rx"), 332 "vRadius": ellipseElement.attr("ry"), 333 "backgroundColor": ellipseElement.attr("fill"), 334 "borderColor": ellipseElement.attr("stroke"), 335 "borderWidth": ellipseElement.attr("stroke-width"), 336 "borderStyle": getDasharrayValue(ellipseElement.attr("stroke-dasharray")), 337 "backgroundOpacity": ellipseElement.attr("fill-opacity"), 338 "borderOpacity": ellipseElement.attr("stroke-opacity") 339 } 340 } 341 }); 342 343 this.showProperties('editEllipse'); 344 this.transferEllipsePropertiesToDialog(x - offsetLeft, y - offsetTop, this.config.properties.ellipse); 345 } 346 347 /** Selects an element if user clicks on it. 348 * @public 349 * @param helperBox helper rectangle around element to be selected. 350 */ 351 this.selectElement = function(helperBox) { 352 helperBox.circleSet.attr(this.config.attributes.opacityVisible); 353 if (selectedObj != null && selectedObj.uuid != helperBox.uuid) { 354 // hide last selection 355 selectedObj.attr(this.config.attributes.opacityHidden); 356 selectedObj.circleSet.attr(this.config.attributes.opacityHidden); 357 } 358 selectedObj = helperBox; 359 selectedObj.visibleSelect = true; 360 this.showSelectedProperties(selectedObj); 361 } 362 363 /** Shows properties of the selected element in the panel "Edit Properties". 364 * @public 365 * @param selObj selected element. 366 */ 367 this.showSelectedProperties = function(selObj) { 368 // show and fill properties 369 this.showProperties('edit' + selObj.classType); 370 this.transferPropertiesToDialog(selObj); 371 } 372 373 /** Transfer properties of the selected element to the panel "Edit Properties". Called from showSelectedProperties(). 374 * @public 375 * @param selObj selected element. 376 */ 377 this.transferPropertiesToDialog = function(selObj) { 378 var selectedProperties = getSelectedProperties(selObj.element, this.config.properties[selObj.classType.charAt(0).toLowerCase() + selObj.classType.slice(1)]); 379 switch (selObj.classType) { 380 case this.config.classTypes.text : 381 this.transferTextPropertiesToDialog(selObj.element.attr("x"), selObj.element.attr("y"), selectedProperties); 382 break; 383 case this.config.classTypes.freeLine : 384 this.transferFreeLinePropertiesToDialog(selectedProperties); 385 break; 386 case this.config.classTypes.straightLine : 387 this.transferStraightLinePropertiesToDialog(selectedProperties); 388 break; 389 case this.config.classTypes.rectangle : 390 this.transferRectanglePropertiesToDialog(selObj.element.attr("x"), selObj.element.attr("y"), selectedProperties); 391 break; 392 case this.config.classTypes.circle : 393 this.transferCirclePropertiesToDialog(selObj.element.attr("cx"), selObj.element.attr("cy"), selectedProperties); 394 break; 395 case this.config.classTypes.ellipse : 396 this.transferEllipsePropertiesToDialog(selObj.element.attr("cx"), selObj.element.attr("cy"), selectedProperties); 397 break; 398 case this.config.classTypes.image : 399 this.transferImagePropertiesToDialog(selObj.element.attr("x"), selObj.element.attr("y"), selectedProperties); 400 break; 401 case this.config.classTypes.icon : 402 selectedProperties["scale"] = parseFloat((selectedProperties["scale"] + '').split("\\s+")[0]); 403 this.transferIconPropertiesToDialog(Math.round(selObj.attr("x") + 1), Math.round(selObj.attr("y") + 1), selectedProperties); 404 break; 405 default : 406 } 407 } 408 409 /** Removes element. 410 * @public 411 * @param helperBox helper rectangle around element to be removed. 412 */ 413 this.removeElement = function(helperBox) { 414 var eluuid = helperBox.uuid; 415 var elclasstype = helperBox.classType; 416 417 var removeSelected = false; 418 if (selectedObj != null && selectedObj.uuid == eluuid) { 419 removeSelected = true; 420 } 421 422 wbElements[eluuid] = null; 423 delete wbElements[eluuid]; 424 helperBox.element.remove(); 425 helperBox.circleSet.remove(); 426 helperBox.remove(); 427 428 // send changes to server 429 this.sendChanges({ 430 "action": "remove", 431 "element": { 432 "type": elclasstype, 433 "properties": { 434 "uuid": eluuid 435 } 436 } 437 }); 438 439 if (removeSelected) { 440 // last selected object = this object ==> reset 441 selectedObj = null; 442 this.showProperties('editNoSelection'); 443 } 444 } 445 446 /** Brings element to front (over all other elements). 447 * @public 448 * @param helperBox helper rectangle around element. 449 */ 450 this.bringFrontElement = function(helperBox) { 451 helperBox.element.toFront(); 452 helperBox.circleSet.toFront(); 453 helperBox.toFront(); 454 helperBox.attr(this.config.attributes.opacityHidden); 455 456 // send changes to server 457 this.sendChanges({ 458 "action": "toFront", 459 "element": { 460 "type": helperBox.classType, 461 "properties": { 462 "uuid": helperBox.uuid 463 } 464 } 465 }); 466 } 467 468 /** Brings element to back (behind all other elements). 469 * @public 470 * @param helperBox helper rectangle around element. 471 */ 472 this.bringBackElement = function(helperBox) { 473 helperBox.toBack(); 474 helperBox.circleSet.toBack(); 475 helperBox.element.toBack(); 476 helperBox.attr(this.config.attributes.opacityHidden); 477 478 // send changes to server 479 this.sendChanges({ 480 "action": "toBack", 481 "element": { 482 "type": helperBox.classType, 483 "properties": { 484 "uuid": helperBox.uuid 485 } 486 } 487 }); 488 } 489 490 /** Clones element to back. 491 * @public 492 * @param helperBox helper rectangle around element to be cloned. 493 */ 494 this.cloneElement = function(helperBox) { 495 var cloneEl, scaleFactor; 496 if (helperBox.classType == this.config.classTypes.icon) { 497 // workaround with scale factor 498 scaleFactor = parseFloat((helperBox.element.attr("scale") + '').split("\\s+")[0]); 499 helperBox.element.scale(1, 1); 500 cloneEl = helperBox.element.clone(); 501 helperBox.element.scale(scaleFactor, scaleFactor); 502 cloneEl.scale(scaleFactor, scaleFactor); 503 } else { 504 cloneEl = helperBox.element.clone(); 505 } 506 507 // shift clone 508 cloneEl.translate(15, 15); 509 510 var hb = drawHelperBox(cloneEl, helperBox.classType, null, null, false, null); 511 if (helperBox.classType == this.config.classTypes.icon) { 512 hb.iconName = helperBox.iconName; 513 } 514 515 var rotationDegree = cloneEl.attr("rotation"); 516 if (rotationDegree != 0) { 517 var bbox = cloneEl.getBBox(); 518 var bboxWidth = parseFloat(bbox.width); 519 var bboxHeight = parseFloat(bbox.height); 520 hb.circleSet.rotate(rotationDegree, bbox.x + bboxWidth / 2, bbox.y + bboxHeight / 2, true); 521 hb.rotate(rotationDegree, bbox.x + bboxWidth / 2, bbox.y + bboxHeight / 2, true); 522 } 523 524 helperBox.attr(this.config.attributes.opacityHidden); 525 wbElements[hb.uuid] = hb; 526 527 var objChanges = { 528 "action": "clone", 529 "element": { 530 "type": hb.classType, 531 "properties": { 532 "uuid": hb.uuid, 533 "rotationDegree": rotationDegree 534 } 535 } 536 }; 537 538 switch (hb.classType) { 539 case this.config.classTypes.text : 540 objChanges.element.properties.x = cloneEl.attr("x"); 541 objChanges.element.properties.y = cloneEl.attr("y"); 542 objChanges.element.properties.text = cloneEl.attr("text"); 543 objChanges.element.properties.fontFamily = cloneEl.attr("font-family"); 544 objChanges.element.properties.fontSize = cloneEl.attr("font-size"); 545 objChanges.element.properties.fontWeight = cloneEl.attr("font-weight"); 546 objChanges.element.properties.fontStyle = cloneEl.attr("font-style"); 547 objChanges.element.properties.color = cloneEl.attr("fill"); 548 549 break; 550 case this.config.classTypes.freeLine : 551 case this.config.classTypes.straightLine : 552 objChanges.element.properties.path = cloneEl.attr("path") + ''; 553 objChanges.element.properties.color = cloneEl.attr("stroke"); 554 objChanges.element.properties.lineWidth = cloneEl.attr("stroke-width"); 555 objChanges.element.properties.lineStyle = getDasharrayValue(cloneEl.attr("stroke-dasharray")); 556 objChanges.element.properties.opacity = cloneEl.attr("stroke-opacity"); 557 558 break; 559 case this.config.classTypes.rectangle : 560 objChanges.element.properties.x = cloneEl.attr("x"); 561 objChanges.element.properties.y = cloneEl.attr("y"); 562 objChanges.element.properties.width = cloneEl.attr("width"); 563 objChanges.element.properties.height = cloneEl.attr("height"); 564 objChanges.element.properties.cornerRadius = cloneEl.attr("r"); 565 objChanges.element.properties.backgroundColor = cloneEl.attr("fill"); 566 objChanges.element.properties.borderColor = cloneEl.attr("stroke"); 567 objChanges.element.properties.borderWidth = cloneEl.attr("stroke-width"); 568 objChanges.element.properties.borderStyle = getDasharrayValue(cloneEl.attr("stroke-dasharray")); 569 objChanges.element.properties.backgroundOpacity = cloneEl.attr("fill-opacity"); 570 objChanges.element.properties.borderOpacity = cloneEl.attr("stroke-opacity"); 571 572 break; 573 case this.config.classTypes.circle : 574 objChanges.element.properties.x = cloneEl.attr("cx"); 575 objChanges.element.properties.y = cloneEl.attr("cy"); 576 objChanges.element.properties.radius = cloneEl.attr("r"); 577 objChanges.element.properties.backgroundColor = cloneEl.attr("fill"); 578 objChanges.element.properties.borderColor = cloneEl.attr("stroke"); 579 objChanges.element.properties.borderWidth = cloneEl.attr("stroke-width"); 580 objChanges.element.properties.borderStyle = getDasharrayValue(cloneEl.attr("stroke-dasharray")); 581 objChanges.element.properties.backgroundOpacity = cloneEl.attr("fill-opacity"); 582 objChanges.element.properties.borderOpacity = cloneEl.attr("stroke-opacity"); 583 584 break; 585 case this.config.classTypes.ellipse : 586 objChanges.element.properties.x = cloneEl.attr("cx"); 587 objChanges.element.properties.y = cloneEl.attr("cy"); 588 objChanges.element.properties.hRadius = cloneEl.attr("rx"); 589 objChanges.element.properties.vRadius = cloneEl.attr("ry"); 590 objChanges.element.properties.backgroundColor = cloneEl.attr("fill"); 591 objChanges.element.properties.borderColor = cloneEl.attr("stroke"); 592 objChanges.element.properties.borderWidth = cloneEl.attr("stroke-width"); 593 objChanges.element.properties.borderStyle = getDasharrayValue(cloneEl.attr("stroke-dasharray")); 594 objChanges.element.properties.backgroundOpacity = cloneEl.attr("fill-opacity"); 595 objChanges.element.properties.borderOpacity = cloneEl.attr("stroke-opacity"); 596 597 break; 598 case this.config.classTypes.image : 599 objChanges.element.properties.x = cloneEl.attr("x"); 600 objChanges.element.properties.y = cloneEl.attr("y"); 601 objChanges.element.properties.url = cloneEl.attr("src"); 602 objChanges.element.properties.width = cloneEl.attr("width"); 603 objChanges.element.properties.height = cloneEl.attr("height"); 604 605 break; 606 case this.config.classTypes.icon : 607 objChanges.element.properties.x = Math.round(hb.attr("x") + 1); 608 objChanges.element.properties.y = Math.round(hb.attr("y") + 1); 609 objChanges.element.properties.name = hb.iconName; 610 objChanges.element.properties.scaleFactor = scaleFactor; 611 612 break; 613 default : 614 } 615 616 this.sendChanges(objChanges); 617 } 618 619 /** Resizes this whiteboard. 620 * @public 621 * @param width new whiteboard width. 622 * @param height new whiteboard height. 623 */ 624 this.resizeWhiteboard = function(width, height) { 625 whiteboard.css({width: width + 'px', height: height + 'px'}); 626 paper.setSize(width, height); 627 628 // send changes to server 629 this.sendChanges({ 630 "action": "resize", 631 "parameters": { 632 "width": width, 633 "height": height 634 } 635 }); 636 } 637 638 /** Open dialog to input a text. 639 * @public 640 * @param x X-coordinate where the text has to be input. 641 * @param y Y-coordinate where the text has to be input. 642 */ 643 this.openTextDialog = function(x, y) { 644 whiteboard.textEl.cx = x - offsetLeft; 645 whiteboard.textEl.cy = y - offsetTop; 646 dialogInputText.dialog("open"); 647 } 648 649 /** Open dialog to paste an image. 650 * @public 651 * @param x X-coordinate where the image has to be pasted. 652 * @param y Y-coordinate where the image has to be pasted. 653 */ 654 this.openImageDialog = function(x, y) { 655 whiteboard.imageEl.cx = x - offsetLeft; 656 whiteboard.imageEl.cy = y - offsetTop; 657 dialogInputImage.dialog("open"); 658 } 659 660 /** Open dialog to paste an icon. 661 * @public 662 * @param x X-coordinate where the icon has to be pasted. 663 * @param y Y-coordinate where the icon has to be pasted. 664 */ 665 this.openIconsDialog = function(x, y) { 666 whiteboard.iconEl.cx = x - offsetLeft; 667 whiteboard.iconEl.cy = y - offsetTop; 668 dialogIcons.dialog("open"); 669 } 670 671 /** Opens dialog to resize the whiteboard. 672 * @public 673 */ 674 this.openResizeDialog = function() { 675 dialogResize.dialog("open"); 676 } 677 678 /** Clears the whiteboard. 679 * @public 680 */ 681 this.clearWhiteboard = function() { 682 paper.clear(); 683 this.showProperties('editNoSelection'); 684 for (eluuid in wbElements) { 685 wbElements[eluuid] = null; 686 delete wbElements[eluuid]; 687 } 688 689 // send changes to server 690 this.sendChanges({ 691 "action": "clear" 692 }); 693 } 694 695 /** Enables / disables properties in the panel "Edit Properties" depends on action. 696 * @public 697 * @param showClass CSS class of the property block to be enabled. 698 */ 699 this.showProperties = function(showClass) { 700 var propsDialog = jQuery(".propertiesPanel"); 701 propsDialog.find(".editPanel").hide(); 702 propsDialog.find("." + showClass).show(); 703 } 704 705 /** Sets Id of the subview - where pinned or unpinned panels are placed. 706 * @public 707 * @param id subview Id. 708 */ 709 this.setIdSubviewProperties = function(id) { 710 idSubviewProperties = id; 711 } 712 713 /** Transfers given text properties to the panel "Edit properties". 714 * @public 715 * @param cx X-coordinate of the text. 716 * @param cy Y-coordinate of the text. 717 * @param props property JavaScript object as key, value. 718 */ 719 this.transferTextPropertiesToDialog = function(cx, cy, props) { 720 jQuery(idSubviewProperties + "_textCx").val(cx); 721 jQuery(idSubviewProperties + "_textCy").val(cy); 722 jQuery(idSubviewProperties + "_textArea").val(props["text"]); 723 jQuery(idSubviewProperties + "_fontFamily option[value='" + props["font-family"] + "']").attr('selected', true); 724 jQuery(idSubviewProperties + "_fontSize").val(props["font-size"]); 725 jQuery("input[name='" + idSubviewProperties.substring(1) + "_fontWeight'][value='" + props["font-weight"] + "']").attr('checked', 'checked'); 726 jQuery("input[name='" + idSubviewProperties.substring(1) + "_fontStyle'][value='" + props["font-style"] + "']").attr('checked', 'checked'); 727 jQuery(idSubviewProperties + "_textColor div").css('backgroundColor', props["fill"]); 728 jQuery(idSubviewProperties + "_textRotation").val(props["rotation"]); 729 } 730 731 /** Transfers free line properties to the panel "Edit properties". 732 * @public 733 * @param props property JavaScript object as key, value. 734 */ 735 this.transferFreeLinePropertiesToDialog = function(props) { 736 jQuery(idSubviewProperties + "_freeLineColor div").css('backgroundColor', props["stroke"]); 737 jQuery(idSubviewProperties + "_freeLineWidth").val(props["stroke-width"]); 738 jQuery(idSubviewProperties + "_freeLineStyle option[value='" + props["stroke-dasharray"] + "']").attr('selected', true); 739 jQuery(idSubviewProperties + "_freeLineOpacity").val(props["stroke-opacity"].toFixed(1)); 740 jQuery(idSubviewProperties + "_freeLineRotation").val(props["rotation"]); 741 } 742 743 /** Transfers straight line properties to the panel "Edit properties". 744 * @public 745 * @param props property JavaScript object as key, value. 746 */ 747 this.transferStraightLinePropertiesToDialog = function(props) { 748 jQuery(idSubviewProperties + "_straightLineColor div").css('backgroundColor', props["stroke"]); 749 jQuery(idSubviewProperties + "_straightLineWidth").val(props["stroke-width"]); 750 jQuery(idSubviewProperties + "_straightLineStyle option[value='" + props["stroke-dasharray"] + "']").attr('selected', true); 751 jQuery(idSubviewProperties + "_straightLineOpacity").val(props["stroke-opacity"].toFixed(1)); 752 jQuery(idSubviewProperties + "_straightLineRotation").val(props["rotation"]); 753 } 754 755 /** Transfers rectangle properties to the panel "Edit properties". 756 * @public 757 * @param cx X-coordinate of the rectangle. 758 * @param cy Y-coordinate of the rectangle. 759 * @param props property JavaScript object as key, value. 760 */ 761 this.transferRectanglePropertiesToDialog = function(cx, cy, props) { 762 jQuery(idSubviewProperties + "_rectCx").val(cx); 763 jQuery(idSubviewProperties + "_rectCy").val(cy); 764 jQuery(idSubviewProperties + "_rectWidth").val(props["width"]); 765 jQuery(idSubviewProperties + "_rectHeight").val(props["height"]); 766 jQuery(idSubviewProperties + "_cornerRadius").val(props["r"]); 767 jQuery(idSubviewProperties + "_rectBkgrColor div").css('backgroundColor', props["fill"]); 768 jQuery(idSubviewProperties + "_rectBorderColor div").css('backgroundColor', props["stroke"]); 769 jQuery(idSubviewProperties + "_rectBorderWidth").val(props["stroke-width"]); 770 jQuery(idSubviewProperties + "_rectBorderStyle option[value='" + props["stroke-dasharray"] + "']").attr('selected', true); 771 jQuery(idSubviewProperties + "_rectBkgrOpacity").val(props["fill-opacity"].toFixed(1)); 772 jQuery(idSubviewProperties + "_rectBorderOpacity").val(props["stroke-opacity"].toFixed(1)); 773 jQuery(idSubviewProperties + "_rectRotation").val(props["rotation"]); 774 } 775 776 /** Transfers circle properties to the panel "Edit properties". 777 * @public 778 * @param cx X-coordinate of the circle. 779 * @param cy Y-coordinate of the circle. 780 * @param props property JavaScript object as key, value. 781 */ 782 this.transferCirclePropertiesToDialog = function(cx, cy, props) { 783 jQuery(idSubviewProperties + "_circleCx").val(cx); 784 jQuery(idSubviewProperties + "_circleCy").val(cy); 785 jQuery(idSubviewProperties + "_radius").val(props["r"]); 786 jQuery(idSubviewProperties + "_circleBkgrColor div").css('backgroundColor', props["fill"]); 787 jQuery(idSubviewProperties + "_circleBorderColor div").css('backgroundColor', props["stroke"]); 788 jQuery(idSubviewProperties + "_circleBorderWidth").val(props["stroke-width"]); 789 jQuery(idSubviewProperties + "_circleBorderStyle option[value='" + props["stroke-dasharray"] + "']").attr('selected', true); 790 jQuery(idSubviewProperties + "_circleBkgrOpacity").val(props["fill-opacity"].toFixed(1)); 791 jQuery(idSubviewProperties + "_circleBorderOpacity").val(props["stroke-opacity"].toFixed(1)); 792 jQuery(idSubviewProperties + "_circleRotation").val(props["rotation"]); 793 } 794 795 /** Transfers ellipse properties to the panel "Edit properties". 796 * @public 797 * @param cx X-coordinate of the ellipse. 798 * @param cy Y-coordinate of the ellipse. 799 * @param props property JavaScript object as key, value. 800 */ 801 this.transferEllipsePropertiesToDialog = function(cx, cy, props) { 802 jQuery(idSubviewProperties + "_ellipseCx").val(cx); 803 jQuery(idSubviewProperties + "_ellipseCy").val(cy); 804 jQuery(idSubviewProperties + "_hRadius").val(props["rx"]); 805 jQuery(idSubviewProperties + "_vRadius").val(props["ry"]); 806 jQuery(idSubviewProperties + "_ellipseBkgrColor div").css('backgroundColor', props["fill"]); 807 jQuery(idSubviewProperties + "_ellipseBorderColor div").css('backgroundColor', props["stroke"]); 808 jQuery(idSubviewProperties + "_ellipseBorderWidth").val(props["stroke-width"]); 809 jQuery(idSubviewProperties + "_ellipseBorderStyle option[value='" + props["stroke-dasharray"] + "']").attr('selected', true); 810 jQuery(idSubviewProperties + "_ellipseBkgrOpacity").val(props["fill-opacity"].toFixed(1)); 811 jQuery(idSubviewProperties + "_ellipseBorderOpacity").val(props["stroke-opacity"].toFixed(1)); 812 jQuery(idSubviewProperties + "_ellipseRotation").val(props["rotation"]); 813 } 814 815 /** Transfers image properties to the panel "Edit properties". 816 * @public 817 * @param cx X-coordinate of the image. 818 * @param cy Y-coordinate of the image. 819 * @param props property JavaScript object as key, value. 820 */ 821 this.transferImagePropertiesToDialog = function(cx, cy, props) { 822 jQuery(idSubviewProperties + "_imageCx").val(cx); 823 jQuery(idSubviewProperties + "_imageCy").val(cy); 824 jQuery(idSubviewProperties + "_imageWidth").val(props["width"]); 825 jQuery(idSubviewProperties + "_imageHeight").val(props["height"]); 826 jQuery(idSubviewProperties + "_imageRotation").val(props["rotation"]); 827 } 828 829 /** Transfers icon properties to the panel "Edit properties". 830 * @public 831 * @param cx X-coordinate of the icon. 832 * @param cy Y-coordinate of the icon. 833 * @param props property JavaScript object as key, value. 834 */ 835 this.transferIconPropertiesToDialog = function(cx, cy, props) { 836 jQuery(idSubviewProperties + "_iconCx").val(cx); 837 jQuery(idSubviewProperties + "_iconCy").val(cy); 838 jQuery(idSubviewProperties + "_iconRotation").val(props["rotation"]); 839 jQuery(idSubviewProperties + "_iconScale").val(props["scale"].toFixed(1)); 840 } 841 842 /** Makes given properties as default (user push the button "Make as default"). 843 * @public 844 * @param properties property JavaScript object as key, value. 845 */ 846 this.makeAsDefault = function(properties) { 847 var classType = properties.charAt(0).toUpperCase() + properties.slice(1); 848 this.propagateProperties(classType, this.config.properties[properties]); 849 } 850 851 /** Gets properties from the panel "Edit Properties". 852 * @public 853 * @param classType element type. 854 * @param props empty property JavaScript object as key, value. 855 * @returns {object} filled property JavaScript object as key, value. 856 */ 857 this.propagateProperties = function(classType, props) { 858 switch (classType) { 859 case this.config.classTypes.text : 860 props["text"] = jQuery(idSubviewProperties + "_textArea").val(); 861 props["font-family"] = jQuery(idSubviewProperties + "_fontFamily option:selected").val(); 862 props["font-size"] = parseInt(jQuery(idSubviewProperties + "_fontSize").val()); 863 props["font-weight"] = jQuery("input[name='" + idSubviewProperties.substring(1) + "_fontWeight']:checked").val(); 864 props["font-style"] = jQuery("input[name='" + idSubviewProperties.substring(1) + "_fontStyle']:checked").val(); 865 props["fill"] = jQuery(idSubviewProperties + "_textColor div").css('backgroundColor'); 866 props["rotation"] = parseInt(jQuery(idSubviewProperties + "_textRotation").val()); 867 break; 868 case this.config.classTypes.freeLine : 869 props["stroke"] = jQuery(idSubviewProperties + "_freeLineColor div").css('backgroundColor'); 870 props["stroke-width"] = parseInt(jQuery(idSubviewProperties + "_freeLineWidth").val()); 871 props["stroke-dasharray"] = jQuery(idSubviewProperties + "_freeLineStyle option:selected").val(); 872 props["stroke-opacity"] = parseFloat(jQuery(idSubviewProperties + "_freeLineOpacity").val()); 873 props["rotation"] = parseInt(jQuery(idSubviewProperties + "_freeLineRotation").val()); 874 break; 875 case this.config.classTypes.straightLine : 876 props["stroke"] = jQuery(idSubviewProperties + "_straightLineColor div").css('backgroundColor'); 877 props["stroke-width"] = parseInt(jQuery(idSubviewProperties + "_straightLineWidth").val()); 878 props["stroke-dasharray"] = jQuery(idSubviewProperties + "_straightLineStyle option:selected").val(); 879 props["stroke-opacity"] = parseFloat(jQuery(idSubviewProperties + "_straightLineOpacity").val()); 880 props["rotation"] = parseInt(jQuery(idSubviewProperties + "_straightLineRotation").val()); 881 break; 882 case this.config.classTypes.rectangle : 883 props["width"] = parseInt(jQuery(idSubviewProperties + "_rectWidth").val()); 884 props["height"] = parseInt(jQuery(idSubviewProperties + "_rectHeight").val()); 885 props["r"] = parseInt(jQuery(idSubviewProperties + "_cornerRadius").val()); 886 props["fill"] = jQuery(idSubviewProperties + "_rectBkgrColor div").css('backgroundColor'); 887 props["stroke"] = jQuery(idSubviewProperties + "_rectBorderColor div").css('backgroundColor'); 888 props["stroke-width"] = parseInt(jQuery(idSubviewProperties + "_rectBorderWidth").val()); 889 props["stroke-dasharray"] = jQuery(idSubviewProperties + "_rectBorderStyle option:selected").val(); 890 props["fill-opacity"] = parseFloat(jQuery(idSubviewProperties + "_rectBkgrOpacity").val()); 891 props["stroke-opacity"] = parseFloat(jQuery(idSubviewProperties + "_rectBorderOpacity").val()); 892 props["rotation"] = parseInt(jQuery(idSubviewProperties + "_rectRotation").val()); 893 break; 894 case this.config.classTypes.circle : 895 props["r"] = parseInt(jQuery(idSubviewProperties + "_radius").val()); 896 props["fill"] = jQuery(idSubviewProperties + "_circleBkgrColor div").css('backgroundColor'); 897 props["stroke"] = jQuery(idSubviewProperties + "_circleBorderColor div").css('backgroundColor'); 898 props["stroke-width"] = parseInt(jQuery(idSubviewProperties + "_circleBorderWidth").val()); 899 props["stroke-dasharray"] = jQuery(idSubviewProperties + "_circleBorderStyle option:selected").val(); 900 props["fill-opacity"] = parseFloat(jQuery(idSubviewProperties + "_circleBkgrOpacity").val()); 901 props["stroke-opacity"] = parseFloat(jQuery(idSubviewProperties + "_circleBorderOpacity").val()); 902 props["rotation"] = parseInt(jQuery(idSubviewProperties + "_circleRotation").val()); 903 break; 904 case this.config.classTypes.ellipse : 905 props["rx"] = parseInt(jQuery(idSubviewProperties + "_hRadius").val()); 906 props["ry"] = parseInt(jQuery(idSubviewProperties + "_vRadius").val()); 907 props["fill"] = jQuery(idSubviewProperties + "_ellipseBkgrColor div").css('backgroundColor'); 908 props["stroke"] = jQuery(idSubviewProperties + "_ellipseBorderColor div").css('backgroundColor'); 909 props["stroke-width"] = parseInt(jQuery(idSubviewProperties + "_ellipseBorderWidth").val()); 910 props["stroke-dasharray"] = jQuery(idSubviewProperties + "_ellipseBorderStyle option:selected").val(); 911 props["fill-opacity"] = parseFloat(jQuery(idSubviewProperties + "_ellipseBkgrOpacity").val()); 912 props["stroke-opacity"] = parseFloat(jQuery(idSubviewProperties + "_ellipseBorderOpacity").val()); 913 props["rotation"] = parseInt(jQuery(idSubviewProperties + "_ellipseRotation").val()); 914 break; 915 case this.config.classTypes.image : 916 props["width"] = parseInt(jQuery(idSubviewProperties + "_imageWidth").val()); 917 props["height"] = parseInt(jQuery(idSubviewProperties + "_imageHeight").val()); 918 props["rotation"] = parseInt(jQuery(idSubviewProperties + "_imageRotation").val()); 919 break; 920 case this.config.classTypes.icon : 921 props["rotation"] = parseInt(jQuery(idSubviewProperties + "_iconRotation").val()); 922 props["scale"] = parseFloat(jQuery(idSubviewProperties + "_iconScale").val()); 923 break; 924 default : 925 } 926 927 return props; 928 } 929 930 /** Restores the whiteboard if user has joined this whiteboard. All elements and a proper message gets restored. 931 * @public 932 * @param jsWhiteboard whiteboard in JSON format from backend (server side). 933 */ 934 this.restoreWhiteboard = function(jsWhiteboard) { 935 var arrElements = jsWhiteboard["elements"]; 936 for (var i = 0; i < arrElements.length; i++) { 937 var objElement = arrElements[i]; 938 this.createElement(objElement.properties, objElement.type); 939 } 940 941 prependMessage(jsWhiteboard["message"]); 942 } 943 944 /** Creates element. 945 * @public 946 * @param props element properties as JavaScript object (key, value). 947 * @param classType element type. 948 */ 949 this.createElement = function(props, classType) { 950 var hb; 951 switch (classType) { 952 case this.config.classTypes.text : 953 var textElement = paper.text(props.x, props.y, props.text); 954 setElementProperties(textElement, { 955 "font-family" : props.fontFamily, 956 "font-size" : props.fontSize, 957 "font-weight" : props.fontWeight, 958 "font-style" : props.fontStyle, 959 "fill" : props.color 960 }); 961 hb = drawHelperBox(textElement, this.config.classTypes.text, props.rotationDegree, null, false, props.uuid); 962 wbElements[hb.uuid] = hb; 963 964 break; 965 case this.config.classTypes.freeLine : 966 var freeLine = paper.path(props.path); 967 setElementProperties(freeLine, { 968 "stroke" : props.color, 969 "stroke-width" : props.lineWidth, 970 "stroke-dasharray" : props.lineStyle, 971 "stroke-opacity" : props.opacity 972 }); 973 hb = drawHelperBox(freeLine, this.config.classTypes.freeLine, props.rotationDegree, null, false, props.uuid); 974 wbElements[hb.uuid] = hb; 975 976 break; 977 case this.config.classTypes.straightLine : 978 var straightLine = paper.path(props.path); 979 setElementProperties(straightLine, { 980 "stroke" : props.color, 981 "stroke-width" : props.lineWidth, 982 "stroke-dasharray" : props.lineStyle, 983 "stroke-opacity" : props.opacity 984 }); 985 hb = drawHelperBox(straightLine, this.config.classTypes.straightLine, props.rotationDegree, null, false, props.uuid); 986 wbElements[hb.uuid] = hb; 987 988 break; 989 case this.config.classTypes.rectangle : 990 var rectElement = paper.rect(props.x, props.y, props.width, props.height, props.cornerRadius); 991 rectElement.scale(1, 1); // workaround for webkit based browsers 992 setElementProperties(rectElement, { 993 "fill" : props.backgroundColor, 994 "stroke" : props.borderColor, 995 "stroke-width" : props.borderWidth, 996 "stroke-dasharray" : props.borderStyle, 997 "fill-opacity" : props.backgroundOpacity, 998 "stroke-opacity" : props.borderOpacity 999 }); 1000 hb = drawHelperBox(rectElement, this.config.classTypes.rectangle, props.rotationDegree, null, false, props.uuid); 1001 wbElements[hb.uuid] = hb; 1002 1003 break; 1004 case this.config.classTypes.circle : 1005 var circleElement = paper.circle(props.x, props.y, props.radius); 1006 circleElement.scale(1, 1); // workaround for webkit based browsers 1007 setElementProperties(circleElement, { 1008 "fill" : props.backgroundColor, 1009 "stroke" : props.borderColor, 1010 "stroke-width" : props.borderWidth, 1011 "stroke-dasharray" : props.borderStyle, 1012 "fill-opacity" : props.backgroundOpacity, 1013 "stroke-opacity" : props.borderOpacity 1014 }); 1015 hb = drawHelperBox(circleElement, this.config.classTypes.circle, props.rotationDegree, null, false, props.uuid); 1016 wbElements[hb.uuid] = hb; 1017 1018 break; 1019 case this.config.classTypes.ellipse : 1020 var ellipseElement = paper.ellipse(props.x, props.y, props.hRadius, props.vRadius); 1021 ellipseElement.scale(1, 1); // workaround for webkit based browsers 1022 setElementProperties(ellipseElement, { 1023 "fill" : props.backgroundColor, 1024 "stroke" : props.borderColor, 1025 "stroke-width" : props.borderWidth, 1026 "stroke-dasharray" : props.borderStyle, 1027 "fill-opacity" : props.backgroundOpacity, 1028 "stroke-opacity" : props.borderOpacity 1029 }); 1030 hb = drawHelperBox(ellipseElement, this.config.classTypes.ellipse, props.rotationDegree, null, false, props.uuid); 1031 wbElements[hb.uuid] = hb; 1032 1033 break; 1034 case this.config.classTypes.image : 1035 var imageElement = paper.image(props.url, props.x, props.y, props.width, props.height); 1036 hb = drawHelperBox(imageElement, this.config.classTypes.image, props.rotationDegree, null, false, props.uuid); 1037 wbElements[hb.uuid] = hb; 1038 1039 break; 1040 case this.config.classTypes.icon : 1041 var iconElement = paper.path(this.config.svgIconSet[props.name]).attr({fill: "#000", stroke: "none"}); 1042 iconElement.scale(props.scaleFactor, props.scaleFactor); 1043 var bbox = iconElement.getBBox(); 1044 // at first bring to 0,0 position after scale 1045 iconElement.translate(0 - bbox.x, 0 - bbox.y); 1046 // at second move to given position 1047 iconElement.translate(props.x, props.y); 1048 // and remains 1049 hb = drawHelperBox(iconElement, this.config.classTypes.icon, props.rotationDegree, null, false, props.uuid); 1050 hb.iconName = props.name; 1051 wbElements[hb.uuid] = hb; 1052 1053 break; 1054 default : 1055 } 1056 } 1057 1058 /** Sends changed properties of the currently selected element to the server (use case: user changes properties in the panel "Edit Properties"). 1059 * @public 1060 * @param type element type. 1061 * @param resize boolean flag, true - resize element, false - otherwise. 1062 * @param resize boolean flag, true - rotate element, false - otherwise. 1063 */ 1064 this.sendPropertiesChanges = function(type, resize, rotate) { 1065 if (selectedObj == null) { 1066 return; 1067 } 1068 1069 var classType = type.charAt(0).toUpperCase() + type.slice(1); 1070 var props = this.propagateProperties(classType, {}); 1071 var rotationDegree = props["rotation"]; 1072 var sendProps = {}; 1073 var oldDim = {}; 1074 1075 // update element 1076 setElementProperties(selectedObj.element, props); 1077 1078 // resize helpers 1079 if (resize != null && resize) { 1080 var bbox = selectedObj.element.getBBox(); 1081 var bboxWidth = parseFloat(bbox.width); 1082 var bboxHeight = parseFloat(bbox.height); 1083 selectedObj.attr("x", bbox.x - 1); 1084 selectedObj.attr("y", bbox.y - 1); 1085 selectedObj.attr("width", (bboxWidth !== 0 ? bboxWidth + 2 : 3)); 1086 selectedObj.attr("height", (bboxHeight !== 0 ? bboxHeight + 2 : 3)); 1087 1088 // redraw circleSet 1089 selectedObj.circleSet.remove(); 1090 selectedObj.circleSet = null; 1091 delete selectedObj.circleSet; 1092 var circleSet = drawCircleSet(bbox.x, bbox.y, bboxWidth, bboxHeight); 1093 circleSet.attr(this.config.attributes.circleSet); 1094 if (selectedObj.visibleSelect) { 1095 circleSet.attr(this.config.attributes.opacityVisible); 1096 } 1097 selectedObj.circleSet = circleSet; 1098 rotate = true; 1099 } 1100 1101 // rotate 1102 if (rotate != null && rotate) { 1103 var bbox2 = selectedObj.element.getBBox(); 1104 var bboxWidth2 = parseFloat(bbox2.width); 1105 var bboxHeight2 = parseFloat(bbox2.height); 1106 selectedObj.element.rotate(rotationDegree, bbox2.x + bboxWidth2 / 2, bbox2.y + bboxHeight2 / 2, true); 1107 selectedObj.rotate(rotationDegree, bbox2.x + bboxWidth2 / 2, bbox2.y + bboxHeight2 / 2, true); 1108 selectedObj.circleSet.rotate(rotationDegree, bbox2.x + bboxWidth2 / 2, bbox2.y + bboxHeight2 / 2, true); 1109 selectedObj.element.attr("rotation", rotationDegree); 1110 } 1111 1112 // extend properties to coordinates 1113 switch (classType) { 1114 case this.config.classTypes.text : 1115 sendProps.text = props["text"]; 1116 sendProps.fontFamily = props["font-family"]; 1117 sendProps.fontSize = props["font-size"]; 1118 sendProps.fontWeight = props["font-weight"]; 1119 sendProps.fontStyle = props["font-style"]; 1120 sendProps.color = props["fill"]; 1121 1122 sendProps.x = parseInt(jQuery(idSubviewProperties + "_textCx").val()); 1123 sendProps.y = parseInt(jQuery(idSubviewProperties + "_textCy").val()); 1124 oldDim.x = selectedObj.element.attr("x"); 1125 oldDim.y = selectedObj.element.attr("y"); 1126 1127 break; 1128 case this.config.classTypes.freeLine : 1129 case this.config.classTypes.straightLine : 1130 sendProps.path = selectedObj.element.attr("path") + ''; 1131 sendProps.color = props["stroke"]; 1132 sendProps.lineWidth = props["stroke-width"]; 1133 sendProps.lineStyle = props["stroke-dasharray"]; 1134 sendProps.opacity = props["stroke-opacity"]; 1135 1136 break; 1137 case this.config.classTypes.rectangle : 1138 sendProps.width = props["width"]; 1139 sendProps.height = props["height"]; 1140 sendProps.cornerRadius = props["r"]; 1141 sendProps.backgroundColor = props["fill"]; 1142 sendProps.borderColor = props["stroke"]; 1143 sendProps.borderWidth = props["stroke-width"]; 1144 sendProps.borderStyle = props["stroke-dasharray"]; 1145 sendProps.backgroundOpacity = props["fill-opacity"]; 1146 sendProps.borderOpacity = props["stroke-opacity"]; 1147 1148 sendProps.x = parseInt(jQuery(idSubviewProperties + "_rectCx").val()); 1149 sendProps.y = parseInt(jQuery(idSubviewProperties + "_rectCy").val()); 1150 oldDim.x = selectedObj.element.attr("x"); 1151 oldDim.y = selectedObj.element.attr("y"); 1152 break; 1153 case this.config.classTypes.circle : 1154 sendProps.radius = props["r"]; 1155 sendProps.backgroundColor = props["fill"]; 1156 sendProps.borderColor = props["stroke"]; 1157 sendProps.borderWidth = props["stroke-width"]; 1158 sendProps.borderStyle = props["stroke-dasharray"]; 1159 sendProps.backgroundOpacity = props["fill-opacity"]; 1160 sendProps.borderOpacity = props["stroke-opacity"]; 1161 1162 sendProps.x = parseInt(jQuery(idSubviewProperties + "_circleCx").val()); 1163 sendProps.y = parseInt(jQuery(idSubviewProperties + "_circleCy").val()); 1164 oldDim.x = selectedObj.element.attr("cx"); 1165 oldDim.y = selectedObj.element.attr("cy"); 1166 break; 1167 case this.config.classTypes.ellipse : 1168 sendProps.hRadius = props["rx"]; 1169 sendProps.vRadius = props["ry"]; 1170 sendProps.backgroundColor = props["fill"]; 1171 sendProps.borderColor = props["stroke"]; 1172 sendProps.borderWidth = props["stroke-width"]; 1173 sendProps.borderStyle = props["stroke-dasharray"]; 1174 sendProps.backgroundOpacity = props["fill-opacity"]; 1175 sendProps.borderOpacity = props["stroke-opacity"]; 1176 1177 sendProps.x = parseInt(jQuery(idSubviewProperties + "_ellipseCx").val()); 1178 sendProps.y = parseInt(jQuery(idSubviewProperties + "_ellipseCy").val()); 1179 oldDim.x = selectedObj.element.attr("cx"); 1180 oldDim.y = selectedObj.element.attr("cy"); 1181 break; 1182 case this.config.classTypes.image : 1183 sendProps.url = selectedObj.element.attr("src"); 1184 sendProps.width = props["width"]; 1185 sendProps.height = props["height"]; 1186 1187 sendProps.x = parseInt(jQuery(idSubviewProperties + "_imageCx").val()); 1188 sendProps.y = parseInt(jQuery(idSubviewProperties + "_imageCy").val()); 1189 oldDim.x = selectedObj.element.attr("x"); 1190 oldDim.y = selectedObj.element.attr("y"); 1191 break; 1192 case this.config.classTypes.icon : 1193 sendProps.name = selectedObj.iconName; 1194 sendProps.scaleFactor = props["scale"].toFixed(1); 1195 1196 sendProps.x = parseInt(jQuery(idSubviewProperties + "_iconCx").val()); 1197 sendProps.y = parseInt(jQuery(idSubviewProperties + "_iconCy").val()); 1198 oldDim.x = Math.round(selectedObj.attr("x") + 1); 1199 oldDim.y = Math.round(selectedObj.attr("y") + 1); 1200 break; 1201 default : 1202 } 1203 1204 // move element and helpers if needed 1205 if (typeof oldDim.x !== "undefined") { 1206 var diffX = sendProps.x - oldDim.x; 1207 var diffY = sendProps.y - oldDim.y; 1208 if (diffX != 0 || diffY != 0) { 1209 selectedObj.element.translate(diffX, diffY); 1210 selectedObj.translate(diffX, diffY); 1211 selectedObj.circleSet.translate(diffX, diffY); 1212 } 1213 } 1214 1215 sendProps.uuid = selectedObj.uuid; 1216 sendProps.rotationDegree = rotationDegree; 1217 1218 // send changes 1219 this.sendChanges({ 1220 "action": "update", 1221 "element": { 1222 "type": classType, 1223 "properties": sendProps 1224 } 1225 }); 1226 } 1227 1228 /** Subscribes to bidirectional channel. This method will be called once the web-application is ready to use. 1229 * @public 1230 */ 1231 this.subscribePubSub = function() { 1232 jQuery.atmosphere.subscribe(this.pubSubUrl, this.pubSubCallback, jQuery.atmosphere.request = { 1233 transport: this.pubSubTransport, 1234 maxRequest: 100000000 1235 }); 1236 this.connectedEndpoint = jQuery.atmosphere.response; 1237 } 1238 1239 /** Callback method defined in subscribePubSub(). This method is always called when new data (updates) are available on server side. 1240 * @public 1241 * @param response response object having state, status and sent data. 1242 */ 1243 this.pubSubCallback = function(response) { 1244 if (response.transport != 'polling' && response.state != 'connected' && response.state != 'closed' && response.status == 200) { 1245 var data = response.responseBody; 1246 if (data.length > 0) { 1247 if (_self.logging) { 1248 logIncoming(data); 1249 } 1250 1251 // convert to JavaScript object 1252 var jsData = JSON.parse(data); 1253 1254 if (_self.logging) { 1255 logProfile(jsData.timestamp); 1256 } 1257 1258 var action = jsData.action; 1259 var sentProps = (jsData.element != null ? jsData.element.properties : null); 1260 1261 switch (action) { 1262 case "join" : 1263 jQuery("#usersCount").html(jsData.parameters.usersCount); 1264 1265 break; 1266 case "create" : 1267 case "clone" : 1268 _self.createElement(sentProps, jsData.element.type); 1269 1270 break; 1271 case "update" : 1272 // find element to be updated 1273 var hbu = wbElements[sentProps.uuid]; 1274 if (hbu == null) { 1275 // not found ==> nothing to do 1276 if (_self.logging) { 1277 logDebug("Element to be updated does not exist anymore in this Whiteboard"); 1278 } 1279 break; 1280 } 1281 1282 var props = {}, oldDimU = {}, newDimU = {}; 1283 1284 switch (jsData.element.type) { 1285 case _self.config.classTypes.text : 1286 props["text"] = sentProps.text; 1287 props["font-family"] = sentProps.fontFamily; 1288 props["font-size"] = sentProps.fontSize; 1289 props["font-weight"] = sentProps.fontWeight; 1290 props["font-style"] = sentProps.fontStyle; 1291 props["fill"] = sentProps.color; 1292 1293 newDimU.x = sentProps.x; 1294 newDimU.y = sentProps.y; 1295 oldDimU.x = hbu.element.attr("x"); 1296 oldDimU.y = hbu.element.attr("y"); 1297 break; 1298 case _self.config.classTypes.freeLine : 1299 case _self.config.classTypes.straightLine : 1300 //props["path"] = sentProps.path; 1301 props["stroke"] = sentProps.color; 1302 props["stroke-width"] = sentProps.lineWidth; 1303 props["stroke-dasharray"] = sentProps.lineStyle; 1304 props["stroke-opacity"] = sentProps.opacity; 1305 break; 1306 case _self.config.classTypes.rectangle : 1307 props["width"] = sentProps.width; 1308 props["height"] = sentProps.height; 1309 props["r"] = sentProps.cornerRadius; 1310 props["fill"] = sentProps.backgroundColor; 1311 props["stroke"] = sentProps.borderColor; 1312 props["stroke-width"] = sentProps.borderWidth; 1313 props["stroke-dasharray"] = sentProps.borderStyle; 1314 props["fill-opacity"] = sentProps.backgroundOpacity; 1315 props["stroke-opacity"] = sentProps.borderOpacity; 1316 1317 newDimU.x = sentProps.x; 1318 newDimU.y = sentProps.y; 1319 oldDimU.x = hbu.element.attr("x"); 1320 oldDimU.y = hbu.element.attr("y"); 1321 break; 1322 case _self.config.classTypes.circle : 1323 props["r"] = sentProps.radius; 1324 props["fill"] = sentProps.backgroundColor; 1325 props["stroke"] = sentProps.borderColor; 1326 props["stroke-width"] = sentProps.borderWidth; 1327 props["stroke-dasharray"] = sentProps.borderStyle; 1328 props["fill-opacity"] = sentProps.backgroundOpacity; 1329 props["stroke-opacity"] = sentProps.borderOpacity; 1330 1331 newDimU.x = sentProps.x; 1332 newDimU.y = sentProps.y; 1333 oldDimU.x = hbu.element.attr("cx"); 1334 oldDimU.y = hbu.element.attr("cy"); 1335 break; 1336 case _self.config.classTypes.ellipse : 1337 props["rx"] = sentProps.hRadius; 1338 props["ry"] = sentProps.vRadius; 1339 props["fill"] = sentProps.backgroundColor; 1340 props["stroke"] = sentProps.borderColor; 1341 props["stroke-width"] = sentProps.borderWidth; 1342 props["stroke-dasharray"] = sentProps.borderStyle; 1343 props["fill-opacity"] = sentProps.backgroundOpacity; 1344 props["stroke-opacity"] = sentProps.borderOpacity; 1345 1346 newDimU.x = sentProps.x; 1347 newDimU.y = sentProps.y; 1348 oldDimU.x = hbu.element.attr("cx"); 1349 oldDimU.y = hbu.element.attr("cy"); 1350 break; 1351 case _self.config.classTypes.image : 1352 //props["src"] = sentProps.url; 1353 props["width"] = sentProps.width; 1354 props["height"] = sentProps.height; 1355 1356 newDimU.x = sentProps.x; 1357 newDimU.y = sentProps.y; 1358 oldDimU.x = hbu.element.attr("x"); 1359 oldDimU.y = hbu.element.attr("y"); 1360 break; 1361 case _self.config.classTypes.icon : 1362 props["scale"] = sentProps.scaleFactor.toFixed(1); 1363 hbu.iconName = sentProps.name; 1364 1365 newDimU.x = sentProps.x; 1366 newDimU.y = sentProps.y; 1367 break; 1368 default : 1369 } 1370 1371 props["rotation"] = sentProps.rotationDegree; 1372 1373 // update element 1374 setElementProperties(hbu.element, props); 1375 1376 // resize helpers 1377 var bbox = hbu.element.getBBox(); 1378 var bboxWidth = parseFloat(bbox.width); 1379 var bboxHeight = parseFloat(bbox.height); 1380 hbu.attr("x", bbox.x - 1); 1381 hbu.attr("y", bbox.y - 1); 1382 hbu.attr("width", (bboxWidth !== 0 ? bboxWidth + 2 : 3)); 1383 hbu.attr("height", (bboxHeight !== 0 ? bboxHeight + 2 : 3)); 1384 1385 if (jsData.element.type == _self.config.classTypes.icon) { 1386 oldDimU.x = Math.round(hbu.attr("x") + 1); 1387 oldDimU.y = Math.round(hbu.attr("y") + 1); 1388 } 1389 1390 // redraw circleSet 1391 hbu.circleSet.remove(); 1392 hbu.circleSet = null; 1393 delete hbu.circleSet; 1394 var circleSet = drawCircleSet(bbox.x, bbox.y, bboxWidth, bboxHeight); 1395 circleSet.attr(_self.config.attributes.circleSet); 1396 if (selectedObj != null && selectedObj.visibleSelect && selectedObj.uuid == hbu.uuid) { 1397 circleSet.attr(_self.config.attributes.opacityVisible); 1398 } 1399 hbu.circleSet = circleSet; 1400 1401 // rotate 1402 hbu.element.rotate(props["rotation"], bbox.x + bboxWidth / 2, bbox.y + bboxHeight / 2, true); 1403 hbu.rotate(props["rotation"], bbox.x + bboxWidth / 2, bbox.y + bboxHeight / 2, true); 1404 hbu.circleSet.rotate(props["rotation"], bbox.x + bboxWidth / 2, bbox.y + bboxHeight / 2, true); 1405 hbu.element.attr("rotation", props["rotation"]); 1406 1407 // move element and helpers if needed 1408 if (typeof oldDimU.x !== "undefined") { 1409 var diffXU = newDimU.x - oldDimU.x; 1410 var diffYU = newDimU.y - oldDimU.y; 1411 if (diffXU != 0 || diffYU != 0) { 1412 hbu.element.translate(diffXU, diffYU); 1413 hbu.translate(diffXU, diffYU); 1414 hbu.circleSet.translate(diffXU, diffYU); 1415 } 1416 } 1417 1418 if (selectedObj != null && selectedObj.uuid == hbu.uuid) { 1419 // transfer changes to dialog 1420 _self.transferPropertiesToDialog(selectedObj); 1421 } 1422 break; 1423 case "remove" : 1424 // find element to be removed 1425 var hbr = wbElements[sentProps.uuid]; 1426 if (hbr == null) { 1427 // not found ==> nothing to do 1428 if (_self.logging) { 1429 logDebug("Element to be removed does not exist anymore in this Whiteboard"); 1430 } 1431 break; 1432 } 1433 1434 var eluuid = hbr.uuid; 1435 var removeSelected = false; 1436 if (selectedObj != null && selectedObj.uuid == eluuid) { 1437 removeSelected = true; 1438 } 1439 1440 wbElements[eluuid] = null; 1441 delete wbElements[eluuid]; 1442 hbr.element.remove(); 1443 hbr.circleSet.remove(); 1444 hbr.remove(); 1445 1446 if (removeSelected) { 1447 selectedObj = null; 1448 _self.showProperties('editNoSelection'); 1449 } 1450 break; 1451 case "move" : 1452 // find element to be updated 1453 var hbm = wbElements[sentProps.uuid]; 1454 if (hbm == null) { 1455 // not found ==> nothing to do 1456 if (_self.logging) { 1457 logDebug("Element to be moved does not exist anymore in this Whiteboard"); 1458 } 1459 break; 1460 } 1461 1462 var oldDimM = {}, newDimM = {}, linePath = null; 1463 1464 switch (jsData.element.type) { 1465 case _self.config.classTypes.text : 1466 newDimM.x = sentProps.x; 1467 newDimM.y = sentProps.y; 1468 oldDimM.x = hbm.element.attr("x"); 1469 oldDimM.y = hbm.element.attr("y"); 1470 break; 1471 case _self.config.classTypes.freeLine : 1472 case _self.config.classTypes.straightLine : 1473 linePath = sentProps.path; 1474 break; 1475 case _self.config.classTypes.rectangle : 1476 newDimM.x = sentProps.x; 1477 newDimM.y = sentProps.y; 1478 oldDimM.x = hbm.element.attr("x"); 1479 oldDimM.y = hbm.element.attr("y"); 1480 break; 1481 case _self.config.classTypes.circle : 1482 newDimM.x = sentProps.x; 1483 newDimM.y = sentProps.y; 1484 oldDimM.x = hbm.element.attr("cx"); 1485 oldDimM.y = hbm.element.attr("cy"); 1486 break; 1487 case _self.config.classTypes.ellipse : 1488 newDimM.x = sentProps.x; 1489 newDimM.y = sentProps.y; 1490 oldDimM.x = hbm.element.attr("cx"); 1491 oldDimM.y = hbm.element.attr("cy"); 1492 break; 1493 case _self.config.classTypes.image : 1494 newDimM.x = sentProps.x; 1495 newDimM.y = sentProps.y; 1496 oldDimM.x = hbm.element.attr("x"); 1497 oldDimM.y = hbm.element.attr("y"); 1498 break; 1499 case _self.config.classTypes.icon : 1500 newDimM.x = sentProps.x; 1501 newDimM.y = sentProps.y; 1502 oldDimM.x = Math.round(hbm.attr("x") + 1); 1503 oldDimM.y = Math.round(hbm.attr("y") + 1); 1504 break; 1505 default : 1506 } 1507 1508 // move element and helpers if needed 1509 if (typeof oldDimM.x !== "undefined") { 1510 var diffXM = newDimM.x - oldDimM.x; 1511 var diffYM = newDimM.y - oldDimM.y; 1512 if (diffXM != 0 || diffYM != 0) { 1513 hbm.element.translate(diffXM, diffYM); 1514 hbm.translate(diffXM, diffYM); 1515 hbm.circleSet.translate(diffXM, diffYM); 1516 1517 if (selectedObj != null && selectedObj.uuid == hbm.uuid) { 1518 // transfer changes to dialog 1519 _self.transferPropertiesToDialog(selectedObj); 1520 } 1521 } 1522 } 1523 1524 // redraw line and helpers if line was moved 1525 if (linePath != null) { 1526 hbm.element.attr("path", linePath); 1527 1528 var bboxM = hbm.element.getBBox(); 1529 var bboxWidthM = parseFloat(bboxM.width); 1530 var bboxHeightM = parseFloat(bboxM.height); 1531 hbm.attr("x", bboxM.x - 1); 1532 hbm.attr("y", bboxM.y - 1); 1533 hbm.attr("width", (bboxWidthM !== 0 ? bboxWidthM + 2 : 3)); 1534 hbm.attr("height", (bboxHeightM !== 0 ? bboxHeightM + 2 : 3)); 1535 1536 // redraw circleSet 1537 hbm.circleSet.remove(); 1538 hbm.circleSet = null; 1539 delete hbm.circleSet; 1540 var circleSetM = drawCircleSet(bboxM.x, bboxM.y, bboxWidthM, bboxHeightM); 1541 circleSetM.attr(_self.config.attributes.circleSet); 1542 if (selectedObj != null && selectedObj.visibleSelect && selectedObj.uuid == hbm.uuid) { 1543 circleSetM.attr(_self.config.attributes.opacityVisible); 1544 } 1545 hbm.circleSet = circleSetM; 1546 1547 // rotate 1548 var rotation = hbm.element.attr("rotation"); 1549 hbm.rotate(rotation, bboxM.x + bboxWidthM / 2, bboxM.y + bboxHeightM / 2, true); 1550 hbm.circleSet.rotate(rotation, bboxM.x + bboxWidthM / 2, bboxM.y + bboxHeightM / 2, true); 1551 } 1552 break; 1553 case "toFront" : 1554 // find element to be brought to top 1555 var hbf = wbElements[sentProps.uuid]; 1556 if (hbf == null) { 1557 // not found ==> nothing to do 1558 if (_self.logging) { 1559 logDebug("Element to be brought to front does not exist anymore in this Whiteboard"); 1560 } 1561 break; 1562 } 1563 1564 hbf.element.toFront(); 1565 hbf.circleSet.toFront(); 1566 hbf.toFront(); 1567 hbf.attr(_self.config.attributes.opacityHidden); 1568 break; 1569 case "toBack" : 1570 // find element to be brought to back 1571 var hbb = wbElements[sentProps.uuid]; 1572 if (hbb == null) { 1573 // not found ==> nothing to do 1574 if (_self.logging) { 1575 logDebug("Element to be brought to back does not exist anymore in this Whiteboard"); 1576 } 1577 break; 1578 } 1579 1580 hbb.toBack(); 1581 hbb.circleSet.toBack(); 1582 hbb.element.toBack(); 1583 hbb.attr(_self.config.attributes.opacityHidden); 1584 break; 1585 case "clear" : 1586 paper.clear(); 1587 _self.showProperties('editNoSelection'); 1588 for (eluuid in wbElements) { 1589 wbElements[eluuid] = null; 1590 delete wbElements[eluuid]; 1591 } 1592 break; 1593 case "resize" : 1594 var width = jsData.parameters.width; 1595 var height = jsData.parameters.height; 1596 whiteboard.css({width: width + 'px', height: height + 'px'}); 1597 paper.setSize(parseInt(width), parseInt(height)); 1598 break; 1599 default: 1600 } 1601 1602 // show new message in the event monitoring pane 1603 prependMessage(jsData.message); 1604 } 1605 } 1606 } 1607 1608 /** Sends a message if user has been joined the whiteboard. 1609 * @public 1610 * @param usersCount current user count. 1611 */ 1612 this.joinUser = function(usersCount) { 1613 // send changes to server 1614 this.sendChanges({ 1615 "action": "join", 1616 "parameters": { 1617 "usersCount": usersCount 1618 } 1619 }); 1620 } 1621 1622 /** Sends any changes on client side to the server (see actions). 1623 * @public 1624 * @param jsObject changes as JavaScript object. 1625 */ 1626 this.sendChanges = function(jsObject) { 1627 // set timestamp 1628 var curDate = new Date(); 1629 jsObject.timestamp = curDate.getTime() + curDate.getTimezoneOffset() * 60000; 1630 1631 // set user 1632 jsObject.user = this.user; 1633 1634 // set whiteboard Id 1635 jsObject.whiteboardId = this.whiteboardId; 1636 1637 var outgoingMessage = JSON.stringify(jsObject); 1638 if (_self.logging) { 1639 logOutgoing(outgoingMessage); 1640 } 1641 1642 // send changes to all subscribed clients 1643 this.connectedEndpoint.push(this.pubSubUrl, null, jQuery.atmosphere.request = {data: 'message=' + outgoingMessage}); 1644 1645 // set data in hidden field 1646 //jQuery("#transferedJsonData").val(JSON.stringify(jsObject)); 1647 // send ajax request 1648 //transferJsonData(); 1649 } 1650 1651 // private access ======================= 1652 1653 // register handlers for drag & drop on element 1654 var ddStartEl = function () { 1655 if (!modeSwitcher.moveMode) { 1656 return false; 1657 } 1658 1659 this.odx = 0; 1660 this.ody = 0; 1661 1662 this.attr("cursor", "move"); 1663 _self.dragDropStart = true; 1664 } 1665 1666 var ddMoveEl = function (dx, dy) { 1667 if (!modeSwitcher.moveMode) { 1668 return false; 1669 } 1670 1671 this.element.translate(dx - this.odx, dy - this.ody); 1672 this.translate(dx - this.odx, dy - this.ody); 1673 this.circleSet.translate(dx - this.odx, dy - this.ody); 1674 this.odx = dx; 1675 this.ody = dy; 1676 } 1677 1678 var ddStopEl = function () { 1679 if (!modeSwitcher.moveMode) { 1680 return false; 1681 } 1682 1683 if (lastHoverObj != null) { 1684 // overlapping ==> handle current overlapped element (hide / show helpers) 1685 if (selectedObj != null && selectedObj.uuid == this.uuid && selectedObj.visibleSelect) { 1686 this.attr(_self.config.attributes.selectBoxVisible); 1687 this.circleSet.attr(_self.config.attributes.opacityVisible); 1688 } else { 1689 this.attr(_self.config.attributes.opacityHidden); 1690 } 1691 this.attr("cursor", "default"); 1692 1693 // handle new element which overlapps the current one - show "move helper" 1694 lastHoverObj.circleSet.attr(_self.config.attributes.opacityHidden); 1695 lastHoverObj.attr(_self.config.attributes.moveBoxVisible); 1696 lastHoverObj.attr("cursor", "move"); 1697 lastHoverObj = null; 1698 } 1699 1700 _self.dragDropStart = false; 1701 1702 var updatePropsDialog = (selectedObj != null && selectedObj.uuid == this.uuid); 1703 var objChanges = { 1704 "action": "move", 1705 "element": { 1706 "type": this.classType, 1707 "properties": { 1708 "uuid": this.uuid 1709 } 1710 } 1711 }; 1712 1713 // show current coordinates in the properties dialog (if needed) and send changes to server 1714 switch (this.classType) { 1715 case _self.config.classTypes.text : 1716 objChanges.element.properties.x = this.element.attr("x"); 1717 objChanges.element.properties.y = this.element.attr("y"); 1718 _self.sendChanges(objChanges); 1719 1720 if (updatePropsDialog) { 1721 jQuery(idSubviewProperties + "_textCx").val(objChanges.element.properties.x); 1722 jQuery(idSubviewProperties + "_textCy").val(objChanges.element.properties.y); 1723 } 1724 break; 1725 case _self.config.classTypes.freeLine : 1726 case _self.config.classTypes.straightLine : 1727 objChanges.element.properties.path = this.element.attr("path") + ''; 1728 _self.sendChanges(objChanges); 1729 1730 break; 1731 case _self.config.classTypes.rectangle : 1732 objChanges.element.properties.x = this.element.attr("x"); 1733 objChanges.element.properties.y = this.element.attr("y"); 1734 _self.sendChanges(objChanges); 1735 1736 if (updatePropsDialog) { 1737 jQuery(idSubviewProperties + "_rectCx").val(objChanges.element.properties.x); 1738 jQuery(idSubviewProperties + "_rectCy").val(objChanges.element.properties.y); 1739 } 1740 break; 1741 case _self.config.classTypes.circle : 1742 objChanges.element.properties.x = this.element.attr("cx"); 1743 objChanges.element.properties.y = this.element.attr("cy"); 1744 _self.sendChanges(objChanges); 1745 1746 if (updatePropsDialog) { 1747 jQuery(idSubviewProperties + "_circleCx").val(objChanges.element.properties.x); 1748 jQuery(idSubviewProperties + "_circleCy").val(objChanges.element.properties.y); 1749 } 1750 break; 1751 case _self.config.classTypes.ellipse : 1752 objChanges.element.properties.x = this.element.attr("cx"); 1753 objChanges.element.properties.y = this.element.attr("cy"); 1754 _self.sendChanges(objChanges); 1755 1756 if (updatePropsDialog) { 1757 jQuery(idSubviewProperties + "_ellipseCx").val(objChanges.element.properties.x); 1758 jQuery(idSubviewProperties + "_ellipseCy").val(objChanges.element.properties.y); 1759 } 1760 break; 1761 case _self.config.classTypes.image : 1762 objChanges.element.properties.x = this.element.attr("x"); 1763 objChanges.element.properties.y = this.element.attr("y"); 1764 _self.sendChanges(objChanges); 1765 1766 if (updatePropsDialog) { 1767 jQuery(idSubviewProperties + "_imageCx").val(objChanges.element.properties.x); 1768 jQuery(idSubviewProperties + "_imageCy").val(objChanges.element.properties.y); 1769 } 1770 break; 1771 case _self.config.classTypes.icon : 1772 objChanges.element.properties.x = Math.round(this.attr("x") + 1); 1773 objChanges.element.properties.y = Math.round(this.attr("y") + 1); 1774 _self.sendChanges(objChanges); 1775 1776 if (updatePropsDialog) { 1777 jQuery(idSubviewProperties + "_iconCx").val(objChanges.element.properties.x); 1778 jQuery(idSubviewProperties + "_iconCy").val(objChanges.element.properties.y); 1779 } 1780 break; 1781 1782 default : 1783 } 1784 } 1785 1786 // register hover on element 1787 var hoverOverEl = function (event) { 1788 if (_self.dragDropStart) { 1789 lastHoverObj = this; 1790 return false; 1791 } 1792 1793 if (modeSwitcher.selectMode) { 1794 this.attr(_self.config.attributes.selectBoxVisible); 1795 this.attr("cursor", "default"); 1796 return true; 1797 } 1798 1799 if (modeSwitcher.moveMode) { 1800 if (selectedObj != null && selectedObj.uuid == this.uuid) { 1801 this.circleSet.attr(_self.config.attributes.opacityHidden); 1802 } 1803 this.attr(_self.config.attributes.moveBoxVisible); 1804 this.attr("cursor", "move"); 1805 return true; 1806 } 1807 1808 if (modeSwitcher.removeMode) { 1809 if (selectedObj != null && selectedObj.uuid == this.uuid) { 1810 this.circleSet.attr(_self.config.attributes.opacityHidden); 1811 } 1812 this.attr(_self.config.attributes.removeBoxVisible); 1813 this.attr("cursor", "crosshair"); 1814 return true; 1815 } 1816 1817 if (modeSwitcher.bringFrontMode) { 1818 if (selectedObj != null && selectedObj.uuid == this.uuid) { 1819 this.circleSet.attr(_self.config.attributes.opacityHidden); 1820 } 1821 this.attr(_self.config.attributes.bringFrontBackBoxVisible); 1822 this.attr("cursor", "default"); 1823 return true; 1824 } 1825 1826 if (modeSwitcher.bringBackMode) { 1827 if (selectedObj != null && selectedObj.uuid == this.uuid) { 1828 this.circleSet.attr(_self.config.attributes.opacityHidden); 1829 } 1830 this.attr(_self.config.attributes.bringFrontBackBoxVisible); 1831 this.attr("cursor", "default"); 1832 return true; 1833 } 1834 1835 if (modeSwitcher.cloneMode) { 1836 if (selectedObj != null && selectedObj.uuid == this.uuid) { 1837 this.circleSet.attr(_self.config.attributes.opacityHidden); 1838 } 1839 this.attr(_self.config.attributes.cloneBoxVisible); 1840 this.attr("cursor", "default"); 1841 return true; 1842 } 1843 1844 this.attr("cursor", whiteboard.css("cursor")); 1845 } 1846 1847 var hoverOutEl = function (event) { 1848 if (_self.dragDropStart) { 1849 lastHoverObj = null; 1850 return false; 1851 } 1852 1853 if (modeSwitcher.selectMode) { 1854 if (selectedObj == null || selectedObj.uuid != this.uuid || !selectedObj.visibleSelect) { 1855 this.attr(_self.config.attributes.opacityHidden); 1856 } 1857 return true; 1858 } 1859 1860 with (modeSwitcher) { 1861 if (moveMode || removeMode || bringFrontMode || bringBackMode || cloneMode) { 1862 if (selectedObj != null && selectedObj.uuid == this.uuid && selectedObj.visibleSelect) { 1863 this.attr(_self.config.attributes.selectBoxVisible); 1864 this.circleSet.attr(_self.config.attributes.opacityVisible); 1865 } else { 1866 this.attr(_self.config.attributes.opacityHidden); 1867 } 1868 this.attr("cursor", "default"); 1869 return true; 1870 } 1871 } 1872 } 1873 1874 // register handler for click on element 1875 var clickEl = function(event) { 1876 if (modeSwitcher.selectMode) { 1877 _self.selectElement(this); 1878 return true; 1879 } 1880 1881 if (modeSwitcher.removeMode) { 1882 _self.removeElement(this); 1883 return true; 1884 } 1885 1886 if (modeSwitcher.bringFrontMode) { 1887 _self.bringFrontElement(this); 1888 return true; 1889 } 1890 1891 if (modeSwitcher.bringBackMode) { 1892 _self.bringBackElement(this); 1893 return true; 1894 } 1895 1896 if (modeSwitcher.cloneMode) { 1897 _self.cloneElement(this); 1898 return true; 1899 } 1900 1901 return false; 1902 } 1903 1904 // mousedown, mousemove and mouseup handlers on whiteboard 1905 var mousedownHandler = function (event) { 1906 if (modeSwitcher.freeLineMode) { 1907 _self.drawFreeLineBegin(event.pageX, event.pageY); 1908 return false; 1909 } 1910 1911 if (modeSwitcher.straightLineMode) { 1912 _self.drawStraightLineBegin(event.pageX, event.pageY); 1913 return false; 1914 } 1915 1916 return false; 1917 } 1918 1919 var mousemoveHandler = function (event) { 1920 if (modeSwitcher.freeLineMode) { 1921 whiteboard.lineEl.path.attr("path", whiteboard.lineEl.path.attr("path") + "L" + (event.pageX - offsetLeft) + "," + (event.pageY - offsetTop)); 1922 return true; 1923 } 1924 1925 if (modeSwitcher.straightLineMode) { 1926 whiteboard.lineEl.pathArray[1] = ["L", event.pageX - offsetLeft, event.pageY - offsetTop]; 1927 whiteboard.lineEl.path.attr("path", whiteboard.lineEl.pathArray); 1928 return true; 1929 } 1930 } 1931 1932 var mouseupHandler = function () { 1933 whiteboard.unbind(".mmu"); 1934 if (whiteboard.lineEl.path) { 1935 var classType, defProperties, dialogType, transferMethod; 1936 if (modeSwitcher.freeLineMode) { 1937 classType = _self.config.classTypes.freeLine; 1938 defProperties = _self.config.properties.freeLine; 1939 dialogType = "editFreeLine"; 1940 transferMethod = "transferFreeLinePropertiesToDialog"; 1941 } else { 1942 classType = _self.config.classTypes.straightLine; 1943 defProperties = _self.config.properties.straightLine; 1944 dialogType = "editStraightLine"; 1945 transferMethod = "transferStraightLinePropertiesToDialog"; 1946 } 1947 1948 var hb = drawHelperBox(whiteboard.lineEl.path, classType, defProperties.rotation, null, true, null); 1949 wbElements[hb.uuid] = hb; 1950 1951 // send changes to server 1952 _self.sendChanges({ 1953 "action": "create", 1954 "element": { 1955 "type": classType, 1956 "properties": { 1957 "uuid": hb.uuid, 1958 "rotationDegree": defProperties.rotation, 1959 "path": whiteboard.lineEl.path.attr("path") + '', 1960 "color": whiteboard.lineEl.path.attr("stroke"), 1961 "lineWidth": whiteboard.lineEl.path.attr("stroke-width"), 1962 "lineStyle": getDasharrayValue(whiteboard.lineEl.path.attr("stroke-dasharray")), 1963 "opacity": whiteboard.lineEl.path.attr("stroke-opacity") 1964 } 1965 } 1966 }); 1967 1968 _self.showProperties(dialogType); 1969 _self[transferMethod](defProperties); 1970 1971 // reset 1972 whiteboard.lineEl.path = null; 1973 whiteboard.lineEl.pathArray = null; 1974 } 1975 } 1976 1977 // click handler on whiteboard 1978 var clickHandler = function(event) { 1979 if (modeSwitcher.rectangleMode) { 1980 _self.drawRectangle(event.pageX, event.pageY); 1981 return true; 1982 } 1983 1984 if (modeSwitcher.circleMode) { 1985 _self.drawCircle(event.pageX, event.pageY); 1986 return true; 1987 } 1988 1989 if (modeSwitcher.ellipseMode) { 1990 _self.drawEllipse(event.pageX, event.pageY); 1991 return true; 1992 } 1993 1994 if (modeSwitcher.imageMode) { 1995 _self.openImageDialog(event.pageX, event.pageY); 1996 return true; 1997 } 1998 1999 if (modeSwitcher.iconMode) { 2000 _self.openIconsDialog(event.pageX, event.pageY); 2001 return true; 2002 } 2003 2004 if (modeSwitcher.textMode) { 2005 _self.openTextDialog(event.pageX, event.pageY); 2006 return true; 2007 } 2008 2009 return false; 2010 } 2011 2012 // mouseleave handler on whiteboard 2013 var mouseleaveHandler = function(event) { 2014 if (modeSwitcher.freeLineMode || modeSwitcher.straightLineMode) { 2015 mouseupHandler(); 2016 } 2017 2018 return false; 2019 } 2020 2021 // draw helper shapes around the element 2022 var drawHelperBox = function(el, classType, rotation, scale, select, id) { 2023 // scale 2024 if (scale && scale != 1.0) { 2025 el.scale(scale, scale); 2026 } 2027 2028 var bbox = el.getBBox(); 2029 var bboxWidth = parseFloat(bbox.width); 2030 var bboxHeight = parseFloat(bbox.height); 2031 var helperRect = paper.rect(bbox.x - 1, bbox.y - 1, (bboxWidth !== 0 ? bboxWidth + 2 : 3), (bboxHeight !== 0 ? bboxHeight + 2 : 3)); 2032 helperRect.attr(_self.config.attributes.helperRect); 2033 helperRect.hover(hoverOverEl, hoverOutEl); 2034 helperRect.click(clickEl); 2035 helperRect.drag(ddMoveEl, ddStartEl, ddStopEl); 2036 2037 // draw invisible circles for possible later selection 2038 var circleSet = drawCircleSet(bbox.x, bbox.y, bboxWidth, bboxHeight); 2039 circleSet.attr(_self.config.attributes.circleSet); 2040 2041 // rotate 2042 if (rotation && rotation != 0) { 2043 el.rotate(rotation, bbox.x + bboxWidth / 2, bbox.y + bboxHeight / 2, true); 2044 el.attr("rotation", parseInt(rotation)); 2045 circleSet.rotate(rotation, bbox.x + bboxWidth / 2, bbox.y + bboxHeight / 2, true); 2046 helperRect.rotate(rotation, bbox.x + bboxWidth / 2, bbox.y + bboxHeight / 2, true); 2047 } 2048 2049 // set references 2050 helperRect.element = el; 2051 helperRect.circleSet = circleSet; 2052 helperRect.classType = classType; 2053 if (id == null) { 2054 helperRect.uuid = uuid(); 2055 } else { 2056 helperRect.uuid = id; 2057 } 2058 2059 if (select) { 2060 if (selectedObj != null) { 2061 // hide last selection 2062 selectedObj.attr(_self.config.attributes.opacityHidden); 2063 selectedObj.circleSet.attr(_self.config.attributes.opacityHidden); 2064 } 2065 2066 // set drawn element as selected 2067 selectedObj = helperRect; 2068 selectedObj.visibleSelect = false; 2069 } 2070 2071 return helperRect; 2072 } 2073 2074 // draw icons in the "choose icon" dialog 2075 var drawIcons = function() { 2076 var x = 0, y = 0; 2077 var fillStroke = {fill: "#000", stroke: "none"}; 2078 var fillNone = {fill: "#000", opacity: 0}; 2079 var fillHover = {fill: "90-#0050af-#002c62", stroke: "#FF0000"}; 2080 var iconPaper = Raphael("iconsArea", 600, 360); 2081 var wbIcons = _self.config.svgIconSet; 2082 2083 for (var name in wbIcons) { 2084 var curIcon = iconPaper.path(wbIcons[name]).attr(fillStroke).translate(x, y); 2085 curIcon.offsetX = x + 20; 2086 curIcon.offsetY = y + 20; 2087 var overlayIcon = iconPaper.rect(x, y, 40, 40).attr(fillNone); 2088 overlayIcon.icon = curIcon; 2089 overlayIcon.iconName = name; 2090 overlayIcon.click(function (event) { 2091 dialogIcons.dialog("close"); 2092 var iconElement = paper.path(this.icon.attr("path")).attr(fillStroke).translate(whiteboard.iconEl.cx - this.icon.offsetX, whiteboard.iconEl.cy - this.icon.offsetY); 2093 var hb = drawHelperBox(iconElement, _self.config.classTypes.icon, _self.config.properties.icon.rotation, _self.config.properties.icon.scale, true, null); 2094 hb.iconName = this.iconName; 2095 wbElements[hb.uuid] = hb; 2096 2097 // send changes to server 2098 var xC = Math.round(hb.attr("x") + 1); 2099 var yC = Math.round(hb.attr("y") + 1); 2100 _self.sendChanges({ 2101 "action": "create", 2102 "element": { 2103 "type": _self.config.classTypes.icon, 2104 "properties": { 2105 "uuid": hb.uuid, 2106 "x": xC, 2107 "y": yC, 2108 "rotationDegree": _self.config.properties.icon.rotation, 2109 "name": this.iconName, 2110 "scaleFactor": _self.config.properties.icon.scale 2111 } 2112 } 2113 }); 2114 2115 _self.showProperties('editIcon'); 2116 _self.transferIconPropertiesToDialog(xC, yC, { 2117 "scale": _self.config.properties.icon.scale, 2118 "rotation": _self.config.properties.icon.rotation 2119 }); 2120 event.stopPropagation(); 2121 event.preventDefault(); 2122 }).hover(function () { 2123 this.icon.attr(fillHover); 2124 }, function () { 2125 this.icon.attr(fillStroke); 2126 }); 2127 x += 40; 2128 if (x > 560) { 2129 x = 0; 2130 y += 40; 2131 } 2132 } 2133 } 2134 2135 var drawCircleSet = function(x, y, width, height) { 2136 var c1 = paper.circle(x, y, 3); 2137 var c2 = paper.circle(x + width, y, 3); 2138 var c3 = paper.circle(x, y + height, 3); 2139 var c4 = paper.circle(x + width, y + height, 3); 2140 2141 // build a set 2142 var circleSet = paper.set(); 2143 circleSet.push(c1, c2, c3, c4); 2144 2145 return circleSet; 2146 } 2147 2148 var setElementProperties = function(el, propsObj) { 2149 for (prop in propsObj) { 2150 if (prop != "rotation") { 2151 if (prop == "stroke-dasharray") { 2152 el.attr(prop, _self.config.dasharrayMapping[propsObj[prop]]); 2153 } else { 2154 el.attr(prop, propsObj[prop]); 2155 } 2156 } 2157 } 2158 } 2159 2160 var getSelectedProperties = function(el, propsObj) { 2161 var selectedProps = {}; 2162 for (prop in propsObj) { 2163 if (prop == "stroke-dasharray") { 2164 selectedProps[prop] = getDasharrayValue(el.attr(prop)); 2165 } else { 2166 selectedProps[prop] = el.attr(prop); 2167 } 2168 } 2169 2170 return selectedProps; 2171 } 2172 2173 var getDasharrayValue = function(label) { 2174 for (value in _self.config.dasharrayMapping) { 2175 if (label == _self.config.dasharrayMapping[value]) { 2176 return value; 2177 } 2178 } 2179 2180 return "No"; 2181 } 2182 2183 var prependMessage = function(msg) { 2184 jQuery("<p style='margin: 2px 0 2px 0'>" + msg + "</p>").prependTo(".monitoringGroup"); 2185 } 2186 2187 var logIncoming = function(data) { 2188 log.info("INCOMING: " + data); 2189 } 2190 2191 var logOutgoing = function(data) { 2192 log.warn("OUTGOING: " + data); 2193 } 2194 2195 var logProfile = function(timestamp) { 2196 var curDate = new Date(); 2197 log.profile("TIME BETWEEN BROADCASTING AND RECEIVING: " + (curDate.getTime() + curDate.getTimezoneOffset() * 60000 - timestamp) + " ms"); 2198 } 2199 2200 var logDebug = function(msg) { 2201 log.debug(msg); 2202 } 2203 2204 // initialize whiteboard 2205 // 1. register handlers on whiteboard 2206 whiteboard.bind("click", clickHandler); 2207 whiteboard.bind("mousedown", mousedownHandler); 2208 whiteboard.bind("mouseleave", mouseleaveHandler); 2209 // 2. draw icons 2210 drawIcons(); 2211 } 2212