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