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 // standard browser cursor definitions
  9 // TODO: remove extra constants in next version
 10 // TODO: consider adding theme cursors for custom behaviors like drag & drop
 11 SC.SYSTEM_CURSOR = SC.DEFAULT_CURSOR = 'default';
 12 SC.AUTO_CURSOR = 'auto';
 13 SC.CROSSHAIR_CURSOR = 'crosshair';
 14 SC.HAND_CURSOR = SC.POINTER_CURSOR = 'pointer';
 15 SC.MOVE_CURSOR = 'move';
 16 SC.E_RESIZE_CURSOR = 'e-resize';
 17 SC.NE_RESIZE_CURSOR = 'ne-resize';
 18 SC.NW_RESIZE_CURSOR = 'nw-resize';
 19 SC.N_RESIZE_CURSOR = 'n-resize';
 20 SC.SE_RESIZE_CURSOR = 'se-resize';
 21 SC.SW_RESIZE_CURSOR = 'sw-resize';
 22 SC.S_RESIZE_CURSOR = 's-resize';
 23 SC.W_RESIZE_CURSOR = 'w-resize';
 24 SC.IBEAM_CURSOR = SC.TEXT_CURSOR = 'text';
 25 SC.WAIT_CURSOR = 'wait';
 26 SC.HELP_CURSOR = 'help';
 27 
 28 /**
 29   @class SC.Cursor
 30 
 31   A Cursor object is used to synchronize the cursor used by multiple views at
 32   the same time. For example, thumb views within a split view acquire a cursor
 33   instance from the split view and set it as their cursor. The split view is
 34   able to update its cursor object to reflect the state of the split view.
 35   Because cursor objects are implemented internally with CSS, this is a very
 36   efficient way to update the same cursor for a group of view objects.
 37 
 38   Note: This object creates an anonymous CSS class to represent the cursor.
 39   The anonymous CSS class is automatically added by SproutCore to views that
 40   have the cursor object set as "their" cursor. Thus, all objects attached to
 41   the same cursor object will have their cursors updated simultaneously with a
 42   single DOM call.
 43 
 44   @extends SC.Object
 45 */
 46 SC.Cursor = SC.Object.extend(
 47 /** @scope SC.Cursor.prototype */ {
 48 
 49   /** @private */
 50   init: function () {
 51     sc_super();
 52 
 53     // create a unique style rule and add it to the shared cursor style sheet
 54     var cursorStyle = this.get('cursorStyle') || SC.DEFAULT_CURSOR,
 55       ss = this.constructor.sharedStyleSheet(),
 56       guid = SC.guidFor(this);
 57 
 58     if (ss.insertRule) { // WC3
 59       ss.insertRule(
 60         '.' + guid + ' {cursor: ' + cursorStyle + ';}',
 61         ss.cssRules ? ss.cssRules.length : 0
 62       );
 63     } else if (ss.addRule) { // IE
 64       ss.addRule('.' + guid, 'cursor: ' + cursorStyle);
 65     }
 66 
 67     this.cursorStyle = cursorStyle;
 68     this.className = guid; // used by cursor clients...
 69     return this;
 70   },
 71 
 72   /**
 73     This property is the connection between cursors and views. The default
 74     SC.View behavior is to add this className to a view's layer if it has
 75     its cursor property defined.
 76 
 77     @readOnly
 78     @type String the css class name updated by this cursor
 79   */
 80   className: null,
 81 
 82   /**
 83     @type String the cursor value, can be 'url("path/to/cursor")'
 84   */
 85   cursorStyle: SC.DEFAULT_CURSOR,
 86 
 87   /** @private */
 88   cursorStyleDidChange: function () {
 89     var cursorStyle, rule, selector, ss, rules, idx, len;
 90     cursorStyle = this.get('cursorStyle') || SC.DEFAULT_CURSOR;
 91     rule = this._rule;
 92     if (rule) {
 93       rule.style.cursor = cursorStyle; // fast path
 94       return;
 95     }
 96 
 97     // slow path, taken only once
 98     selector = '.' + this.get('className');
 99     ss = this.constructor.sharedStyleSheet();
100     rules = (ss.cssRules ? ss.cssRules : ss.rules) || [];
101 
102     // find our rule, cache it, and update the cursor style property
103     for (idx = 0, len = rules.length; idx < len; ++idx) {
104       rule = rules[idx];
105       if (rule.selectorText === selector) {
106         this._rule = rule; // cache for next time
107         rule.style.cursor = cursorStyle; // update the cursor
108         break;
109       }
110     }
111   }.observes('cursorStyle')
112 
113   // TODO implement destroy
114 
115 });
116 
117 
118 /** @private */
119 SC.Cursor.sharedStyleSheet = function () {
120   var ssEl,
121     head,
122     ss = this._styleSheet;
123 
124   if (!ss) {
125     // create the stylesheet object the hard way (works everywhere)
126     ssEl = document.createElement('style');
127     head = document.getElementsByTagName('head')[0];
128     if (!head) head = document.documentElement; // fix for Opera
129     head.appendChild(ssEl);
130 
131     // Get the actual stylesheet object, not the DOM element.  We expect it to
132     // be the last stylesheet in the document, but test to make sure no other
133     // stylesheet has appeared.
134     for (var i = document.styleSheets.length - 1; i >= 0; i--) {
135       ss = document.styleSheets[i];
136 
137       if (ss.ownerNode === ssEl) {
138         // We've found the proper stylesheet.
139         this._styleSheet = ss;
140         break;
141       }
142     }
143   }
144 
145   return ss;
146 };
147