/* Start ----------------------------------------------------- strings.js*/

// ========================================================================
// Sprout Core
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

// English Strings.
String.English = {};
Object.extend(String.English,{
  "Invalid.CreditCard(%@)": "%@ is not a valid credit card number",
  "Invalid.Email(%@)": "%@ is not a valid email address",
  "Invalid.NotEmpty(%@)": "%@ must not be empty",
  "Invalid.Password": "Your passwords do not match.  Please try typing them again.",
  // "CommentedOut": "String",
  /*
     "A bunch of": "Commented out",
     "Strings": "Do not include",
  */
  "Invalid.General(%@)": "%@ is invalid.  Please try again.",
  "Invalid.Number(%@)": "%@ is not a number."
}) ;

/* End ------------------------------------------------------- strings.js*/

/* Start ----------------------------------------------------- core.js*/

//@license
// ==========================================================================
// SproutCore -- JavaScript Application Framework
// copyright 2006-2008, Sprout Systems, Inc. and contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
//
// For more information about SproutCore, visit http://www.sproutcore.com
//
//
// ==========================================================================
//@license

// ==========================================================================
// Utility Classes
// Author: Charles Jolley
// copyright 2006, Sprout Systems, Inc.
//
// This file contains a number of utility methods and classes used throughout
// SproutCore. This should be loaded after your load Prototype but before you
// load any other SproutCore objects.  In general, this is the only
// dependency most SproutCore objects will have.
//
// ==========================================================================

// All objects live in the SproutCore namespace, which is also availabe in the
// abreviation SC.
if (!window.SC) SC = {};  SproutCore = SC ;

// this makes for some nicer to read code
var YES = true ; var NO = false ;

// this is used by the JavascriptCompile class on the server side.  You can
// use this to automatically determine the order javascript files need to be
// included in.  On the client side, this is a NOP.
function require(file) { return null ; }

// implement window.console.log() for IE.
if (!window.console) { 
 window.console = { 
   _output: [],
   log: function(str) { this._output.push(str) ; },
   tail: function(lines) { 
     if (!lines) lines = 1 ;
     var loc = this._output.length - lines ;
     if (loc < 0) loc = 0 ;
     var ret = [] ;
     while(loc < this._output.length) {
       ret.push(this._output[loc]) ; loc++ ;
     }
     return ret.join("\n");
   }  
 } ;
}
window.logCount = 0 ;

// ........................................
// GENERAL UTILITIES
// 

Object.extend(SC,{

  _downloadFrames: 0, // count of download frames inserted into document
  
	download: function(path) {
    var tempDLIFrame=document.createElement('iframe');
    var frameId = 'DownloadFrame_' + this._downloadFrames;
    tempDLIFrame.setAttribute('id',frameId);
    tempDLIFrame.style.border='10px';
    tempDLIFrame.style.width='0px';
    tempDLIFrame.style.height='0px';
    tempDLIFrame.style.position='absolute';
    tempDLIFrame.style.top='-10000px';
    tempDLIFrame.style.left='-10000px';    
    // Don't set the iFrame content yet if this is Safari
    if (!(SC.isSafari())) {
      tempDLIFrame.setAttribute('src',path);    
    }
    document.getElementsByTagName('body')[0].appendChild(tempDLIFrame);
    if (SC.isSafari()) {
      tempDLIFrame.setAttribute('src',path);    
    }
    this._downloadFrames = this._downloadFrames + 1;
    if (!(SC.isSafari())) {
      var r = function() { 
        document.body.removeChild(document.getElementById(frameId)); 
      } ;
      var t = r.invokeLater(null, 2000);
    }
	},
		
  // Call this method during setup of your app to queue up methods to be 
  // called once the entire document has finished loading.  If you call this
  // method once the document has already loaded, then the function will be
  // called immediately.
  callOnLoad: function(func) { 
    if (SC._onloadQueueFlushed) func.call(document);
    var queue = SC._onloadQueue || [] ;
    queue.push(func) ; SC._onloadQueue = queue ;
  },

  // To flush the callOnLoad queue, you need to set window.onload=SC.didLoad
  didLoad: function() { 
    SC.app = SC.Application.create();
    SC.app.run();
    
    // set the current language
    var b = $tag('body');
    Element.addClassName(b, String.currentLanguage().toLowerCase()) ;

    // call the onloadQueue.
    var queue ;
    SC.runLoop.beginRunLoop() ;
    if (window.callOnLoad) {
      if (window.callOnLoad instanceof Array) {
        queue = window.callOnLoad ;
      } else if (window.callOnLoad instanceof Function) {
        queue = [window.callOnLoad] ;
      }
    } else queue = [] ;
    queue = queue.concat(SC._onloadQueue) ;
    var func = null ;
    while(func = queue.shift()) func.call(document) ;
    SC._onloadQueueFlushed = true ;
        
    // start the app; call main.
    if (window.main && (main instanceof Function)) main() ; // start app.
    
    // finally handle any routes if any.
    if (typeof Routes != 'undefined') {
      Routes.doRoutes() ; // old style.
    } else if (typeof SC.Routes != 'undefined') {
      SC.Routes.ping() ; // handle routes, if modules is installed.
    }
    
    SC.runLoop.endRunLoop();
  },
  
  // this will take a URL of any type and convert it to a fully qualified URL.
  normalizeURL: function(url) {
    if (url.slice(0,1) == '/') {
      url = window.location.protocol + '//' + window.location.host + url ;
    } else if ((url.slice(0,5) == 'http:') || (url.slice(0,6) == 'https:')) {
      // no change
    } else {
      url = window.location.href + '/' + url ;
    }
    return url ;
  },
  
  // use this instead of typeOf() to get the type of item.  The return values
  // are: 'string', 'number', 'function', 'class', 'object', 'hash', 'null', 
  // 'undefined', 'boolean'.  
  // 'object' will be returned for any items inheriting from SC.Object. 'hash' 
  // is any other type of object.
  typeOf: function(item) {
    if (item === undefined) return T_UNDEFINED ;
    if (item === null) return T_NULL ; 
    var ret = typeof(item) ;
    if (ret == "object") {
      if (item instanceof Array) {
        ret = T_ARRAY ;
      } else if (item instanceof Function) {
        ret = (item.isClass) ? T_CLASS : T_FUNCTION ;
      } else if (item instanceof SC.Error) {
        ret = T_ERROR ;        
      } else if (item.isObject === true) {
        ret = T_OBJECT ;
      } else ret = T_HASH ;
    } else if (ret === T_FUNCTION) ret = (item.isClass) ? T_CLASS : T_FUNCTION;
    return ret ;
  },
  
  // this will compare two values to see if they are equal.  If you have two 
  // values of unknown type, this is faster across all browsers than ===.
  isEqual: function(a,b) {
    if (a === null) {
      return b === null ;
    } else if (a === undefined) {
      return b === undefined ;
    } else if (typeof(a) == typeof(b)) {
      return a == b ;
    }
  },
  
  isArray: function( obj )
  {
    return ($type(obj) === T_ARRAY) || (obj && obj.objectAt);
  },
  
  _nextGUID: 0,
  
  /**
    Returns a unique GUID for the object.  If the object does not yet have
    a guid, one will be assigned to it.  You can call this on any object,
    SC.Object-based or not, but be aware that it will add a _guid property.
  */
  guidFor: function(obj) {
    if (obj == null) return 0 ;
    return obj._guid ? obj._guid : (obj._guid = SC._nextGUID++);
  },
  

  /**
    Convenience method to inspect an object by converting it to a hash.
  */
  inspect: function(obj) {
    return $H(obj).inspect() ;  
  },
  
  /** Browser and Platform info. */
  Platform: {
    
    /** The current IE version number or 0 if not IE. */
    IE: function() {
      if (Prototype.Browser.IE) {
        return (navigator.appVersion.match(/\bMSIE.*7\.\b/)) ? 7 : 6 ;
      } else return 0 ;
    }(),
    
    /** The current Safari major version number of 0 if not Safari */
    Safari: function() {
      if (Prototype.Browser.WebKit) {
        var vers = parseInt(navigator.appVersion.replace(/^.*?AppleWebKit\/(\d+).*?$/,'$1'),0) ;
        return (vers > 420) ? 3 : 2 ;
      } return 0 ;
    }(),
    
    /** The current Firefox major version number or 0 if not Firefox */
    Firefox: function() {
      var ret = 0;
      if (Prototype.Browser.Gecko) {
        if(navigator.userAgent.indexOf("Firefox") != -1)
        {
          ret = parseFloat((navigator.userAgent.match(/Firefox\/(.)/)[1]) || 0);
        }
        if (ret < 1) ret = 2; // default to version 2 if it is a Gecko browser.
      } 
      return ret ;
    }(),    
      
    isWindows: function() {
      return !!(navigator.appVersion.match(/(Windows)/)) ;
    }(),
    
    isMac: function() {
      if(Prototype.Browser.Gecko) {
        return !!(navigator.appVersion.match(/(Macintosh)/));
      } else {
        return !!(navigator.appVersion.match(/(Mac OS X)/)) ;    
      }
    }()
    
  },
  
  // DEPRECATED.  here for compatibility only.
  /** @private */
  isIE: function() { 
    return SC.Platform.IE > 0 ;
  },

  /** @private */
  isSafari: function() {
    return SC.Platform.Safari > 0 ;
  },
  
  /** @private */
  isSafari3: function() {
    return SC.Platform.Safari >= 3 ;
  },
  
  /** @private */
  isIE7: function() {
    return SC.Platform.IE >= 7 ;
  },

  /** @private */
  isIE6: function() {
    return (SC.Platform.IE >= 6) && (SC.Platform.IE < 7) ;
  },

  /** @private */
  isWindows: function() {
    return SC.Platform.isWindows;
  },

  /** @private */
  isMacOSX: function() {
    return SC.Platform.isMac ;
  },
  
  /** @private */
  isFireFox: function() {
    return SC.Platform.Firefox > 0 ;
  },
  
  /** @private */
  isFireFox2: function() {
    return SC.Platform.Firefox >= 2 ;
  }
  
});

/** @deprecated  Use guidFor() instead. */
SC.getGUID = SC.guidFor ;

// Save the Platform.Browser name.
SC.Platform.Browser = function() {
  if (SC.Platform.IE >0) {
    return 'IE';
  } else if (SC.Platform.Safari > 0) {
    return 'Safari';
  } else if (SC.Platform.Firefox >0) {
    return 'Firefox'; 
  }
}() ;

T_ERROR = 'error' ;
T_OBJECT = 'object' ;
T_NULL = 'null';
T_CLASS = 'class' ;
T_HASH = 'hash' ;
T_FUNCTION = 'function' ;
T_UNDEFINED = 'undefined' ;
T_NUMBER = 'number' ;
T_BOOL = 'boolean' ;
T_ARRAY = 'array' ;
T_STRING = 'string' ;

$type = SC.typeOf ;

$I = SC.inspect ;

Object.extend(Object,{

  // this will serialize a general JSON object into a URI.
  serialize: function(obj) {
    var ret = [] ;
    for(var key in obj) {
      var value = obj[key] ;
      if (typeof value == 'number') { value = '' + value ; }
      if (!(typeof value == 'string')) { value = value.join(','); }
      ret.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)) ;
    }
    return ret.join('&') ;
  }
  
}) ;


// This will add or remove the class name based on the flag, allowing you to
// treat it like a bool setting.  Simplifies the common case where you need
// to make a class name match a bool.
Element.setClassName = function(element,className,flag) {
  if(SC.isIE())
  {
    if (flag) { 
      Element.addClassName(element,className); 
    } else {
      Element.removeClassName(element,className) ;
    }
  } 
  else
  {
    if (flag) { 
      element.addClassName(className); 
    } else {
      element.removeClassName(className) ;
    }
  } 
} ;

// ........................................
// EVENT EXTENSIONS
// 
Object.extend(Event,{
  // get the character code for key pressed events.
  getCharCode: function(e) {
    return (e.keyCode) ? e.keyCode : ((e.which)?e.which:0) ; 
  },
  
  // get the pressed char as a string.
  getCharString: function(e) {
    return String.fromCharCode(Event.getCharCode(e)) ;
  },
  
  pointerLocation: function(event) {
    var ret = {
      x: event.pageX || (event.clientX +
        (document.documentElement.scrollLeft || document.body.scrollLeft)),
      y: event.pageY || (event.clientY +
        (document.documentElement.scrollTop || document.body.scrollTop))
      
    };
    return ret ;
  },
  
  ALT_KEY: '_ALT',
  CTRL_KEY: '_CTRL',
  SHIFT_KEY: '_SHIFT'
  
});



/* End ------------------------------------------------------- core.js*/

/* Start ----------------------------------------------------- foundation/date.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

// Extensions to the Date object. Comes from JavaScript Toolbox at:
// http://www.mattkruse.com/javascript/date/source.html

// ------------------------------------------------------------------
// These functions use the same 'format' strings as the 
// java.text.SimpleDateFormat class, with minor exceptions.
// The format string consists of the following abbreviations:
// 
// Field        | Full Form          | Short Form
// -------------+--------------------+-----------------------
// Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
// Month        | MMM (name or abbr.)| MM (2 digits), M (1 or 2 digits)
//              | NNN (abbr.)        |
// Day of Month | dd (2 digits)      | d (1 or 2 digits)
// Day of Week  | EE (name)          | E (abbr)
// Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
// Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
// Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
// Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
// Minute       | mm (2 digits)      | m (1 or 2 digits)
// Second       | ss (2 digits)      | s (1 or 2 digits)
// AM/PM        | a                  |
//
// NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
// Examples:
//  "MMM d, y" matches: January 01, 2000
//                      Dec 1, 1900
//                      Nov 20, 00
//  "M/d/yy"   matches: 01/20/00
//                      9/2/00
//  "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
// ------------------------------------------------------------------

var MONTH_NAMES=new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
var DAY_NAMES=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');
function LZ(x) {return(x<0||x>9?"":"0")+x;}

Object.extend(Date,{
  
  // returns the current time as an offset
  now: function() {
    return new Date().getTime() ;
  },
  
  // ------------------------------------------------------------------
  // isDate ( date_string, format_string )
  // Returns true if date string matches format of format string and
  // is a valid date. Else returns false.
  // It is recommended that you trim whitespace around the value before
  // passing it to this function, as whitespace is NOT ignored!
  // ------------------------------------------------------------------
  isDate: function(val,format) {
  	var date = Date.getDateFromFormat(val,format);
  	if (date==0) { return false; }
  	return true;
	},

  // -------------------------------------------------------------------
  // compareDates(date1,date1format,date2,date2format)
  //   Compare two date strings to see which is greater.
  //   Returns:
  //   1 if date1 is greater than date2
  //   0 if date2 is greater than date1 of if they are the same
  //  -1 if either of the dates is in an invalid format
  // -------------------------------------------------------------------
  compareDates: function(date1,dateformat1,date2,dateformat2) {
  	var d1= Date.getDateFromFormat(date1,dateformat1);
  	var d2= Date.getDateFromFormat(date2,dateformat2);
  	if (d1==0 || d2==0) {
  		return -1;
  		}
  	else if (d1 > d2) {
  		return 1;
  		}
  	return 0;
	},
	
  // ------------------------------------------------------------------
  // getDateFromFormat( date_string , format_string )
  //
  // This function takes a date string and a format string. It matches
  // If the date string matches the format string, it returns the 
  // getTime() of the date. If it does not match, it returns 0.
  // ------------------------------------------------------------------
  getDateFromFormat: function(val,format) {
  	val=val+"";
  	format=format+"";
  	var i_val=0;
  	var i_format=0;
  	var c="";
  	var token="";
  	var token2="";
  	var x,y;
  	var now=new Date();
  	var year=now.getFullYear();
  	var month=now.getMonth()+1;
  	var date=1;
  	var hh=now.getHours();
  	var mm=now.getMinutes();
  	var ss=now.getSeconds();
  	var ampm="";

  	while (i_format < format.length) {
  		// Get next token from format string
  		c=format.charAt(i_format);
  		token="";
  		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
  			token += format.charAt(i_format++);
  			}
  		// Extract contents of value based on format token
  		if (token=="yyyy" || token=="yy" || token=="y") {
  			if (token=="yyyy") { x=4;y=4; }
  			if (token=="yy")   { x=2;y=2; }
  			if (token=="y")    { x=2;y=4; }
  			year=Date._getInt(val,i_val,x,y);
  			if (year==null) { return 0; }
  			i_val += year.length;
  			if (year.length==2) {
  				if (year > 70) { year=1900+(year-0); }
  				else { year=2000+(year-0); }
  				}
  			}
  		else if (token=="MMM"||token=="NNN"){
  			month=0;
  			for (var i=0; i<MONTH_NAMES.length; i++) {
  				var month_name=MONTH_NAMES[i];
  				if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase()) {
  					if (token=="MMM"||(token=="NNN"&&i>11)) {
  						month=i+1;
  						if (month>12) { month -= 12; }
  						i_val += month_name.length;
  						break;
  						}
  					}
  				}
  			if ((month < 1)||(month>12)){return 0;}
  			}
  		else if (token=="EE"||token=="E"){
  			for (var i=0; i<DAY_NAMES.length; i++) {
  				var day_name=DAY_NAMES[i];
  				if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase()) {
  					i_val += day_name.length;
  					break;
  					}
  				}
  			}
  		else if (token=="MM"||token=="M") {
  			month=Date._getInt(val,i_val,token.length,2);
  			if(month==null||(month<1)||(month>12)){return 0;}
  			i_val+=month.length;}
  		else if (token=="dd"||token=="d") {
  			date=Date._getInt(val,i_val,token.length,2);
  			if(date==null||(date<1)||(date>31)){return 0;}
  			i_val+=date.length;}
  		else if (token=="hh"||token=="h") {
  			hh=Date._getInt(val,i_val,token.length,2);
  			if(hh==null||(hh<1)||(hh>12)){return 0;}
  			i_val+=hh.length;}
  		else if (token=="HH"||token=="H") {
  			hh=Date._getInt(val,i_val,token.length,2);
  			if(hh==null||(hh<0)||(hh>23)){return 0;}
  			i_val+=hh.length;}
  		else if (token=="KK"||token=="K") {
  			hh=Date._getInt(val,i_val,token.length,2);
  			if(hh==null||(hh<0)||(hh>11)){return 0;}
  			i_val+=hh.length;}
  		else if (token=="kk"||token=="k") {
  			hh=Date._getInt(val,i_val,token.length,2);
  			if(hh==null||(hh<1)||(hh>24)){return 0;}
  			i_val+=hh.length;hh--;}
  		else if (token=="mm"||token=="m") {
  			mm=Date._getInt(val,i_val,token.length,2);
  			if(mm==null||(mm<0)||(mm>59)){return 0;}
  			i_val+=mm.length;}
  		else if (token=="ss"||token=="s") {
  			ss=Date._getInt(val,i_val,token.length,2);
  			if(ss==null||(ss<0)||(ss>59)){return 0;}
  			i_val+=ss.length;}
  		else if (token=="a") {
  			if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";}
  			else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";}
  			else {return 0;}
  			i_val+=2;}
  		else {
  			if (val.substring(i_val,i_val+token.length)!=token) {return 0;}
  			else {i_val+=token.length;}
  			}
  		}
  	// If there are any trailing characters left in the value, it doesn't match
  	if (i_val != val.length) { return 0; }
  	// Is date valid for month?
  	if (month==2) {
  		// Check for leap year
  		if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
  			if (date > 29){ return 0; }
  			}
  		else { if (date > 28) { return 0; } }
  		}
  	if ((month==4)||(month==6)||(month==9)||(month==11)) {
  		if (date > 30) { return 0; }
  		}
  	// Correct hours value
  	if (hh<12 && ampm=="PM") { hh=hh-0+12; }
  	else if (hh>11 && ampm=="AM") { hh-=12; }
  	var newdate=new Date(year,month-1,date,hh,mm,ss);
  	return newdate.getTime();
  },

  // ------------------------------------------------------------------
  // parseDate( date_string [, prefer_euro_format] )
  //
  // This function takes a date string and tries to match it to a
  // number of possible date formats to get the value. It will try to
  // match against the following international formats, in this order:
  // y-M-d   MMM d, y   MMM d,y   y-MMM-d   d-MMM-y  MMM d
  // M/d/y   M-d-y      M.d.y     MMM-d     M/d      M-d
  // d/M/y   d-M-y      d.M.y     d-MMM     d/M      d-M
  // 
  // Also understands: 
  // 
  // yesterday, today, tomorrow, now
  //
  // A second argument may be passed to instruct the method to search
  // for formats like d/M/y (european format) before M/d/y (American).
  // Returns a Date object or null if no patterns match.
  // ------------------------------------------------------------------
  parseDate: function(val) {
  	var preferEuro=(arguments.length==2)?arguments[1]:false;
  	generalFormats=new Array('E NNN dd HH:mm:ss UTC yyyy','y-M-d','y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d','d MMM y','d.MMM.y','y MMM d','y.MMM.d');
  	monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');
  	dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');
  	var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');
  	var d=null;
  	
  	// first look for natural language
  	d = 0 ; var now = new Date().getTime() ;
  	switch(val.toLowerCase()) {
  	  case 'yesterday'.loc():
  	    d = now - (24*60*60*1000) ;
  	    break ;
  	  case 'today'.loc():
  	  case 'now'.loc():
  	    d = now ;
  	    break ;
  	  case 'tomorrow'.loc():
  	    d = now + (24*60*60*1000) ;
  	    break;
  	}
  	if (d>0) return new Date(d) ;
  	
  	for (var i=0; i<checkList.length; i++) {
  		var l=window[checkList[i]];
  		for (var j=0; j<l.length; j++) {
  			d=Date.getDateFromFormat(val,l[j]);
  			if (d==0) d = Date.getDateFromFormat(val,l[j] + ' H:m:s') ;
  			if (d==0) d = Date.getDateFromFormat(val,l[j] + ' h:m:s a') ;
  			if (d!=0) return new Date(d); 
  		}
  	}
  	return null;
  },
  
  // ------------------------------------------------------------------
  // Utility functions for parsing in getDateFromFormat()
  // ------------------------------------------------------------------
  _isInteger: function(val) {
  	var digits="1234567890";
  	for (var i=0; i < val.length; i++) {
  		if (digits.indexOf(val.charAt(i))==-1) { return false; }
  	}
  	return true;
  },
  
  _getInt: function(str,i,minlength,maxlength) {
  	for (var x=maxlength; x>=minlength; x--) {
  		var token=str.substring(i,i+x);
  		if (token.length < minlength) { return null; }
  		if (Date._isInteger(token)) { return token; }
  	}
  	return null;
  }

}) ;

Object.extend(Date.prototype, {
  
  // ------------------------------------------------------------------
  // formatDate (date_object, format, naturalLanguage)
  // Returns a date in the output format specified.
  // The format string uses the same abbreviations as in getDateFromFormat()
  // 
  // ------------------------------------------------------------------
  format: function(format) {
  	format=format+"";
    var date = this ;
  	var result="";
  	var i_format=0;
  	var c="";
  	var token="";
  	var y=date.getFullYear()+"";
  	var M=date.getMonth()+1;
  	var d=date.getDate();
  	var E=date.getDay();
  	var H=date.getHours();
  	var m=date.getMinutes();
  	var s=date.getSeconds();
  	var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
  	// Convert real date parts into formatted versions
  	var value=new Object();
  	if (y.length < 4) {y=""+(y-0+1900);}
  	value["y"]=""+y;
  	value["yyyy"]=y;
  	value["yy"]=y.substring(2,4);
  	value["M"]=M;
  	value["MM"]=LZ(M);
  	value["MMM"]=MONTH_NAMES[M-1];
  	value["NNN"]=MONTH_NAMES[M+11];
  	value["d"]=d;
  	value["dd"]=LZ(d);
  	value["E"]=DAY_NAMES[E+7];
  	value["EE"]=DAY_NAMES[E];
  	value["H"]=H;
  	value["HH"]=LZ(H);
  	if (H==0){value["h"]=12;}
  	else if (H>12){value["h"]=H-12;}
  	else {value["h"]=H;}
  	value["hh"]=LZ(value["h"]);
  	if (H>11){value["K"]=H-12;} else {value["K"]=H;}
  	value["k"]=H+1;
  	value["KK"]=LZ(value["K"]);
  	value["kk"]=LZ(value["k"]);
  	if (H > 11) { value["a"]="PM"; }
  	else { value["a"]="AM"; }
  	value["m"]=m;
  	value["mm"]=LZ(m);
  	value["s"]=s;
  	value["ss"]=LZ(s);
  	while (i_format < format.length) {
  		c=format.charAt(i_format);
  		token="";
  		while ((format.charAt(i_format)==c) && (i_format < format.length)) {
  			token += format.charAt(i_format++);
  			}
  		if (value[token] != null) { result=result + value[token]; }
  		else { result=result + token; }
  		}
  	return result;
  },
  
  utcFormat: function() { return (new Date(this.getTime() + (this.getTimezoneOffset() * 60 * 1000))).format('E NNN dd HH:mm:ss UTC yyyy'); }

}) ;


/* End ------------------------------------------------------- foundation/date.js*/

/* Start ----------------------------------------------------- foundation/string.js*/

// ==========================================================================
// SproutCore -- JavaScript Application Framework
// copyright 2006-2008, Sprout Systems, Inc. and contributors.
// ==========================================================================

// These are basic enhancements to the string class used throughout 
// SproutCore.
// capitalize a string.
Object.extend(String.prototype,{

  // Capitalize a string.
  //
  // mode: optional.  'each' - capitalize each word. the default.
  //                  'first' - capitalize the first word only.
  
  capitalize: function(mode) {
    var words = (mode == 'first') ? this : this.split(' ') ;
    words = words.map(function(word) {
      if (word.length == 0) return word ;
      return word.charAt(0).toUpperCase() + word.substring(1) ;
    }) ;
    return words.join(' ') ;
  },

  // Interpolate string. looks for %@ or %@1; to control the order of params.
  format: function() {
    var args = $A(arguments) ;
    
    // first, replace any ORDERED replacements.
    var str = this.gsub(/%@([0-9]+)/, function(m) {
      return (args[parseInt(m[1],0)-1] || '').toString(); 
    }) ;

    // now, replace any remaining %@ items.  Use this indexOf() method b/c
    // it is faster than split().
    var ret = [] ;
    var idx = -1 ;
    var loc = 0 ;
    while((idx = str.indexOf("%@",loc)) >= 0) {
     // slice off initial part of string and push into ret. update loc.
     ret.push(str.slice(loc,idx)) ;
     loc = idx + 2 ; // 2 to skip '%@'.
     
     // add in replacement.
     var value = args.shift() ;
     if (value && value.toString) value = value.toString() ;
     ret.push(value) ;
    }
    
    // include any remaining bits of the string.
    if (loc < str.length) {
      ret.push(str.slice(loc,str.length)) ;
    }
    
    // join return value.
    return (ret.length > 1) ? ret.join('') : ret[0] ;
  },

  // localize a string.  Also interpolates any items you pass just like
  // format().
  loc: function() {
    // NB: This could be implemented as a wrapper to locWithDefault() but
    // it would add some overhead to deal with the arguments and adds stack
    // frames, so we are keeping the implementation separate.
    
    var kit = String[String.currentLanguage()];
    var str = kit[this] ;
    if (!str) str = String.English[this] || this ;
    return str.format.apply(str,arguments) ;
  },
  
  // this works just like loc except it will return the first argument
  // as a default if the matching value is not found.
  locWithDefault: function(def) {
    var kit = String[String.currentLanguage()];
    var str = kit[this] ;
    if (!str) str = String.English[this] || def ;
    var args = $A(arguments) ;
    args.shift() ; // escape def.
    return str.format.apply(str, args) ;
  },
  
  classify: function() {
    return this.camelize().capitalize() ;
  },
  
  decamelize: function() { 
    return this.replace(/([a-z])([A-Z])/g,'$1_$2').toLowerCase();
  },

  dasherize: function() {
    return this.decamelize().replace(/[ _]/g,'-') ;  
  },
  
  humanize: function() {
    return this.decamelize().replace(/[-_]/g,' ') ;
  },
  
  toHref: function() {
    if (this.match(/.+@.+\...+/)) {
      return 'mailto:' + this;
    } else if (this.indexOf('http://') != 0 && this.indexOf('https://') !=0 && this.match(/[^.]+\.[^.]+/)) {
      return 'http://' + this;
    } else {
      return this;
    }
  },
  
  trim: function ()
  {
    return this.replace(/^\s+|\s+$/g,"");
  },
  
  strip: function()
  {
    return this.trim();
  }
  
}) ;

// Shorter alias of the format function.
String.prototype.fmt = String.prototype.format ;

// Add strings for various languages to this collection.  String.loc()
// method will try to localize the string passed using the current language.
// if the language is not available, it will use English.
Object.extend(String,
/** @scope String @static */ {

  /**
    The current browser language as a two letter code.
  */
  browserLanguage: ((navigator.language || navigator.browserLanguage).split('-', 1)[0]),
  
  /**
    If YES, localization will favor the detected language instead of the
    preferred one.
  */
  useAutodetectedLanguage: NO,
  
  /**
    This property is set by the build tools to the current build language.
  */
  preferredLanguage: null,
  
  /**
    Returns the hash key to use for loc strings.  The default implementation
    will autodetect the browser language and look for a loc string to 
    match.  If it can't find one then it will introspect to find loc strings
    that are defined and use those instead.
  */
  currentLanguage: function () {
    
    var ret = (this.useAutodetectedLanguage) ? (this.browserLanguage || this.preferredLanguage || 'en') : (this.preferredLanguage || this.browserLanguage || 'en') ;

    // then try a couple of normalized forms...
    if (!this[ret]) ret = this.normalizedLanguage(ret);
    return ret ;
  },
  
  /**
    Returns a normalized language string for the two letter country code.
  */
  normalizedLanguage: function(ret) {
    switch(ret) {
      case 'fr':
        ret = 'French'; 
        break ;
      case 'de':
        ret = 'German'; 
        break ;
      case 'ja':
      case 'jp':
        ret = 'Japanese'; 
        break ;
      case 'en':
        ret = 'English' ;
        break ;
      
      case 'es':
        ret = 'Spanish' ;
        break;
        
      default:
        break ;
    }
    return ret;
  },
  
  /**
    Adds loc strings for the named language.  This method takes care of 
    creating the localized string hash if it does not already exist.
    The language can be one of the following or any two-letter country code.
    
    English, French, German, Japanese, Spanish
    
    @param language {String} the language code
    @param strings {Hash} hash of loc strings.
    @returns {this}
  */
  addStringsFor: function(language, strings) {    
    // convert language to a normalized name...
    language = String.normalizedLanguage(language) ;
    if (!String[language]) String[language] = {} ;
    Object.extend(String[language], strings || {}); 
    return this;
  }

});

String.English  = String.English  || {};
String.French   = String.French   || {};
String.German   = String.German   || {};
String.Japanese = String.Japanese || {};


/* End ------------------------------------------------------- foundation/string.js*/

/* Start ----------------------------------------------------- foundation/benchmark.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('core') ;
require('foundation/date');
require('foundation/string') ;

/** @namespace SC.Benchmark

  This bit of meta-programming magic can install a benchmark handler on any
  object method.  When a benchmark is installed, the time required to execute
  the method will be printed to the console log everytime the method is 
  called.

  This class can be used to implement benchmarking.  To use this object, just
  call start() with a key name and end() with a keyname.  The benchmark will 
  be logged.  If you set verbose = true, then benchmark will log everytime it 
  saves a bench.  Otherwise, it just keeps stats.  You can get the stats by
  calling report().

  Benchmark does not require anything other than the date.js class.  It also
  does not rely on SC.Object so that you can benchmark code in that area as
  well.

*/
SC.Benchmark = {

  /**
    If true, then benchmarks will be logged to the console as they are 
    recorded.
  
    @type bool
  */
  verbose: NO,
  
  /**
    If false, benchmarking will be disabled.  You might want to disable this
    during production to maximize performance.
  
    @type bool
  */
  enabled: YES,
  
/**
   This hash stores collected stats.  It contains key value pairs.  The value
   will be a hash with the following properties:
   
  * * *runs*: the number of times this stat has run
  * * *amt*: the total time consumed by this (divide by runs to get avg)
  * * *name*: an optional longer name you assigned to the stat key.  Set this  using name().
  * * *_starts*: this array is used internally. 
*/
  stats: {},

  /**
    Call this method at the start of whatever you want to collect.
    if topLevelOnly is passed, then recursive calls to the start will be 
    ignored and only the top level call will be benchmarked.
    
    @param key {String} A unique key that identifies this benchmark.  All calls to start/end with the same key will be groups together.
    @param topLevelOnly {Boolean} If true then recursive calls to this method with the same key will be ignored.  
    @param time {Integer} Only pass if you want to explicitly set the start time.  Otherwise the start time is now.
  */
  start: function(key, topLevelOnly, time) {
    if (!this.enabled) return ;
    var stat = this._statFor(key) ;
    
    if (topLevelOnly && stat._starts.length > 0) {
      stat._starts.push('ignore') ;
    } else {
      stat._starts.push(time || Date.now()) ;
    }
  },
  
  /**
    Call this method at the end of whatever you want to collect.  This will
    save the collected benchmark.
    
    @param key {String} The benchmark key you used when you called start()
    @param time {Integer} Only pass if you want to explicitly set the end time.  Otherwise start time is now.
  */
  end: function(key, time) {
    if (!this.enabled) return ;
    var stat = this._statFor(key) ;
    var start = stat._starts.pop() ;
    if (!start) {
      console.log('WARNING: SC.Benchmark "%@" ended without a matching start.  No information was saved.'.fmt(key));
      return ;
    }

    // top level only.
    if (start == 'ignore') return ; 
    
    stat.amt += (time || Date.now()) - start ;
    stat.runs++ ;
    
    if (this.verbose) this.log(key) ;
  },

  /**
    This is a simple way to benchmark a function.  The function will be 
    run with the name you provide the number of times you indicate.  Only the
    function is a required param.
  */  
  bench: function(func, key, reps) {
    if (!key) key = "bench%@".fmt(this._benchCount++) ;
    if (!reps) reps = 1 ;
    var ret ;
    
    while(--reps >= 0) {
      SC.Benchmark.start(key) ;
      ret = func();
      SC.Benchmark.end(key) ; 
    }
    
    return ret ;
  },
  
  /**  
    This bit of metaprogramming magic install a wrapper around a method and
    benchmark it whenever it is run.
  */  
  install: function(object,method, topLevelOnly) {
    
    // vae the original method.
    var __func = object['b__' + method] = object[method] ;
    
    // replace with this helper.
    object[method] = function() {
      var key = '%@(%@)'.fmt(method, $A(arguments).join(', ')) ;
      SC.Benchmark.start(key, topLevelOnly) ;
      var ret = __func.apply(this, arguments) ;
      SC.Benchmark.end(key) ;
      return ret ;
    } ;
  },
  
  /**
    Restore the original method, deactivating the benchmark.
  
    @param {object} object the object to change
    @param {string} method the method name as a string.
  
  */  
  restore: function(object,method) {
    object[method] = object['b__' + method] ;
  },
  
  /**
    This method will return a string containing a report of the stats
    collected so far.  If you pass a key, only the stats for that key will
    be returned.  Otherwise, all keys will be used.
  */
  report: function(key) {
    if (key) return this._genReport(key) ;
    var ret = [] ;
    for(var key in this.stats) {
      if (!this.stats.hasOwnProperty(key)) continue ;
      ret.push(this._genReport(key)) ;
    }
    return ret.join("\n") ;
  },

  /**
    This method is just like report() except that it will log the results to
    the console.
  */  
  log: function(key) {
    console.log(this.report(key)) ;
  },
  
  /**
    This will activate profiling if you have Firebug installed.  Otherwise
    does nothing.
  */
  startProfile: function(key) {
    if (!this.enabled) return ;
    if (console && console.profile) console.profile(key) ;
  },
  
  endProfile: function(key) {
    if (!this.enabled) return ;
    if (console && console.profileEnd) console.profileEnd(key) ;
  },
  
  // PRIVATE METHODS
  
  _genReport: function(key) {
    var stat = this._statFor(key) ;
    var avg = (stat.runs > 0) ? (Math.floor(stat.amt * 1000 / stat.runs) / 1000) : 0 ;
     
    return 'BENCH %@ msec: %@ (%@x)'.fmt(avg, (stat.name || key), stat.runs) ;  
  },
  
  // @private
  // returns a stats hash for the named key.  If the hash does not exist yet,
  // creates it.
  _statFor: function(key) {
    var ret = this.stats[key] ;
    if (!ret) ret = this.stats[key] = {
      runs: 0, amt: 0, name: key, _starts: []      
    };
    return ret ;
  },
  
  reset: function() { this.stats = {} ;  /*debugger;*/},
  
  // This is private, but it is used in some places, so we are keeping this for
  // compatibility.
  _bench: function(func, name) {
    SC.Benchmark.bench(func, name, 1) ;
  },
  
  _benchCount: 1
  
} ;


/* End ------------------------------------------------------- foundation/benchmark.js*/

/* Start ----------------------------------------------------- mixins/observable.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

/**
  @namespace 
  
  Key-Value-Observing (KVO) simply allows one object to observe changes to a 
  property on another object. It is one of the fundamental ways that models, 
  controllers and views communicate with each other in a SproutCore 
  application.  Any object that has this module applied to it can be used in 
  KVO-operations.
  
  This module is applied automatically to all objects that inherit from
  SC.Object, which includes most objects bundled with the SproutCore 
  framework.  You will not generally apply this module to classes yourself,
  but you will use the features provided by this module frequently, so it is
  important to understand how to use it.
  
  h2. Enabling Key Value Observing

  With KVO, you can write functions that will be called automatically whenever 
  a property on a particular object changes.  You can use this feature to
  reduce the amount of "glue code" that you often write to tie the various 
  parts of your application together.
  
  To use KVO, just use the KVO-aware methods get() and set() to access 
  properties instead of accessing properties directly.  Instead of writing:
  
  {{{
    var aName = contact.firstName ;
    contact.firstName = 'Charles' ;
  }}}

  use:

  {{{
    var aName = contact.get('firstName') ;
    contact.set('firstName', 'Charles') ;
  }}}
  
  get() and set() work just like the normal "dot operators" provided by 
  JavaScript but they provide you with much more power, including not only
  observing but computed properties as well.

  h2. Observing Property Changes

  You typically observe property changes simply by adding the observes() 
  call to the end of your method declarations in classes that you write.  For
  example:
  
  {{{
    SC.Object.create({
      valueObserver: function() {
        // Executes whenever the "Value" property changes
      }.observes('value')
    }) ;
  }}}
  
  Although this is the most common way to add an observer, this capability is
  actually built into the SC.Object class on top of two methods defined in
  this mixin called addObserver() and removeObserver().  You can use these two
  methods to add and remove observers yourself if you need to do so at run 
  time.  
  
  To add an observer for a property, just call:
  
  {{{
    object.addObserver('propertyKey', targetObject, targetAction) ;
  }}}
  
  This will call the 'targetAction' method on the targetObject to be called
  whenever the value of the propertyKey changes.
  

*/
SC.Observable = {

  /**  
    Manually add a new binding to an object.  This is the same as doing
    the more familiar propertyBinding: 'property.path' approach.
  */
  bind: function(toKey, fromPropertyPath) {
    
    var r = SC.idt.active ;
    
    var binding ;
    var props = { to: [this, toKey] } ;

    // for strings try to do default relay
    var pathType = $type(fromPropertyPath) ;
    if (pathType == T_STRING || pathType == T_ARRAY) {
      binding = this[toKey + 'BindingDefault'] || SC.Binding.From;
      binding = binding(fromPropertyPath) ;
    } else binding = fromPropertyPath ;

    // check the 'from' value of the relay. if it starts w/
    // '.' || '*' then convert to a local tuple.
    var relayFrom = binding.prototype.from ;
    if ($type(relayFrom) == T_STRING) switch(relayFrom.slice(0,1)) {
      case '*':
      case '.':
        relayFrom = [this,relayFrom.slice(1,relayFrom.length)];
    }        

    if(r) bt = new Date().getTime();

    binding = binding.create(props, { from: relayFrom }) ;
    this.bindings.push(binding) ;

    if (r) SC.idt.b1_t += (new Date().getTime()) - bt ;
    
    return binding ;
  },
  
  /**  
    didChangeFor makes it easy for you to verify that you haven't seen any
    changed values.  You need to use this if your method observes multiple
    properties.  To use this, call it like this:
  
    if (this.didChangeFor('render','height','width')) {
       // DO SOMETHING HERE IF CHANGED.
    }
  */  
  didChangeFor: function(context) {    
    var keys = $A(arguments) ;
    context = keys.shift() ;
    
    var ret = false ;
    if (!this._didChangeCache) this._didChangeCache = {} ;
    if (!this._didChangeRevisionCache) this._didChangeRevisionCache = {};
    
    var seen = this._didChangeCache[context] || {} ;
    var seenRevisions = this._didChangeRevisionCache[context] || {} ;
    var loc = keys.length ;
    var rev = this._kvo().revision ;
    
    while(--loc >= 0) {
      var key = keys[loc] ;
      if (seenRevisions[key] != rev) {
        var val = this.get(key) ;
        if (seen[key] !== val) ret = true ;
        seen[key] = val ;
      }
      seenRevisions[key] = rev ;
    }
    
    this._didChangeCache[context] = seen ;
    this._didChangeRevisionCache[context] = seenRevisions ;
    return ret ;
  },

  // ..........................................
  // PROPERTIES
  // 
  // Use these methods to get/set properties.  This will handle observing
  // notifications as well as allowing you to define functions that can be 
  // used as properties.

  /**  
    Retrieves the value of key from the object.
    
    This method is generally very similar to using object[key] or object.key,
    however it supports both computed properties and the unknownProperty
    handler.
    
    *Computed Properties*
    
    Computed properties are methods defined with the property() modifier
    declared at the end, such as:
    
    {{{
      fullName: function() {
        return this.getEach('firstName', 'lastName').compact().join(' ');
      }.property('firstName', 'lastName')
    }}}
    
    When you call get() on a computed property, the property function will be
    called and the return value will be returned instead of the function
    itself.
    
    *Unknown Properties*
    
    Likewise, if you try to call get() on a property whose values is
    undefined, the unknownProperty() method will be called on the object.
    If this method reutrns any value other than undefined, it will be returned
    instead.  This allows you to implement "virtual" properties that are 
    not defined upfront.
    
    @param key {String} the property to retrieve
    @returns {Object} the property value or undefined.
    
  */
  get: function(key) {
    var ret = this[key] ;
    if (ret === undefined) {
      return this.unknownProperty(key) ;
    } else if (ret && (ret instanceof Function) && ret.isProperty) {
      return ret.call(this,key) ;
    } else return ret ;
  },

  /**  
    Sets the key equal to value.
    
    This method is generally very similar to calling object[key] = value or
    object.key = value, except that it provides support for computed 
    properties, the unknownProperty() method and property observers.
    
    *Computed Properties*
    
    If you try to set a value on a key that has a computed property handler
    defined (see the get() method for an example), then set() will call
    that method, passing both the value and key instead of simply changing 
    the value itself.  This is useful for those times when you need to 
    implement a property that is composed of one or more member
    properties.
    
    *Unknown Properties*
    
    If you try to set a value on a key that is undefined in the target 
    object, then the unknownProperty() handler will be called instead.  This
    gives you an opportunity to implement complex "virtual" properties that
    are not predefined on the obejct.  If unknownProperty() returns 
    undefined, then set() will simply set the value on the object.
    
    *Property Observers*
    
    In addition to changing the property, set() will also register a 
    property change with the object.  Unless you have placed this call 
    inside of a beginPropertyChanges() and endPropertyChanges(), any "local"
    observers (i.e. observer methods declared on the same object), will be
    called immediately.  Any "remote" observers (i.e. observer methods 
    declared on another object) will be placed in a queue and called at a
    later time in a coelesced manner.
    
    *Chaining*
    
    In addition to property changes, set() returns the value of the object
    itself so you can do chaining like this:
    
    {{{
      record.set('firstName', 'Charles').set('lastName', 'Jolley');
    }}}
    
    @param key {String} the property to set
    @param value {Object} the value to set or null.
    @returns {this}
  */
  set: function(key, value) {
    var func = this[key] ;
    var ret = value ;

    this.propertyWillChange(key) ;

    // set the value.
    if (func && (func instanceof Function) && (func.isProperty)) {
      ret = func.call(this,key,value) ;
    } else if (func === undefined) {
      ret = this.unknownProperty(key,value) ;
    } else ret = this[key] = value ;

    // post out notifications.
    this.propertyDidChange(key, ret) ;
    return this ;
  },  

  /**
    Sets the property only if the passed value is different from the
    current value.  Depending on how expensive a get() is on this property,
    this may be more efficient.
    
    @param key {String} the key to change
    @param value {Object} the value to change
    @returns {this}
  */
  setIfChanged: function(key, value) {
    return (this.get(key) !== value) ? this.set(key, value) : this ;
  },
  
  /**  
    Navigates the property path, returning the value at that point.
    
    If any object in the path is undefined, returns undefined.
  */
  getPath: function(path) {
    var tuple = SC.Object.tupleForPropertyPath(path, this) ;
    if (tuple[0] === null) return undefined ;
    return tuple[0].get(tuple[1]) ;
  },
  
  /**
    Navigates the property path, finally setting the value.
    
    @param path {String} the property path to set
    @param value {Object} the value to set
    @returns {this}
  */
  setPath: function(path, value) {
    var tuple = SC.Object.tupleForPropertyPath(path, this) ;
    if (tuple[0] == null) return null ;
    tuple[0].set(tuple[1], value) ;
    return this;
  },

  
  /** 
    Convenience method to get an array of properties.
    
    Pass in multiple property keys or an array of property keys.  This
    method uses getPath() so you can also pass key paths.

    @returns {Array} Values of property keys.
  */
  getEach: function() {
    var keys = $A(arguments).flatten() ;
    var ret = [];
    for(var idx=0; idx<keys.length;idx++) {
      ret[ret.length] = this.getPath(keys[idx]);
    }
    return ret ;
  },
  
  
  /**  
    Increments the value of a property.
    
    @param key {String} property name
    @returns {Number} new value of property
  */
  incrementProperty: function(key) { 
    this.set(key,(this.get(key) || 0)+1); 
    return this.get(key) ;
  },

  /**  
    decrements a property
    
    @param key {String} property name
    @returns {Number} new value of property
  */
  decrementProperty: function(key) {
    this.set(key,(this.get(key) || 0) - 1 ) ;
    return this.get(key) ;
  },

  /**  
    Inverts a property.  Property should be a bool.
    
    @param key {String} property name
    @param value {Object} optional parameter for "true" value
    @param alt {Object} optional parameter for "false" value
    @returns {Object} new value
  */
  toggleProperty: function(key,value,alt) { 
    if (value === undefined) value = true ;
    if (alt == undefined) alt = false ;
    value = (this.get(key) == value) ? alt : value ;
    this.set(key,value);
    return this.get(key) ;
  },

  /**  
    Called whenever you try to get or set an undefined property.
    
    This is a generic property handler.  If you define it, it will be called
    when the named property is not yet set in the object.  The default does
    nothing.
    
    @param key {String} the key that was requested
    @param value {Object} The value if called as a setter, undefined if called as a getter.
    @returns {Object} The new value for key.
  */
  unknownProperty: function(key,value) {
    if (!(value === undefined)) { this[key] = value; }
    return value ;
  },

  /**  
    Generic property observer called whenever a property on the receiver 
    changes.
    
    If you need to observe a large number of properties on your object, it
    is sometimes more efficient to implement this observer only and then to
    handle requests yourself.  Although this observer will be triggered 
    more often than an observer registered on a specific property, it also
    does not need to be registered which can make it faster to setup your 
    object instance.
    
    You will often implement this observer using a switch statement on the
    key parameter, taking appropriate action. 
    
    @param observer {null} no longer used; usually null
    @param target {Object} the target of the change.  usually this
    @param key {String} the name of the property that changed
    @param value {Object} the new value of the property.
    @returns {void}
  */
  propertyObserver: function(observer,target,key,value) {},

  /**  
    Begins a grouping of property changes.
    
    You can use this method to group property changes so that notifications
    will not be sent until the changes are finished.  If you plan to make a 
    large number of changes to an object at one time, you should call this 
    method at the beginning of the changes to suspend change notifications.
    When you are done making changes, all endPropertyChanges() to allow 
    notification to resume.
    
    @returns {this}
  */
  beginPropertyChanges: function() {
    this._kvo().changes++ ;
    return this;
  },

  /**  
    Ends a grouping of property changes.
    
    You can use this method to group property changes so that notifications
    will not be sent until the changes are finished.  If you plan to make a 
    large number of changes to an object at one time, you should call 
    beginsPropertyChanges() at the beginning of the changes to suspend change 
    notifications. When you are done making changes, call this method to allow 
    notification to resume.
    
    @returns {this}
  */
  endPropertyChanges: function() {
    var kvo = this._kvo() ;  kvo.changes--;
    if (kvo.changes <= 0) this._notifyPropertyObservers() ;
    return this ;
  },

  /**  
    Notify the observer system that a property is about to change.

    Sometimes you need to change a value directly or indirectly without 
    actually calling get() or set() on it.  In this case, you can use this 
    method and propertyDidChange() instead.  Calling these two methods 
    together will notify all observers that the property has potentially 
    changed value.
    
    Note that you must always call propertyWillChange and propertyDidChange as 
    a pair.  If you do not, it may get the property change groups out of order 
    and cause notifications to be delivered more often than you would like.
    
    @param key {String} The property key that is about to change.
    @returns {this}
  */
  propertyWillChange: function(key) {
    this._kvo().changes++ ;
    return this ;
  },

  /**  
    Notify the observer system that a property has just changed.

    Sometimes you need to change a value directly or indirectly without 
    actually calling get() or set() on it.  In this case, you can use this 
    method and propertyWillChange() instead.  Calling these two methods 
    together will notify all observers that the property has potentially 
    changed value.
    
    Note that you must always call propertyWillChange and propertyDidChange as 
    a pair. If you do not, it may get the property change groups out of order 
    and cause notifications to be delivered more often than you would like.
    
    @param key {String} The property key that has just changed.
    @param value {Object} The new value of the key.  May be null.
    @returns {this}
  */
  propertyDidChange: function(key,value) {
    this._kvo().changed[key] = value ;
    var kvo = this._kvo() ;  kvo.changes--; kvo.revision++ ;
    if (kvo.changes <= 0) this._notifyPropertyObservers() ;
    return this ;
  },

  /**
    Convenience method to call propertyWillChange/propertyDidChange.
    
    Sometimes you need to notify observers that a property has changed value 
    without actually changing this value.  In those cases, you can use this 
    method as a convenience instead of calling propertyWillChange() and 
    propertyDidChange().
    
    @param key {String} The property key that has just changed.
    @param value {Object} The new value of the key.  May be null.
    @returns {this}
  */
  notifyPropertyChange: function(key, value) {
    this.propertyWillChange(key) ;
    this.propertyDidChange(key, value) ;
    return this; 
  },
  
  /**  
    Notifies all of observers of a property changes.
    
    Sometimes when you make a major update to your object, it is cheaper to
    simply notify all observers that their property might have changed than
    to figure out specifically which properties actually did change.
    
    In those cases, you can simply call this method to notify all property
    observers immediately.  Note that this ignores property groups.
    
    @returns {this}
  */
  allPropertiesDidChange: function() {
    this._notifyPropertyObservers(true) ;
    return this ;
  },

  /**  
    Adds an observer on a property.
    
    This is the core method used to register an observer for a property.
    
    Once you call this method, anytime the key's value is set, your observer
    will be notified.  Note that the observers are triggered anytime the
    value is set, regardless of whether it has actually changed.  Your
    observer should be prepared to handle that.
    
    @param key {String} the key to observer
    @param func {String} the function to call when the key changes.
    @returns {SC.Object}
  */
  addObserver: function(key,func) {
    var kvo = this._kvo() ;

    // if the key contains a '.', then create a chained observer.
    key = key.toString() ;
    var parts = key.split('.') ;
    if (parts.length > 1) {
      var co = SC._ChainObserver.createChain(this,parts,func) ;
      co.masterFunc = func ;
      var chainObservers = kvo.chainObservers[key] || [] ;
      chainObservers.push(co) ;
      kvo.chainObservers[key] = chainObservers ;

    // otherwise, bind as a normal property
    } else {      
      var observers = kvo.observers[key] = (kvo.observers[key] || []) ;
      var found = false; var loc = observers.length;
      while(!found && --loc >= 0) found = (observers[loc] == func) ;
      if (!found) observers.push(func) ;
    }
    
    return this;

  },

  removeObserver: function(key,func) {
    var kvo = this._kvo() ;

    // if the key contains a '.', this is a chained observer.
    key = key.toString() ;
    var parts = key.split('.') ;
    if (parts.length > 1) {
      var chainObservers = kvo.chainObserver[key] || [] ;
      var newObservers = [] ;
      chainObservers.each(function(co) {
        if (co.masterFunc != func) newObservers.push(co) ;
      }) ;
      kvo.chainObservers[key] = newObservers ;

    // otherwise, just like a normal observer.
    } else {
      var observers = kvo.observers[key] || [] ;
      observers = observers.without(func) ;
      kvo.observers[key] = observers ;
    }
    
    return this;
  },

  addProbe: function(key) { this.addObserver(key,logChange); },
  removeProbe: function(key) { this.removeObserver(key,logChange); },

  /**
    Logs the named properties to the console.
    
    @param propertyNames one or more property names
  */
  logProperty: function() {
    var props = $A(arguments) ;
    for(var idx=0;idx<props.length; idx++) {
      var prop = props[idx] ;
      console.log('%@:%@: '.fmt(this._guid, prop), this.get(prop)) ;
    }
  },
  
  /**  
    This method will listen for the observed value to change one time and 
    then will remove itself.  You can also set an optional timeout that
    will cause the function to be triggered (and the observer removed) after
    a set amount of time even if the value never changes.  The function
    can expect an extra parameter, 'didTimeout', set to true.
  
    The returned value is the function actually set as the observer. You
    can manually remove this observer by calling the cancel() method on it.
  */
  observeOnce: function(key, func, timeout) {
    var timeoutObject = null ;
    var target = this ;
    var handler = function(theTarget,theKey,theValue,didTimeout) {
      func(theTarget,theKey,theValue,didTimeout) ;
      target.removeObserver(key,handler) ;
      if (timeoutObject) { timeoutObject.invalidate(); }
    } ;

    target.addObserver(key,handler) ;
    if (timeout) timeoutObject = function() {
      handler(target,key,target.get(key),true) ;
    }.invokeLater(this, timeout) ;

    handler.cancel = function() { target.removeObserver(key,handler); } ;
    return handler ;
  },

  /**
    Use this to indicate that one key changes if other keys it depends on 
    change.
  */  
  registerDependentKey: function(key) {
    var keys = $A(arguments) ;
    var dependent = keys.shift() ;
    var kvo = this._kvo() ;
    for(var loc=0;loc<keys.length;loc++) {
    	var key = keys[loc];
    	if (key instanceof Array) {
    		key.push(dependent) ;
    		this.registerDependentKey.apply(this,key) ;
    	} else {
				var dependents = kvo.dependents[key] || [] ;
				dependents.push(dependent) ;
				kvo.dependents[key] = dependents ;
    	}
    }
  },

  // INTERNAL PROPERTY SUPPORT

  // returns the kvo object.
  _kvo: function() {
    if (!this._kvod) this._kvod = { 
      changes: 0, changed: {}, observers: {}, dependents: {},
      chainObservers: {}, revision: 0
    } ;
    return this._kvod ;
  },

  propertyRevision: 1,
  
  // this is invoked when the properties changed.
  _notifyPropertyObservers: function(allObservers) {
    // locals
    var key ; var observers ; var keys = [] ;
    var loc; var oloc ; var value ;
    var kvo = this._kvo() ;

    SC.Observers.flush() ; // hookup as many observers as possible.

    // increment revision
    this.propertyRevision++;
    
    // find the keys to notify on.
    var keySource = (allObservers) ? kvo.observers : kvo.changed ;
    var allKeys = {} ;
    
    // This private function is used to recursively discover all 
    // dependent keys.
    var _addDependentKeys = function(key) {
      if (allKeys[key] !== undefined) return ;
      allKeys[key] = key ;

      if (allObservers) return ;
      var dependents = kvo.dependents[key] ;
      if (dependents && dependents.length > 0) {
        var loc = dependents.length ;
        while(--loc >= 0) {
          var depKey = dependents[loc] ;
          _addDependentKeys(depKey) ;
        }
      }
    } ;

    // loop throught keys to notify.  find all dependent keys as well.
    // note that this loops recursively.
    for(key in keySource) { 
      if (!keySource.hasOwnProperty(key)) continue ;
      _addDependentKeys(key) ;
    }

    // Convert the found keys into an array
    for(key in allKeys) {
      if (!allKeys.hasOwnProperty(key)) continue ;
      keys.push(key) ;
    }
    var starObservers = kvo.observers['*'] ;

    // clear out changed to avoid recursion.
    var changed = kvo.changed ; kvo.changed = {} ;

    // notify all observers.
    var target = this ; 
    loc = keys.length ;
    
    var notifiedKeys = {} ; // avoid duplicates.
    while(--loc >= 0) {
      key = keys[loc] ; observers = kvo.observers[key] ;
      if (!notifiedKeys[key]) {
        notifiedKeys[key] = key ;
        value = (allObservers || (!changed[key])) ? this.get(key) : changed[key];

        if (starObservers) {
          observers = (observers) ? observers.concat(starObservers) : starObservers ;  
        }

        if (observers) {
          oloc = observers.length ;
          var args = [target, key, value, this.propertyRevision] ;
          while(--oloc >= 0) {
            var observer = observers[oloc] ;
            SC.NotificationQueue.add(null, observer, args) ;
          } // while(oloc)
        } // if (observers) 

        // notify self.
        if (this.propertyObserver != SC.Object.prototype.propertyObserver) {
          SC.NotificationQueue.add(this, this.propertyObserver,
            [null, target, key, value, this.propertyRevision]) ;
        }
      }

    } // while(--loc)

    SC.NotificationQueue.flush() ; 
  }
    
} ;

// ........................................................................
// FUNCTION ENHANCEMENTS
//
// Enhance function.
Object.extend(Function.prototype,
/** @scope Function.prototype */ {
  
  /**
    Indicates that the function should be treated as a computed property.
    
    Computed properties are methods that you want to treat as if they were
    static properties.  When you use get() or set() on a computed property,
    the object will call the property method and return its value instead of 
    returning the method itself.  This makes it easy to create "virtual 
    properties" that are computed dynamically from other properties.
    
    Consider the following example:
    
    {{{
      contact = SC.Object.create({

        firstName: "Charles",
        lastName: "Jolley",
        
        // This is a computed property!
        fullName: function() {
          return this.getEach('firstName','lastName').compact().join(' ') ;
        }.property('firstName', 'lastName'),
        
        // this is not
        getFullName: function() {
          return this.getEach('firstName','lastName').compact().join(' ') ;
        }
      });

      contact.get('firstName') ;
      --> "Charles"
      
      contact.get('fullName') ;
      --> "Charles Jolley"
      
      contact.get('getFullName') ;
      --> function()
    }}}
    
    Note that when you get the fullName property, SproutCore will call the
    fullName() function and return its value whereas when you get() a property
    that contains a regular method (such as getFullName above), then the 
    function itself will be returned instead.
    
    h2. Using Dependent Keys

    Computed properties are often computed dynamically from other member 
    properties.  Whenever those properties change, you need to notify any
    object that is observing the computed property that the computed property
    has changed also.  We call these properties the computed property is based
    upon "dependent keys".
    
    For example, in the contact object above, the fullName property depends on
    the firstName and lastName property.  If either property value changes,
    any observer watching the fullName property will need to be notified as 
    well.
    
    You inform SproutCore of these dependent keys by passing the key names
    as parameters to the property() function.  Whenever the value of any key
    you name here changes, the computed property will be marked as changed
    also.
    
    You should always register dependent keys for computed properties to 
    ensure they update.
    
    h2. Using Computed Properties as Setters
    
    Computed properties can be used to modify the state of an object as well
    as to return a value.  Unlike many other key-value system, you use the 
    same method to both get and set values on a computed property.  To 
    write a setter, simply declare two extra parameters: key and value.
    
    Whenever your property function is called as a setter, the value 
    parameter will be set.  Whenever your property is called as a getter the
    value parameter will be undefined.
    
    For example, the following object will split any full name that you set
    into a first name and last name components and save them.
    
    {{{
      contact = SC.Object.create({
        
        fullName: function(key, value) {
          if (value !== undefined) {
            var parts = value.split(' ') ;
            this.beginPropertyChanges()
              .set('firstName', parts[0])
              .set('lastName', parts[1])
            .endPropertyChanges() ;
          }
          return this.getEach('firstName', 'lastName').compact().join(' ');
        }.property('firstName','lastName')
        
      }) ;
      
    }}}
    
    bq. *Why Use The Same Method for Getters and Setters?*  Most property-
    based frameworks expect you to write two methods for each property but
    SproutCore only uses one.  We do this because most of the time when
    you write a setter is is basically a getter plus some extra work.  There 
    is little added benefit in writing both methods when you can conditionally
    exclude part of it.  This helps to keep your code more compact and easier
    to maintain.
    
    @param dependentKeys {String...} optional set of dependent keys
    @returns {Function} the declared function instance
  */
  property: function() {
    this.dependentKeys = $A(arguments) ; 
    this.isProperty = true; return this; 
  },
  
  // Declare that a function should observe an object at the named path.  Note
  // that the path is used only to construct the observation one time.
  observes: function(propertyPaths) { 
    this.propertyPaths = $A(arguments); 
    return this;
  },
  
  typeConverter: function() {
    this.isTypeConverter = true; return this ;
  },
  
  /**
    Creates a timer that will execute the function after a specified 
    period of time.
    
    If you pass an optional set of arguments, the arguments will be passed
    to the function as well.  Otherwise the function should have the 
    signature:
    
    {{{
      function functionName(timer)
    }}}

    @param interval {Number} the time to wait, in msec
    @param target {Object} optional target object to use as this
    @returns {SC.Timer} scheduled timer
  */
  invokeLater: function(target, interval) {
    if (interval === undefined) interval = 1 ;
    var f = this;
    if (arguments.length > 2) {
      var args =$A(arguments).slice(2,arguments.length);
      args.unshift(target);
      f = f.bind.apply(f, args) ;
    }
    return SC.Timer.schedule({ target: target, action: f, interval: interval });
  }    
  
}) ;

// ........................................................................
// OBSERVER QUEUE
//
// This queue is used to hold observers when the object you tried to observe
// does not exist yet.  This queue is flushed just before any property 
// notification is sent.
SC.Observers = {
  queue: {},
  
  addObserver: function(propertyPath, func) {
    // try to get the tuple for this.
    if (typeof(propertyPath) == "string") {
      var tuple = SC.Object.tupleForPropertyPath(propertyPath) ;
    } else {
      var tuple = propertyPath; 
    }
    
    if (tuple) {
      tuple[0].addObserver(tuple[1],func) ;
    } else {
      var ary = this.queue[propertyPath] || [] ;
      ary.push(func) ;
      this.queue[propertyPath] = ary ;
    }
  },
  
  removeObserver: function(propertyPath, func) {
    var tuple = SC.Object.tupleForPropertyPath(propertyPath) ;
    if (tuple) {
      tuple[0].removeObserver(tuple[1],func) ;
    }
    
    var ary = this.queue[propertyPath] ;
    if (ary) {
      ary = ary.without(func) ;
      this.queue[propertyPath] = ary ;
    }
  },
  
  flush: function() {
    var newQueue = {} ;
    for(var path in this.queue) {
      var funcs = this.queue[path] ;
      var tuple = SC.Object.tupleForPropertyPath(path) ;
      if (tuple) {
        var loc = funcs.length ;
        while(--loc >= 0) {
          var func = funcs[loc] ;
          tuple[0].addObserver(tuple[1],func) ;
        }
      } else newQueue[path] = funcs ;
    }
    
    // set queue to remaining items
    this.queue = newQueue ; 
  }
} ;

// ........................................................................
// NOTIFCATION QUEUE
//
// Property notifications are placed into this queue first and then processed
// to keep the stack size down.
SC.NotificationQueue = {
  queue: [],
  maxFlush: 5000, // max time you can spend flushing before we reschedule.
  _flushing: false,
  add: function(target, func, args) { this.queue.push([target, func, args]);},
  flush: function(force) {
    if (this._flushing && !force) return ;
    this._flushing = true ;
    
    var start = new Date().getTime() ;
    var now = start ;
    var n = null ;
    while(((now - start) < this.maxFlush) && (n = this.queue.pop())) { 
      var t = n[0] || n[1] ;
      n[1].apply(t,n[2]) ;
      now = Date.now() ;
    }
    this._flushing = false ;
    
    if (this.queue.length > 0) { 
      SC.NotificationQueue.flush.invokeLater(SC.NotificationQueue, 1) ;
    }
  }
} ;


/* End ------------------------------------------------------- mixins/observable.js*/

/* Start ----------------------------------------------------- mixins/array.js*/

// ==========================================================================
// SproutCore -- JavaScript Application Framework
// copyright 2006-2008, Sprout Systems, Inc. and contributors.
// ==========================================================================

require('mixins/observable');

// Make Arrays observable
Object.extend(Array.prototype, SC.Observable) ;

SC.OUT_OF_RANGE_EXCEPTION = "Index out of range" ;

/**
  @namespace
  
  This module implements Observer-friendly Array-like behavior.  This mixin is 
  picked up by the Array class as well as other controllers, etc. that want to  
  appear to be arrays.
  
  You can use the methods defined in this module to access and modify array 
  contents in a KVO-friendly way.  You can also be notified whenever the 
  membership if an array changes by observing the "[]" property.

  To support SC.Array in your own class, you must override two
  primitives to use it: replace() and objectAt().  

*/
SC.Array = {

/**
  @field {Number} length
  
  Your array must support the length property.  your replace methods should
  set this property whenever it changes.
*/
  // length: 0,
  
/**
  This is one of the primitves you must implement to support SC.Array.  You 
  should replace amt objects started at idx with the objects in the passed 
  array.  You should also call this.arrayContentDidChange() ;
  
  @param {Number} idx 
    Starting index in the array to replace.  If idx >= length, then append to 
    the end of the array.

  @param {Number} amt 
    Number of elements that should be removed from the array, starting at 
    *idx*.

  @param {Array} objects 
    An array of zero or more objects that should be inserted into the array at 
    *idx* 
*/
  replace: function(idx, amt, objects) {
    throw "replace() must be implemented to support SC.Array" ;
  },

/**
  This is one of the primitives you must implement to support SC.Array.  
  Returns the object at the named index.  If your object supports retrieving 
  the value of an array item using get() (i.e. myArray.get(0)), then you do
  not need to implement this method yourself.
  
  @param {Number} idx
    The index of the item to return.  If idx exceeds the current length, 
    return null.
*/
  objectAt: function(idx)
  {
    if (idx < 0) return undefined ;
    if (idx >= this.get('length')) return undefined;
    return this.get(idx);
  },

  // this is required to support the enumerable options.  Override with your
  // own method if you prefer.
  _each: function(iterator) {
    var len ;
    for (var i = 0, len = this.get('length'); i < len; i++)
      iterator(this.objectAt(i));
  },
  
  /**  
    When you implement replace(), be sure to call this method whenever the 
    membership of your array changes.  This will make sure users are properly 
    notified.
  */
  arrayContentDidChange: function() {
    var kvo = (this._kvo) ? this._kvo().changes : '(null)';
    this.notifyPropertyChange('[]') ;
  },
  
  /**
    @field []

    This is the handler for the special array content property.  If you get
    this property, it will return this.  If you set this property it a new 
    array, it will replace the current content.
  */
  '[]': function(key, value) {
    if (value !== undefined) {
      this.replace(0, this.get('length'), value) ;
    }  
    return this ;
  }.property(),
  
/**  
  This will use the primitive replace() method to insert an object at the 
  specified index.
  
  @param {Number} idx index of insert the object at.
  @param {Object} object object to insert
*/
  insertAt: function(idx, object) {
    if (idx > this.get('length')) throw SC.OUT_OF_RANGE_EXCEPTION ;
    this.replace(idx,0,[object]) ;
    return this ;
  },
  
  /**
    Remove an object at the specified index using the replace() primitive method.
  
    @param {Number} idx index of object to remove
  */
  removeAt: function(idx) {
    if ((idx < 0) || (idx >= this.get('length'))) throw SC.OUT_OF_RANGE_EXCEPTION;
    var ret = this.objectAt(idx) ;
    this.replace(idx,1,[]);
    return ret ;
  },
  
  /**
    Search the array of this object, removing any occurrences of it.
    @param {object} obj object to remove
  */
  removeObject: function(obj) {
    var loc = this.get('length') || 0;
    while(--loc >= 0) {
      var curObject = this.objectAt(loc) ;
      if (curObject == obj) this.removeAt(loc) ;
    }
    return this ;
  },
  
  /**
    Push the object onto the end of the array.  Works just like push() but it 
    is KVO-compliant.
  */
  pushObject: function(obj) {
    this.insertAt(this.get('length'), obj) ;
    return obj ;
  },
  
  /**
    Pop object from array or nil if none are left.  Works just like pop() but 
    it is KVO-compliant.
  */
  popObject: function() {
    var len = this.get('length') ;
    if (len == 0) return null ;
    
    var ret = this.objectAt(len-1) ;
    this.removeAt(len-1) ;
    return ret ;
  },
  
  /**
    Shift an object from start of array or nil if none are left.  Works just 
    like shift() but it is KVO-compliant.
  */
  shiftObject: function() {
    if (this.get('length') == 0) return null ;
    var ret = this.objectAt(0) ;
    this.removeAt(0) ;
    return ret ;
  },
  
  /**
    Unshift an object to start of array.  Works just like unshift() but it is 
    KVO-compliant.
  */
  unshiftObject: function(obj) {
    this.insertAt(0, obj) ;
    return obj ;
  },
  
  /**  
    Compares each item in the array.  Returns true if they are equal.
  */
  isEqual: function(ary) {
    if (!ary) return false ;
    if (ary == this) return true;
    
    var loc = ary.get('length') ;
    if (loc != this.get('length')) return false ;

    while(--loc >= 0) {
      if (ary.objectAt(loc) != this.objectAt(loc)) return false ;
    }
    return true ;
  },

  /**
    Invoke the passed method and arguments on the member elements as long as 
    the value returned is the first argument.
    
    @param {Object} retValue the expected return value
    @param {String} methodName the method to call
    @returns {Object} the return value of the last time the method was 
     invoked.
  */
  invokeWhile: function(retValue, methodName) {
    var ret ;
    var args = $A(arguments) ;
    retValue = args.shift() ;
    methodName = args.shift() ; 

    try {
      this._each(function(item) {
        var func = (item) ? item[methodName] : null ;
        ret = func.apply(item, args) ;
        if (ret != retValue) throw $break ;
      }); 
    } catch (e) {
      if (e != $break) throw e ;
    }
    return ret ;
  }
      
} ;

// All arrays have the SC.Array mixin.  Do this before we add the 
// enumerable methods since Arrays are already enumerable.
Object.extend(Array.prototype, SC.Array) ; 

// Now make SC.Array enumerable and add other array method we did not want to
// override in Array itself.
Object.extend(SC.Array, Enumerable) ;
Object.extend(SC.Array, {
  /**
    Returns a new array that is a slice of the receiver.  This implementation
    uses the observable array methods to retrieve the objects for the new slice.
    
    @param beginIndex {Integer} (Optional) index to begin slicing from. Default: 0
    @param endIndex {Integer} (Optional) index to end the slice at. Default: 0
  */
  slice: function(beginIndex, endIndex) {
    var ret = []; 
    var length = this.get('length') ;
    if (beginIndex == null) beginIndex = 0 ;
    if ((endIndex == null) || (endIndex > length)) endIndex = length ;
    while(beginIndex < endIndex) ret[ret.length] = this.objectAt(beginIndex++) ;
    return ret ;
  }
  
}) ;

// ........................................................
// A few basic enhancements to the Array class.
// These methods add support for the SproutCore replace() method as well as 
// optimizing certain enumerable methods.
//
Object.extend(Array.prototype, {

  // primitive for array support.
  replace: function(idx, amt, objects) {
    if (!objects || objects.length == 0) {
      this.splice(idx, amt) ;
    } else {
      var args = [idx, amt].concat(objects) ;
      this.splice.apply(this,args) ;
    }
    this.arrayContentDidChange() ;
    return this ;
  },
  
  // These are faster implementations of the iterations defined by prototype.
  // The iterators there are cool but they consume large numbers of stack
  // frames.  These are API compatible, but much faster because they duplicate
  // code instead of calling a bunch of common methods.

  each: function(iterator) {
    try {
      for(var index=0;index<this.length;index++) {
        var item = this[index] ;
        iterator.call(item,item,index) ;
      }
    } catch (e) {
      if (e != $break) throw e ;
    }
    return this ;
  },

  invoke: function(iterator) {
    var args = $A(arguments) ;
    var methodName = args.shift() ;
    var ret = [] ;
    try {
      for(var index=0;index<this.length;index++) {
        var item = this[index] ;
        ret.push(item[methodName].apply(item,args)) ;
      } ;
    } catch (e) {
      if (e != $break) throw e ;
    }
    return ret ;
  },

  /*
    Invoke the passed method and arguments on the member elements as long as 
    the value returned is the first argument.
    
    @param {Object} retValue the expected return value
    @param {String} methodName the method to call
    @returns {Object} the return value of the last time the method was 
     invoked.
  */
  invokeWhile: function(retValue, methodName) {
    var ret ;
    var args = $A(arguments) ;
    retValue = args.shift() ;
    methodName = args.shift() ; 

    try {
      for(var index=0; index < this.length; index++) {
        var item = this[index] ;
        var func = (item) ? item[methodName] : null ;
        ret = func.apply(item, args) ;
        if (ret != retValue) return retValue ;
      }
    } catch (e) {
      if (e != $break) throw e ;
    }
    return ret ;
  },
  
  map: function(iterator) {
    var ret = [] ;
    try {
      for(var index=0;index<this.length;index++) {
        var item = this[index] ;
        ret.push((iterator || Prototype.K).call(item,item,index))  ;
      } ;
    } catch (e) {
      if (e != $break) throw e ;
    }
    return ret ;
  },

  
  // If you ask for an unknown property, then try to collect the value
  // from member items.
  unknownProperty: function(key, value) {
    if (value !== undefined) return null ;
    return this.invoke('get', key) ;
  }
    
}) ;

Array.prototype.collect = Array.prototype.map ;


// Returns the passed item as an array.  If the item is already an array,
// it is returned as is.  If it is not an array, it is placed into one.  If
// it is null, an empty array is returned.
Array.asArray = function (array) {
  if(array && 
      ((array.length === undefined) || ($type(array) == T_FUNCTION))) {
    return [array]; 
  }
  return (array) ? array : [] ;
};

// Alias for asArray
Array.from = Array.asArray ;

// Map added array methods to other enumerables
Object.extend(Enumerable, {
  
  invokeWhile: function(retValue, methodName) {
    var ret ;
    var args = $A(arguments) ;
    retValue = args.shift() ;
    methodName = args.shift() ; 

    try {
      var obj = this ;
      this._each(function(item) {
        var func = (item) ? item[methodName] : null ;
        ret = func.apply(item, args) ;
        if (ret != retValue) $break ;
      }) ;
    } catch (e) {
      if (e != $break) throw e ;
    }
    return ret ;
  }

}) ;



/* End ------------------------------------------------------- mixins/array.js*/

/* Start ----------------------------------------------------- foundation/object.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('core') ;
require('foundation/benchmark') ;
require('mixins/observable') ;
require('mixins/array') ;

SC.BENCHMARK_OBJECTS = NO;

/** 
  @class 

  Root Object for the SproutCore Framework
  
  h2. Class at a Glance
  
  Add a one or two paragraph description of the class here.
  
  This is the root object of the SproutCore framework.  All other objects
  inherit from you.  SC.Object among other things implements support for
  class inheritance, observers, and bindings.  You should use SC.Object to
  create your own subclasses as well.

  h2. Overview
  
  Some overview information about the class should go here.
  Please see online documentation for more information about this.

  h2. Subclassing Notes
  
  I should be able to put a long list of things here I suppose.
  
  - Do bullet
  - points
  - work?
  
  {{{
    // Also some sample
    code.goesHere() ;
  }}}
  
  @extends SC.Observable 
  @author Charles Jolley
  @version 1.0
  @since Version 1.0

*/
SC.Object = function(noinit) { 
	if (noinit === SC.Object._noinit_) return ;
	var ret = SC.Object._init.apply(this,$A(arguments)) ;
  return ret ;
};

Object.extend(SC.Object, 
/** @scope SC.Object */ {

	_noinit_: '__noinit__',
	
  /**
    Add properties to a object's class definition.
    
    @params {Hash} props the properties you want to add
    @returns {void}
  */
  mixin: function(props) {
    var ext = $A(arguments) ;
    for(var loc=0;loc<ext.length;loc++) {
      Object.extend(this,ext[loc]);
    }
    return this ;
  },
  
  /**
    Creates a new subclass, add to the receiver any passed properties
    or methods.
    
    @params {Hash} props the methods of properties you want to add
    @returns {Class} A new object class
  */
  extend: function(props) {   
    
    if (SC.BENCHMARK_OBJECTS) SC.Benchmark.start('SC.Object.extend') ;
     
    // build function.  copy class methods on to it.
    var ret = function(noinit) { 
      if (noinit && (typeof(noinit) == 'string') && (noinit == SC.Object._noinit_)) return ;
      var ret = SC.Object._init.apply(this,$A(arguments)); 
      return ret ;
    };
    for(var prop in this) { ret[prop] = this[prop]; }

    // extend the prototype with passed arguments.
    var base = new this(SC.Object._noinit_) ;

//    var base = SC.Object._extend({},this.prototype) ;
    var extensions = $A(arguments) ;
    for(var loc=0;loc<extensions.length;loc++) {
      base = SC.Object._extend(base, extensions[loc]);
    }
    ret.prototype = base ;
    
    // return new extension
    ret._guid = SC._nextGUID++ ; // each time we extend, get a new guid.
    ret._type = this ;
    
    if (SC.BENCHMARK_OBJECTS) SC.Benchmark.end('SC.Object.extend') ;
    
    return ret ;
  },

  /**
    Creates a new instance of the class.

    Unlike most frameworks, you do not pass paramters into the init funciton
    for an object.  Instead, you pass a hash of additonal properties you want
    to have assigned to the object when it is first created.  This is
    functionally like creating a anonymous subclass of the receiver and then
    instantiating it, but more efficient.
    
    You can use create() like you would a normal constructor in a class-based
    system, or you can use it to create highly customized singleton objects
    such as controllers or app-level objects.  This is often more efficient
    than creating subclasses and than instantiating them.
    
    @param {Hash} props optional hash of method or properties to add to the instance.
    @returns {SC.Object} new instance of the receiver class.
  */
  create: function(props) {
    var ret = new this($A(arguments),this) ;
    return ret ;
  },
    
  /**
    Takes an array of hashes and returns newly created instances.
    
    This convenience method will take an array of properties and simply
    instantiates objects from them.
    
    @params {Array} array Array of hashes with properties to assigned to each object.
    @returns {Array} array of instantiated objects.
  */
  createArray: function(array) {
    var obj = this ;
    return array.map(function(props) { return obj.create(props); }) ;
  },

  /**
    Adding this function to the end of a view declaration will define the 
    class as an outlet that can be constructed using the outlet() method 
    (instead of get()).
    
    @returns {Outlet} a specially constructed function that will be used to
     build the outlet later.
  */
  outlet: function() {
    var obj = this ;
    return function() {
      var ret = obj.create() ; ret.owner = this ; return ret ;
    } ;
  },
  
  /**
    Alway YES since this is a class.
  */
  isClass: YES,
  
  /**
    Returns the name of this class.  If the name is not known, triggers
    a search.  This can be expensive the first time it is called.
  */
  objectClassName: function() {
    if (!this._objectClassName) this._findObjectClassNames() ;
    if (this._objectClassName) return this._objectClassName ;
    var ret = this ;
    while(ret && !ret._objectClassName) ret = ret._type; 
    return (ret && ret._objectClassName) ? ret._objectClassName : 'Anonymous' ;
  },

  /** @private
    This is a way of performing brute-force introspection.  This searches 
    through all the top-level properties looking for classes.  When it finds
    one, it saves the class path name.
  */
  _findObjectClassNames: function() {
    
    if (SC._foundObjectClassNames) return ;
    SC._foundObjectClassNames = true ;
    
    var seen = [] ;
    var searchObject = function(root, object, levels) {
      levels-- ;

      // not the fastest, but safe
      if (seen.indexOf(object) >= 0) return ;
      seen.push(object) ;

      for(var key in object) {
        if (key == '__scope__') continue ;
        if (key == '_type') continue ;
        if (!key.match(/^[A-Z0-9]/)) continue ;

        var path = (root) ? [root,key].join('.') : key ;
        var value = object[key] ;


        switch($type(value)) {
        case T_CLASS:
          if (!value._objectClassName) value._objectClassName = path;
          if (levels>=0) searchObject(path, value, levels) ;
          break ;

        case T_OBJECT:
          if (levels>=0) searchObject(path, value, levels) ;
          break ;

        case T_HASH:
          if (((root != null) || (path=='SC')) && (levels>=0)) searchObject(path, value, levels) ;
          break ;

        default:
          break;
        }
      }
    } ;
    
    searchObject(null, window, 2) ;
  },
  
  toString: function() { return this.objectClassName(); },
  
  // ..........................................
  // PROPERTY SUPPORT METHODS
  //
  // get the tuple for a property path (the object and key).
  tupleForPropertyPath: function(path,root) {
    if (path.constructor == Array) return path ;
    
    // * = the rest is a chained property.
    var parts = path.split('*') ; var key = null ;
    if (parts && parts.length > 1) {
      key = parts.pop(); path = parts.join('*') ;
    }
    
    // get object path. property is last part if * was nt found.
    parts = path.split('.') ;
    if (!key) key = parts.pop() ;
    
    // convert path to object.
    var obj = this.objectForPropertyPath(parts,root) ;
    return (obj && key) ? [obj,key] : null ;
  },
  
  objectForPropertyPath: function(path,root) {
    var parts = (typeof(path) == "string") ? path.split('.') : path ;
    if (!root) root = window ;
    var key = parts.shift() ;
    while(key && root) { 
      root = (root.get) ? root.get(key) : root[key]; 
      key = parts.shift(); 
    }
    return (parts.length > 0) ? undefined : root ;  
  },
  
  
  // ..........................................
  // INTERNAL SUPPORT METHODS
  //
  _init: function(extensions,type) {
    
    var ret = this ;
    for(var loc=0;loc<extensions.length;loc++) {
      ret = SC.Object._extend(ret,extensions[loc]) ;
    }
    ret._guid = SC._nextGUID++ ;
    ret._type = type ;
    ret.init() ;
    
    return ret ;
  },

  // This will extend the base object with the properties passed in the second
  // var, maintaining super's for the functions and concatinating cprops.
  _extend: function(base,ext) { return this._extendAllProps(false, base, ext); },
  
  _extendAllProps: function(allProperties, base,ext) {
    var cprops = base._cprops ; var f = Prototype.emptyFunction ;
    
    // first, save any concat props.
    var concats = {} ;
    if (cprops) for(var cloc=0;cloc<cprops.length;cloc++) {
      var p = cprops[cloc]; var p1 = base[p]; var p2 = ext[p] ;
      p1 = (p1 && p2) ? Array.from(p1).concat(p2) : (p1 || p2) ;
      concats[p] = p1 ;
    }
    
    // deal with observers, bindings, and properties only if they are not 
    // defined already in the ext.  If they are defined in ext, they will be
    // concated using the above code.
    var bindings = (ext._bindings) ? null : (base._bindings || []).slice() ;
    var observers = (ext._observers) ? null : (base._observers || []).slice();
    var properties = (ext._properties) ? null : (base._properties || []).slice() ;
    var outlets = (ext.outlets) ? null : (base.outlets || []).slice() ;
    
    // now copy properties, add superclass to func.
    for(var key in ext) {
      // avoid copying builtin methods
      if (!allProperties && !ext.hasOwnProperty(key)) continue ; 

      // add super to funcs.  Be sure not to set the base of a func to itself
      // to avoid infinite loops.
      var value = (concats.hasOwnProperty(key) ? concats[key] : null) || ext[key] ;
      if (value && (value instanceof Function) && (!value.base)) {
        if (value != base[key]) value.base = base[key] || f;          
      }
      
      // Possibly add to a bindings.
      var keyLen = key.length ;
      if (bindings && (key.slice(keyLen-7,keyLen) == "Binding")) {
        bindings.push(key) ;
        
      // Also add observers, outlets, and properties for functions...
      } else if (value && (value instanceof Function)) {
        if (observers && value.propertyPaths) {
          observers.push(key) ;
        } else if (properties && value.dependentKeys) {
          properties.push(key) ;
        } else if (outlets && value.autoconfiguredOutlet) {
          outlets.push(key) ;
        }
      }

      // copy property
      base[key] = value ;
    }

    // copy bindings, observers, and properties if defined.
    if (bindings) base._bindings = bindings;
    if (observers) base._observers = observers ;
    if (properties) base._properties = properties ;
    if (outlets && outlets.length > 0) base.outlets = outlets ;

    //console.log('bindings: %@ -- observers: %@ -- properties: %@'.format(base._bindings,base._observers,base._properties)) ;
    
    return base ;
  },
  
  // Returns true if the receiver is a subclass of the named class.  If the
  // receiver is the class passed, this will return false.  See kindOf().
  subclassOf: function(scClass) {
    if (this == scClass) return false ;
    var t = this._type ;
    while(t) {
      if (t == scClass) return true ;
      t = t._type ;
    }
    return false ;
  },
  
  // Returns true if the receiver is the passed class or is any subclass.
  kindOf: function(scClass) { 
    if (this == scClass) return true ;
    return this.subclassOf(scClass); 
  }
  
}) ;

SC.idt = { count: 0, t: 0.0, keys: 0, observers: 0, bindings: 0, pv: 0, observers_t: 0, bindings_t: 0, pv_t: 0, conf_t: 0, b1_t: 0, b2_t: 0, b3_t: 0, e_count: 0, e_t: 0, v_count: 0, v_t: 0, vc_t: 0 ,active: false } ;

SC.report = function() {
  var c = SC.idt.count ;
  var e = SC.idt.e_count ;
  var v = SC.idt.v_count ;
  var ret = [] ;
  ret.push('CREATED: ' + c + ' (avg time: '+(Math.floor(SC.idt.t * 100 / c) / 100)+' msec)') ;
  ret.push('EXTENDED: ' + e + ' (avg time: '+(Math.floor(SC.idt.e_t * 100 / e) / 100)+' msec)') ;
  ret.push('AVG KEYS: ' + (Math.floor(SC.idt.keys * 100 / c) / 100)) ;
  ret.push('AVG OBSERVERS: ' + (Math.floor(SC.idt.observers * 100 / c) / 100)  + ' ('+ (Math.floor(SC.idt.observers_t * 100 / c) / 100) + ' msec)') ;
  ret.push('AVG BINDINGS: ' + (Math.floor(SC.idt.bindings * 100 / c) / 100)  + ' ('+ (Math.floor(SC.idt.bindings_t * 100 / c) / 100) + ' msec)') ;
  ret.push('AVG PV: ' + (Math.floor(SC.idt.pv * 100 / c) / 100)  + ' ('+ (Math.floor(SC.idt.pv_t * 100 / c) / 100) + ' msec)') ;
  ret.push('AVG CONFIGURE OUTLETS: ' + (Math.floor(SC.idt.conf_t * 100 / c) / 100)  + ' msec') ;
  ret.push('AVG B1: ' + (Math.floor(SC.idt.b1_t * 100 / c) / 100)  + ' msec') ;
  ret.push('EXT: ' + SC.idt.ext_c + ' (avg time: ' + (Math.floor(SC.idt.ext_t * 100 / SC.idt.ext_c) / 100)  + ' msec)') ;
  ret.push('VIEWS: ' + v + ' (avg time: ' + (Math.floor(SC.idt.v_t * 100 / v) / 100)  + ' msec)') ;
  ret.push('VIEW CREATE: ' + (Math.floor(SC.idt.vc_t * 100 / v) / 100)  + ' msec)') ;
  console.log(ret.join("\n")) ;
  return ret.join("\n") ;
} ;

// ..........................................
// DEFAULT OBJECT INSTANCE
// 
SC.Object.prototype = {
  
  /**
    Always YES since this is an object and not a class.
  */
  isObject: true,
  
  /**
    Returns YES if the named value is an executable function.
    
    @param methodName {String} the property name to check
    @returns {Boolean}
  */
  respondsTo: function( methodName )
  {
    return !!(methodName && this[methodName] && ($type(this[methodName]) == T_FUNCTION));
  },
  
  /**
    If the passed property is a method, then it will be executed with the
    passed arguments.  Otherwise, returns NO.
    
    @param methodName {String} the method name to try to perform.
    @param args {*arguments} arbitrary arguments to pass along to the method.
    @returns {Object} NO if method could not be performed or method result.
  */
  tryToPerform: function( methodName, args )
  {
    if ( !methodName ) return false;
    
    var args = $A(arguments);
    var name = args.shift();
    if (this.respondsTo(name))
    {
      return this[name].apply(this,args);
    }
    return false;
  },
  
  /**
   this function is called automatically when a new object instance is
   created.  You can use this to configure your child elements if you want.
   Be sure to invoke the base method.
  */
  init: function() {
    
    // This keySource is used to fix a bug in FF that will not iterate through
    // keys you add to an HTMLElement.  The key values are still accessible, 
    // they just aren't visible to the for...in loop.  viewType is the hash
    // of values that was applied to the HTMLElement, so its a 1:1 substitute.
    var keySource = this.viewType || this ; 
    var loc ; var keys ; var key ; var value ; 
    
    var r = SC.idt.active ; var idtStart ; var idtSt ;
    if (r) {
      SC.idt.count++;
      idtStart = new Date().getTime() ;
    } ;
    
    // Add Observers
    if (keys = keySource._observers) for(loc=0;loc<keys.length;loc++) {
      key = keys[loc] ; value = this[key] ;

      if (r) {
        SC.idt.keys++ ;
        SC.idt.observers++ ;
        idtSt = new Date().getTime() ;
      }
      
      var propertyPaths = null ;
      if ((value instanceof Function) && value.propertyPaths) {
        propertyPaths = value.propertyPaths ;
        value = value.bind(this) ;
      } else if (typeof(value) == "string") {
        propertyPaths = [value] ;
        value = this.propertyObserver.bind(this,key.slice(0,-8)) ;
      }
      
      // a property path string was found.  Convert this to an object/path
      // and observe.
      if (propertyPaths) for(var ploc=0;ploc<propertyPaths.length;ploc++) { 
        var propertyPath = propertyPaths[ploc] ;
        var object = null;  
        
        // most common case. refers to local property.
        if (propertyPath.indexOf('.') == -1) {
          this.addObserver(propertyPath, value) ;
          
        // deal with more exotic cases
        } else switch(propertyPath.slice(0,1)) {
          // start with a . or * means this is a chained local path.
          case '*':
          case '.':
            propertyPath = propertyPath.slice(1,propertyPath.length) ;
            this.addObserver(propertyPath, value) ;
            break ;
            
          // this is an absolute path. so hook 'er up.
          default:
            SC.Observers.addObserver(propertyPath, value) ;
        }
      }
      
      if (r) SC.idt.observers_t += (new Date().getTime()) - idtSt ;
    }

    // Add Bindings
    this.bindings = [] ;
    if (keys = keySource._bindings) for(loc=0;loc<keys.length;loc++) {
      key = keys[loc] ; value = this[key] ;

      if (r) {
        SC.idt.keys++ ;
        SC.idt.bindings++ ;
        idtSt = new Date().getTime() ;
      }
        
      // get propertyKey
      var propertyKey = key.slice(0,-7) ; // contentBinding => content
      this[key] = this.bind(propertyKey, value) ;
      if (r) SC.idt.bindings_t += (new Date().getTime()) - idtSt ;      
    }

    // Add Properties
    if (keys = keySource._properties) for(loc=0;loc<keys.length;loc++) {
      key = keys[loc] ; value = this[key] ;
      if (value && value.dependentKeys && (value.dependentKeys.length > 0)) {
        args = value.dependentKeys.slice() ;
        args.unshift(key) ;
        this.registerDependentKey.apply(this,args) ;
      }
    }

    // Call 'initMixin' methods to automatically setup modules.
    if (this.initMixin) {
      var inc = Array.from(this.initMixin) ;
      for(var idx=0; idx < inc.length; idx++) inc[idx].call(this);
    }
    
    if (r) { SC.idt.t += ((new Date().getTime()) - idtStart); }
  },
  
  /**  
    EXPERIMENTAL: You can use this to call super in any method.  
    
    This currently does not work in some versions of Safari.  Instead you
    should use:
    
    argments.callee.base.apply(this, arguments); to call super.
    
    @params args {*args} any arguments you want to pass along.
    @returns {void}
  */
  $super: function(args) {
    var caller = SC.Object.prototype.$super.caller; 
    if (!caller) throw "$super cannot determine the caller method" ;
    if (caller.base) caller.base.apply(this, arguments) ;
  },
  
  /**  
    Add passed properties to the object's class.
    
    @param props {Hash} properties to append.
    @returns {void}
  */
  mixin: function() { return SC.Object.mixin.apply(this,arguments) ; },

  /**
    Returns all the keys defined on this object, excluding any defined in
    parent classes unless you pass all.
    
    @param {Boolean} all OPTIONAL: if YES return all keys, NO return only keys belonging to object itself.  Defaults to NO.
  */
  keys: function(all) {
    var ret = []; for(var key in this) { 
      if (all || ret.hasOwnProperty(key)) ret.push(key); 
    }; return ret ;  
  },

  /**  
    returns true if the receiver is an instance of the named class.  See also
    kindOf().
    
    @param {Class} scClass the class
    @returns {Boolean}
  */
  instanceOf: function(scClass) {
    return this._type == scClass ;  
  },
  
  /**  
    Returns true if the receiver is an instance of the named class or any 
    subclass of the named class.  See also instanceOf().
    
    @param scClass {Class} the class
    @returns {Boolean}
  */
  kindOf: function(scClass) {
    var t = this._type ;
    while(t) { 
      if (t == scClass) return true ;
      t = t._type ;
    }  
    return false ;
  },

  /** @private */
  toString: function() {
    if (!this.__toString) {
      this.__toString = "%@:%@".fmt(this._type.objectClassName(), this._guid);
    } 
    return this.__toString ;
  },
  
  // ..........................................
  // OUTLETS
  // 

  /**  
    Activates any outlet connections in the the object.  This is called 
    automatically for views typically.
    
    A view may contain outlets.  Outlets are a way to find and connect to
    elements within the view.
    
    @param key {String} optional single key to awake.
    @returns {void}
  */
  awake: function(key) { 
    // if a key is passed, convert that from an outlet and awake it. otherwise
    // awake self.
    if (key !== undefined) {
      var obj = this.outlet(key) ;
      if (obj) obj.awake() ;
      return ;
    }

    if (this._awake) return ;
    this._awake = true ;
    
    // it would be cool to do this in a recursive way, but sadly we cannot
    // without a stack overflow problem. Just loop through outlets and collect
    // items to awake.
    this.bindings.invoke('relay') ; 
    
    if (this.outlets && this.outlets.length) {
      var stack = [] ;
      var working = [this, this.outlets.slice()] ;
      while(working) {
        // find the next item to work on.
        var next = working[1].pop() ;
        var obj = working[0] ;
        
        // an item was found in the array. Process it.
        if (next) {
          next = obj[next] ;
          if (next) {
            // awake these bindings.
            if (next.bindings) next.bindings.invoke('relay') ;
            
            // next has outlets itself. Start a new context and process them.
            if (next.outlets && next.outlets.length > 0) {
              stack.push(working) ;
              working = [next,next.outlets.slice()] ;
            }
          }
          
        // no more items found in the current array. pop the stack.
        } else working = stack.pop() ;
      }
    }
    
  },
  
  /**  
    Array of outlets to awake automatically.
    
    If you have outlets defined on a class, add this array with their
    property names to have them awake automatically.  This array is merged
    with the parent class outlet's array automatically when you call extend().
    
    @type {Array}
    @field
  */
  outlets: [],

  /**
    Just like get() except it will also create an outlet-packaged view if that 
    is the value of the property.
    
    This method works just like get() except if the value of the property
    is an outlet-packaged View, the view will be created first.  You can use
    this to create lazy outlets.
    
    @param {String} key The key to read as an outlet.
    @returns {Object} the value of the key, possibly an awakened outlet.
  */        
  outlet: function(key) {
    var value = this[key] ; // get the current value.

    // if its an outlet, then configure it first.
    if (value && (value instanceof Function) && value.isOutlet == true) {
      if (!this._originalOutlets) this._originalOutlets = {} ;
      this._originalOutlets[key] = value ;

      // create the outlet by calling the outlet function.  this should be the owner view.
      value = value.call(this) ;
      this.set(key, value) ;
    } else if (typeof(value) == "string") {
      if (!this._originalOutlets) this._originalOutlets = {} ;
      this._originalOutlets[key] = value ;

      value = (this.$$sel) ? this.$$sel(value) : $$sel(value) ;
      if (value) value = (value.length > 0) ? ((value.length == 1) ? value[0] : value) : null ;
      this.set(key, value) ;
    }

    return value ;
  },

  /**
    Invokes the named method after the specified period of time.
    
    This is a convenience method that will create a single run timer to
    invoke a method after a period of time.  The method should have the
    signature:
    
    {{{
      methodName: function(timer)
    }}}
    
    If you would prefer to pass your own parameters instead, you can instead
    call invokeLater() directly on the function object itself.
    
    @param interval {Number} period from current time to schedule.
    @param methodName {String} method name to perform.
    @returns {SC.Timer} scheduled timer.
  */
  invokeLater: function(methodName, interval) {
    if (interval === undefined) interval = 1 ;
    var f = methodName ;
    if (arguments.length > 2) {
      var args =$A(arguments).slice(2,arguments.length);
      args.unshift(this);
      if ($type(f) === T_STRING) f = this[methodName] ;
      f = f.bind.apply(f, args) ;
    }
    return SC.Timer.schedule({ target: this, action: f, interval: interval });
  },
  
  _cprops: ['_cprops','outlets','_bindings','_observers','_properties', 'initMixin']  

} ;

Object.extend(SC.Object.prototype, SC.Observable) ;


function logChange(target,key,value) {
  console.log("CHANGE: " + target + "["+key+"]=" + value) ;
}


// ........................................................................
// CHAIN OBSERVER
//

// ChainObservers are used to automatically monitor a property several 
// layers deep.
// org.plan.name = SC._ChainObserver.create({
//    target: this, property: 'org',
//    next: SC._ChainObserver.create({
//      property: 'plan',
//      next: SC._ChainObserver.create({
//        property: 'name', func: myFunc
//      })
//    })
//  })
//
SC._ChainObserver = SC.Object.extend({
  
  isChainObserver: true,
  
  // the object this link in the chain is observing.
  target: null,
    
  // the property on the object this link is observing
  property: null,
    
  // if not null, the next link in the chain.  The target property
  // of this next link will be set to the value of this link.
  next: null,
    
  // if not null, the function to be called when the property value changes.
  func: null,
    
  propertyObserver: function(observing,target,key,value) {
    if ((key == 'target') && (value != this._target)) {
      var func = this.boundObserver() ;
      if (this._target && this._target.removeObserver) {
        this._target.removeObserver(this.property,func);
      } 
      this._target = value ;
      if (this._target && this._target.addObserver) {
        this._target.addObserver(this.property,func) ;
      }
      
      // the target property has probably changed since the target has
      // changed. notify myself of this.
      if (!(observing == 'init')) this.targetPropertyObserver() ;
    }
  },
  
  boundObserver: function() {
    if (!this._boundObserver) {
      this._boundObserver = this.targetPropertyObserver.bind(this) ;
    }
    return this._boundObserver ;
  },
  
  // invoked when the target property changes.  act based 
  targetPropertyObserver: function() {
    
    // get the new target property value.  if the target or property is 
    // not valid, just use null.
    var value = (this.target && this.target.get && this.property) ? this.target.get(this.property) : null ;

    if (value !== this._lastTargetProperty) {
      this._lastTargetProperty = value ;
      // if we have another item in the chain, just forward the change.
      if (this.next) {
        this.next.set('target',value) ;
        
      // otherwise, invoke the function.
      } else if (this.func) this.func(this.target,this.property,value) ;
    }
    
  },
  
  // hookup observer.
  init: function() {
    arguments.callee.base.call(this) ;
    this.propertyObserver('init',this,'target',this.get('target')) ;
  }
    
}) ;

// Class Methods.
SC._ChainObserver.mixin({
  createChain: function(target,keys,func) {
    var property = keys.shift() ;
    var nextTarget = (target && property && target.get) ? target.get(property) : null ;
    var next = (keys && keys.length>0) ? this.createChain(nextTarget,keys,func) : null ;
    
    return this.create({
      target: target, property: property,
      next: next, func: ((next) ? null : func)        
    }) ;
  }
}) ;


/* End ------------------------------------------------------- foundation/object.js*/

/* Start ----------------------------------------------------- foundation/input_manager.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('core') ;
require('foundation/object') ;

/** @class

  An InputManager knows how to convert incoming keyboard events and convert
  them into actions on a responder.  The default version should provide the
  correct behavior for most people.  However, if you want some special
  behavior, you can always write your own.

  An instance of this input manager is created as a property of
  SC.Responder.  To overide the inputManager used for all responders, replace
  this property with your own instance.  To override the inputManager used 
  only for one responder, set the inputManager property on your specific 
  instance.

  Note that generally you do not want to write your own inputManager.  They
  are tricky to get right.  Instead, you should implement the various handler
  methods on the responder.
  
  @extends SC.Object
  @author Charles Jolley
*/
SC.InputManager = SC.Object.extend(
/** @scope SC.InputManager.prototype */ {
  
  /**
    The is the primary entry point for the inputManager.  If you override
    the input manager, have this method process the key event and invoke
    methods on the responder. 
  */
  interpretKeyEvents: function(event, responder)
  {
    var codes = this.codesForEvent(event) ;
    var cmd = codes[0]; var chr = codes[1];
    if (!cmd && !chr) return false ;  //nothing to do.

    // if this is a command key, try to do something about it.
    if (cmd)
    {
      var methodName = SC.MODIFIED_COMMAND_MAP[cmd] || SC.BASE_COMMAND_MAP[cmd.split('_').last()];
      if (methodName && responder.respondsTo(methodName))
      {
        // the responder has a method matching the keybinding... call it.
        return responder[methodName]( event );
      }
    } 
    // if(cmd == "space") chr = " ";
    if ( chr && responder.respondsTo('insertText'))
    {
      // if we haven't returned yet and there is plain text, then do an insert of the text.
      return responder.insertText(chr);
    }

    return false ; //nothing to do.
  },
  
  /**
    this will get a standardized command code for the event.  It returns
    null if the event is plain text, not a command code.
  */  
  codesForEvent: function(e) {
    var code = e.keyCode ;
    var ret = null ; var key = null ;
    var modifiers = '' ;
    
    // handle function keys.
    if (code) {
      ret = SC.FUNCTION_KEYS[code] ;
      if (!ret && (e.altKey || e.ctrlKey)) ret = SC.PRINTABLE_KEYS[code] ;
      if (ret) {
        if (e.altKey) modifiers += 'alt_' ;
        if (e.ctrlKey) modifiers += 'ctrl_' ;
        if (e.shiftKey) modifiers += 'shift_' ;
      }
    }

    // otherwise just go get the right key.
    if (!ret) {
      var code = e.charCode || e.keyCode ;
      key = ret = String.fromCharCode(code) ;
      var lowercase = ret.toLowerCase() ;
      if (ret != lowercase) {
        modifiers = 'shift_' ;
        ret = lowercase ;
      } else ret = null ;
    }

    if (ret) ret = modifiers + ret ;
    return [ret, key] ;
  }
    
});

SC.MODIFIED_COMMAND_MAP = {
  'ctrl_.': 'cancel',
  'shift_tab': 'insertBacktab',
  'shift_left': 'moveLeftAndModifySelection',
  'shift_right': 'moveRightAndModifySelection',
  'shift_up': 'moveUpAndModifySelection',
  'shift_down': 'moveDownAndModifySelection',
  'alt_left': 'moveLeftAndModifySelection',
  'alt_right': 'moveRightAndModifySelection',
  'alt_up': 'moveUpAndModifySelection',
  'alt_down': 'moveDownAndModifySelection',
  'ctrl_a': 'selectAll'
} ;

SC.BASE_COMMAND_MAP = {
  'escape': 'cancel',
  'backspace': 'deleteBackward',
  'delete': 'deleteForward',
  'return': 'insertNewline',
  'tab': 'insertTab',
  'left': 'moveLeft',
  'right': 'moveRight',
  'up': 'moveUp',
  'down': 'moveDown',
  'home': 'moveToBeginningOfDocument',
  'end': 'moveToEndOfDocument',
  'pagedown': 'pageDown',
  'pageup': 'pageUp'
} ;

SC.MODIFIER_KEYS = {
  16:'shift', 17:'ctrl', 18: 'alt'
};

SC.FUNCTION_KEYS = {
  8: 'backspace',  9: 'tab',  13: 'return',  19: 'pause',  27: 'escape',  
  33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 
  37: 'left', 38: 'up', 39: 'right', 40: 'down', 44: 'printscreen', 
  45: 'insert', 46: 'delete', 112: 'f1', 113: 'f2', 114: 'f3', 115: 'f4', 
  116: 'f5', 117: 'f7', 119: 'f8', 120: 'f9', 121: 'f10', 122: 'f11', 
  123: 'f12', 144: 'numlock', 145: 'scrolllock'
} ;

SC.PRINTABLE_KEYS = {
  32: ' ', 48:"0", 49:"1", 50:"2", 51:"3", 52:"4", 53:"5", 54:"6", 55:"7",
  56:"8", 57:"9", 59:";", 61:"=", 65:"a", 66:"b", 67:"c", 68:"d", 69:"e",
  70:"f", 71:"g", 72:"h", 73:"i", 74:"j", 75:"k", 76:"l", 77:"m", 78:"n",
  79:"o", 80:"p", 81:"q", 82:"r", 83:"s", 84:"t", 85:"u", 86:"v", 87:"w",
  88:"x", 89:"y", 90:"z", 107:"+", 109:"-", 110:".", 188:",", 190:".",
  191:"/", 192:"`", 219:"[", 220:"\\", 221:"]", 222:"\""
} ;

// make reverse keycode lookup for using in unit tests...
SC.KEY_CODES = {};
for (var i=0, n=256; i < n; i++)
{
  if (SC.MODIFIER_KEYS[i] !== undefined) {
    SC.KEY_CODES[ SC.MODIFIER_KEYS[i] ] = i;
  } else if (SC.FUNCTION_KEYS[i] !== undefined) {
    SC.KEY_CODES[ SC.FUNCTION_KEYS[i] ] = i;
  } else if (SC.PRINTABLE_KEYS[i] !== undefined) {
    SC.KEY_CODES[ SC.PRINTABLE_KEYS[i] ] = i;
  }
}




/* End ------------------------------------------------------- foundation/input_manager.js*/

/* Start ----------------------------------------------------- foundation/responder.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('core') ;
require('foundation/object') ;
require('foundation/input_manager');

/**
  @class 
  
  A responder is capable of responding to events such as mouse clicks and key 
  presses.  
  
  All views are a type of responder, but you can extend this class yourself 
  as well to implement your own responders if you need to.
  
  @extends SC.Object
  
*/
SC.Responder = SC.Object.extend(
  /** @scope SC.Responder.prototype */
  {
    
  // .......................................................................
  // Managing Responder States
  //
  
  /**  
    This method will be called just before you become first responder to see
    if you want to be first responder.  If you don't want to handle
    keyboard events, return false. (this is the default).  Note that this is
    a property.
  */
  acceptsFirstResponder: false,
  
  /**
    This property is the next responder that should be allowed to handle
    events if you don't want to handle it.  For views, this is automatically
    set to your parentNode.
  */
  nextResponder: null,
  
  /**  
    This property is true whenever you are the first responder.  Observable.
  */
  isFirstResponder: false,

  pane: null,
  
  /**
    Call this method to become the first responder.  You will now be the
    first to receive keyboard events, etc.  Return false if you could not
    be made first responder for some reason.
  */
  becomeFirstResponder: function()
  {
    if (!this.get('acceptsFirstResponder')) return false; // responder doesn't want it

    var pane = this.get('pane');
    if (!pane) return false;                              // there's no pane of which to become FR.. doesn't matter if it *does* want it...

    if (pane.get('firstResponder') == this) return true;  // we already got it... and setting it again will trigger the willResign callback

    pane.set('firstResponder',this);
    return true;
  },
  
  /**
    Call this method if you no longer want to be first responder.  Normally
    you don't need to do this since you will lose firstResponder status
    when the user focuses a different view.
  */
  resignFirstResponder: function()
  {
    var pane = this.get('pane');
    if (!pane) return false;
    
    if (pane.get('firstResponder') != this) return false;
    
    pane.set('firstResponder',null);
    return true;
  },
  
  noResponderFor: function() {},
  
  /**
    This method will be called just before you become the first responder
    so you can make changes.
  */
  didBecomeFirstResponder: function() {},
  
  /**
    This method will be called just before you lose first responder status.
  */
  willLoseFirstResponder: function() {},


  // .......................................................................
  // Keyboard Events
  //
  
  /**
    Set this value to an SC.InputManager instance if you want to use a 
    custom input manager for this specific instance.  If you want to override
    the input manager used for all responders, replace SC.Responder.inputManager.
  */
  inputManager: function()
  {
    return SC.Responder.inputManager;
  }.property(),

  /**
    this method is invoked on the firstResponder whenever the user presses
    a key.  You can interpret this yourself or call interpretKeyEvents()
    which will use the inputManager to handle the event.  The default
    version just passes this onto the next responder.
  */
  keyDown: function(evt) {
    var nr = this.get('nextResponder') ;
    return (nr && nr.keyDown) ? nr.keyDown(evt) : false;
  },
  
  /**  
    This method is invoked on the firstResponder whenever the user releases
    a key.  Default version just passes onto the next responder.
  */
  keyUp: function(evt) {
    var nr = this.get('nextResponder') ;
    return (nr && nr.keyUp) ? nr.keyUp(evt) : false ;
  },
  
  /**  
    This method is invoked whenever the user changes the flag keys that are
    pressed.  pressedFlags is a hash.  The default version passes on to the
    nextRespoonder.
  */
  flagsChanged: function(pressedFlags, evt) {
    //var nr = this.get('nextResponder') || SC.window.get('firstResponder') ;
    var nr = this.get('nextResponder') ;
    return (nr && nr.flagsChanges) ? nr.flagsChanges(pressedFlags, evt) : false;
  },

  /**
    This will be called if a keydown event isn't handled by the responder chain.
  */
  performKeyEquivalent: function(keystring, evt) {
    return false;
  },

  /*
    call this method with one or more event objects to process the incoming
    text.  This will use the current inputManager (see above) to process
    the event and then to make an appropriate call on the receiver.
  */
  interpretKeyEvents: function(evts) {
    var inputManager = this.get('inputManager');
    if (inputManager) return inputManager.interpretKeyEvents(evts, this) ;
    return false ;
  },

  /**
  * Attempt to perform a method. If either the method does not exist, or the method returns false, send the request to the nextResponder.
  * @param {String} method The name of the method to perform
  * @return {SC.Responder} The responder that handled the command, or false
  */
  doCommand: function(method)
  {
    var responder = this;
    var args      = $A(arguments);
    var method    = args.shift();
    var aliases   = this._commandAliases[method];
    var handled   = false;
    
    do {
      // attempt to handle the method
      if (this._responderHandledCommand(responder, method, args)) return responder;
      // perhaps under an alias?
      if (aliases)
      {
        for (var i=0, n=aliases.length; i < n; i++)
        {
          if (this._responderHandledCommand(responder, aliases[i], args)) return responder;
        }
      }
      // nope... next responder, please...
    } while (responder = responder.get('nextResponder'));
    // ran out of responders... 
    return false;
  },
  
  /** @private */
  _responderHandledCommand: function( responder, method, args )
  {
    // if the method does not explicitly return a false-y value (false, "", 0), it's considered "handled"
    // WARNING: returning null, or void is *not* == false
    return (responder.respondsTo(method) && (responder[method].apply(responder, args) != false));
  },
  /** @private */
  _commandAliases: {
    'mouseDown':   ['didMouseDown'],
    'mouseUp':     ['didMouseUp'],
    'doubleClick': ['didDoubleClick'],
    'click':       ['didClick'],
    'mouseDown':   ['didMouseDown']
  }

  // .......................................................................
  // Mouse Event Handlers
  //
  // Implement any of these if you want them to be implemented.
  
  // when the mouse is pressed.
  // mouseDown: function(evt) {},
  
  // when the mouse is releases
  // mouseUp: function(evt) {},
  
  // when the mouse is dragged
  // mouseDragged: function(evt) {},
  
  // when the mouse leaves the view responder
  // mouseOut: function(evt) {},
  
  // when the mouse enters the view.
  // mouseOver: function(evt) {},
  
  // when the mouse is moved.
  // mouseMoved: function(evt) {},
  
  // .......................................................................
  // Event Handlers
  //
  // These methods are called by the input manager in response to keyboard
  // events.  Most of these methods are defined here for you, but not actually
  // implemented in code.

  // insert the text or act on the key.
  // insertText: function(text) {},
  
  // if the user presses a key-combination event, this will be called so you
  // can run the command. 
  // performKeyEquivalent: function(charCode, evt) { return false; },
  
  // this method is called if no other view in the current view hierarchy is
  // bound to the escape or command-. key equivalent.  You can use this to 
  // cancel whatever operation is running.
  //
  // cancel: function(sender, evt) {},

  // delete the current selection or delete one element backward from the
  // current selection.
  // deleteBackward: function(sender, evt) {},
  
  // delete the current selection or delete one element forward from the
  // current selection.
  // deleteForward: function(sender, evt) {},
  
  // a field editor might respond by selecting the field before it.
  // insertBacktab: function(sender, evt) {},
  
  // insert a newline character or end editing of the receiver.
  // insertNewline: function(sender, evt) {},
  
  // insert a tab or move forward to the next field.
  // insertTab: function(sender, evt) {},
  
  // move insertion point/selection backward one. (i.e. left arrow key)
  // moveLeft: function(sender, evt) {},
  
  // move the insertion point/selection forward one (i.e. right arrow key)
  // in left-to-right text, this could be the left arrow key.
  // moveRight: function(sender, evt) {},
  
  // move the insertion point/selection up one (i.e. up arrow key)
  // moveUp: function(sender, evt) {},
  
  // move the insertion point/selection down one (i.e. down arrow key)
  // moveDown: function(sender, evt) {},
  
  // move left, extending the selection. - shift || alt
  // moveLeftAndModifySelection: function(sender, evt) {},
  
  // move right, extending the seleciton - shift || alt
  // moveRightAndModifySelection: function(sender, evt) {},
  
  // move up, extending the selection - shift || alt
  // moveUpAndModifySelection: function(sender, evt) {},
  
  // move down, extending selection - shift || alt
  // moveDownAndModifySelection: function(sender, evt) {},
  
  // move insertion point/selection to beginning of document.
  // moveToBeginningOfDocument: function(sender, evt) {},
  
  // move insertion point/selection to end of document.
  // moveToEndOfDocument: function(sender, evt) {},
  
  // page down
  // pageDown: function(sender, evt) {},
  
  // page up
  // pageUp: function(sender, evt) {},
  
  // select all
  // selectAll: function(sender, evt) {},
  
});

SC.Responder.mixin({
  inputManager: SC.InputManager.create()
}) ;


/* End ------------------------------------------------------- foundation/responder.js*/

/* Start ----------------------------------------------------- foundation/node_descriptor.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('core') ;

/**
  This object can generate HTML DOM elements from a hash-based description of 
  the nodes.  See the NodeDescriptor wiki page for complete docs.

  See https://wiki.sproutit.com/engineering/show/NodeDescriptor 

  @deprecated
*/
SC.NodeDescriptor = {
  create: function(descriptor, opts) {
    if (!opts) opts = {} ;
    // collect info from descriptor
    var tag = opts.tag || descriptor.tag || 'div' ;
    var className = opts.cssClass || descriptor.cssClass ;
    var elementId = opts.id || descriptor.id ;
    var style = opts.style || descriptor.style ;
    var innerHTML = opts.innerHTML || descriptor.innerHTML ;
    if (!innerHTML) {
      var childNodes = opts.childNodes || descriptor.childNodes ;
    } 

    // create element
    var ret = $(document.createElement(tag)) ;
    if (className) ret.className = className ;
    if (elementId) ret.id = elementId ;
    if (style) {
      for (var name in style) element.style[name.camelize()] = style[name];
    }
    
    // apply extra attributes
    for(var attr in descriptor) {
      if (this.ignoredProperties.indexOf(attr) == -1) {
        ret.setAttribute(attr,descriptor[attr]) ;
      }
    }
    
    // build child nodes, if they exist.
    if (innerHTML) {
      ret.innerHTML = innerHTML ;
    } else if (childNodes) {
      var that = this ;
      childNodes.each(function(desc) {
        ret.appendChild(that.create(desc)) ;
      }) ;
    }
    
    return ret ;
  },
  
  ignoredProperties: ['tag','cssClass','id','style','childNodes','innerHTML']
};



/* End ------------------------------------------------------- foundation/node_descriptor.js*/

/* Start ----------------------------------------------------- foundation/binding.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('foundation/object') ;

// A relay simply allows you to connect the properties of two objects. 
// Whenever the value of one property changes, the value of the other 
// property will change also.  This allows you to design your system more
// modular.
//
// You can also configure a relay to be one way so only the from -> to path
// changes are permitted. We will also allow transforms and placeholder values 
// eventually.

SC.Binding = SC.Object.extend({
  
  // ......................................
  // PROPERTIES
  // Configure these on setup.  They should be either tuples or property
  // paths.  The relay will try to observe both of them.
  from: '', to: '',
  
  // set to true if you don't want changes to -> from to relay.
  oneWay: false,
  
  // set this to some value if you want a placeholder when the source
  // value is an empty array or null (if you don't also set a nullPlaceholder)
  emptyPlaceholder: null,
  
  // set this to some value if you want a placeholder value when the source
  // value is null.  If you don't set this but you do set an emptyPlaceholder
  // then the emptyPlaceholder will be used instead.
  nullPlaceholder: null,
  
  // set this to some value if you want a placeholder when the value is 
  // an array with multiple values.  If you set this, arrays with single
  // values will be converted to indivdual objects.  If this is set ot null,
  // multiple values will be allowed to pass untouched.
  multiplePlaceholder: null,
  
  // set this to a function if you want a transform performed on an input
  // value.  This transform is performed both ways and before placeholder
  // values are applied.
  transform: null,
  
  // ......................................
  // METHODS
  
  // This will connect the relay so that properties can be forwarded.  If
  // the items are already connected, this has no effect.
  connect: function() {
    if (this._connected) return ;
    var funcs = this._boundObservers() ;
    
    SC.Observers.addObserver(this.get('from'),funcs.from) ;
    SC.Observers.addObserver(this.get('to'),funcs.to) ;
    this._connected = true ;
    return this ;
  },
  
  // this will disconnect the relay from its observing.  Call this if you 
  // want to destroy the relay. This returns null so you can use it in
  // assignment.
  disconnect: function() {  
    if (!this._connected) return ;
    var funcs = this._boundObservers() ;
    SC.Observers.removeObserver(this.get('from'),funcs.from) ;
    SC.Observers.removeObserver(this.get('to'),funcs.to) ;
    this._connected = false ;
    return this ;
  },
  
  // simulate a from -> to relay.
  relay: function() { 
    var tuple = SC.Object.tupleForPropertyPath(this.get('from')) ;
    if (tuple) tuple = this._walkTuple(tuple) ;
    if (tuple) this._fromObserver(tuple[0],tuple[1],tuple[0].get(tuple[1]));
  },
  
  // ......................................
  // INTERNAL METHODS

  init: function() {
    arguments.callee.base.call(this) ;
    this.connect() ;
  },
  
  _boundObservers: function() {
    var ret = this._boundObserverFuncs ;
    if (!ret) {
      this._boundObserverFuncs = ret = {
        from: this._fromObserver.bind(this),
        to: this._toObserver.bind(this)
      } ;
    }
    return ret ;
  },
  
  _fromObserver: function(target,key,value, propertyRevision) {
    // to avoid echos, check against last toProperty revision.
    if (propertyRevision <= this._lastFromPropertyRevision) return ;
    this._lastFromPropertyRevision = propertyRevision ;
    
    // no need to forward values if they haven't actually changed.
    if (!this._didChange(this._lastFromValue,value)) return ;
    this._lastFromValue = value ;
    
    // try to get the to object.
    var tuple = SC.Object.tupleForPropertyPath(this.get('to')) ;
    if (tuple) tuple = this._walkTuple(tuple) ;
    if (tuple) {

      // transform the value
      var transformFunc = this.transform ;
      if (transformFunc) value = transformFunc('to', key, value) ;
      this._lastToValue = value ;

      // apply placeholder settings.
      var pholder ;

      // handle empty placeholder.
      if (value && (value == []) && (pholder = this.get('emptyPlaceholder'))) {
        value = pholder ;

      // handle multiple value placeholder
      } else if (value && (value instanceof Array) && (pholder = this.get('multiplePlaceholder'))) {
        value = (value.length == 1) ? value[0] : pholder ;
      }

      // now handle null placeholder.  This is used if one of these other 
      // transforms results in a null value.
      if ((value == null) && (pholder = this.get('nullPlaceholder') || this.get('emptyPlaceholder'))) {
        value = pholder ;
      }

      tuple[0].set(tuple[1],value) ;      
      this._lastToPropertyRevision = tuple[0].propertyRevision ;
      
    }
  },

  _toObserver: function(target,key,value, propertyRevision) {
    // try to get the to object.
    if (this.get('oneWay')) return ; // block.

    // to avoid echos, check against last toProperty revision.
    if (propertyRevision <= this._lastToPropertyRevision) return ;
    this._lastToPropertyRevision = propertyRevision ;
    
    // no need to forward values if they haven't actually changed.
    if (!this._didChange(this._lastToValue,value)) return ;
    this._lastToValue = value ;
    
    var tuple = SC.Object.tupleForPropertyPath(this.get('from')) ;
    if (tuple) tuple = this._walkTuple(tuple) ;
    if (tuple) {
      // transform the value
      var transformFunc = this.get('transform') ;
      if (transformFunc) value = transformFunc('from', key, value) ;
      this._lastFromValue = value ;
      
      // send along to the 'from' source.
      tuple[0].set(tuple[1],value) ;
      var result = tuple[0].get(tuple[1]);
      if (result) this._lastFromPropertyRevision = result.propertyRevision ;
      
      // now that it has been set, the FROM object might not allow some 
      // changes.  If that is the case, then we need to set this back on the
      // sender.
      if (result != value) {
        target.set(key,result) ;
        this._lastToPropertyRevision = target.propertyRevision ;
      }
      
    } 
  },
  
  _didChange: function(lastValue, newValue) {
    if (newValue && lastValue) {
      if (typeof(newValue) == typeof(lastValue)) {
        if (lastValue == newValue) return false ;
      }
    } else if (((newValue === null) && (lastValue === null)) || ((newValue === undefined) && (lastValue === undefined))) return false ;
    return true ;
  },
  
  _lastToPropertyRevision: 0,
  _lastFromPropertyRevision: 0,
  
  _walkTuple: function(tuple) {
    var parts = tuple[1].split('.') ;
    if (parts.length > 1) {
      tuple = tuple.slice() ; // duplicate to avoid an error.
      var obj = tuple[0] ;
      tuple[1] = parts.pop() ;
      for(var loc=0;(obj && (loc<parts.length));loc++) {
        obj = obj.get(parts[loc]) ;
      }
      tuple[0] = obj ;
    }
    return (tuple[0] && tuple[1]) ? tuple : null ;
  }
  
}) ;

SC.Binding.mixin({
  // Constant values for placeholders
  MULTIPLE_PLACEHOLDER: '@@MULT@@',
  NULL_PLACEHOLDER: '@@NULL@@',
  EMPTY_PLACEHOLDER: '@@EMPTY@@'
}) ;

// This is the basic method you can use to create a builder function for
// different types of relays.  The first param is either a string or a set
// of properties.  The second param is a set of properties.  Both are opt.
SC.Binding.From = function(from,opts) {
  if (!opts) opts = {} ;
  if (($type(from) == T_STRING) || ($type(from) == T_ARRAY)) {
    opts.from = from ;
  } else Object.extend(opts,from) ;
  var ret = SC.Binding.extend(opts) ; 
  return ret ;
} ;

SC.Binding.build = function(tr) {
  return function(from) { 
    return SC.Binding.From(from,{ transform: tr }) ; 
  } ;
} ; 

SC.Binding.NoChange = SC.Binding.From;

// This binding will return errors as null.
SC.Binding.NoError = SC.Binding.build(function(dir, key, value) {
  return ($type(value) == T_ERROR) ? null : value ;  
}) ;

SC.Binding.NoError.ext = function(bindFunc) {
  return function(d,k,v) {
    return ($type(value) == T_ERROR) ? null : bindFunc(d,k,v) ;  
  } ;
} ;

// This binding only allows single, null, or error values.  If an array is 
// passed, it will be mapped like so:
//
// [] => null
// [x] => x
// [x,x,x] => MULTIPLE_PLACEHOLDER
//
SC.Binding.Single = SC.Binding.build(function(d,k,v) {
  if ($type(v) == T_ARRAY) {
    switch(v.length) {
      case 0:
        v = null ;
        break ;
      case 1:
        v = v[0] ;
        break ;
      default:
        v = SC.Binding.MULTIPLE_PLACEHOLDER ;
    }
  }
  return v;
});

// This binding works just like Single except if you pass a multiple value
// it will be converted to NULL.
SC.Binding.SingleNull = SC.Binding.build(function(d,k,v) {
  if ($type(v) == T_ARRAY) {
    switch(v.length) {
      case 0:
        v = null ;
        break ;
      case 1:
        v = v[0] ;
        break ;
      default:
        v = null ;
    }
  }
  return v;
});

// NoError versions.
SC.Binding.SingleNoError = SC.Binding.NoError.ext(SC.Binding.Single);
SC.Binding.SingleNullNoError = SC.Binding.NoError.ext(SC.Binding.SingleNull);

// This requires the value to be a multiple.  null => [], x => [x]
SC.Binding.Multiple = SC.Binding.build(function(d,k,v) {
  var t = $type(v) ;
  if (t != T_ARRAY) {
    if (t == null) {
      v = [] ;
    } else if (t != T_ERROR) {
      v = [v] ;
    }
  }
  return v ;
}) ;

SC.Binding.MultipleNoError = SC.Binding.NoError.ext(SC.Binding.Multiple);

// Converts value to a bool.  true if: not null, not empty array, not 0, '',
// etc.
SC.Binding.Bool = SC.Binding.build(function(d,k,v) {
  return ($type(v) == T_ARRAY) ? (v.length > 0) : !!v ;
}) ;

// Converts value to a bool, but its only true if not null
SC.Binding.NotNull = SC.Binding.build(function(d,k,v) { 
  return (v != null) ;
}) ;

// Converts inverse of bool.
SC.Binding.Not = SC.Binding.build(function(d,k,v) {
  return !(($type(v) == T_ARRAY) ? (v.length > 0) : !!v) ;
}) ;

// Converts value to a bool, but its only true if not null
SC.Binding.IsNull = SC.Binding.build(function(d,k,v) {
  return (v == null) ;
}) ;

// No Error versions.
SC.Binding.BoolNoError = SC.Binding.NoError.ext(SC.Binding.Bool) ;
SC.Binding.NotNullNoError = SC.Binding.NoError.ext(SC.Binding.NotNull) ;
SC.Binding.NotNoError = SC.Binding.NoError.ext(SC.Binding.Not) ;
SC.Binding.IsNullNoError = SC.Binding.NoError.ext(SC.Binding.IsNull) ;

// .........................................................
// DEPRECATED

// This relay forces all values to be multiple values
SC.Binding.Multiple = function(from) {
  return SC.Binding.From(from,{
    transform: function(dir,key,value) { 
      return (value) ? (SC.isArray(value) ? value : [value]) : value;
    }
  }) ;
} ;

// This relay forces all values to be multiple values, null values are not
// allowed.
SC.Binding.MultipleNotEmpty = function(from) {
  return SC.Binding.From(from,{
    transform: function(dir,key,value) { 
      return (value) ? (SC.isArray(value) ? value : [value]) : [];
    }
  }) ;
} ;

// This relay allows single values and no null/empty values.
SC.Binding.SingleNotEmpty = function(from) {
  return SC.Binding.From(from,{
    multiplePlaceholder: SC.Binding.MULTIPLE_PLACEHOLDER,
    emptyPlaceholder: SC.Binding.EMPTY_PLACEHOLDER,
    nullPlaceholder: SC.Binding.NULL_PLACEHOLDER
  }) ;
} ;

// This relay allows any values, but only one way flow.
SC.Binding.OneWay = function(from) {
  return SC.Binding.From(from, { oneWay: true }) ;
} ;

// This relay forces the value to be true or false.  Empty and null values
// represent false.
SC.Binding.Flag = function(from) {
  return SC.Binding.From(from, { 
    transform: function(dir,key,value) {
      return (value && (value instanceof Array)) ? (value.length == 0) : !!value ;
    }
  }) ;
} ;

SC.Binding.OneWayFlag = function(from) {
  var ret = SC.Binding.Flag(from) ;
  ret.oneWay = true ;
  return ret ;  
} ;




/* End ------------------------------------------------------- foundation/binding.js*/

/* Start ----------------------------------------------------- foundation/path_module.js*/

// ==========================================================================
// SC.PathModule
// Author: Charles Jolley
//
// When creating a web app, you often need to find elements in your page to
// configure them on load.  These elements are often marked with a class or
// id.  General purpose methods such as $$() or getElementsByClassName are
// not very good for this because they search entire trees of nodes.
//
// These methods are much faster because they make some assumptions about 
// how your elements are structured.
//
// ==========================================================================

require('core') ;
require('foundation/benchmark') ;

// Constants
SC.BENCHMARK_SELECTOR = NO ;
NO_LIMIT = 10000 ;

/**
  PathModule is high-performance API for locating DOM nodes in your HTML
  document.  It's designed to be used by searching elements from a parent
  node.  

  PathModule is patched into Prototypes Element.Methods, which means that 
  you can automatically access this API for any DOM element retrieved via
  the $() operator.  You can also mixin the PathModule api to your own 
  objects.  Be sure to also have a property called rootElement that points to 
  the root DOM element you want to search.
*/
SC.PathModule = {

    $$func: function(func, levels, max, nest) {
      return SC._PathModule.$$func(this.rootElement,func,levels,max,nest);
    },

    $$C: function(className, levels, max, nest) {
      return SC._PathModule.$$C(this.rootElement,className,levels,max,nest);
    },

    $$T: function(tagName, levels, max, nest) {
      return SC._PathModule.$$T(this.rootElement,tagName,levels,max,nest);
    },

    $$P: function(property, value, levels, max, nest) {
      return SC._PathModule.$$P(this.rootElement, property, value, levels, max, nest);
    },

    $$S: function(selector, levels, max, nest) {
      return SC._PathModule.$$S(this.rootElement, selector, levels, max, nest);
    },

    // finds the first node for which func returns true.
    $func: function(func, levels) {
      return SC._PathModule.$func(this.rootElement, func, levels);
    },

    // finds the first node with class name.  returns element, not array.
    $C: function(className, levels) {
      return SC._PathModule.$C(this.rootElement, className, levels);
    },

    // finds the first node with tag name.  returns element, not array.
    $T: function(tagName, levels) {
      return SC._PathModule.$T(this.rootElement, tagName, levels);
    },

    // find first node with an attribute matching then named value.
    $P: function(attr, value, levels) {
      return SC._PathModule.$P(this.rootElement, attr, value, levels);
    },

    // find first node matching the specified class selector.
    $S: function(selector, levels) {
      return SC._PathModule.$S(this.rootElement, selector, levels);
    },
    
    // find an element and configure it as the named view if it is not already
    // configured.
    $$view: function(selector, viewClass, levels, max, nest) {
      return SC._PathModule.$$view(this.rootElement, selector, viewClass, levels, max, nest) ;
    },

    // find the first element matching the selector and create a view if it is
    // not already configured.
    $view: function(selector, viewClass, levels) {
      return SC._PathModule.$view(this.rootElement, selector, viewClass, levels) ;
    }
    
};

// This version of PathModule can be added to Element.Methods among others.
// It is the same API except that it passes the element as the first parameter.
SC._PathModule = {

  // this is the core method that needs to be implemented for each object.
  // this method will search the receiver (up to _levels_ deep) for _max_
  // elements for which the function func() returns true. 
  //
  // el:     (req) the root element.  If you pass null, document will be used. 
  // func:   (req) the function to be used to check each element.
  // levels: (opt) the max levels deep in nested elements. defaults: NO_LIMIT
  // max:    (opt) the maximum number of elements to return. defaults: 
  //         NO_LIMIT
  // nest:   (opt) if true, then the children of matched elements will also be
  //         searched.  if your element matches will never be nested, setting
  //         this to false can be much faster.  defaults: false
  $$func: function(el, func, levels, max, nest) {
    // defaults
    levels = levels || NO_LIMIT ; max = max || NO_LIMIT; nest = nest || false;
    
    // function to search this node.
    var searchNode = function(node, depth, remain, includeThisNode) {
      var ret = [] ;
      
      // does this node match?
      // we extend any returned elements so you can chain these methods. 
      // on browsers that support HTMLElement.prototype, this will do nothing.
      var match = (includeThisNode) ? func(node) : false ;
      if (match) { ret.push(node); remain--; }

      // can we search children?
      depth-- ;
      if ((match && !nest) || (remain<=0) || (depth<=0)) return ret;

      // search children.
      node = node.firstChild ;
      while(node && (remain > 0)) {
        var found = searchNode(node,depth,remain,true) ;
        remain -= found.length ;
        ret = ret.concat(found) ;
        node = node.nextSibling ;
      }
      return ret ;
    } ;
    
    return searchNode(el || document, levels+1, max, false) ;
  },
  
  // finds all nodes with class name.
  $$C: function(el, className, levels, max, nest) {
    return SC._PathModule.$$func(el, function(node) {
      return Element.hasClassName(node, className) ; 
    }, levels, max, nest) ;
  },
  
  // find all nodes with tag name
  $$T: function(el, tagName, levels, max, nest) {
    tagName = tagName.toUpperCase() ;
    return SC._PathModule.$$func(el, function(node) { 
      return node.tagName == tagName; 
    }, levels, max, nest) ;
  },

  // find all nodes with an attribute matching then named value.
  $$P: function(el, property, value, levels, max, nest) {
    return SC._PathModule.$$func(el, function(node) { 
      var pvalue = (node.getAttribute) ? node.getAttribute(property) : node[property] ;
      return pvalue == value; 
    }, levels, max, nest) ;
  },

  // this will parse the selector and then execute the selectors to return
  // the result.  this understands a very simple form for selectors. the 
  // parts are split by spaces. and treated like so:
  // '.classname' = class name
  // 'tag' = tag
  // '#id' = an id. (this will bypass the normal selector methods) 
  // 'attr=value' = find an attr with the value.
  //
  // understands the following qualifiers:
  // .selector:3   => find .selector at most 3 levels deep
  // .selector:3:4 => find at most 4 .selectors at most 3 levels deep
  // .selector?    => find at most 1 .selector, any depth.
  // .selector?3   => find at most 3 .selectors, any depth
  // .selector:*:4 => find at more 4 .selectors at any depth.
  // 
  // You can also directly access child nodes by doing:
  // .selector[0][1][3] -> get child 3 of child 1 of child 0.
  // or:
  // [0][2][3] -> search base.
  //
  $$S: function(el,selector, levels, max, nest) {
    var parts = selector.split(' ') ;
    var ret = [el] ; var nextMax = null ; var bits ;
    var blevels; var bmax; var q; var indicies ;

    parts.each(function(part) {
      // this is included for compatibility with earlier changes I made to
      // prototype.  the preferred way to do this in the future is with with
      // the colon format.
      if (part == '?') { nextMax = 1; return ; }

      // get defaults
      blevels = levels; bmax = (nextMax) ? nextMax : max ;
      nextMax = null ;

      // if there are indexes, find those and save them.
      if (part.slice(part.length-1,part.length) == ']') {
        bits = part.split('[') ;
        part = bits.shift();
        indicies = bits.map(function(b) {return parseInt(b.slice(0,-1),0);});
      } else indicies = null ;

      // find found modifiers
      bits = part.split('?') ;
      part = bits[0] ;
      if (bits.length > 1) {
        bmax = (bits[1].length == 0) ? 1 : parseInt(bits[1],0) ;
      }

      // find level modifiers
      bits = part.split(':') ;
      part = bits[0] ;
      if (bits.length > 1) blevels = (bits[1] == '*') ? '*' : parseInt(bits[1],0) ;
      if (bits.length > 2) bmax = (bits[2] == '*') ? '*' : parseInt(bits[2],0) ;

      // convert star to NO_LIMIT
      if (blevels == '*') blevels = NO_LIMIT ;
      if (bmax == '*') bmac = NO_LIMIT ;

      // prepare the selector info.
      // q tells us how to process the first part.
      // part is the first part of the selector
      // bits are any extra css selectors.
      if (part && part.length > 0) {
        q = part.slice(0,1) ; 
        if (['.','#'].indexOf(q) >= 0) { 
          part = part.slice(1,part.length) ; 
        }
        
        bits = part.split('.') ;
        part = bits[0] ;
        bits = bits.slice(1,bits.length) ;

        // optimization for # types.
        var fret = null ;
        if (q == '#') {
          fret = $(part) ;
          fret = (fret) ? [fret] : null ;
          if (fret) ret = fret ;
        }
        
        // otherwise pass this off to $$func to actually search.
        if (fret == null) {
          fret = [] ;
          for(var i=0;i<ret.length;i++) {
            var found = SC._PathModule.$$func(ret[i],function(node) {
              var match ;
              switch(q) {
                case '.':
                  NODE = node ;
                  var elementClassName = node.className;
                  if (elementClassName && (elementClassName.length > 0)) {
                    match = (elementClassName == part ||
                      elementClassName.match( new RegExp("(^|\\s)" + part + 
                        "(\\s|$)"))) ;
                  } else match = false ;
                  break ;
                case '#':
                  match = node.id == part ;
                  break ;
                default:
                  if (node.tagName) {
                    match = (node.tagName.toLowerCase() == part.toLowerCase()) ;
                  } else { match = false; }
              }

              // if the first item matched, verify that the others match as well.
              for(var i=0; match && i < bits.length; i++) {
                if (!Element.hasClassName(node,bits[i])) match = false ; 
              }
              return match ; 
            }, blevels, bmax, nest) ;
            fret = fret.concat(found) ;
          }
          ret = fret ;
        }
      } else if ($type(ret) != T_ARRAY) ret = [ret] ;

      // now follow indicies, if there are any
      if (indicies && indicies.length > 0 && ret) {
        ret = ret.map(function(el){
          for(var iloc=0; el && (iloc < indicies.length); iloc++) {
            el = el.childNodes[indicies[iloc]] ;
          }
          return el;
        }) ;
      }
    }) ;
    return ret ;
  },

  // finds the first node for which func returns true.
  $func: function(el,func, levels) {
    var ret = SC._PathModule.$$func(el,func,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },

  // finds the first node with class name.  returns element, not array.
  $C: function(el, className, levels) {
    var ret = SC._PathModule.$$C(el, className,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },

  // finds the first node with tag name.  returns element, not array.
  $T: function(el, tagName, levels) {
    var ret = SC._PathModule.$$T(el, tagName,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },

  // find first node with an attribute matching then named value.
  $P: function(el, attr, value, levels) {
    var ret = SC._PathModule.$$P(el, attr, value,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },

  // find first node matching the specified class selector.
  $S: function(el, selector, levels) {
    var ret = SC._PathModule.$$S(el, selector,levels,1,false) ;
    return (ret.length>0) ? ret[0] : null ;
  },
  
  // find an element and configure it as the named view if it is not already
  // configured.
  $$view: function(el, selector, viewClass, levels, max, nest) {
    //if (!viewClass) viewClass = SC.View;
    var ret ;
    if (selector && (typeof(selector) != 'string')) {
      ret = [selector] ;
    } else {
      ret = SC._PathModule.$$S(el,selector,levels,max,nest) ;
    }
    if (ret) ret = ret.map(function(x) { return (viewClass) ? viewClass.viewFor(x) : SC.View.findViewForElement(x); }) ;
    return ret ;
  },

  // find the first element matching the selector and create a view if it is
  // not already configured.
  $view: function(el, selector, viewClass, levels) {
    var ret = SC._PathModule.$$view(el, selector, viewClass, levels, 1, false) ;
    return (ret.length>0) ? ret[0] : null ;
  }
  
} ;

// Install benchmark report for selector if configured.
if (SC.BENCHMARK_SELECTOR) {
  ['$$S','$$C','$$T','$S','$C','$T'].each(function(mname) {
    SC.Benchmark.install(SC._PathModule,mname) ;
  })  ;
  SC.Benchmark.install(window,'$$$') ;
}

// longer form aliases of the functions.
Object.extend(SC._PathModule, {
  $$class: SC._PathModule.$$C,
  $$tag: SC._PathModule.$$T,
  $$sel: SC._PathModule.$$S,
  $$prop: SC._PathModule.$$P,
  
  $class: SC._PathModule.$C,
  $tag: SC._PathModule.$T,
  $sel: SC._PathModule.$S,
  $prop: SC._PathModule.$P
}) ;

// longer form aliases of the functions.
Object.extend(SC.PathModule, {
  $$class: SC.PathModule.$$C,
  $$tag: SC.PathModule.$$T,
  $$sel: SC.PathModule.$$S,
  $$prop: SC.PathModule.$$P,
  
  $class: SC.PathModule.$C,
  $tag: SC.PathModule.$T,
  $sel: SC.PathModule.$S,
  $prop: SC.PathModule.$P
}) ;

// Add _PathModule to the appropriate elements.  Add to Element.Methods which
// will cause it to be copied onto elements automatically in IE when you 
// call IE.
Object.extend(Element.Methods,SC._PathModule) ;
Object.extend(Element,SC._PathModule) ;

// applies to Element.
if (typeof HTMLElement != 'undefined') {
  Object.extend(HTMLElement.prototype,SC.PathModule) ;
}

// applies to document
Object.extend(document,SC.PathModule) ;
document.rootElement = document ;

// applies to window.
Object.extend(Object.extend(window,SC.PathModule), {
  $$func: function(func, levels, max, nest) {
    return document.$$func(func,levels,max,nest) ;
  }
});

// applies to arrays. Note the override of $$func to act on the group.
Object.extend(Object.extend(Array.prototype,SC.PathModule), {
  $$func: function(func, levels, max, nest) {
    var ret = [] ;
    for(var loc=0;loc<this.length;loc++) {
      ret = ret.concat(this[loc].$$func(func,levels,max,nest)) ;
    }
    return ret ;
  }
}) ;


/* End ------------------------------------------------------- foundation/path_module.js*/

/* Start ----------------------------------------------------- mixins/delegate_support.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

/**
  @namespace
  
  Support methods for the Delegate design pattern.
  
  The Delegate design pattern makes it easy to delegate a portion of your 
  application logic to another object.  This is most often used in views to 
  delegate various application-logic decisions to controllers in order to 
  avoid having to bake application-logic directly into the view itself.
  
  The methods provided by this mixin make it easier to implement this pattern
  but they are not required to support delegates.
  
  h2. The Pattern
  
  The delegate design pattern typically means that you provide a property,
  usually ending in "delegate", that can be set to another object in the 
  system.  
  
  When events occur or logic decisions need to be made that you would prefer
  to delegate, you can call methods on the delegate if it is set.  If the 
  delegate is not set, you should provide some default functionality instead.
  
  Note that typically delegates are not observable, hence it is not necessary
  to use get() to retrieve the value of the delegate.
  
*/
SC.DelegateSupport = {  
  
  /**
    Invokes the named method on the delegate that you pass.  If no delegate
    is defined or if the delegate does not implement the method, then a 
    method of the same name on the receiver will be called instead.  
    
    You can pass any arguments you want to pass onto the delegate after the
    delegate and methodName.
    
    @param {Object} delegate a delegate object.  May be null.
    @param {String} methodName a method name
    @param {*} args (OPTIONAL) any additional arguments
    
    @returns value returned by delegate
  */
  invokeDelegateMethod: function(delegate, methodName, args) {
    args = $A(arguments); args = args.slice(2, args.length) ;
    if (!delegate || !delegate[methodName]) delegate = this ;
    return delegate[methodName].apply(delegate, args) ;
  },
  
  /**
    Gets the named property from the delegate if the delegate exists and it
    defines the property.  Otherwise, gets the property from the receiver.
    
    @param {Object} delegate the delegate or null
    @param {String} key the property to get.
  */
  getDelegateProperty: function(delegate, key) {
    return (delegate && (delegate[key] != null)) ? delegate.get(key) : this.get(key) ;
  }
  
};


/* End ------------------------------------------------------- mixins/delegate_support.js*/

/* Start ----------------------------------------------------- views/view.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('foundation/object') ;
require('foundation/responder') ;
require('foundation/node_descriptor') ;
require('foundation/binding');
require('foundation/path_module');

require('mixins/delegate_support') ;

SC.BENCHMARK_OUTLETS = NO ;
SC.BENCHMARK_CONFIGURE_OUTLETS = NO ;

/** 
  @class
  
  A view is the root class you use to manage the web page DOM in your
  application.  You can use views to render visible content on your page,
  provide animations, and to capture and respond to events.  

  You can use SC.View directly to manage DOM elements or you can extend one
  of the many subclasses provided by SproutCore.  This documentation describes
  the general concepts you need to understand when working with views, though
  most often you will want to work with one of the subclasses instead.
  
  h2. Working with DOM Elements
  
  h2. Handling Events
  
  @extends SC.Responder
  @extends SC.PathModule
  @extends SC.DelegateSupport
  @since SproutCore 1.0
*/
SC.View = SC.Responder.extend(SC.PathModule,  SC.DelegateSupport,
/** @scope SC.View.prototype */ {

  // ..........................................
  // VIEW API
  //
  // The methods in this section are used to manage actual views.  You can
  // basically interact with child elements in two ways.  One using an API
  // similar to the DOM API.  Alternatively, you can treat the view like an
  // array and use standard iterators.
  //

  /**
    Insert the view into the the receiver's childNodes array.
    
    The view will be added to the childNodes array before the beforeView.  If 
    beforeView is null, then the view will be added to the end of the array.  
    This will also add the view's rootElement DOM node to the receivers 
    containerElement DOM node as a child.

    If the specified view already belongs to another parent, it will be 
    removed from that view first.
    
    @param view {SC.View} the view to insert as a child node.
    @param beforeView {SC.View} view to insert before, or null to insert at 
     end
    @returns {SC.View} the receiver
  */
  insertBefore: function(view, beforeView) { 
    this._insertBefore(view,beforeView,true);
  },

  /** @private */
  _insertBefore: function(view, beforeView, updateDom) {
    // verify that beforeView is a child.
    if (beforeView) {
      if (beforeView.parentNode != this) throw "insertBefore() beforeView must belong to the receiver" ;
      if (beforeView == view) throw "insertBefore() views cannot be the same";
    }
    
    if (view.parentNode) view.removeFromParent() ;
    this.willAddChild(this, beforeView) ;
    view.willAddToParent(this, beforeView) ;
    
    // patch in the view.
    if (beforeView) {
      view.set('previousSibling', beforeView.previousSibling) ;
      view.set('nextSibling', beforeView) ;
      beforeView.set('previousSibling', view) ;      
    } else {
      view.set('previousSibling', this.lastChild) ;
      view.set('nextSibling', null) ;
      this.set('lastChild', view) ;
    }

    if (view.previousSibling) view.previousSibling.set('nextSibling',view);
    if (view.previousSibling == null) this.set('firstChild',view) ;
    view.set('parentNode', this) ;

    // Update DOM. -- ANIMATE
    // Note that this code is not called when outlets are first configured.
    // The assumption is that the created view already belongs to the 
    // document somwhere.
    if (updateDom) {
      var beforeElement = (beforeView) ? beforeView.rootElement : null;
      
      (this.containerElement || this.rootElement).insertBefore(view.rootElement,beforeElement);

      // regenerate the childNodes array.
      this._rebuildChildNodes();
    }

    // update cached states.
    view._updateIsVisibleInWindow() ;
    view._flushInternalCaches() ;
    view._invalidateClippingFrame() ;
    
    // call notices.
    view.didAddToParent(this, beforeView) ;
    this.didAddChild(view, beforeView) ;
    
    return this ;
  },

  /**
    Remove the view from the receiver's childNodes array.  
    
    This will also remove the view's DOM element from the recievers DOM.
    
    @param view {SC.View} the view to remove
    @returns {SC.View} the receiver
  */
  removeChild: function(view) {
    if (!view) return ;
    if (view.parentNode != this) throw "removeChild: view must belong to parent";
    
    view.willRemoveFromParent() ;
    this.willRemoveChild(view) ;
    
    // unpatch.
    if (view.previousSibling) {
      view.previousSibling.set('nextSibling', view.nextSibling);
    } else this.set('firstChild', view.nextSibling) ;
    
    if (view.nextSibling) {
      view.nextSibling.set('previousSibling', view.previousSibling) ;
    } else this.set('lastChild', view.previousSibling) ;

    // Update DOM -- ANIMATE
    var el = (this.containerElement || this.rootElement);
    if (el && (view.rootElement.parentNode == el) && (el != document)) {
      el.removeChild(view.rootElement);
    }
    
    // regenerate the childNodes array.
    this._rebuildChildNodes();

    view.set('nextSibling', null);
    view.set('previousSibling', null);
    view.set('parentNode', null) ;
    
    // update parent state.
    view._updateIsVisibleInWindow() ;
    view._flushInternalCaches();
    view._invalidateClippingFrame() ;
    
    view.didRemoveFromParent(this) ;
    this.didRemoveChild(view);
    return this;
  },

  /**
    Replace the oldView with the specified view in the receivers childNodes 
    array. This will also replace the DOM node of the oldView with the DOM 
    node of the new view in the receivers DOM.

    If the specified view already belongs to another parent, it will be 
    removed from that view first.

    @param view {SC.View} the view to insert in the DOM
    @param view {SC.View} the view to remove from the DOM.
    @returns {SC.View} the receiver
  */
  replaceChild: function(view, oldView) {
    this.insertBefore(view,oldView) ; this.removeChild(oldView) ;
    return this;
  },

  /**
    Removes the receiver from its parentNode.  If the receiver does not belong
    to a parentNode,  this method does nothing.
    
    @returns {null}
  */
  removeFromParent: function() {
    if (this.parentNode) this.parentNode.removeChild(this) ;    
    return null ;
  },

  /** 
    Works just like removeFromParent but also removes the view from internal
    caches and sets the rootElement to null so that the view and its DOM can
    be garbage collected.
    
    SproutCore includes special gaurds that ensure views and their related 
    DOM elements will be garbage collected whenever your web page unloads.
    However, if you create and destroy views frequently while your application
    is running, you should call this method when views are no longer needed
    to ensure they will be garbage collected even while your application is
    still running.
    
    @returns {null}
  */
  destroy: function() {
    this.removeFromParent() ;
    delete SC.View._view[SC.guidFor(this)];
    return null ;
  },
  
  /**
    Appends the specified view to the end of the receivers childNodes array.  
    This is equivalent to calling insertBefore(view, null);
    
    @param view {SC.View} the view to insert
    @returns {SC.View} the receiver 
  */
  appendChild: function(view) {
    this.insertBefore(view,null) ;   
    return this ; 
  },

  /**
    The array of views that are direct children of the receiver view.  The DOM 
    elements managed by the views are also directl children of the 
    containerElement for the receiver.
    
    @field
    @type Array
  */
  childNodes: [],

  /**
    The first child view in the childNodes array.  If the view does not have 
    any children, this property will be null.
     
    @field
    @type SC.View
  */
  firstChild: null,
  
  /**
    The last child view in the childNodes array.  If the view does not have any children,
    this property will be null.
     
    @field
    @type SC.View
  */
  lastChild: null,
  
  /**
    The next sibling view in the childNodes array of the receivers parentNode.  
    If the receiver is the last view in the array or if the receiver does not 
    belong to a parent view this property will be null.
     
    @field
    @type SC.View
  */
  nextSibling: null,

  /**
    The previous sibling view in the childNodes array of the receivers 
    parentNode.  If the receiver is the first view in the array or if the 
    receiver does not belong to a parent view this property will be null.
     
    @field
    @type SC.View
  */
  previousSibling: null,

  /**
    The parent view this view belongs to.  If the receiver does not belong to a parent view
    then this property is null.
     
    @field
    @type SC.View
  */
  parentNode: null,
  

  /**
    The pane this view belongs to.  The pane is the root of the responder 
    chain that this view belongs to.  Typically a view's pane will be the 
    SC.window object. However, if you have added the view to a dialog, panel, 
    popup or other pane, this property will point to that pane instead.
    
    If the view does not belong to a parentNode or if the view is not 
    onscreen, this property will be null.
    
    @field
    @type SC.View
  */
  pane: function()
  {
    var view = this;
    while(view = view.get('parentNode'))
    {
      if (view.get('isPane') ) break;
    }
    return view;
  }.property(),
  

  /**
    Removes all child views from the receiver.
    
    @returns {void}  
  */
  clear: function() {
    while(this.firstChild) this.removeChild(this.firstChild) ;
  },

  /**
    This method is called on the view just before it is added to a new parent 
    view.  
    
    You can override this method to do any setup you need on your view or to 
    reset any cached values that are impacted by being added to a view.  The 
    default implementation does nothing.
    
    @param parent {SC.View} the new parent
    @paran beforeView {SC.View} the view in the parent's childNodes array that 
      will follow this view once it is added.  If the view is being added to 
      the end of the array, this will be null.
    @returns {void}
  */
  willAddToParent: function(parent, beforeView) {},
  
  /**
    This method is called on the view just after it is added to a new parent 
    view.  
    
    You can override this method to do any setup you need on your view or to 
    reset any cached values that are impacted by being added to a view.  The 
    default implementation does nothing.
    
    @param parent {SC.View} the new parent
    @paran beforeView {SC.View} the view in the parent's childNodes array that 
      will follow this view once it is added.  If the view is being added to 
      the end of the array, this will be null.
    @returns {void}
  */
  didAddToParent: function(parent, beforeView) {},
  
  /**
    This method is called on the view just before it is removed from a parent 
    view.  
    
    You can override this method to clear out any values that depend on the 
    view belonging to the current parentNode.  The default implementation does 
    nothing.
    
    @returns {void}
  */
  willRemoveFromParent: function() {},
  
  /**
    This method is called on the view just after it is removed from a parent 
    view.  
    
    You can override this method to clear out any values that depend on the 
    view belonging to the current parentNode.  The default implementation does 
    nothing.

    @param oldParent {SC.View} the old parent view
    @returns {void}
  */
  didRemoveFromParent: function(oldParent) {},

  /**
    This method is called just before a new child view is added to the 
    receiver's childNodes array.  You can use this to prepare for any layout 
    or other cleanup you might need to do.
    
    The default implementation does nothing.
    
    @param child {SC.View} the view to be added
    @param beforeView {SC.View} and existing child view that will follow the 
      child view in the array once it is added.  If adding to the end of the 
      array, this param will be null.
    @returns {void}
  */
  willAddChild: function(child, beforeView) {},
  
  /**
    This method is called just after a new child view is added to the 
    receiver's childNodes array.  You can use this to prepare for any layout 
    or other cleanup you might need to do.
    
    The default implementation does nothing.
    
    @param child {SC.View} the view that was added
    @param beforeView {SC.View} and existing child view that will follow the 
      child view in the array once it is added.  If adding to the end of the 
      array, this param will be null.
    @returns {void}
  */
  didAddChild: function(child, beforeView) {},
  
  /**
    This method is called just before a child view is removed from the 
    receiver's childNodes array.  You can use this to prepare for any layout 
    or other cleanup you might need to do.
    
    The default implementation does nothing.
    
    @param child {SC.View} the view to be removed
    @returns {void}
  */
  willRemoveChild: function(child) {},
  
  /**
    This method is called just after a child view is removed from the 
    receiver's childNodes array.  You can use this to prepare for any layout 
    or other cleanup you might need to do.
    
    The default implementation does nothing.
    
    @param child {SC.View} the view that was removed
    @returns {void}
  */
  didRemoveChild: function(child) {},
  
  
  nextKeyView: null,
  previousKeyView: null,
  
  nextValidKeyView: function()
  {
    var view = this;
    while (view = view.get('nextKeyView'))
    {
      if (view.get('isVisible') && view.get('acceptsFirstResponder')) {
        return view;
      }
    }
    return null;
  },
  
  previousValidKeyView: function()
  {
    var view = this;
    while (view = view.get('previousKeyView'))
    {
      if (view.get('isVisible') && view.get('acceptsFirstResponder')) {
        return view;
      }
    }
    return null;
  },
  
  /** @private 
    Invoked whenever the child hierarchy changes and any internally cached 
    values might need to be recalculated.       
  */
  _flushInternalCaches: function() {
    // only flush cache for parent if this item was cached since the top level
    // cached can only be populated if this one is populated also...
    if ((this._needsClippingFrame != null) || (this._needsFrameChanges != null)) {
      this._needsClippingFrame = this._needsFrameChanges = null ;
      if (this.parentNode) this.parentNode._flushInternalCaches() ;
    }
  },
  
  // ..........................................
  // SC.Responder implementation 
  //
  
  nextResponder: function()
  {
    return this.parentNode;
  }.property('parentNode'),
  
  // recursively travels down the view hierarchy looking for a view that returns true to  performKeyEquivalent
  performKeyEquivalent: function(keystring, evt)
  {
    var child = this.get('firstChild');
    while (child)
    {
      if (child.performKeyEquivalent(keystring, evt)) return true;
      child = child.get('nextSibling');
    }
    return false;
  },
  
  // ..........................................
  // ELEMENT API
  //

  /**
    An array of currently applied classNames.
    
    @field
    @type {Array}
    @param value {Array} Array of class names to apply to the element
  */
  classNames: function(key, value) { 
    if (value !== undefined) {
        value = Array.from(value) ;
        if (this.rootElement) this.rootElement.className = value.join(' ') ;
        this._classNames = value.slice() ;
    }
    
    if (!this._classNames) {
      var classNames = this.rootElement.className;
      this._classNames = (classNames && classNames.length > 0) ? classNames.split(' ') : [] ;
    }
    return this._classNames ;
  }.property(),

  /**
    Detects the presence of the class name on the root element.
    
    @param className {String} the class name
    @returns {Boolean} YES if class name is currently applied, NO otherwise
  */
  hasClassName: function(className) {
    return (this._classNames || this.get('classNames')).indexOf(className) >= 0 ;
  },

  /**
    Adds the class name to the element.
    
    @param className {String} the class name to add.
    @returns {String} the class name
  */
  addClassName: function(className) {
    if (this.hasClassName(className)) return ; // nothing to do

    var classNames = this._classNames || this.get('classNames') ;
    classNames.push(className) ;
    this.set('classNames', classNames) ;
    return className ;
  },

  /**
    Removes the specified class name from the element.
    
    @param className {String} the class name to remove
    @returns {String} the class name
  */
  removeClassName: function(className) {
    if (!this.hasClassName(className)) return ; // nothing to do
    
    var classNames = this._classNames || this.get('classNames') ;
    classNames = this._classNames = classNames.without(className) ;
    this.set('classNames', classNames) ;
    return className ;
  },

  /**
    Adds or removes the class name according to flag.
    
    This is a simple way to add or remove a class from the root element.
    
    @param className {String} the class name
    @param flag {Boolean} YES to add class name, NO to remove it.
    @returns {String} The class Name.
  */
  setClassName: function(className, flag) {
    return (!!flag) ? this.addClassName(className) : this.removeClassName(className);
  },

  /**
    Toggles the presence of the class name.
    
    If the specified CSS class is applied, it will be removed.  If it is not
    present, it will be added.  Note that if this changes the potential 
    layout of the view, you must wrap calls to this in viewFrameDidChange()
    and viewFrameWillChange().
    
    @param className {String} the class name
    @returns {Boolean} YES if classname is now applied
  */
  toggleClassName: function(className) {
    return this.setClassName(className, !this.hasClassName(className)) ;
  },

  /**
    Retrieves the current value of the named CSS style.
    
    This method is designed to work cross platform and uses the current
    computed style, which is the combination of all applied CSS class names
    and inline styles.
    
    @param style {String} the style key.
    @returns {Object} the style value or null if not-applied/auto
  */
  getStyle: function(style) {
    var element = this.rootElement ;
    if (!this._computedStyle) {
      this._computedStyle = document.defaultView.getComputedStyle(element, null) ;
    }

    //if (style == 'float') style = 'cssFloat' ;
    style = (style === 'float') ? 'cssFloat' : style.camelize() ;
    var value = element.style[style];
    if (!value) {
      value = this._computedStyle ? this._computedStyle[style] : null ;
    }

    if (style === 'opacity') {
      value = value ? parseFloat(value) : 1.0;
    }
    if (value === 'auto') value = null ;
    
    return value ;
  },
  
  
  /**
    Sets the passed hash of CSS styles and values on the element.  You should
    pass your properties pre-camelized.
    
    @param styles {Hash} hash of keys and values
    @param camelized {Boolean} optional bool set to NO if you did not camelize.
    @returns {Boolean} YES if set succeeded.
  */
  setStyle: function(styles, camelized) {
    return Element.setStyle(this.rootElement, styles, camelized) ;
  },

/**
  Updates the HTML of an element.  
  
  This method takes care of nasties like processing scripts and inserting
  HTML into a table.  It is also somewhat slow.  If you control the HTML 
  being inserted and you are not working with table elements, you should use
  the innerHTML property instead.  If you are setting content generated by
  users, this method can insert the content safely.
  
  @param html {String} the html to insert.
*/
  update: function(html) {
    Element.update((this.containerElement || this.rootElement),html) ;
    this.propertyDidChange('innerHTML') ;
  },

  /**
    Retrieves the value for an attribute on the DOM element
    
    @param attrName {String} the attribute name
    @returns {String} attribute value
  */
  getAttribute: function(attrName) {
    return Element.readAttribute(this.rootElement,attrName) ;
  },

  /**
    Sets an attribute on the root DOM element.
    
    @param attrName {String} the attribute name
    @param value {String} the new attribute value
    @returns {String} the set attribute name
  */
  setAttribute: function(attrName, value) {
    this.rootElement.setAttribute(attrName, value) ;
  },
  
  /**
    Returns true if the named attributes is defined on the views root element.
    
    @param attrName {String} the attribute name
    @returns {Boolean} YES if attribute is present.
  */
  hasAttribute: function(attrName) {
    return Element.hasAttribute(this.rootElement, attrName) ;
  },

  // ..........................................
  // STYLE API
  //
  // These properties can be used to directly manipulate various CSS 
  // styles on the view.  These properties are required for animation
  // support.  Values are typically assumed to be in px.
  
  /**
    SC.View's unknown property is used to implement a large class of 
    properties beginning with the the world "style".  You can get or set
    any of these properties to edit individual CSS style properties.
  */
  unknownProperty: function(key, value) {
    if (key && key.match && key.match(/^style/)) {
      key = key.slice(5,key.length).replace(/^./, function(x) { 
        return x.toLowerCase(); 
      });

      var ret = null ;
      
      // handle dimensional properties
      if (key.match(/height$|width$|top$|bottom$|left$|right$/i)) {
        if (value !== undefined) {
          this.viewFrameWillChange() ;
          var props = {} ;
          props[key] = (value) ? value + 'px' : 'auto' ;
          this.setStyle(props) ;
          this.viewFrameDidChange() ;
        }
        ret = this.getStyle(key) ;
        ret = (ret === 'auto') ? null : parseInt(ret, 0) ;

      // all other properties just pass through (and do not change frame)
      } else {
        if (value !== undefined) {
          var props = {} ;
          props[key] = value ;
          this.setStyle(props) ;
        }
        ret = this.getStyle(key) ;
      }
      return ret;
      
    } else return arguments.callee.base.call(this, key, value) ;
  },
  
  // ..........................................
  // DOM API
  //
  // The methods in this section give you some low-level control over how the
  // view interacts with the DOM.  You do not normally need to work with this.
  
  /**
    This is the DOM element actually managed by this view.  This will be set
    by the view when it is created.  You should rarely need to access this 
    property directly.  When you do access it, you should only do so from 
    within methods you write on your SC.View subclasses, never from outside
    the view.
    
    Unlike most properties, you do not need to use get()/set() to access this
    property.  It is not currently safe to edit this property once the view
    has been createde.
    
    @field
    @type {Element}
  */
  rootElement: null,
  
  /**
    Normally when you add child views to your view, their DOM elements will
    be set as direct children of the root element.  However you can
    choose instead to designate an alertnative child node using this 
    property.  Set this to a selector string to begin with.  The first time
    it is accessed, the view will convert it to an actual element.  It is not
    currently safe to edit this property once the view has been created.
    
    Like rootElement, you should only access this property from within
    methods you write on an SC.View subclass, never from outside the view.
    Unlike most properties, it is not necessary to use get()/set().
    
    @field
    @type {Element}
  */
  containerElement: null,

  // ..........................................
  // VIEW LAYOUT
  //
  // The following methods can be used to implement automatic resizing.
  // The frame and bounds provides a simple way for you to compute the 
  // location and size of your views.  You can then use the automatic
  // resizing.
  
  /**
    Returns true if the view or any of its contained views implement the
    clippingFrameDidChange method.
    
    If this property returns false, then notifications about changes to the 
    clippingFrame will probably not be called on the receiver.  Normally if 
    you do not need to worry about this property since implementing the 
    clippingFrameDidChange() method will change its value and cause your 
    method to be invoked.
    
    This property is automatically updated whenever you add or remove a child 
    view.
  */
  needsClippingFrame: function() {
    if (this._needsClippingFrame == null) {
      var ret = this.clippingFrameDidChange != SC.View.prototype.clippingFrameDidChange;
      var view = this.get('firstChild') ;
      while(!ret && view) {
        ret = view.get('needsClippingFrame') ;
        view = view.get('nextSibling') ;
      }
      this._needsClippingFrame = ret ;
    }
    return this._needsClippingFrame ;
  }.property(),
  
  /**
    Returns true if the view or any of its contained views implements any 
    resize methods.
    
    If this property returns false, changes to your frame view may not be 
    relayed to child methods.  This may mean that your various frame 
    properties could become stale unless you call refreshFrames() first.

    If you want you make sure your frames are up to date, see hasManualLayout.
    
    This property is automatically updated whenever you add or remove a child 
    view.  It returns true if you implement any of the resize methods or if 
    hasManualLayout is true.
  */
  needsFrameChanges: function() {
    if (this._needsFrameChanges == null)   {
      var ret = this.get('needsClippingFrame') || this.get('hasManualLayout') ;
      var view = this.get('firstChild') ;
      while(!ret && view) {
        ret = view.get('needsFrameChanges') ;
        view = view.get('nextSibling') ;
      }
      this._needsFrameChanges = ret ;
    }
    return this._needsFrameChanges ;
  }.property(),
  

  /**
    Returns true if the receiver manages the layout for itself or its 
    children.
    
    Normally this property returns true automatically if you implement
    resizeChildrenWithOldSize() or resizeWithOldParentSize() or 
    clippingFrameDidChange().
    
    If you do not implement these methods but need to make sure your frame is 
    always up-to-date anyway, set this property to true.
  */
  hasManualLayout: function() {
    return (this.resizeChildrenWithOldSize != SC.View.prototype.resizeChildrenWithOldSize) ||
    (this.resizeWithOldParentSize != SC.View.prototype.resizeWithOldParentSize) ||
    (this.clippingFrameDidChange != SC.View.prototype.clippingFrameDidChange) ;
  }.property(),
    
  /**
    Convert a point _from_ the offset parent of the passed view to the current 
    view.

    This is a useful utility for converting points in the coordinate system of
    another view to the coordinate system of the receiver. Pass null for 
    targetView to convert a point from a window offset.  This is the inverse 
    of convertFrameToView().
    
    Note that if your view is not visible on the screen, this may not work.
    
    @param {Point} f The point or frame to convert
    @param {SC.View} targetView The view to convert from.  Pass null to convert from window coordinates.
      
    @returns {Point} The converted point or frame
  */
  convertFrameFromView: function(f, targetView) {
    
    // first, convert to root level offset.
    var thisOffset = SC.viewportOffset(this.get('offsetParent')) ;
    var thatOffset = (targetView) ? SC.viewportOffset(targetView.get('offsetParent')) : SC.ZERO_POINT;
    
    // now get adjustment.
    var adjustX = thatOffset.x - thisOffset.x ;
    var adjustY = thatOffset.y - thisOffset.y ;
    return { x: (f.x + adjustX), y: (f.y + adjustY), width: f.width, height: f.height  };
  },
  
  /**
    Convert a point _to_ the offset parent of the passed view from the current 
    view.

    This is a useful utility for converting points in the coordinate system of
    the receiver to the coordinate system of another view. Pass null for 
    targetView to convert a point to a window offset.  This is the inverse of 
    convertFrameFromView().
    
    Note that if your view is not visible on the screen, this may not work.
    
    @param {Point} f The point or frame to convert
    @param {SC.View} targetView The view to convert to.  Pass null to convert to window coordinates.
      
    @returns {Point} The converted point or frame
  */
  convertFrameToView: function(f, sourceView) {
    // first, convert to root level offset.
    var thisOffset = SC.viewportOffset(this.get('offsetParent')) ;
    var thatOffset = (sourceView) ? SC.viewportOffset(sourceView.get('offsetParent')) : SC.ZERO_POINT ;
    
    // now get adjustment.
    var adjustX = thisOffset.x - thatOffset.x ;
    var adjustY = thisOffset.y - thatOffset.y ;
    return { x: (f.x + adjustX), y: (f.y + adjustY), width: f.width, height: f.height };
  },

  /**
    This property returns a DOM ELEMENT that is the offset parent for
    this view's frame coordinates.  Depending on your CSS, this parent
    may or may not match with the parent view.
    
    @example
    offsetView = $view(this.get('offsetParent')) ;
    
    @field
    @type {Element}
  */
  offsetParent: function() {
    
    // handle simple cases.
    var el = this.rootElement ;
    if (!el || el === document.body) return el;
    if (el.offsetParent) return el.offsetParent ;

    // in some cases, we can't find the offset parent so we walk up the 
    // chain until an element is found with a position other than
    // 'static'
    //
    // Note that IE places DOM elements not in the main body inside of a 
    // document-fragment root.  We need to treat document-fragments (i.e. 
    // nodeType === 11) as null values
    var ret = null ;
    while(!ret && (el = el.parentNode) && (el.nodeType !== 11) && (el !== document.body)) {
      if (Element.getStyle(el, 'position') !== 'static') ret = el;
    }
    if (!ret && (el === document.body)) ret = el ;
    return ret ;
  }.property(),

  /**
    The inner bounds for the content shown inside of this frame.  Reflects 
    scroll position and other properties.

    The inner frame returns the actual available frame for child elements, 
    less any borders or scroll bars. 
    
    This value can change when:
    - the receiver's frame changes
    - the receiver's child views change, adding or removing scrollbars
    - You can the CSS or applied style that effects the borders or scrollbar visibility
  */
  innerFrame: function(key, value) {
    
    var f ;
    if (this._innerFrame == null) {  

      // get the base frame
      // The _collectInnerFrame function is set at the bottom of this file
      // based on the browser type.
      var el = this.rootElement ;
      f = this._collectFrame(SC.View._collectInnerFrame) ;

      // bizarely for FireFox if your offsetParent has a border, then it can 
      // impact the offset
      if (SC.Platform.Firefox) {
        var parent = el.offsetParent ;
        if (parent && (Element.getStyle(parent, 'overflow') != 'visible')) {
          var left = parseInt(Element.getStyle(parent, 'borderLeftWidth'),0) || 0 ;
          var top = parseInt(Element.getStyle(parent, 'borderTopWidth'),0) || 0 ;
          f.x += left; f.y += top ;
        }
      }
      
      // fix the x & y with the clientTop/clientLeft
      var clientLeft, clientTop ;
      if (el.clientLeft == null) {
        clientLeft = parseInt(this.getStyle('border-left-width'),0) || 0 ;
      } else clientLeft = el.clientLeft ;

      if (el.clientTop == null) {
        clientTop = parseInt(this.getStyle('border-top-width'),0) || 0 ;
      } else clientTop = el.clientTop ;

      f.x += clientLeft; f.y += clientTop;
      
      // cache this frame if using manual layout mode
      this._innerFrame = SC.cloneRect(f);
    } else f = SC.cloneRect(this._innerFrame) ;
    return f ;
  }.property('frame'),

  /** 
    The outside bounds of your view, offset top/left from its offsetParent

    The frame rect is the area actually occupied by a view including any
    borders or padding, but excluding margins.  
    
    The frame is calculated and cached the first time you get it.  Afer that, 
    the frame cache should automatically update when you make changes that 
    will effect the view frames unless you change the frame indirectly, such 
    as through changing CSS classes or by-passing the view to edit the DOM.

    If you make a change like this, be sure to wrap the code that makes this
    change with calls to viewFrameWillChange() and viewFrameDidChange() on the
    highest-level view that will be impacted by the change.  Calling this
    method will automatically update child frames as well.
    
    When you set the frame property, it will update the left, top, height,
    and width CSS attributes on the element.  Since the height and width in
    the frame rect includes borders and padding, the view will automatically
    adjust the height and width CSS it sets to account for this.  

    If you would prefer to edit the CSS attributes for the frame directly
    instead, you can do so by using the styleTop, styleLeft, styleRight, 
    styleBottom, styleWidth, and styleHeight properties on the view.  These
    properties will update the CSS attributes and call viewFrameDidChange()/
    viewFrameWillChange().

    @field
  */
  frame: function(key, value) {

    // if value was passed, set the values in the style
    // now update the frame if needed.  Only actually change the style for
    // those parts of the frame that were passed in.
    if (value !== undefined) {
      
      this.viewFrameWillChange() ;
      
      var f= value ;
      var style = {} ;
      var didResize = false ;

      // collect required info 
      // reposition X
      if (value.x !== undefined) {
        style.left = Math.floor(f.x) + 'px' ;
        style.right = 'auto';
      }

      // reposition Y
      if (value.y !== undefined) {
        style.top = Math.floor(f.y) + 'px' ;
        style.bottom = 'auto';
      }
      
      // Resize width
      if (value.width !== undefined) {
        didResize = true ;
        var padding = 0 ;
        var idx = SC.View.WIDTH_PADDING_STYLES.length;
        while(--idx >= 0) {
          padding += parseInt(this.getStyle(SC.View.WIDTH_PADDING_STYLES[idx]), 0) || 0;
        }
        style.width = (Math.floor(f.width) - padding).toString() + 'px' ;
      }
      
      // Resize Height
      if (value.height !== undefined) {
        didResize = true ;
        var padding = 0 ;
        var idx = SC.View.HEIGHT_PADDING_STYLES.length;
        while(--idx >= 0) {
          padding += parseInt(this.getStyle(SC.View.HEIGHT_PADDING_STYLES[idx]), 0) || 0;
        }
        style.height = (Math.floor(f.height) - padding).toString() + 'px' ;
      }

      // now apply style change and clear the cached frame
      this.setStyle(style) ;
      
      // notify for a resize only.
      this.viewFrameDidChange() ;
    }
    
    // build frame.  We can use a cached version but only 
    // if layoutMode == SC.MANUAL_MODE
    var f;
    if (this._frame == null) {
      var el = this.rootElement ;
      f = this._collectFrame(function() {
        return { 
          x: el.offsetLeft, 
          y: el.offsetTop, 
          width: el.offsetWidth, 
          height: el.offsetHeight 
        };
      }) ;
      
      // bizarely for FireFox if your offsetParent has a border, then it can 
      // impact the offset
      if (SC.Platform.Firefox) {
        var parent = el.offsetParent ;
        if (parent && (Element.getStyle(parent, 'overflow') != 'visible')) {
          var left = parseInt(Element.getStyle(parent, 'borderLeftWidth'),0) || 0 ;
          var top = parseInt(Element.getStyle(parent, 'borderTopWidth'),0) || 0 ;
          f.x += left; f.y += top ;
        }
      }
      
      // cache this frame if using manual layout mode
      this._frame = SC.cloneRect(f);
    } else f = SC.cloneRect(this._frame) ;

    // finally return the frame. 
    return f ;
  }.property(),
  
  /**
    The current frame size.
    
    This property will actually return the same value as the frame property, 
    however setting this property will set only the frame size and ignore any 
    origin you might pass.
    
    @field
  */
  size: function(key, value) {
    if (value !== undefined) {
      this.set('frame',{ width: value.width, height: value.height }) ;
    }
    return this.get('frame') ;
  }.property('frame'),
  
  /**
    The current frame origin.
    
    This property will actually return the same value as the frame property, 
    however setting this property will set only the frame origin and ignore 
    any size you might pass.
    
    @field
  */
  origin: function(key, value) {
    if (value !== undefined) {
      this.set('frame',{ x: value.x, y: value.y }) ;
    }
    return this.get('frame') ;
  }.property('frame'),
  
  /**
    Call this method before you make a change that will impact the frame of 
    the view such as changing the border thickness or adding/removing a CSS 
    style.
    
    Once you finish making your changes, be sure to call viewFrameDidChange() 
    as well. This will deliver any relevant resizing and other notifications.  
    It is safe to nest multiple calls to this method.
    
    This method is called automatically anytime you set the frame.
    
    @returns {void}
  */
  viewFrameWillChange: function() {
    if (this._frameChangeLevel++ <= 0) {
      this._frameChangeLevel = 1 ;

      // save frame information if view has manual layout.
      if (this.get('needsFrameChanges')) {
        this._cachedFrames = this.getEach('innerFrame', 'clippingFrame', 'frame') ;
      } else this._cachedFrames = null ;
      this.beginPropertyChanges(); // suspend change notifications
    }
  },

  /**
    Call this method just after you finish making changes that will impace the 
    frame of the view such as changing the border thickness or adding/removing 
    a CSS style.
    
    It is safe to next multiple calls to this method.   This method is called 
    automatically anytime you set the frame.
    
    @returns {void}
  */
  viewFrameDidChange: function(force) {
    
    // clear the frame caches
    this.recacheFrames() ;

    // if this is a top-level call then also deliver notifications as needed.
    if (--this._frameChangeLevel <= 0) {
      this._frameChangeLevel = 0 ;
      if (this._cachedFrames) {
        var newFrames = this.getEach('innerFrame', 'clippingFrame') ;
        
        // notify if clippingFrame has changed and clippingFrameDidChange is 
        // implemented.
        var nf = newFrames[1]; var of = this._cachedFrames[1] ;
        if (force || (nf.width != of.width) || (nf.height != of.height)) {
          this._invalidateClippingFrame() ;
        }

        // notify children if the size of the innerFrame has changed.
        var nf = newFrames[0]; var of = this._cachedFrames[0] ;
        if (force || (nf.width != of.width) || (nf.height != of.height)) {
          this.resizeChildrenWithOldSize(this._cachedFrames.last()) ;          
        }
        
        // clear parent scrollFrame if needed
        var parent = this.parentNode ;
        while (parent && parent != SC.window) {
          if (parent._scrollFrame) parent._scrollFrame = null ;
          parent = parent.parentNode ;
        }
        
        this.notifyPropertyChange('frame') ; // trigger notifications.
      }
      
      // allow notifications again
      this.endPropertyChanges() ;
    }
  },


  /**
    Clears any cached frames so the next get will recompute them.
    
    This method does not notify any observers of changes to the frames.  It 
    should only be used when you need to make sure your frame info is up to 
    date but you do not expect anything to have happened that frame observers 
    would be interested in.
  */
  recacheFrames: function() {
    this._innerFrame = this._frame = this._clippingFrame = this._scrollFrame = null ; 
  },
  
  /**
    Set to true if you expect this view to have scrollable content.

    Normally views do not monitor their onscroll event.  If you set this 
    property to true, however, the view will observe its onscroll event and 
    update its scrollFrame and clippedFrame.

    This will also register the view as a scrollable area that can be 
    auto-scrolled during a drag/drop event.
  */
  isScrollable: false,
  
  /**
    The frame used to control scrolling of content.
    
    x,y => offset from the innerFrame root.
    width,height => total size of the frame
    
    If the frame does not have scrollable content, then the size will be equal 
    to the innerFrame size.

    This frame changes when:
    - the receiver's innerFrame changes
    - the scroll location is changed programatically
    - the size of child views changes
    - the user scrolls the view
    
    @field
  */
  scrollFrame: function(key, value) {  

    // if value was passed, update the scroll x,y only.
    if (value != undefined) {
      var el = this.rootElement ;
      if (value.x != null) el.scrollLeft = 0-value.x ;
      if (value.y != null) el.scrollTop = 0-value.y ;
      this._scrollFrame = null ;
      this._invalidateClippingFrame() ;
    }
    
    // build frame.  We can use a cached version but only 
    var f;
    if (this._scrollFrame == null) {
      var el = this.rootElement ;
      f = this._collectFrame(function() {
        return { 
          x: 0 - el.scrollLeft, 
          y: 0 - el.scrollTop, 
          width: el.scrollWidth, 
          height: el.scrollHeight 
        };
      }) ;
      
      // cache this frame if using manual layout mode
      this._scrollFrame = SC.cloneRect(f);
    } else f = SC.cloneRect(this._scrollFrame) ;

    // finally return the frame. 
    return f ;
  }.property('frame'),
  
  /**
    The visible portion of the view.

    Returns the subset of the receivers frame that is actually visible on
    screen. This frame is automatically updated whenever one of the following 
    changes:
    
    - A parent view is resized
    - A parent view's scrollFrame changes.
    - The receiver is moved or resized
    - The receiver or a parent view is added to or removed from the window.
    
    @field
  */
  clippingFrame: function() {
    var f ;
    if (this._clippingFrame == null) {

      //if (this instanceof SC.SplitView) debugger ;
      
      // my clipping frame is usually my frame
      f = this.get('frame') ;
      
      // scope to my parents clipping frame.
      if (this.parentNode) {
        
        // use only the visible portion of the parent's innerFrame.
        var parent = this.parentNode ;
        var prect = SC.intersectRects(parent.get('clippingFrame'), parent.get('innerFrame'));

        // convert the local view's coordinates
        prect = this.convertFrameFromView(prect, parent) ;

        // if parent is scrollable, then adjust by scroll frame also.
        if (this.parentNode.get('isScrollable')) {
          var scrollFrame = this.get('scrollFrame') ;
          prect.x -= scrollFrame.x ; 
          prect.y -= scrollFrame.y ;
        }
        
        // blend with current frame
        f = SC.intersectRects(f, prect) ;
      } else {
        f.width = f.height = 0 ;
      }
      
      this._clippingFrame = SC.cloneRect(f) ;

    } else f = SC.cloneRect(this._clippingFrame) ;
    return f ;
  }.property('frame', 'scrollFrame'),
  
  /**
    Called whenever the receivers clippingFrame has changed.  You can override 
    this method to perform partial rendering or other clippingFrame-dependent 
    actions.
    
    The default implementation does nothing (and may not even be called do to 
    optimizations).  Note that this is the preferred way to respond to changes 
    in the clippingFrame of using an observer since this method is gauranteed 
    to happen in the correct order.  You can use observers and bindings as 
    well if you wish to handle anything that need not be handled 
    synchronously.
  */
  clippingFrameDidChange: function() {
    
  },
  
  /**
    Called whenever the view's innerFrame size changes.  You can override this 
    method to perform your own layout of your child views.  
    
    If you do not override this method, the view will assume you are using 
    CSS to layout your child views.  As an optimization the view may not 
    always call this method if it determines that you have not overridden it.
    
    This default version simply calls resizeWithOldParentSize() on all of its
    children.
    
    @param oldSize {Size} The old frame size of the view.
    @returns {void}
  */
  resizeChildrenWithOldSize: function(oldSize) {
    var child = this.get('firstChild') ;
    while(child) {
      child.resizeWithOldParentSize(oldSize) ;
      child = child.get('nextSibling') ;
    }
  },

  /**
    Called whenever the parent's innerFrame size has changed.  You can 
    override this method to change how your view responds to this change.
    
    If you do not override this method, the view will assume you are using CSS 
    to control your layout and it will simply relay the change information to 
    your child views.  As an optmization, the view may not always call this 
    method if it determines that you have not overridden it.
    
    @param oldSize {Size} The old frame size of the parent view.
  */
  resizeWithOldParentSize: function(oldSize) {
    this.viewFrameWillChange() ;
    this.viewFrameDidChange(YES) ;
  },
  
  /** @private
    Handler for the onscroll event.  Hooked in on init if isScrollable is 
    true.  Notify children that their clipping frame has changed.
  */
  _onscroll: function() {
    this._scrollFrame = null ;
    this.notifyPropertyChange('scrollFrame') ;
    SC.Benchmark.start('%@.onscroll'.fmt(this)) ;
    this._invalidateClippingFrame() ;
    SC.Benchmark.end('%@.onscroll'.fmt(this)) ;
  },

  _frameChangeLevel: 0,
  
  /** @private
    Used internally to collect client offset and location info.  If the 
    element is not in the main window or hidden, it will be added temporarily 
    and then the passed function will be called.
  */
  _collectFrame: function(func) {
    var el = this.rootElement ;
    
    // if not visible in window, move parent node into window and get 
    // dim and offset.  If the element has no parentNode, then just move
    // the element in.
    var isVisibleInWindow = this.get('isVisibleInWindow') ;
    if (!isVisibleInWindow) {
      var pn = el.parentNode || el ;
      if (pn === SC.window.rootElement) pn = el ;
      
      var pnParent = pn.parentNode ; // cache former parent node
      var pnSib = pn.nextSibling ; // cache next sibling
      SC.window.rootElement.insertBefore(pn, null) ;
    }

    // if view is not displayed, temporarily display it also
    var display = this.getStyle('display') ;
    var isHidden = !(display != 'none' && display != null) ;

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    if (isHidden) {
      var els = this.rootElement.style;
      var originalVisibility = els.visibility;
      var originalPosition = els.position;
      var originalDisplay = els.display;
      els.visibility = 'hidden';
      els.position = 'absolute';
      els.display = 'block';
    }

    var ret = func.call(this) ;
    
    if (isHidden) {
      els.display = originalDisplay;
      els.position = originalPosition;
      els.visibility = originalVisibility;
    }

    if (!isVisibleInWindow) {
      if (pnParent) {
        pnParent.insertBefore(pn, pnSib) ;
      } else {
        if(pn.parentNode)
          SC.window.rootElement.removeChild(pn) ;
      }
    }  
    
    return ret;
  },
  
  /** @private
    Called whenever some aspect of the receiver's frames have changed that 
    probably has invalidated the child views clippingFrames.  Events that cause 
    this include:
    
    - change to the innerFrame size
    - change to the scrollFrame
    - change to the clippingFrame
    
    For performance reasons, this only passes onto children if they or a decendent 
    implements the clippingFrameDidChange method.
  */
  _invalidateChildrenClippingFrames: function() {
    var view = this.get('firstChild') ;
    while(view) {
      view._invalidateClippingFrame() ;
      view = view.get('nextSibling') ;
    }
  },

  /** @private
    Called by a parentNode whenever the clippingFrame needs to be recalculated.
  */
  _invalidateClippingFrame: function() {  
    if (this.get('needsClippingFrame')) {
      this._clippingFrame = null ;
      this.clippingFrameDidChange() ; 
      this.notifyPropertyChange('clippingFrame') ;
      this._invalidateChildrenClippingFrames() ;
    }
  },
  
  // ..........................................
  // PROPERTIES
  //

  /** 
    Used to show or hide the view. 
    
    If this property is set to NO, then the DOM element will be hidden using
    display:none.  You will often want to bind this property to some setting 
    in your application to make various parts of your app visible as needed.

    If you have animation enabled, then changing this property will actually
    trigger the animation to bring the view in or out.
    
    The default binding format is SC.Binding.Bool
    
    @field
    @type {Boolean}
  */
  isVisible: true,
  
  /** @private */
  isVisibleBindingDefault: SC.Binding.Bool,
  
  /**
    (Read Only) The current display visibility of the view.
    
    Usually, this property will mirror the current state of the isVisible
    property.  However, if your view animates its visibility in and out, then
    this will not become false until the animation completes.
    
    @type {Boolean}
  */
  displayIsVisible: true,

  /**
    true when the view is actually visible in the DOM window.

    This property is set to true only when the view is (a) in the main DOM
    hierarchy and (b) all parent nodes are visible and (c) the receiver node
    is visible.
    
    @type {Boolean}
    @field
  */
  isVisibleInWindow: NO,
  
  /**
    If true, the tooltip will be localized.  Also used by some subclasses.
    
    @type {Boolean}
    @field
  */
  localize: false,

  /**
    Applied to the title attribute of the rootElement DOM if set. 
    
    If localize is true, then the toolTip will be localized first.
    
    @type {String}
    @field
  */
  toolTip: '',


  /**
    The HTML you want to use when creating a new element. 
    
    You can specify the HTML as a string of text, using the NodeDescriptor, or
    by pointing directly to an element.
    
    Note that as an optimization, SC.View will actually convert the value of 
    this property to an actual DOM structure the first time you create a view 
    and then clone the DOM structure for future views.  
    
    This means that in general you should only set the value of emptyElement 
    when you create a view subclass.  Changing this property value at other 
    times will often have no effect.
    
    @field
    @type {String}
  */
  emptyElement: "<div></div>",
  
  /**
    If true, view will display in a lightbox when you show it.
    
    @field
    @type {Boolean}
  */
  isPanel: false,

  /**
    If true, the view should be modal when shown as a panel.
  
    @field
    @type {Boolean}
  */
  isModal: true,
  
  /**
    Enable visible animation by default.
  */
  isAnimationEnabled: true,
  
  /**
    General support for animation.  Just call this method and it will build
    and play an animation starting from the current state.  The second param
    is optional.  It should either be a hash of animator options or an 
    animator object returned by a previous call to transitionTo().
  
  */
  transitionTo: function(target,animator,opts) {
    var animatorOptions = opts || {} ;

    // Create or reset the animator.
    if (animator && !animator._isAnimator) {
      var finalStyle = animator ;
      if (!this.get("isAnimationEnabled"))  {
        animatorOptions = Object.clone(animatorOptions) ;
        animatorOptions.duration = 1; 
      }
      if (animatorOptions.duration) {
        animatorOptions.duration = parseInt(animatorOptions.duration,0) ;
      }

      animator = Animator.apply(this.rootElement, finalStyle, animatorOptions);
      animator._isAnimator = true ;
    }

    // trigger animation
    if (animator) {
      animator.jumpTo(animator.state) ;
      animator.seekTo(target) ;
    }
    return animator ;    
  },

  /**
    The contents of the view as HTML. You can use this property to both 
    retrieve the content and to change it.  Use this property instead of 
    manually changing the content of your view as this property works around
    certain cross-browser bugs.
    
    @field
  */
  innerHTML: function(key, value) {
    if (value !== undefined) {
      
      // Clear the text node.
      this._textNode = null ;
      
      // Safari2 has a bad habit of sometimes not actually changing its 
      // innerHTML. This will make sure the innerHTML get's changed properly.
      if (SC.isSafari() && !SC.isSafari3()) {
        var el = (this.containerElement || this.rootElement) ; var reps = 0 ;
        var f = function() {
          el.innerHTML = '' ; el.innerHTML = value ;
          if ((reps++ < 5) && (value.length>0) && (el.innerHTML == '')) {
            f.invokeLater() ;
          }
        };
        f();
      } else (this.containerElement || this.rootElement).innerHTML = value;
    } else value = (this.containerElement || this.rootElement).innerHTML ;
    return value ;
  }.property(),

  /**
    The contents of the view as plain text.  You can use this property to
    both retrieve the content and to change it.  Use this property instead of
    the innerHTML property when you want to set plain text only as this 
    property is much faster.
  
    @field
  */
  innerText: function(key, value) {
    if (value !== undefined) {
      if (value == null) value = '' ;

      // add a textNode if necessary
      if (this._textNode == null) {
        this._textNode = document.createTextNode(value) ;
        var el = this.rootElement || this.containerElement ;
        while(el.firstChild) el.removeChild(el.firstChild) ;
        el.appendChild(this._textNode) ;
      } else this._textNode.data = value ;
    }
    
    return (this._textNode) ? this._textNode.data : this.innerHTML().unescapeHTML() ;
    
  }.property(),
  

  // ..........................................
  // SUPPORT METHODS
  //
  init: function() {
    arguments.callee.base.call(this) ;

    // configure them outlets.
    if (SC.BENCHMARK_CONFIGURE_OUTLETS) SC.Benchmark.start('SC.View.configureOutlets') ;
    this.configureOutlets() ;
    if (SC.BENCHMARK_CONFIGURE_OUTLETS) SC.Benchmark.end('SC.View.configureOutlets') ;

    var toolTip = this.get('toolTip') ;
    if(toolTip && (toolTip != '')) this._updateToolTipObserver();

    // if container element is a string, convert it to an actual DOM element.
    if (this.containerElement && ($type(this.containerElement) === T_STRING)) {
      this.containerElement = this.$sel(this.containerElement);
    }

    // register as a drop target and scrollable.
    if (this.get('isDropTarget')) SC.Drag.addDropTarget(this) ;
    if (this.get('isScrollable')) SC.Drag.addScrollableView(this) ;
    
    // add scrollable handler
    if (this.isScrollable) this.rootElement.onscroll = SC.View._onscroll ;
    
    // setup isVisibleInWindow ;
    this.isVisibleInWindow = (this.parentNode) ? this.parentNode.get('isVisibleInWindow') : NO;
  },
  
  // this method looks through your outlets array and will try to
  // reconfigure any missing ones.
  configureOutlets: function() {
    
    if (!this.outlets || (this.outlets.length <= 0)) return ; 

    // lookup outlets as selector paths or execute the function if there 
    // is one.
    this.beginPropertyChanges(); // bundle changes
    for(var oloc=0;oloc < this.outlets.length;oloc++) {
      var view = this.outlet(this.outlets[oloc]) ;
    }    
    this.endPropertyChanges() ;
  },

  // ..........................................
  // VISIBILITY METHODS
  //
  
  // Calling this method will show the view.  Don't call this method
  // directly but instead set the isVisible property to true.  You can
  // override this method to provide your own show capabilities.
  show: function() {
    Element.show(this.rootElement) ;
    this.removeClassName('hidden') ;
    this.set('displayIsVisible',true) ;
  },

  // This is the primitive method for hiding a view.  It will be called when
  // isVisible is set to false after an animation runs or immediate if no
  // animation is defined.
  hide: function() {
    Element.hide(this.rootElement) ;
    this.addClassName('hidden') ;
    this.set('displayIsVisible', false) ;
  },

  // ..........................................
  // DEPRECATED. DO NOT USE
  //

  // deprecated.  Included only for compatibility.
  animateVisible: function(key, value) {
    if (value !== undefined) return this.set('isAnimationEnabled',value) ;
    return this.get('isAnimationEnabled');
  }.property('isAnimationEnabled'),


  // ..........................................
  // PRIVATE METHODS
  //

  // this will set the rootElement, cleaning up any old element.
  _attachRootElement: function(el) {
    if (this.rootElement) this.rootElement._configured = null ;
    this.rootElement = el ; 
    el._configured = this._guid ;
  },
  
  // This method is called internally after you add or remove a child view.
  // It will rebuild the childNodes array to reflect all children.
  _rebuildChildNodes: function() {
    var ret = [] ; var view = this.firstChild;
    while(view) { ret.push(view); view = view.nextSibling; }
    this.set('childNodes', ret) ;
  },
  
  _toolTipObserver: function() {
    var toolTip = this.get('toolTip') ;
    if (this.get('localize')) toolTip = toolTip.loc() ;
    this.rootElement.title = toolTip ;
  }.observes("toolTip"),
  
  _isVisibleObserver: function() {
    var flag = this.get('isVisible') ;
    if ((this._isVisible === undefined) || (flag != this._isVisible)) {
      this._isVisible = flag ;
      if (flag) {
        this._show() ;
      } else this._hide() ; 

      // update parent state.
      this._updateIsVisibleInWindow() ;
    }
  }.observes('isVisible'),
  
  _updateIsVisibleInWindow: function(parentNodeState) {
    if (parentNodeState === undefined) {
      var parentNode = this.get('parentNode') ;
      parentNodeState = (parentNode) ? parentNode.get('isVisibleInWindow') : false ;
    }
    
    var visible = parentNodeState && this.get('isVisible') ;

    // if state changes, update and notify children.
    if (visible != this.get('isVisibleInWindow')) {
      this.set('isVisibleInWindow', visible) ;
      this.recacheFrames() ;
      var child = this.get('firstChild') ;
      while(child) {
        child._updateIsVisibleInWindow(visible) ;
        child = child.get('nextSibling') ;
      }
    }
  },
  
  // Calling this method will show the view.  Don't call this method
  // directly but instead set the isVisible property to true.  You can
  // override this method to provide your own show capabilities.
  _show: function(anchorView, triggerEvent) {
    // compatibility
    if (this.showView) return this.showView() ;

    // if this is a type of pane, call the pane manager.
    var paneType = this.get('paneType') ;
    if (this.get('isPanel')) paneType = SC.PANEL_PANE; // compatibility
    if (paneType) {
      if (anchorView === undefined) anchorView = null ;
      if (triggerEvent === undefined) triggerEvent = null ;
      SC.PaneManager.manager().showPaneView(this, paneType, anchorView, triggerEvent) ;
      this.set('displayIsVisible', true) ;
      
    // if an animation is defined and animations are configured, use that.
    // the displayIsVisible property will be set to true when the animation
    // completes.
    } else if (this.visibleAnimation && this.get('isAnimationEnabled')) {
      this._transitionVisibleTo(1.0) ;

      // at this point the animation has been reset to the beginng.  Run the 
      // core show() method immediately so the animation will be visible.
      this.show() ; 
      
    // otherwise, just change over visible settings.
    } else {
      this._visibleAnimator = null ;
      this.show() ;
    }

    return this ;
  },

  _hide: function() {
    // compatibility
    if (this.hideView) return this.hideView() ;

    // if this is a type of pane, call the pane manager.
    var isPane = (!!this.get('paneType')) || this.get('isPanel') ;
    if (isPane) {
      SC.PaneManager.manager().hidePaneView(this) ;
      this.set('displayIsVisible', false) ;
      
    // if an animation is defined and animations are configured, use that.
    // the displayIsVisible property will be set to false when the animation
    // completes.
    } else if (this.visibleAnimation && this.get('isAnimationEnabled')) {
      this._transitionVisibleTo(0.0) ;
      
    // otherwise, just change over visible settings.
    } else {
      this._visibleAnimator = null;
      this.hide();
    }
     
    return this ;
  },
  
  
  _transitionVisibleTo: function(target) {
    var a ;

    // if an animator already exists, just transition to the new state.
    if (this._visibleAnimator) {
      this.transitionTo(target,this._visibleAnimator); 
      
    // otherwise, build the animator from the options passed.  Patch in our
    // own onComplete handler.
    } else {
      var opts = this.visibleAnimation ;
      var style = [opts.hidden,opts.visible] ;
      opts.onComplete = 
        this._animateVisibleDidComplete.bind(this,opts.onComplete) ;
      this._visibleAnimator = this.transitionTo(target,style,opts);      
    }
  },

  // This is called when the animation completes.  Finish cleaning up the
  // visibility section.
  _animateVisibleDidComplete: function(chainFunc) {
    if (!this.get('isVisible')) this.hide() ;
    if (chainFunc) chainFunc(this) ;
  },
  
  _firstResponderObserver: function(target, key, value) {
    this.setClassName('focus',value) ;
  }.observes('isFirstResponder'),
  
  _dropTargetObserver: function() {
    if (this.get('isDropTarget')) {
      SC.Drag.addDropTarget(this) ;
    } else SC.Drag.removeDropTarget(this) ;
  }.observes('isDropTarget'),
  
  // .............................................
  // SPECIAL TYPES OF VIEWS
  //

  // This will show the pane as a popup or picker (depending on the paneType
  // you have set.)  This works just like setting isVisible to true, except
  // that it also passes the anchorView and triggerEvent you pass in.
  popup: function(anchorView, triggerEvent) {
    
    // this will bypass the normal observer machinery, calling the private
    // _show method ourselves.  To avoid triggering _show twice, we patch up
    // the internal _isVisible property.
    this._isVisible = true ;
    this._show(anchorView, triggerEvent) ;
    this.set('isVisible', true);
  },
  
  // This can be used to manually add observers to the rootElement for the 
  // methods in the passed map.  You generally don't want to do this since we 
  // handle event propgation through the responder chain.
  configureObserverMethods: function(methodMap) {
    for(var name in methodMap) {
      if (!methodMap.hasOwnProperty(name)) continue ;
      if (this[name]) {
        var method = this[name].bindAsEventListener(this);
        Event.observe(this.rootElement,methodMap[name],method) ;
      }
    }
  }//,
  
  // toString: function() {
  //   var el = this.rootElement ;
  //   var tagName = (!!el.tagName) ? el.tagName.toLowerCase() : 'document' ;
  // 
  //   var className = el.className ;
  //   className = (className && className.length>0) ? 'class=%@'.fmt(className) : null;
  // 
  //   var idName = el.id ;
  //   idName = (idName && idName.length>0) ? 'id=%@'.fmt(idName) : null;
  // 
  //   return "%@:%@<%@>".fmt(this._type, this._guid, [tagName,idName, className].compact().join(' ')) ;
  // }
    
}) ;
  
// Class Methods
SC.View.mixin({  

  // this is the global registry of views.  It's used to map elements back
  // to the views that own them.
  _view: {},
  
  findViewForElement: function(el) {
    var guid = el._configured ;
    return (guid) ? SC.View._view[guid] : null ;  
  },
  
  // ..........................................
  // SETUP
  //
  // This works much like create except that it works on the passed in
  // element instead of trying to find something new.  If you pass null for 
  // the first parameter,  then a new element will be created with the html
  // you set in content.
  viewFor: function(el,config) {
    if (el) el = $(el) ;

    var r = SC.idt.active ; var vStart ;
    if (r) SC.idt.v_count++;

    if (r) vStart = new Date().getTime() ;
    
    // find or build the element.
    if (!el) {    
      var emptyElement = this.prototype._cachedEmptyElement || this.prototype.emptyElement; 
      
      // if the emptyElement is a string not starting with '<', treat it like 
      // an id and find it in the doc.  If an element is found, cache it for
      // future use.
      var isString = typeof(emptyElement) == 'string' ; 
      if (isString && (emptyElement.slice(0,1) != '<')) {
        var el = $sel(emptyElement) ;
        if (el) {
          this.prototype.emptyElement = emptyElement = el ;
          isString = false ;
        }
      }

      // if still a string, then use it to create HTML.  Save the generated 
      // element so that we can avoid doing this over again.
      if (isString) {
        SC._ViewCreator.innerHTML = emptyElement ;
        el = $(SC._ViewCreator.firstChild) ;
        SC.NodeCache.appendChild(el) ;
        this.prototype._cachedEmptyElement = el.cloneNode(true) ;
        
      } else if (typeof(emptyElement) == "object") {
        if (emptyElement.tagName) {
          el = emptyElement.cloneNode(true) ;
        } else el = SC.NodeDescriptor.create(emptyElement) ;
      }
    }
    if (r) SC.idt.vc_t += (new Date().getTime()) - vStart ;

    // configure only once.
    if (el && el._configured) return SC.View.findViewForElement(el); 
    
    // Now that we have found an element, instantiate the view.
    var args = $A(arguments) ; args[0] = { rootElement: el } ;
    if (r) vStart = new Date().getTime();
    var ret = new this(args,this) ; // create instance.
    if (r) SC.idt.v_t += (new Date().getTime()) - vStart;
    el._configured = ret._guid ;

    // return the view.
    SC.View._view[ret._guid] = ret ;
    return ret ;    
  },
  
  // create in the view work is like viewFor but with 'null' for el
  create: function(configs) {
    var args = $A(arguments) ;
    args.unshift(null) ;
    return this.viewFor.apply(this,args) ;  
  },
  
  // extend works just like a normal extend except that we need to delete the cached empty
  // element.
  extend: function(configs) {
    var ret = SC.Object.extend.apply(this, arguments) ;  
    ret.prototype._cachedEmptyElement = null ;
    return ret ;
  },

  /** 
    Defines a view as an outlet.  This will return an function that
    can be executed at a later time to actually create itself as an outlet.
  */
  outletFor: function(path) {
    var viewClass = this ; // save the view class
    var func = function() {
      if (SC.BENCHMARK_OUTLETS) SC.Benchmark.start("OUTLET(%@)".format(path)) ;

      // if no path was passed, then create the view from scratch
      if (path == null) {
        var ret = viewClass.viewFor(null) ;
        
      // otherwise, try to find the HTML element identified by the path.
      // If the element cannot be found in the caller (the owner view), then
      // search the entire document.
      } else {
        var ret = (this.$$sel) ? this.$$sel(path) : $$sel(path) ;

        // if some HTML has been found, then loop through and create views for each 
        // one.  Be sure to setup the proper parent view.
        if (ret) {
          var owner = this ; var views = [] ;
          for(var loc=0;loc<ret.length;loc++) {
            
            // create the new view instance
            var view = viewClass.viewFor(ret[loc], { owner: owner }) ;
            
            // if successful, then we need to determine the new parentNode.
            // then walk up the DOM tree to find the first parent element 
            // managed by a view (including this).
            //
            // If a matching view is not found, but the view IS in a DOM
            // somewhere then make the view a child of either SC.page or 
            // SC.window.
            //  
            // Add the view to the list of child views also.
            //
            if (view && view.rootElement && view.rootElement.parentNode) {
              var node = view.rootElement.parentNode;
              var parentView = null ;

              // go up the chain.  stop when we find a parent view, or the rootElement
              // for SC.page.
              while(node && !parentView) {
                switch(node) {
                case this.rootElement:
                  parentView = this;
                  break ;
                case SC.page.rootElement:
                  parentView = SC.page ;
                  break;
                case SC.window.rootElement:
                  parentView = SC.window ;
                default:
                  node = node.parentNode ; 
                }
              }

              // if a parentView was found, then add to parentView.
              if (parentView) {
                parentView._insertBefore(view,null,false) ;
                parentView._rebuildChildNodes() ; // this is not done with _insertBefore.
                view._updateIsVisibleInWindow();
              }
              
            // view is not in a DOM. nothing to do.
            }

            // add to return array
            views[views.length] = view ;
            
          }
          ret = views ;
          ret = (ret.length == 0) ? null : ((ret.length == 1) ? ret[0] : ret);
        }
        
      }
      
      if (SC.BENCHMARK_OUTLETS) SC.Benchmark.end("OUTLET(%@)".format(path)) ;
      return ret ;
    } ;
    func.isOutlet = true ;
    return func ;
  },
  
  automaticOutletFor: function() {
    var ret = this.outletFor.apply(this, arguments) ;
    ret.autoconfiguredOutlet = YES ;
    return ret ;
  }

}) ;

// IE Specfic Overrides
if (SC.Platform.IE) {
  SC.View.prototype.getStyle = function(style) {
    var element = this.rootElement ;

    // collect value
    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
    var value = element.style[style];
    if (!value && element.currentStyle) value = element.currentStyle[style];

    // handle opacity
    if (style === 'opacity') {
      if (value = (this.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) {
        if (value[1]) value = parseFloat(value[1]) / 100;
      }
      value = 1.0;
    }

    // handle auto
    if (value === 'auto') {
      switch(style) {
        case 'width':
          if (this.getStyle('display') === 'none') {
            value = null ;
          } else if (element.currentStyle) {
            var paddingLeft = parseInt(element.currentStyle.paddingLeft,0)||0;
            var paddingRight = parseInt(element.currentStyle.paddingRight,0)||0;
            var borderLeftWidth = parseInt(element.currentStyle.borderLeftWidth, 0) || 0 ;
            var borderRightWidth = parseInt(element.currentStyle.borderRightWidth, 0) || 0 ;
            value = (element.offsetWidth - paddingLeft - paddingRight - borderLeftWidth - borderRightWidth) + 'px' ;
          }
          break ;
        case 'height':
          if (this.getStyle('display') === 'none') {
            value = null ;
          } else if (element.currentStyle) {
            var paddingTop = parseInt(element.currentStyle.paddingTop,0)||0;
            var paddingBottom = parseInt(element.currentStyle.paddingBottom,0)||0;
            var borderTopWidth = parseInt(element.currentStyle.borderTopWidth, 0) || 0 ;
            var borderBottomWidth = parseInt(element.currentStyle.borderBottomWidth, 0) || 0 ;
            value = (element.offsetHeight - paddingTop - paddingBottom - borderTopWidth - borderBottomWidth) + 'px' ;
          }

          break ;
        default:
          value = null ;
      }
    }

    return value;
  };
  
  // Called from innerFrame to actually collect the values for the innerFrame.
  // Normally the value we want for the width/height is stored in clientWidth/
  // height but in IE this is only good if the element hasLayout.  In this
  // case always use the scrollWidth/Height.
  SC.View._collectInnerFrame = function() {
    var el = this.rootElement ;
    var hasLayout = (el.currentStyle) ? el.currentStyle.hasLayout : NO ;
    return { 
      x: el.offsetLeft, 
      y: el.offsetTop, 
      width: (hasLayout) ? Math.min(el.scrollWidth, el.clientWidth) : el.scrollWidth, 
      height: (hasLayout) ? Math.min(el.scrollHeight, el.clientHeight) : el.scrollHeight 
    };
  } ;
  
} else {
  
  // Called from innerFrame to actually collect the values for the innerFrame.
  // This method should return the smaller of the scrollWidth/height (which
  // will be set if the element is scrollable), or the clientWdith/height 
  // (which is set if the element is not scrollable).
  SC.View._collectInnerFrame = function() {
    var el = this.rootElement ;
    return { 
      x: el.offsetLeft, 
      y: el.offsetTop, 
      width: Math.min(el.scrollWidth, el.clientWidth), 
      height: Math.min(el.scrollHeight, el.clientHeight) 
    };
  } ;
}


// this handler goes through the guid to avoid any potential memory leaks
SC.View._onscroll = function(evt) { $view(this)._onscroll(evt); } ;

SC.View.WIDTH_PADDING_STYLES = ['paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];

SC.View.HEIGHT_PADDING_STYLES = ['paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'];

SC.View.SCROLL_WIDTH_PADDING_STYLES = ['borderLeftWidth', 'borderRightWidth'];
SC.View.SCROLL_HEIGHT_PADDING_STYLES = ['borderTopWidth', 'borderBottomWidth'];

SC.View.elementFor = SC.View.viewFor ; // Old Sprout Compatibility.

// This div is used to create nodes.  It should normally remain empty.
SC._ViewCreator = document.createElement('div') ;

// This div can be used to hold elements you don't want on the page right now.
SC.NodeCache = document.createElement('div') ;


/* End ------------------------------------------------------- views/view.js*/

/* Start ----------------------------------------------------- panes/pane.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view');

SC.KEYVIEW_SELECTING_NONE      = 0;
SC.KEYVIEW_SELECTING_NEXT      = 1;
SC.KEYVIEW_SELECTING_PREVIOUS  = 2;

/**
  @class
  
  A PaneView provides the root view context for a popup, menu, dialog, sheet,
  widget or a window itself.  The responder chain, which is used to route 
  keyboard and mouse events, always terminates with a pane view.
  
  You can use PaneViews to display various pop-up widgets as well as to 
  implement your own behaviors.
  
  To use a pane, you typically just create a view and set its paneType 
  property to the name of the type of pane view you want it to display in.
  Whenever you set the view's isVisible property to true, it will display
  inside of the pane view automatically.
  
  You will rarely use the SC.PaneView directly.  Instead, you should use
  one of the subclasses included in SproutCore or create your own.
  
  @extends SC.View
  @since SproutCore 1.0
*/
  
SC.PaneView = SC.View.extend({

  // panes do not belong to other panes...
  pane: null,

  isPane: true,
  isModal: false,
  
  canBecomeKeyPane: true,
  isKeyPane: false,
  makeKeyPane: function()
  {
    if (!this.get('canBecomeKeyPane')) return false;
    if (this.get('isKeyPane')) return false;
    SC.app.set('keyPane', this);
    return true;
  },
  didBecomeKeyPane: function() {},
  willResignKeyPane: function() {},
  
  canBecomeMainPane: true,
  isMainPane: false,
  makeMainPane: function()
  {
    if (!this.get('canBecomeMainPane')) return false;
    if (this.get('isMainPane')) return false;
    SC.app.set('mainPane', this);
    return true;
  },
  didBecomeMainPane: function() {},
  willResignMainPane: function() {},



  performKeyInterfaceControl: function( keystring, evt )
  {
    // TODO!
    return false;
  },

  keyViewSelectionDirection: SC.KEYVIEW_SELECTING_NONE,
  
  selectPreviousKeyView: function() {},
  selectNextKeyView: function() {},
  
  autorecalculatesKeyViewLoop: false,
  recalculateKeyViewLoop: function() {},
  


  nextResponder: null,

  // This property points to the responder (usually a view) that should be
  // the first to receive keyboard events.  Usually you set this by calling
  // becomeFirstResponder on the view itself.
  _firstResponder: null,
  firstResponder: function(key,value)
  {
    if (value !== undefined) {
      if (this._firstResponder) {
        this._firstResponder.willLoseFirstResponder();
      }
      if (this._firstResponder) {
        this._firstResponder.set('isFirstResponder',false) ;
      }
      
      this._firstResponder = value ;
     
      if (this._firstResponder) {
        this._firstResponder.set('isFirstResponder',true) ;
      }
      if (this._firstResponder) {
        this._firstResponder.didBecomeFirstResponder() ;
      }
    }
    return this._firstResponder; //TODO: shouldn't we return the defaultResponder if !this._firstResponder
  }.property(),

  // This property can be set to point to a default responder that should 
  // handle keyboard events if no responders in the normal chain decide to
  // take it.
  defaultResponder: null

});

/* End ------------------------------------------------------- panes/pane.js*/

/* Start ----------------------------------------------------- views/container.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view') ;

/** 
  @class
  
  A container view will place the view of its content property inside
  of itself as its only child.  You can override this with a custom
  replaceContent() method if you want to do something different when the
  content changes.
  
  @extends SC.View

*/
SC.ContainerView = SC.View.extend(
  /** @scope SC.ContainerView.prototype */
  {
  
  /** change this property value to replace the content of the container.
      @type SC.View
  */
  content: null,
  
  /** @private */
  contentBindingDefault: SC.Binding.Single,
  
  /** 
    define an outlet named rootView to use that view as the container.
    
    Normally container view will set its own child views to the content you
    set. Instead, you can set the rootView property to point to a child view 
    of the container and the child views in that view will be changed instead.
    
    @type SC.View
  */
  rootView: null,

  /**
    Replaces any child views with the passed new content.  
    
    This method is automatically called whenever your content property 
    changes.  You can override it if you want to provide some behavior other
    than the default.
    
    @param {SC.View} newContent the new content view or null.
  */
  replaceContent: function(newContent) {
    var containerView = this.get('rootView') || this ;
    containerView.clear() ;
    var newView = newContent ;
    
    if (newView) {
      newView.viewFrameWillChange() ;
      containerView.appendChild(newView) ;
      newView.viewFrameDidChange() ;
    }
  },
  
  _contentObserver: function() {
    this.replaceContent(this.get('content'));
  }.observes('content')
  
}) ;


/* End ------------------------------------------------------- views/container.js*/

/* Start ----------------------------------------------------- panes/overlay.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('panes/pane') ;
require('views/container') ;

// Panes are views that appear over the top of your regular content such as
// dialogs boxes and pop-up menus.  This class provides the basic support for
// these functions.
//
// SproutCore provides built-in support for four different types of panes, but
// you can also create your own types of panes yourself.  The pane view
// automatically groups all panes of a similar type together. 
//
// To create your own type of pane, create a subclass of SC.PaneView and then
// register it with the SC.PaneManager:
//
// SC.PaneManager.registerPane('paneType', SC.PaneView);
//
// You can then make views show in the panes by settings the paneType 
// property on the view and set 'isVisible' to true.
//
// Note that PaneView instances are reused by the pane manager.  You should
// design your view be reused in this way.
//
SC.OverlayPaneView = SC.PaneView.extend({

  // This property will be set to the content view when you are asked to
  // display it.
  content: null,
  
  // Set this to a value that indicates where you want your view to appear
  // compare to other types of panes in the system.  the PaneManagers groups
  // all panes of the same type together, with the most recently opened one
  // appearing on top.
  layer: 0,
  
  // This property will be set to the view that triggered your pane to show
  // if relevant.  You can use this to properly size and position your pane.
  anchorView: null,
  
  // This property will be set to the event that triggered your pane to show
  // if relevant.  You can use this to properly size and position your pane.
  triggerEvent: null,
  
  // if true, this pane will be modal.  Clicks outside of the containerView
  // will be ignored.  If false, this pane is semi-modal: it will
  // hide if you click outside of the containerView.
  isModal: true,
  
  // override to position your pane on view.
  positionPane: function() {
  },
  
  // This method will do its best to position your pane on screen in relation
  // to the anchor view passed.
  fitPositionToScreen: function(preferredPosition, paneView, anchor) {
    
    // first build up the frame and convert to window...
    var f = paneView.get('frame') ;
    f.x = preferredPosition.x ; f.y = preferredPosition.y ;
    f = paneView.convertFrameToView(f, null) ;

    // get useful other frame limits.
    var aframe = anchor.convertFrameToView(anchor.get('frame'), null) ;
    var wframe = SC.window.get('frame') ;

    // make sure the right edge fits on the screen.  If not, anchor to 
    // right edge of anchor or right edge of window, whichever is closer.
    if (SC.maxX(f) > wframe.width) {
      var mx = Math.max(SC.maxX(aframe), f.width) ;
      f.x = Math.min(mx, wframe.width) - f.width ;
    }

    // if the left edge is off of the screen, try to position at left edge
    // of anchor.  If that pushes right edge off screen, shift back until 
    // right is on screen or left = 0
    if (SC.minX(f) < 0) {
      f.x = SC.minX(Math.max(aframe,0)) ;
      if (SC.maxX(f) > wframe.width) {
        f.x = Math.max(0, wframe.width - f.width);
      }
    }

    // make sure bottom edge fits on screen.  If not, try to anchor to top
    // of anchor or bottom edge of screen.
    if (SC.maxY(f) > wframe.height) {
      var mx = Math.max((aframe.y - f.height), 0) ;
      if (mx > wframe.height) {
        f.y = Math.max(0, wframe.height - f.height) ;
      } else f.y = mx ;
    }

    // if Top edge is off screen, try to anchor to bottom of anchor. If that
    // pushes off bottom edge, shift up until it is back on screen or top =0
    if (SC.minY(f) < 0) {
      var mx = Math.min(SC.maxY(aframe), (wframe.height - aframe.height)) ;
      f.y = Math.max(mx, 0) ;
    }

    return f ;
    
  },
  
  resizeWithOldParentSize: function(oldSize) {
    this.positionPane() ;  
  },
  
  // ...........................................
  // KEYBOARD SUPPORT
  //
  acceptsFirstResponder: true,

  keyDown: function(evt) {
    if (!this.interpretKeyEvents(evt)) {
      return arguments.callee.base.apply(this,arguments);
    }
  },
  
  // when you hit return from within a dialog or panel, look for a child 
  // view with isDefault => true
  insertNewline: function(sender, evt) {
    var button = this._findViewWithKeyIn('isDefault', SC.ButtonView, this) ;
    if (button) {
      button.triggerAction(evt) ;
      return true ;
    } else return false ;
  },
  
  cancel: function(sender, evt) {
    var button = this._findViewWithKeyIn('isCancel', SC.ButtonView, this) ;    
    if (button) {
      button.triggerAction(evt) ;
      return true ;
    } else return false ;
  },
  
  _findViewWithKeyIn: function(keyName, rootClass, rootView, ignoreRoot) {
    if (!ignoreRoot) {
      if ((rootView instanceof rootClass) && rootView.get(keyName)) {
        return rootView ;
      }
    }
    
    var child = rootView.get('firstChild') ;
    while(child) {
      var ret = this._findViewWithKeyIn(keyName, rootClass, child) ;
      if (ret) return ret ;
      child = child.get('nextSibling') ;
    }
    return null ;
  },
  
  focusFirstKeyView: function() {
    var fr = this._findViewWithKeyIn('acceptsFirstResponder', SC.Responder, this, true) ;
    if (!fr) fr = this;
    fr.becomeFirstResponder() ;
  },

  click: function(evt) {
    if (!this.get('isModal')) {
      var content = this.containerView.get('content') ;
      if (content) content.set('isVisible', false) ;
    }
    return true ; 
  },
    
  // ...........................................
  // PRIVATE METHODS
  //
  _contentDidChange: function() {
    var containerView = this.get('containerView') ;
    if (containerView) containerView.set('content',this.get('content')) ;
  }.observes('content'),
  
  outlets: ['containerView'],
  containerView: SC.ContainerView.extend({
    outlets: ['rootView'],

    rootView: SC.View.extend({
      // absorb all clicks so the pane will not hide.
      click: function() { return true; }
    }).outletFor('.pane-root?'),
    
    _fixWidth: function() {
      var content = this.get('content') ;
      if (content) {
        content.resizeWithOldParentSize(this.get('size')) ;
        
        // compute space we need to add for border/padding
        var padding = 0;
        this.getEach('styleBorderLeftWidth', 'styleBorderRightWidth', 'stylePaddingLeft', 'stylePaddingRight').each(function(x) { padding += x || 0; });
        this.recacheFrames() ;
        content.recacheFrames() ;
        
        this.set('size', { width: (content.get('size').width + padding) });
        this.owner.positionPane() ;
        this.owner.setStyle({ visibility: 'visible' }) ;
      } 
      if (this.get('isVisibleInWindow')) 
      {
        this.owner.focusFirstKeyView();
      }
    }.observes('content'),
    
    init: function() {
      arguments.callee.base.apply(this,arguments) ;
      
      // only Safari does well enough with animations to handle this dainty
      // guy. 
      if (SC.isSafari()) {
        this.visibleAnimation = {
          visible: 'opacity: 1.0', hidden: 'opacity: 0.0', duration: 100  
        } ;
      }
    },

    // allow click through outside of the rootView.
    click: function(evt) {
      return false ;
    }
    
  }).outletFor('.pane-wrapper?'),
  
  // just before the view becomes visible, set the width of the container
  // view to match its content
  show: function() {
    this.containerView._fixWidth();
    arguments.callee.base.apply(this,arguments);
    //this.focusFirstKeyView() ;
  }

});


/* End ------------------------------------------------------- panes/overlay.js*/

/* Start ----------------------------------------------------- validators/validator.js*/

// ==========================================================================
// Validators
// Author: Charles Jolley
// ==========================================================================

require('foundation/object');

/**
  @class
  
  Validators provide a way for you to implement simple form field validation
  and transformation.  To use a validator, simply name the validator in the
  "validate" attribute in your text field.  For example, if you want to
  validate a field using the PhoneNumberValidator use this:

  <input value="1234567890" validate="phone-number" />

  Validators get notified at three points.  You can implement one or all
  of these methods to support validation.  All of the validate methods except
  for validateKeypress behave the same way.  You are passed a form, field,
  and possibly the oldValue.  You are expected to return Validator.OK or
  an error string.  Inside this method you typically do one of all of the
  following:

  1. You can simply validate the field value and return OK or an error str
  
  2. You can modify the field value (for example, you could format the
     string to match some predefined format).
     
  3. If you need to roundtrip the server first to perform validation, you can
     return Validator.OK, then save the form and field info until after the
     roundtrip.  On return, if there is a problem, first verify the field
     value has not changed and then call form.errorFor(field,str) ;

  @extends SC.Object
  @author Charles Jolley
  @version 1.0
*/
SC.Validator = SC.Object.extend(
/** @scope SC.Validator.prototype */ {

  // ..........................................
  // OBJECT VALUE CONVERSION
  //
  // The following methods are used to convert the string value of a field
  // to and from an object value.  The default implementations return
  // the string, but you can override this to provide specific behaviors. 
  // For example, you might add or remove a dollar sign or convert the 
  // value to a number.
  
/**
  Returns the value to set in the field for the passed object value.  
  
  The form and view to be set MAY (but will not always) be passed also.  You
  should override this method to help convert an input object into a value
  that can be displayed by the field.  For example, you might convert a 
  date to a property formatted string or a number to a properly formatted
  value.
  
  @param {Object} object The object to transform
  @param {SC.FormView} form The form this field belongs to. (optional)
  @param {SC.View} view The view the value is required for.
  @returns {Object} a value (usually a string) suitable for display
*/
  fieldValueForObject: function(object, form, view) { return object; },
  
  /**
    Returns the object value for the passed string.
    
    The form and view MAY (but wil not always) be passed also.  You should
    override this method to convert a field value, such as string, into an
    object value suitable for consumption by the rest of the app.  For example
    you may convert a string into a date or a number.
    
    @param {String} value the field value.  (Usually a String).
    @param {SC.FormView} form The form this field belongs to. (optional)
    @param {SC.View} view The view this value was pulled from.
    @returns {Object} an object suitable for consumption by the app.
  */
  objectForFieldValue: function(value, form, view) { return value; },
  
  // ..........................................
  // VALIDATION PRIMITIVES
  //

  /**
    Validate the field value.  
    
    You can implement standard behavior for your validator by using the
    vaidate and validateError methods.  validate() should return false
    if the field is not valid.

    @param {SC.FormView} form the form this view belongs to
    @param {SC.View} field the field to validate.  Responds to fieldValue.
    @returns {Boolean} YES if field is valid.
  */
  validate: function(form, field) { return true; },

  /**
    Returns an error object if the field is invalid.
  
    This is the other standard validator method that can be used to impement
    basic validation.  This should return an error object explaining why
    the field is not valid.  It will only be called if validate() returned
    false.
  
    @param {SC.FormView} form the form this view belongs to
    @param {SC.View} field the field to validate.  Responds to fieldValue.
    @returns {SC.Error} an error object
  */
  validateError: function(form, field) { 
    return $error(
      "Invalid.General(%@)".loc(field.get('fieldValue')),
      field.get('fieldKey')) ; 
  },

  // ..........................................
  // VALIDATION API
  //

  /**
    Invoked just before the user ends editing of the field.
  
    The default implementation calls your validate() method and then
    validateError() if valiate() returns NO.  This method should return 
    SC.Validator.OK if validation succeeded or an error object if it fails.
  
    @param {SC.FormView} form the form for the field
    @param {SC.View} field the field to validate
    @param {Object} oldValue: the value of the field before the change

    @returns SC.Validator.OK or an error object.
  
  */
  validateChange: function(form, field, oldValue) { 
    return (this.validate(form,field)) ? SC.Validator.OK : this.validateError(form, field);
  },

  /**
    Invoked just before the form is submitted.
  
    This method gives your validators one last chance to perform validation
    on the form as a whole.  The default version does the same thing as the 
    validateChange() method.
  
    @param {SC.FormView} form the form for the field
    @param {SC.View} field the field to validate

    @returns SC.Validator.OK or an error object.
  
  */  
  validateSubmit: function(form, field) { 
    return (this.validate(form,field)) ? SC.Validator.OK : this.validateError(form, field);
  },

  /**
    Invoked 1ms after the user types a key (if a change is allowed).  
  
    You can use this validate the new partial string and return an error if 
    needed. The default will validate a partial only if there was already an 
    error. This allows the user to try to get it right before you bug them.
  
    Unlike the other methods, you should return SC.Validator.NO_CHANGE if you
    did not actually validate the partial string.  If you return 
    SC.Validator.OK then any showing errors will be hidden.
  
    @param {SC.FormView} form the form for the field
    @param {SC.View} field the field to validate

    @returns SC.Validator.OK, SC.Validator.NO_CHANGE or an error object.
  */  
  validatePartial: function(form, field) { 
    if (!field.get('isValid')) {
      return (this.validate(form,field)) ? SC.Validator.OK : this.validateError(form, field);
    } else return SC.Validator.NO_CHANGE ;
  },
  
  /**
    Invoked when the user presses a key.  
  
    This method is used to restrict the letters and numbers the user is 
    allowed to enter.  You should not use this method to perform full 
    validation on the field.  Instead use validatePartial().
  
    @param {SC.FormView} form the form for the field
    @param {SC.View} field the field to validate
    @param {String} char the characters being added
    
    @returns {Boolean} YES if allowed, NO otherwise
  */
  validateKeypress: function(form, field,charStr) { return true; },

  // .....................................
  // OTHER METHODS

  /**
    Called on all validators when they are attached to a field.  
  
    You can use this to do any setup that you need.  The default does nothing.
    
    @param {SC.FormView} form the form for the field
    @param {SC.View} field the field to validate
  */
  attachTo: function(form,field) { },

  /**
    Called on a validator just before it is removed from a field.  You can 
    tear down any setup you did for the attachTo() method.
    
    @param {SC.FormView} form the form for the field
    @param {SC.View} field the field to validate
  */
  detachFrom: function(form, field) {}

}) ;

SC.Validator.mixin(/** @scope SC.Validator */ {

  /**
    Return value when validation was performed and value is OK.
  */
  OK: true, 
  
  /**
    Return value when validation was not performed.
  */
  NO_CHANGE: false,  

  /**
    Invoked by a field whenever a validator is attached to the field.
    
    The passed validatorKey can be a validator instance, a validator class
    or a string naming a validator. To make your validator
    visible, you should name your validator under the SC.Validator base.
    for example SC.Validator.Number would get used for the 'number' 
    validator key.
  
    This understands validatorKey strings in the following format:

    * 'key' or 'multiple_words' will find validators Key and MultipleWords

    * if you want to share a single validator among multiple fields (for
      example to validate that two passwords are the same) set a name inside
      brackets. i.e. 'password[pwd]'.

    @param {SC.FormView} form the form for the field
    @param {SC.View} field the field to validate
    @param {Object} validatorKey the key to validate
    
    @returns {SC.Validaotr} validator instance or null
  */  
  findFor: function(form,field, validatorKey) {
    
    // Convert the validator into a validator instance.
    var validator ;
    if (!validatorKey) return ; // nothing to do...
    
    if (validatorKey instanceof SC.Validator) {
      validator = validatorKey ;
    } else if (validatorKey.isClass) {
      validator = validatorKey.create() ;
      
    } else if ($type(validatorKey) == T_STRING) {

      // extract optional key name
      var name = null ;
      var m = validatorKey.match(/^(.+)\[(.*)\]/) ;
      if (m) {
        validatorKey = m[1] ; name = m[2]; 
      }
      
      // convert the validatorKey name into a class.
      validatorKey = ('-' + validatorKey).camelize() ;
      var validatorClass = SC.Validator[validatorKey] ;
      if (validatorClass == null) {
        throw "validator %@ not found for %@".fmt(validatorKey, field) ;
        return null ;
      } else if (name) {

        // if a key was also passed, then find the validator in the list of
        // validators for the form.  Otherwise, just create a new instance.
        if (!form) {
          throw "named validator (%@) could not be found for field %@ because the field does not belong to a form".fmt(name,field) ;
          return null ;
        }
        
        if (!form._validatorHash) form._validatorHash = {} ;
        var validator = (name) ? form._validatorHash[name] : null ;
        if (!validator) validator = validatorClass.create() ;
        if (name) form._validatorHash[name] = validator ;
      } else validator = validatorClass.create() ;
    } 
    
    return validator ;
  },
  
  /**
    Convenience class method to call the fieldValueForObject() instance
    method you define in your subclass.
  */
  fieldValueForObject: function(object, form, field) {
    return this.prototype.fieldValueForObject(object,form,field) ;
  },

  /**
    Convenience class method to call the objectForFieldValue() instance
    method you define in your subclass.
  */
  objectForFieldValue: function(value, form, field) {
    return this.prototype.objectForFieldValue(value,form,field) ;
  }
  
}) ;



/* End ------------------------------------------------------- validators/validator.js*/

/* Start ----------------------------------------------------- validators/credit_card.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('validators/validator') ;

/**
  Validate a field value as a credit card number. 
  
  This validator will perform a basic check to ensure the credit card number
  is mathematically valid.  It will also accept numbers with spaces, dashes
  or other punctuation.  
  
  Converted credit card numbers are broken into units of 4.
  
  Basic credit card validation courtesy David Leppek 
  (https://www.azcode.com/Mod10)

  @class
  @extends SC.Validator
  @author Charles Jolley
  @version 1.0
*/
SC.Validator.CreditCard = SC.Validator.extend(
/** @scope SC.Validator.CreditCard.prototype */ {

  /**
    Expects a string of 16 digits.  Will split into groups of 4 for display.
  */
  fieldValueForObject: function(object, form, field) {
    if (typeof(object) == "string" && object.length == 16) {
      object = [object.slice(0,4),object.slice(4,8),object.slice(8,12),object.slice(12,16)].join(' ') ;
    }
    return object ;
  },

  /**
    Removes all whitespace or dashes to make a single string.
  */
  objectForFieldValue: function(value, form, field) {
    return value.replace(/[\s-\.\:]/g,'') ;
  },
  
  validate: function(form, field) { 
    return this.checkNumber(field.get('fieldValue')) ; 
  },
  
  validateError: function(form, field) {
    var label = field.get('errorLabel') || 'Field' ;
    return $error("Invalid.CreditCard(%@)".loc(label), label);
  },
  
  checkNumber: function(ccNumb) {
    var valid = "0123456789";  // Valid digits in a credit card number
    var len = ccNumb.length;  // The length of the submitted cc number
    var iCCN = parseInt(ccNumb,0);  // integer of ccNumb
    var sCCN = ccNumb.toString();  // string of ccNumb
    sCCN = sCCN.replace (/^\s+|\s+$/g,'');  // strip spaces
    var iTotal = 0;  // integer total set at zero
    var bNum = true;  // by default assume it is a number
    var bResult = false;  // by default assume it is NOT a valid cc
    var temp;  // temp variable for parsing string
    var calc;  // used for calculation of each digit

    // Determine if the ccNumb is in fact all numbers
    for (var j=0; j<len; j++) {
      temp = "" + sCCN.substring(j, j+1);
      if (valid.indexOf(temp) == "-1"){bNum = false;}
    }

    // if it is NOT a number, you can either alert to the fact, 
    // or just pass a failure
    if(!bNum) bResult = false;

    // Determine if it is the proper length 
    if((len == 0)&&(bResult)){  // nothing, field is blank AND passed above # check
      bResult = false;
    } else{  // ccNumb is a number and the proper length - let's see if it is a valid card number
      if(len >= 15){  // 15 or 16 for Amex or V/MC
        for(var i=len;i>0;i--){  // LOOP throught the digits of the card
          calc = parseInt(iCCN,0) % 10;  // right most digit
          calc = parseInt(calc,0);  // assure it is an integer
          iTotal += calc;  // running total of the card number as we loop - Do Nothing to first digit
          i--;  // decrement the count - move to the next digit in the card
          iCCN = iCCN / 10;                               // subtracts right most digit from ccNumb
          calc = parseInt(iCCN,0) % 10 ;    // NEXT right most digit
          calc = calc *2;                                 // multiply the digit by two
          // Instead of some screwy method of converting 16 to a string and then parsing 1 and 6 and then adding them to make 7,
          // I use a simple switch statement to change the value of calc2 to 7 if 16 is the multiple.
          switch(calc){
            case 10: calc = 1; break;       //5*2=10 & 1+0 = 1
            case 12: calc = 3; break;       //6*2=12 & 1+2 = 3
            case 14: calc = 5; break;       //7*2=14 & 1+4 = 5
            case 16: calc = 7; break;       //8*2=16 & 1+6 = 7
            case 18: calc = 9; break;       //9*2=18 & 1+8 = 9
            default: calc = calc;           //4*2= 8 &   8 = 8  -same for all lower numbers
          }                                               
        iCCN = iCCN / 10;  // subtracts right most digit from ccNum
        iTotal += calc;  // running total of the card number as we loop
      }  // END OF LOOP
      if ((iTotal%10)==0){  // check to see if the sum Mod 10 is zero
        bResult = true;  // This IS (or could be) a valid credit card number.
      } else {
        bResult = false;  // This could NOT be a valid credit card number
        }
      }
    }
    return bResult; // Return the results
  }
    
}) ;


/* End ------------------------------------------------------- validators/credit_card.js*/

/* Start ----------------------------------------------------- foundation/error.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('foundation/object');

/**
  @class
  
  An error, used to represent an error state.
  
  Many API's within SproutCore will return an instance of this object whenever
  they have an error occur.  An error includes an error code, description,
  and optional human readable label that indicates the item that failed. 
  
  Depending on the error, other properties may also be added to the object
  to help you recover from the failure.
  
  You can pass error objects to various UI elements to display the error in
  the interface. You can easily determine if the value returned by some API is 
  an error or not using the helper SC.$ok(value).
  
  @extends SC.Object
  @since SproutCore 1.0
*/
SC.Error = SC.Object.extend(
/** @scope SC.Error.prototype */ {
  
  /**
    error code.  Used to designate the error type.
  */
  code: -1,
  
  /**
    Human readable description of the error.  This can also be a non-localized
    key.
  */
  description: '',
  
  /**
    Human readable name of the item with the error.
  */
  label: null
}) ;

/**
  Creates a new SC.Error instance with the passed description, label, and
  code.  All parameters are optional.
  
  @param description {String} human readable description of the error
  @param label {String} human readable name of the item with the error
  @param code {Number} an error code to use for testing.
  @returns {SC.Error} new error instance.
*/
SC.Error.desc = function(description, label, code) {
  var opts = { description: description } ;
  if (label !== undefined) opts.label = label ;
  if (code !== undefined) opts.code = code ;
  return this.create(opts) ;
} ;

/**
  Shorthand form of the SC.Error.desc method.
*/
SC.$error = function(description, label, c) { 
  return SC.Error.desc(description,label, c); 
} ;
var $error = SC.$error ; // export globally

/**
  Returns YES if the passed value is an error object, otherwise NO.
*/
SC.$ok = function(ret) {
  return (ret !== false) && ($type(ret) != T_ERROR) ;
};
var $ok = SC.$ok ; // export globally.

// STANDARD ERROR OBJECTS

/**
  Standard error code for errors that do not support multiple values.
*/
SC.Error.HAS_MULTIPLE_VALUES = -100 ;


/* End ------------------------------------------------------- foundation/error.js*/

/* Start ----------------------------------------------------- drag/drag.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('core') ;
require('views/view') ;

SC.DRAG_LINK = 0x0004; SC.DRAG_COPY = 0x0001; SC.DRAG_MOVE = 0x0002;
SC.DRAG_NONE = 0x0000; SC.DRAG_ANY = 0x0007 ;
SC.DRAG_AUTOSCROLL_ZONE_THICKNESS = 20 ;

/**

  @class
  
  An instance of this object is created whenever a drag occurs.  The instance
  manages the mouse events and coordinating with droppable targets until the
  user releases the mouse button. 

  To initiate a drag, you should call SC.Drag.start() with the options below
  specified in a hash. Pass the ones you need to get the drag you want:  
   
  - *event: (req)* The mouse event that triggered the drag.  This will be used 
    to position the element.
  
   - *source: (req)* The drag source object that should be consulted during 
     the drag operations. This is usually the container view that initiated 
     the drag.
  
   - *dragView: (req)*  This should point to a view that will be used as the 
     source image for the drag. The drag operation will clone the DOM elements 
     for this view and add the class name 'drag-image' to the outermost 
     element.
  
   - *ghost:  YES | NO*  If YES or not passed, then drag view image will show, 
     but the source dragView will not be hidden.  set to NO to make it appear 
     that the dragView itself is being dragged around.
  
   - *slideBack: YES | NO*  If YES or not specified, then if the drag 
     operation is cancelled, the dragView will slide back to its source 
     origin.
  
   - *origin:*  If passed, this will be used as the origin point for the 
     ghostView when it slides back.  You normally do not need to pass this 
     unless the ghost view does not appear in the main UI.
  
   - *data:* Optional hash of data types and values.  You can use this to pass 
     a static set of data instead of providing a dataSource.  If you provide
     a dataSource, it will override this.
  
   - *dataSource:*  Optional object that will provide the data for the drag to 
     be consumed by the drop target.  If you do not pass this parameter or the 
     data hash, then the source object will be used if it implements the 
     SC.DragDataSource protocol.
  
   - *anchorView:* if you pass this optional view, then the drag will only be 
     allowed to happen within this view.  The ghostView will actually be added 
     as a child of this view during the drag.  Normally the anchorView is the 
     window.
   
  @extends SC.Object
*/
SC.Drag = SC.Object.extend(
/** @scope SC.Drag.prototype */ {
  
  /** [RO] The source object used to coordinate this drag. */  
  source: null,

  /**  The actual image being dragged around the screen.  
  
    This is created automatically from the dragView.
  */
  ghostView: null,

  /**  
    The view that was used as the source of the ghostView.  
    
    The drag view is not moved from its original location during a drag.
    Instead, the DOM content of the view is cloned and managed by the 
    ghostView.  If you want to visually indicate that the view is being 
    movied, you may want to temporarily hide it during the drag.
  */
  dragView: null,
  
  /**  
    If YES, the dragView is automatically hidden while dragging around the ghost.
  */
  ghost: YES,
  
  /**
    If YES, then the ghostView will slide back to its original location if drag is cancelled.
  */
  slideBack: YES,
  
  /**  The original mouse down event. */
  mouseDownEvent: null,

  /**  
    The origin to slide back to in the coordinate of the dragView's offsetParent.
  */
  ghostOffset: { x: 0, y: 0 },
  
  /**
    The current location of the mouse pointer in window coordinates.  
  
    This is updated as long as the mouse button is pressed.
  */
  location: null,

  // ..........................................
  // DRAG DATA
  //

  /**
    Data types supported by this drag operation.
    
    Returns an array of data types supported by the drag source.  This may 
    be generated dynamically depending on the data source.

    If you are implementing a drag source, you will need to provide these data
    types so that drop targets can detect if they can accept your drag data.
    
    If you are implementing a drop target, you should inspect this property
    on your dragEntered() and prepareForDragOperation() methods to determine 
    if you can handle any of the data types offered up by the drag source.
    
    @field {Array} available data types
  */
  dataTypes: function() {
    
    // first try to use the data source.
    if (this.dataSource) return this.dataSource.get('dragDataTypes') ;
    
    // if that fails, get the keys from the data hash.
    if (this.data) {
      var ret = [];
      for(var key in this._data) {
        if (this.data.hasOwnProperty(key)) ret.push(key) ;
      }
      return ret ;
    }    
    
    // if that fails, then check to see if the source object is a dataSource.
    var source = this.get('source') ;
    if (source && source.dragDataTypes) return source.get('dragDataTypes') ;
    
    // no data types found. :(
    return [] ; 
  }.property(),
  
  /**
    Checks for a named data type in the drag.
    
    @param dataType {String} the data type
    @returns {Boolean} YES if data type is present in dataTypes array.
  */
  hasDataType: function(dataType) {
    var dataTypes = this.get('dataTypes') || [] ;
    return (dataTypes.indexOf(dataType) >= 0) ;  
  },
  
  /**
    Retrieve the data for the specified dataType from the drag source.
  
    Drop targets can use this method during their performDragOperation() method
    to retrieve the actual data provided by the drag data source.  This data
    may be generated dynamically depending on the data source.
    
    @param {Object} dataType data type you want to retrieve.  Should be one of the values returned in the dataTypes property
    
    @returns {Object} The generated data.
  */
  dataForType: function(dataType) {
    
    // first try to use the data Source.
    if (this.dataSource) {
      return this.dataSource.dragDataForType(dataType, this) ;
      
    // then try to use the data hash.
    } else if (this.data) {
      return this.data[dataType];
      
    // if all else fails, check to see if the source object is a data source.
    } else {
      var source = this.get('source') ;
      if (source && $type(source.dragDataForType) == T_FUNCTION) {
        return source.dragDataForType(dataType, this) ;
        
      // no data source found. :(
      } else return null ;
    }
  },
  
  /**
    Optional object used to provide the data for the drag.

    Drag source can designate a dataSource object to generate the data for 
    a drag dynamically.  The data source can and often is the drag source 
    object itself.  

    Data Source objects must comply with the SC.DragDataSource interface.  If
    you do not want to implement this interface, you can provide the data 
    directly with the data property.

    If you are implementing a drop target, use the dataTypes property and 
    dataForTypes() method to access data instead of working directly with these
    properties.
    
    @type {Object}
  */
  dataSource: null,

  /**
    Optional hash of data.  Used if no dataSource was provided.
    
    Drag sources can provide a hash of data when the drag begins instead of 
    specifying an actual dataSource.  The data is stored in this property.
    If you are implementing a drop target, use the dataTypes property and 
    dataForTypes() method to access data instead of working directly with these
    properties.
    
    @type {Object}
  */
  data: null,

  // required by autoscroll
  _dragInProgress: YES,
  
  // this will actually start the drag process.
  startDrag: function() {
    
    // create the ghost view and position.
    this._createGhostView() ;
    
    // compute the offset from the original mouse location.
    var origin = this.dragView.convertFrameToView(this.dragView.get('frame'), null);
    var pointer = Event.pointerLocation(this.event) ;
    
    window.dragEvent = this.event ;
    
    this.ghostOffset = { x: (pointer.x-origin.x), y: (pointer.y-origin.y) };

    // position the ghost view.
    this._positionGhostView(this.event) ;
    
    // notify window a drag is in process. mouseDragged notifications will
    // go to the drag instead.
    SC.window.dragDidStart(this) ;
    
    if (this.source && this.source.dragDidBegin) {
      this.source.dragDidBegin(this, pointer) ;
    }
  },
  
  _lastLoc: {},

  /**  
    @private
    
    This method is called repeatedly during a mouse drag.  It updates the
    position of the ghost image, then it looks for a current drop target and
    notifies it.
  */
  mouseDragged: function(evt) {
    
    var loc = Event.pointerLocation(evt) ;
    var scrolled = this._autoscroll(evt) ;
    
    // ignore duplicate calls.
    if (!scrolled && (loc.x == this._lastLoc.x) && (loc.y == this._lastLoc.y)) return ;
    this._lastLoc = loc ;
    this.set('location', loc) ;

    this._positionGhostView(evt) ;
    var last = this._lastTarget ;

    // STEP 1: Determine the deepest drop target that allows an operation.
    // if the drop target selected the last time this method was called differs
    // from the deepest target found, then go up the chain until we either hit the
    // last one or find one that will allow a drag operation
    var target = this._findDropTarget(evt) ; // deepest drop target
    var op = SC.DRAG_NONE ;
    
    while (target && (target != last) && (op == SC.DRAG_NONE)) {
      
      // make sure the drag source will permit a drop operation on the named target.
      // if source does not implement this callback, just assume a drop is allowed
      // by the source.
      if (target && this.source && this.source.dragSourceOperationMaskFor) {
        op = this.source.dragSourceOperationMaskFor(target, this) ;
      } else op = SC.DRAG_ANY ;

      // now, let's see if the target will accept the drag.  If it does not respond
      // to dragEntered, then assume NO drag opts.
      if ((op != SC.DRAG_NONE) && target && target.dragEntered) {
        op = op & target.dragEntered(this, evt) ;
      } else op = SC.DRAG_NONE ;

      // if DRAG_NONE, then look for the next parent that is a drop zone.
      if (op == SC.DRAG_NONE) target = this._findNextDropTarget(target) ;
    }

    // STEP 2: Refocus the drop target.
    // If a new drop target was found then this part of the method will exit the
    // last drag target and start the new one.
    if (target != last) {

      // if the new target does not match the last target, exit that target.
      if (last && last.dragExited) last.dragExited(this, evt) ;

      if (target && this.source && this.source.dragSourceOperationMaskFor) {
        op = this.source.dragSourceOperationMaskFor(target, this) ;
      } else op = SC.DRAG_ANY ;

      // save new op and set target to null if op = DRAG_NONE
      this.sourceDropOperations = op ;
      
      // now notify the new target, if there is one.  Save the allowed drop
      // operations as the logical AND between the ops allowed by the source
      // and target.
      if (target && target.dragEntered) {
        this.dropOperations = op & target.dragEntered(this, evt) ;
      } else this.dropOperations = SC.DRAG_NONE ;

      if (this.dropOperations == SC.DRAG_NONE) target = null ;
    
    // if nothing has changed, send dragUpdated
    } else {
      if (target && target.dragUpdated) target.dragUpdated(this, evt) ;
    }
     
    // notify source that the drag moved.
    if (this.source && this.source.dragDidMove) {
      this.source.dragDidMove(this, loc) ;
    }   
    
    this._lastTarget = target ;
  },
  
  /**
    @private
    
    Called when the mouse is released.  Performs any necessary cleanup and
    executes the drop target protocol to try to complete the drag operation.
  */
  mouseUp: function(evt) {
    var loc = Event.pointerLocation(evt) ;

    // try to have the drop target perform the drop...
    var target = this._lastTarget ;
    var op = this.dropOperations;
    
    if (target && target.prepareForDragOperation(op, this)) {
      op = target.performDragOperation(op, this) ;  
    } else {
      op = SC.DRAG_NONE;
    }
    
    // create cleanupFunc.  This function will be called at the end of this
    // function or after the ghostView slides back to its origin.
    var drag = this ;
    var cleanupFunc = function() {
      if (target) target.concludeDragOperation(op, this) ;  
      drag._destroyGhostView() ;
    };
    
    // notify drop target.
    if (target && target.dragEnded) target.dragEnded(this, evt) ;
    this._lastTarget = null ;
    
    // clean up ghost view.  if sldeBack is true, then do the animation.
    if ((op == SC.DRAG_NONE) && this.get('slideBack')) {
      var loc = this.dragView.convertFrameToView(this.dragView.get('origin'), null) ;
      this._ghostView.transitionTo(1.0, 
        "left: %@px; top: %@px".fmt(loc.x, loc.y), 
        { duration: 200, onComplete: cleanupFunc }) ;
        
    } else cleanupFunc() ;
    
    // notify the source that everything has completed.
    if (this.source && this.source.dragDidEnd) {
      this.source.dragDidEnd(this, loc, op) ;
    }
    
    this._dragInProgress = NO ; // required by autoscroll.
    
  },
  
  // ..........................................
  // PRIVATE PROPERTIES AND METHODS
  //
  
  _ghostViewClass: SC.View.extend({ 
    emptyElement: '<div class="sc-ghost-view"></div>'
  }),
  
  // positions the ghost view underneath the mouse with the initial offset
  // recorded by when the drag started.
  _positionGhostView: function(evt) {
    var loc = Event.pointerLocation(evt) ;
    loc.x -= this.ghostOffset.x ;
    loc.y -= this.ghostOffset.y ;
    loc = this._ghostView.convertFrameFromView(loc, null) ;
    this._ghostView.set('origin', loc) ;   
  },
  
  // this will create the ghostView and add it to the main HTML document.
  // it will also position it underneath the current mouse location.
  _createGhostView: function() {
    
    // create the elements by cloning the dragView.
    var el = this.dragView.rootElement.cloneNode(true) ;
    
    // create the ghost view instance add ghost class name.
    this._ghostView = this._ghostViewClass.viewFor(el) ;
    this._ghostView.owner = this ;
    this._ghostView.addClassName('sc-ghost-view') ;
    
    // add to bottom of main document body and to window.
    SC.window.appendChild(this._ghostView) ;
  },

  _destroyGhostView: function() {  
    if (this._ghostView) {
      this._ghostView.removeFromParent() ;
      this._ghostView = null ; // this will allow the GC to collect it.
    }
  },
  
  _ghostView: null,
  
  // Return an array of drop targets, sorted with any nested drop targets
  // at the top of the array.  The first time this method is called during
  // a drag, it will reconstruct this array using the current set of 
  // drop targets.  Afterwards it uses the cached set until the drop
  // completes.
  //
  // This means that if you change the view hierarchy of your drop targets
  // during a drag, it will probably be wrong.
  _getOrderedDropTargets: function() {
    if (this._cachedDropTargets) return this._cachedDropTargets ;
    var ret = [];
    
    // build array of drop targets
    var dt = SC.Drag._dropTargets ;
    for(var key in dt) {
      if (!dt.hasOwnProperty(key)) continue ;
      ret.push(dt[key]) ;      
    }

    // views must be sorted so that drop targets with the deepest nesting 
    // levels appear first in the array.  The getDepthFor().
    var depth = {} ;
    var getDepthFor = function(x) {
      if (!x) return 0 ;
      var guid = SC.guidFor(x);
      var ret = depth[guid];
      if (!ret) {
        ret = 1 ;
        while((x = x.parentNode) && (x !== SC.window)) {
          if (dt[SC.guidFor(x)] !== undefined) ret++ ;
        }
        depth[guid] = ret ;
      }
      return ret ;
    } ;

    ret.sort(function(a,b) {
      if (a===b) return 0;
      a = getDepthFor(a) ;
      b = getDepthFor(b) ;
      return (a > b) ? -1 : 1 ;
    }) ;

    this._cachedDropTargets = ret ;
    
    return ret ;
  },
  
  // This will search through the drop targets, looking for one in the 
  // target area.
  _findDropTarget: function(evt) {
    var dt = this._getOrderedDropTargets() ;
    var loc = Event.pointerLocation(evt) ;

    var ret = null ;
    for(var idx=0;idx<dt.length;idx++) {
      var t = dt[idx] ;

      if(!t.get('isVisibleInWindow')) continue ;
      
      // get frame, converted to view.
      var f = t.convertFrameToView(t.get('clippingFrame'), null) ;
      
      // check to see if loc is inside.  If so, then make this the drop
      // target unless there is a drop target and the current one 
      // is not deeper.
      if (SC.pointInRect(loc, f)) return t;
    } 
    return null ;
  },
  
  // Search the parent nodes of the target to find another view matching the 
  // drop target.  Returns null if no matching target is found.
  _findNextDropTarget: function(target) {
    while ((target = target.parentNode) && (target != SC.window)) {
      if (SC.Drag._dropTargets[target._guid]) return target ;
    }
    return null ;
  },
  
  // ............................................
  // AUTOSCROLLING
  //
  
  // Performs auto-scrolling for the drag.  This will only do anything if
  // the user keeps the mouse within a few pixels of one location for a little
  // while.
  //
  // Returns true if a scroll was performed
  _autoscroll: function(evt) {
  
    // If drag has ended, exit
    if (!this._dragInProgress) return ;
    
    // STEP 1: Find the first view that we can actually scroll.  This view 
    // must be:
    // - scrollable
    // - the mouse pointer must be within a scrolling hot zone
    // - there must be room left to scroll in that direction. 
    
    // NOTE: an event is passed only when called from mouseDragged
    var loc = (evt) ? Event.pointerLocation(evt) : this._lastMouseLocation ;
    if (!loc) return false ;
    this._lastMouseLocation = loc ;

    var view = this._findScrollableView(loc) ;
    
    // these will become either 1 or -1 to indicate scroll direction or 0 for no scroll.
    var verticalScroll, horizontalScroll ;
    var min, max, edge ;
    var scrollableView = null;

    while(view && !scrollableView) {
      
      // quick check...can we scroll this view right now?
      verticalScroll = view.get('hasVerticalScroller') ? 1 : 0;
      horizontalScroll = view.get('hasHorizontalScroller') ? 1 : 0;
      
      // at least one direction might be scrollable.  Collect some extra
      // info to investigate further.
      if ((verticalScroll != 0) || (horizontalScroll != 0)) {
        var f = view.convertFrameToView(view.get('frame'), null) ;
        var innerSize = view.get('innerFrame') ;
        var scrollFrame = view.get('scrollFrame') ;
      }
      
      
      if (verticalScroll != 0) {
        
        // bottom hotzone?
        max = SC.maxY(f); min = max - SC.DRAG_AUTOSCROLL_ZONE_THICKNESS ; 
        edge = SC.maxY(scrollFrame) ;
        
        if ((edge >= innerSize.height) && (loc.y >= min) && (loc.y <= max)) {
          verticalScroll = 1 ;
          
        // no...how about top hotzone?
        } else {
          min = SC.minY(f); max = min + SC.DRAG_AUTOSCROLL_ZONE_THICKNESS ;
          edge = SC.minY(scrollFrame) ;
          if ((edge <= innerSize.height) && (loc.y >= min) && (loc.y <= max)) {
            verticalScroll = -1 ;
          
          // no, ok don't scroll vertical
          } else verticalScroll = 0 ;
        }
      }

      if (horizontalScroll != 0) {
        // right hotzone?
        max = SC.maxX(f); min = max - SC.DRAG_AUTOSCROLL_ZONE_THICKNESS ; 
        edge = SC.maxX(scrollFrame) ;
        if ((edge >= innerSize.width) && (loc.x >= min) && (loc.x <= max)) {
          horizontalScroll = 1 ;
          
        // no...how about left hotzone?
        } else {
          min = SC.minY(f); max = min + SC.DRAG_AUTOSCROLL_ZONE_THICKNESS ;
          edge = SC.minY(scrollFrame) ;
          if ((edge <= innerSize.width) && (loc.x >= min) && (loc.x <= max)) {
            horizontalScroll = -1 ;
          
          // no, ok don't scroll vertical
          } else horizontalScroll = 0 ;
        }
      }

      // if we can scroll, then set this.
      if ((verticalScroll != 0) || (horizontalScroll != 0)) {
        scrollableView = view ;
      } else view = this._findNextScrollableView(view) ;
    }

    
    // STEP 2: Only scroll if the user remains within the hot-zone for a period of
    // time
    if (scrollableView && (this._lastScrollableView == scrollableView)) {
      if ((Date.now() - this._hotzoneStartTime) > 100) {
        this._horizontalScrollAmount *= 1.05 ;
        this._verticalScrollAmount *= 1.05 ;
      }
      
    // otherwise, reset everything and disallow scroll
    } else {
      this._lastScrollableView = scrollableView ;
      this._horizontalScrollAmount = 15 ;
      this._verticalScrollAmount = 15 ;
      this._hotzoneStartTime = (scrollableView) ? Date.now() : null ;
      
      horizontalScroll = verticalScroll = 0 ;
    }
    
    // STEP 3: Scroll!
    if (scrollableView && ((horizontalScroll != 0) || (verticalScroll != 0))) {
      var scroll = { 
        x: horizontalScroll * this._horizontalScrollAmount,
        y: verticalScroll * this._verticalScrollAmount 
      } ;

      scrollableView.scrollBy(scroll) ;
      
    }

    // If a scrollable view was found, then reschedule
    if (scrollableView) {
      this.invokeLater('_autoscroll', 100, null);
      return true ;
    } else return false ;
  },

  // Returns an array of scrollable views, sorted with nested scrollable
  // views at the top of the array.  The first time this method is called
  // during a drag, it will reconstrut this array using the current ste of
  // scrollable views.  Afterwards it uses the cached set until the drop
  // completes.
  _scrollableViews: function() {
    if (this._cachedScrollableView) return this._cachedScrollableView ;
    var ret = [];
    
    // build array of drop targets
    var dt = SC.Drag._scrollableViews ;
    for(var key in dt) {
      if (!dt.hasOwnProperty(key)) continue ;
      ret.push(dt[key]) ;      
    }
    
    // now resort.  This custom function will sort nested drop targets
    // at the start of the list.
    ret = ret.sort(function(a,b) {
      var view = a;
      while((view = view.parentNode) && (view != SC.window)) {
        if (b == view) return -1 ;
      }
      return 1; 
    }) ;

    this._cachedScrollableView = ret ;
    
    return ret ;
  },
  
  // This will search through the scrollable views, looking for one in the 
  // target area.
  _findScrollableView: function(loc) {
    var dt = this._scrollableViews() ;

    var ret = null ;
    for(var idx=0;idx<dt.length;idx++) {
      var t = dt[idx] ;

      if(!t.get('isVisibleInWindow')) continue ;
      
      // get frame, converted to view.
      var f = t.convertFrameToView(t.get('frame'), null) ;
      
      // check to see if loc is inside.  
      if (SC.pointInRect(loc, f)) return t;
    } 
    return null ;
  },
  
  // Search the parent nodes of the target to find another scrollable view.
  // return null if none is found.
  _findNextScrollableView: function(view) {
    while ((view = view.parentNode) && (view != SC.window)) {
      if (SC.Drag._scrollableViews[view._guid]) return view ;
    }
    return null ;
  }  
    
  
}) ;

SC.Drag.mixin(
/** @scope SC.Drag */ {
  
   
  /**  
   This is the method you use to initiate a new drag.  See class documentation 
   for more info on the options taken by this method.
   
   @params {Hash} ops a hash of options.  See documentation above.
   
  */
  start: function(ops) {
    var ret = this.create(ops) ;
    ret.startDrag() ;
    return ret ;
  },
  
  _dropTargets: {},
  _scrollableViews: {},
  
  /**
    Register the view object as a scrollable view.  These views will auto-scroll
    during a drag.
  */
  addScrollableView: function(target) {
    this._scrollableViews[target._guid] = target ;  
  },

  /**
    Remove the view object as a scrollable view.  These views will auto-scroll
    during a drag.
  */
  removeScrollableView: function(target) {
    delete this._scrollableViews[target._guid] ;  
  },
  
  /**
    Register the view object as a drop target.
    
    This method is called automatically whenever a view is created with the
    isDropTarget property set to YES.  You generally will not need to call it
    yourself.
  */
  addDropTarget: function(target) {
    this._dropTargets[target._guid] = target ;
  },

  /**
    Remove a view from the list of drop targets.
    
    This method is called automatically whenever a view is removed from the 
    hierarchy.  You generally will not need to call it yourself.
  */
  removeDropTarget: function(target) {
    delete this._dropTargets[target._guid] ;
  },

  /**
    Convenience method to turn a operation mask into a descriptive string.
  */
  inspectOperation: function(op) {
    var ret = [] ;
    if (op === SC.DRAG_NONE) {
      ret = ['DRAG_NONE'];
    } else if (op === SC.DRAG_ANY) {
      ret = ['DRAG_ANY'] ;
    } else {
      if (op & SC.DRAG_LINK) {
        ret.push('DRAG_LINK') ;
      }

      if (op & SC.DRAG_COPY) {
        ret.push('DRAG_COPY') ;
      }

      if (op & SC.DRAG_MOVE) {
        ret.push('DRAG_MOVE') ;
      }
    }
    return ret.join('|') ;
  }

});

/* End ------------------------------------------------------- drag/drag.js*/

/* Start ----------------------------------------------------- drag/drag_data_source.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('drag/drag') ;


/**
  @namespace

  This mixin can be used to implement a dynamic data source for a drag 
  operation.  You can return a set of allowed data types and then the 
  method will be used to actually get data in that format when requested.
*/
SC.DragDataSource = {

  /**
    Implement this property as an array of data types you want to support
    for drag operations.
  */
  dragDataTypes: [],

  /**
    Implement this method to return the data in the format passed.  Return
    null if the requested data type cannot be generated.
  
    @param {Object} dataType The proposed dataType to return.  This will 
      always be one of the data types declared in dragDataTypes.
    
    @param {SC.Drag} drag The Drag instance managing this drag.
    
    @returns The data object for the specified type
  
  */
  dragDataForType: function(dataType, drag) { return null; }
  
};




/* End ------------------------------------------------------- drag/drag_data_source.js*/

/* Start ----------------------------------------------------- foundation/unittest.js*/

// script.aculo.us unittest.js v1.8.0, Tue Nov 06 15:01:40 +0300 2007

// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//           (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
//           (c) 2005-2008 Michael Schuerig (http://www.schuerig.de/michael/)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/

// experimental, Firefox-only
Event.simulateMouse = function(element, eventName) {
  var options = Object.extend({
    pointerX: 0,
    pointerY: 0,
    buttons:  0,
    ctrlKey:  false,
    altKey:   false,
    shiftKey: false,
    metaKey:  false
  }, arguments[2] || {});
  var oEvent = document.createEvent("MouseEvents");
  oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
    options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
  
  if(this.mark) Element.remove(this.mark);
  this.mark = document.createElement('div');
  this.mark.appendChild(document.createTextNode(" "));
  document.body.appendChild(this.mark);
  this.mark.style.position = 'absolute';
  this.mark.style.top = options.pointerY + "px";
  this.mark.style.left = options.pointerX + "px";
  this.mark.style.width = "5px";
  this.mark.style.height = "5px;";
  this.mark.style.borderTop = "1px solid red;" ;
  this.mark.style.borderLeft = "1px solid red;" ;
  
  if(this.step)
    alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
  
  $(element).dispatchEvent(oEvent);
};

// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
// You need to downgrade to 1.0.4 for now to get this working
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
Event.simulateKey = function(element, eventName) {
  var options = Object.extend({
    ctrlKey: false,
    altKey: false,
    shiftKey: false,
    metaKey: false,
    keyCode: 0,
    charCode: 0
  }, arguments[2] || {});

  var oEvent = document.createEvent("KeyEvents");
  oEvent.initKeyEvent(eventName, true, true, window, 
    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
    options.keyCode, options.charCode );
  $(element).dispatchEvent(oEvent);
};

Event.simulateKeys = function(element, command) {
  for(var i=0; i<command.length; i++) {
    Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
  }
};

var Test = {} ;
Test.Unit = {};

// security exception workaround
Test.Unit.inspect = Object.inspect;

Test.Unit.Logger = Class.create();
Test.Unit.Logger.prototype = {
  initialize: function(log) {
    this.log = $(log);
    this.logId = this.log.id || 'x';
    if (this.log) {
      this._createLogTable();
    }
  },
  start: function(testName) {
    if (!this.log) return;
    this.testName = testName;
    this.lastLogLine = document.createElement('tr');
    this.statusCell = document.createElement('td');
    this.nameCell = document.createElement('td');
    this.nameCell.className = "nameCell";
    this.nameCell.appendChild(document.createTextNode(testName));
    this.messageCell = document.createElement('td');
    this.lastLogLine.appendChild(this.statusCell);
    this.lastLogLine.appendChild(this.nameCell);
    this.lastLogLine.appendChild(this.messageCell);
    this.loglines.appendChild(this.lastLogLine);
  },
  finish: function(status, summary) {
    if (!this.log) return;
    this.lastLogLine.className = status;
    this.statusCell.innerHTML = status;
    this.messageCell.innerHTML = this._toHTML(summary);
    this.addLinksToResults();
  },
  message: function(message) {
    if (!this.log) return;
    this.messageCell.innerHTML = this._toHTML(message);
  },
  summary: function(summary) {
    if (!this.log) return;
    this.logsummary.innerHTML = this._toHTML(summary);
  },
  _createLogTable: function() {
    this.log.innerHTML =
    '<div id="' + this.logId + '-logsummary" class="logsummary"></div>' +
    '<table id="' + this.logId + '-logtable">' +
    '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
    '<tbody id="' + this.logId + '-loglines"></tbody>' +
    '</table>';
    this.logsummary = $(this.logId + '-logsummary') ;
    this.loglines = $(this.logId + '-loglines');
  },
  _toHTML: function(txt) {
    return txt.escapeHTML().replace(/\n/g,"<br/>");
  },
  addLinksToResults: function(){ 
    $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
      td.title = "Run only this test" ;
      Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
    });
    $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
      td.title = "Run all tests" ;
      Event.observe(td, 'click', function(){ window.location.search = "";});
    });
  }
} ;

document.incrementQueuedTests = function(amt) {
  if (amt == undefined) amt = 1 ;
  var cnt = document.queuedTests || 0 ;
  document.queuedTests = cnt + amt ;
} ;


document.setTestStatus = function(stat) {
  var cur = document.testStatus || 'SUCCESS' ;
  document.testStatus = (cur == 'SUCCESS') ? stat : cur ;
};

Test.Unit.Runner = Class.create();
Test.Unit.Runner.prototype = {
  initialize: function(testcases) {
    this.options = Object.extend({
      testLog: 'testlog'
    }, arguments[1] || {});
    this.options.resultsURL = this.parseResultsURLQueryParameter();
    this.options.tests      = this.parseTestsQueryParameter();

    if (this.options.testLog) {
      var testLogElement = $(this.options.testLog) || null;
      
      // if the testLog was not found, then build it.
      // if root log is not found, create and add it.
      var rootLog = $('test-log') ;
      if (!rootLog) {
        var el = document.createElement('div') ;
        el.id = 'test-log' ;
        $(el).addClassName('testlog') ;
        var body = document.getElementsByTagName('body')[0] ;
        body.insertBefore(el, body.firstChild) ;
        rootLog = el ;
      }

      if (rootLog) {
        var el = document.createElement('div') ;
        el.id = this.options.testLog ;
        rootLog.appendChild(el) ;
        testLogElement = el ;
      }
      
      this.options.testLog = testLogElement ;
      
    }

    if(this.options.tests) {
      this.tests = [];
      for(var i = 0; i < this.options.tests.length; i++) {
        if(/^test/.test(this.options.tests[i])) {
          this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
        }
      }
    } else {
      if (this.options.test) {
        this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
      } else {
        this.tests = [];
        for(var testcase in testcases) {
          if(/^test/.test(testcase)) {
            this.tests.push(
               new Test.Unit.Testcase(
                 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
                 testcases[testcase], testcases["setup"], testcases["teardown"]
               ));
          }
        }
      }
    }
    this.currentTest = 0;
    this.logger = new Test.Unit.Logger(this.options.testLog);
    document.incrementQueuedTests(1) ;
    setTimeout(this.runTests.bind(this), 1000);
  },
  parseResultsURLQueryParameter: function() {
    return window.location.search.parseQuery()["resultsURL"];
  },
  parseTestsQueryParameter: function(){
    if (window.location.search.parseQuery()["tests"]){
        return window.location.search.parseQuery()["tests"].split(',');
    };
  },
  // Returns:
  //  "ERROR" if there was an error,
  //  "FAILURE" if there was a failure, or
  //  "SUCCESS" if there was neither
  getResult: function() {
    var hasFailure = false;
    for(var i=0;i<this.tests.length;i++) {
      if (this.tests[i].errors > 0) {
        return "ERROR";
      }
      if (this.tests[i].failures > 0) {
        hasFailure = true;
      }
    }
    if (hasFailure) {
      return "FAILURE";
    } else {
      return "SUCCESS";
    }
  },
  postResults: function() {
    if (this.options.resultsURL) {
      new Ajax.Request(this.options.resultsURL, 
        { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
    }
  },
  runTests: function() {
    var test = this.tests[this.currentTest];
    if (!test) {
      // finished!
      this.postResults();
      this.logger.summary(this.summary());
      document.incrementQueuedTests(-1) ;
      document.setTestStatus(this.getResult()) ;
      return;
    }
    if(!test.isWaiting) {
      this.logger.start(test.name);
    }
    test.run();
    if(test.isWaiting) {
      this.logger.message("Waiting for " + test.timeToWait + "ms");
      setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
    } else {
      this.logger.finish(test.status(), test.summary());
      this.currentTest++;
      // tail recursive, hopefully the browser will skip the stackframe
      this.runTests();
    }
  },
  summary: function() {
    var assertions = 0;
    var failures = 0;
    var errors = 0;
    var messages = [];
    for(var i=0;i<this.tests.length;i++) {
      assertions +=   this.tests[i].assertions;
      failures   +=   this.tests[i].failures;
      errors     +=   this.tests[i].errors;
    }
    return (
      (this.options.context ? this.options.context + ': ': '') + 
      this.tests.length + " tests, " + 
      assertions + " assertions, " + 
      failures   + " failures, " +
      errors     + " errors");
  }
} ;

Test.Unit.Assertions = Class.create();
Test.Unit.Assertions.prototype = {
  initialize: function() {
    this.assertions = 0;
    this.failures   = 0;
    this.errors     = 0;
    this.messages   = [];
  },
  summary: function() {
    return (
      this.assertions + " assertions, " + 
      this.failures   + " failures, " +
      this.errors     + " errors" + "\n" +
      this.messages.join("\n"));
  },
  pass: function() {
    this.assertions++;
  },
  fail: function(message) {
    this.failures++;
    this.messages.push("Failure: " + message);
  },
  info: function(message) {
    this.messages.push("Info: " + message);
  },
  error: function(error) {
    this.errors++;
    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
  },
  status: function() {
    if (this.failures > 0) return 'failed';
    if (this.errors > 0) return 'error';
    return 'passed';
  },
  assert: function(expression) {
    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
    try { expression ? this.pass() : 
      this.fail(message); }
    catch(e) { this.error(e); }
  },
  assertEqual: function(expected, actual) {
    var message = arguments[2] || "assertEqual";
    try { (expected == actual) ? this.pass() :
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
        '", actual "' + Test.Unit.inspect(actual) + '"'); }
    catch(e) { this.error(e); }
  },
  assertInspect: function(expected, actual) {
    var message = arguments[2] || "assertInspect";
    try { (expected == actual.inspect()) ? this.pass() :
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
        '", actual "' + Test.Unit.inspect(actual) + '"'); }
    catch(e) { this.error(e); }
  },
  assertEnumEqual: function(expected, actual) {
    var message = arguments[2] || "assertEnumEqual";
    try { $A(expected).length == $A(actual).length && 
      expected.zip(actual).all(function(pair) { return pair[0] == pair[1]; }) ?
        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
          ', actual ' + Test.Unit.inspect(actual)); }
    catch(e) { this.error(e); }
  },
  assertNotEqual: function(expected, actual) {
    var message = arguments[2] || "assertNotEqual";
    try { (expected != actual) ? this.pass() : 
      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
    catch(e) { this.error(e); }
  },
  assertIdentical: function(expected, actual) { 
    var message = arguments[2] || "assertIdentical"; 
    try { (expected === actual) ? this.pass() : 
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
    catch(e) { this.error(e); } 
  },
  assertNotIdentical: function(expected, actual) { 
    var message = arguments[2] || "assertNotIdentical"; 
    try { !(expected === actual) ? this.pass() : 
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
    catch(e) { this.error(e); } 
  },
  assertNull: function(obj) {
    var message = arguments[1] || 'assertNull' ;
    try { (obj==null) ? this.pass() : 
      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
    catch(e) { this.error(e); }
  },
  assertMatch: function(expected, actual) {
    var message = arguments[2] || 'assertMatch';
    var regex = new RegExp(expected);
    try { (regex.exec(actual)) ? this.pass() :
      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
    catch(e) { this.error(e); }
  },
  assertHidden: function(element) {
    var message = arguments[1] || 'assertHidden';
    this.assertEqual("none", element.style.display, message);
  },
  assertNotNull: function(object) {
    var message = arguments[1] || 'assertNotNull';
    this.assert(object != null, message);
  },
  assertType: function(expected, actual) {
    var message = arguments[2] || 'assertType';
    try { 
      (actual.constructor == expected) ? this.pass() : 
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
        '", actual "' + (actual.constructor) + '"'); }
    catch(e) { this.error(e); }
  },
  assertNotOfType: function(expected, actual) {
    var message = arguments[2] || 'assertNotOfType';
    try { 
      (actual.constructor != expected) ? this.pass() : 
      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
        '", actual "' + (actual.constructor) + '"'); }
    catch(e) { this.error(e); }
  },
  assertInstanceOf: function(expected, actual) {
    var message = arguments[2] || 'assertInstanceOf';
    try { 
      (actual instanceof expected) ? this.pass() : 
      this.fail(message + ": object was not an instance of the expected type"); }
    catch(e) { this.error(e); } 
  },
  assertNotInstanceOf: function(expected, actual) {
    var message = arguments[2] || 'assertNotInstanceOf';
    try { 
      !(actual instanceof expected) ? this.pass() : 
      this.fail(message + ": object was an instance of the not expected type"); }
    catch(e) { this.error(e); } 
  },
  assertRespondsTo: function(method, obj) {
    var message = arguments[2] || 'assertRespondsTo';
    try {
      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
      this.fail(message + ": object doesn't respond to [" + method + "]"); }
    catch(e) { this.error(e); }
  },
  assertReturnsTrue: function(method, obj) {
    var message = arguments[2] || 'assertReturnsTrue';
    try {
      var m = obj[method];
      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
      m() ? this.pass() : 
      this.fail(message + ": method returned false"); }
    catch(e) { this.error(e); }
  },
  assertReturnsFalse: function(method, obj) {
    var message = arguments[2] || 'assertReturnsFalse';
    try {
      var m = obj[method];
      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
      !m() ? this.pass() : 
      this.fail(message + ": method returned true"); }
    catch(e) { this.error(e); }
  },
  assertRaise: function(exceptionName, method) {
    var message = arguments[2] || 'assertRaise';
    try { 
      method();
      this.fail(message + ": exception expected but none was raised"); }
    catch(e) {
      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
    }
  },
  assertElementsMatch: function() {
    var expressions = $A(arguments), elements = $A(expressions.shift());
    if (elements.length != expressions.length) {
      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
      return false;
    }
    elements.zip(expressions).all(function(pair, index) {
      var element = $(pair.first()), expression = pair.last();
      if (element.match(expression)) return true;
      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
    }.bind(this)) && this.pass();
  },
  assertElementMatches: function(element, expression) {
    this.assertElementsMatch([element], expression);
  },
  benchmark: function(operation, iterations) {
    var startAt = new Date();
    (iterations || 1).times(operation);
    var timeTaken = ((new Date())-startAt);
    this.info((arguments[2] || 'Operation') + ' finished ' + 
       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
    return timeTaken;
  },
  _isVisible: function(element) {
    element = $(element);
    if(!element.parentNode) return true;
    this.assertNotNull(element);
    if(element.style && Element.getStyle(element, 'display') == 'none')
      return false;
    
    return this._isVisible(element.parentNode);
  },
  assertNotVisible: function(element) {
    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
  },
  assertVisible: function(element) {
    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
  },
  benchmark: function(operation, iterations) {
    var startAt = new Date();
    (iterations || 1).times(operation);
    var timeTaken = ((new Date())-startAt);
    this.info((arguments[2] || 'Operation') + ' finished ' + 
       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
    return timeTaken;
  }
} ;

Test.Unit.Testcase = Class.create();
Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
  initialize: function(name, test, setup, teardown) {
    Test.Unit.Assertions.prototype.initialize.bind(this)();
    this.name           = name;
    
    if(typeof test == 'string') {
      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
      this.test = function() {
        eval("with(this){\n"+test+"\n}");
      } ;
    } else {
      this.test = test || function() {};
    }
    
    this.setup          = setup || function() {};
    this.teardown       = teardown || function() {};
    this.isWaiting      = false;
    this.timeToWait     = 1000;
  },
  wait: function(time, nextPart) {
    this.isWaiting = true;
    this.test = nextPart;
    this.timeToWait = time;
  },
  run: function() {
    //try {
      //try {
        if (!this.isWaiting) this.setup.bind(this)();
        this.isWaiting = false;
        this.test.bind(this)();
      //} catch(e) {
      //  throw e; // required for IE compatibility.
      //} finally {
        if(!this.isWaiting) {
          this.teardown.bind(this)();
        }
      //}
    //}
    //catch(e) { this.error(e); }
  }
});

// *EXPERIMENTAL* BDD-style testing to please non-technical folk
// This draws many ideas from RSpec http://rspec.rubyforge.org/

Test.setupBDDExtensionMethods = function(){
  var METHODMAP = {
    shouldEqual:     'assertEqual',
    shouldNotEqual:  'assertNotEqual',
    shouldEqualEnum: 'assertEnumEqual',
    shouldBeA:       'assertType',
    shouldNotBeA:    'assertNotOfType',
    shouldBeAn:      'assertType',
    shouldNotBeAn:   'assertNotOfType',
    shouldBeNull:    'assertNull',
    shouldNotBeNull: 'assertNotNull',
    
    shouldBe:        'assertReturnsTrue',
    shouldNotBe:     'assertReturnsFalse',
    shouldRespondTo: 'assertRespondsTo'
  };
  var makeAssertion = function(assertion, args, object) { 
   	this[assertion].apply(this,(args || []).concat([object]));
  } ;
  
  Test.BDDMethods = {};   
  $H(METHODMAP).each(function(pair) { 
    Test.BDDMethods[pair.key] = function() { 
       var args = $A(arguments); 
       var scope = args.shift(); 
       makeAssertion.apply(scope, [pair.value, args, this]); }; 
  });
  
  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
    function(p){ Object.extend(p, Test.BDDMethods); }
  );
} ;

Test.context = function(name, spec, log){
  Test.setupBDDExtensionMethods();
  
  var compiledSpec = {};
  var titles = {};
  for(specName in spec) {
    switch(specName){
      case "setup":
      case "teardown":
        compiledSpec[specName] = spec[specName];
        break;
      default:
        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
        var body = spec[specName].toString().split('\n').slice(1);
        if(/^\s*\{/.test(body[0])) body = body.slice(1);
        body.pop();
        body = body.map(function(statement){ 
          return statement.strip() ;
        });
        compiledSpec[testName] = body.join('\n');
        titles[testName] = specName;
    }
  }
  
  // create a DIV for the log if needed.
  if (log === undefined) log = name.dasherize() ;
  
  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: (log || 'testlog'), context: name });
  
};

Test.Observer = function()
{
  return function() { 
    if (!arguments.callee.notified) arguments.callee.notified = 0;
    return ++arguments.callee.notified;
  };
} ;




/* End ------------------------------------------------------- foundation/unittest.js*/

/* Start ----------------------------------------------------- drag/drag_source.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('drag/drag') ;

/**
  @namespace

  The DataSource protocol is used to dynamically generate multiple types of
  data from a single object.  You must implement this protocol if you want to
  provide the data for a drag event.

*/
SC.DragSource = {

  /**  
  
    This method is called when the drag begins. You can use this to do any
    visual highlighting to indicate that the receive is the source of the 
    drag.
  
    @param {SC.Drag} drag The Drag instance managing this drag.
  
    @param {Point} atPoint  The point in *window* coordinates where the drag 
      began.  You can use convertOffsetFromView() to convert this to local 
      coordinates.
  */
  dragDidBegin: function(drag, atPoint) {},
  
  /**  
    This method is called when the drag ended. You can use this to do any
    cleanup.  The operation is the actual operation performed on the drag.
  
    @param {SC.Drag} drag The drag instance managing the drag.
  
    @param {Point} endPoint The point in WINDOW coordinates where the drag 
      ended. 
  
    @param {String} operation The drag operation that was performed. One of 
      SC.DRAG_COPY, SC.DRAG_MOVE, SC.DRAG_LINK, or SC.DRAG_NONE.
  
  */
  dragDidEnd: function(drag, endPoint, operation) {},
  
  /**
    This method is called whenever the drag image is moved.  This is
    similar to the dragUpdated() method called on drop targets.
  */
  dragDidMove: function(drag, newPoint) {},
  
  /**
    This method must be overridden for drag operations to be allowed. 
    Return a bitwise OR'd mask of the drag operations allowed on the
    specified target.  If you don't care about the target, just return a
    constant value.
  
    @param dropTarget The proposed target of the drop.
    @param drag The SC.Drag instance managing this drag.
  
  */
  dragSourceOperationMaskFor: function(dropTarget, drag) {
    return SC.DRAG_NONE ;
  },
  
  /**
    If this property is set to NO or is not implemented, then the user may
    modify the drag operation by changing the modifier keys they have 
    pressed.
  */
  ignoreModifierKeysWhileDragging: NO
    
} ;



/* End ------------------------------------------------------- drag/drag_source.js*/

/* Start ----------------------------------------------------- mixins/control.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

/**
  Indicates a value has a mixed state of both on and off.
*/
SC.MIXED_STATE = '__MIXED__' ;

/**
  @namespace

  A Control is a view that also implements some basic state functionality.
  Apply this mixin to any view that you want to have standard control
  functionality including showing a selected state, enabled state, focus
  state, etc.
  
  h2. About Values and Content
  
  Controls typically are used to represent a single value, such as a number,
  boolean or string.  The value a control is managing is typically stored in
  a "value" property.  You will typically use the value property when working
  with controls such as buttons and text fields in a form.
  
  An alternative way of working with a control is to use it to manage some
  specific aspect of a content object.  For example, you might use a label
  view control to display the "name" property of a Contact record.  This 
  approach is often necessary when using the control as part of a collection
  view.
  
  You can use the content-approach to work with a control by setting the 
  "content" and "contentValueKey" properties of the control.  The 
  "content" property is the content object you want to manage, while the 
  "contentValueKey" is the name of the property on the content object 
  you want the control to display.  
  
  The default implementation of the Control mixin will essentially map the
  contentValueKey of a content object to the value property of the 
  control.  Thus if you are writing a custom control yourself, you can simply
  work with the value property and the content object support will come for
  free.  Just write an observer for the value property and update your 
  view accordingly.
  
  If you are working with a control that needs to display multiple aspects
  of a single content object (for example showing an icon and label), then
  you can override the contentValueDidChange() method instead of observing
  the value property.  This method will be called anytime _any_ property 
  on the content object changes.  You should use this method to check the
  properties you care about on the content object and update your view if 
  anything you care about has changed.
  
  h2. Delegate Support
  
  Controls can optionally get the contentDisplayProperty from a 
  displayDelegate, if it is set.  The displayDelegate is often used to 
  delegate common display-related configurations such as which content value
  to show.  Anytime your control is shown as part of a collection view, the
  collection view will be automatically set as its displayDelegate.
  
*/
SC.Control = {
  
  initMixin: function() {
    this._contentObserver(); // setup content observing if needed.
    this.isSelectedObserver() ;
    this.isEnabledObserver() ;
    this.isFocusedObserver(); 
  },
  
  /** 
    Set to true when the item is selected. 
    
    This property is observable and bindable.
  */
  isSelected: false,
  isSelectedBindingDefault: SC.Binding.OneWayBool,

  /** 
    Set to true when the item is enabled. 
    
    This property is observable and bindable.
  */
  isEnabled: true,
  isEnabledBindingDefault: SC.Binding.OneWayBool,
  
  /**
    The value represented by this control.
    
    Most controls represent a value of some type, such as a number, string
    or image URL.  This property should hold that value.  It is bindable
    and observable.  Changing this value will immediately change the
    appearance of the control.  Likewise, editing the control 
    will immediately change this value.
    
    If instead of setting a single value on a control, you would like to 
    set a content object and have the control display a single property
    of that control, then you should use the content property instead.
  */
  value: null,
  
  /**
    The content object represented by this control.
    
    Often you need to use a control to display some single aspect of an 
    object, especially if you are using the control as an item view in a
    collection view.
    
    In those cases, you can set the content and contentValueKey for the
    control.  This will cause the control to observe the content object for
    changes to the value property and then set the value of that property 
    on the "value" property of this object.
    
    Note that unless you are using this control as part of a form or 
    collection view, then it would be better to instead bind the value of
    the control directly to a controller property.
  */
  content: null,
  
  /**
    The property on the content object that would want to represent the 
    value of this control.  This property should only be set before the
    content object is first set.  If you have a displayDelegate, then
    you can also use the contentValueKey of the displayDelegate.
  */
  contentValueKey: null,

  /**
    Invoked whenever any property on the content object changes.  
    
    The default implementation will update the value property of the view
    if the contentValueKey property has changed.  You can override this
    method to implement whatever additional changes you would like.
    
    The key will typically contain the name of the property that changed or 
    '*' if the content object itself has changed.  You should generally do
    a total reset of '*' is changed.
    
    @param {Object} target the content object
    @param {String} key the property that changes
  */
  contentPropertyDidChange: function(target, key) {
    if (!!this._contentValueKey && ((key == this._contentValueKey) || (key == '*'))) {
      var content = this.get('content') ;
      var value = (content) ? content.get(this._contentValueKey) : null;
      if (value != this._contentValue) {
        this._contentValue = value ;
        this.set('value', value) ;
      }
    }
  },

  /**
    Relays changes to the value back to the content object if you are using
    a content object.
    
    This observer is triggered whenever the value changes.  It will only do
    something if it finds you are using the content property and
    contentValueKey and the new value does not match the old value of the
    content object.  
    
    If you are using contentValueKey in some other way than typically
    implemented by this mixin, then you may want to override this method as
    well.
  */
  updateContentWithValueObserver: function() {
    if (!this._contentValueKey) return; // do nothing if disabled

    // get value.  return if value matches current content value.
    // this avoids infinite loops where setting the value from the content
    // in turns sets the content and so on.
    var value = this.get('value') ;
    if (value == this._contentValue) return ; 

    var content = this.get('content') ;
    if (!content) return; // do nothing if no content.
    
    // passed all of our checks, update the content (and the _contentValue
    // to avoid infinite loops)
    this._contentValue = value ;
    content.set(this._contentValueKey, value) ;
    
  }.observes('value'),
  
  /**
    Default observer for selected state changes
    
    The default will simply add either a "mixed" or "sel" class name to the
    root element of your view based on the state. You can override this with
    your own behavior if you prefer.
  */
  isSelectedObserver: function() {
    var sel = this.get('isSelected') ;
    this.setClassName('mixed', sel == SC.MIXED_STATE) ;
    this.setClassName('sel', sel && (sel != SC.MIXED_STATE)) ;
  }.observes('isSelected'),
  
  /**
    Default observer for the isEnabled state.
    
    The default will simply add or remove a "disabled" class name to the root 
    element of your view based on the state.  You can override this with your
    own behavior if you prefer.
  */
  isEnabledObserver: function() {
    var disabled = !this.get('isEnabled') ;
    this.setClassName('disabled', disabled);

    // set disabled attr as well if relevant
    if (this.rootElement && (this.rootElement.disabled !== undefined) && (this.rootElement.disabled != disabled)) {
      this.rootElement.disabled = disabled ;
    }
  }.observes('isEnabled'),

  /**
    Default observer for the isFirstResponder state.
    
    The default will add or remove a "focus" class name ot the root element
    of your view based on the state.  You can override this with your own
    behavior if you prefer.
  */
  isFocusedObserver: function() {
    this.setClassName('focus', !!this.get('isFirstResponder')) ;
  }.observes('isFirstResponder'),
  
  // This should be null so that if content is also null, the
  // _contentObserver won't do anything on init.
  _content: null,
  
  /** @private
    Observes when a content object has changed and handles notifying 
    changes to the value of the content object.
  */
  _contentObserver: function() {
    var content = this.get('content') ;
    if (this._content == content) return; // nothing changed
    
    // create bound observer function
    if (!this._boundContentPropertyDidChangeObserver) {
      this._boundContentPropertyDidChangeObserver = this.contentPropertyDidChange.bind(this) ;
    }
    var f = this._boundContentPropertyDidChangeObserver ;

    // remove an observer from the old content if necessary
    if (this._content && this._content.removeObserver) {
      this._content.removeObserver('*', f) ;
    }

    // cache for future use
    var del = this.displayDelegate ;
    this._contentValueKey = this.getDelegateProperty(del, 'contentValueKey');

    
    // add observer to new content if necessary.
    this._content = content ;
    if (this._content && this._content.addObserver) {
      this._content.addObserver('*', f) ;
    }
    
    // notify that value did change.
    this.contentPropertyDidChange(this._content, '*') ;
    
  }.observes('content')
      
};


/* End ------------------------------------------------------- mixins/control.js*/

/* Start ----------------------------------------------------- mixins/validatable.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

/**
  @namespace

  Views that include the Validatable mixin can be used with validators to 
  ensure their values are valid.  
  
*/
SC.Validatable = {
  
  initMixin: function() {
    this._validatorObserver() ;
  },
  
  /**
    The validator for this field.  
  
    Set to a validator class or instance.  If this points to a class, it will 
    be instantiated when the validator is first used.
  */
  validator: null,

  /**
    This property must return the human readable name you want used when 
    describing an error condition.  For example, if set this property to
    "Your Email", then the returned error string might be something like
    "Your Email is not valid".
    
    You can return a loc string here if you like.  It will be localized when
    it is placed into the error string.
  */
  errorLabel: null,

  /**
    YES if the receiver is currently valid.
    
    This property watches the value property by default.  You can override
    this property if you want to use some other method to calculate the
    current valid state.
    
    @field
  */
  isValid: function() { 
    return $type(this.get('value')) != T_ERROR; 
  }.property('value'),
  
  /**
    The form that the view belongs to.  May be null if the view does not 
    belong to a form.  This property is usually set automatically by an 
    owner form view.
  */
  ownerForm: null,
  
  /**
    Attempts to validate the receiver. 
    
    Runs the validator and returns SC.Validator.OK, SC.Validator.NO_CHANGE,
    or an error object.  If no validator is installed, this method will
    always return SC.Validator.OK.

    @param {Boolean} partialChange YES if this is a partial edit.
    @returns SC.Validator.OK, error, or SC.Validator.NO_CHANGE
  */
  performValidate: function(partialChange) {
    var ret = SC.Validator.OK ;

    if (this._validator) {
      var form = this.get('ownerForm') ;
      if (partialChange) {
        ret = this._validator.validatePartial(form,this) ;

        // if the partial returned NO_CHANGE, then check to see if the 
        // field is valid anyway.  If it is not valid, then don't update the
        // value.  This way the user can have partially constructed values 
        // without the validator trying to convert it to an object.
        if ((ret == SC.Validator.NO_CHANGE) && (this._validator.validateChange(form, this) == SC.Validator.OK)) {
          ret = SC.Validator.OK; 
        }
      } else ret = this._validator.validateChange(form, this) ;
    }
    return ret ;
  },

  /**
    Runs validateSubmit.  You should use this in your implementation of 
    validateSubmit.  If no validator is installed, this always returns
    SC.Validator.OK
  */
  performValidateSubmit: function() {
    return (this._validator) ? this._validator.validateSubmit(this.get('ownerForm'), this) : SC.Validator.OK;
  },
  
  /**
    Invoked by the owner form just before submission.  Override with your 
    own method to commit any final changes after you perform validation. 
    
    The default implementation simply calls performValidateSubmit() and 
    returns that value.
  */
  validateSubmit: function() { return this.performValidateSubmit(); },
  
  /**
    Convert the field value string into an object.
    
    This method will call the validators objectForFieldValue if it exists.
    
    @param fieldValue the raw value from the field.
    @returns converted object
  */
  objectForFieldValue: function(fieldValue) {
    return (this._validator) ? this._validator.objectForFieldValue(fieldValue, this.get('ownerForm'), this) : fieldValue ;
  },
  
  /**
    Convert the object into a field value.
    
    This method will call the validator's fieldValueForObject if it exists.
    
    @param object the objec to convert
    @returns converted field value
  */
  fieldValueForObject: function(object) {
    return (this._validator) ? this._validator.fieldValueForObject(object, this.get('ownerForm'), this) : object ;
  },
  
  /**
    Default observer for isValid property.
    
    The default implementation will add/remove a valid class name to the
    root element of your view.
  */
  isValidObserver: function() {
    var invalid = !this.get('isValid') ;
    this.setClassName('invalid', invalid) ;
  }.observes('isValid'),
  
  // invoked whenever the attached validator changes.
  _validatorObserver: function() {
    var form = this.get('ownerForm') ;
    var val = SC.Validator.findFor(form, this, this.get('validator')) ;
    if (val != this._validator) {
      if (this._validator) this._validator.detachFrom(form, this) ;
      this._validator = val;
      if (this._validator) this._validator.attachTo(form, this) ;
    }  
  }.observes('validator', 'ownerForm')
      
};


/* End ------------------------------------------------------- mixins/validatable.js*/

/* Start ----------------------------------------------------- views/field/field.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view') ;
require('mixins/control') ;
require('mixins/validatable') ;

/**
  @class

  Generic base class for working with views that depend on the "input" HTML 
  tag such as text fields.
  
  You can work with subclasses of SC.FieldView or extend this class with your
  own values as well.  Unlike most other HTML elements, web browsers have 
  built-in support for editing the value of an input field.  This class
  handles blending the browser input methods with the editing and events 
  handling provided by the framework.  
  
  @extends SC.View
  @extends SC.Control
  @extends SC.Validatable
  @author Charles Jolley
  @version 1.0
*/
SC.FieldView = SC.View.extend(SC.Control, SC.Validatable,
/** @scope SC.FieldView.prototype */ {

  // PUBLIC PROPERTIES
  // You generally do not need to override these properties though you might
  // change them....
  
  /**
    The value of the field.  
    
    The form view will pick up whatever value is published here.  Generally 
    you do not need to observe this property directly.  Instead you should 
    override setFieldValue(), getFieldValue() and the error property.
    
    @field
  */
  value: null,

  /**
    name of key this field should display as part of a form.
  
    If you add a field as part of an SC.FormView, then the form view will 
    automatically bind the field to the property key you name here on the 
    content object.
  */
  fieldKey: null,
  
  /**
    The human readable label you want shown for errors.  May be a loc string.
  
    If your field fails validation, then this is the name that will be shown
    in the error explanation.
  */  
  fieldLabel: null,
  
  /**
    The human readable label for this field for use in error strings.  
    
    This is either the fieldLabel or a humanized form of the fieldKey.
    
    @field
  */  
  errorLabel: function() {
    var ret = this.get('fieldLabel') ;
    if (ret) return ret ;
    
    // if field label is not provided, compute something...
    var fk = this.get('fieldKey') ;
    var def = (fk || '').humanize().capitalize() ;  
    return "FieldKey.%@".fmt(fk).locWithDefault(def) ; // localize if poss.
  }.property('fieldLabel','fieldKey'),
  
  /**
    The raw value of the field, ignoring validation.  
  
    You generally should not override this.  Instead override setFieldValue() 
    and getFieldValue().
  
    @field
  */  
  fieldValue: function(key,value) {
    if (value !== undefined) this._setFieldValue(value) ;
    return this._getFieldValue() ;
  }.property('value'),

  // ACTIONS
  // You generally do not need to override these but they may be used.

  /**
    Called to perform validation on the field just before the form 
    is submitted.  If you have a validator attached, this will get the
    validators.
  */  
  validateSubmit: function() {
    var ret = this.performValidateSubmit() ;
    // save the value if needed
    var value = ($ok(ret)) ? this._getFieldValue() : ret ;
    if (value != this.get('value')) this.set('value', value) ;
    return ret ;
  },
  
  // OVERRIDE IN YOUR SUBCLASS
  // Override these primitives in your subclass as required.
  
  /**
    Override to set the actual value of the field.
    
    The default implementations set the value on the new value.  The value
    will have already been converted to a field value using any validator.
    
    @param {Object} newValue the value to display.
  */
  setFieldValue: function(newValue) {
    if (this.rootElement.value != newValue) this.rootElement.value = newValue;
  },

  /**
    Override to retrieve the actual value of the field.
    
    The default implementation gets the value attribute of the rootElement.
  */
  getFieldValue: function() {
    return this.rootElement.value;
  },

  /**
    Call by your subclass anytime you want the view to pick up the current 
    value from the form and post it out. 
  
    @param partialChange (optional) YES if this is a partial change.
    @returns result of validation.
  */
  fieldValueDidChange: function(partialChange) {

    // get the field value and set it.
    // if ret is an error, use that instead of the field value.
    var ret = this.performValidate(partialChange) ;
    if (ret == SC.Validator.NO_CHANGE) return ret ;

    // if the validator says everything is OK, then in addition to posting
    // out the value, go ahead and pass the value back through itself.
    // This way if you have a formatter applied, it will reformat.
    //
    // Do this BEFORE we set the value so that the valueObserver will not
    // overreact.
    //
    var value = ($ok(ret)) ? this._getFieldValue() : ret ;
    if (!partialChange && $ok(ret)) this._setFieldValue(value) ;
    if (value != this.get('value')) this.set('value',value) ;
    return ret ;
  },

  /**
    Override to enable editing of this field.
    
    The default just sets the disabled property on the root element.
  */
  enableField: function() {
    this.rootElement.disabled = NO ;
  },

  /**
    Override to disable editing of the field
    
    The default just sets the disabled property on the root element.
  */
  disableField: function() {
    this.rootElement.disabled = YES ;
  },

  /**
    Overrides enabled observer to also call enableField()/disableField() 
    methods.
  */
  isEnabledObserver: function() {
    isEnabled = this.get('isEnabled') ;
    arguments.callee.base.apply(this, arguments);
    (isEnabled) ? this.enableField() : this.disableField(); 
  }.observes('isEnabled'),

  // PRIVATE SUPPORT METHODS
  //
  init: function() {
    arguments.callee.base.call(this) ;
    if (this.rootElement) this._setFieldValue(this.get('value')) ;
  },
  
  
  // called whenever the value is set on the object.  Will set the value
  // on the field if the value is changed.
  _valueObserver: function() {
    var value = this.get('value') ;
    var isError = $type(value) == T_ERROR ;
    if (!isError && (value != this._getFieldValue())) {
      this._setFieldValue(value) ;
    } 
  }.observes('value'),
  
  // these methods use the validator to conver the raw field value returned
  // by your subclass into an object and visa versa.
  _setFieldValue: function(newValue) {
    return this.setFieldValue(this.fieldValueForObject(newValue)) ;
  },
  
  _getFieldValue: function() {
    return this.objectForFieldValue(this.getFieldValue()) ;
  }
  
}) ;




/* End ------------------------------------------------------- views/field/field.js*/

/* Start ----------------------------------------------------- mixins/editable.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

/**
  @namespace

  The Editable mixin is a standard protocol used to activate keyboard editing 
  on views that are editable such as text fields, label views and item views.
  
  You should apply this mixin, or implement the methods, if you are
  designing an item view for a collection and you want to automatically
  trigger editing.
  
  h2. Using Editable Views
  
  To use a view that includes the Editable mixin, you simply call three
  methods on the view:
  
  - To begin editing, call beginEditing().  This will make the view first responder and allow the user to make changes to it.  If the view cannot begin editing for some reason, it will return NO.
  
  - If you want to cancel editing, you should try calling discardEditing().  This will cause the editor to discard its changed value and resign first responder.  Some editors do not support cancelling editing and will return NO.  If this is the case, you may optionally try calling commitEditing() instead to force the view to resign first responder, even though this will commit the changes.
  
  - If you want to end editing, while saving any changes that were made, try calling commitEditing().  This will cause the editor to validate and apply its changed value and resign first responder.  If the editor cannot validate its contents for some reason, it will return NO.  In this case you may optionally try calling discardEditing() instead to force the view to resign first responder, even though this will discard the changes.
  
  
  h2. Implementing an Editable View
  
  To implement a new view that is editable, you should implement the three
  methods defined below: beginEditing(), discardEditing(), and
  commitEditing().  If you already allow editing when your view becomes first
  responder and commit your changes when the view loses first responder status
  then you can simply apply this mixin and not override any methods.
  
  
  @since SproutCore 1.0
  
*/
SC.Editable = {  

  /**
    Indicates whether a view is editable or not.  You can optionally 
    implement the methods in this mixin to disallow editing is isEditable is
    NO.
  */
  isEditable: NO,
  
  /**
    Indicates whether editing is currently in progress.  The methods you
    implement should generally up this property as appropriate when you 
    begin and end editing.
  */
  isEditing: NO,
  
  /**
    Begins editing on the view.
    
    This method is called by other views when they want you to begin editing.
    You should write this method to become first responder, perform any 
    additional setup needed to begin editing and then return YES.
    
    If for some reason you do not want to allow editing right now, you can
    also return NO.  If your view is already editing, then you should not
    restart editing again but just return YES.

    The default implementation checks to see if editing is allowed, then
    becomes first responder and updates the isEditing property if appropriate.
    Generally you will want to replace this method with your own 
    implementation and not call the default.
    
    @returns {Boolean} YES if editing began or is in progress, NO otherwise
  */
  beginEditing: function() {
    if (!this.get('isEditable')) return NO ;
    if (this.get('isEditing')) return YES ;
    
    // begin editing
    this.set('isEditing', YES) ;
    this.becomeFirstResponder() ;
    return YES ;
  },
  
  /**
    Ends editing on the view, discarding any changes that were made to the
    view value in the meantime.
    
    This method is called by other views when they want to cancel editing
    that began earlier.  When this method is called you should resign first
    responder, restore the original value of the view and return YES.
    
    If your view cannot revert back to its original state before editing began
    then you can implement this method to simply return NO.  A properly
    implemented client may try to call commitEditing() instead to force your
    view to end editing anyway.
    
    If this method is called on a view that is not currently editing, you
    should always just return YES.
    
    The default implementation does not support discarding changes and always
    returns NO.
    
    @returns {Boolean} YES if changes were discarded and editing ended.
  */
  discardEditing: function() {
    // if we are not editing, return YES, otherwise NO.
    return !this.get('isEditing') ;
  },
  
  /**
    Ends editing on the view, committing any changes that were made to the 
    view value in the meantime.
    
    This method is called by other views when they want to end editing, 
    saving any changes that were made to the view in the meantime.  When this
    method is called you should resign first responder, save the latest
    value of the view and return YES.
    
    If your view cannot save the current state of the view for some reason 
    (for example if validation fails), then you should return NO.  Properly
    implemented clients may then try to call discardEditing() to force your
    view to resign first responder anyway.
    
    Some views apply changes to their value immediately during an edit instead
    of waiting for the view to end editing.  If this is the case, you should
    still implement commitEditing but you simply may not save any value 
    changes.
  
    If this method is called on a view that is not currently editing, you
    should always just reutrn YES.
    
    The default implementation sets isEditing to NO, resigns first responder
    and returns YES.
    
    @returns {Boolean} YES if changes were discarded and editing ended.
  */
  commitEditing: function() {
    if (!this.get('isEditing')) return YES;
    this.set('isEditing', NO) ;
    this.resignFirstResponder() ;
    return YES ;
  }

} ;


/* End ------------------------------------------------------- mixins/editable.js*/

/* Start ----------------------------------------------------- views/field/text_field.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/field/field') ;
require('mixins/editable') ;

/**
  @class
  
  A text field is an input element with type "text".  This view adds support
  for hinted values, etc.
  
  @extends SC.FieldView
  @extends SC.Editable
  @author Charles Jolley
*/
SC.TextFieldView = SC.FieldView.extend(SC.Editable,
/** @scope SC.TextFieldView.prototype */ {
  
  emptyElement: '<input type="text" value="" />',

  // PROPERTIES

  /**
    The hint to display while the field is not active.  Can be a loc key.
  */  
  hint: null,
  
  /**
    automatically set to YES if the hint is currently showing.
  */
  isHintShowing: false,

  /**
    If YES then the text field is currently editing. 
  */
  isEditing: NO,

  // PRIVATE SUPPORT METHODS
  init: function() {
    
    // compatibility...
    if (this.hint == null) {
      this.hint = this.rootElement.getAttribute('hint') ;
    }

    if (this.validator == null) {
      this.validator = this.rootElement.getAttribute('validate') ;
    }

    arguments.callee.base.call(this) ;
    
    // observe important events for this field.
    var focusListener = this._fieldDidFocus.bindAsEventListener(this) ;
    Event.observe(this.rootElement, 'focus', focusListener) ;

    var blurListener = this._fieldDidBlur.bindAsEventListener(this) ;
    Event.observe(this.rootElement, 'blur', blurListener) ;

    this._updateFieldHint() ;
  },


  // FOCUS AND BLUR EVENTS -
  // These should be hooked into the firstResponder system..
  _fieldDidFocus: function() {
    if (!this._isFocused) {
      this._isFocused = true ;
      this.becomeFirstResponder() ;
    }
  },
  
  _fieldDidBlur: function() {
    if (this._isFocused) {
      this._isFocused = false ;
      this.resignFirstResponder() ;
    }
  },

  /**
    tied to the isEnabled state
  */
  acceptsFirstResponder: function() {
    return this.get('isEnabled');
  }.property('isEnabled'),
  
  // First Responder
  // When we become first responder, make sure the field gets focus and
  // the hint value is hidden if needed.

  // when we become first responder, focus the text field if needed and
  // hide the hint text.
  /** @private */
  didBecomeFirstResponder: function() {

    // focus the text field.
    if (!this._isFocused) {
      this._isFocused = true ;
      if (this.get('isVisibleInWindow')) {
        this.rootElement.focus();
		    this.invokeLater(this._selectRootElement, 1) ;
      }
    }

    // hide the hint text if it is showing.
    this._updateFieldHint() ;
  },

  // In IE, you can't modify functions on DOM elements so we need to wrap the 
  // call to select() like this.
  _selectRootElement: function() {
	this.rootElement.select() ;
  },

  // when we lose first responder, blur the text field if needed and show
  // the hint text if needed.
  /** @private */
  willLoseFirstResponder: function() {
    
    if (this._isFocused) {
      this._isFocused = false ;
      this._updateFieldHint() ;
      return this.rootElement.blur() ;
    } else {
      this._value = this.rootElement.value ;
      this.fieldValueDidChange() ;
      this._updateFieldHint() ;
      return true;
    }
  },
  
  _isFocused: false,
  
  _updateFieldHint: function() {
    
    // show the hint if:
    // - flag is true
    // - hint != null or empty.
    // - this._value = null or empty.
    var hint = this.get('hint') ;
    var showHint = !!(!this._isFocused && ((this._value == null) || this._value == '') && (hint)) ;
    
    this.setClassName('show-hint', showHint);
    this.rootElement.value = (showHint) ? hint : (this._value || '') ;
    this.set('isHintShowing', showHint);
  },
  
  // field value updates...
  /** @private */
  getFieldValue: function() {
    return this._value ;
  },
  
  /** @private */
  setFieldValue: function(value) {
    if (this._value == value) return ;
    this._value = value ;
    this._updateFieldHint() ;
  },
  
  /** @private */
  mouseDown: function(e)
  {
    e._stopWhenHandled = false;
    return false;
  },
  
  // trap key-press events and notify as needed.
  /** @private */
  keyDown: function(evt) {
    if (this._value != this.rootElement.value) {
      this._value = this.rootElement.value ;
      this.fieldValueDidChange(true) ;
    }
    
    return this.interpretKeyEvents(evt); // start bubbling key events...
    //return false;
  },
  
  /** @private */
  keyUp: function() {
    if (this._value != this.rootElement.value) {
      this._value = this.rootElement.value ;
      this.fieldValueDidChange(true) ;
    }
  },

  // if make a text field first responder before the view becomes visible,
  // then focus the text field when it does become visible.
  _focusOnVisible: function() {
    if (this.get('isVisibleInWindow') && this._isFocused) {
      this.rootElement.focus() ;
      
      if(SC.isIE()){
        var selector = function() {
          Element.select(arguments.callee.it);
        };
        selector.it = this.rootElement;
        setTimeout(selector,0.05);
      } else{
        this.rootElement.select.bind(this.rootElement).delay(0.05);  
      }
    }
  }.observes('isVisibleInWindow'),
  
  // THESE ARE DUMMY IMPLEMENTATIONS OF THE REPONDER METHODS FOR KEYBOARD
  // ACTIONS HANDLED BY THE BROWSER.  This avoids having the responder 
  // bubble up these items.

  /** @private */
  deleteBackward: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  deleteForward: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveLeft: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveRight: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveUp: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveDown: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveLeftAndModifySelection: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveRightAndModifySelection: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveUpAndModifySelection: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveDownAndModifySelection: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveToBeginningOfDocument: function(evt) { evt._stopWhenHandled = false; return true; },

  /** @private */
  moveToEndOfDocument: function(evt) { evt._stopWhenHandled = false; return true; },
  
  /** @private */
  selectAll: function(evt) { evt._stopWhenHandled = false; return true; }
  
}) ;


/* End ------------------------------------------------------- views/field/text_field.js*/

/* Start ----------------------------------------------------- views/field/textarea_field.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/field/text_field') ;

/**
  @class
  
  Manages a text area field.
  
  @extends SC.TextFieldView
  @author Skip Baney
  @version 1.0
*/
SC.TextareaFieldView = SC.TextFieldView.extend(
/** @scope SC.TextareaFieldView.prototype */ {
  
  emptyElement: '<textarea></textarea>',
  didBecomeFirstResponder: function() {
    if (this.get('isVisibleInWindow')) {
      this.rootElement.focus();
	    this.invokeLater(this._selectRootElement, 1) ;
    }
    // hide the hint text if it is showing.
    this._updateFieldHint() ;
  },

  insertNewline: function(evt) { evt._stopWhenHandled = false; return true; }

});


/* End ------------------------------------------------------- views/field/textarea_field.js*/

/* Start ----------------------------------------------------- mixins/inline_editor_delegate.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

/**
  @namespace
  
  The inline editor delegate receives notifications from the inline text
  editor before, during, and after the user completes inline editing.
  
  The inline editor delegate is used by views that work with the inline
  editor.  You may need to implement this protocol if you want to
  use the inline editor in your own custom views.
  
  @since SproutCore 1.0
*/
SC.InlineEditorDelegate = {
  
    /**
     This is a  classname you can apply to the inline editor field
     to configure it's styling, in addition to the the editor's 
     default style-cloning behavior.
      
      @property inlineEditorClassName {String} A class name to use with the inline editor.
    */
    inlineEditorClassName: "",
  
  
    /**
      Called just before the inline edit displays itself but after it has been 
      configured for display.  
      
      You can use this method to make last minute changes to the display of 
      the inline editor or to collect its value.
      
      @param inlineEditor {SC.InlineTextFieldView} The inline editor.
      @returns {void}
    */
    inlineEditorWillBeginEditing: function(inlineEditor) {},

    /**
      Called just after an inline editor displays itself.
      
      You can use this method to perform any hiding or other view changes
      you need to perform on your own view to make room for the new editor.
      
      Note tht editors are placed over the top of views in the page, not 
      inside of them from a DOM perspective.
      
      @param inlineEditor {SC.InlineTextFieldView} The inline editor.
      @returns {void}
    */
    inlineEditorDidBeginEditing: function(inlineEditor) {},
    
    /**
      Called just before an inline editor tries to end editing and hide 
      itself.
      
      You can use this method to control whether the inline editor will
      actually be allowed to end editing.  For example, you might disallow
      the editor to end editing if the new value fails validation.
      
      @param inlineEditor {SC.InlineTextFieldView} the inline editor
      @param finalValue {Object} the final value
      @returns {Boolean} YES to allow the editor to end editing.
    */
    inlineEditorShouldEndEditing: function(inlineEditor, finalValue) {
      return YES ;
    },
    
    /**
      Called just after the inline editor has ended editing. You can use this 
      method to save the final value of the inline editor and to perform any 
      other cleanup you need to do.
      
      @param inlineEditor {SC.InlineTextFieldView} the inline editor
      @param finalValue {Object} the final value
      @returns {void}
    */
    inlineEditorDidEndEditing: function(inlineEditor, finalValue) {}
};


/* End ------------------------------------------------------- mixins/inline_editor_delegate.js*/

/* Start ----------------------------------------------------- views/inline_text_field.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view') ;
require('mixins/delegate_support') ;
require('views/field/text_field') ;
require('views/field/textarea_field') ;
require('mixins/inline_editor_delegate');

/**
  @class
  
  The inline text editor is used to display an editable area for controls 
  that are not always editable such as label views and source list views.
  
  You generally will not use the inline editor directly but instead will
  invoke beginEditing() and endEditing() on the views yous are 
  editing. If you would like to use the inline editor for your own views, 
  you can do that also by using the editing API described here.
  
  h2. Using the Inline Editor in Your Own Views

  If you need to use the inline editor with custom views you have written,
  you will need to work with the class methods to begin, commit, and discard
  editing changes.
  
  h3. Starting the Editor
  
  The inline editor works by positioning itself over the top of your view 
  with the same offset, width, and font information.  As the user types, the
  field will automatically resize vertically to make room for the user's text.
  
  To activate the inline editor you must call beginEdition() with at least 
  the target view you want the editor to position itself on top of:
  
  {{{
    SC.InlineTextFieldView.beginEditing({
      target: view, validator: validator
    }) ;
  }}}

  You can pass a variety of options to this method to configure the inline
  editor behavior, including:

  - *frame* The editors initial frame in viewport coordinates (REQ)
  - *exampleElement* A DOM element to use when copying styles.
  - *delegate* Optional delegate to receive update notices.  If not passed, the target view will be treated as the delegate. (REQ)
  - *value* The initial value of the edit field.  If not passed, the value property of the target view will be used instead.
  - *multiline* If YES then the hitting return will add to the value instead of exiting the inline editor.
  - *selectedRange* The range of text that should be selected.  If omitted, then the insertion point will be placed at the end of the value.
  - *commitOnBlur* If YES then bluring will commit the value, otherwise it will discard the current value.  Defaults to YES.
  - *validator* Optional validator will be attached to the field.
  
  If the inline editor is currently in use elsewhere, it will automatically
  close itself over there and begin editing for your view instead.  The 
  editor expects your source view to implement the InlineTextFieldViewDelegate
  protocol.

  h2. Commiting or Discarding Changes
  
  Normally the editor will automatically commit or discard its changes 
  whenever the user exits the edit mode.  If you need to force the editor to
  end editing, you can do so by calling commitEditing() or discardEditing():
  
  {{{
    SC.InlineTextFieldView.commitEditing();
    SC.InlineTextFieldView.discardEditing();
  }}}
  
  Both methods will try to end the editing context and will call the 
  relevent delegate methods on the delegate you passed to beginEditing().
  
  Note that it is possible an editor may not be able to commit editing 
  changes because either the delegate disallowed it or because its validator
  failed.  In this case commitEditing() will return NO.  If you want to
  end editing anyway, you can discard the editing changes instead by calling
  discardEditing().  This method will generally suceed unless your delegate
  refuses it as well.
  
  @extends SC.View
  @extends SC.DelegateSupport
  @extends SC.InlineEditorDelegate
  @since SproutCore 1.0
*/
SC.InlineTextFieldView = SC.View.extend(SC.DelegateSupport, SC.InlineEditorDelegate,
/** @scope SC.InlineTextFieldView.prototype */ {

  /**
    Invoked by the class method to begin editing on an inline editor.
    
    You generally should call the class method beginEditing() instead of
    this one since it will make sure to create and use the shared editor
    instance.

    @params options {Hash} hash of options for editing
    @returns {Boolean} YES if editor began editing, NO if it failed.
  */
  beginEditing: function(options) {
    
    // end existing editing if necessary
    this.beginPropertyChanges();
    if (this.get('isEditing') && !this.blurEditor()) {
      this.endPropertyChanges();  return NO ;
    }

    this._optframe = options.frame ;
    this._exampleElement = options.exampleElement ;
    this._delegate = options.delegate ;

    if (!this._optframe || !this._delegate) {
      throw "At least frame and delegate options are required for inline editor";
    }
    
    this._originalValue = options.value || '' ;
    this._multiline = (options.multiline !== undefined) ? options.multiline : NO ;
    this._commitOnBlur =  (options.commitOnBlur !== undefined) ? options.commitOnBlur : YES ;

    // set field values
    var field = this.outlet('field') ;
    field.set('validator', options.validator) ;
    field.set('value', this._originalValue) ;
    field.set('selectedRange', options.selectedRange || { start: this._originalValue.length, length: 0 }) ;

    this.set('isEditing', YES) ;
    
    // add to window.
    SC.app.get("keyPane").appendChild(this) ;
    
    // get style for view.
    this.updateViewStyle() ;

    var del = this._delegate ;

    this._className = this.getDelegateProperty(del,"inlineEditorClassName");
    if(this._className && !this.hasClassName(this._className)) {
        this.setClassName(this._className,true);
      }
    
    this.invokeDelegateMethod(del, 'inlineEditorWillBeginEditing', this) ;
    this.resizeToFit(field.getFieldValue()) ;

    // allow notifications to go
    this.endPropertyChanges() ;
    
    // and become first responder
    this.field.becomeFirstResponder() ;

    this.invokeDelegateMethod(del, 'inlineEditorDidBeginEditing', this) ;
  },
  
  
  /**
    Tries to commit the current value of the field and end editing.  
    
    Do not use this method, use the class method instead.
  */
  commitEditing: function() {
    // try to validate field.  If it fails, return false.  
    var field = this.outlet('field') ;
    if (!$ok(field.validateSubmit())) return NO ;
    return this._endEditing(field.get('value')) ;
  },
  
  /**
    Tries to discard the current value of the field and end editing.
    
    Do not use this method, use the class method instead.
  */
  discardEditing: function() {
    return this._endEditing(this._originalValue) ;
  },
  
  /**
    Invoked whenever the editor loses (or should lose) first responder 
    status to commit or discard editing.
  */
  blurEditor: function() {
    if (!this.get('isEditing')) return YES ;
    return (this._commitOnBlur) ? this.commitEditing() : this.discardEditing();  
  },
  
  /** @private
    Called by commitEditing and discardEditing to actually end editing.

    @returns {Boolean} NO if editing did not exit
  */
  _endEditing: function(finalValue) {
    if (!this.get('isEditing')) return YES ;
    
    // get permission from the delegate.
    var del = this._delegate ;
    if (!this.invokeDelegateMethod(del, 'inlineEditorShouldEndEditing', this, finalValue)) return NO ; 

    // OK, we are allowed to end editing.  Notify delegate of final value
    // and clean up.
    this.invokeDelegateMethod(del, 'inlineEditorDidEndEditing', this, finalValue) ;

    // If the delegate set a class name, let's clean it up:
    if(this._className) this.setClassName(this._className, false);
    
    // cleanup cached values
    this._originalValue = this._delegate = this._exampleElement =  this._optframe = this._className = null ;
    this.set('isEditing', NO) ;

    // resign first responder if not done already.  This may call us in a 
    // loop but since isEditing is already NO, nothing will happen.
    if (this.field.get('isFirstResponder')) this.field.resignFirstResponder();
    if (this.get('parentNode')) this.removeFromParent() ;
    
    return YES ;
  },
  
  /**
    YES if the editor is currently visible and editing.
  
    @type {Boolean}
  */
  isEditing: NO,
  
  /** @private */
  emptyElement: [
    '<div class="sc-inline-text-field-view">',
      '<div class="sizer"></div>',
      '<textarea class="inner-field" wrap="virtual"></textarea>',
    '</div>'
  ].join(''),

  /**
    Collects the appropriate style information from the targetView to 
    make the inline editor appear similar.
  */
  updateViewStyle: function() {
    // collect font and frame from target.
    var f= this._optframe ;
    var el = this._exampleElement ;
    
    var styles = {
      fontSize:       Element.getStyle(el,'font-size'),
      fontFamily:     Element.getStyle(el,'font-family'),
      fontWeight:     Element.getStyle(el,'font-weight'),
      paddingLeft:    Element.getStyle(el,'padding-left'),
      paddingRight:   Element.getStyle(el,'padding-right'),
      paddingTop:     Element.getStyle(el,'padding-top'),
      paddingBottom:  Element.getStyle(el,'padding-bottom'),
      lineHeight:     Element.getStyle(el,'line-height'),
      textAlign:      Element.getStyle(el,'text-align')
    } ;
      
    var field = this.outlet('field') ;
    var sizer = this.outlet('sizer') ;
    
    field.setStyle(styles) ;
    
    styles.opacity = 0 ;
    sizer.setStyle(styles) ;
    sizer.recacheFrames() ;
    
    this.set('frame', f) ;
  },
  
  /**
    Resizes the visible textarea to fix the actual text in the text area.
    
    This method works by keeping a div positioned immediately beneath the 
    text area with an opacity of 0 that contains the same text as the 
    input text field itself.  This is then used to calculate the required 
    size for the text area.
  */
  resizeToFit: function(newValue)
  {
    var sizer  = this.outlet('sizer');
    var field  = this.outlet('field');
    
    // XSS attack waiting to happen... escape the form input;
    var text = (newValue || '').escapeHTML();

    // convert the textarea's newlines into something comparable for the sizer 
    // div appending a space to give a line with no text a visible height.
    text = text.replace(/  /g, "&nbsp; ").replace(/\n/g, "<br />&nbsp;");
    
    // get the text size
    sizer.set('innerHTML', text || "&nbsp;");
    sizer.recacheFrames() ;
    var h = sizer.get('frame').height;
    this.set('frame', { height: h }) ;
  },

  /** 
    The actual text field view used for editing.
  */
  field: SC.TextareaFieldView.extend({

    // handle mouseDown event if we are currently editing so that events
    // don't go any further?
    mouseDown: function(e) {
      arguments.callee.base.call(this, e) ;
      return this.owner.get('isEditing');
    },

    // [Safari] if you don't take key focus away from an element before you 
    // remove it from the DOM key events are no longer sent to the browser.
    willRemoveFromParent: function() {
      this.get('rootElement').blur();
    },
    
    // ask owner to end editing.
    willLoseFirstResponder: function()
    {
      // should have been covered by willRemoveFromParent, but this was needed 
      // too.
      this.get('rootElement').blur();
      return this.owner.blurEditor() ;
    },

    // invoked when the user presses escape.  Returns true to ignore
    // keystroke
    cancel: function() { 
      this.owner.discardEditing(); 
      return YES;
    },
    
    // do it here instead of waiting on the binding to make sure the UI
    // updates immediately.
    fieldValueDidChange: function(partialChange) {
      arguments.callee.base.call(this, partialChange) ;
      this.owner.resizeToFit(this.getFieldValue()) ;
    },
    
    // invoked when the user presses return.  If this is a multi-line field,
    // then allow the newine to proceed.  Otherwise, try to commit the 
    // edit.
    insertNewline: function(evt) { 
      if (this.owner._multiline) {
        return arguments.callee.base.call(this, evt) ;
      } else {
        this.owner.commitEditing() ;
        return YES ;
      }
    },
    
    // Tries to find the next key view when tabbing.  If the next view is 
    // editable, begins editing.
    
    insertTab: function(evt)
    {
      var next = this.get("owner")._delegate.nextValidKeyView();
      this.owner.commitEditing() ;
      if(next) next.beginEditing();
      return YES ;
    },

    insertBacktab: function(evt)
    {
      var prev = this.get("owner")._delegate.previousValidKeyView();
      this.owner.commitEditing() ;
      if(prev) prev.beginEditing();
      return YES ;
    }

  }).outletFor('.inner-field?'),
  
  sizer: SC.View.outletFor('.sizer?')

});


SC.InlineTextFieldView.mixin(
/** @static SC.InlineTextFieldView */ {
  /** Call this method to make the inline editor begin editing for your view.
      
      If the inline editor is already being used for another value it will
      try to dismiss itself from the other editor and attach itself to the
      new view instead.  If this process fails for some reason (for example
      if the other view did not allow the view to end editing) then this
      method will return false.

      You should pass a set of options that at least includes the target
      view.  See class definition for options.
      
      @params options {Hash} hash of options for editing
      @returns {Boolean} YES if editor began editing, NO if it failed.
  */
  beginEditing: function(options) {
    if (!this.sharedEditor) this.sharedEditor = this.create() ;
    return this.sharedEditor.beginEditing(options) ;
  },
  
  /** Save the current value of the inline editor and exit edit mode.
  
    If the inline editor is being used it will try to end the editing and
    close.  If the inline editor could not end for some reason (for example
    if the delegate did not allow the editing to end) then this method will
    return NO.
    
    @returns {Boolean} YES if the inline editor ended or no edit was in 
      progress.
  */
  commitEditing: function() {
    return (this.sharedEditor) ? this.sharedEditor.commitEditing() : YES ;
  },

  /** Discard the current value of the inline editor and exit edit mode.
  
    If the inline editor is in use, this method will try to end the editing,
    restoring the original value of the target view.  If the inline editor
    could not end for some reason (for example if the delegate did not 
    allow editing to end) then this method will return NO.
    
    @returns {Boolean} YES if the inline editor ended or no edit was in progress.
  */
  discardEditing: function() {
    return (this.sharedEditor) ? this.sharedEditor.discardEditing() : YES ;  
  },
  

  
  /**
    The current shared inline editor.  This property will often remain NULL
    until you actually begin editing for the first time.
    
    @type {SC.InlineTextFieldView}
  */
  sharedEditor: null
  
}) ;

/* End ------------------------------------------------------- views/inline_text_field.js*/

/* Start ----------------------------------------------------- views/label.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view') ;
require('mixins/control') ;
require('mixins/delegate_support');
require('views/inline_text_field');
require('mixins/inline_editor_delegate');

/**
  @class
  
  Displays a static string of text.
  
  You use a label view anytime you need to display a static string of text 
  or to display text that may need to be edited using only an inline control.
  
  @extends SC.View
  @extends SC.Control
  @extends SC.DelegateSupport
  @extends SC.InlineEditorDelegate
  @extends SC.Editable
  @since SproutCore 1.0
*/
SC.LabelView = SC.View.extend(SC.DelegateSupport, SC.Control, SC.InlineEditorDelegate,
/** @scope SC.LabelView.prototype */ {

  emptyElement: '<span class="sc-label-view"></span>',

  /**
    If true, value will be escaped to avoid scripting attacks.
    
    This is a default value that can be overridden by the
    settings on the owner view.
  */
  escapeHTML: true,

  /**
    If true, then the value will be localized.
    
    This is a default default that can be overidden by the
    settings in the owner view.
  */
  localize: false,
  
  /**
    Set this to a validator or to a function and the value
    will be passed through it before being set.
    
    This is a default default that can be overidden by the
    settings in the owner view.
  */
  formatter: null,

  /** 
    The value of the label.
    
    You may also set the value using a content object and a contentValueKey.
    
    @field {String}
  */
  value: '',
  
  /**
    [RO] The value that will actually be displayed.
    
    This property is dynamically computed by applying localization, 
    string conversion and other normalization utilities.
    
    @field
  */
  displayValue: function() {
    var value = this.get('value') ;
    
    // 1. apply the formatter
    var formatter = this.getDelegateProperty(this.displayDelegate, 'formatter') ;
    if (formatter) {
      var formattedValue = ($type(formatter) == T_FUNCTION) ? formatter(value, this) : formatter.fieldValueForObject(value, this) ;
      if (formattedValue != null) value = formattedValue ;
    }
    
    // 2. If the returned value is an array, convert items to strings and 
    // join with commas.
    if ($type(value) == T_ARRAY) {
      var ary = [];
      for(var idx=0;idx<value.get('length');idx++) {
        var x = value.objectAt(idx) ;
        if (x != null && x.toString) x = x.toString() ;
        ary.push(x) ;
      }
      value = ary.join(',') ;
    }
    
    // 3. If value is not a string, convert to string. (handles 0)
    if (value != null && value.toString) value = value.toString() ;
    
    // 4. Localize
    if (value && this.getDelegateProperty(this.displayDelegate, 'localize')) value = value.loc() ;
    
    return value ;
  }.property('value'),
  
  /**
    enables editing using the inline editor
  */
  isEditable: NO,

  /**
    YES if currently editing label view.
  */
  isEditing: NO,


  /**
    set to true to have the value you set automatically localized.
  */
  localize: false,
  
  /**
    Validator to use during inline editing.
    
    If you have set isEditing to YES, then any validator you set on this
    property will be used when the label view is put into edit mode.
    
    @type {SC.Validator}
  */
  validator: null,

  /**
    Event dispatcher callback.
    If isEditable is set to true, opens the inline text editor view.

    @param {DOMMouseEvent} evt DOM event
    
  */
  doubleClick: function( evt ) { return this.beginEditing(); },
  
  
  /**
    Opens the inline text editor (closing it if it was already open for 
    another view).
    
    @return {Boolean} YES if did begin editing
  */
  beginEditing: function()
  {
    if (this.get('isEditing')) return YES ;
    if (!this.get('isEditable')) return NO ;
    
    var value = this.get('value') || '' ;
    var f = this.convertFrameToView(this.get('frame'), null) ;
    var el = this.rootElement;
    SC.InlineTextFieldView.beginEditing({
      frame: f,
      delegate: this,
      exampleElement: el,
      value: value, 
      multiline: NO, 
      validator: this.get('validator')
    });
  },
  
  /**
    Cancels the current inline editor and then exits editor. 
    
    @return {Boolean} NO if the editor could not exit.
  */
  discardEditing: function() {
    if (!this.get('isEditing')) return YES ;
    return SC.InlineTextFieldView.discardEditing() ;
  },
  
  /**
    Commits current inline editor and then exits editor.
    
    @return {Boolean} NO if the editor could not exit
  */
  commitEditing: function() {
    if (!this.get('isEditing')) return YES ;
    return SC.InlineTextFieldView.commitEditing() ;
  },

  /** @private
    Set editing to true so edits will no longer be allowed.
  */
  inlineEditorWillBeginEditing: function(inlineEditor) {
    this.set('isEditing', YES);
  },

  /** @private 
    Hide the label view while the inline editor covers it.
  */
  inlineEditorDidBeginEditing: function(inlineEditor) {
    this._oldOpacity = this.getStyle('opacity') ;
    this.setStyle({ opacity: 0.0 }) ;
  },

  /** @private
    Could check with a validator someday...
  */
  inlineEditorShouldEndEditing: function(inlineEditor, finalValue) {
    return YES ;
  },

  /** @private
    Update the field value and make it visible again.
  */
  inlineEditorDidEndEditing: function(inlineEditor, finalValue) {
    this.setIfChanged('value', finalValue) ;
    this.setStyle({ opacity: this._oldOpacity }) ;
    this._oldOpacity = null ;
    this.set('isEditing', NO) ;
  },

  /** 
    @private
    
    Invoked whenever the monitored value on the content object 
    changes.
    
    The value processed is either the contentValueKey, if set, or 
    it is the content object itself.
  */
  _valueDidChange: function() {

    var value = this.get('value') ;
    if (value === this._value) return; // nothing to do
    this._value = value ;

    // get display value
    value = this.get('displayValue') ;
    
    // Escape HTML
    if (this.getDelegateProperty(this.displayDelegate, 'escapeHTML')) {
      this.set('innerText', value || '') ;
    } else this.set('innerHTML', value || '') ;

  }.observes('value')
  

});


/* End ------------------------------------------------------- views/label.js*/

/* Start ----------------------------------------------------- mixins/collection_view_delegate.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

/**
  Indicates that the collection view expects to accept a drop ON the specified
  item.
*/
SC.DROP_ON = 0x01;

/**
  Indicates that the collection view expects to accept a drop BEFORE the 
  specified item.
*/
SC.DROP_BEFORE = 0x02;

/**
  Indicates that the collection view want's to know which operations would 
  be allowed for either drop operation.
*/
SC.DROP_ANY = 0x03;

/**
  @namespace

  A Collection View Delegate is consulted by a SC.CollectionView's to control
  certain behaviors such as selection control and drag and drop behaviors.
  
  To act as a Collection Delegate, the object should be set as the delegate
  property of the collection view and should implement one or more of the
  methods below.
  
  You can also choose to mixin this delegate to get suitable default 
  implementations of these methods.
  
  @since SproutCore 1.0
*/
SC.CollectionViewDelegate = {
  
  /**
    This method will be called anytime the collection view is about to
    change the selection in response to user mouse clicks or keyboard events.
    
    You can use this method to adjust the proposed selection, eliminating any
    selected objects that cannot be selected.  The default implementation of
    this method simply returns the proposed selection.
    
    @param view {SC.CollectionView} the collection view
    @param sel {Array} Proposed array of selected objects.
    @returns The actual array allowed or null if no change is allowed.
  */
  collectionViewSelectionForProposedSelection: function(view, sel) {
    return sel ;
  },

  /**
    Called by the collection view just before it starts a drag to give you
    an opportunity to decide if the drag should be allowed. 
    
    You can use this method to implement fine-grained control over when a 
    drag will be allowed and when it will not be allowed.  For example, you
    may enable content reordering but then implement this method to prevent
    reordering of certain items in the view.
    
    The default implementation always returns YES.
    
    @param view {SC.CollectionView} the collection view
    @returns {Boolean} YES to alow, NO to prevent it
  */
  collectionViewShouldBeginDrag: function(view) { return YES; },
  
  /**
    Called by the collection view just before it starts a drag so that 
    you can provide the data types you would like to support in the data.
    
    You can implement this method to return an array of the data types you
    will provide for the drag data.
    
    If you return null or an empty array, can you have set canReorderContent
    to YES on the CollectionView, then the drag will go ahead but only 
    reordering will be allowed.  If canReorderContent is NO, then the drag
    will not be allowed to start.
    
    If you simply want to control whether a drag is allowed or not, you
    should instead implement collectionViewShouldBeginDrag().
    
    The default returns an empty array.
    
    @param view {SC.CollectionView} the collection view to begin dragging.
    @returns {Array} array of supported data types.
  */
  collectionViewDragDataTypes: function(view) { return []; },
  
  /**
    Called by a collection view when a drag concludes to give you the option
    to provide the drag data for the drop.
    
    This method should be implemented essentially as you would implement the
    dragDataForType() if you were a drag data source.  You will never be asked
    to provide drag data for a reorder event, only for other types of data.
    
    The default implementation returns null.
    
    @param view {SC.CollectionView} the collection view that initiated the drag
    @param dataType {String} the data type to provide
    @param drag {SC.Drag} the drag object
    @returns {Object} the data object or null if the data could not be provided.
  */
  collectionViewDragDataForType: function(view, dataType, drag) {  
    return null ;
  },
  
  /**
    Called by the collection view during a drag to let you determine the
    kind and location of a drop you might want to accept.
    
    You can override this method to implement fine-grained control over how
    and when a dragged item is allowed to be dropped into a collection view.

    This method will be called by the collection view both to determine in 
    general which operations you might support and specifically the operations
    you would support if the user dropped an item over a specific location.
    
    If the dropOperations parameter is SC.DROP_ANY, then you should simply
    return the logical-or of all the operations you might possibly support
    (or you can simply return the value of proposedDragOperation if you
    support anything.)  In this case, you should make your method as fast as
    possible since it may be called frequently during a drag.
    
    If hte dropOperation parameter is SC.DROP_ON or SC.DROP_BEFORE, then the
    proposedInsertionPoint will be a non-negative value and you should
    determine the specific operations you will support if the user dropped the
    drag item at that point.
    
    If you do not like the proposed drop operation or insertion point, you 
    can override these properties as well by setting the proposedDropOperation
    and proposedInsertionIndex properties on the collection view during this
    method.  These properties are ignored all other times.
    
    @param view {SC.CollectionView} the collection view
    @param drag {SC.Drag} the current drag object
    @param dropOperation {String} the proposed drop operation.  Will be one of SC.DROP_ON, SC.DROP_BEFORE, or SC.DROP_ANY.
    @param proposedInsertionIndex {Number} an index into the content array 
      representing the proposed insertion point.
    @param proposedDragOperations {Number} proposed logical OR of allowed drag operations.
    @returns the allowed drag operation.  Defaults to proposedDragOperation
  */
  collectionViewValidateDrop: function(view, drag, dropOperation, proposedInsertionIndex, proposedDragOperation) {
    return proposedDragOperation ;
  },
  
  /**
    Called by the collection view to actually accept a drop.  This method will
    only be invoked AFTER your validateDrop method has been called to
    determine if you want to even allow the drag operation to go through.
    
    You should actually make changes to the data model if needed here and
    then return the actual drag operation that was performed.  If you return
    SC.DRAG_NONE and the dragOperation was SC.DRAG_REORDER, then the default
    reorder behavior will be provided by the collection view.
    
    @param  view {SC.CollectionView}
  */
  collectionViewAcceptDrop: function(view, drag, dropOperation, proposedInsertionIndex, dragOperation) {
    return SC.DRAG_NONE ;
  },
  
  /**
    Called by the collection view whenever the deleteSelection() method is
    called.  You can implement this method to get fine-grained control over
    which items can be deleted.  To prevent deletion, return null.
    
    This method is only called if canDeleteContent is YES on the collection
    view.
    
    @param view {SC.CollectionView} the collection view
    @param item {Array} proposed array  of items to delete.
    @returns {Array} items allowed to delete or null.
  */
  collectionViewShouldDeleteContent: function(view, items) { return items; },
  
  /**
    Called by the collection view to actually delete the selected items.
    
    The default behavior will use standard array operators to remove the 
    items from the content array.  You can implement this method to provide
    your own deletion method.
    
    If you simply want to controls the items to be deleted, you should instead
    implement collectionViewShouldDeleteItems().  This method will only be 
    called if canDeleteContent is YES and collectionViewShouldDeleteContent()
    returns a non-empty array.
    
    @param view {SC.CollectionView} the view collection view
    @param items {Array} the items to delete
    @returns {Boolean} YES if the operation succeeded, NO otherwise.
  */
  collectionViewDeleteContent: function(view, items) { return NO; }

};

/* End ------------------------------------------------------- mixins/collection_view_delegate.js*/

/* Start ----------------------------------------------------- views/collection/collection.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view') ;
require('views/label') ;
require('mixins/collection_view_delegate') ;

SC.BENCHMARK_UPDATE_CHILDREN = NO ;
SC.VALIDATE_COLLECTION_CONSISTANCY = NO ;

/**
  Special drag operation passed to delegate if the collection view proposes
  to perform a reorder event.
*/
SC.DRAG_REORDER = 0xfff0001 ;

/** Indicates that selection points should be selected using horizontal 
  orientation.
*/
SC.HORIZONTAL_ORIENTATION = 'horizontal';

/** Selection points should be selected using vertical orientation. */
SC.VERTICAL_ORIENTATION = 'vertical' ;

/** Enables an optimization using zombie group views.  This option is configurable for perf testing purposes.  You should not change it. */
SC.ZOMBIE_GROUPS_ENABLED = YES ;

/** Enables an optimization that removes the root element from the DOM during 
a render and then readds it when complete.  This option is configurable for 
perf testing purposes.  You should not change it. */
SC.REMOVE_COLLECTION_ROOT_ELEMENT_DURING_RENDER = NO ;

/**
  @class 
  
  Renders a collection of views from a source array of model objects.
   
  The CollectionView is the root view class for rendering collections of 
  views based on a source array of objects.  It can automatically create the
  and layout the views, including displaying them in groups.  It also 
  handles event input for the entire collection.

  To use CollectionView, just create the view and set the 'content' property
  to an array of objects.  (Note that if you setup a binding, it will 
  always transform content to an array.)  The view will create instances of
  exampleView to render the array.  You can also bind to the selection 
  property if you want to monitor selection. (be sure to set the isEnabled
  property to allow selection.)
  
  @extends SC.View
  @extends SC.CollectionViewDelegate
  
*/
SC.CollectionView = SC.View.extend(SC.CollectionViewDelegate,
/** @scope SC.CollectionView.prototype */
{
  
  // ......................................
  // PROPERTIES
  //
  
  /**
    An array of content objects

    This array should contain the content objects you want the collection view 
    to display.  An item view (based on the exampleView view class) will be 
    created for each content object, in the order the content objects appear 
    in this array.
    
    If you make the collection editable, the collection view will also modify 
    this array using the observable array methods of SC.Array.
    
    Usually you will want to bind this property to a controller property 
    that actually contains the array of objects you to display.
    
    @type Array
  */
  content: [],
  
  /** @private */
  contentBindingDefault: SC.Binding.MultipleNotEmpty,
  
  /**  
    The array of currently selected objects.  

    This array should contain the currently selected content objects.  It is 
    modified automatically by the collection view when the user changes the 
    selection on the collection.

    Any item views representing content objects in this array will have their 
    isSelected property set to YES automatically.
    
    The CollectionView can deal with selection arrays that contain content 
    objects that do not belong to the content array itself.  Sometimes this 
    will happen if you share the same selection across multiple collection 
    views.
    
    Usually you will want to bind this property to a controller property that 
    actually manages the selection for your display.
    
    @type Array
  */
  selection: [],
  
  /** @private */
  selectionBindingDefault: SC.Binding.Multiple,

  /**
    Delegate used to implement fine-grained control over collection view 
    behaviors.
    
    You can assign a delegate object to this property that will be consulted
    for various decisions regarding drag and drop, selection behavior, and
    even rendering.  The object you place here must implement some or all of
    the SC.CollectionViewDelegate mixin.
  */
  delegate: null,
  
  /** 
    Allow user to select content using the mouse and keyboard 
  
    Set this property to NO to disallow the user from selecting items.
    If you have items in your selection property, they will still be reflected
    visually.
    
    @type {Bool}
  */
  isSelectable: true,
  
  /** @private */
  isSelectableBindingDefault: SC.Binding.Bool,
  
  /**
    Enable or disable the view.  
    
    The collection view will set the isEnabled property of its item views to
    reflect the same view of this property.  Whenever isEnabled is false,
    the collection view will also be not selectable or editable, regardless of the  
    settings for isEditable & isSelectable.
    
    @type {Bool}
  */
  isEnabled: true,
  
  /** @private */
  isEnabledBindingDefault: SC.Binding.Bool,

  /**
    Allow user to edit content views.
    
    The collection view will set the isEditable property on its item views to
    reflect the same value of this property.  Whenever isEditable is false, 
    the user will not be able to reorder, add, or delete items regardless of 
    the canReorderContent and canDeleteContent and isDropTarget properties.
  */
  isEditable: true,
  
  /** @private */
  isEditableBindingDefault: SC.Binding.Bool,
  
  /**
    Allow user to reorder items using drag and drop.
    
    If true, the user will can use drag and drop to reorder items in the list.
    If you also accept drops, this will allow the user to drop items into 
    specific points in the list.  Otherwise items will be added to the end.
  */
  canReorderContent: false,
  
  /** @private */
  canReorderContentBindingDefault: SC.Binding.Bool,

  /**
    Allow the user to delete items using the delete key
    
    If true the user will be allowed to delete selected items using the delete
    key.  Otherwise deletes will not be permitted.
  */
  canDeleteContent: NO,
  
  /** @private */
  canDeleteContentBindingDefault: SC.Binding.Bool,
  
  /**
    Accept drops for data other than reordering.
    
    Setting this property to return true when the view is instantiated will 
    cause it to be registered as a drop target, activating the other drop 
    machinery.
  */
  isDropTarget: NO,
  
  /**
    Use toggle selection instead of normal click behavior.
    
    If set to true, then selection will use a toggle instead of the normal
    click behavior.  Command modifiers will be ignored and instead clicking
    once will enable an item and clicking on it again will disable it.
    
    @type Boolean
  */
  useToggleSelection: NO,

  /**
    Trigger the action method on a single click.
  
    Normally, clicking on an item view in a collection will select the content 
    object and double clicking will trigger the action method on the 
    collection view.  
  
    If you set this property to true, then clicking on a view will both select 
    it (if isSelected is true) and trigger the action method.  
  
    Use this if you are using the collection view as a menu of items.
  
    @type {Boolean}
  */  
  actOnSelect: false,  
  
  
  /**
    Select an item immediately on mouse down
  
    Normally as soon as you begin a click the item will be selected.
    
    In some UI scenarios, you might want to prevent selection until
    the mouse is released, so you can perform, for instance, a drag operation
    without actually selecting the target item.  
    
    @type {Boolean}
  */  
  selectOnMouseDown: true,

  /**
    Property key to use to group objects.
  
    If groupBy is set to a non-null value, then the collection view will
    automatically display item views in groups based on the value of the 
    passed property key.  The exampleGroupView will be used to display the 
    items in groups.
  
    If this property is set, you MUST ensure the items in the content array 
    are already sorted by the group key.  Otherwise item view groups might 
    appear more than once.
  
    @type {String}
  */
  groupBy: null,
  
  /**
    The view class to use when creating new item views.
  
    The collection view will automatically create an instance of the view 
    class you set here for each item in its content array.  You should provide 
    your own subclass for this property to display the type of content you 
    want. 
  
    For best results, the view you set here should understand the following 
    properties:
  
    - *content* The content object from the content array your view should display
    - *isEnabled* True if the view should appear enabled
    - *isSelected* True if the view should appear selected
  
    In general you do not want your child views to actually respond to mouse 
    and keyboard events themselves.  It is better to let the collection view 
    do that.

    If you do implement your own event handlers such as mouseDown or mouseUp, 
    you should be sure to actually call the same method on the collection view 
    to give it the chance to perform its own selection housekeeping.
  
    @type {SC.View}
  */
  exampleView: SC.View,

  /**
    The view class to use when displaying item views in groups.
  
    If the groupBy property is not null, then the collection view will create
    an instance of this view class with the item views that belong to the 
    group as child nodes for each distinct group value it encounters.
  
    Your groupView should have two outlets:
  
    {{{
      labelView: The view to display the group label.  The group value will be 
      set as the content property of this view.
    
      itemView: This is the view the item views will be added to as children 
      to this view.
    }}}
  
    If groupBy is null, then this property will not be used.  The default 
    class provided here simply displays the group value in an H1 tag.
  
    @type {SC.View}
  */
  exampleGroupView: SC.View.extend({
    emptyElement: '<div><h1></h1><div class="well"></div></div>',
    outlets: ['labelView','itemView'],
    labelView: SC.LabelView.outletFor('h1?'),
    itemView: SC.View.outletFor('.well?')
  }),
  
  /**
    Invoked when the user double clicks on an item (or single clicks of 
    actOnSelect is true)

    Set this to the name of the action you want to send down the
    responder chain when the user double clicks on an item (or single clicks 
    if actOnSelect is true).  You can optionally specify a specific target as 
    well using the target property.

    If you do not specify an action, then the collection view will also try to 
    invoke the action named on the target item view.
    
    Older versions of SproutCore expected the action property to contain an 
    actual function that would be run.  This format is still supported but is 
    deprecated for future use.  You should generally use the responder chain 
    to handle your action for you.
    
    @type {String}
  */  
  action: null,

  /**
    Optional target to send the action to when the user double clicks.
    
    If you set the action property to the name of an action, you can 
    optionally specify the target object you want the action to be sent to.  
    This can be either an actual object or a property path that will resolve 
    to an object at the time that the action is invoked.  
    
    This property is ignored if you use the deprecated approach of making the
    action property a function.
    
    @type {String|Object}
  */
  target: null,
  
  /**
    Set to true whenever the content changes and remains true until
    the content has been rerendered.  
    
    You can also set this to true yourself to be notified when it is 
    completed.
  */
  isDirty: false,
  
  /**
    The maximum time the collection view will spend updating its
    views before it takes a break from the update.  
  
    This keeps your browser from freezing or displaying a slow script 
    warning while the render code works. Number is in msec.
  
    Future versions of CollectionView may ignore this property as newer
    rendering techniques make it no longer necessary.
  */
  maxRenderTime: 0,

  /** 
    Property to on content items to use for display.

    Built-in item views such as the LabelViews and ImageViews will use the
    value of this property as a key on the content object to determine the
    value they should display.
    
    For example, if you set contentValueKey to 'name' and set the 
    exampleView to an SC.LabelView, then the label views created by the 
    colleciton view will display the value of the content.name.
    
    If you are writing your own custom item view for a collection, you can
    get this behavior automatically by including the SC.Control mixin on your
    view.  You can also ignore this property if you like.  The collection view
    itself does not use this property to impact rendering.
  */
  contentValueKey: null,

  /**
    Enables keyboard-based navigate if set to true.
  */
  acceptsFirstResponder: false,

  /**
    If your layout uses a grid or horizontal-based layout, then make sure this 
    property is always up to date with the current number of items per row.  
    
    The CollectionView will use this property to support keyboard navigation 
    using the arrow keys.
    
    If your collection view is simply a vertical list of items then you do not 
    need to edit this property.
  */
  itemsPerRow: 1,

  /**  
    Property returns all of the item views, regardless of group view.

    @field
    @returns {Array} the item views.
  */
  itemViews: function() {
    if (!this._itemViews) {
      

      var range = this.get('nowShowingRange') ;
      var content = this.get('content') || [] ;
      this._itemViews = [] ;
      for(var idx=0;idx<range.length;idx++) {
        var cur = content.objectAt(idx) ;
        this._itemViews.push(this.itemViewForContent(cur)) ;
      }
    }
    return this._itemViews;
  }.property(),

  /**
    Property returns all of the rendered group views in order of their 
    appearance with the content.
  */
  groupViews: function() {
    if (!this._groupViews) {
      var groupBy = this.get('groupBy') ;
      if (groupBy) {
        var range = this.get('nowShowingRange') ;
        var content = this.get('content') || [] ;
        var groupValue = undefined ;
        this._groupViews = [] ;

        for(var idx=0;idx<range.length;idx++) {
          var cur = content.objectAt(idx) ;
          var curGroupValue = (cur) ? cur.get(groupBy) : null ;
          if (curGroupValue != groupValue) {
            groupValue = curGroupValue ;
            this._groupViews.push(this.groupViewForGroupValue(groupValue)) ;
          }
        }
        
      }
    }
    return this._groupViews; 
  }.property(),
  
  /**
    Returns true if the passed view belongs to the collection.
    
    This method uses the internal hash of item views and works even if 
    your items are stored in group views.  This is faster than searching
    the child view hierarchy yourself.
    
    @param {SC.View} view The view to search for.

    @returns {Boolean} True if the view is an item view in the receiver.
  */
  hasItemView: function(view) {
    if (!this._itemViewsByGuid) this._itemViewsByGuid = {} ;
    return !!this._itemViewsByGuid[SC.guidFor(view)] ;
  },

  /** 
    Find the item view underneath the passed mouse location.
    
    The default implementation of this method simply searches each item view's
    frame to find one that includes the location.  If you are doing your own
    layout, you may be able to perform this calculation more quickly.  If so,
    consider overriding this method for better performance during drag 
    operations.
    
    @param {Point} loc   The current mouse location in the coordinate of the 
      collection view
      
    @returns {SC.View} The item view under the collection
  */
  itemViewAtLocation: function(loc) {
    var itemView = this._itemViewRoot ;
    while(itemView) {
      var frame = itemView.get('frame');
      if (SC.pointInRect(loc, frame)) return itemView ;
    }
    return null; // not in an itemView right now.
  },
  

  
  /** 
    Find the first content item view for the passed event.
    
    This method will go up the view chain, starting with the view that was the 
    target of the passed event, looking for a child item.  This will become 
    the view that is selected by the mouse event.
    
    This method only works for mouseDown & mouseUp events.  mouseMoved events 
    do not have a target.
    
    @param {Event} evt An event
    
  */
  itemViewForEvent: function(evt)
  {
    var view = SC.window.firstViewForEvent( evt );
    // work up the view hierarchy to find a match...
    do {
      // item clicked was the ContainerView itself... i.e. the user clicked outside the child items
      // nothing to return...
      if ( view == this ) return null;
      
      // sweet!... the view is not only in the collection, but it says we can hit it.
      // hit it and quit it... 
      if ( this.hasItemView(view) && (!view.hitTest || view.hitTest(evt)) ) return view;
    } while ( view = view.get('parentNode') );
    
    // nothing was found... 
    return null;
  },


  /**
    Returns the itemView that represents the passed content object.  
    
    If no item view is currently rendered for the object, this method will
    return null.
    
    @param {Object} obj The content object. 
    @returns {SC.View} The item view or null
  */
  itemViewForContent: function(obj) {
    var key = (obj) ? SC.guidFor(obj) : '0';
    return this._itemViewsByContent[key];
  },

  /**
    Returns the groupView that represents the passed group value.
    
    If no group view is currently rendered for the gorup value, this method
    will return null.  If grouping is disabled, this method will also return
    null.
    
    @param {Object} value The group value.
    @param {SC.View} The group view or null
  */
  groupViewForGroupValue: function(groupValue) {
    return this._groupViewsByValue[groupValue] ;
  },

  /**
    Returns the groupValue for the passed group view.
    
    Older-style groupViews expect the group value to be set directly on 
    their labelView while newer groupViews expect their groupValue to be set.
    This method takes into account both approaches.
    
    @param {SC.View} groupView the group view.
    @returns {Object} the value of the group view or null.
  */
  groupValueForGroupView: function(groupView) {
    if (!groupView) return null ;
    var ret ;
    if (groupView.groupValue === undefined) {
      ret = groupView.get('content') ;
    } else ret = groupView.get('groupValue') ;
    return ret ;
  },
  
  /**
    Expands the index into a range of content objects that have the same
    group value.
    
    This method searches backward and forward through your content array for  
    objects that have the same group value as the object at the index you 
    pass in.  You can use this method when implementing layoutGroupView to 
    determine the range of the content that belongs to the group.  
    
    Since this method simply searches through the content array, it is really
    only suitable for content arrays of a few hundred items or less.  If you
    expect to have a larger size of content array, then you may need to do
    something custom in your data model to calculate this range in less time.
    
    @param {Number} contentIndex index of a content object
    @returns {Range} a range of objects
  */
  groupRangeForContentIndex: function(contentIndex) {
    var content = Array.from(this.get('content')) ;
    var len = content.get('length') ;
    var groupBy = this.get('groupBy') ;
    if (!groupBy) return { start: 0, length: len } ;

    var min = contentIndex, max = contentIndex ;
    var cur = content.objectAt(contentIndex) ;
    var groupValue = (cur) ? cur.get(groupBy) : null ;
    
    // find first item at bottom that does not match.  add one to get start
    while(--min >= 0) {
      var cur = content.objectAt(min) ;
      var curGroupValue = (cur) ? cur.get(groupBy) : null ;
      if (curGroupValue !== groupValue) break ;
    }
    min++ ;
    
    // find first item at top that does not match.  keep value to calc range
    while(++max < len) {
      var cur = content.objectAt(max) ;
      var curGroupValue = (cur) ? cur.get(groupBy) : null ;
      if (curGroupValue !== groupValue) break ;
    }
    
    return { start: min, length: max-min } ;
  },

  // Determines the group value at a specified index.
  groupValueAtContentIndex: function(contentIndex) {
    var groupBy = this.get('groupBy') ;
    var content = Array.from(this.get('content')).objectAt(contentIndex) ;
    return (groupBy && content && content.get) ? content.get(groupBy) : null;
  },
    
  // ......................................
  // GENERATING CHILDREN
  //
  
  /**
    Update the itemViews in the receiver to match the currently visible 
    content objects.  Normally this method assumes the content objects 
    themselves have not changed and only updates the views if the range of 
    visible content has changed.  If you pass true to the fullUpdate property, 
    then the entire set of itemViews will be revalidated in case any content 
    objects have changed.
    
    @param {Bool} fullUpdate (Optional) if set to true, assumes content has
      changed and will perform a full update.
    
  */
  updateChildren: function(fullUpdate) {
    var f ;

    // if the collection is not presently visible in the window, then there is 
    // really nothing to do here.  Just mark the view as dirty and return.
    if (!this.get('isVisibleInWindow')) {
      this.set('isDirty', true) ;
      this._needsFullUpdate = this._needsFullUpdate || fullUpdate ;
      return; 
    }

    if (SC.BENCHMARK_UPDATE_CHILDREN) {
      var bkey = '%@.updateChildren(%@)'.fmt(this, (fullUpdate) ? 'FULL' : 'FAST') ;
      SC.Benchmark.start(bkey);
    }

    this.beginPropertyChanges() ; // avoid sending notifications
    
    // STEP 1: Update frame size if needed.  Required to compute the 
    // clippingFrame.
    var f ;
    if ((f = this.computeFrame()) && !SC.rectsEqual(f, this.get('frame'))) {
      var parent = this.get('parentNode') ;
      if (parent) parent.viewFrameWillChange() ;
      this.set('frame', f) ;
      if (parent) parent.viewFrameDidChange() ;
      
      if ((f = this.computeFrame()) && !SC.rectsEqual(f, this.get('frame'))) {
        this.set('frame', f) ;
      } 
    } 

    // Save the current clipping frame.  If the frame methods are called again
    // later but the frame has not actually changed, we don't want to run
    // updateChildren again.
    var clippingFrame = this._lastClippingFrame = this.get('clippingFrame') ;
	
	// this is a workaround for the clippingFrame bug in ie7 clippingFrame is always returning dimensions 0,0
	if(clippingFrame.width==0 && clippingFrame.height==0){
		clippingFrame = this._lastClippingFrame = f;
	}
    
    // STEP 2: Calculate the new range of content to display in 
    // the clipping frame.  Determine if we need to do a full update or
    // not.
   
    var range = this.contentRangeInFrame(clippingFrame) ;
    var content = Array.from(this.get('content'));
     
    //make sure the range isn't greater than the content length 
    //this will prevent trying to render items that aren't really there.
    range.length = Math.min(SC.maxRange(range), content.get('length')) - range.start ;

    var nowShowingRange = this.get('nowShowingRange') ;
    fullUpdate = fullUpdate || (SC.intersectRanges(range, nowShowingRange).length <= 0) ;
    this.set('nowShowingRange', range) ;

    // STEP 3: Update item views.
    var groupBy = this.get('groupBy') ;
    var didChange = false ;
    
    // If this is a fullUpdate, then rebuild the itemViewsByContent hash
    // from scratch.  This is necessary if the content or the visible range
    // might have changed.
    if (fullUpdate) {
     
      var itemViewsByContent = {} ; // this will replace the current hash.
      
      // iterate through all of the views and insert them.  If the view 
      // already exists, it will simply be reused.
      var idx = SC.maxRange(range) ;
      while(--idx >= range.start) {
        var c = content.objectAt(idx) ;
        var key = SC.guidFor(c) ;
        var itemView = this._insertItemViewFor(c, groupBy, idx) ;

        // add item view to new hash and remove from old hash.
        itemViewsByContent[key] = itemView;
       
        delete this._itemViewsByContent[key];
      }
      
      // Now iterate through the old hash.  Any left over item views should
      // be removed.
      for(var key in this._itemViewsByContent) {
        if (!this._itemViewsByContent.hasOwnProperty(key)) continue ;
        var itemView = this._itemViewsByContent[key] ;
        this._removeItemView(itemView, groupBy) ;
      } ;
      
      // Swap out remaining content items.
      this._itemViewsByContent = itemViewsByContent ;
      didChange = true;
      
    // If a fullUpdate is not required, then we assume no content has changed
    // and we just need to add or remove some views to bring the ranges up
    // to date.
    } else {
      // Find changed range at the top.  Note that the length here may be 
      // negative.  Negative means views should be removed.
      var start = range.start ;
      var length = (nowShowingRange.start - start) ;
      if (length != 0) {
        this._insertOrRemoveItemViewsInRange(start, length, groupBy) ;
        didChange = true ;
      }
      
      // Find the changed range at the bottom.  Note that the length here may
      // also be negative. Negative means views should be removed.
      var start = SC.maxRange(nowShowingRange) ;
      var length = SC.maxRange(range) - start ;
      if (length != 0) {
        this._insertOrRemoveItemViewsInRange(start, length, groupBy) ;
        didChange = true ;
      }
    }

    // Recache frames just in case this changed the scroll height.
    this.recacheFrames() ;

    // Set this to true once children have been rendered.  Whenever the 
    // content changes, we don't want resize or clipping frame changes to 
    // cause a refresh until the content has been rendered for the first time.
    this._hasChildren = range.length>0 ;
    this.set('isDirty',false); 

    // Clean out some cached items and notify their changes.
    // NOTE: This must be called after _hasChildren has been set or 
    // updateSelectionStates() may not run.
    if (didChange) {
      this._flushZombieGroupViews() ;
      this.updateSelectionStates() ;
      
      this._itemViews = null ;
      this.notifyPropertyChange('itemViews') ;
      
      this._groupViews = null ;
      this.notifyPropertyChange('groupViews') ;
    }

    this.endPropertyChanges() ;
    if (SC.BENCHMARK_UPDATE_CHILDREN) SC.Benchmark.end(bkey);    
  },

  /**
    Rebuild all the child item views in the collection view.
    
    This will remove all the child views from the collection view and rebuild 
    them from scratch.  This method is generally expensive, but if you have 
    made a substantial number of changes to the content array, this may be the 
    most efficient way to perform the update.
    
    In general the collection view will automatically keep the item views in 
    sync with the content objects for you.  You should not need to call this 
    method very often.
    
    @returns {void}
  */
  rebuildChildren: function() {
    
    this.beginPropertyChanges() ;
    
    // iterate through itemViews and remove them
    while(this._itemViewRoot) this._removeItemViewFromChain(this._itemViewRoot) ;
    
    // iterate through groupViews and remove them .. if grouping is disabled,
    // _groupViewRoot will be null anyway.
    while(this._groupViewRoot) this._removeGroupView(this._groupViewRoot) ;
    
    // now updateChildren.
    this._hasChildren = false ;
    this.updateChildren() ;
    
    this.endPropertyChanges() ;
  },
  
  /**
    Update the selection state for the item views to reflect the selection 
    array.
    
    This will update the isSelected property of all item views so that only 
    those representing content objects found in the selection array are 
    selected.
    
    This method is called automatically whenever your content or selection 
    properties changed.  You should not need to call or override it often.
  */
  updateSelectionStates: function() {
    if (!this._hasChildren) return ;
    var selection = this.get('selection') || [];

    // First, for efficiency, turn the selection into a hash by GUID.  This 
    // way, we'll only have to perform a linear search over the children.
    // This hash is cached and flushed each time the selection changes.
    var selectionHash = this._selectionHash ;
    if (!selectionHash) {
      selectionHash = {} ;
      var idx = selection.get('length') ;
      while(--idx >= 0) {
        var cur = selection.objectAt(idx) ;
        var key = SC.guidFor(cur) ;
        selectionHash[key] = true ;
      }
      this._selectionHash = selectionHash ;
    }

    // Iterate over the item views and set their selection property.
    for(var key in this._itemViewsByContent) {
      if (!this._itemViewsByContent.hasOwnProperty(key)) continue ;
      var itemView = this._itemViewsByContent[key] ;
      var isSelected = (key) ? selectionHash[key] : false ;
      if (itemView.get('isSelected') != isSelected) {
        itemView.set('isSelected', isSelected) ;
      }
    }
  },
    
  
  /**
    Calls updateChildren whenever the view is resized, unless you have not 
    implemented custom layout or incremental rendering.
    
    UPDATE:
    -- add/remove any children as needed
    -- update layout on all itemViews unless you have a more efficient
  */
  resizeChildrenWithOldSize: function(oldSize) {
    if (!this._hasChildren) return ;
    this.updateChildren() ; // add/remove any new views.
    this.layoutResize() ; // perform layout on all of the views if needed.
  },

  /**
    Whenever your clipping frame changes, determine new range to display.  If 
    new range is a change, then it will update the children and relayout.
    
    UPDATE:
    -- add/remove any children as needed
    -- update layout on added children only
  */
  clippingFrameDidChange: function() {
    if (!this._hasChildren) return ;
    SC.Benchmark.start('%@.clippingFrameDidChange'.fmt(this.toString())) ;
    if (!SC.rectsEqual(this._lastClippingFrame, this.get('clippingFrame'))) {
          if (this._hasChildren) this.updateChildren() ;
        }
    SC.Benchmark.end('%@.clippingFrameDidChange'.fmt(this.toString())) ;
  },

  /**
    Override to return the computed frame dimensions of the collection view.
    
    These dimensions are automatically applied at the end of a call to  
    updateChildren() if they change at all.  This method is critical for 
    support of incremental rendering.
  
    @returns {Rect} width and/or height you want this collection view to have.
  */
  computeFrame: function() { return null; },
  
  /**
    Override to return the range of items to render for a given frame.

    You can override this method to implement support for incremenetal 
    rendering.  The range you return here will be used to limit the number of 
    actual item views that are created by the collection view.
    
    @param {Rect} frame The frame you should use to determine the range.
    
    @returns {Range} A hash that indicates the range of content objects to 
      render.  ({ start: X, length: Y }) 
  */  
  contentRangeInFrame: function(frame) {
    var content = this.get('content') ;
    var len = ((content && content.get) ? content.get('length') : 0) || 0 ;
    return { start: 0, length: len };
  },


  /**
    This method is called whenever a group view is added or during the 
    layoutResize() method.  You should use this method to size and position 
    the group view.
    
    The included contentIndexHint can be used to help you determine the range
    of content that should be included in the group.  If you are renderings a
    list of items 100 or less, you can get the range of content belonging to
    the group using the contentRangeForGroup() method.  If you are managing
    a much larger set of content, you should probably implement your own 
    data model.
    
    Your layout method should can optionally also use the firstLayout to 
    further optimize itself.  Normally, you will want to only change a view's
    actual frame if it does not match your calculated size.  However, if 
    firstLayout is true, you can simply set the new layout without checking
    first.
    
    @param {SC.View} groupView the view to size and position.
    @param {Object} groupValue the value the groupView represents.
    @param {Number} contentIndexHint the index of a content object.
    @param {Bool} firstLayout True if this is the first the view has been laid out. 
    
  */
  layoutGroupView: function(groupView, groupValue, contentIndexHint, firstLayout) {
    
  },
  
  /**
    This method is called whenever an itemView is added or during the 
    layoutResize() method.  You should use this method to size and position
    the itemView.
    
    @param {SC.View} itemViewthe item view to layout
    @param {Number} contentIndex the index of the content this layout represents.
    @param {Bool} firstLayout true if this is the first time it has been laid out.
  */
  layoutItemView: function(itemView, contentIndex, firstLayout) {
    
  },
  
  /**
    This method is called whenever the view is resized.  The default
    implementation will simply iterate through the visible content range and
    call layoutItemView() and layoutGroupView() on all the views.
    
    If you would like to provide a more efficient method for updating the
    layout on a resize, you could override this method and do the iterating 
    yourself.
  */
  layoutResize: function() {
    if (!this._hasChildren) return ; // ignore calls before first render
    var nowShowingRange = this.get('nowShowingRange') ;
    var groupBy = this.get('groupBy') ;
    var groupValue = undefined ;
    var content = this.get('content') || [] ;
    
    var idx = SC.maxRange(nowShowingRange) ;
    while(--idx >= nowShowingRange.start) {
      var cur = content.objectAt(idx) ;
      var itemView = this.itemViewForContent(cur) ;

      // should never happen, but recover just in case.
      if (!itemView) continue ; 
      
      // if grouping is enabled, get the group value and layout based on that.
      if (groupBy && ((curGroupValue = (cur) ? cur.get(groupBy) : null) !== groupValue)) {
        var groupView = this.groupViewForGroupValue(groupValue) ;
        if (groupView) {
          this.layoutGroupView(groupView, groupValue, idx, false) ;
        }
      }
      
      // now layout the itemView itself.
      this.layoutItemView(itemView, idx, false) ;
    }
  },
    
  
  // Ordered array of item views currently on display.  This array 
  // is reset whenever the item views are regenerated.
  _itemViews: null,

  // Ordered array of group views currently in the display.  This array is
  // reset whenever the group views are regenerated.
  _groupViews: null,
  
  // Most recent content range on display.
  _visibleContentRange: null,
  
  // Hash of itemViews to the content guids they current represent.  This
  // only matches views in currently in the _visibleContentRange.
  _itemViewsByContent: null,

  // Hash of groupViews to the group key they currently represent.
  _groupViewsByValue: null,
  
  // Hash of counts of item views contained in a group view.  When the count
  // of a group reaches zero, it will be removed.
  _groupViewCounts: null,

  // Array of unused itemViews.  Push/pop only.
  _itemViewPool: null,
  
  // Array of unused groupViews.  Push/pop only.
  _groupViewPool: null,
  
  // When a group view's item view count reaches zero, it is moved to this
  // hash until updateChildren() completes.  During that time, if the group 
  // is needed again, it can be reused.  At the end of updateChildren() this
  // hash will be flushed and its members returned to the groupView pool.
  //
  _zombieGroupViews: null,
  
  /** @private
    Finds or creates the itemView for the named content and inserts it into 
    view under the correct group if needed.  Note that this method does not 
    take into account the actual ORDER of item views in the hierarchy.  It 
    assumes that manual layout will ensure the items appear visually in the 
    proper order anyway.

    @param {SC.View} itemView The item view to remove
    @param {String} groupBy the value used for grouping or null if grouping is 
      disabled.
    
    @returns {SC.View} the new itemView.
  */
  _insertItemViewFor: function(content, groupBy, contentIndex) {

    // first look for a matching record.
    var key = SC.guidFor(content) ;
    var ret = this._itemViewsByContent[key];
    var firstLayout = false ;

    // if no record was found, pull an item view from the pool or create one.
    // set the content.
    if (!ret) {
      ret = this._itemViewPool.pop() || this.get('exampleView').create({ 
        owner: this, displayDelegate: this 
      }) ;
      ret.addClassName('sc-collection-item') ; // add class name for display
      
      // set content and add to content hash
      ret.set('content', content) ;
      this._itemViewsByContent[key] = ret ;
      this._itemViewsByGuid[SC.guidFor(ret)] = ret ;
      firstLayout = true ;
    }
    if (!ret) throw "Could not create itemView for content: %@".fmt(content);

    // Determine proper parent view and insert itemView if needed.
    // Also update count of itemViews.
    var canGroup = !!(groupBy && content) ;
    var groupValue = (canGroup) ? content.get(groupBy) : null;  
    var parentView = (canGroup) ? this._insertGroupViewFor(groupValue, contentIndex) : this ;
    var curParentView = ret.get('parentNode') ;
    
    if (curParentView != parentView) {
      
      // if the item is already inside of another group, then it is probably
      // just being moved, so remove it from its parent group first...
      if (groupBy && curParentView) {
        
        // reduce the group view count.  If this it the last item in the 
        // group view, the count will be <= 0 and we will need to remove t
        // the group view itself.
        if (--this._groupViewCounts[SC.guidFor(curParentView)] <= 0) {
          this._removeGroupView(curParentView, groupValue) ;
        }
        
      }
      
      parentView.appendChild(ret) ;
      if (groupBy) this._groupViewCounts[SC.guidFor(parentView)]++ ;
    }

    // Layout itemView.
    this.layoutItemView(ret, contentIndex, firstLayout) ;
    return ret ;
  },

  /** @private
    Removes the itemView from the receiver and returns it to the itemView pool 
    for later reuse.  
    
    If the itemView belongs to a groupView and this leaves the groupView empty 
    as well, then the groupView will be moved to the zombieGroupViews hash.
    
    @param {SC.View} itemView The item view to remove
    @param {String} groupBy the value used for grouping or null if grouping is 
      disabled.
    
    @returns {SC.View} The itemView that was removed.
  */
  _removeItemView: function(itemView, groupBy) {
    
    // If we are grouping, then decrement the groupViewCount.  If the new 
    // count is zero, save groupView for later removal.
    var groupView = null ; var groupValue ;
    if (groupBy && (groupView = itemView.get('parentNode'))) {
      if (--this._groupViewCounts[SC.guidFor(groupView)] > 0) groupView = null ; 
      if (groupView) {
        var content = itemView.get('content') ;
        groupValue = (content) ? content.get(groupBy) : null ;
      }
    }
    
    // Remove itemView from parent and remove from content hash.
    var content = itemView.get('content') ;
    var key = SC.guidFor(content) ;
    delete this._itemViewsByContent[key] ;
    delete this._itemViewsByGuid[SC.guidFor(itemView)] ;
    itemView.removeFromParent() ;
    
    // Clear content and return itemView to pool
    itemView.set('content', null) ;
    this._itemViewPool.push(itemView) ;
    
    // if a groupView is set, then it also needs to be returned to the pool
    if (groupView) this._removeGroupView(groupView, groupValue) ;
    
    return itemView;
  },
  
  /** @private
    Adds or removes itemViews for the content in the specified range.
    Note that this is not passed as a formal range because the length 
    could be negative.  
    
    A negative length means views should be removed.
  */
  _insertOrRemoveItemViewsInRange: function(start, length, groupBy) {
    // zero length means do nothing.
    if (length == 0) return ;

    var content = this.get('content') || [] ;
    
    // negative length == remove item views
    if (length < 0) {
      while(++length < 0) {
        var c = content.objectAt(start + length) ;
        var itemView = this.itemViewForContent(c) ;
        if (itemView) this._removeItemView(itemView, groupBy) ;
      }
      
    // positive length == add item views.
    } else if (length > 0) {
      while(--length >= 0) {
        var idx = start + length ;
        var c = content.objectAt(idx) ;
        this._insertItemViewFor(c, groupBy, idx) ;
      }  
    }
  },

  /** @private
    Finds or creates a groupView for the named group value and inserts it into
    the receiver.  This method does not take into account the actual ORDER of
    the groupViews in the hierarchy.  It assumes that manual layout will 
    ensure the items appear visually in the proper order anyway.
    
    @returns {SC.View} the new groupView.
  */
  _insertGroupViewFor: function(groupValue, contentIndex) {
    var ret =  this._groupViewsByValue[groupValue] ; 
    // if (ret) return ret ; // nothing to do
    
    var firstLayout = false ;
    
    // if the group was not found, check the zombie pool.  If found in zombie
    // pool, restore it to the regular group view hash.
    if (!ret && this._zombieGroupViews) {
      ret = this._zombieGroupViews[groupValue] ;
      if (ret) {
        delete this._zombieGroupViews[groupValue] ;
        this._groupViewsByValue[groupValue] = ret ;
        this._groupViewCounts[SC.guidFor(ret)] = 0 ;
      }
    }

    // If groupValue still not found, create one.
    if (!ret) {
      ret = this._groupViewPool.pop() || this.get('exampleGroupView').create({
         owner: this, displayDelegate: this 
      });
      ret.addClassName('sc-collection-group') ;

      // set the groupValue on the groupView.  Older groupViews expect us to 
      // set this directly on the labelView.  Newer groupViews should have a 
      // groupValue property.
      if (ret.groupValue !== undefined) {
        ret.set('groupValue', groupValue) ;
      } else ret.set('content', groupValue) ;
      
      // save in cache
      this._groupViewsByValue[groupValue] = ret ;
      this._groupViewCounts[SC.guidFor(ret)] = 0 ;
      firstLayout = true; 
    }
    
    // If the group view does not already belong to the receiver, add it.
    if (!ret) throw "Could not create a groupView for value: %@".fmt(groupValue) ;
    if (ret.get('parentNode') != this) this.appendChild(ret) ;
    
    // Layout the group View
    this.layoutGroupView(ret, groupValue, contentIndex, firstLayout) ;
    
    return ret ;
  },

  /** @private
    Called whenever a groupView is no longer being used.
    
    Theoretically, this method removes a group view from the receiver and 
    stores it in the pool for later use.  In actuality, this will just moved 
    the view to the zombieGroupView pool.  You must call 
    _flushZombieGroupViews() to actually remove them from the receiver.
  */
  _removeGroupView: function(groupView, groupValue) {
    if (SC.ZOMBIE_GROUPS_ENABLED) {
      this._zombieGroupViews[groupValue] = groupView ;
    } else {
      this._finalRemoveGroupView(groupView) ;
    }
    
    delete this._groupViewsByValue[groupValue] ;
    delete this._groupViewCounts[SC.guidFor(groupView)] ;
    return groupView ;
  },
  
  /** @private
    Flushes any zombie group views, removing them from their parent view and 
    returning them to the groupView pool for later consumption.
  */
  _flushZombieGroupViews: function() {
    if (!SC.ZOMBIE_GROUPS_ENABLED) return ; // nothing to do
    
    for(var key in this._zombieGroupViews) {
      if (!this._zombieGroupViews.hasOwnProperty(key)) continue ;
      var groupView = this._zombieGroupViews[key] ;
      this._finalRemoveGroupView(groupView) ;
    } 
    this._zombieGroupViews = {} ; // reset
  },
  
  /** @private
    Final method to actually remove a groupView from its parent view and
    return it to the groupView pool.
  */
  _finalRemoveGroupView: function(groupView) {
    groupView.removeFromParent() ;

    // set the groupValue on the groupView.  Older groupViews expect us to set 
    // this directly on the labelView.  Newer groupViews should have a 
    // groupValue property.
    if (groupView.groupValue !== undefined) {
      groupView.set('groupValue', null) ;
    } else groupView.set('content', null) ;
    
    this._groupViewPool.push(groupView) ;
    return groupView ;
  },
  
  /** @private
    Removes the rootElement from the DOM temporarily if needed to optimize performance.
  */
  _removeRootElementFromDom: function() {
    if (!SC.REMOVE_COLLECTION_ROOT_ELEMENT_DURING_RENDER) return ;
    if (this._cachedRootElementParent === undefined) {
      var parent = this._cachedRootElementParent = this.rootElement.parentNode ;
      this._cachedRootElementNextSibling = this.rootElement.nextSibling ;
      if (parent) parent.removeChild(this.rootElement) ;
    }
  },
  
  /** @private
    Re-adds root element into DOM if necessary.  Inverts _removeRootElementFromDom().
  */
  _restoreRootElementInDom: function() {
    if (!SC.REMOVE_COLLECTION_ROOT_ELEMENT_DURING_RENDER) return ;
    if (this._cachedRootElementParent) {
      this._cachedRootElementParent.insertBefore(this.rootElement, this._cachedRootElementNextSibling);
    }
    this._cachedRootElementParent = this._cachedRootElementNextSibling = null ;
  },
  
    
  // ......................................
  // SELECTION
  //

  /** @private
    Finds the smallest index of a content object in the selected array.
  */
  _indexOfSelectionTop: function() {
    var content = this.get('content');
    var sel = this.get('selection');
    if (!content || !sel) return - 1;

    // find the first item in the selection
    var contentLength = content.get('length') ;
    var indexOfSelected = contentLength ; var idx = sel.length ;
    while(--idx >= 0) {
      var curIndex = content.indexOf(sel[idx]) ;
      if ((curIndex >= 0) && (curIndex < indexOfSelected)) indexOfSelected = curIndex ;
    }
    
    return (indexOfSelected >= contentLength) ? -1 : indexOfSelected ;
  },

  /**
    Finds the largest index of a content object in the selection array.
  */
  _indexOfSelectionBottom: function() {
    var content = this.get('content');
    var sel = this.get('selection');
    if (!content || !sel) return - 1;

    var indexOfSelected = -1 ; var idx = sel.length ;
    while(--idx >= 0) {
      var curIndex = content.indexOf(sel[idx]) ;
      if (curIndex > indexOfSelected) indexOfSelected = curIndex ;
    }
    
    return (indexOfSelected < 0) ? -1 : indexOfSelected ;
  },
  
  /**
    Select one or more items before the current selection, optionally
    extending the current selection.  Also scrolls the selected item into 
    view.
    
    Selection does not wrap around.
    
    @param extend {Boolean} (Optional) If true, the selection will be extended 
      instead of replaced.  Defaults to false.
    @param numberOfItems {Integer} (Optional) The number of previous to be 
      selected.  Defaults to 1
    @returns {void}
  */
  selectPreviousItem: function(extend, numberOfItems)
  {
    if (numberOfItems == null) numberOfItems = 1 ;
    if (extend == null) extend = false ;

    var content  = this.get('content');
    var contentLength = content.get('length') ;

    // if extending, then we need to do some fun stuff to build the array
    var selTop, selBottom, anchor ;
    if (extend) {
      selTop = this._indexOfSelectionTop() ;
      selBottom = this._indexOfSelectionBottom() ;
      anchor = (this._selectionAnchor == null) ? selTop : this._selectionAnchor ;
      this._selectionAnchor = anchor ;

      // If the selBottom is after the anchor, then reduce the selection
      if (selBottom > anchor) {
        selBottom = selBottom - numberOfItems ;
        
      // otherwise, select the previous item from the top 
      } else {
        selTop = selTop - numberOfItems ;
      }
      
      // Ensure we are not out of bounds
      if (selTop < 0) selTop = 0 ;
      if (selBottom < selTop) selBottom = selTop ;
      
    // if not extending, just select the item previous to the selTop
    } else {
      selTop = this._indexOfSelectionTop() - numberOfItems;
      if (selTop < 0) selTop = 0 ;
      selBottom = selTop ;
      anchor = null ;
    }
    
    // now build array of new items to select
    var items = [] ;
    while(selTop <= selBottom) {
      items[items.length] = content.objectAt(selTop++) ;
    }

    // ensure that the item is visible and set the selection
    if (items.length > 0) {
      this.scrollToContent(items.first());
      this.selectItems(items);
    }
    
    this._selectionAnchor = anchor ;
  },
  
  /**
    Select one or more items folling the current selection, optionally
    extending the current selection.  Also scrolls to selected item.

    Selection does not wrap around.
    
    @param extend {Boolean} (Optional) If true, the selection will be extended 
      instead of replaced.  Defaults to false.
    @param numberOfItems {Integer} (Optional) The number of items to be 
      selected.  Defaults to 1.
    @returns {void}
  */
  selectNextItem: function(extend, numberOfItems)
  {
    if (numberOfItems == null) numberOfItems = 1 ;
    if (extend == null) extend = false ;

    var content  = this.get('content');
    var contentLength = content.get('length') ;

    // if extending, then we need to do some fun stuff to build the array
    var selTop, selBottom, anchor ;
    if (extend) {
      selTop = this._indexOfSelectionTop() ;
      selBottom = this._indexOfSelectionBottom() ;
      anchor = (this._selectionAnchor == null) ? selTop : this._selectionAnchor ;
      this._selectionAnchor = anchor ;

      // If the selTop is before the anchor, then reduce the selection
      if (selTop < anchor) {
        selTop = selTop + numberOfItems ;
        
      // otherwise, select the next item after the top 
      } else {
        selBottom = selBottom + numberOfItems ;
      }
      
      // Ensure we are not out of bounds
      if (selBottom >= contentLength) selBottom = contentLength-1;
      if (selTop > selBottom) selTop = selBottom ;
      
    // if not extending, just select the item next to the selBottom
    } else {
      selBottom = this._indexOfSelectionBottom() + numberOfItems;
      if (selBottom >= contentLength) selBottom = contentLength-1;
      selTop = selBottom ;
      anchor = null ;
    }
    
    // now build array of new items to select
    var items = [] ;
    while(selTop <= selBottom) {
      items[items.length] = content.objectAt(selTop++) ;
    }

    // ensure that the item is visible and set the selection
    if (items.length > 0) {
      this.scrollToContent(items.first());
      this.selectItems(items);
    }
    
    this._selectionAnchor = anchor ;
  },
  
  /**
    Scroll the rootElement (if needed) to ensure that the item is visible.
    @param {SC.Record} record The record to scroll to
    @returns {void}
  */
  scrollToContent: function(record) {
    // find the itemView.  if not present, add one.
    var itemView = this.itemViewForContent(record) ;
    if (!itemView) {
      var content = Array.from(this.get('content')) ;
      var contentIndex = content.indexOf(record) ;
      var groupBy = this.get('groupBy');
      itemView = this._insertItemViewFor(record, groupBy, contentIndex); 
    }
    if (itemView) this.scrollToItemView(itemView);
  },
  
  /**
    Scroll the rootElement (if needed) to ensure that the item is visible.
    @param {SC.View} view The item view to scroll to
    @returns {void}
  */
  scrollToItemView: function( view )
  {
    // find first scrollable view.
    var scrollable = this ;
    while(scrollable && (scrollable != SC.window) && (!scrollable.get('isScrollable'))) {
      scrollable = scrollable.get('parentNode') ;
    }
    if (!scrollable || (scrollable == SC.window)) return ; // no scrollable!
    scrollable.scrollToVisible(view) ;
  },

  /** 
    Selects the passed array of items, optionally extending the
    current selection.
    
    @param items {Array} The item or items to select.
    @param extendSelection {Boolean} If true, extends the selection instead of 
      replacing it.
  */
  selectItems: function(items, extendSelection) {
    var base = (extendSelection) ? this.get('selection') : [] ;
    var sel = [items].concat(base).flatten().uniq() ;
    
    // if you are not extending the selection, then clear the selection 
    // anchor.
    this._selectionAnchor = null ;
    this.set('selection',sel) ;  
  },

  /** 
    Removes the items from the selection.
  */
  deselectItems: function(items) {
    items = [items].flatten() ;
    var base = this.get('selection') || [] ; 
    var sel = base.map(function(i) { return (items.include(i)) ? null : i; });
    sel = sel.compact() ;
    this.set('selection',sel) ;
  },
  
  /**
    Deletes the selected content if canDeleteContent is YES.  
    
    This will invoke delegate methods to provide fine-grained control.
    
    @returns {Boolean} YES if deletion is possible, even if none actually occurred.
  */
  deleteSelection: function() {
    
    // perform some basic checks...
    if (!this.get('canDeleteContent')) return NO;  
    var sel = Array.from(this.get('selection'));
    if (!sel || sel.get('length') === 0) return NO ;

    // let the delegate decide what to actually delete.  If this returns an
    // empty array or null, just do nothing.
    sel = this.invokeDelegateMethod(this.delegate, 'collectionViewShouldDeleteContent', this, sel) ;
    sel = Array.from(sel) ; // ensure this is an array
    if (!sel || sel.get('length') === 0) return YES ;

    // now have the delegate (or us) perform the deletion.  The collection
    // view implements a default version of this method.
    this.invokeDelegateMethod(this.delegate, 'collectionViewDeleteContent', this, sel) ;
    return YES ;
  },

  /**
    Default implementation of the delegate method.
    
    This method will delete the passed items from the content array using 
    standard array methods.  This is often suitable if you are using an
    array controller or a real array for your content.
    
    @param view {SC.CollectionView} this
    @param sel {Array} the items to delete
    @returns {Boolean} YES if the deletion was a success.
  */
  collectionViewDeleteContent: function(view, sel) {
    
    // get the content.  Bail if this cannot be used as an array.
    var content = this.get('content') ; 
    if (!content || !content.removeObject) return NO ;
    
    // suspend property notifications and remove the objects...
    if (content.beginPropertyChanges) content.beginPropertyChanges();
    var idx = sel.get('length') ;
    while(--idx >= 0) {
      var item = sel.objectAt(idx) ;
      content.removeObject(item) ;      
    }
    // begin notifying again...
    if (content.endPropertyChanges) content.endPropertyChanges() ;
    
    return YES ; // done!
  },
  
  // ......................................
  // EVENT HANDLING
  //

  keyDown: function(evt) {
    return this.interpretKeyEvents(evt) ;
  },
  
  keyUp: function() { return true; },

  /** @private
    Handle select all keyboard event.
  */
  selectAll: function(evt) {
    var content = (this.get('content') || []).slice() ;
    this.selectItems(content, NO) ;
    return YES ;
  },
  
  /** @private
    Handle delete keyboard event.
  */
  deleteBackward: function(evt) {
    return this.deleteSelection() ;
  },
  
  /** @private
    Handle delete keyboard event.
  */
  deleteForward: function(evt) {
    return this.deleteSelection() ;
  },
  
  /** @private
    Selects the same item on the next row or moves down one if 
    itemsPerRow = 1
  */
  moveDown: function(sender, evt) {
    this.selectNextItem(false, this.get('itemsPerRow') || 1) ;
    return true ;
  },
  
  /** @private
    Selects the same item on the next row or moves up one if 
    itemsPerRow = 1
  */
  moveUp: function(sender, evt) {
    this.selectPreviousItem(false, this.get('itemsPerRow') || 1) ;
    return true ;
  },

  /** @private
    Selects the previous item if itemsPerRow > 1.  Otherwise does nothing.
  */
  moveLeft: function(sender, evt) {
    if ((this.get('itemsPerRow') || 1) > 1) this.selectPreviousItem(false, 1) ;
    return true ;
  },

  /** @private
    Selects the next item if itemsPerRow > 1.  Otherwise does nothing.
  */
  moveRight: function(sender, evt) {
    if ((this.get('itemsPerRow') || 1) > 1) this.selectNextItem(false, 1) ;
    return true ;
  },

  moveDownAndModifySelection: function(sender, evt) {
    this.selectNextItem(true, this.get('itemsPerRow') || 1) ;
    return true ;
  },
  
  moveUpAndModifySelection: function(sender, evt) {
    this.selectPreviousItem(true, this.get('itemsPerRow') || 1) ;
    return true ;
  },

  /** @private
    Selects the previous item if itemsPerRow > 1.  Otherwise does nothing.
  */
  moveLeftAndModifySelection: function(sender, evt) {
    if ((this.get('itemsPerRow') || 1) > 1) this.selectPreviousItem(true, 1) ;
    return true ;
  },

  /** @private
    Selects the next item if itemsPerRow > 1.  Otherwise does nothing.
  */
  moveRightAndModifySelection: function(sender, evt) {
    if ((this.get('itemsPerRow') || 1) > 1) this.selectNextItem(true, 1) ;
    return true ;
  },

  /**
    Handles mouse down events on the collection view or on any of its 
    children.
    
    The default implementation of this method can handle a wide variety
    of user behaviors depending on how you have configured the various
    options for the collection view.
    
    @param ev {Event} the mouse down event
    @returns {Boolean} Usually YES.
  */
  mouseDown: function(ev) {

    // When the user presses the mouse down, we don't do much just yet.
    // Instead, we just need to save a bunch of state about the mouse down
    // so we can choose the right thing to do later.

    // save the original mouse down event for use in dragging.
    this._mouseDownEvent = ev ;

    // Toggle selection only triggers on mouse up.  Do nothing.
    if (this.useToggleSelection) return true;

    // Make sure that saved mouseDown state is always reset in case we do
    // not get a paired mouseUp. (Only happens if subclass does not call us 
    // like it should)
    this._mouseDownAt = this._shouldSelect = this._shouldDeselect =
      this._shouldReselect = this._refreshSelection = false;

    // find the actual view the mouse was pressed down on.  This will call
    // hitTest() on item views so they can implement non-square detection
    // modes. -- once we have an item view, get its content object as well.
    var mouseDownView    = this._mouseDownView = this.itemViewForEvent(ev);
    var mouseDownContent = 
      this._mouseDownContent = (mouseDownView) ? mouseDownView.get('content') : null;

    // become first responder if possible.
    this.becomeFirstResponder() ;
    
    // recieved a mouseDown on the collection element, but not on one of the 
    // childItems... unless we do not allow empty selections, set it to empty.
    if (!mouseDownView) {
      if (this.get('allowDeselectAll')) this.selectItems([], false);
      return true ;
    }
    
    // collection some basic setup info
    var selection  = this.get('selection') || [];
    var isSelected = selection.include(mouseDownContent);
    var modifierKeyPressed = ev.ctrlKey || ev.metaKey ;
    if (mouseDownView.checkboxView && (Event.element(ev) == el.checkboxView.rootElement)) {
      modifierKeyPressed = true ;
    }
    this._modifierKeyPressed = modifierKeyPressed ;  

    this._mouseDownAt = Date.now();

    // holding down a modifier key while clicking a selected item should 
    // deselect that item...deselect and bail.
    if (modifierKeyPressed && isSelected) {
      this._shouldDeselect = mouseDownContent;
    // if the shiftKey was pressed, then we want to extend the selection
    // from the last selected item
    } else if (ev.shiftKey && selection.get('length') > 0) {
      selection = this._findSelectionExtendedByShift(selection, mouseDownContent) ;
      this.selectItems(selection) ;
      
    // If no modifier key was pressed, then clicking on the selected item 
    // should clear the selection and reselect only the clicked on item.
    } else if (!modifierKeyPressed && isSelected) {
      this._shouldReselect = mouseDownContent;
      
    // Otherwise, if selecting on mouse down,  simply select the clicked on 
    // item, adding it to the current selection if a modifier key was pressed.
    } else {
      if (this.get("selectOnMouseDown")){
         this.selectItems(mouseDownContent, modifierKeyPressed);
      } else this._shouldSelect = mouseDownContent ;
    }
    
    // saved for extend by shift ops.
    this._previousMouseDownContent = mouseDownContent;
    
    return true;
  },
  
  mouseUp: function(ev) {
    
    var canAct = this.get('actOnSelect') ;
    var view = this.itemViewForEvent(ev) ;
    
    if (this.useToggleSelection) {
      if (!view) return ; // do nothing when clicked outside of elements
      
      // determine if item is selected. If so, then go on.
      var selection = this.get('selection') || [] ;
      var content = (view) ? view.get('content') : null ;
      var isSelected = selection.include(content) ;
      if (isSelected) {
        this.deselectItems([content]) ;
      } else this.selectItems([content],true) ;
      
    } else {
      var content = (view) ? view.get('content') : null ;

      // this will be set if the user simply clicked on an unselected item and 
      // selectOnMouseDown was NO.
      if (this._shouldSelect) this.selectItems(this._shouldSelect, this._modifierKeyPressed);
      
      // This is true if the user clicked on a selected item with a modifier
      // key pressed.
      if (this._shouldDeselect) this.deselectItems(this._shouldDeselect);

      // This is true if the user clicked on a selected item without a 
      // modifier-key pressed.  When this happens we try to begin editing 
      // on the content.  If that is not allowed, then simply clear the 
      // selection and reselect the clicked on item.
      if (this._shouldReselect) {

        // - contentValueIsEditable is true
        var canEdit = this.get('contentValueIsEditable') ;
        
        // - the user clicked on an item that was already selected
        // - is the only item selected
        if (canEdit) {
          var sel = this.get('selection') ;
          canEdit = sel && (sel.get('length') === 1) && (sel.objectAt(0) === this._shouldReselect) ;
        }

        // - the item view responds to contentHitTest() and returns YES.
        // - the item view responds to beginEditing and returns YES.
        if (canEdit) {
          var itemView = this.itemViewForContent(this._shouldReselect) ;
          canEdit = itemView && (!itemView.contentHitTest || itemView.contentHitTest(ev)) ;
          canEdit = (canEdit && itemView.beginEditing) ? itemView.beginEditing() : NO ;
        }
        
        // if cannot edit, just reselect
        if (!canEdit) this.selectItems(this._shouldReselect,false) ;
      }

      this._cleanupMouseDown() ;
    }

    this._mouseDownEvent = null ;
    if (canAct) this._action(ev, view) ;
    
    return false;  // bubble event to allow didDoubleClick to be called...
  },
  
  _cleanupMouseDown: function() {
    this._mouseDownAt = this._shouldDeselect = this._shouldReselect = this._refreshSelection = this._shouldSelect = false;
    this._mouseDownEvent = this._mouseDownContent = this._mouseDownView = null ;
  },
  
  mouseMoved: function(ev) {
    var view = this.itemViewForEvent(ev) ;
    // handle hover events.
    if(this._lastHoveredItem && ((view === null) || (view != this._lastHoveredItem)) && this._lastHoveredItem.mouseOut) {
      this._lastHoveredItem.mouseOut(ev); 
    }
    this._lastHoveredItem = view ;
    if (view && view.mouseOver) view.mouseOver(ev) ;
  },

  mouseOut: function(ev) {
  
    var view = this._lastHoveredItem ;
    this._lastHoveredItem = null ;
    if (view && view.didMouseOut) view.didMouseOut(ev) ;
  },
  
  doubleClick: function(ev) {
    var view = this.itemViewForEvent(ev) ;
    if (view) {
      this._action(view, ev) ;
      return true ;
    } else return false ;
  },

  _findSelectionExtendedByShift: function(selection, mouseDownContent) {
    var content = this.get('content');

    // bounds of the collection...
    var contentLowerBounds = 0;
    var contentUpperBounds = (content.get('length') - 1);

    var selectionBeginIndex = content.indexOf(selection.first());
    var selectionEndIndex   = content.indexOf(selection.last());

    var previousMouseDownIndex = content.indexOf(this._previousMouseDownContent);
    // _previousMouseDownContent couldn't be found... either it hasn't been set yet or the record has been deleted by the user
    // fall back to the first selected item.
    if (previousMouseDownIndex == -1) previousMouseDownIndex = selectionBeginIndex;


    var currentMouseDownIndex = content.indexOf(mouseDownContent);
    // sanity check...
    if (currentMouseDownIndex == -1) throw "Unable to extend selection to an item that's not in the content array!";

    // clicked before the current selection set... extend it's beginning...
    if (currentMouseDownIndex < selectionBeginIndex) {
      selectionBeginIndex = currentMouseDownIndex;
    }
    
    // clicked after the current selection set... extend it's ending...
    if (currentMouseDownIndex > selectionEndIndex) {
      selectionEndIndex = currentMouseDownIndex;
    }
    
    // clicked inside the selection set... need to determine where the last
    // selection was and use that as an anchor.
    if ((currentMouseDownIndex > selectionBeginIndex) && (currentMouseDownIndex < selectionEndIndex)) {
      if (currentMouseDownIndex === previousMouseDownIndex) {
        selectionBeginIndex = currentMouseDownIndex;
        selectionEndIndex   = currentMouseDownIndex;
      } else if (currentMouseDownIndex > previousMouseDownIndex) {
        selectionBeginIndex = previousMouseDownIndex;
        selectionEndIndex   = currentMouseDownIndex;
      } else if (currentMouseDownIndex < previousMouseDownIndex){
        selectionBeginIndex = currentMouseDownIndex;
        selectionEndIndex   = previousMouseDownIndex;
      }
    }
    
    // slice doesn't include the last index passed... silly..
    selectionEndIndex++;

    // shouldn't need to sanity check that the selection is in bounds due to 
    // the indexOf checks above...I'll have faith that indexOf hasn't lied to 
    // me...
    return content.slice(selectionBeginIndex, selectionEndIndex);
  },
  

  // if content value is editable and we have one item selected, then edit.
  // otherwise, invoke action.
  insertNewline: function() {
    if (this.get('contentValueIsEditable')) {
      var sel = this.get('selection') ;
      if (sel && sel.get('length') === 1) {
        var itemView = this.itemViewForContent(sel.objectAt(0)) ;
        if (itemView && itemView.beginEditing) {
          this.scrollToItemView(itemView) ;
          itemView.beginEditing() ;
        }
      }
      
    // invoke action!
    } else {
      var sel = this.get('selection') ;
      var itemView = (sel && sel.get('length') === 1) ? this.itemViewForContent(sel.objectAt(0)) : null ;
      this._action(itemView, null) ;
    }
    
    return YES ; // always handle
  },
  
  // ......................................
  // FIRST RESPONDER
  //

  /**
    Called whenever the collection becomes first responder. 
    Adds the focused class to the element.
  */
  didBecomeFirstResponder: function() {
    this.addClassName('focus') ;
  },
  
  willLoseFirstResponder: function() {
    this.removeClassName('focus');
  },
  
  // ......................................
  // DRAG AND DROP SUPPORT
  //

  /**
    When reordering its content, the collection view will store its reorder
    data using this special data type.  The data type is unique to each 
    collection view instance.  You can use this data type to detect reorders
    if necessary.
    
    @field
    @type {String}
  */
  reorderDataType: function() {
    if (!this._reorderDataTypeKey) {
      this._reorderDataTypeKey = "SC.CollectionView.Reorder.%@".fmt(SC.guidFor(this)) ;
    }
    return this._reorderDataTypeKey ;
  }.property(),
  
  _reorderDataType: function() {
    return this.get('reorderDataType') ;
  },
  
  /**
    This property is set to the array of content objects that are the subject
    of a drag whenever a drag is initiated on the collection view.  You can
    consult this property when implementing your collection view delegate 
    methods, but otherwise you should not use this property in your code.
    
    Note that drag content will always appear in the same order the content
    appears in the source content array.
    
    @field
    @type {Array}
  */
  dragContent: null,
  
  /**
    This property is set to the proposed insertion index during a call to
    collectionViewValidateDrop().  Your delegate implementations can change
    the value of this property to enforce a drop some in some other location.
    
    @type {Number}
    @field
  */
  proposedInsertionIndex: null,
  
  /**
    This property is set to the proposed drop operation during a call to
    collectionViewValidateDrop().  Your delegate implementations can change
    the value of this property to enforce a different type of drop operation.
    
    @type {Number}
    @field
  */
  proposedDropOperation: null,
  
  /**  @private
    mouseDragged event handler.  Initiates a drag if the following conditions
    are met:
    
    - collectionViewShouldBeginDrag() returns YES *OR*
    - the above method is not implemented and canReorderContent is true.
    - the dragDataTypes property returns a non-empty array
    - a mouse down event was saved by the mouseDown method.
  */
  mouseDragged: function(ev) {
    // if the mouse down event was cleared, there is nothing to do; return.
    if (this._mouseDownEvent === null) return YES ;

    // Don't do anything unless the user has been dragging for 123msec
    if ((Date.now() - this._mouseDownAt) < 123) return YES ;
    
    // OK, they must be serious, decide if a drag will be allowed.
    if (this.invokeDelegateMethod(this.delegate, 'collectionViewShouldBeginDrag', this)) {

      // First, get the selection to drag.  Drag an array of selected
      // items appearing in this collection, in the order of the 
      // collection.
      //
      // Set this to the dragContent property.
      var content = this.get('content') || [] ;
      var dragContent;
      
      // if we don't select on mouse down, then the selection has not been 
      // updated to whatever the user clicked.  Instead use
      // mouse down content.
      if (!this.get("selectOnMouseDown")) {
        dragContent = [this._mouseDownContent];
      } else {
        dragContent = this.get('selection').sort(function(a,b) {
          a = content.indexOf(a) ;
          b = content.indexOf(b) ;
          return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
        });
      }
      
      this.set('dragContent', dragContent) ;

      // Get the set of data types supported by the delegate.  If this returns
      // a null or empty array and reordering content is not also supported
      // then do not start the drag.
      if (this.get('dragDataTypes').get('length') > 0) {
        // Build the drag view to use for the ghost drag.  This 
        // should essentially contain any visible drag items.
        var view = this.ghostViewFor(dragContent) ;

        // Initiate the drag
        SC.Drag.start({
          event: this._mouseDownEvent,
          source: this,
          dragView: view,
          ghost: NO,
          slideBack: YES,
          dataSource: this
        }) ; 

        // Also use this opportunity to clean up since mouseUp won't 
        // get called.
        this._cleanupMouseDown() ;
        this._lastInsertionIndex = null ;
        
      // Drag was not allowed by the delegate, so bail.
      } else {
        this.set('dragContent', null) ;
      }
      
      return YES ;
    }
  },

  /**
    Implements the drag data source protocol for the collection view.  This
    property will consult the collection view delegate if one is provided. It
    will also do the right thing if you have set canReorderContent to YES.
    
    @field 
    @type {Array}
  */
  dragDataTypes: function() {
    
    // consult delegate.
    var ret = this.invokeDelegateMethod(this.delegate, 'collectionViewDragDataTypes', this) ;
    var canReorderContent = this.get('canReorderContent') ;
    
    // bail if ret returned null or empty array and cannot reorder.
    if ((!ret || ret.get('length')===0) && !canReorderContent) return [];
    
    // add reorder type if needed.
    if (canReorderContent) {
      ret = (ret) ? ret.slice() : [] ;
      
      var key = this.get('reorderDataType') ;
      if (ret.indexOf(key) < 0) ret.push(key) ;
    }
    return ret ;
    
    //data: { "_mouseDownContent": dragContent }
      
  }.property(),

  /**
    Implements the drag data source protocol method.  The implementation of
    this method will consult the collection view delegate if one has been
    provided.  It also respects the canReoderContent method.
  */
  dragDataForType: function(dataType, drag) {
    
    // if this is a reorder, then return drag content.
    if (this.get('canReorderContent')) {
      if (dataType === this.get('reorderDataType')) return this.get('dragContent') ;
    }
    
    // otherwise, just pass along to the delegate.
    return this.invokeDelegateMethod(this.delegate, 'collectionViewDragDataForType', this, dataType, drag) ;
  },

  /**
    Implements the SC.DropTarget interface.  The default implementation will
    consult the collection view delegate, if you implement those methods.
  */
  dragEntered: function(drag, evt) {

    // the proposed drag operation is DRAG_REORDER only if we can reorder
    // content and the drag contains reorder content.
    var op = SC.DRAG_NONE ;
    if (this.get('canReorderContent')) {
      var types = drag.get('dataTypes') ;
      if (types.indexOf(this.get('reorderDataType')) >= 0) {
        op = SC.DRAG_REORDER ;
      }
    }
    
    // Now pass this onto the delegate.
    op = this.invokeDelegateMethod(this.delegate, 'collectionViewValidateDrop', this, drag, SC.DROP_ANY, -1, op) ;
    
    if (op === SC.DRAG_REORDER) op = SC.DRAG_MOVE ;
    
    // return
    return op ;
  },

  // Determines the allowed drop operation insertion point, operation type,
  // and the drag operation to be performed.  Used by dragUpdated() and 
  // performDragOperation().
  _computeDropOperationState: function(drag, evt) {
    
    // get the insertion index for this location.  This can be computed
    // by a subclass using whatever method.  This method is not expected to
    // do any data valdidation, just to map the location to an insertion 
    // index.
    var loc = drag.get('location') ;
    loc = this.convertFrameFromView(loc, null) ;
    var dropOp = SC.DROP_BEFORE ;
    var dragOp = SC.DRAG_NONE ;
    
    // STEP 1: Try with a DROP_ON option -- send straight to delegate if 
    // supported by view.
    
    // get the computed insertion index and possibly drop operation.
    // prefer to drop ON.
    var idx = this.insertionIndexForLocation(loc, SC.DROP_ON) ;
    if ($type(idx) === T_ARRAY) {
      dropOp = idx[1] ;
      idx = idx[0] ;
    }

    // if the return drop operation is DROP_ON, then just check it with the
    // delegate method.  If the delegate method does not support dropping on,
    // then it will return DRAG_NONE, in which case we will try again with
    // drop before.
    if (dropOp === SC.DROP_ON) {
      
      // Now save the insertion index and the dropOp.  This may be changed by
      // the collection delegate.
      this.set('proposedInsertionIndex', idx) ;
      this.set('proposedDropOperation', dropOp) ;
      dragOp = this.invokeDelegateMethod(this.delegate, 'collectionViewValidateDrop', this, drag, dropOp, idx, dragOp) ;
      idx = this.get('proposedInsertionIndex') ;
      dropOp = this.get('proposedDropOperation') ;
      this._dropInsertionIndex = this._dropOperation = null ;

      // The delegate is OK with a drop on also, so just return.
      if (dragOp !== SC.DRAG_NONE) {
        return [idx, dropOp, dragOp] ;
        
      // The delegate is NOT OK with a drop on, try to get the insertion
      // index again, but this time prefer SC.DROP_BEFORE, then let the 
      // rest of the method run...
      } else {
        dropOp = SC.DROP_BEFORE ;
        idx = this.insertionIndexForLocation(loc, SC.DROP_BEFORE) ;
        if ($type(idx) === T_ARRAY) {
          dropOp = idx[1] ;
          idx = idx[0] ;
        }
      }
    }

    // if this is a reorder drag, set the proposed op to SC.DRAG_REORDER and
    // validate the insertion point.  This only works if the insertion point
    // is DROP_BEFORE.  DROP_ON is not handled by reordering content.
    if ((idx >= 0) && this.get('canReorderContent') && (dropOp === SC.DROP_BEFORE)) {

      var objects = drag.dataForType(this.get('reorderDataType')) ;
      if (objects) {
        var content = this.get('content') || [] ;

        // if the insertion index is in between two items in the drag itself, 
        // then this is not allowed.  Either use the last insertion index or 
        // find the first index that is not in between selections.  Stop when
        // we get to the beginning.
        var previousContent = (idx > 0) ? content.objectAt(idx-1) : null ;
        var nextContent = (idx < content.get('length')) ? content.objectAt(idx) : null;

        var isPreviousInDrag = (previousContent) ? objects.indexOf(previousContent)>=0 : NO;
        var isNextInDrag = (nextContent) ? objects.indexOf(nextContent)>=0 : NO;

        if (isPreviousInDrag && isNextInDrag) {
          if (this._lastInsertionIndex == null) {
            while((idx >= 0) && (objects.indexOf(content.objectAt(idx)) >= 0)) {
              idx-- ;
            } 
          } else idx = this._lastInsertionIndex ;
        }

        // If we found a valid insertion point to reorder at, then set the op
        // to custom DRAG_REORDER.
        if (idx >= 0) dragOp = SC.DRAG_REORDER ;
      }
    }

    // Now save the insertion index and the dropOp.  This may be changed by
    // the collection delegate.
    this.set('proposedInsertionIndex', idx) ;
    this.set('proposedDropOperation', dropOp) ;
    dragOp = this.invokeDelegateMethod(this.delegate, 'collectionViewValidateDrop', this, drag, dropOp, idx, dragOp) ;
    idx = this.get('proposedInsertionIndex') ;
    dropOp = this.get('proposedDropOperation') ;
    this._dropInsertionIndex = this._dropOperation = null ;
    
    // return generated state
    return [idx, dropOp, dragOp] ;
  },
  
  /** 
    Implements the SC.DropTarget interface.  The default implementation will
    determine the drop location and then consult the collection view delegate
    if you implement those methods.  Otherwise it will handle reordering
    content on its own.
  */
  dragUpdated: function(drag, evt) {

    var state = this._computeDropOperationState(drag, evt) ;
    var idx = state[0], dropOp = state[1], dragOp = state[2] ;
    
    // if the insertion index or dropOp have changed, update the insertion
    // point
    if (dragOp !== SC.DRAG_NONE) {
      if ((this._lastInsertionIndex !== idx) || (this._lastDropOperation !== dropOp)) {
        var itemView = this.itemViewForContent(this.get('content').objectAt(idx));
        this.showInsertionPoint(itemView, dropOp) ;
      }

      this._lastInsertionIndex = idx ;
      this._lastDropOperation = dropOp ;

    } else {
      this.hideInsertionPoint() ;
      this._lastInsertionIndex = this._lastDropOperation = null ;
    }

    // Normalize drag operation to the standard kinds accepted by the drag
    // system.
    return (dragOp === SC.DRAG_REORDER) ? SC.DRAG_MOVE : dragOp;  
  },

  /**
    Implements the SC.DropTarget protocol.  Hides any visible insertion 
    point and clears some cached values.
  */
  dragExited: function() {
    this.hideInsertionPoint() ;
    this._lastInsertionIndex = this._lastDropOperation = null ;
  },

  /**
    Implements the SC.DropTarget protocol.  Hides any visible insertion 
    point and clears some cached values.
  */
  dragEnded: function() {
    this.hideInsertionPoint() ;
    this._lastInsertionIndex = this._lastDropOperation = null ;
  },

  /**
    Implements the SC.DropTarget protocol.
  */
  prepareForDragOperation: function(op, drag) { return YES; },
  
  /**
    Implements the SC.DropTarget protocol.  Consults the collection view
    delegate to actually perform the operation unless the operation is 
    reordering content.
  */
  performDragOperation: function(op, drag) { 
    
    // Get the correct insertion point, drop operation, etc.
    var state = this._computeDropOperationState(drag, null, op) ;
    var idx = state[0], dropOp = state[1], dragOp = state[2] ;

    // The dragOp is the kinds of ops allowed.  The drag operation must 
    // be included in that set.
    if (dragOp === SC.DRAG_REORDER) {
      op = (op & SC.DRAG_MOVE) ? SC.DRAG_REORDER : SC.DRAG_NONE ;
    } else {
      op = op & dragOp ;
    }
    
    // If no allowed drag operation could be found, just return.
    if (op === SC.DRAG_NONE) return op;
    
    // Some operation is allowed through, give the delegate a chance to
    // handle it.
    var performed = this.invokeDelegateMethod(this.delegate, 'collectionViewAcceptDrop', this, drag, dropOp, idx, op) ;

    // If the delegate did not handle the drag (i.e. returned SC.DRAG_NONE),
    // and the op type is REORDER, then do the reorder here.
    if ((performed === SC.DRAG_NONE) && (op === SC.DRAG_REORDER)) {
      var objects = drag.dataForType(this.get('reorderDataType')) ;
      if (!objects) return SC.DRAG_NONE ;

      var content = this.get('content') ;
      content.beginPropertyChanges(); // suspend notifications

      // find the old index and remove it.
      var objectsIdx = objects.get('length') ;
      while(--objectsIdx >= 0) {
        var obj = objects.objectAt(objectsIdx) ;
        var old = content.indexOf(obj) ;
        if (old >= 0) content.removeAt(old) ;
        if ((old >= 0) && (old < idx)) idx--; //adjust idx
      }
    
      // now insert objects at new location
      content.replace(idx, 0, objects) ;
      content.endPropertyChanges(); // restart notifications
      
      // make the op into its actual value
      op = SC.DRAG_MOVE ;
    }

    return op; 
  },
  
  /**
    Default delegate method implementation, returns YES if canReorderContent
    is also true.
  */
  collectionViewShouldBeginDrag: function(view) {
    return this.get('canReorderContent') ;
  },
  
  concludeDragOperation: function(op, drag) {
    this.hideInsertionPoint() ;
    this._lastInsertionIndex = null ;
  },

  /** 
    The insertion orientation.  This is used to determine which
    dimension we should pay attention to when determining insertion point for
    a mouse click.
    
    {{{
      SC.HORIZONTAL_ORIENTATION: look at the X dimension only
      SC.VERTICAL_ORIENTATION: look at the Y dimension only
    }}}
  */
  insertionOrientation: SC.HORIZONTAL_ORIENTATION,
  
  /**
    Get the preferred insertion point for the given location, including 
    an insertion preference of before or after the named index.
    
    You can implement this method in a subclass if you like to perform a 
    more efficient check.  The default implementation will loop through the 
    item views looking for the first view to "switch sides" in the orientation 
    you specify.

    This method should return an array with two values.  The first value is
    the insertion point index and the second value is the drop operation,
    which should be one of SC.DROP_BEFORE or SC.DROP_ON. 
    
    The preferred drop operation passed in should be used as a hint as to 
    the type of operation the drag and drop could would prefer to receive.
    If the dropOperaiton is SC.DROP_ON, then you should return a DROP_ON
    mode if possible.  Otherwise, you should never return DROP_ON.
    
    For compatibility, you can also return just the insertion index.  If you
    do this, then the collction view will assume the drop operation is 
    SC.DROP_BEFORE.
    
    If an insertion is NOT allowed, you should return -1 as the insertion 
    point.  In this case, the drop operation will be ignored.
    
    @param loc {Point} the mouse location.
    @param dropOperation {DropOp} the preferred drop operation.
    @returns {Array} [proposed drop index, drop operation] 
  */
  insertionIndexForLocation: function(loc, dropOperation) {  
    var content = this.get('content') ;
    var f, itemView, curSide, lastSide = null ;
    var orient = this.get('insertionOrientation') ;
    var ret=  null ;
    for(var idx=0; ((ret == null) && (idx<content.length)); idx++) {
      itemView = this.itemViewForContent(content.objectAt(idx));
      f = this.convertFrameFromView(itemView.get('frame'), itemView) ;
      
      // if we are a horizontal orientation, look for the first item that 
      // will "switch sides" on the x path an the maxY is greater than Y.
      // This assumes you will flow top to bottom, but it should work if you
      // flow LTR or RTL.
      if (orient == SC.HORIZONTAL_ORIENTATION) {
        if (SC.maxY(f) > loc.y) {
          curSide = (SC.maxX(f) < loc.x) ? -1 : 1 ;
        } else curSide = null ;
        
      // if we are a vertical orientation, look for the first item that
      // will "swithc sides" on the y path and the maxX is greater than X.
      // This assumes you will flow LTR, but it should work if you flow
      // bottom to top or top to bottom.
      } else {
        if (SC.minX(f) < loc.x) {
          curSide = (SC.maxY(f) < loc.y) ? -1 : 1 ;
        } else curSide = null ;
      } 
      
      // if we "switched" sides then return this item view.
      if (curSide !== null) {
        
        // OK, we found an item view, while we have this data, decide if
        // we should insert before or after the view
        if ((lastSide !== null) && (curSide != lastSide)) {
          ret = idx ;
          if (orient == SC.HORIZONTAL_ORIENTATION) {
            if (SC.midX(f) < loc.x) ret++ ;
          } else {
            if (SC.midY(f) < loc.y) ret++ ;
          }
        }
        lastSide =curSide ;
      }
    }
    
    // Handle some edge cases
    if ((ret == null) || (ret < 0)) ret = 0 ;
    if (ret > content.length) ret = content.length ;
    
    // Done. Phew.  Return.
    return ret;
  },
  
  /** 
    Override to show the insertion point during a drag.
    
    Called during a drag to show the insertion point.  Passed value is the
    item view that you should display the insertion point before.  If the
    passed value is null, then you should show the insertion point AFTER that
    last item view returned by the itemViews property.
    
    Once this method is called, you are guaranteed to also recieve a call to
    hideInsertionPoint() at some point in the future.
    
    The default implementation of this method does nothing.
    
    @param itemView {SC.View} view the insertion point should appear directly before. If null, show insertion point at end.
    @param dropOperation {Number} the drop operation.  will be SC.DROP_BEFORE or SC.DROP_ON
    
    @returns {void}
  */
  showInsertionPoint: function(itemView, dropOperation) {
    return (dropOperation === SC.DROP_BEFORE) ? this.showInsertionPointBefore(itemView) : this.hideInsertionPoint() ;
  },
  
  /**
    @deprecated
    
    Show the insertion point during a drag before the named item view.
    
    This method has been deprecated in favor of the more generic 
    showInsertionPoint() which can be used to show drops occurring both on
    and before an itemView.  If you do not implement showInsertionPoint() 
    yourself, the default implementation will call this method whenever the
    drop operation is SC.DROP_BEFORE.
    
    @param itemView {SC.View} the item view to show before.
    @returns {void}
  */
  showInsertionPointBefore: function(itemView) {},
  
  /**
    Override to hide the insertion point when a drag ends.
    
    Called during a drag to hide the insertion point.  This will be called 
    when the user exits the view, cancels the drag or completes the drag.  It 
    will not be called when the insertion point changes during a drag.
    
    You should expect to receive one or more calls to 
    showInsertionPointBefore() during a drag followed by at least one call to 
    this method at the end.  Your method should not raise an error if it is 
    called more than once.
    
    @returns {void}
  */
  hideInsertionPoint: function() {},

  /**
    Override this method to provide your own ghost image for a drag.  
    
    Note that the only purpose of this view is to render a visible drag 
    element.  It is not critical that you make this element bindable, etc.
    
    @param dragContent {Array} Array of content objects that will be used in 
     the drag.
  */
  ghostViewFor: function(dragContent) {
    var view = SC.View.create() ;
    view.setStyle({ position: 'absolute', overflow: 'hidden' });
    
    var viewFrame = this.convertFrameToView(this.get('frame'), null) ;
    view.set('frame', viewFrame) ;
    
    var idx = dragContent.length ;
    var maxX = 0; var maxY = 0 ; var minX =100000; var minY = 100000 ;
    
    while(--idx >= 0) {
      var itemView = this.itemViewForContent(dragContent[idx]) ;
      if (!itemView) continue ;

      var f = itemView.get('frame') ;
      f = this.convertFrameFromView(f, itemView) ;
      
      var dom = itemView.rootElement ;
      if (!dom) continue ;
      
      // save the maxX & maxY.  This will be used to trim the size 
      // of the ghost view later.
      if (SC.maxX(f) > maxX) maxX = SC.maxX(f) ;
      if (SC.maxY(f) > maxY) maxY = SC.maxY(f) ;
      if (SC.minX(f) < minX) minX = SC.minX(f) ;
      if (SC.minY(f) < minY) minY = SC.minY(f) ;

      // Clone the contents of this node.  We should probably apply the 
      // computed style to the cloned nodes in order to make sure they match 
      // even if the CSS styles do not match.  Make sure the items are 
      // properly positioned.
      dom = dom.cloneNode(true) ;

      Element.setStyle(dom, { position: "absolute", left: "%@px".fmt(f.x), top: "%@px".fmt(f.y), width: "%@px".fmt(f.width), height: "%@px".fmt(f.height) }) ;
      view.rootElement.appendChild(dom) ;
    }

    // Now we have a view, create another view that will wrap the other view 
    // and position it inside.
    var wrapper = SC.View.create() ;
    wrapper.setStyle({ position: 'absolute', overflow: 'hidden' }) ;
    wrapper.set('frame', { 
      x: viewFrame.x+minX, y: viewFrame.y+minY, 
      width: (maxX-minX+1), height: (maxY-minY+1) 
    }) ;
    wrapper.appendChild(view) ;
    view.set('frame', { x: 0-minX, y: 0-minY }) ;
    return wrapper ;
  },
  
  // ......................................
  // INTERNAL
  //
  
  init: function() {

    // Initialize internal hashes and arrays.  Normally the best approach to this 
    // is to initialize a property only when it is used.  However, these properties
    // are critical to layout and therefore will always be needed so it is faster
    // to do it once here.
    this._itemViewsByContent= {};
    this._groupViewsByValue= {};
    this._groupViewCounts= {};
    this._zombieGroupViews= {};
    this._itemViewsByGuid = {} ;

    this._itemViewPool= [];
    this._groupViewPool= [];

    arguments.callee.base.apply(this, arguments) ;
    this._dropTargetObserver();
  },

  // Perform the action.  Supports legacy behavior as well as newer style
  // action dispatch.
  _action: function(view, evt) {
    
    var action = this.get('action');
    var target = this.get('target') || null;
    if (action) {
      // if the action is a function, just call it
      if ($type(action) == T_FUNCTION) return this.action(view, evt) ;
      
      // otherwise, use the new sendAction style
      SC.app.sendAction(action, target, this) ;
      
    // if no action is specified, then trigger the support action,
    // if supported.
    } else if (!view) {
      return ; // nothing to do
      
    // if the target view has its own internal action handler,
    // trigger that.
    } else if ($type(view._action) == T_FUNCTION) {
      return view._action(evt) ;
      
    // otherwise call the action method to support older styles.
    } else if ($type(view.action) == T_FUNCTION) {
      return view.action(evt) ;
    }
  },

  /** Add/remove from drop targets as needed. */
  _dropTargetObserver: function() {
    var canDrop = this.get('canReorderContent') || this.get('isDropTarget') ;
    if (canDrop) {
      SC.Drag.addDropTarget(this) ;
    } else {
      SC.Drag.removeDropTarget(this) ;
    }
  }.observes('canReorderContent', 'isDropTarget'),

  /** @private
    Whenever content changes, update children and also start observing
    new [] property.
  */
  _contentObserver: function() {
    var content = this.get('content') ;
    if (SC.isEqual(content, this._content)) return ; // nothing to do

    if (!this._boundContentPropertyObserver) {
      this._boundContentPropertyObserver = this._contentPropertyObserver.bind(this) ;
    }
    var func = this._boundContentPropertyObserver ;

    // remove old observer, add new observer, and trigger content property change
    if (this._content) this._content.removeObserver('[]', func) ;
    if (content) content.addObserver('[]', func) ;
    this._content = content; //cache
    this._contentPropertyRevision = null ;
    
    var rev = (content) ? content.propertyRevision : -1 ;
    this._contentPropertyObserver(this, '[]', content, rev) ; 
  }.observes('content'),
  
  /** @private
    Whenever the selection changes, update the itemViews.
  */
  _selectionObserver: function() {
    var sel = this.get('selection') ;
    if (SC.isEqual(sel, this._selection)) return ; // nothing to do

    if (!this._boundSelectionPropertyObserver) {
      this._boundSelectionPropertyObserver = this._selectionPropertyObserver.bind(this) ;
    }
    var func = this._boundSelectionPropertyObserver ;
    
    if (this._selection) this._selection.removeObserver('[]', func) ;
    if (sel) sel.addObserver('[]', func) ;
    this._selection = sel ;
    this._selectionPropertyRevision = null ;
    var propertyRevision = (sel) ? sel.propertyRevision : null;
    this._selectionPropertyObserver(this, '[]', sel, propertyRevision) ;
  }.observes('selection'),
  
  // called on content change *and* content.[] change...
  // update children if this is a new propertyRevision
  //
  // UPDATE:
  // -- recheck all item views, add/remove children as needed
  // -- update layout on all item views.
  // -- optional: determine the first item view that does not match.
  //
  _contentPropertyObserver: function(target, key, value, rev) {    
    if (!this._updatingContent && (!rev || (rev != this._contentPropertyRevision))) {
      this._contentPropertyRevision = rev ;
      this._updatingContent = true ;
      this._hasChildren = false ;
      this.updateChildren(true) ;
      this._updatingContent = false ;
    }
  },
  
  // called on selection change and selection.[] change...
  // update selection states if this is a new propertyRevision
  _selectionPropertyObserver: function(target, key, value, rev) {
    if (!this._updatingSel && (!rev || (rev != this._selectionPropertyRevision))) {
      this._selectionPropertyRevision = rev ;
      this._updatingSel = true ;
      this._selectionHash = null ; // flush cache
      this.updateSelectionStates() ;
      this._updatingSel = false ;
    }
  },

  // If isVisibleInWindow status changes, updateChildren if we are dirty.
  _isVisibleInWindowObserver: function() {
    if (this.get('isDirty')) this.updateChildren() ;
  }.observes('isVisibleInWindow'),
  
  // ======================================================================
  // DEPRECATED APIS (Still available for compatibility)
  
  /** @private 
    If set to false, this method will prevent you from deselecting all of
    the items in your view.  This is better implemented using a controller
    that prohibits empty selection.
  */
  allowDeselectAll: true,

  /** @private */
  itemExistsInCollection: function( view ) { return this.hasItemView(view); },
  
  /** @private */
  viewForContentRecord: function(rec) { return this.itemViewForContent(rec); }
  
  
}) ;




/* End ------------------------------------------------------- views/collection/collection.js*/

/* Start ----------------------------------------------------- foundation/undo_manager.js*/

// ==========================================================================
// SproutCore -- JavaScript Application Framework
// copyright 2006-2008, Sprout Systems, Inc. and contributors.
// ==========================================================================

require('core');

/**
  @class
  
  This is a simple undo manager.  To use this UndoManager, all you need to
  do is to make sure that you register a function with this manager to undo
  every change you make.  You can then invoke the undo/redo methods to do it.
  
  h4. USING THE UNDOMANAGER
  
  Typically you create an undo manager inside on of your controllers.  Then,
  whenever you are about to perform an action on your model object, all you
  need to do is to register a function with the undo manager that can undo 
  whatever  you just did.
  
  Besure the undo function you register also saves undo functions.  This makes
  redo possible.
  
  More docs TBD.
  
  @extends SC.Object
*/
SC.UndoManager = SC.Object.extend(
/** @scope SC.UndoManager.prototype */
{

  /** 
    (Property) Name of the next undo action name.  
  
    Use this property to build your Undo menu name.
    
  */
  undoActionName: function() { 
    return (this.undoStack) ? this.undoStack.name : null ;
  }.property('undoStack'),
  
  /** 
    (Property) Name of the next return action name.  
  
    Use this property to build your Redo menu name.
    
  */
  redoActionName: function() { 
    return (this.redoStack) ? this.redoStack.name : null ;
  }.property('redoStack'),

  /** 
    True if there is an undo action on the stack.
    
    Use to validate your menu item.
  */
  canUndo: function() { 
    return this.undoStack != null; 
  }.property('undoStack'),
  
  /** 
    True if there is an redo action on the stack.
    
    Use to validate your menu item.
  */
  canRedo: function() { 
    return this.redoStack != null; 
  }.property('redoStack'),
  
  /**  
    Tries to undo the last action.  
  
    Returns true if succeeded.  Fails if an undo group is currently open.
  */
  undo: function() { this._undoOrRedo('undoStack','isUndoing'); },
  
  /**  
    Tries to redo the last action.  
  
    Returns true if succeeded.  Fails if an undo group is currently open.
  */
  redo: function() { this._undoOrRedo('redoStack','isRedoing'); },
  
  /**
    True if the manager is currently undoing events. 
  */
  isUndoing: false, 
  
  /**
    True if the manager is currently redoing events.
  */
  isRedoing: false, 
  
  /** @private */
  groupingLevel: 0,
  
  // --------------------------------
  // SIMPLE REGISTRATION
  //
  // These are the core method to register undo/redo events.
  
  /**
    This is how you save new undo events.
    
    @param {Function} func A prebound function to be invoked when the undo executes.
    @param {String} [name] An optional name for the undo.  If you are using 
      groups, this is not necessary.
  */
  registerUndo: function(func, name) {
    this.beginUndoGroup(name) ;
    this._activeGroup.actions.push(func) ;
    this.endUndoGroup(name) ;
  },

  /**
    Begins a new undo groups

    Whenver you start an action that you expect to need to bundle under a single
    undo action in the menu, you should begin an undo group.  This way any
    undo actions registered by other parts of the application will be
    automatically bundled into this one action.
    
    When you are finished performing the action, balance this with a call to
    endUndoGroup().
  */
  beginUndoGroup: function(name) {
    // is a group already active? Just increment the counter.
    if (this._activeGroup) {
      this.groupingLevel++ ;
      
    // otherwise, create a new active group.  
    } else {
      var stack = (this.isUndoing) ? 'redoStack' : 'undoStack' ;
      this._activeGroup = { name: name, actions: [], prev: this.get(stack) } ;
      this.set(stack, this._activeGroup) ;
      this.groupingLevel = 1 ;
    }
  },
 
  /** end the undo group.  see beginUndoGroup() */
  endUndoGroup: function(name) {
    // if more than one groups are active, just decrement the counter.
    if (!this._activeGroup) raise("endUndoGroup() called outside group.") ;
    if (this.groupingLevel > 1) {
      this.groupingLevel-- ;
      
    // otherwise, close out the current group.
    } else {
      this._activeGroup = null ; this.groupingLevel = 0 ;
    }
    this.propertyDidChange((this.isUndoing) ? 'redoStack' : 'undoStack') ;
  },

  /**
    Change the name of the current undo group.  
  
    Normally you don't want to do this as it will effect the whole group.
  */
  setActionName: function(name) {
    if (!this._activeGroup) raise("setActionName() called outside group.") ;
    this._activeGroup.name = name ;
  },
  
  // --------------------------------
  // PRIVATE
  //
  _activeGroup: null, undoStack: null, redoStack: null, 
  _undoOrRedo: function(stack,state) {
    if (this._activeGroup) return false ;
    if (this.get(stack) == null) return true; // noting to do.

    this.set(state, true) ;
    var group = this.get(stack) ;
    this.set(stack, group.prev) ;
    var action ;

    var useGroup = group.actions.length > 1; 
    if (useGroup) this.beginUndoGroup(group.name) ;
    while(action = group.actions.pop()) { action(); }
    if (useGroup) this.endUndoGroup(group.name) ;
    
    this.set(state, false) ;
  }
  
}) ;


/* End ------------------------------------------------------- foundation/undo_manager.js*/

/* Start ----------------------------------------------------- mixins/scrollable.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

/**
  @namespace

  Any views you implement that are scrollable should include this mixin to
  provide basic support for scrolling actions.  You can also override any 
  of these methods as needed for your own specific behaviors.
  
  Often times instead of adding SC.Scrollable to your view, you should
  place your view inside of an SC.ScrollView.  See that class for more 
  info.
  
  Note that isScrollable must always be true.

*/
SC.Scrollable = {
  
  /** Informs the view system that the receiver is scrollable.
  
    Must always be true.
  */
  isScrollable: true,
  
  /** 
    Amount to scroll one vertical line.
  
    Used by the default implementation of scrollDownLine() and scrollUpLine().  Defaults
    to 20px.
  */
  verticalLineScroll: 20,
  
  /**
    Amount to scroll one horizontal line.
  
    Used by the default implementation of scrollLeftLine() and scrollRightLine(). Defaults
    to 20px.
  */
  horizontalLineScroll: 20,
  
  /**
    Amount to scroll one vertical page.
    
    Used by the default implementation of scrollUpPage() and scrollDownPage(). Defaults to
    current innerFrame height.
  */
  verticalPageScroll: function() {
    return this.get('innerFrame').height ;
  }.property('innerFrame'),
  
  /**
    Amount to scroll one horizontal page.
    
    Used by the default implementation of scrollLeftPage() and scrollRightPage().  Defaults
    to current innerFrame width.
  */
  horizontalPageScroll: function() {
    return this.get('innerFrame').width ;  
  }.property('innerFrame'),
  
  /**
    Returns true if the receiver has enough vertical content to require 
    scrolling.
    
    If you do not want to allow vertical scrolling, override this to be false 
    and set the appropriate CSS.
    
  */
  hasVerticalScroller: function() {
    return this.get('scrollFrame').height > this.get('innerFrame').height ;
  }.property('scrollFrame'),
  
  /**
    Returns true if the receiver has enough horizontal content to require 
    scrolling.
    
    If you do not want to allow horizontal scrolling, override this to be 
    false and set the appropriate CSS.
    
  */
  hasHorizontalScroller: function() {
    return this.get('scrollFrame').width > this.get('innerFrame').width ;
  }.property('scrollFrame'),

  /**
    Scrolls the receiver in the horizontal and vertical directions by the 
    amount specified, if allowed.
    
    @param {Point} amount the amount to scroll.  Must include x, y or both
    @returns {Point} the actual amount scrolled.
  */
  scrollBy: function(amount) {
    var sf = this.get('scrollFrame') ;
    var f = this.get('innerFrame') ;

    if (!this.get('hasVerticalScroller')) amount.y = 0 ;
    if (sf.height <= f.height) amount.y = 0 ;
    
    if (!this.get('hasHorizontalScroller')) amount.x = 0 ; 
    if (sf.width <= f.width) amount.x = 0 ;

    // compute new sf
    var newSf = { x: sf.x - (amount.x || 0), y: sf.y - (amount.y || 0) } ;
    this.set('scrollFrame', newSf) ;
    newSf = this.get('scrollFrame') ;
    
    return { x: newSf.x - sf.x, y: newSf.y - sf.y }; 
  },

  /**
    Scrolls the receiver to the specified x,y coordinate
  */
  scrollTo: function(x,y) {
    this.set('scrollFrame', { x: 0-x, y: 0-y }) ;  
  },
  
  /**
    Scroll the view to make the passed frame visible.
    
    Frame must be relative to the receiver's offsetParent.
    
    @param {SC.View} view the view you want to make visible
  */
  scrollToVisible: function(view) {

    // get frames and convert them to proper offsets
    var f = this.get('innerFrame') ;
    var sf = this.get('scrollFrame') ;
    
    // frame of the view, relative to the top of the scroll frame
    var vf = this.convertFrameFromView(view.get('frame'), view) ;
    vf.x -= (f.x + sf.x); vf.y -= (f.y + sf.y);
    
    // first visible origin
    var vo = { 
      x: 0-sf.x, 
      y: 0-sf.y, 
      width: f.width, 
      height: f.height 
    };

    // if top edge is not visible, shift origin
    vo.y -= Math.max(0, SC.minY(vo) - SC.minY(vf)) ;
    vo.x -= Math.max(0, SC.minX(vo) - SC.minX(vf)) ;

    // if bottom edge is not visible, shift origin
    vo.y += Math.max(0, SC.maxY(vf) - SC.maxY(vo)) ;
    vo.x += Math.max(0, SC.maxX(vf) - SC.maxX(vo)) ;

    // scroll to that origin.
    this.scrollTo(vo.x, vo.y) ;
  },
  
  /**
    Scrolls the receiver down one line if allowed.
    
    @param {Number} lines number of lines to scroll
    @returns {Number} the amount actually scrolled.
  */
  scrollDownLine: function(lines) {
    if (lines === undefined) lines = 1 ;
    return this.scrollBy({ y: this.get('verticalLineScroll')*lines }).y ;
  },

  /**
    Scrolls the receiver down up line if allowed.
    
    @param {Number} lines number of lines to scroll
    @returns {Number} the amount actually scrolled.
  */
  scrollUpLine: function(lines) {
    if (lines === undefined) lines = 1 ;
    return 0-this.scrollBy({ y: 0-this.get('verticalLineScroll')*lines }).y ;
  },

  /**
    Scrolls the receiver right one line if allowed.
    
    @param {Number} lines number of lines to scroll
    @returns {Number} the amount actually scrolled.
  */
  scrollRightLine: function(lines) {
    if (lines === undefined) lines = 1 ;
    return this.scrollTo({ y: this.get('horizontalLineScroll')*lines }).x ;
  },

  /**
    Scrolls the receiver left one line if allowed.
    
    @param {Number} lines number of lines to scroll
    @returns {Number} the amount actually scrolled.
  */
  scrollLeftLine: function(lines) {
    if (lines === undefined) lines = 1 ;
    return 0-this.scrollTo({ y: 0-this.get('horizontalLineScroll')*lines }).x ;
  },

  /**
    Scrolls the receiver down one page if allowed.
    
    @param {Number} pages number of pages to scroll
    @returns {Number} the amount actually scrolled.
  */
  scrollDownPage: function(pages) {
    if (pages === undefined) pages = 1 ;
    return this.scrollBy({ y: this.get('verticalPageScroll')*pages }).y ;
  },

  /**
    Scrolls the receiver down up page if allowed.
    
    @param {Number} pages number of pages to scroll
    @returns {Number} the amount actually scrolled.
  */
  scrollUpPage: function(pages) {
    if (pages === undefined) pages = 1 ;
    return 0-this.scrollBy({ y: 0-this.get('verticalPageScroll')*pages }).y ;
  },

  /**
    Scrolls the receiver right one page if allowed.
    
    @param {Number} pages number of pages to scroll
    @returns {Number} the amount actually scrolled.
  */
  scrollRightPage: function(pages) {
    if (pages === undefined) pages = 1 ;
    return this.scrollTo({ y: this.get('horizontalPageScroll')*pages }).x ;
  },

  /**
    Scrolls the receiver left one page if allowed.
    
    @param {Number} pages number of pages to scroll
    @returns {Number} the amount actually scrolled.
  */
  scrollLeftPage: function(pages) {
    if (pages === undefined) pages = 1 ;
    return 0-this.scrollTo({ y: 0-this.get('horizontalPageScroll')*pages }).x ;
  }
  
} ;



/* End ------------------------------------------------------- mixins/scrollable.js*/

/* Start ----------------------------------------------------- views/button/button.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view') ;
require('views/label') ;
require('mixins/control') ;

// Constants
SC.TOGGLE_BEHAVIOR = 'toggle';
SC.PUSH_BEHAVIOR =   'push';
SC.TOGGLE_ON_BEHAVIOR = "on";
SC.TOGGLE_OFF_BEHAVIOR = "off" ;  

/** @class

  A button handles simple link functions.  It can be set to a selected,
  enabled or disabled state.
  
  @extends SC.View
  @extends SC.Control
  @author Charles Jolley
  @version 1.0
  
*/
SC.ButtonView = SC.View.extend(SC.Control,
/** @scope SC.ButtonView.prototype */ {
  
  emptyElement: '<a href="javascript:;" class="sc-button-view regular"><span class="button-inner"><span class="label"></span></span></a>',
  
  // PROPERTIES
  
  /**
    Used to automatically update the state of the button view for toggle style
    buttons.
    
    for toggle style buttons, you can set the value and it will be used to
    automatically update the state of the button view.  The value will also
    change as the user selects to deselects.  The button will make its best
    effort to convert this value into a reasonable selection state:
  
    null, false, 0 -> isSelected = false
    any other single value -> isSelected = true
    array -> if all values are the same state: that state.  otherwise MIXED.
    
    @type Object

  */  
  value: false,
  
  /**
    Value of a selected toggle button.
  
    for a toggle button, set this to any object value you want.  The button
    will be selected if the value property equals the targetValue.  If the
    value is an array of multiple items that contains the targetValue, then
    the button will be set to a mixed state.

    default is false
    
    @type Object
  */
  toggleOnValue: true,

  /**
    Value of an unselected toggle button.
  
    For a toggle button, set this to any object value you want.  When the
    user toggle's the button off, the value of the button will be set to this
    value.
  
    default is false 
  
    @type Object

  */
  toggleOffValue: false,
  
  /**
    optionally set this to the theme you want this button to have.  
    
    This is used to determine the type of button this is.  You generally 
    should set a class name on the HTML with the same value to allow CSS 
    styling.
    
    The default SproutCore theme supports "regular", "checkbox", and "radio"
  */
  theme: 'regular',
  
  /**
    Optionally set the behavioral mode of this button.  
  
    Possible values are:

    {{{
     SC.PUSH_BEHAVIOR: ('push')
       Pressing the button will trigger an action tied to the button. Does
       not change the value of the button.
  
     SC.TOGGLE_BEHAVIOR: ('toggle')
       Pressing the button will invert the current value of the button. If
       the button has a mixed value, it will be set to true.
  
     SC.TOGGLE_ON_BEHAVIOR: ('on')
       Pressing the button will set the current state to true no matter the
       previous value.
  
     SC.TOGGLE_OFF_BEHAVIOR: ('off')
       Pressing the button will set the current state to false no matter the
       previous value.
    }}}
  
  */  
  buttonBehavior: SC.PUSH_BEHAVIOR,
  
  /**
    If NO the button will be disabled. 
    
    @type Bool
  */  
  isEnabled: YES,
  
  /**
    button's selection state.  Returns YES, NO, or SC.MIXED_STATE
  */
  isSelected: NO,

  /**
    If YES, then this button will be triggered when you hit return.
    
    This is the same as setting the keyEquivalent to 'return'.  This will also
    apply the "def" classname to the button.
  */
  isDefault: NO,
  isDefaultBindingDefault: SC.Binding.OneWayBool,
  
  /**
    If YES, then this button will be triggered when you hit escape.
    
    This is the same as setting the keyEquivalent to 'escape'.
  */  
  isCancel: NO,
  isCancelBindingDefault: SC.Binding.OneWayBool,
  
  /**
    If YES, then the title will be localized.
  */
  localize: NO,
  
  /**
    The selector path to the element that contains the button title. 
    
    This property is only used if you try to get or set the title property.
  */
  titleSelector: '.label',
  
  /**
    The button title.
    
    This property is observable and bindable.
    
    @field {String}
  */
  title: function(key, value) {
    
    // set the value of the label text.  Possibly localize and set innerHTML.
    if (value !== undefined) {
      if (this._title != value) {
        var text = this._title = value ;
        var lsel = this.get('titleSelector') ;
        var el = (lsel) ? this.$sel(lsel) : this.rootElement ;

        if (this.get('localize')) text = text.loc() ;
        el.innerHTML = text ;
      }
    }

    // lazily fetch the label text.
    if (!this._title) {
      var el = this.$sel(this.get('titleSelector')) ;
      this._title = (el) ? el.innerHTML : '' ;
    }
    return this._title ;
  }.property(),
  
  /**
    The button href value.
      
    This property is observable and bindable. Can be used to create localized button href values.
  */
  href: function(key, value) {
    var el = this.rootElement;
    if (value !== undefined) {
      if (el) {
        el.setAttribute('href', value);
      }
    }
    return (el) ? el.getAttribute('href') : null;
  }.property(),

  /**
    The name of the action you want triggered when the button is pressed.  
    
    This property is used in conjunction with the target property to execute
    a method when a regular button is pressed.  These properties are not 
    relevant when the button is used in toggle mode.
    
    If you do not set a target, then pressing a button will cause the
    responder chain to search for a view that implements the action you name
    here.  If you set a target, then the button will try to call the method
    on the target itself.
    
    For legacy support, you can also set the action property to a function.  
    Doing so will cause the function itself to be called when the button is
    clicked.  It is generally better to use the target/action approach and 
    to implement your code in a controller of some type.
    
    @type String
  */
  action: null,
  
  /**
    The target object to invoke the action on when the button is pressed.
    
    If you set this target, the action will be called on the target object
    directly when the button is clicked.  If you leave this property set to
    null, then the button will search the responder chain for a view that 
    implements the action when the button is pressed instead.
    
    @type Object
  */
  target: null,

  /**
    The key equivalent that should trigger this button on the page.
  */
  keyEquivalent: null,
  
  /** @private {String} used to store a previously defined key equiv */
  _defaultKeyEquivalent: null,
  
  performKeyEquivalent: function( keystring, evt )
  {
    if (!this.get('isEnabled')) return false;
    var keyEquivalent = this.get('keyEquivalent');
    if (keyEquivalent && (keyEquivalent == keystring))
    {
      // button has defined a keyEquivalent and it matches!
      // if triggering succeeded, true will be returned and the operation will be handeled 
      // (i.e performKeyEquivalent will cease crawling the view tree)
      return this.triggerAction(evt);
    }
    return false;
  },
  
  /**
    fakes a click... evt is optional.  
    
    Temporarily highlights the button to show that it is being triggered.  
    Does nothing if the button is disabled. 
    
    @returns {bool} success/failure of the request
  */  
  triggerAction: function(evt) {  
    if (!this.get('isEnabled')) return false;
    this.setClassName('active', true);
    this.didTriggerAction();
    this._action(evt);
    this.invokeLater('setClassName', 200, 'active', false) ;
    return true;
  },
  
  didTriggerAction: function() {},

  // ................................................................
  // INTERNAL SUPPORT
  
  /** @private */
  init: function() {
    arguments.callee.base.call(this) ;
    
    //cache the key equivalent
    if(this.get("keyEquivalent")) this._defaultKeyEquivalent = this.get("keyEquivalent"); 
    // setup initial CSS clases
    this._isDefaultOrCancelObserver() ;
    
    // If we need to localze, handle it...
    var el ;
    var sel = this.get('titleSelector') ;
    if (this.get('localize') && sel && (el = this.$sel(sel))) {
      this._title = (el.innerHTML || '').strip() ;
      el.innerHTML = this._title.loc() ;
    }
  },

  // determines the target selected state
  _selectedStateFromValue: function(value) {
    var targetValue = this.get('toggleOnValue') ;
    var state ;
    
    if ($type(value) == T_ARRAY) {
      if (value.length == 1) {
        state = (value[0] == targetValue) ;
      } else {
        state = (value.indexOf(targetValue) >= 0) ? SC.MIXED_STATE : false ;
      }
    } else {
      state = (value == targetValue) ;
    }
    return state ;
  },
  
  /** @private */
  propertyObserver: function(observing,target,key,value) {
    if (target != this) return ;

    // handle changes to the value
    switch(key) {

      // determine the new selection state.
      case 'value':
        value = this.get('value') ;
        if (value == this._value) return ; // process value one time.
        this._value = value ;
      
        // set the new selected state if it does not match
        var state = this._selectedStateFromValue(value) ;
        this.setIfChanged('isSelected', state) ;
        break ;

      // forward to value if needed.
      case 'isSelected':
        var newState = this.get('isSelected') ;
        var curState = this._selectedStateFromValue(this.get('value')) ;
        if (curState != newState) {
          var valueKey = (newState) ? 'toggleOnValue' : 'toggleOffValue' ;
          this.set('value', this.get(valueKey)) ;
        }
        break ;
        
      // otherwise do nothing
      default:
        break ;
    }
  },
  
  _isDefaultOrCancelObserver: function() {
    var isDef = !!this.get('isDefault') ;
    var isCancel = !isDef && this.get('isCancel') ;
    
    if(this.didChangeFor('defaultCancelChanged','isDefault','isCancel')) {
      this.setClassName('def', isDef) ;
      if (isDef) {
        this.setIfChanged('keyEquivalent', 'return');
      } 
      else if (isCancel)
      {
        this.setIfChanged('keyEquivalent', 'escape') ;
      }
      else
      {
        //restore the default key equivalent
        this.set("keyEquivalent",this._defaultKeyEquivalent);
      }
    }
      
  }.observes('isDefault', 'isCancel'),
    
    
  isMouseDown: false, 
    
  // on mouse down, set active only if enabled.  
  /** @private */
  mouseDown: function(evt) {
    this.setClassName('active',this.get('isEnabled')) ;
    this._isMouseDown = true;
    return true ;
  },
  
  // remove the active class on mouse down as well
  /** @private */
  
  mouseOut: function(evt)
  {
    this.setClassName('active', false);
    return true;
  },
  
  // add the active class name if the mouse is down
  // this covers a scenario where the user drags out and back on to a button
  mouseOver: function(evt)
  {
    this.setClassName('active', this._isMouseDown) ;
    return true;
  },
  
  // on mouse up, trigger the action only if we are enabled and the mouse
  // was released inside the view.
  /** @private */
  mouseUp: function(evt) {
    this.setClassName('active', false) ;
    this._isMouseDown = false;
    var tgt = Event.element(evt) ;
    var inside = false ;
    while(tgt && (tgt != this.rootElement)) tgt = tgt.parentNode;
    if (tgt == this.rootElement) inside = true ;
    
    if (inside && this.get('isEnabled')) this._action(evt) ;
    return true ;
  },

  // perform action depending on the behavior of the button.
  _action: function(evt) {
    switch(this.get('buttonBehavior')) {
      
    // When toggling, try to invert like values. i.e. 1 => 0, etc.
    case SC.TOGGLE_BEHAVIOR:
      var sel = this.get('isSelected') ;
      if (sel == true) {
        this.set('value', this.get('toggleOffValue')) ;
      } else {
        this.set('value', this.get('toggleOnValue')) ;
      }
      break ;
      
    // set value to on.  change 0 => 1.
    case SC.TOGGLE_ON_BEHAVIOR:
      this.set('value', this.get('toggleOnValue')) ;
      break ;
      
    // set the value to false. change 1 => 0
    case SC.TOGGLE_OFF_BEHAVIOR:
      this.set('value', this.get('toggleOffValue')) ;
      break ;
      
    // otherwise, just trigger an action if there is one.
    default:
      //if (this.action) this.action(evt);
      var action = this.get('action');
      var target = this.get('target') || null;
      if (action)
      {
        if (this._hasLegacyActionHandler()) {
          // old school... 
          this._triggerLegacyActionHandler(evt);
        } else {
          // newer action method + optional target syntax...
          SC.app.sendAction(action, target, this);
        }
      }
    }
  },
  
  /** @private */
  _hasLegacyActionHandler: function()
  {
    var action = this.get('action');
    if (action && ($type(action) == T_FUNCTION)) return true;
    if (action && ($type(action) == T_STRING) && (action.indexOf('.') != -1)) return true;
    return false;
  },

  /** @private */
  _triggerLegacyActionHandler: function( evt )
  {
    if (!this._hasLegacyActionHandler()) return false;
    
    var action = this.get('action');
    if ($type(action) == T_FUNCTION) this.action(evt);
    if ($type(action) == T_STRING)
    {
      eval("this.action = function(e) { return "+ action +"(this, e); };");
      this.action(evt);
    }
  }
  
}) ;


/* End ------------------------------------------------------- views/button/button.js*/

/* Start ----------------------------------------------------- views/button/disclosure.js*/

// ==========================================================================
// SC.CheckboxView
// ==========================================================================

require('views/button/button');

/** @class

  Disclosure triangle button.

  @extends SC.ButtonView
  @author    Charles Jolley 
  @version 1.0
*/
SC.DisclosureView = SC.ButtonView.extend(
/** @scope SC.DisclosureView.prototype */ {

  emptyElement: '<a href="javascript:;" class="sc-disclosure-view sc-button-view button disclosure"><img src="%@" class="button" /><span class="label"></span></a>'.fmt('/static/sproutcore/en/blank.gif'),
  
  buttonBehavior: SC.TOGGLE_BEHAVIOR,

  /**
    This is the value that will be set when the disclosure triangle is toggled
    open.
  */
  toggleOnValue: YES,
  
  /**
    The value that will be set when the disclosure triangle is toggled closed.
  */
  toggleOffValue: NO,

  valueBindingDefault: SC.Binding.Bool,
  
  init: function() {
    arguments.callee.base.apply(this,arguments) ;
    if (this.get('value') == this.get('toggleOnValue')) {
      this.set('isSelected', true) ;
    }
  }
  
}) ;


/* End ------------------------------------------------------- views/button/disclosure.js*/

/* Start ----------------------------------------------------- views/source_list_group.js*/

// ==========================================================================
// SC.ListItemView
// ==========================================================================

require('views/view') ;
require('mixins/delegate_support');
require('mixins/control') ;
require('views/button/disclosure');

/** @class

  Displays a group view in a source list.  Handles displaying a disclosure
  triangle which can be used to show/hide children.

  @extends SC.View
  @extends SC.DelegateSupport
  @author   Charles Jolley 
  @version 0.1
*/

SC.SourceListGroupView = SC.View.extend(SC.Control, SC.DelegateSupport, {
  
  emptyElement: ['<div class="sc-source-list-group">',
    '<a href="javascript:;" class="sc-source-list-label sc-disclosure-view sc-button-view button disclosure no-disclosure">',
    '<img src="%@" class="button" />'.fmt('/static/sproutcore/en/blank.gif'),
    '<span class="label"></span></a>',
  '</div>'].join(''),
  
  /**
    The group value to display for this group.
  */
  content: null,
  
  /**
    The current group visibility.  Used by the source list to determine 
    the layout size of the group.
  */
  isGroupVisible: YES,
  
  /** 
    YES if group is showing its titlebar.
    
    Group views will typically hide their header if the content is set to 
    null.  You can also override this method to always hide the header if 
    you want and the SourceListView will not leave room for it.
  */
  hasGroupTitle: YES,
  
  groupTitleKey: null,
  
  groupVisibleKey: null,
  
  contentPropertyDidChange: function(target, key) {
    var content = this.get('content') ;
    var labelView = this.outlet('labelView') ;
    
    // hide labelView if content is null.
    if (content == null) {
      labelView.setIfChanged('isVisible', NO) ;
      this.setIfChanged('hasGroupTitle', NO) ;
      return ;
    } else {
      labelView.setIfChanged('isVisible', YES) ;
      this.setIfChanged('hasGroupTitle', YES) ;
    }
    
   // set the title if that changed.
    var groupTitleKey = this.getDelegateProperty(this.displayDelegate, 'groupTitleKey') ;
    if ((key == '*') || (groupTitleKey && (key == groupTitleKey))) {
      var title = (content && content.get && groupTitleKey) ? content.get(groupTitleKey) : content;
      if (title != this._title) {
        this._title = title ;
        if (title) title = title.capitalize() ;
        labelView.set('title', title) ;
      }
    }
    
    // set the group visibility if changed
    var groupVisibleKey = this.getDelegateProperty(this.displayDelegate, 'groupVisibleKey') ;
    if ((key == '*') || (groupVisibleKey && (key == groupVisibileKey))) {

      if (groupVisibleKey) {
        
        labelView.removeClassName('no-disclosure') ;

        var isVisible = (content && content.get) ? !!content.get(groupVisibleKey) : YES ;
        if (isVisible != this.get('isGroupVisible')) {
          this.set('isGroupVisible', isVisible) ;
          labelView.set('value', isVisible) ;
        }
        
      } else labelView.addClassName('no-disclosure') ;
    }
  },
  
  // called when the user clicks on the disclosure triangle
  disclosureValueDidChange: function(newValue) {
    if (newValue == this.get('isGroupVisible')) return; // nothing to do
    
    // update group if necessary
    var group = this.get('content') ;
    var groupVisibleKey = this.getDelegateProperty(this.displayDelegate, 'groupVisibleKey') ;
    if (group && group.set && groupVisibleKey) {
      group.set(groupVisibleKey, newValue) ;
    }
    
    // update my own value and then update my collection view.
    this.set('isGroupVisible', newValue) ;
    if (this.owner && this.owner.updateChildren) this.owner.updateChildren(true) ;
    
  },
  
  /** @private */
  labelView: SC.DisclosureView.extend({
    
    /** 
      Always default to open disclosures.
    */
    value: YES,
    
    // if the disclosure value changes, call the owner's method.  Note
    // normally you would do this with a binding, but since this is a semi-
    // private class anyway, there is no reason to go to all that trouble.
    _valueObserver: function() {
      if (this.owner) this.owner.disclosureValueDidChange(this.get('value'));
    }.observes('value')
    
  }).outletFor('.sc-source-list-label:1:1')
  
}) ;


/* End ------------------------------------------------------- views/source_list_group.js*/

/* Start ----------------------------------------------------- views/image.js*/

// ========================================================================
// SproutCore
// copyright 2006-2008 Sprout Systems, Inc.
// ========================================================================

require('views/view') ;
require('mixins/control') ;

lc_cnt = 0 ;

SC.IMAGE_STATE_NONE = 'none';
SC.IMAGE_STATE_LOADING = 'loading';
SC.IMAGE_STATE_LOADED = 'loaded';
SC.IMAGE_STATE_FAILED = 'failed';

/**
  URL to a transparent GIF.  Used for spriting.
*/
SC.BLANK_IMAGE_URL = '/static/sproutcore/en/blank.gif';

/**
  @class

  Displays an image in the browser.  
  
  The ImageView can be used to efficiently display images in the browser.
  It includes a built in support for a number of features that can improve
  your page load time if you use a lot of images including a image loading
  queue and automatic support for CSS spriting.

  @extends SC.View
  @extends SC.Control
  @author Charles Jolley
*/
SC.ImageView = SC.View.extend(SC.Control, 
/** @scope SC.ImageView.prototype */ {
  
  /** Image views contain an img tag. */
  emptyElement: '<img src="%@" class="sc-image-view" />'.fmt(SC.BLANK_IMAGE_URL),
  
  /**
    Current load status of the image.
    
    This status changes as an image is loaded from the server.  If spriting
    is used, this will always be loaded.  Must be one of the following
    constants: SC.IMAGE_STATE_NONE, SC.IMAGE_STATE_LOADING, 
    SC.IMAGE_STATE_LOADED, SC.IMAGE_STATE_FAILED
  */
  status: SC.IMAGE_STATE_NONE,
  
  /**
    A url or CSS class name.
    
    This is the image you want the view to display.  It should be either a
    url or css class name.  You can also set the content and 
    contentValueKey properties to have this value extracted 
    automatically.
    
    If you want to use CSS spriting, set this value to a CSS class name.  If
    you need to use multiple class names to set your icon, separate them by
    spaces.
  */
  value: null,
  _value: null,

  /**
    Invoked whenever the content or value changes. (To be removed in 
    SproutCore 1.0)
    
    This method is no longer necessary since we have the standard SC.Control
    behavior.
    
    @deprecated
    @param {Object} content The content object.
    @returns {String} the URL or CSS class name
  */
  transform: function(content) { return content; },
  
  valueObserver: function() {
    
    // get the new URL.
    var value = this.get('value') ;
    
    // invoke the old transform method if it is defined
    if (this.transform !== SC.ImageView.prototype.transform) {
      var content = this.get('content') || '' ;
      value = this.transform(content) ;
    }
    
    // if the value has not changed, do nothing.
    if (value == this._value) return ;
    
    // if the old value was a class name, then we need to remove it.
    if (this._value && this._value.length>0 && !SC.ImageView.valueIsUrl(this._value)) {
      var classNames = this._value.split(' ') ;
      var idx = classNames.length ;
      while(--idx >= 0) { 
        this.removeClassName(classNames[idx]); 
      }
      this.removeClassName('sc-sprite') ;
    }
    this._value = value ;
    
    // if the new value is empty, just clear the img.
    if (!value || value.length == 0) {
      this.rootElement.src = SC.BLANK_IMAGE_URL;
      this.set('status', SC.IMAGE_STATE_NONE) ;
      this._imageUrl = null; //clear
      
    // if a new value was set that is a URL, load the image URL.
    } else if (SC.ImageView.valueIsUrl(value)) {
      this.beginPropertyChanges() ;
      this.set('status', SC.IMAGE_STATE_LOADING) ;
      this._imageUrl = value ; // save to verify later.
      SC.imageCache.loadImage(value, this, this._onLoadComplete) ;
      this.endPropertyChanges() ;
      
    // if the new is a CSS class name, set an empty image and add class name 
    } else {
      var classNames = value.split(' ');
      var idx = classNames.length ;
      while(--idx >= 0) this.addClassName(classNames[idx]) ;
      this.addClassName('sc-sprite') ;
      this.rootElement.src = SC.BLANK_IMAGE_URL ;
      this.set('status', SC.IMAGE_STATE_LOADED) ;
    }
  }.observes('value'),
  

  /** 
    Invoked once an image loads.  If an image has already been loaded,
    this method will be invoked immediately.
  */
  _onLoadComplete: function(url, status, img) {  
    
    // sometimes this method gets called later after the url has already
    // changed.  If this is the case, bail...
    if (url !== this._imageUrl) return ;
    
    this.beginPropertyChanges() ;
    this.set('imageWidth', parseInt(img.width,0)) ;
    this.set('imageHeight', parseInt(img.height,0)) ;
    this.set('status',status) ;
    this.endPropertyChanges() ;
    
    if (status ==