1 sc_require("views/view");
  2 
  3 SC.View.reopen(
  4   /** @scope SC.View.prototype */ {
  5   // ..........................................................
  6   // KEY RESPONDER
  7   //
  8 
  9   /** @property
 10     YES if the view is currently first responder and the pane the view belongs
 11     to is also key pane.  While this property is set, you should expect to
 12     receive keyboard events.
 13   */
 14   isKeyResponder: NO,
 15 
 16   /**
 17     This method is invoked just before you lost the key responder status.
 18     The passed view is the view that is about to gain keyResponder status.
 19     This gives you a chance to do any early setup. Remember that you can
 20     gain/lose key responder status either because another view in the same
 21     pane is becoming first responder or because another pane is about to
 22     become key.
 23 
 24     @param {SC.Responder} responder
 25   */
 26   willLoseKeyResponderTo: function(responder) {},
 27 
 28   /**
 29     This method is invoked just before you become the key responder.  The
 30     passed view is the view that is about to lose keyResponder status.  You
 31     can use this to do any setup before the view changes.
 32     Remember that you can gain/lose key responder status either because
 33     another view in the same pane is becoming first responder or because
 34     another pane is about to become key.
 35 
 36     @param {SC.Responder} responder
 37   */
 38   willBecomeKeyResponderFrom: function(responder) {},
 39 
 40   /**
 41     Invokved just after the responder loses key responder status.
 42     @param {SC.Responder} responder
 43   */
 44   didLoseKeyResponderTo: function(responder) {},
 45 
 46   /**
 47     Invoked just after the responder gains key responder status.
 48     By default, it calls focus on the view root element. For accessibility 
 49     purposes.
 50   
 51     @param {SC.Responder} responder
 52   */
 53   didBecomeKeyResponderFrom: function(responder) {},
 54 
 55   /**
 56     This method will process a key input event, attempting to convert it to
 57     an appropriate action method and sending it up the responder chain.  The
 58     event is converted using the key bindings hashes, (SC.BASE_KEY_BINDINGS
 59     and SC.MODIFIED_KEY_BINDINGS) which map key events to method names. If
 60     no key binding method is found, then the key event will be passed along
 61     to any insertText() method found.
 62 
 63     @param {SC.Event} event
 64     @returns {Object} object that handled event, if any
 65   */
 66   interpretKeyEvents: function(event) {
 67     var codes = event.commandCodes(),
 68         cmd = codes[0],
 69         chr = codes[1],
 70         ret,
 71         match,
 72         methodName,
 73         target,
 74         pane,
 75         handler;
 76 
 77     if (!cmd && !chr) { return null ; } //nothing to do.
 78 
 79     // if this is a command key, try to do something about it.
 80     if (cmd) {
 81       match = cmd.match(/[^_]+$/);
 82       methodName = SC.MODIFIED_KEY_BINDINGS[cmd];
 83       if (!methodName && match && match.length > 0) {
 84         methodName = SC.BASE_KEY_BINDINGS[match[0]];
 85       }
 86       if (methodName) {
 87         target = this;
 88         pane = this.get('pane');
 89         handler = null;
 90         while(target && !(handler = target.tryToPerform(methodName, event))){
 91           target = (target===pane)? null: target.get('nextResponder') ;
 92         }
 93         return handler ;
 94       }
 95     }
 96 
 97     if (chr && this.respondsTo('insertText')) {
 98       // if we haven't returned yet and there is plain text, then do an insert
 99       // of the text.  Since this is not an action, do not send it up the
100       // responder chain.
101       ret = this.insertText(chr, event);
102       return ret ? (ret===YES ? this : ret) : null ; // map YES|NO => this|nil
103     }
104 
105     return null ; //nothing to do.
106   },
107 
108   /**
109     This method is invoked by interpretKeyEvents() when you receive a key
110     event matching some plain text.  You can use this to actually insert the
111     text into your application, if needed.
112 
113     @param {SC.Event} event
114     @returns {Object} receiver or object that handled event
115   */
116   insertText: function(chr) {
117     return NO ;
118   },
119 
120   /**
121     Recursively travels down the view hierarchy looking for a view that
122     implements the key equivalent (returning to YES to indicate it handled
123     the event).  You can override this method to handle specific key
124     equivalents yourself.
125 
126     The keystring is a string description of the key combination pressed.
127     The evt is the event itself. If you handle the equivalent, return YES.
128     Otherwise, you should just return sc_super.
129 
130     @param {String} keystring
131     @param {SC.Event} evt
132     @returns {Boolean}
133   */
134   performKeyEquivalent: function(keystring, evt) {
135     var ret = NO,
136         childViews = this.get('childViews'),
137         len = childViews.length,
138         idx = -1, view ;
139     while (!ret && (++idx < len)) {
140       view = childViews[idx];
141 
142       ret = view.tryToPerform('performKeyEquivalent', keystring, evt);
143     }
144 
145     return ret ;
146   },
147 
148   /**
149     The first child of this view for the purposes of tab ordering. If not
150     provided, the first element of childViews is used. Override this if
151     your view displays its child views in an order different from that
152     given in childViews.
153 
154     @type SC.View
155     @default null
156   */
157   firstKeyView: null,
158 
159   /**
160     @private
161 
162     Actually calculates the firstKeyView as described in firstKeyView.
163 
164     @returns {SC.View}
165   */
166   _getFirstKeyView: function() {
167     // if first was given, just return it
168     var firstKeyView = this.get('firstKeyView');
169     if(firstKeyView) return firstKeyView;
170 
171     // otherwise return the first childView
172     var childViews = this.get('childViews');
173     if(childViews) return childViews[0];
174   },
175 
176   /**
177     The last child of this view for the purposes of tab ordering. If not set, can be generated two different ways:
178     1. If firstKeyView is provided, it will be generated by starting from firstKeyView and traversing the childViews nextKeyView properties.
179     2. If firstKeyView is not provided, it will simply return the last element of childViews.
180 
181     The first way is not very efficient, so if you provide firstKeyView you should also provide lastKeyView.
182 
183     @type SC.View
184     @default null
185   */
186   lastKeyView: null,
187 
188   /**
189     @private
190 
191     Actually calculates the lastKeyView as described in lastKeyView.
192 
193     @returns {SC.View}
194   */
195   _getLastKeyView: function() {
196     // if last was given, just return it
197     var lastKeyView = this.get('lastKeyView');
198     if(lastKeyView) return lastKeyView;
199 
200     var view,
201     prev = this.get('firstKeyView');
202 
203     // if first was given but not last, build by starting from first and
204     // traversing until we hit the end. this is obviously the least efficient
205     // way
206     if(prev) {
207       while(view = prev._getNextKeyView()) {
208         prev = view;
209       }
210 
211       return prev;
212     }
213 
214     // if neither was given, it's more efficient to just return the last
215     // childView
216     else {
217       var childViews = this.get('childViews');
218 
219       if(childViews) return childViews[childViews.length - 1];
220     }
221   },
222 
223   /**
224     Optionally points to the next key view that should gain focus when tabbing
225     through an interface.  If this is not set, then the next key view will
226     be set automatically to the next sibling as defined by its parent's
227     childViews property.
228 
229     If any views define this, all of their siblings should define it as well,
230     otherwise undefined behavior may occur. Their parent view should also define
231     a firstKeyView.
232 
233     This may also be set to a view that is not a sibling, but once again all
234     views in the chain must define it or undefined behavior will occur.
235 
236     Likewise, any view that sets nextKeyView should also set previousKeyView.
237 
238     @type SC.View
239     @default null
240   */
241 
242   nextKeyView: undefined,
243 
244   /**
245     @private
246 
247     Gets the next key view by checking if the user set it and otherwise just
248     getting the next by index in childViews.
249 
250     @return {SC.View}
251   */
252   _getNextKeyView: function() {
253     var pv = this.get('parentView'),
254     nextKeyView = this.get('nextKeyView');
255 
256     // if the parent defines lastKeyView, it takes priority over this views
257     // nextKeyView
258     if(pv && pv.get('lastKeyView') === this) return null;
259 
260     // if this view defines a nextKeyView, use it
261     if(nextKeyView !== undefined) return nextKeyView;
262 
263     // otherwise generate one based on parent view's childViews
264     if(pv) {
265       var childViews = pv.get('childViews');
266       return childViews[childViews.indexOf(this) + 1];
267     }
268   },
269 
270   /**
271     Computes the next valid key view. This is the next key view that
272     acceptsFirstResponder. Computed using depth first search. If the current view
273     is not valid, it will first traverse its children before trying siblings. If
274     the current view is the only valid view, the current view will be returned. Will
275     return null if no valid view can be found.
276 
277     @property
278     @type SC.View
279   */
280   nextValidKeyView: function() {
281     var cur = this, next;
282     while(next !== this) {
283       next = null;
284 
285       // only bother to check children if we are visible
286       if(cur.get('isVisibleInWindow')) next = cur._getFirstKeyView();
287 
288       // if we have no children, check our sibling
289       if(!next) next = cur._getNextKeyView();
290 
291       // if we have no children or siblings, unroll up closest parent that has a
292       // next sibling
293       if(!next) {
294         while(cur = cur.get('parentView')) {
295           if(next = cur._getNextKeyView()) break;
296         }
297       }
298 
299       // if no parents have a next sibling, start over from the beginning
300       if(!next) {
301         if(!SC.TABBING_ONLY_INSIDE_DOCUMENT) break;
302         else next = this.get('pane');
303       }
304 
305       // if it's a valid firstResponder, we're done!
306       if(next.get('isVisibleInWindow') && next.get('acceptsFirstResponder')) {
307         return next;
308       }
309       // otherwise keep looking
310       cur = next;
311     }
312     // this will only happen if no views are visible and accept first responder
313     return null;
314   }.property('nextKeyView'),
315 
316   /**
317     Optionally points to the previous key view that should gain focus when tabbing
318     through an interface.  If this is not set, then the previous key view will
319     be set automatically to the previous sibling as defined by its parent's
320     childViews property.
321 
322     If any views define this, all of their siblings should define it as well,
323     otherwise undefined behavior may occur. Their parent view should also define
324     a lastKeyView.
325 
326     This may also be set to a view that is not a sibling, but once again all
327     views in the chain must define it or undefined behavior will occur.
328 
329     Likewise, any view that sets previousKeyView should also set nextKeyView.
330 
331     @type SC.View
332     @default null
333   */
334   previousKeyView: undefined,
335 
336   /**
337     @private
338 
339     Gets the previous key view by checking if the user set it and otherwise just
340     getting the previous by index in childViews.
341 
342     @return {SC.View}
343   */
344   _getPreviousKeyView: function() {
345     var pv = this.get('parentView'),
346     previousKeyView = this.get('previousKeyView');
347 
348     // if the parent defines firstKeyView, it takes priority over this views
349     // previousKeyView
350     if(pv && pv.get('firstKeyView') === this) return null;
351 
352     // if this view defines a previousKeyView, use it
353     if(previousKeyView !== undefined) return previousKeyView;
354 
355     // otherwise generate one based on parent view's childViews
356     if(pv) {
357       var childViews = pv.get('childViews');
358       return childViews[childViews.indexOf(this) - 1];
359     }
360   },
361 
362   /**
363     Computes the previous valid key view. This is the previous key view that
364     acceptsFirstResponder. Traverse views in the opposite order from
365     nextValidKeyView. If the current view is the pane, tries deepest child. If the
366     current view has a previous view, tries its last child. If this view is the
367     first child, tries the parent. Will return null if no valid view can be
368     found.
369 
370     @property
371     @type SC.View
372   */
373   // TODO: clean this up
374   previousValidKeyView: function() {
375     var cur = this, prev;
376 
377     while(prev !== this) {
378       // normally, just try to get previous view's last child
379       if(cur.get('parentView')) prev = cur._getPreviousKeyView();
380 
381       // if we are the pane and address bar tabbing is enabled, trigger it now
382       else if(!SC.TABBING_ONLY_INSIDE_DOCUMENT) break;
383 
384       // if we are the pane, get our own last child
385       else prev = cur;
386 
387       // loop down to the last valid child
388       if(prev) {
389         do {
390           cur = prev;
391           prev = prev._getLastKeyView();
392         } while(prev && prev.get('isVisibleInWindow'));
393 
394         // if we ended on a null, unroll to the last one
395         // we don't unroll if we ended on a hidden view because we need
396         // to traverse to its previous view next iteration
397         if(!prev) prev = cur;
398       }
399 
400       // if there is no previous view, traverse to the parent
401       else prev = cur.get('parentView');
402 
403       // if the view is valid, return it
404       if(prev.get('isVisibleInWindow') && prev.get('acceptsFirstResponder')) return prev;
405 
406       // otherwise, try to find its previous valid keyview
407       cur = prev;
408     }
409 
410     // if none of the views accept first responder and we make it back to where
411     // we started, just return null
412     return null;
413   }.property('previousKeyView')
414 });
415 
416