1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 
  9 /*global ViewBuilder */
 10 sc_require('views/high_light');
 11 /** @class
 12 
 13   A Designer class provides the core editing functionality you need to edit
 14   a view in the UI.  When your app loads in `design.mode`, a peer Designer
 15   instance is created for every view using the class method Designer or
 16   `SC.ViewDesigner` if the view class does not define a Designer class.
 17 
 18   Whenever you put your app into design mode, all events will be routed first
 19   to the peer designer for an object, which will have an opportunity to
 20   prosent a design UI.
 21 
 22   Likewise, the designer palettes provided by the view builder will focus on
 23   the designer instead of the view itself.
 24 
 25   ## Designer UI
 26 
 27   The basic ViewDesigner class automatically handles the UI interaction for
 28   layout.  You can also double click on the view to perform a default action.
 29 
 30   For views with `isContainerView` set to `YES`, double clicking on the view will
 31   automatically "focus" the view.  This allows you to select the view's
 32   children instead of the view itself.
 33 
 34   @extends SC.Object
 35   @since SproutCore 1.0
 36 */
 37 SC.ViewDesigner = SC.Object.extend(
 38 /** @scope SC.ViewDesigner.prototype */ {
 39 
 40   /** The view managed by this designer. */
 41   view: null,
 42 
 43   /** The class for the design.  Set when the view is created. */
 44   viewClass: null,
 45 
 46   /** Set to YES if the view is currently selected for editing. */
 47   designIsSelected: NO,
 48 
 49   /** Set to YES if this particular designer should not be enabled. */
 50   designIsEnabled: YES,
 51 
 52   /**
 53     The current page.  Comes from the view.
 54 
 55     @property {SC.Page}
 56   */
 57   page: function() {
 58     var v = this.get('view');
 59     return (v) ? v.get('page') : null;
 60   }.property('view').cacheable(),
 61 
 62   /**
 63     The design controller from the page.  Comes from page
 64 
 65     @property {SC.PageDesignController}
 66   */
 67   designController: function() {
 68     var p = this.get('page');
 69     return (p) ? p.get('designController') : null ;
 70   }.property('page').cacheable(),
 71 
 72   /**
 73     If set to NO, the default childView encoding will not run.  You can use
 74     this option, for example, if your view creates its own childViews.
 75 
 76     Alternatively, you can override the `encodeChildViewsDesign()` and
 77     `encodeChildViewsLoc()` methods.
 78 
 79     @type Boolean
 80   */
 81   encodeChildViews: YES,
 82 
 83   concatenatedProperties: ['designProperties', 'localizedProperties', 'excludeProperties'],
 84 
 85 
 86   // ..........................................................
 87   // SIZE AND POSITIONING SUPPORT
 88   //
 89 
 90   /**
 91     Set to `NO` to hide horizontal resize handles
 92   */
 93   canResizeHorizontal: YES,
 94 
 95   /**
 96     Set to `NO` to resize vertical handles
 97   */
 98   canResizeVertical: YES,
 99 
100   /**
101     Allows moving.
102   */
103   canReposition: YES,
104 
105   /**
106     Determines the minimum allowed width
107   */
108   minWidth: 10,
109 
110   /**
111     Determines the minimum allowed height
112   */
113   minHeight: 10,
114 
115   /**
116     Determines maximum allowed width.  `null` means no limit
117   */
118   maxWidth: 100000000,
119 
120   /**
121     Determines maximum allowed height.  `null` means no limit
122   */
123   maxHeight: 100000000,
124 
125   /**
126     Returns the current layout for the view.  Set this property to update
127     the layout.  Direct properties are exposed a well. You will usually want
128     to work with those instead.
129 
130     @property
131     @type {Hash}
132   */
133   layout: function(key, value) {
134     var view = this.get('view');
135     if (!view) return null;
136 
137     if (value !== undefined) view.set('layout', value);
138     return view.get('layout');
139   }.property('view').cacheable(),
140 
141   /**
142     The current anchor location.  This determines which of the other dimension
143     metrics are actually used to compute the layout.  The value may be one of:
144 
145        TOP_LEFT, TOP_CENTER, TOP_RIGHT, TOP_HEIGHT,
146        CENTER_LEFT, CENTER_CENTER, CENTER_RIGHT, CENTER_HEIGHT
147        BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT, BOTTOM_HEIGHT,
148        WIDTH_LEFT, WIDTH_CENTER, WIDTH_RIGHT, WIDTH_HEIGHT,
149        null
150 
151     @property
152     @type {Number}
153   */
154   anchorLocation: function(key, value) {
155     var layout = this.get('layout'),
156         K      = SC.ViewDesigner,
157         h, v, frame, view, pview, pframe, ret;
158 
159     if (!layout) return null;
160 
161     // update to refelct new anchor locations...
162     if (value !== undefined) {
163 
164       ret    = {};
165       view   = this.get('view');
166       frame  = view.get('frame');
167       pview  = view.get('parentView');
168       pframe = pview ? pview.get('frame') : null;
169       if (!pframe) pframe = SC.RootResponder.responder.computeWindowSize();
170 
171       // compute new layout in each direction
172       if (value & K.ANCHOR_LEFT) {
173         ret.left = frame.x;
174         ret.width = frame.width;
175 
176       } else if (value & K.ANCHOR_RIGHT) {
177         ret.right = (pframe.width - SC.maxX(frame));
178         ret.width = frame.width;
179 
180       } else if (value & K.ANCHOR_CENTERX) {
181         ret.centerX = Math.round(SC.midX(frame) - (pframe.width/2)) ;
182         ret.width = frame.width;
183 
184       } else if (value & K.ANCHOR_WIDTH) {
185         ret.left = frame.x;
186         ret.right = (pframe.width - SC.maxX(frame));
187       }
188 
189       // vertical
190       if (value & K.ANCHOR_TOP) {
191         ret.top = frame.y;
192         ret.height = frame.height;
193 
194       } else if (value & K.ANCHOR_BOTTOM) {
195         ret.bottom = (pframe.height - SC.maxY(frame));
196         ret.height = frame.height;
197 
198       } else if (value & K.ANCHOR_CENTERY) {
199         ret.centerY = Math.round(SC.midY(frame) - (pframe.height/2)) ;
200         ret.height = frame.height;
201 
202       } else if (value & K.ANCHOR_HEIGHT) {
203         ret.top = frame.y;
204         ret.bottom = (pframe.height - SC.maxY(frame));
205       }
206 
207       this.set('layout', ret);
208       layout = ret ;
209     }
210 
211     if (!SC.none(layout.left)) {
212       h = SC.none(layout.width) ? K.ANCHOR_WIDTH : K.ANCHOR_LEFT;
213     } else if (!SC.none(layout.right)) h = K.ANCHOR_RIGHT;
214     else if (!SC.none(layout.centerX)) h = K.ANCHOR_CENTERX;
215     else h = 0;
216 
217     if (!SC.none(layout.top)) {
218       v = SC.none(layout.height) ? K.ANCHOR_HEIGHT : K.ANCHOR_TOP;
219     } else if (!SC.none(layout.bottom)) v = K.ANCHOR_BOTTOM ;
220     else if (!SC.none(layout.centerY)) v = K.ANCHOR_CENTERY ;
221     else v = 0;
222 
223     return v | h;
224   }.property('layout').cacheable(),
225 
226   _layoutProperty: function(key, value) {
227     var layout = this.get('layout');
228     if (!layout) return null;
229 
230     if (!SC.none(layout) && (value !== undefined)) {
231       layout = SC.copy(layout);
232       layout[key] = value;
233       this.set('layout', layout);
234     }
235 
236     return layout[key];
237   },
238 
239   /**
240     Returns the top offset of the current layout or `null` if not defined
241   */
242   layoutTop: function(key, value) {
243     return this._layoutProperty('top', value);
244   }.property('layout').cacheable(),
245 
246   /**
247     Returns the bottom offset of the current layout or `null` if not defined
248   */
249   layoutBottom: function(key, value) {
250     return this._layoutProperty('bottom', value);
251   }.property('layout').cacheable(),
252 
253   /**
254     Returns the centerY offset of the current layout or `null` if not defined
255   */
256   layoutCenterY: function(key, value) {
257     return this._layoutProperty('centerY', value);
258   }.property('layout').cacheable(),
259 
260   /**
261     Returns the height offset of the current layout or null if not defined
262   */
263   layoutHeight: function(key, value) {
264     return this._layoutProperty('height', value);
265   }.property('layout').cacheable(),
266 
267   /**
268     Returns the top offset of the current layout or `null` if not defined
269   */
270   layoutTop: function(key, value) {
271     return this._layoutProperty('top', value);
272   }.property('layout').cacheable(),
273 
274   /**
275     Returns the left offset of the current layout or `null` if not defined
276   */
277   layoutLeft: function(key, value) {
278     return this._layoutProperty('left', value);
279   }.property('layout').cacheable(),
280 
281   /**
282     Returns the right offset of the current layout or `null` if not defined
283   */
284   layoutRight: function(key, value) {
285     return this._layoutProperty('right', value);
286   }.property('layout').cacheable(),
287 
288   /**
289     Returns the centerX offset of the current layout or `null` if not defined
290   */
291   layoutCenterX: function(key, value) {
292     return this._layoutProperty('centerX', value);
293   }.property('layout').cacheable(),
294 
295   /**
296     Returns the width offset of the current layout or `null` if not defined
297   */
298   layoutWidth: function(key, value) {
299     return this._layoutProperty('width', value);
300   }.property('layout').cacheable(),
301 
302   // ..........................................................
303   // GENERIC PROPERTIES
304   //
305   // Adds support for adding generic properties to a view.  These will
306   // overwrite whatever you write out using specifically supported props.
307 
308   // ..........................................................
309   // HANDLE ENCODING OF VIEW DESIGN
310   //
311 
312   /**
313     Encodes any simple properties that can just be copied from the view onto
314     the coder.  This is used by `encodeDesignProperties()` and
315     `encodeLocalizedProperties()`.
316   */
317   encodeSimpleProperties: function(props, coder) {
318     var view = this.get('view'), proto = this.get('viewClass').prototype ;
319     props.forEach(function(prop) {
320       var val = view[prop] ; // avoid get() since we don't want to exec props
321 
322       //handle bindings
323       if (prop.length > 7 && prop.slice(-7) === "Binding" && val !== undefined){
324         coder.js(prop,val.encodeDesign());
325       }
326       else{
327         if (val !== undefined && (val !== proto[prop])) {
328           coder.encode(prop, val) ;
329         }
330       }
331     }, this);
332   },
333 
334 
335   /**
336     Array of properties that can be encoded directly.  This is an easy way to
337     add support for simple properties that need to be written to the design
338     without added code.  These properties will be encoded by
339     `encodeDesignProperties()`.
340 
341     You can add to this array in your subclasses.
342   */
343   designProperties: ['layout', 'isVisible', 'isEnabled', 'styleClass'],
344 
345 
346   /*
347     Array of properties specifically not displayed in the editable properties
348     list
349   */
350 
351   excludeProperties: ['layout', 'childViews'],
352 
353 
354   /*
355     Array of properties available to edit in greenhouse
356 
357   */
358   editableProperties: function(){
359 
360     var con = this.get('designAttrs'),
361         view = this.get('view'),
362         ret = [],
363         designProperties = this.get('designProperties'),
364         excludeProperties = this.get('excludeProperties');
365     if(con) con = con[0];
366     for(var i in con){
367       if(con.hasOwnProperty(i) && excludeProperties.indexOf(i) < 0){
368         if(!SC.none(view[i])) ret.pushObject(SC.Object.create({value: view[i], key: i, view: view}));
369       }
370     }
371     designProperties.forEach(function(k){
372       if(excludeProperties.indexOf(k) < 0){
373         ret.pushObject(SC.Object.create({value: view[k], key: k, view: view}));
374       }
375     });
376 
377     return ret;
378   }.property('designProperties').cacheable(),
379 
380 
381   /**
382     Invoked by a design coder to encode design properties.  The default
383     implementation invoked `encodeDesignProperties()` and
384     `encodeChildViewsDesign()`.  You can override this method with your own
385     additional encoding if you like.
386   */
387   encodeDesign: function(coder) {
388     coder.set('className', SC._object_className(this.get('viewClass')));
389     this.encodeDesignProperties(coder);
390     this.encodeDesignAttributeProperties(coder);
391     this.encodeChildViewsDesign(coder);
392     return YES ;
393   },
394 
395   /**
396     Encodes the design properties for the view.  These properties are simply
397     copied from the view onto the coder.  As an optimization, the value of
398     each property will be checked against the default value in the class. If
399     they match, the property will not be emitted.
400   */
401   encodeDesignProperties: function(coder) {
402     return this.encodeSimpleProperties(this.get('designProperties'), coder);
403   },
404 
405 
406   encodeDesignAttributeProperties: function(coder){
407     var designProps = this.get('designProperties'),
408         designAttrs = this.get('designAttrs'),
409         simpleProps = [];
410 
411     if(designAttrs) designAttrs = designAttrs[0];
412 
413     for(var attr in designAttrs){
414       if(designAttrs.hasOwnProperty(attr) && designProps.indexOf(attr) < 0 && attr !== 'childViews'){
415         simpleProps.push(attr);
416       }
417     }
418     return this.encodeSimpleProperties(simpleProps, coder);
419   },
420 
421   /**
422     Encodes the design for child views.  The default implementation loops
423     through child views.  If you store your child views elsewhere in your
424     config (for example as named properties), then you may want to override
425     this method with your own encoding.
426   */
427   encodeChildViewsDesign: function(coder) {
428     if (!this.get('encodeChildViews')) return;
429     var view = this.view, childViews = view.get('childViews');
430     if (childViews.length>0) coder.object('childViews', childViews);
431   },
432 
433   /**
434     Array of localized that can be encoded directly.  This is an easy way to
435     add support for simple properties that need to be written to the
436     localization without added code.  These properties will be encoded by
437     `encodeLocalizedProperties()`.
438 
439     You can add to this array in your subclasses.
440   */
441   localizedProperties: [],
442 
443   /**
444     Invoked by a localization coder to encode design properties.  The default
445     implementation invoked `encodeLocalizedProperties()` and
446     `encodeChildViewsLoc()`.  You can override this method with your own
447     additional encoding if you like.
448   */
449   encodeLoc: function(coder) {
450     coder.set('className', SC._object_className(this.get('viewClass')));
451     this.encodeLocalizedProperties(coder);
452     this.encodeChildViewsLoc(coder);
453     return YES ;
454   },
455 
456   /**
457     Encodes the localized properties for the view.  These properties are
458     simply copied from the view onto the coder.  As an optimization, the value
459     of  each property will be checked against the default value in the class.
460     If they match, the property will not be emitted.
461   */
462   encodeLocalizedProperties: function(coder) {
463     return this.encodeSimpleProperties(this.get('localizedProperties'),coder);
464   },
465 
466   /**
467     Encodes the design for child views.  The default implementation loops
468     through child views.  If you store your child views elsewhere in your
469     config (for example as named properties), then you may want to override
470     this method with your own encoding.
471   */
472   encodeChildViewsLoc: function(coder) {
473     if (!this.get('encodeChildViews')) return;
474     var view = this.view, childViews = view.childViews;
475     if (childViews.length>0) coder.object('childViews', childViews);
476   },
477 
478   /**
479     This method is invoked when the designer is instantiated.  You can use
480     this method to reload any state saved in the view.  This method is called
481     before any observers or bindings are setup to give you a chance to
482     configure the initial state of the designer.
483   */
484   awakeDesign: function() {},
485 
486 
487   /**
488     over-ride this method in your designers to customize drop operations
489     default just calls appendChild
490 
491     TODO: Come up with a better name for this method.
492   */
493   addView: function(view){
494     this.view.appendChild(view);
495   },
496 
497   // ..........................................................
498   // VIEW RELAYING
499   //
500   // View property changes relay automatically...
501 
502   /**
503     Invoked whenever the view changes.  This will observe all property
504     changes on the new view.
505   */
506   viewDidChange: function() {
507     var view = this.get('view'), old = this._designer_view ;
508     if (view === old) return; // nothing to do
509 
510     var func = this.viewPropertyDidChange ;
511     if (old) old.removeObserver('*', this, func);
512     this._designer_view = view ;
513     if (view) view.addObserver('*', this, func);
514     this.viewPropertyDidChange(view, '*', null, null);
515   }.observes('view'),
516 
517   /**
518     Invoked whenever a property on the view has changed.  The passed key will
519     be '*' when the entire view has changed.  The default implementation here
520     will notify the property as changed on the receiver if the
521     property value is undefined on the receiver.
522 
523     It will notify all properties changed for '*'.  You may override this
524     method with your own behavior if you like.
525   */
526   viewPropertyDidChange: function(view, key) {
527     if (key === '*') this.allPropertiesDidChange();
528     else if (this[key] === undefined) this.notifyPropertyChange(key) ;
529 
530     if ((key === '*') || (key === 'layout')) {
531       if (this.get('designIsSelected') && this._handles) {
532         this._handles.set('layout', SC.clone(view.get('layout')));
533       }
534     }
535   },
536 
537   /**
538     The `unknownProperty` handler will pass through to the view by default.
539     This will often provide you the support you need without needing to
540     customize the Designer.  Just make sure you don't define a conflicting
541     property name on the designer itself!
542   */
543   unknownProperty: function(key, value) {
544     if (value !== undefined) {
545       this.view.set(key, value);
546       return value ;
547     } else return this.view.get(key);
548   },
549 
550   // ......................................
551   // PRIVATE METHODS
552   //
553 
554   init: function() {
555 
556     // setup design from view state...
557     this.awakeDesign();
558 
559     // setup bindings, etc
560     sc_super();
561 
562     // now add observer for property changes on view to relay change out.
563     this.viewDidChange();
564 
565     // and register with designController, if defined...
566     var c= this.get('designController');
567     if (c) c.registerDesigner(this) ;
568 
569   },
570 
571   destroy: function() {
572     sc_super();
573     this.set('view', null); // clears the view observer...
574   },
575 
576   designIsSelectedDidChange: function() {
577     if (SC.kindOf(this.view, SC.Pane)) return this ;
578 
579     var isSel = this.get('designIsSelected');
580     var handles = this._handles;
581 
582     if (isSel) {
583 
584       if (!handles) {
585         handles = this._handles = SC.SelectionHandlesView.create({
586           designer: this
587         });
588       }
589 
590       var parent = this.view.get('parentView');
591       if (!handles.get('parentView') !== parent) parent.appendChild(handles);
592       handles.set('layout', this.view.get('layout'));
593     } else if (handles) {
594       if (handles.get('parentView')) handles.removeFromParent();
595     }
596   }.observes('designIsSelected'),
597 
598   tryToPerform: function(methodName, arg1, arg2) {
599     // only handle event if we are in design mode
600     var page = this.view ? this.view.get('page') : null ;
601     var isDesignMode = page ? page.get('needsDesigner') || page.get('isDesignMode') : NO ;
602 
603     // if we are in design mode, route event handling to the designer
604     // otherwise, invoke default method.
605     if (isDesignMode) {
606       return sc_super();
607     } else {
608       return SC.Object.prototype.tryToPerform.apply(this.view, arguments);
609     }
610   },
611 
612   // ..........................................................
613   // DRAWING SUPPORT
614   //
615 
616   /**
617     Update the layer to add any design-specific marking
618   */
619   didCreateLayer: function() {},
620 
621   /**
622     Update the layer to add any design-specific marking
623   */
624   didUpdateLayer: function() {},
625 
626   /**
627     Update the layer to add any design-specific marking
628   */
629   willDestroyLayer: function() {},
630 
631   // ..........................................................
632   // ROOT DESIGNER SUPPORT
633   //
634 
635   parentDesignerIsRoot: function(){
636     var dc = this.get('designController'), view = this.get('view');
637     return dc.get('rootDesigner') === view.getPath('parentView.designer');
638   }.property(),
639 
640   /**
641     set this property to `YES` if you want your designer to become Root
642   */
643   acceptRootDesigner: NO,
644 
645   isRootDesigner: NO,
646 
647   isRootDesignerDidChange: function() {
648 
649     var isRoot = this.get('isRootDesigner'),
650         highLight = this._highLight;
651 
652     if (isRoot && this.get('designIsEnabled')) {
653 
654       if (!highLight) {
655         highLight = this._highLight = SC.RootDesignerHighLightView.create({
656           designer: this
657         });
658       }
659 
660       var parent = this.view.get('parentView');
661       highLight.set('targetFrame', this.view.get('frame'));
662 
663       if (!highLight.get('parentView') !== parent) parent.insertBefore(highLight,this.view);
664     }
665     else if (highLight) {
666       if (highLight.get('parentView')) highLight.removeFromParent();
667     }
668   }.observes('isRootDesigner'),
669 
670   resignRootDesigner: function(){
671     var prevRoot = this.get('prevRootDesigner');
672     if(this.get('isRootDesigner') && prevRoot){
673       var dc = this.get('designController');
674       if(dc) dc.makeRootDesigner(prevRoot);
675     }
676   },
677 
678   shouldReleaseRootDesigner: function(evt){
679     var frame = this.view.get('frame');
680     if(this.get('isRootDesigner') && !SC.pointInRect({ x: evt.pageX, y: evt.pageY }, frame)){
681       this.resignRootDesigner();
682       return YES;
683     }
684     return NO;
685   },
686 
687   // ..........................................................
688   // MOUSE HANDLING
689   //
690 
691   HANDLE_MARGIN: 10,
692 
693   /**
694     Select on `mouseDown`.  If `metaKey` or `shiftKey` is pressed, add to
695     selection.  Otherwise just save starting info for dragging
696   */
697   mouseDown: function(evt) {
698     this.shouldReleaseRootDesigner(evt);
699     if (!this.get('designIsEnabled') || !this.get('parentDesignerIsRoot')) return NO ;
700 
701     // save mouse down info
702     var view = this.get('view'),
703         info, vert, horiz, repos, frame, pview, margin, canH, canV;
704 
705     if (!view) return NO; // nothing to do
706 
707     // save mouse down state for later use
708     this._mouseDownInfo = info = {
709       layout:   SC.copy(view.get('layout')),
710       selected: this.get('designIsSelected'),
711       dragged:  NO,
712       metaKey:  evt.metaKey || evt.shiftKey,
713       source:   this,
714       x: evt.pageX, y: evt.pageY
715     };
716     info.hanchor = info.vanchor = info.reposition = NO;
717 
718     // detect what operations are available.
719     repos = this.get('canReposition');
720     horiz = vert = NO ;
721     if (info.selected) {
722       frame = view.get('frame');
723       pview = view.get('parentView');
724       if (frame && pview) frame = pview.convertFrameToView(frame, null);
725 
726       margin = this.HANDLE_MARGIN;
727 
728       // detect if we are in any hotzones
729       if (frame) {
730         if (Math.abs(info.x - SC.minX(frame)) <= margin) {
731           horiz = "left";
732         } else if (Math.abs(info.x - SC.maxX(frame)) <= margin) {
733           horiz = "right";
734         }
735 
736         if (Math.abs(info.y - SC.minY(frame)) <= margin) {
737           vert = "top";
738         } else if (Math.abs(info.y - SC.maxY(frame)) <= margin) {
739           vert = "bottom";
740         }
741       }
742 
743       canH = this.get('canResizeHorizontal');
744       canV = this.get('canResizeVertical');
745 
746       // look for corners if can resize in both directions...
747       if (canH && canV) {
748         if (!vert || !horiz) vert = horiz = NO ;
749 
750       // if can only resize horizonal - must be in middle vertical
751       } else if (canH) {
752         vert = NO ;
753         if (Math.abs(info.y - SC.midY(frame)) > margin) horiz = NO;
754 
755       // if can only resize vertical - must be in middle horizontal
756       } else if (canV) {
757         horiz = NO ;
758         if (Math.abs(info.x - SC.midX(frame)) > margin) vert = NO ;
759 
760       // otherwise, do not allow resizing
761       } else horiz = vert = NO ;
762     }
763 
764     // now save settings...
765     if (horiz) info.hanchor = horiz ;
766     if (vert) info.vanchor = vert ;
767     if (!horiz && !vert && repos) info.reposition = YES ;
768 
769     // if not yet selected, select item immediately.  This way future events
770     // will be handled properly
771     if (!info.selected) {
772       this.get('designController').select(this, info.metaKey);
773     }
774 
775     // save initial info on all selected items
776     if (info.reposition) this.get('designController').prepareReposition(info);
777 
778     return YES ;
779   },
780 
781   prepareReposition: function(info) {
782     var view = this.get('view'),
783         layout = view ? SC.copy(view.get('layout')) : {};
784     info[SC.keyFor('layout', SC.guidFor(this))] = layout;
785     return this ;
786   },
787 
788   /**
789     mouse dragged will resize or reposition depending on the settings from
790     mousedown.
791   */
792   mouseDragged: function(evt) {
793     if (!this.get('designIsEnabled') || !this.get('parentDesignerIsRoot')) return NO ;
794     var info = this._mouseDownInfo,
795         view = this.get('view'),
796         layout, startX, startY;
797     //do some binding!!!
798     if(evt.altKey && SC._Greenhouse){
799       startX = evt.pageX;
800       startY = evt.pageY;
801 
802       var dragLink = SC.DrawingView.create({
803         layout: {left: 0, top: 0, right: 0, bottom: 0},
804         startPoint: {x: startX, y: startY},
805         endPoint: {x: startX, y: startY},
806         // private update
807         _pointsDidChange: function(){
808           var sp = this.get('startPoint'),
809               ep = this.get('endPoint'),
810               xDiff, yDiff, newLink;
811 
812           xDiff = Math.abs(sp.x - ep.x);
813           yDiff = Math.abs(sp.y - ep.y);
814           if (xDiff > 5 || yDiff > 5){
815             newLink = {};
816             newLink.shape = SC.LINE;
817             newLink.start = {x: sp.x, y: sp.y};
818             newLink.end = {x: ep.x, y: ep.y};
819             newLink.style = { color: 'green', width: 3 };
820             this.setIfChanged('shapes', [newLink]);
821           }
822         }.observes('startPoint', 'endPoint')
823       });
824       SC.designPage.get('designMainPane').appendChild(dragLink);
825 
826       SC.Drag.start({
827         event: evt,
828         source: this,
829         dragLink: dragLink,
830         dragView: SC.View.create({ layout: {left: 0, top: 0, width: 0, height: 0}}),
831         ghost: NO,
832         slideBack: YES,
833         dataSource: this,
834         anchorView: view
835       });
836     }
837     //normal drag
838     else{
839       if (view && (info.hanchor || info.vanchor)) {
840         layout = SC.copy(this.get('layout'));
841         if (info.hanchor) this._mouseResize(evt, info, this.HKEYS, layout);
842         if (info.vanchor) this._mouseResize(evt, info, this.VKEYS, layout);
843         this.set('layout', layout);
844 
845       } else if (info.reposition) {
846         this.get('designController').repositionSelection(evt, info);
847       }
848     }
849 
850   },
851 
852   // ..........................................................
853   // Drag source and drag data source
854   //
855   dragDataTypes: ['SC.Binding'],
856 
857   dragDataForType: function(drag, dataType) {
858     return dataType === 'SC.Binding' ? this.get('view') : null;
859   },
860 
861   /**
862     On `mouseUp` potentially change selection and cleanup.
863   */
864   mouseUp: function(evt) {
865     if (!this.get('designIsEnabled') || !this.get('parentDesignerIsRoot')) return NO ;
866 
867     var info = this._mouseDownInfo;
868 
869     // if selected on mouse down and we didn't do any dragging, then deselect.
870     if (info.selected && !info.dragged) {
871 
872       // is the mouse still inside the view?  If not, don't do anything...
873       var view = this.get('view'),
874           frame = view ? view.get('frame') : null,
875           pview = view.get('parentView');
876 
877       if (frame && pview) frame = pview.convertFrameToView(frame, null);
878 
879       if (!frame || SC.pointInRect({ x: evt.pageX, y: evt.pageY }, frame)) {
880         var controller = this.get('designController');
881         if (info.metaKey) controller.deselect(this);
882         else controller.select(this, NO);
883       }
884     }
885     //double click
886     if(SC._Greenhouse && evt.clickCount === 2){
887       var dc = this.get('designController');
888       if(this.acceptRootDesigner && dc) {
889         dc.makeRootDesigner(this);
890       }
891       else{
892         //TODO: [MB] decide if this is the functionality I want...
893         SC._Greenhouse.sendAction('openInspector', view);
894       }
895     }
896 
897     this._mouseDownInfo = null;
898 
899     return YES ;
900   },
901 
902   /**
903     Called by `designerController` to reposition the view
904   */
905   mouseReposition: function(evt, info) {
906     var layout = SC.copy(this.get('layout'));
907     this._mouseReposition(evt, info, this.HKEYS, layout);
908     this._mouseReposition(evt, info, this.VKEYS, layout);
909     this.set('layout', layout);
910     return this;
911   },
912 
913   HKEYS: {
914     evtPoint: "pageX",
915     point:    "x",
916     min:      "minWidth",
917     max:      "maxWidth",
918     head:     "left",
919     tail:     "right",
920     center:   "centerX",
921     size:     "width",
922     anchor:   "hanchor"
923   },
924 
925   VKEYS: {
926     evtPoint: "pageY",
927     point:    "y",
928     min:      "minHeight",
929     max:      "maxHeight",
930     head:     "top",
931     tail:     "bottom",
932     center:   "centerY",
933     size:     "height",
934     anchor:   "vanchor"
935   },
936 
937   /**
938     Generic resizer.  Must pass one set of keys: VKEYS, HKEYS
939   */
940   _mouseResize: function(evt, info, keys, ret) {
941 
942     var delta  = evt[keys.evtPoint] - info[keys.point],
943         layout = info.layout,
944         view   = this.get('view'),
945         min    = this.get(keys.min),
946         max    = this.get(keys.max),
947 
948         headKey   = keys.head,
949         tailKey   = keys.tail,
950         centerKey = keys.center,
951         sizeKey   = keys.size,
952 
953         hasHead   = !SC.none(layout[keys.head]),
954         hasTail   = !SC.none(layout[keys.tail]),
955         hasCenter = !SC.none(layout[keys.center]),
956         hasSize   = !SC.none(layout[keys.size]),
957         w;
958 
959     if (info[keys.anchor] === headKey) {
960 
961       // if left aligned, adjust left size and width if set.
962       if (hasHead) {
963         if (hasSize) {
964           w = layout[sizeKey];
965           ret[sizeKey] = Math.min(max, Math.max(min, Math.floor(layout[sizeKey] - delta)));
966           min = (layout[headKey]+w) - min;
967           max = (layout[headKey]+w) - max;
968 
969           ret[headKey] = Math.max(max, Math.min(min, Math.floor(layout[headKey]+delta)));
970 
971         } else {
972           ret[headKey] = Math.floor(layout[headKey]+delta);
973         }
974 
975       // if right aligned or centered, adjust the width...
976       } else if (hasTail || hasCenter) {
977         if (hasCenter) delta *= 2;
978         ret[sizeKey] = Math.max(min, Math.min(max, Math.floor((layout[sizeKey]||0)-delta)));
979 
980       // otherwise, adjust left
981       } else ret[headKey] = Math.floor((layout[headKey]||0)+delta);
982 
983     } else if (info[keys.anchor] === tailKey) {
984 
985       // reverse above
986       if (hasTail) {
987 
988         if (hasSize) {
989           w = layout[sizeKey];
990           ret[sizeKey] = Math.min(max, Math.max(min, Math.floor(layout[sizeKey] + delta)));
991           min = (layout[tailKey]+w)-min;
992           max = (layout[tailKey]+w)-max;
993 
994           ret[tailKey] = Math.max(max, Math.min(min, Math.floor(layout[tailKey]-delta)));
995 
996         } else {
997           ret[tailKey] = Math.floor(layout[tailKey]-delta);
998         }
999 
1000       } else {
1001         if (hasCenter) delta *= 2;
1002         ret[sizeKey] = Math.max(min, Math.min(max, Math.floor((layout[sizeKey]||0)+delta)));
1003       }
1004     }
1005 
1006     return this;
1007   },
1008 
1009   _mouseReposition: function(evt, info, keys, ret) {
1010     var delta  = evt[keys.evtPoint] - info[keys.point],
1011         layout = info[SC.keyFor('layout', SC.guidFor(this))],
1012         view   = this.get('view'),
1013 
1014         headKey   = keys.head,
1015         tailKey   = keys.tail,
1016         centerKey = keys.center,
1017         sizeKey   = keys.size,
1018 
1019         hasHead   = !SC.none(layout[headKey]),
1020         hasTail   = !SC.none(layout[tailKey]),
1021         hasCenter = !SC.none(layout[centerKey]),
1022         hasSize   = !SC.none(layout[sizeKey]),
1023         w;
1024 
1025     // auto-widths can't be repositioned
1026     if (hasHead && hasTail && !hasSize) return NO ;
1027 
1028     // left/top aligned, just adjust top/left location
1029     if (hasHead) {
1030       ret[headKey] = layout[headKey]+delta;
1031 
1032     // right/bottom aligned, adjust bottom/right location
1033     } else if (hasTail) {
1034       ret[tailKey] = layout[tailKey]-delta;
1035 
1036     } else if (hasCenter) {
1037       ret[centerKey] = layout[centerKey]+delta;
1038 
1039     } else ret[headKey] = (layout[headKey]||0)+delta;
1040 
1041     return YES ;
1042   },
1043 
1044   // ..........................................................
1045   // Drag data source (for binding lines)
1046   //
1047   /**
1048     This method must be overridden for drag operations to be allowed.
1049     Return a bitwise OR'd mask of the drag operations allowed on the
1050     specified target.  If you don't care about the target, just return a
1051     constant value.
1052 
1053     @param {SC.View} dropTarget The proposed target of the drop.
1054     @param {SC.Drag} drag The SC.Drag instance managing this drag.
1055 
1056   */
1057   dragSourceOperationMaskFor: function(drag, dropTarget) {
1058     return SC.DRAG_LINK;
1059   },
1060 
1061   /**
1062     This method is called when the drag begins. You can use this to do any
1063     visual highlighting to indicate that the receiver is the source of the
1064     drag.
1065 
1066     @param {SC.Drag} drag The Drag instance managing this drag.
1067 
1068     @param {Point} loc The point in *window* coordinates where the drag
1069       began.  You can use convertOffsetFromView() to convert this to local
1070       coordinates.
1071   */
1072   dragDidBegin: function(drag, loc) {
1073   },
1074 
1075   /**
1076     This method is called whenever the drag image is moved.  This is
1077     similar to the `dragUpdated()` method called on drop targets.
1078 
1079     @param {SC.Drag} drag The Drag instance managing this drag.
1080 
1081     @param {Point} loc  The point in *window* coordinates where the drag
1082       mouse is.  You can use convertOffsetFromView() to convert this to local
1083       coordinates.
1084   */
1085   dragDidMove: function(drag, loc) {
1086     var dragLink = drag.dragLink;
1087     var endX, endY, pv, frame, globalFrame;
1088     if (dragLink) {
1089       // if using latest SproutCore 1.0, loc is expressed in browser window coordinates
1090       pv = dragLink.get('parentView');
1091       frame = dragLink.get('frame');
1092       globalFrame = pv ? pv.convertFrameToView(frame, null) : frame;
1093       if (globalFrame) {
1094         endX = loc.x - globalFrame.x;
1095         endY = loc.y - globalFrame.y;
1096         dragLink.set('endPoint', {x: endX , y: endY});
1097       }
1098     }
1099   },
1100 
1101   /**
1102     This method is called when the drag ended. You can use this to do any
1103     cleanup.  The operation is the actual operation performed on the drag.
1104 
1105     @param {SC.Drag} drag The drag instance managing the drag.
1106 
1107     @param {Point} loc The point in WINDOW coordinates where the drag
1108       ended.
1109 
1110     @param {DragOp} op The drag operation that was performed. One of
1111       SC.DRAG_COPY, SC.DRAG_MOVE, SC.DRAG_LINK, or SC.DRAG_NONE.
1112 
1113   */
1114   dragDidEnd: function(drag, loc, op) {
1115     var dragLink = drag.dragLink;
1116     if (dragLink) dragLink.destroy();
1117   }
1118 }) ;
1119 
1120 // Set default Designer for view
1121 if (!SC.View.Designer) SC.View.Designer = SC.ViewDesigner ;
1122 
1123 // ..........................................................
1124 // DESIGN NOTIFICATION METHODS
1125 //
1126 // These methods are invoked automatically on the designer class whenever it
1127 // is loaded.
1128 
1129 SC.ViewDesigner.mixin({
1130 
1131   ANCHOR_LEFT:    0x0001,
1132   ANCHOR_RIGHT:   0x0002,
1133   ANCHOR_CENTERX: 0x0004,
1134   ANCHOR_WIDTH:   0x0010,
1135 
1136   ANCHOR_TOP:     0x0100,
1137   ANCHOR_BOTTOM:  0x0200,
1138   ANCHOR_CENTERY: 0x0400,
1139   ANCHOR_HEIGHT:  0x1000,
1140 
1141   /**
1142     Invoked whenever a designed view is loaded.  This will save the design
1143     attributes for later use by a designer.
1144   */
1145   didLoadDesign: function(designedView, sourceView, attrs) {
1146     designedView.isDesign = YES ; // indicates that we need a designer.
1147     designedView.designAttrs = attrs;
1148     //designedView.sourceView = sourceView; TODO: not sure we need this...
1149   },
1150 
1151   /**
1152     Invoked whenever a location is applied to a designed view.  Saves the
1153     attributes separately for use by the design view.
1154   */
1155   didLoadLocalization: function(designedView, attrs) {
1156     // nothing to do for now.
1157   },
1158 
1159   /**
1160     Invoked whenver a view is created.  This will create a peer designer if
1161     needed.
1162   */
1163   didCreateView: function(view, attrs) {
1164     // add designer if page is in design mode
1165     var page = view.get('page'), design = view.constructor;
1166 
1167     if (design.isDesign && page && page.get('needsDesigner')) {
1168 
1169       // find the designer class
1170       var cur = design, origDesign = design;
1171       while(cur && !cur.Designer) cur = cur.superclass;
1172       var DesignerClass = (cur) ? cur.Designer : SC.View.Designer;
1173 
1174       // next find the first superclass view that is not a design (and a real
1175       // class).  This is important to make sure that we can determine the
1176       // real name of a view's class.
1177       while (design && design.isDesign) design = design.superclass;
1178       if (!design) design = SC.View;
1179 
1180       view.designer = DesignerClass.create({
1181         view: view,
1182         viewClass: design,
1183         designAttrs: origDesign.designAttrs
1184         //sourceView: origDesign.sourceView TODO: not sure we need this...
1185       });
1186     }
1187   }
1188 
1189 });
1190 
1191 
1192 // ..........................................................
1193 // FIXUP SC.View
1194 //
1195 
1196 SC.View.prototype._orig_respondsTo = SC.View.prototype.respondsTo;
1197 SC.View.prototype._orig_tryToPerform = SC.View.prototype.tryToPerform;
1198 SC.View.prototype._orig_createLayer = SC.View.prototype.createLayer;
1199 SC.View.prototype._orig_updateLayer = SC.View.prototype.updateLayer;
1200 SC.View.prototype._orig_destroyLayer = SC.View.prototype.destroyLayer;
1201 
1202 /**
1203   If the view has a designer, then patch respondsTo...
1204 */
1205 /*SC.View.prototype.respondsTo = function( methodName ) {
1206   var ret = !!(SC.typeOf(this[methodName]) === SC.T_FUNCTION);
1207   if (this.designer) ret = ret || this.designer.respondsTo(methodName);
1208   return ret ;
1209 } ;*/
1210 SC.View.prototype.respondsTo = function( methodName ) {
1211   if (this.designer) {
1212     var ret = !!(SC.typeOf(this[methodName]) === SC.T_FUNCTION);
1213     ret = ret || this.designer.respondsTo(methodName);
1214     return ret;
1215   }
1216   else {
1217     return this._orig_respondsTo(methodName);
1218   }
1219 };
1220 
1221 /**
1222   If the view has a designer, give it an opportunity to handle an event
1223   before passing it on to the main view.
1224 */
1225 /*SC.View.prototype.tryToPerform = function(methodName, arg1, arg2) {
1226   if (this.designer) {
1227     return this.designer.tryToPerform(methodName, arg1, arg2);
1228   } else {
1229     return this._orig_respondsTo(methodName) && this[methodName](arg1, arg2);
1230   }
1231 } ;*/
1232 SC.View.prototype.tryToPerform = function(methodName, arg1, arg2) {
1233   if (this.designer) {
1234     return this.designer.tryToPerform(methodName, arg1, arg2);
1235   }
1236   else {
1237     return this._orig_tryToPerform(methodName, arg1, arg2);
1238   }
1239 };
1240 
1241 
1242 /*
1243   If the view has a designer, also call designers didCreateLayer method to
1244   allow drawing.
1245 */
1246 SC.View.prototype.createLayer = function() {
1247   var ret = this._orig_createLayer.apply(this, arguments);
1248   if (this.designer) this.designer.didCreateLayer();
1249   return ret ;
1250 };
1251 
1252 /*
1253   If the view has a designer, also call the designer's didUpdateLayer method
1254   to allow drawing.
1255 */
1256 SC.View.prototype.updateLayer = function() {
1257   var ret = this._orig_updateLayer.apply(this, arguments);
1258   if (this.designer) this.designer.didUpdateLayer();
1259   return ret ;
1260 };
1261 
1262 /**
1263   If the view has a designer, also call the designers willDestroyLayer
1264   method.
1265 */
1266 SC.View.prototype.destroyLayer = function() {
1267   if (this.designer) this.designer.willDestroyLayer();
1268   return this._orig_destroyLayer.apply(this, arguments);
1269 };
1270