1 /**
  2  * This object is responsable for creating and updating the properties for figures
  3  *
  4  * @constructor
  5  * @this {Builder}
  6  * 
  7  *  Builder allows for an {Array} of {BuilderProperty}'s to be displayed in a property panel,
  8  *  edited values update the owner
  9  *  @author Zack Newsham <zack_newsham@yahoo.co.uk>
 10  **/
 11 function Builder(){
 12     
 13 }
 14 
 15 
 16 /**Creates a {Builder} out of JSON parsed object
 17  *@param {JSONObject} o - the JSON parsed object
 18  *@return {Builder} a newly constructed Builder
 19  *@author Alex Gheorghiu <alex@scriptoid.com>
 20  **/
 21 Builder.load = function(o){
 22     var newBuilder = new Builder();
 23     newBuilder.properties = BuilderProperty.loadArray(o.properties);
 24     newBuilder.figureId = o.figureId;
 25     return newBuilder;
 26 }
 27 
 28 /**
 29  *Creates the property panel for a figure
 30  *@param {DOMObject} DOMObject - the div of the properties panel
 31  *@param {Figure} figure - the figure for which the properties will be displayed
 32  **/
 33 Builder.contructPropertiesPanel = function(DOMObject, figure){
 34     for(var i=0; i<figure.properties.length; i++){
 35         figure.properties[i].injectInputArea(DOMObject,figure.id);
 36     }
 37 }
 38 
 39 /**
 40  *Creates the properties for main CanvasProps
 41  *@param {DOMObject} DOMObject - the div of the properties panel
 42  *@param {CanvasProps} canvasProps - the CanvasProps for which the properties will be displayed
 43  **/
 44 Builder.constructCanvasPropertiesPanel = function(DOMObject, canvasProps){
 45     var div = document.createElement("div");
 46     //div.innerHTML = '<div></div>';why is this here?
 47 
 48     //width
 49     var divWidth = document.createElement("div");
 50     divWidth.innerHTML = '<div class = "label">Width</div>';
 51 
 52     var inputWidth = document.createElement("input");
 53     inputWidth.type = "text";
 54     inputWidth.className = "text"; //required for onkeydown
 55     //inputWidth.style.cssText = "float: right";
 56     inputWidth.value = canvasProps.getWidth();
 57     divWidth.appendChild(inputWidth);
 58 
 59     div.appendChild(divWidth);
 60 
 61     //height
 62     var divHeight = document.createElement("div");
 63     divHeight.innerHTML = '<div class = "label">Height</div>';
 64 
 65     var inputHeight = document.createElement("input");
 66     inputHeight.type = "text";
 67     inputHeight.className = "text"; //required for onkeydown
 68     //inputHeight.style.cssText = "float: right";
 69     inputHeight.value = canvasProps.getHeight();
 70     divHeight.appendChild(inputHeight);
 71 
 72     div.appendChild(divHeight);
 73 
 74     //update button
 75     var divButton = document.createElement("div");
 76     divButton.innerHTML = '<div class = "label"></div>';
 77 
 78     var btnUpdate = document.createElement("input");
 79     btnUpdate.setAttribute("type", "button");
 80     btnUpdate.setAttribute("value", "Update");
 81 
 82     btnUpdate.onclick = function(){
 83         //update canvas props
 84         
 85         Log.group("builder.js->constructCanvasPropertiesPanel()->Canvas update");
 86         //Log.info('Nr of actions in Undo system: ' + History.ACTIONS.length);
 87         //Did the width change?
 88         if(doUndo && canvasProps.getWidth() != inputWidth.value){            
 89             var undo = new CanvasResizeCommand(canvasProps, History.OBJECT_STATIC, "Width", canvasProps.getWidth(), inputWidth.value);
 90             canvasProps.width = inputWidth.value;
 91             History.addUndo(undo);
 92         }
 93         
 94         //Did the height change?
 95         if(doUndo && canvasProps.getHeight() != inputHeight.value){            
 96             var undo = new CanvasResizeCommand(canvasProps, History.OBJECT_STATIC, "Height", canvasProps.getHeight(), inputHeight.value);
 97             canvasProps.height = inputHeight.value;
 98             History.addUndo(undo);
 99         }
100         //Log.info('Nr of actions in Undo system: ' + History.ACTIONS.length);
101         Log.groupEnd();
102         
103         canvasProps.setWidth(inputWidth.value);
104         canvasProps.setHeight(inputHeight.value);
105         //alert(canvasProps);
106 
107         draw();
108     };
109 
110     divButton.appendChild(btnUpdate);
111     div.appendChild(divButton);
112 
113 
114 
115     DOMObject.appendChild(div);
116 }
117 
118 
119 
120 /** The stucture that will hold any changable property of a shape
121  *
122  * @constructor
123  * @this {Builder}
124  * @param {String} name - the name of the property
125  * @param {String} property - the property (in dot notation) we are using in the form of 'primitives.0.style.strokeStyle'
126  * @param {Object} type - could be either, 'Color', 'Boolean', 'Text' or {Array}
127  * In case it's an {Array} it is of type [{Text,Value}] and is used to generate a DD menu
128  */
129 function BuilderProperty(name, property, type){
130     this.name = name;
131     this.property = property;
132     this.type = type;
133 //    Log.info('BuilderProperty(): ' + 'Propery type: ' + this.type + ' name: ' + this.name + ' property: ' + this.property);
134 }
135 
136 /**Color property type*/
137 BuilderProperty.TYPE_COLOR = 'Color';
138 
139 /**Text property type*/
140 BuilderProperty.TYPE_TEXT = 'Text';
141 
142 /**SingleText property type*/
143 BuilderProperty.TYPE_SINGLE_TEXT = 'SingleText';
144 
145 /**Text size property type*/
146 BuilderProperty.TYPE_TEXT_FONT_SIZE = 'TextFontSize';
147 
148 /**Font family property type*/
149 BuilderProperty.TYPE_TEXT_FONT_FAMILY = 'TextFontFamily';
150 
151 /**Text aligment property type*/
152 BuilderProperty.TYPE_TEXT_FONT_ALIGNMENT = 'TextFontAlignment';
153 
154 /**Boolean property type*/
155 BuilderProperty.TYPE_BOOLEAN = 'Boolean';
156 
157 /**Line width property type*/
158 BuilderProperty.TYPE_LINE_WIDTH = 'LineWidth';
159 
160 /**Image Fill type*/
161 BuilderProperty.TYPE_IMAGE_FILL = 'ImageFill';
162 
163 /**File Upload type*/
164 BuilderProperty.TYPE_IMAGE_UPLOAD = "ImageUpload";
165 
166 /**Connector's end property type*/
167 BuilderProperty.TYPE_CONNECTOR_END= 'ConnectorEnd';
168 
169 BuilderProperty.IMAGE_FILL = [{Text: 'No Scaling', Value: CanvasImage.FIXED_NONE},{Text: 'Fit to Area', Value: CanvasImage.FIXED_BOTH},{Text: 'Fit to Width',Value: CanvasImage.FIXED_WIDTH},{Text: 'Fit to Height',Value: CanvasImage.FIXED_HEIGHT},{Text: ' Auto Fit',Value: CanvasImage.FIXED_AUTO}]
170 /**Line widths*/
171 BuilderProperty.LINE_WIDTHS = [
172     {Text: '1px', Value: '1'},{Text: '2px',Value: '2'},{Text: '3px',Value: '3'},
173     {Text: '4px',Value: '4'},{Text: '5px',Value: '5'},{Text: '6px',Value: '6'},
174     {Text: '7px',Value: '7'},{Text: '8px',Value: '8'},{Text: '9px',Value: '9'},
175     {Text: '10px',Value: '10'}];
176 
177 /**Font sizes*/
178 BuilderProperty.FONT_SIZES = [];
179 for(var i=0; i<73; i++){
180   BuilderProperty.FONT_SIZES.push({Text:i+'px', Value:i});
181 }
182 
183 /**Connector ends*/
184 BuilderProperty.CONNECTOR_ENDS = [{Text:'Normal', Value:'Normal'},{Text:'Arrow', Value:'Arrow'},
185     {Text:'Empty Triangle', Value:'Empty'},{Text:'Filled Triangle', Value:'Filled'}];
186 
187 /**Display separator*/
188 BuilderProperty.SEPARATOR = 'SEPARATOR';
189 
190 /**Creates a {BuilderProperty} out of JSON parsed object
191  *@param {JSONObject} o - the JSON parsed object
192  *@return {BuilderProperty} a newly constructed Point
193  *@author Alex Gheorghiu <alex@scriptoid.com>
194  **/
195 BuilderProperty.load = function(o){
196     var prop = new BuilderProperty();
197     prop.name = o.name;
198     prop.property = o.property;
199     prop.type = o.type;
200     return prop;
201 }
202 
203 
204 /**Creates an array of BuilderProperties from an array of {JSONObject}s
205  *@param {Array} v - the array of JSONObjects
206  *@return an {Array} of {BuilderProperty}-ies
207  *@author Alex Gheorghiu <alex@scriptoid.com>
208  **/
209 BuilderProperty.loadArray = function(v){
210     var newProps = [];
211     for(var i=0; i< v.length; i++){
212         newProps.push(BuilderProperty.load(v[i]));
213     }
214     return newProps;
215 }
216 
217 BuilderProperty.prototype = {
218 
219     toString:function(){
220         return 'Propery type: ' + this.type + ' name: ' + this.name + ' property: ' + this.property;
221     },
222 
223     equals : function(anotherBuilderProperty){
224         return this.type == anotherBuilderProperty.type
225             && this.name == anotherBuilderProperty.name
226             && this.property == anotherBuilderProperty.property;
227     },
228     
229     /**
230      *Generates a HTML fragment to allow to edit its property.
231      *For example if current property is a color then this method will
232      *inject a color picker in the specified DOMObject
233      *
234      *@param {HTMLElement} DOMObject - the div of the properties panel
235      *@param {Number} figureId - the id of the figure we are using
236      */
237     injectInputArea:function(DOMObject,figureId){
238         if(this.name == BuilderProperty.SEPARATOR){
239             DOMObject.appendChild(document.createElement("hr"));
240             return;
241         }
242         else if(this.type == BuilderProperty.TYPE_COLOR){
243             this.generateColorCode(DOMObject,figureId);
244         }
245         else if(this.type == BuilderProperty.TYPE_TEXT){
246             this.generateTextCode(DOMObject,figureId);
247         }
248         else if(this.type == BuilderProperty.TYPE_SINGLE_TEXT){
249             this.generateSingleTextCode(DOMObject,figureId);
250         }
251         else if(this.type == BuilderProperty.TYPE_TEXT_FONT_SIZE){            
252             this.generateArrayCode(DOMObject,figureId, BuilderProperty.FONT_SIZES);
253 //            this.generateFontSizesCode(DOMObject,figureId);
254         }
255         else if(this.type == BuilderProperty.TYPE_TEXT_FONT_FAMILY){
256             this.generateArrayCode(DOMObject,figureId, Text.FONTS);
257         }
258         else if(this.type == BuilderProperty.TYPE_TEXT_FONT_ALIGNMENT){
259             this.generateArrayCode(DOMObject,figureId, Text.ALIGNMENTS);
260         }
261         else if(this.type == BuilderProperty.TYPE_CONNECTOR_END){
262             this.generateArrayCode(DOMObject,figureId, BuilderProperty.CONNECTOR_ENDS);
263         }
264         else if(this.type == BuilderProperty.TYPE_LINE_WIDTH){
265             this.generateArrayCode(DOMObject,figureId, BuilderProperty.LINE_WIDTHS);
266         }
267         else if(this.type == BuilderProperty.TYPE_IMAGE_FILL){
268             this.generateArrayCode(DOMObject,figureId, BuilderProperty.IMAGE_FILL);
269         }
270         else if(this.type == BuilderProperty.TYPE_IMAGE_UPLOAD){
271             this.generateFileCode(DOMObject, figureId)
272         }
273     },
274 
275 
276     /**
277      *Creates an Ajax file upload element
278      *@param {HTMLElement} DOMObject - the div of the properties panel
279      *@param {Number} figureId - the id of the figure we are using
280      */
281     generateFileCode:function(DOMObject, figureId){
282         var d = new Date();
283         var uniqueId = d.getTime();
284         var value = this.getValue(figureId);
285         var div = document.createElement("div");
286         div.innerHTML = '<div class=label style="width: 100%"><div style="float: left;">' + this.name + '</div><div style="float: right; clear: right;" align="right"></div></div>';
287 
288         var upload = document.createElement("input");
289         upload.type = "submit";
290         upload.value = "Upload";
291         upload.style.cssText="float: right; clear: both;";
292         upload.onclick = function(figureId,property){
293                             return function(){
294                                 updateFigure(figureId, property, "")
295                             }
296                         }(figureId, this.property);
297         
298         var file = document.createElement("input" );
299         file.type = "file";
300         file.id = "fileToUpload";
301         file.name = "fileToUpload";
302         file.style.cssText="width: 150px; float: right;";
303         div.children[0].children[1].appendChild(file);
304         div.children[0].appendChild(upload);
305         DOMObject.appendChild(div);
306     },
307 
308     /**
309      *Creates a boolean editor; usually a chechbox
310      *
311      *@param {HTMLElement} DOMObject - the div of the properties panel
312      *@param {Number} figureId - the id of the figure we are using
313      **/
314     generateBooleanCode:function(DOMObject,figureId){
315         var d = new Date();
316         var uniqueId = d.getTime();
317         var value = this.getValue(figureId);
318         var div = document.createElement("div");
319         div.innerHTML = '<div class=label>' + this.name + '</div>';
320 
321         var check = document.createElement("input");
322         check.type = "checkbox"
323         check.className = "text"; //required for onkeydown
324         check.style.cssText ="float: right";
325         check.checked = value;
326         div.children[0].appendChild(check);
327         check.onclick = function(figureId,property){
328                             return function(){
329                                 updateFigure(figureId, property, this.checked)
330                             }
331                         }(figureId, this.property);
332                         
333         DOMObject.appendChild(div);
334     },
335 
336 
337     /**Generate the code to edit the text.
338      *The text got updated when you leave the input area
339      *
340      *@param {HTMLElement} DOMObject - the div of the properties panel
341      *@param {Number} figureId - the id of the figure we are using    
342      **/
343     generateTextCode:function(DOMObject,figureId){
344         var uniqueId = new Date().getTime();
345         var value = this.getValue(figureId);
346 
347         var div = document.createElement("div");
348         div.className = "textLine";
349         div.innerHTML = '<div class = "label">' + this.name + '</div>';
350 
351         var text = document.createElement("textarea");
352         text.className = "text"; //required for onkeydown
353         //text.style.cssText ="float: right";
354         //text.style.cssText ="float: left";
355         text.value = value;
356         text.style.width = "100%";
357         //text.style.float = "left";
358         div.appendChild(document.createElement("br"));
359         div.appendChild(text);
360 
361         text.onchange = function(figureId,property){
362             return function(){
363                 updateFigure(figureId, property, this.value)
364             }
365         }(figureId, this.property);
366 
367 
368         text.onmouseout = text.onchange;
369         text.onkeyup = text.onchange;
370         DOMObject.appendChild(div);
371     },
372 
373 
374     /**Generate the code to edit the text.
375      *The text got updated when you leave the input area
376      *
377      *@param {HTMLElement} DOMObject - the div of the properties panel
378      *@param {Number} figureId - the id of the figure we are using
379      **/
380     generateSingleTextCode:function(DOMObject,figureId){
381         var uniqueId = new Date().getTime();
382         var value = this.getValue(figureId);
383 
384         var div = document.createElement("div");
385         div.className = "line";
386         div.innerHTML = '<div class = "label">' + this.name + '</div>';
387 
388         var text = document.createElement("input");
389         text.type = "text";
390         text.className = "text"; //required for onkeydown
391         text.style.cssText = "float: right";
392         //text.style.cssText ="float: left";
393         text.value = value;
394         //text.style.width = "100%";
395         //text.style.float = "left";
396         div.appendChild(text);
397 
398         text.onchange = function(figureId,property){
399             return function(){
400                 updateFigure(figureId, property, this.value)
401             }
402         }(figureId, this.property);
403 
404 
405         text.onmouseout = text.onchange;
406         text.onkeyup = text.onchange;
407         DOMObject.appendChild(div);
408     },
409 
410 
411     /**Used to generate a drop down menu
412      *
413      *@param {HTMLElement} DOMObject - the div of the properties panel
414      *@param {Number} figureId - the id of the figure we are using
415      *@param {Array} v - a vector or hashes ex: [{Text:'Normal', Value:'Normal'},{Text:'Arrow', Value:'Arrow'}]
416      */
417     generateArrayCode:function(DOMObject,figureId, v){
418 //        Log.info("Font size length: " + v.length);
419         var uniqueId = new Date().getTime();
420         
421         var value = this.getValue(figureId);
422 
423         var div = document.createElement("div");
424         div.className = "line";
425         div.innerHTML = '<div class="label">'+this.name+'</div>';
426         
427         var select = document.createElement("select");
428         select.style.cssText ="float: right;";
429         div.appendChild(select);
430         
431         for(var i=0; i< v.length; i++){
432             var option = document.createElement("option");
433             option.value = v[i].Value;
434 //            Log.info("\t Text : " + v[i].Text + " Value : " + v[i].Value);
435             option.text = v[i].Text; //see: http://www.w3schools.com/jsref/coll_select_options.asp
436             select.options.add(option); //push does not exist in the options array
437             if(option.value == value){
438                 option.selected = true;
439             }
440         }
441 
442         var selProperty = this.property; //save it in a separate variable as if refered by (this) it will refert to the 'select' DOM Object
443         select.onchange = function(){
444             //alert('Font size triggered. Figure id : ' + figureId + ' property: ' + selProperty + ' new value' + this.options[this.selectedIndex].value);
445             updateFigure(figureId, selProperty, this.options[this.selectedIndex].value);
446         };
447 
448         DOMObject.appendChild(div);
449     },
450     
451 
452     /**
453      *Used to generate a color picker
454      *
455      *@param{HTMLElement} DOMObject - the div of the properties panel
456      *@param{Number} figureId - the id of the figure we are using
457      */
458     generateColorCode: function(DOMObject,figureId){
459         var value = this.getValue(figureId);
460        
461         var uniqueId = new Date().getTime();
462         var div = document.createElement("div");
463         div.className = "line";
464         var retVal = '<div class="label">' + this.name + '</div>\n';
465         retVal += '<div id="colorSelector' + uniqueId + '" style="/*border: 1px solid #000000;*/ width: 16px; height: 16px; display: block; float: right; padding-right: 3px;">\n';
466         retVal += '<input type="text" value="' + value + '" id="colorpickerHolder' + uniqueId + '">\n';
467         //retVal+='<div style="background-color: '+value+';  width: 20px; height: 20px;"></div>\n';
468         retVal += '</div>\n';
469         //very unhappy with this innerHTML thingy
470         div.innerHTML = retVal;
471 
472         DOMObject.appendChild(div);
473 
474         //let plugin do the job
475         $('#colorpickerHolder'+uniqueId).colorPicker();
476 
477         //on change update the figure
478         var propExposedToAnonymous = this.property;
479         $('#colorpickerHolder'+uniqueId).change(function() {
480             Log.info('generateColorCode(): figureId: ' + figureId + 'type: ' + this.type + ' name: ' + this.name + ' property: ' + this.property);
481             updateFigure(figureId, propExposedToAnonymous, $('#colorpickerHolder'+uniqueId).val());
482         });
483     },
484     
485 
486 
487     /**We use this to return a value based on the figure and the property string,
488      *similar to Javas Class.forname...sort of anyway
489      *We need this because passing direct references to simple data types (including strings) only passes the value, not a reference to that value
490      *
491      *@param{Number} figureId - the id of the shape {Figure} or {Connector} we are using, could also be the canvas (figureId = 'a')
492      */
493     getValue:function(figureId){
494         var obj = stack.figureGetById(figureId);
495         if(obj == null){ //ok so it's not a Figure...so it should be a Connector
496             obj = CONNECTOR_MANAGER.connectorGetById(figureId);
497         }                
498         if(obj == null){
499             if(figureId == "canvas"){
500                 obj = canvas;
501             }
502         }
503         var propertyAccessors = this.property.split(".");
504 //        Log.info("BuilderProperty::getValue() : propertyAccessors : " + propertyAccessors );
505         for(var i = 0; i<propertyAccessors.length-1; i++){
506 //            Log.info("\tBuilderProperty::getValue() : i = " + i  + ' name= ' + propertyAccessors[i]);
507             obj = obj[propertyAccessors[i]];
508         }
509         
510         //null is allowed, undefined is not
511         if(obj[propertyAccessors[propertyAccessors.length -1]] === undefined){
512             /**If there is a getxxx() accesory method in the object we will call that function*/
513             var propName = "get"+propertyAccessors[propertyAccessors.length -1];
514             if(propName in obj){ //@see https://developer.mozilla.org/en/JavaScript/Reference/Operators/Special_Operators/in_Operator
515                 return obj["get"+propertyAccessors[propertyAccessors.length -1]]();
516             }
517             else{
518                 return null; // :p
519             }            
520         }
521         else{
522             //Access the object property's
523             return obj[propertyAccessors[propertyAccessors.length -1]];
524         }
525     }
526 }